16655 text-install i18n issues
authorasano
Wed, 11 Aug 2010 15:34:19 +0900
changeset 859 e9ed0d564fad
parent 858 21440f082500
child 860 a1a3344ea203
16655 text-install i18n issues
usr/src/cmd/text-install/osol_install/text_install/Makefile
usr/src/cmd/text-install/osol_install/text_install/__init__.py
usr/src/cmd/text-install/osol_install/text_install/base_screen.py
usr/src/cmd/text-install/osol_install/text_install/disk_selection.py
usr/src/cmd/text-install/osol_install/text_install/disk_window.py
usr/src/cmd/text-install/osol_install/text_install/edit_field.py
usr/src/cmd/text-install/osol_install/text_install/fdisk_partitions.py
usr/src/cmd/text-install/osol_install/text_install/help_screen.py
usr/src/cmd/text-install/osol_install/text_install/i18n.py
usr/src/cmd/text-install/osol_install/text_install/inner_window.py
usr/src/cmd/text-install/osol_install/text_install/install_progress.py
usr/src/cmd/text-install/osol_install/text_install/log_viewer.py
usr/src/cmd/text-install/osol_install/text_install/main_window.py
usr/src/cmd/text-install/osol_install/text_install/network_type.py
usr/src/cmd/text-install/osol_install/text_install/partition_edit_screen.py
usr/src/cmd/text-install/osol_install/text_install/summary.py
usr/src/cmd/text-install/osol_install/text_install/test/test_i18n.py
usr/src/cmd/text-install/osol_install/text_install/text_install.py
usr/src/cmd/text-install/osol_install/text_install/users.py
usr/src/pkg/manifests/system-install-text-install.mf
--- a/usr/src/cmd/text-install/osol_install/text_install/Makefile	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/Makefile	Wed Aug 11 15:34:19 2010 +0900
@@ -45,6 +45,7 @@
 		error_window.py \
 		fdisk_partitions.py \
 		help_screen.py \
+		i18n.py \
 		inner_window.py \
 		install_progress.py \
 		install_status.py \
--- a/usr/src/cmd/text-install/osol_install/text_install/__init__.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/__init__.py	Wed Aug 11 15:34:19 2010 +0900
@@ -39,7 +39,7 @@
 from os import environ
 
 _ = gettext.translation("textinstall", "/usr/share/locale",
-                        fallback=True).gettext
+                        fallback=True).ugettext
 LOG_LOCATION_FINAL = "/var/sadm/system/logs/install_log"
 DEFAULT_LOG_LOCATION = "/tmp/install_log"
 DEFAULT_LOG_LEVEL = "info"
--- a/usr/src/cmd/text-install/osol_install/text_install/base_screen.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/base_screen.py	Wed Aug 11 15:34:19 2010 +0900
@@ -156,7 +156,7 @@
                     self.on_continue()
                 except UIMessage as msg:
                     self.main_win.screen_list.previous_screen()
-                    error_str = str(msg)
+                    error_str = unicode(msg)
                     if error_str:
                         self.main_win.error_line.display_err(error_str)
             elif next_screen is None:
--- a/usr/src/cmd/text-install/osol_install/text_install/disk_selection.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/disk_selection.py	Wed Aug 11 15:34:19 2010 +0900
@@ -40,6 +40,9 @@
 from osol_install.text_install.disk_window import DiskWindow, \
                                                   get_minimum_size, \
                                                   get_recommended_size
+from osol_install.text_install.i18n import fit_text_truncate, \
+                                           textwidth, \
+                                           ljust_columns
 from osol_install.text_install.list_item import ListItem
 from osol_install.text_install.scroll_window import ScrollWindow
 from osol_install.text_install.window_area import WindowArea
@@ -106,8 +109,7 @@
         
         disk_header_text = []
         for header in DiskScreen.DISK_HEADERS:
-            header_str = header[1][:header[0]-1]
-            header_str = header_str.ljust(header[0]-1)
+            header_str = fit_text_truncate(header[1], header[0]-1, just="left")
             disk_header_text.append(header_str)
         self.disk_header_text = " ".join(disk_header_text)
         max_note_size = DiskScreen.DISK_HEADERS[5][0]
@@ -179,7 +181,7 @@
             self.center_win.add_text(DiskScreen.DISK_SEEK_TEXT, 5, 1,
                                      self.win_size_x - 3)
             self.main_win.do_update()
-            offset = len(DiskScreen.DISK_SEEK_TEXT) + 2
+            offset = textwidth(DiskScreen.DISK_SEEK_TEXT) + 2
             spin_index = 0
             self.center_win.window.timeout(250)
             while self.td_handle.is_alive():
@@ -262,10 +264,11 @@
         y_loc += 1
         self.center_win.window.hline(y_loc, self.center_win.border_size[1] + 1,
                                      curses.ACS_HLINE,
-                                     len(self.disk_header_text))
+                                     textwidth(self.disk_header_text))
         
         y_loc += 1
-        disk_win_area = WindowArea(4, len(self.disk_header_text) + 2, y_loc, 0)
+        disk_win_area = WindowArea(4, textwidth(self.disk_header_text) + 2,
+                                   y_loc, 0)
         disk_win_area.scrollable_lines = len(self.disks) + 1
         self.disk_win = ScrollWindow(disk_win_area,
                                      window=self.center_win)
@@ -280,7 +283,7 @@
         for disk in self.disks:
             disk_text_fields = []
             type_field = disk.type[:len_type]
-            type_field = type_field.ljust(len_type)
+            type_field = ljust_columns(type_field, len_type)
             disk_text_fields.append(type_field)
             disk_size = disk.size.size_as("gb")
             size_field = "%*.1f" % (len_size, disk_size)
@@ -291,11 +294,11 @@
                 bootable_field = " " * (len_boot)
             disk_text_fields.append(bootable_field)
             device_field = disk.name[:len_dev]
-            device_field = device_field.ljust(len_dev)
+            device_field = ljust_columns(device_field, len_dev)
             disk_text_fields.append(device_field)
             if disk.vendor is not None:
                 mftr_field = disk.vendor[:len_mftr]
-                mftr_field = mftr_field.ljust(len_mftr)
+                mftr_field = ljust_columns(mftr_field, len_mftr)
             else:
                 mftr_field = " " * len_mftr
             disk_text_fields.append(mftr_field)
--- a/usr/src/cmd/text-install/osol_install/text_install/disk_window.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/disk_window.py	Wed Aug 11 15:34:19 2010 +0900
@@ -36,6 +36,7 @@
 from osol_install.text_install import _, LOG_LEVEL_INPUT
 from osol_install.text_install.base_screen import UIMessage
 from osol_install.text_install.edit_field import EditField
+from osol_install.text_install.i18n import fit_text_truncate, textwidth
 from osol_install.text_install.inner_window import InnerWindow, no_action
 from osol_install.text_install.list_item import ListItem
 from osol_install.text_install.scroll_window import ScrollWindow
@@ -225,12 +226,12 @@
             right_header_str = header[2]
             # Trim the header to fit in the column width,
             # splitting columns with at least 1 space
-            left_header_str = left_header_str[:header[0]-1]
-            right_header_str = right_header_str[:header[0]-1]
             # Pad with extra space(s) to align the columns
-            left_header_str = left_header_str.ljust(header[0]-1)
-            right_header_str = right_header_str.ljust(header[0]-1)
+            left_header_str = fit_text_truncate(left_header_str,
+                                                header[0]-1, just="left")
             self.left_header_string.append(left_header_str)
+            right_header_str = fit_text_truncate(right_header_str,
+                                                header[0]-1, just="left")
             self.right_header_string.append(right_header_str)
         self.left_header_string = " ".join(self.left_header_string)
         self.right_header_string = " ".join(self.right_header_string)
@@ -240,9 +241,9 @@
                             DiskWindow.SCROLL_PAD)
         self.add_text(self.right_header_string, 0, right_win_offset)
         self.window.hline(1, DiskWindow.SCROLL_PAD, curses.ACS_HLINE,
-                          len(self.left_header_string))
+                          textwidth(self.left_header_string))
         self.window.hline(1, right_win_offset, curses.ACS_HLINE,
-                          len(self.right_header_string))
+                          textwidth(self.right_header_string))
         self.no_ut_refresh()
     
     def print_data(self):
--- a/usr/src/cmd/text-install/osol_install/text_install/edit_field.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/edit_field.py	Wed Aug 11 15:34:19 2010 +0900
@@ -31,6 +31,7 @@
 from curses.ascii import isprint, ctrl, ismeta
 
 from osol_install.text_install.base_screen import UIMessage
+from osol_install.text_install.i18n import rjust_columns
 from osol_install.text_install.inner_window import InnerWindow, consume_action
 
 
@@ -197,7 +198,7 @@
             width = self.area.columns - 1
             if text:
                 text = text.lstrip(self.numeric_pad)
-                text = text.rjust(width, self.numeric_pad)
+                text = rjust_columns(text, width, self.numeric_pad)
         self._set_text(text)
     
     def _set_text(self, text):
@@ -352,7 +353,7 @@
                 self.validate(self, **self.validate_kwargs)
             except UIMessage as error_str:
                 if self.error_win is not None:
-                    self.error_win.display_err(str(error_str))
+                    self.error_win.display_err(unicode(error_str))
                 return False
         if self.error_win is not None and self.error_win.visible:
             self.error_win.clear_err()
@@ -371,7 +372,7 @@
                 return True
             except UIMessage as error_str:
                 if self.error_win is not None:
-                    self.error_win.display_err(str(error_str))
+                    self.error_win.display_err(unicode(error_str))
                 return False
         return True
     
--- a/usr/src/cmd/text-install/osol_install/text_install/fdisk_partitions.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/fdisk_partitions.py	Wed Aug 11 15:34:19 2010 +0900
@@ -30,6 +30,7 @@
 
 from osol_install.text_install.base_screen import BaseScreen, SkipException
 from osol_install.text_install.disk_window import DiskWindow
+from osol_install.text_install.i18n import textwidth
 from osol_install.text_install.list_item import ListItem
 from osol_install.text_install.window_area import WindowArea
 from osol_install.text_install import _
@@ -164,7 +165,7 @@
         y_loc += disk_win_area.lines
         
         y_loc += 3
-        whole_disk_width = len(self.use_whole) + 3
+        whole_disk_width = textwidth(self.use_whole) + 3
         cols = (self.win_size_x - whole_disk_width) / 2
         whole_disk_item_area = WindowArea(1, whole_disk_width, y_loc, cols)
         self.whole_disk_item = ListItem(whole_disk_item_area,
@@ -173,7 +174,7 @@
                                         centered=True)
         
         y_loc += 1
-        partial_width = len(self.use_part) + 3
+        partial_width = textwidth(self.use_part) + 3
         cols = (self.win_size_x - partial_width) / 2
         partial_item_area = WindowArea(1, partial_width, y_loc, cols)
         self.partial_disk_item = ListItem(partial_item_area,
--- a/usr/src/cmd/text-install/osol_install/text_install/help_screen.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/help_screen.py	Wed Aug 11 15:34:19 2010 +0900
@@ -35,7 +35,7 @@
 from osol_install.text_install import _
 from osol_install.text_install.action import Action
 from osol_install.text_install.base_screen import BaseScreen
-from osol_install.text_install.inner_window import InnerWindow
+from osol_install.text_install.i18n import convert_paragraph, textwidth
 from osol_install.text_install.list_item import ListItem
 from osol_install.text_install.scroll_window import ScrollWindow
 from osol_install.text_install.window_area import WindowArea
@@ -194,7 +194,7 @@
             topic_format = info[1]
             help_topic = self.get_help_topic(info[0])
             help_topic = topic_format % help_topic
-            hilite = min(self.win_size_x, len(help_topic) + 1)
+            hilite = min(self.win_size_x, textwidth(help_topic) + 1)
             list_item = ListItem(WindowArea(1, hilite, idx, 0),
                                  window=self.scroll_region, text=help_topic)
             help_screens = info[0]
@@ -247,8 +247,7 @@
         
         self.main_win.set_header_text(help_header)
         
-        help_text = InnerWindow.convert_paragraph(help_text,
-                                                  self.win_size_x - 5)
+        help_text = convert_paragraph(help_text, self.win_size_x - 5)
         logging.debug("help_text #lines=%d, text is \n%s",
                       len(help_text), help_text)
         area = WindowArea(x_loc=0, y_loc=1,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/osol_install/text_install/i18n.py	Wed Aug 11 15:34:19 2010 +0900
@@ -0,0 +1,197 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+
+''' methods for internationalized text processing, which
+counts "columns" in applicable cases, rather than assuming
+columns are same as characters or bytes.
+
+'''
+
+import locale
+from unicodedata import east_asian_width
+from osol_install.text_install import _
+
+# for specifying justification in fit_text_truncate()
+LEFT = "left"
+RIGHT = "right"
+CENTER = "center"
+
+def get_encoding():
+        ''' Get encoding of current locale
+        '''
+        enc = locale.getlocale(locale.LC_CTYPE)[1]
+        if enc is None:
+            enc = locale.getpreferredencoding()
+
+        return enc
+
+def charwidth(c):
+        ''' Count column width needed for given Unicode character
+        '''
+        if isinstance(c, str):
+            c = c.decode(get_encoding())
+        width_class = east_asian_width(c)
+
+        if width_class == "F" or width_class == "W":
+            return 2
+        else:
+            return 1
+
+def textwidth(text):
+        ''' Count column width needed for given string.
+        '''
+        if isinstance(text, str):
+            text = text.decode(get_encoding())
+        text = text.expandtabs(4)
+
+        width_total = 0
+
+        for char in text:
+            width_total += charwidth(char)
+
+        return width_total
+
+def if_wrap_on_whitespace():
+        ''' Get information on whether wrapping text should be done on
+        white space or on arbitrary position. Default is True as in English.
+        '''
+        val = _("DONT_TRANSLATE_BUT_REPLACE_msgstr_WITH_True_OR_False: "
+                "Should wrap text on whitespace in this language")
+
+        if val == "False":
+            return False
+        else:
+            return True
+
+def fit_text_truncate(text, max_width, just="", fillchar=u" "):
+        ''' Fit a text in max_width columns, by truncating the text if needed.
+        If just is one of LEFT, RIGHT, or CENTER, justify text
+        and fill unused room with fillchar.
+        '''
+        if isinstance(text, str):
+            text = text.decode(get_encoding())
+
+        text = text.expandtabs(4)
+
+        if fillchar is None:
+            fillchar = u" "
+        if isinstance(fillchar, str):
+            fillchar = fillchar.decode(get_encoding())
+        if charwidth(fillchar) != 1:
+            raise ValueError('Cannot use multi-column character "%c" as '
+                             'fillchar.' % fillchar)
+
+        fitted_text = u""
+        width_total = 0
+
+        for char in text:
+            width = charwidth(char)
+            if width_total + width > max_width:
+                break
+            fitted_text += char
+            width_total += width
+
+        npad = max_width - width_total
+
+        if just and npad > 0:
+            if just == LEFT:
+                fitted_text = fitted_text + fillchar * npad
+            elif just == RIGHT:
+                fitted_text = fillchar * npad + fitted_text
+            elif just == CENTER:
+                nleft = npad // 2
+                nright = npad - nleft
+                fitted_text = fillchar * nleft + fitted_text + fillchar * nright
+            else:
+                raise ValueError("Unknown just=%s" % just)
+
+        return fitted_text
+
+def ljust_columns(text, max_width, fillchar=u" "):
+    ''' alternative to ljust(); counts multicolumn characters correctly
+    '''
+    return fit_text_truncate(text, max_width, just=LEFT, fillchar=fillchar)
+
+def rjust_columns(text, max_width, fillchar=u" "):
+    ''' alternative to rjust(); counts multicolumn characters correctly
+    '''
+    return fit_text_truncate(text, max_width, just=RIGHT, fillchar=fillchar)
+
+def center_columns(text, max_width, fillchar=u" "):
+    ''' alternative to center(); counts multicolumn characters correctly
+    '''
+    return fit_text_truncate(text, max_width, just=CENTER, fillchar=fillchar)
+
+def convert_paragraph(text, max_chars):
+    '''Break a paragraph of text up into chunks that will each
+    fit within max_chars. Splits on whitespace (if wrapping on
+    whitespace is used in current language) and newlines.
+    
+    max_chars defaults to the size of this window.
+    
+    '''
+    wrap_on_whitespace = if_wrap_on_whitespace()
+
+    if isinstance(text, str):
+        text = text.decode(get_encoding())
+
+    text_lines = text.expandtabs(4).splitlines()
+    paragraphed_lines = []
+
+    for line in text_lines:
+        width_total = 0
+        last_whitespace = None
+        width_upto_last_whitespace = None
+        start_pt = 0
+        for i, c in enumerate(line):
+            width = charwidth(c)
+            if width_total + width > max_chars:
+                if wrap_on_whitespace and last_whitespace is not None :
+                    # put upto last white space
+                    end_pt = last_whitespace + 1
+                    paragraphed_lines.append(line[start_pt:end_pt].lstrip())
+                    # next line will start at char next to the white space
+                    start_pt = last_whitespace + 1
+                    width_total -= width_upto_last_whitespace
+                    # forget last white space
+                    last_whitespace = None
+                    width_upto_last_whitespace = None
+                else:
+                    # white space didn't appear; put upto last char
+                    end_pt = i
+                    paragraphed_lines.append(line[start_pt:end_pt].lstrip())
+                    # next line will start at current char
+                    start_pt = i
+                    width_total = 0
+            width_total += width
+            if c == u' ':
+                if i == start_pt:
+                    # not count leading white space
+                    width_total -= width
+                else:
+                    last_whitespace = i
+                    width_upto_last_whitespace = width_total
+        # flush last part of "line" (each item of text_lines)
+        paragraphed_lines.append(line[start_pt:].lstrip())
+    return paragraphed_lines
+
--- a/usr/src/cmd/text-install/osol_install/text_install/inner_window.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/inner_window.py	Wed Aug 11 15:34:19 2010 +0900
@@ -34,7 +34,10 @@
 from copy import copy
 
 from osol_install.text_install import LOG_LEVEL_INPUT
-
+from osol_install.text_install.i18n import fit_text_truncate, \
+                                           convert_paragraph, \
+                                           get_encoding, \
+                                           textwidth
 
 KEY_ESC = 27
 KEY_BS = 127 # Backspace code that curses doesn't translate right
@@ -310,55 +313,30 @@
         
         '''
         win_y, win_x = self.window.getmaxyx()
-        logging.log(LOG_LEVEL_INPUT, "start_y=%d, start_x=%d, max_chars=%s, centered=%s,"
-                    " win_max_x=%s, win_max_y=%s",
+        logging.log(LOG_LEVEL_INPUT, "start_y=%d, start_x=%d, max_chars=%s, "
+                    "centered=%s, win_max_x=%s, win_max_y=%s",
                     start_y, start_x, max_chars, centered, win_x, win_y)
         max_x = self.window.getmaxyx()[1] - self.border_size[1]
         start_x += self.border_size[1]
-        if centered:
-            length = len(text)
-            max_x = max_x - start_x
-            if self.window.getmaxyx()[0] == (start_y + 1):
-                max_x -= 1 # Cannot print to bottom-right corner
-            start_x = max((max_x - length) / 2 + start_x, start_x)
         
         abs_max_chars = max_x - start_x
         if max_chars is None:
             max_chars = abs_max_chars
         else:
             max_chars = min(max_chars, abs_max_chars)
-        
-        logging.log(LOG_LEVEL_INPUT, "calling addnstr with params start_y=%s, start_x=%s, "
-                    "text=%s, max_chars=%s", start_y, start_x, text, max_chars)
-        self.window.addnstr(start_y, start_x, text, max_chars)
-        self.no_ut_refresh()
-    
-    @staticmethod
-    def convert_paragraph(text, max_chars):
-        '''Break a paragraph of text up into chunks that will each
-        fit within max_chars. Splits on whitespace and newlines
-        
-        max_chars defaults to the size of this window.
+
+        text = fit_text_truncate(text, max_chars)
+
+        if centered:
+            start_x = (max_x - textwidth(text)) / 2 + start_x
         
-        '''
-        text_lines = text.expandtabs(4).splitlines()
-        paragraphed_lines = []
-        for line in text_lines:
-            if len(line) <= max_chars:
-                paragraphed_lines.append(line)
-            else:
-                start_pt = 0
-                end_pt = 0
-                while end_pt + max_chars < len(line): 
-                    end_pt = line.rfind(" ", start_pt, start_pt + max_chars)
-                    if end_pt == -1:
-                        end_pt = start_pt + max_chars
-                    paragraphed_lines.append(line[start_pt:end_pt].lstrip())
-                    start_pt = end_pt + 1
-                else:
-                    paragraphed_lines.append(line[end_pt:].lstrip())
-        return paragraphed_lines
-    
+        if isinstance(text, unicode):
+            text = text.encode(get_encoding())
+        logging.log(LOG_LEVEL_INPUT, "calling addstr with params start_y=%s,"
+                    "start_x=%s, text=%s", start_y, start_x, text)
+        self.window.addstr(start_y, start_x, text)
+        self.no_ut_refresh()
+
     def add_paragraph(self, text, start_y=0, start_x=0, max_y=None,
                       max_x=None):
         '''Add a block of text to the window
@@ -380,11 +358,13 @@
             max_y = win_size_y - start_y - self.border_size[0]
         if max_x is None:
             max_x = win_size_x
-        max_chars = max_x - start_x - self.border_size[1] - 1
+        max_chars = max_x - start_x - self.border_size[1] * 2
         y_index = start_y
         if isinstance(text, basestring):
-            text = self.convert_paragraph(text, max_chars)
+            text = convert_paragraph(text, max_chars)
         for line in text:
+            logging.log(LOG_LEVEL_INPUT, "add_paragraph:add_text: y_index=%d, "
+                        "line=%s\n", y_index, line)
             self.add_text(line, y_index, start_x, max_chars)
             y_index += 1
             if y_index > max_y + start_y:
--- a/usr/src/cmd/text-install/osol_install/text_install/install_progress.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/install_progress.py	Wed Aug 11 15:34:19 2010 +0900
@@ -35,6 +35,7 @@
 
 from osol_install.text_install import _, LOG_LEVEL_INPUT
 from osol_install.text_install.base_screen import BaseScreen
+from osol_install.text_install.i18n import ljust_columns
 from osol_install.text_install.inner_window import InnerWindow
 from osol_install.text_install.window_area import WindowArea
 from osol_install.text_install.ti_install import perform_ti_install
@@ -205,7 +206,7 @@
     def set_status_message(self, message):
         '''Set the status message on the screen, completely overwriting
         the previous message'''
-        self.center_win.add_text(message.ljust(self.status_msg_loc[2]),
+        self.center_win.add_text(ljust_columns(message, self.status_msg_loc[2]),
                                  self.status_msg_loc[0],
                                  self.status_msg_loc[1],
                                  max_chars=self.status_msg_loc[2])
--- a/usr/src/cmd/text-install/osol_install/text_install/log_viewer.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/log_viewer.py	Wed Aug 11 15:34:19 2010 +0900
@@ -30,7 +30,7 @@
 
 from osol_install.text_install import _
 from osol_install.text_install.base_screen import BaseScreen
-from osol_install.text_install.inner_window import InnerWindow
+from osol_install.text_install.i18n import convert_paragraph
 from osol_install.text_install.scroll_window import ScrollWindow
 from osol_install.text_install.window_area import WindowArea
 
@@ -79,5 +79,5 @@
                 if log_file is not None:
                     log_file.close()
             max_chars = self.win_size_x - 4
-            self.log_data = InnerWindow.convert_paragraph(log_data, max_chars)
+            self.log_data = convert_paragraph(log_data, max_chars)
         return self.log_data
--- a/usr/src/cmd/text-install/osol_install/text_install/main_window.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/main_window.py	Wed Aug 11 15:34:19 2010 +0900
@@ -34,6 +34,9 @@
 from osol_install.text_install.color_theme import ColorTheme
 from osol_install.text_install.action import Action
 from osol_install.text_install.error_window import ErrorWindow
+from osol_install.text_install.i18n import textwidth, \
+                                           center_columns, \
+                                           get_encoding
 from osol_install.text_install.inner_window import InnerWindow
 from osol_install.text_install.list_item import ListItem
 from osol_install.text_install.scroll_window import ScrollWindow
@@ -157,7 +160,7 @@
     
     def set_header_text(self, header_text):
         '''Set the header_text'''
-        text = header_text.center(self.header.area.columns - 1)
+        text = center_columns(header_text, self.header.area.columns - 1)
         self.header.add_text(text)
         self._cur_header_text = text
     
@@ -193,12 +196,12 @@
             strings.append(action_str)
         display_str = "".join(strings)
         max_len = self.footer.window.getmaxyx()[1]
-        length = len(display_str)
+        length = textwidth(display_str)
         if not InnerWindow.USE_ESC:
             length += (len(" Esc-") - len("  F")) * len(self.actions)
         if length > max_len:
             raise ValueError("Can't display footer actions - string too long")
-        self.footer.window.addstr(display_str)
+        self.footer.window.addstr(display_str.encode(get_encoding()))
         self.footer.window.noutrefresh()
     
     def getch(self):
@@ -250,7 +253,7 @@
         
         # Add the header, a border, and the question to the window
         self.popup_win.window.border()
-        header_x = (self.popup_win.area.columns - len(header)) / 2
+        header_x = (self.popup_win.area.columns - textwidth(header)) / 2
         self.popup_win.add_text(header, 0, header_x)
         y_loc = 2
         y_loc += self.popup_win.add_paragraph(question, y_loc, 2)
@@ -267,10 +270,10 @@
         
         # Create two "buttons" of equal size by finding the larger of the
         # two, and centering them
-        max_len = max(len(left_btn_txt), len(right_btn_txt))
+        max_len = max(textwidth(left_btn_txt), textwidth(right_btn_txt))
         left_btn_txt = " [ %s ]" % left_btn_txt.center(max_len)
         right_btn_txt = " [ %s ]" % right_btn_txt.center(max_len)
-        button_len = len(left_btn_txt) + 1
+        button_len = textwidth(left_btn_txt) + 1
         win_size = self.popup_win.window.getmaxyx()
         left_area = WindowArea(1, button_len, y_loc,
                                (win_size[1] / 2) - (button_len + 2))
--- a/usr/src/cmd/text-install/osol_install/text_install/network_type.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/network_type.py	Wed Aug 11 15:34:19 2010 +0900
@@ -34,6 +34,7 @@
 from osol_install.text_install import _
 from osol_install.text_install.base_screen import BaseScreen, UIMessage
 from osol_install.text_install.edit_field import EditField
+from osol_install.text_install.i18n import textwidth
 from osol_install.text_install.list_item import ListItem
 from osol_install.text_install.window_area import WindowArea
 
@@ -67,7 +68,7 @@
     
     def __init__(self, main_win):
         super(NetworkTypeScreen, self).__init__(main_win)
-        self.hostfield_offset = len(NetworkTypeScreen.HOSTNAME_TEXT)
+        self.hostfield_offset = textwidth(NetworkTypeScreen.HOSTNAME_TEXT)
         self.menu_item_desc_max = (self.win_size_x -
                                    NetworkTypeScreen.ITEM_DESC_OFFSET)
         
--- a/usr/src/cmd/text-install/osol_install/text_install/partition_edit_screen.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/partition_edit_screen.py	Wed Aug 11 15:34:19 2010 +0900
@@ -32,6 +32,7 @@
 import platform
 
 from osol_install.profile.disk_info import PartitionInfo, SliceInfo
+from osol_install.text_install import LOG_LEVEL_INPUT
 from osol_install.text_install.action import Action
 from osol_install.text_install.base_screen import BaseScreen, \
                                                   SkipException, \
@@ -172,7 +173,10 @@
                                    reset=self.orig_data)
         y_loc += disk_win_area.lines
         
-        y_loc += 2
+        y_loc += 1
+        logging.log(LOG_LEVEL_INPUT, "calling addch with params start_y=%s,"
+                    "start_x=%s, ch=%c", y_loc, self.center_win.border_size[1],
+                    DiskWindow.DESTROYED_MARK)
         self.center_win.window.addch(y_loc, self.center_win.border_size[1],
                                      DiskWindow.DESTROYED_MARK,
                                      self.center_win.color_theme.inactive)
--- a/usr/src/cmd/text-install/osol_install/text_install/summary.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/summary.py	Wed Aug 11 15:34:19 2010 +0900
@@ -34,7 +34,7 @@
 from osol_install.text_install import _
 from osol_install.text_install.action import Action
 from osol_install.text_install.base_screen import BaseScreen
-from osol_install.text_install.inner_window import InnerWindow
+from osol_install.text_install.i18n import convert_paragraph
 from osol_install.text_install.window_area import WindowArea
 from osol_install.text_install.scroll_window import ScrollWindow
 
@@ -70,7 +70,7 @@
         # Wrap the summary text, accounting for the INDENT (used below in
         # the call to add_paragraph)
         max_chars = self.win_size_x - SummaryScreen.INDENT - 1
-        summary_text = InnerWindow.convert_paragraph(summary_text, max_chars)
+        summary_text = convert_paragraph(summary_text, max_chars)
         area = WindowArea(x_loc=0, y_loc=y_loc,
                           scrollable_lines=(len(summary_text)+1))
         area.lines = self.win_size_y - y_loc
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/cmd/text-install/osol_install/text_install/test/test_i18n.py	Wed Aug 11 15:34:19 2010 +0900
@@ -0,0 +1,189 @@
+#!/usr/bin/python2.6
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+#
+
+'''
+To run these tests:
+
+1) nightly -n developer.sh # build the gate
+2) export PYTHONPATH=${WS}/proto/root_i386/usr/snadm/lib:${WS}/proto/root_i386/usr/lib/python2.6/vendor-packages
+3) python2.6 test_i18n.py
+
+A single test may be run by specifying the test as an argument to step 3:
+python2.6 test_i18n.py I18nTestCase.test_charwidth
+
+Since the proto area is used for the PYTHONPATH, the gate must be rebuilt for
+these tests to pick up any changes in the tested code.
+
+'''
+
+import unittest
+import locale
+
+from osol_install.text_install.i18n import get_encoding, \
+                                           if_wrap_on_whitespace, \
+                                           charwidth, \
+                                           textwidth, \
+                                           fit_text_truncate, \
+                                           convert_paragraph, \
+                                           LEFT, \
+                                           RIGHT, \
+                                           CENTER
+
+class I18nTestCase(unittest.TestCase):
+    def setUp(self):
+        # save current locale
+        self.save_locale = locale.getlocale(locale.LC_ALL)
+        # set locale
+        locale.setlocale(locale.LC_ALL, "")
+        # if not in UTF-8 locale, fallback to en_US.UTF-8
+        enc = locale.getlocale(locale.LC_CTYPE)[1]
+        if enc is None or enc.upper() != "UTF-8" and enc.upper() != "UTF8":
+            print "This test script uses hard-coded Unicode characters and "
+            print "needs to run in a UTF-8 locale. Fallback to en_US.UTF-8."
+            locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
+
+    def tearDown(self):
+        # restore locale
+        locale.setlocale(locale.LC_ALL, self.save_locale)
+
+    def test_charwidth(self):
+        ''' test charwidth() '''
+        # example of 1-column character (ASCII)
+        self.assertEqual(charwidth(u'A'), 1)
+        self.assertEqual(charwidth(u'A'.encode(get_encoding())), 1)
+        # example of 1-column character (non-ASCII)
+        self.assertEqual(charwidth(u'\u00c0'), 1)
+        self.assertEqual(charwidth(u'\u00c0'.encode(get_encoding())), 1)
+        # example of 2-column character
+        self.assertEqual(charwidth(u'\u3042'), 2)
+        self.assertEqual(charwidth(u'\u3042'.encode(get_encoding())), 2)
+
+    def test_textwidth(self):
+        ''' test textwidth() '''
+        # without tab
+        self.assertEqual(textwidth(u'A\u00c0\u3042'), 1 + 1 + 2)
+        self.assertEqual(textwidth(u'A\u00c0\u3042'.encode(get_encoding())),
+                         1 + 1 + 2)
+        # with tab
+        self.assertEqual(textwidth(u'\tA\u00c0\u3042'), 4 + 1 + 1 + 2)
+        self.assertEqual(textwidth(u'\tA\u00c0\u3042'.encode(get_encoding())),
+                         4 + 1 + 1 + 2)
+
+    def test_fit_text_truncate(self):
+        ''' test fit_text_truncate() '''
+        c0 = u'\u3042' # 2 columns
+        c1 = u'\u3044' # 2 columns
+        c2 = u'\u3046' # 2 columns
+        c3 = u'\u3048' # 2 columns
+        s = c0 + c1 + c2 + c3
+        # no justification
+        self.assertEqual(fit_text_truncate(s, 9), s[:4])
+        self.assertEqual(fit_text_truncate(s, 8), s[:4])
+        self.assertEqual(fit_text_truncate(s, 7), s[:3])
+        self.assertEqual(fit_text_truncate(s, 6), s[:3])
+        self.assertEqual(fit_text_truncate(s, 5), s[:2])
+        self.assertEqual(fit_text_truncate(s, 4), s[:2])
+        self.assertEqual(fit_text_truncate(s, 3), s[:1])
+        self.assertEqual(fit_text_truncate(s, 2), s[:1])
+        self.assertEqual(fit_text_truncate(s, 1), s[:0])
+        self.assertEqual(fit_text_truncate(s, 0), s[:0])
+        # justify to left
+        self.assertEqual(fit_text_truncate(s, 9, just=LEFT), s[:4] + u' ')
+        self.assertEqual(fit_text_truncate(s, 8, just=LEFT), s[:4])
+        self.assertEqual(fit_text_truncate(s, 7, just=LEFT), s[:3] + u' ')
+        self.assertEqual(fit_text_truncate(s, 6, just=LEFT), s[:3])
+        self.assertEqual(fit_text_truncate(s, 5, just=LEFT), s[:2] + u' ')
+        self.assertEqual(fit_text_truncate(s, 4, just=LEFT), s[:2])
+        self.assertEqual(fit_text_truncate(s, 3, just=LEFT), s[:1] + u' ')
+        self.assertEqual(fit_text_truncate(s, 2, just=LEFT), s[:1])
+        self.assertEqual(fit_text_truncate(s, 1, just=LEFT), s[:0] + u' ')
+        self.assertEqual(fit_text_truncate(s, 0, just=LEFT), s[:0])
+        # justify to right
+        self.assertEqual(fit_text_truncate(s, 9, just=RIGHT), u' ' + s[:4])
+        self.assertEqual(fit_text_truncate(s, 8, just=RIGHT), s[:4])
+        self.assertEqual(fit_text_truncate(s, 7, just=RIGHT), u' ' + s[:3])
+        self.assertEqual(fit_text_truncate(s, 6, just=RIGHT), s[:3])
+        self.assertEqual(fit_text_truncate(s, 5, just=RIGHT), u' ' + s[:2])
+        self.assertEqual(fit_text_truncate(s, 4, just=RIGHT), s[:2])
+        self.assertEqual(fit_text_truncate(s, 3, just=RIGHT), u' ' + s[:1])
+        self.assertEqual(fit_text_truncate(s, 2, just=RIGHT), s[:1])
+        self.assertEqual(fit_text_truncate(s, 1, just=RIGHT), u' ' + s[:0])
+        self.assertEqual(fit_text_truncate(s, 0, just=RIGHT), s[:0])
+        # justify to center
+        self.assertEqual(fit_text_truncate(s, 12, just=CENTER),
+                         u' ' * 2 + s[:4] + u' ' * 2)
+        self.assertEqual(fit_text_truncate(s, 11, just=CENTER),
+                         u' ' + s[:4] + u' ' * 2)
+        self.assertEqual(fit_text_truncate(s, 10, just=CENTER),
+                         u' ' + s[:4] + u' ')
+        self.assertEqual(fit_text_truncate(s, 9, just=CENTER), s[:4] + u' ')
+        self.assertEqual(fit_text_truncate(s, 8, just=CENTER), s[:4])
+        self.assertEqual(fit_text_truncate(s, 7, just=CENTER), s[:3] + u' ')
+        self.assertEqual(fit_text_truncate(s, 6, just=CENTER), s[:3])
+        self.assertEqual(fit_text_truncate(s, 5, just=CENTER), s[:2] + u' ')
+        self.assertEqual(fit_text_truncate(s, 4, just=CENTER), s[:2])
+        self.assertEqual(fit_text_truncate(s, 3, just=CENTER), s[:1] + u' ')
+        self.assertEqual(fit_text_truncate(s, 2, just=CENTER), s[:1])
+        self.assertEqual(fit_text_truncate(s, 1, just=CENTER), s[:0] + u' ')
+        self.assertEqual(fit_text_truncate(s, 0, just=CENTER), s[:0])
+
+    def test_fit_text_truncate_multicolumn_fillchar(self):
+        ''' test fit_text_truncate() with specifying multicolumn fillchar,
+            expecting ValueError is raised
+        '''
+        self.assertRaises(ValueError, fit_text_truncate,
+                          u"\u3042", 9, just=LEFT, fillchar=u'\u3042')
+        self.assertRaises(ValueError, fit_text_truncate,
+                          u"\u3042", 9, just=RIGHT, fillchar=u'\u3042')
+        self.assertRaises(ValueError, fit_text_truncate,
+                          u"\u3042", 9, just=CENTER, fillchar=u'\u3042')
+
+    def test_convert_paragraph(self):
+        ''' test convert_paragraph() '''
+        c0 = u'\u3042' # 2 columns
+        c1 = u'\u3044' # 2 columns
+        c2 = u' ' # 1 column (white space)
+        c3 = u'\u3046' # 2 columns
+        c4 = u'\u3048' # 2 columns
+        s = c0 + c1 + c2 + c3 + c4
+
+        if if_wrap_on_whitespace():
+            self.assertEqual(convert_paragraph(s, 10), [s])
+            self.assertEqual(convert_paragraph(s, 9), [s])
+            self.assertEqual(convert_paragraph(s, 8), [s[:3], s[3:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 7), [s[:3], s[3:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 6), [s[:3], s[3:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 5), [s[:3], s[3:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 4), [s[:2], s[2:].lstrip()])
+        else:
+            self.assertEqual(convert_paragraph(s, 10), [s])
+            self.assertEqual(convert_paragraph(s, 9), [s])
+            self.assertEqual(convert_paragraph(s, 8), [s[:4], s[4:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 7), [s[:4], s[4:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 6), [s[:3], s[3:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 5), [s[:3], s[3:].lstrip()])
+            self.assertEqual(convert_paragraph(s, 4), [s[:2], s[2:].lstrip()])
+
+if __name__ == '__main__':
+    unittest.main()
--- a/usr/src/cmd/text-install/osol_install/text_install/text_install.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/text_install.py	Wed Aug 11 15:34:19 2010 +0900
@@ -35,6 +35,8 @@
 import subprocess
 import sys
 import traceback
+import locale
+import gettext
 from optparse import OptionParser
 
 import libbe
@@ -53,6 +55,7 @@
 from osol_install.text_install.disk_selection import DiskScreen
 from osol_install.text_install.fdisk_partitions import FDiskPart
 from osol_install.text_install.help_screen import HelpScreen
+from osol_install.text_install.i18n import get_encoding
 from osol_install.text_install.install_progress import InstallProgress
 from osol_install.text_install.install_status import InstallStatus
 from osol_install.text_install.log_viewer import LogViewer
@@ -97,6 +100,8 @@
     logging.shutdown()
     if logname is not None:
         print _("Exiting Text Installer. Log is available at:\n%s") % logname
+    if isinstance(errcode, unicode):
+        errcode = errcode.encode(get_encoding())
     sys.exit(errcode)
 
 
@@ -147,6 +152,8 @@
 
 
 if __name__ == '__main__':
+    locale.setlocale(locale.LC_ALL, "")
+    gettext.install("textinstall", "/usr/share/locale", unicode=True)
     if os.getuid() != 0:
         print _("The OpenSolaris Text Installer must be run with "
                 "root privileges")
--- a/usr/src/cmd/text-install/osol_install/text_install/users.py	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/cmd/text-install/osol_install/text_install/users.py	Wed Aug 11 15:34:19 2010 +0900
@@ -34,6 +34,7 @@
 from osol_install.text_install.error_window import ErrorWindow
 from osol_install.text_install.list_item import ListItem
 from osol_install.text_install.window_area import WindowArea
+from osol_install.text_install.i18n import textwidth
 
 
 class UserScreen(BaseScreen):
@@ -74,11 +75,11 @@
         super(UserScreen, self).__init__(main_win)
         self.max_text_len = (self.win_size_x - UserScreen.MAX_PASS_LEN -
                              UserScreen.ITEM_OFFSET) / 2
-        max_field = max(len(UserScreen.ROOT_LABEL),
-                        len(UserScreen.CONFIRM_LABEL),
-                        len(UserScreen.NAME_LABEL),
-                        len(UserScreen.USERNAME_LABEL),
-                        len(UserScreen.USER_PASS_LABEL))
+        max_field = max(textwidth(UserScreen.ROOT_LABEL),
+                        textwidth(UserScreen.CONFIRM_LABEL),
+                        textwidth(UserScreen.NAME_LABEL),
+                        textwidth(UserScreen.USERNAME_LABEL),
+                        textwidth(UserScreen.USER_PASS_LABEL))
         self.text_len = min(max_field + 1, self.max_text_len)
         self.list_area = WindowArea(1, self.text_len, 0,
                                     UserScreen.ITEM_OFFSET)
--- a/usr/src/pkg/manifests/system-install-text-install.mf	Mon Aug 09 13:43:53 2010 -0700
+++ b/usr/src/pkg/manifests/system-install-text-install.mf	Wed Aug 11 15:34:19 2010 +0900
@@ -95,6 +95,8 @@
 file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/fdisk_partitions.pyc mode=0444
 file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/help_screen.py mode=0444
 file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/help_screen.pyc mode=0444
+file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/i18n.py mode=0444
+file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/i18n.pyc mode=0444
 file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/inner_window.py mode=0444
 file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/inner_window.pyc mode=0444
 file path=usr/lib/python2.6/vendor-packages/osol_install/text_install/install_progress.py mode=0444