410013796724260
• Webmoney
R335386147728
Z369087728698
Интернационализация, i18n, l10nВопрос интернационализации пользовательского интерфейса - один из важных вопросов при разработке приложения. Для этого недостаточно использовать Unicode и перевести на нужный язык все сообщения пользовательского интерфейса. Интернационализация приложения означает нечто большее, чем поддержка Unicode. Дата, время, денежные суммы и даже числа могут по-разному представляться на различных языках. Широкое распространение получили условные сокращения терминов интернационализации и локализации приложений i18n и l10n, в которых цифра означает количество символов между первой и последней позицией:
В отдельной литературе делают акцент на этих двух определениях, под которыми понимается :
Java - первый язык программирования, в котором изначально были предусмотрены средства интернационализации. Строки формируются из символов Unicode. Поддержка этого стандарта кодирования позволяет создавать Java-приложения, обрабатывающие тексты на любом из существующих в мире языков. Большинство фреймворков, используемые в Java 2EE, поддерживает интернационализацию приложений с использованием Java-технологии, существенно снижая трудозатраты при разработке Web-приложения, "говорящего" на нескольких языках. Региональные стандарты LocaleПриложение, которое адаптировано для международного рынка, легко определить по возможности выбора языка, для работы с ним. Но профессионально адаптированные приложения могут иметь разные региональные настройки даже для тех стран, в которых используется одинаковый язык. В любом случае команды меню, надписи на кнопках и программные сообщения должны быть переведены на местный язык, возможно с использованием специального национального алфавита. Но существует еще много других более тонких различий, которые касаются форматов представления вещественных чисел (разделители целой и дробной частей, разделителей групп тысяч) и денежных сумм (включение и местоположения денежного знака), а также формата даты (порядок следования и символы разделители дней, месяцев и лет). Существует ряд классов, которые выполняют форматирование, принимая во внимание указанные выше различия. Для управления форматированием используется класс Locale. Региональный стандарт Locale определяет язык. Кроме этого могут быть указаны географическое расположение и вариант языка. Например, в США используется следующий региональный стандарт: language=English, location=United States В Германии региональный стандарт имеет вид : language=German, location=Germany В Швейцарии используются четыре официальных языка : немецкий, французский, итальянский и ретороманский. Поэтому немецкие пользователи в Швейцарии, вероятно, захотят использовать следующий региональный стандарт: language=German, location=Switzerland В данном случае текст, даты и числа будут форматироваться так же, как и для Германии, но денежные суммы будут отображаться в швейцарских франках, а не в евро. Если задавать только язык, например language=German, то особенности конкретной страны (например, формат представления денежных единиц) не будут учтены. Вариант языка используется довольно редко. Например, в настоящее время в норвежском языке (производном от датского) определены два набора правил правописания (Bokmel) и новый (Nynorsk). В этом случае, для задания традиционных правил орфографии используется параметр, определяющий вариант: language=Norwegian, location=Norway, variant=Bokmel Для выражения языка и расположения в компактной и стандартной форме в Java используются коды, определенные Международной организацией по стандартизации (ISO). Язык обозначается двумя строчными буквами в соответствии со стандартом ISO-639, а страна ( расположение)- двумя прописными буквами согласно стандарту ISO-3166. Чтобы задать региональный стандарт, необходимо объединить код языка, код страны и вариант (если он есть), а затем передать полученную строку в конструктор класса Locale. Locale german = new Locale ("de"); Locale germanGermany = new Locale ("de", "DE"); Locale germanSwitzerland = new Locale ("de", "CH"); Locale norwegianNorwayBokmel = new Locale ("no","NO","B"); Для удобства пользователей в JDK предусмотрено несколько предопределенных объектов с региональными настройками, а для некоторых языков также имеются объекты, позволяющие указать язык без указания страны:
Помимо вызова конструктора или выбора предопределенных объектов, существует еще два пути получения объектов с региональными настройками. Статический метод getDefault() класса Locale позволяет определить региональную настройку, которая используется в операционной системе по-умолчанию. Изменить настройку по-умолчанию можно вызвав метод setDefault (). Однако следует помнить, что данный метод воздействует только на Java-программу, а не на операционную систему в целом. Региональные настройки, getAvailableLocalesМетод getLocale() возвращает региональные настройки того компьютера, на котором он запущен. И наконец, все зависимые от региональных настроек вспомогательные классы могут возвращать массив поддерживаемых региональных стандартов. Например, приведенный ниже метод возвращает все региональные настройки, поддерживаемые классом DateFormat. Locale [] supportedLocales = DateFormat.getAvailableLocales(); Какие действия можно выполнять на основе полученных региональных настроек? Выбор невелик. Единственными полезными методами класса Locale являются методы определения кодов языка и страны. Наиболее важными из них является метод getDisplayName(), возвращающий строку с описанием региональной настройки, которая содержит не какие-то двухбуквенные загадочные коды, а вполне понятные пользователю обозначения German (Switzerland) Но данная строка отображается на используемом по умолчанию языке, что далеко не всегда бывает удобно. Если пользователь выбрал немецкий язык интерфейса, то строку описания следует отобразить именно на немецком языке, для чего можно передать в качестве параметра соответствующую региональную настройку так, как представлено в следующих строках кода : Locale loc = new Locale ("de", "CH"); System.out.println (loc.getDisplayName (Locale.GERMAN)); В результате выполнения этого кода описание региональной настройки будет выведено на указанном в ней языке : Deutsch (Schweiz) Данный пример поясняет, зачем нужны объекты Locale. Передавая их методам, способным реагировать на региональные настройки, можно отображать текст на языке, понятном пользователю. Форматирование числовых значений NumberFormatРанее упоминалось, что в разных странах и регионах используются различные способы представления чисел и денежных сумм. В пакете java.text содержатся классы, позволяющие форматировать числа и выполнять разбор их строкового представления. Для форматирования чисел в соответствии с конкретным региональным стандартом необходимо выполнить ряд действий:
В качестве фабричных методов (factory method) используются статические методы getNumberInstance (), getCurrencyInstance (), getPercentInstance () класса NumberFormat. Они получают в качестве параметра объект Locale и возвращают объекты, предназначенные для форматирования чисел, денежных сумм и значений, выраженных в процентах. Например, для отображения денежной суммы в формате, принятом в Германии, можно использовать приведенный ниже фрагмент кода: Locale loc = new Locale ("de", "DE"); NumberFormat currFmt; currFmt = NumberFormat.getCurrencyInstance (loc); double amt = 123456.78; System.out.println (currFmt.format (amt)); В результате выполнения этого кода будет получена следующая строка: 123.456,78 € Для обозначения евро здесь используется знак €, который располагается в конце строки. Кроме этого, следует обратить внимание на символы, применяемые для обозначения дробной части и разделения десятичных разрядов. Для преобразования строки, записанной в соответствии с определенным региональным стандартом, в число предусмотрен метод parse (), который выполняет синтаксический анализ строки с автоматическим использованием заданного по умолчанию регионального стандарта. В приведенном ниже примере показан способ преобразования строки, введеной пользователем в поле редактирования, в число. Метод parse () способен преобразовывать числа, в которых в качестве разделителя используется точка и запятая. TextField inputField; . . . NumberFormat fmt = NumberFormat.getNumberInstance (); // Получить объект форматирования для используемого // по умолчанию регионального стандарта Number input = fmt.parse (inputField.getText ().trim ()); double x = input.doubleValue (); Метод parse () возвращает результат абстрактного типа Number. На самом деле возвращаемый объект является экземпляром класса Long или Double, в зависимости от того, представляет исходная строка целое число или число с плавающей точкой. Если это не важно, то для получения числового значения достаточно использовать метод doubleValue() класса Number. Для объектов типа Number не поддерживается автоматическое приведение к простым типам. Если число представлено в некорректном формате, генерируется исключение ParseException. Например, не допускается наличие символа пробела в начале строки, преобразуемой в число. (Для их удаления следует использовать метод trim ()). Любые символы, которые располагаются в строке после числа, лишь игнорируются и исключение в этом случае не возникает. Очевидно, что классы, возвращаемые методами getXxxInstance (), являются экземплярами не абстрактного класса NumberFormat, а одного из его подклассов. Фабричным методам известно лишь то, как найти объект, представляющий определенный региональный стандарт. Для получения списка поддерживаемых региональных стандартов можно использовать статистический метод getAvailableLocales, возвращающий массив региональных стандартов, для которых существуют объекты форматирования. Методы пакета java.text.NumberFormat
Денежные суммыДля форматирования денежных сумм используется метод getCurrencyInstance() класса NumberFormat.Однако этот метод не обеспечивает достаточной гибкости - он возвращает форматированную строку для одной валюты. Допустим, Вы выписываете счет для иностранного потребителя, в котором одни суммы представлены в долларах, а другие в евро. Использование двух приведенных ниже объектов форматирования не является решением задачи. NumberFormat dollarFormatter = NumberFormat.getCurrencyInstance (Locale.US); NumberFormat euroFormatter = NumberFormat.getCurrencyInstance (Locale.GERMANY); Счет, содержащий такие значения, как $100,000 и 100.000€, будет выглядеть достаточно странно, поскольку символы разделителей групп разрядов отличаются. Для управления форматированием денежных сумм следует использовать класс Currency.Для получения объекта Currency необходимо передать статическому методу Currency.getInstance () идентификатор валюты. Затем необходимо вызвать метод setCurrency () каждого объекта форматирования. Ниже показано, как настроить объект форматирования евро для американсого потребителя. NumberFormat euroFormatter = NumberFormat.getCurrencyInstance (Locale.US); euroFormatter.setCurrency (Currency.getInstance ("EUR")); Идентификаторы валют определены стандартом ISO 4217. Некоторые из них приведены в таблице.
Методы пакета java.util.Currency
Форматирование даты и времени DateFormatПри форматировании даты и времени в соответствии с региональными стандартами следует иметь в виду четыре особенности:
Для учета перечисленных возможносте в Java имеется класс DateFormat, который используется почти также, как и класс NumberFormat. В первую очередь следует сформировать объект регионального стандарта. Для получения массива региональных стандартов, поддерживающих формат даты, можно использовать предлагаемый по умолчанию статический метод getAvailableLocales (). Далее необходимо вызвать один из трех фабричных методов: fmt = DateFormat.getDateInstance (dateStyle, loc); fmt = DateFormat.getTimeInstance (timeStyle, loc); fmt = DateFormat.getDateTimeInstance (dateStyle, timeStyle, loc); Для указания нужного стиля предусмотрен параметр, в качестве которого задается одна из следующих констант: DateFormat.DEFAULT; DateFormat.FULL ; // Wednesday, Septemer 15 2004, 8:15:03 pm // для регионального стандарта США DateFormat.LONG ; // Septemer 15, 2004 8:15:03 pm // для регионального стандарта США DateFormat.MEDIUM ; // Sep 15, 2004 8:15:03 pm // для регионального стандарта США DateFormat.SHORT ; // 9/15/04 8:15 pm // для регионального стандарта США Представленные выше фабричные методы возвращают объект, который можно использовать для форматирования даты. Date date = new Date (); String s = fmt.format (date); Для преобразования строки в дату используется метод parse(), который работает аналогично одноименному методу класса NumberFormat. Например, приведенный ниже код преобразует строку, введенную пользователем в поле редактирования; при этом учитываются региональный настройки по умолчанию: TextField inputField; . . . DateFormat fmt; fmt = DateFormat.getDateInstance (DateFormat.MEDIUM); Date input = fmt.parse (inputField.getText ().trim ()); В случае некорректного ввода даты попытка преобразования приведет к генерации исключения ParseException (). Следует отметить, что в начале строки, подлежащей преобразованию в дату также не допускаются пробелы. Для их удаления следует вызвать метод trim (). Любые символы, котрые располагаются после даты, игнорируются. К сожалению, пользователь должен вводить дату в конкретном формате. Например, если установлен тип представления даты MEDIUM в региональном стандарте США, то предполагается, что введенная строка должна иметь вид Sep 18, 1997. Но если пользователь введет строку Sep 18 1997 (без запятой) или 9/18/97 (в кратком формате), то это приведет к ошибке преобразования. Для интерпретации неточно указанных дат предусмотрен флаг lenient. Если данный флаг установлен, то неверно заданная дата February 30, 1999 будет автоматически преобразована в дату March 2, 1999. Такое поведение вряд ли можно считать безопасным, поэтому данный флаг следует отключить. В этом случае, при попытке пользователя ввести некорректное сочетание дня, месяца и года во время преобразования строки в дату будет сгенерировано исключение IllegalArgumetException. Пакеты ресурсов resourcesПри локализации приложений необходимо переводить огромное количество сообщений, надписей интерфейса и т.п. Для упрощения задачи рекомендуется собрать все локализуемые строки в отдельном месте, которое называется ресурсом (resource). В этом случае достаточно отредактировать файлы ресурсов, не трогая исходный код программы. В Java для определения строковых ресурсов используются файлы свойств, а для ресурсов других типов создаются классы ресурсов.
Определение файла ресурсов ResourceBundleДля локализации приложений создаются так называемые пакеты ресурсов (resource bundle). Каждый пакет представляет собой файл свойств или класс, который описывает элементы, специфические для конкретного регионального стандарта (например, сообщения, надписи и т.д.). В каждый пакет помещаются ресурсы для всех региональных стандартов, поддержка которых предполагается в программе. Для именования пакетов ресурсов используются специальные соглашения. Например, ресурсы, специфические для Германии, помещаются в файл с именем имяПакета_de_DE, а ресурсы, общие для стран, в которых используется немецкий язык, размещаются в классе имяПакета_de. Общие правила таковы : ресурсы для конкретной страны именуются по принципу: имяПакета_язык_СТРАНА Имя файла ресурсов для конкретного языка формируется так : имяПакета_язык Ресурсы, применяемые по умолчанию, помещаются в файл, имя которого не содержит суффикса. Для загрузки пакета ресурсов используется метод getBundle(). ResourceBundle bundle; bundle = ResourceBundle.getBundle("ProgramResources", currentLocale) Метод getBundle () пытается загрузить информацию из пакета ресурсов, которая соответствует языку, расположению и варианту текущего регионального стандарта.Если попытка загрузки окончилась неудачей, последовательно отбрасывается вариант, страна и язык. Затем осуществляется поиск ресурса, соответствующего текущему региональному стандарту, и происходит обращение к пакету ресурсов по умолчанию. Если и эта попытка завершается неудачей, генерируется исключение MissingResourceException. Таким образом, метод getBundle () пытается загрузить первый доступный ресурс из перечисленных пакетов : имяПакета_трс_язык_трс_СТРАНА_трс_вариант имяПакета_трс_язык_трс_СТРАНА имяПакета_трс_язык имяПакета_рсу_язык_рсу_СТРАНА_рсу_вариант имяПакета_рсу_язык_рсу_СТРАНА имяПакета_рсу_язык имяПакета Здесь используются сокращения :
Даже, если метод getBundle () находит пакет, например имяПакета_de_DE, он продолжает искать пакеты имяПакета_de, имяПакета. Если такие пакеты существуют, то они становятся родительскими по отношению к пакету имяПакета_de_DE в иерархии ресурсов. Родительские классы нужны в тех случаях, когда необходимый ресурс не найден в пакете имяПакета_de_DE, и выполняется поиск ресурса в пакетах имяПакета_de, имяПакета. Другими словами, поиск ресурса проверяется последовательно во всех пакетах до первого вхождения. Очевидно, что это очень полезный механизм, однако для его реализации вручную программисту пришлось бы выполнить большой объем рутинной работы. Средства поддержки пакетов ресурсов Java автоматически находят ресурсы, наилучшим образом соответствующие конкретному региональному стандарту. Для включения в существующую программу новых локальных настроек необходимо всего лишь дополнить соответствующие пакеты ресурсов. Создавая приложения, не обязательно помещать все ресурсы в один пакет. Можно создать один пакет для надписей на кнопках, другой - для сообщений об ошибках и т.д. Файлы свойств propertiesДля интернационализации строк необходимо все строки поместить в файл свойств, например MyPackage.properties. Файл свойств - это обычный текстовый файл, каждая строка которого содержит ключ и значение. Пример содержимого такого файла приведен ниже : colorName=black PageSize=210x297 buttonName=Insert Имя файла выбирается по принципу, описанному в предыдущем разделе. MyPackage.properties MyPackage_en.properties MyPackage_de_DE.properties Для загрузки пакета ресурсов из файла свойств применяется приведенное ниже выражение : ResourceBundle bundle; bundle = ResourceBundle.getBundle("MyPackage", locale); Поиск конкретной строки выполняется следующим образом : String label = bundle.getString ("PageSize"); Файлы свойств могут содержать только ASCII-символы. Для размещения в них сомволов в кодировке Unicode следует использовать формат \uxxxx. Например, строка 'colorName=Зеленый' для кириллицы будет иметь вид colorName=\u0417\u0435\u043B\u0435\u043D\u044B\u0439 Классы, реализующие пакеты ресурсовДля поддержки ресурсов, не являющихся строками, необходимо определить классы, являющиеся подклассами класса ResourceBundle. Выбор имен таких классов осуществляется в соответствии с соглашениями об именовании, например: MyProgrammResource.java MyProgrammResource_en.java MyProgrammResource_de_DE.java Для загрузки класса используется тот же метод getBundle (), что и для загрузки свойств. ResourceBoundle boundle = ResourceBoundle.getBundle ("MyProgrammResource", locale); Если два пакета ресурсов, один из которых реализован в виде класса, а другой в виде файла свойств имеют одинаковые имена, то при загрузке предпочтение отдается классу. В каждом классе, реализующем пакет ресурсов, поддерживается таблица поиска. Для получения значения используется строка-ключ. Color background; double[ ] paperSize; background = (Color) bundle.getObject("backgroundColor"); paperSize = (double[ ])bundle.getObject("defaultPaperSize"); Самый простой способ реализации пакета ресурсов - создание подкласса ListResourceBundle. Класс ListResourceBundle позволяет помещать все ресурсы в массив объектов и выполнять поиск. Подкласс класса ListResourceBundle должен иметь следующую структуру: public class имяПакета_язык_СТРАНА extends ListResourceBundle { private static final Objects[][] contents = { {ключ1, значение1}, {ключ2, значение2}, . . . } public Object[][] getContents () { return contents; } } Пример классов, созданных на базе ListResourceBundle, приведен ниже. // Листинг примера использования ListResourceBundle public class ProgramResources_de extends ListResourceBundle { private static final Objects[] [] contents = { {"backgroundColor", Color.black}, {defaultPaperSize, new double[] {210, 297}} } public Object[][] getContents () {return contents; } } public class ProgramResources_en_US extends ListResourceBundle { private static final Objects[][] contents = { {"backgroundColor", Color.blue}, {defaultPaperSize, new double[] {216, 279}} } public Object[][] getContents () {return contents; } } Класс пакета ресурсов можно также создать как подкласс класса ResourceBundle. В этом случае необходимо реализовывать два метода, предназначенные для получения объекта Enumeration,содержащего ключи, и для извлечения значения, соответствующего конкретному ключу. Enumeration <String> getKeys (); Object handleGetObject (String key); Метод getObject () класса ResourceBundle вызывает определяемый разработчиком метод handleGetObject (). Методы пакета java.util.ResourceBundle
Форматирование сообщений MessageFormatВ библиотеке Java содержится класс MessageFormat, который форматирует текст, содержащий фрагменты, представленные посредством переменных. Например : String template = "On {2}, a {0} destroyed {1} houses and caused {3} of damage."; В данном примере номера в фигурных скобках используются как "заполнители" для реальных имен и значений. Статический метод MessageFormat.format () позволяет подставить значения переменных. В JDK 5.0 поддерживаются методы с переменным числом параметров: таким образом, подстановка может быть выполнена так, как показано ниже. String message; * * * message = MessageFormat.format (template, "hurricane", 99, new GregorianCalendar (1999, 0, 1) .getTime (), 10.0E7); В более старых версиях JDK необходимо было помещать значения в массив Object []. В рассматриваемом примере переменная {0} замещается значением "hurricane", переменная {1} заменяется значением 99 и т.д. Статический метод format () форматирует значения с учетом текущего регионального стандарта. Для того, чтобы использовать класс MessageFormat с произвольными региональными настройками, необходимо поступить следующим образом : MessageFormat mf = new MessageFormat (pattern locale); String msg = mf.format (new Object[] { значения }); Здесь вызывается метод format суперкласса Format. К сожалению, класс MessageFormat не предоставляет аналогичный метод, обеспечивающий работу с переменным числом параметров. В результате обработки строки, рассматриваемой в качестве примера, будет получено следующее сообщение: On 1/1/99 12:00 АМ, a hurricane destroyed 99 houses and caused 100,000,000 of damage. Результат можно преобразовать, если сумму ущерба представить в денежных единицах, а дату с учетом формата: String template = "On {2,date,long}, a {0} destroyed {1} houses and caused {3,number,currency} of damage."; В результате будет получено сообщение: On January 1, 1999, a hurricane destroyed 99 houses and caused $100,000,000 of damage. В составе переменной допускается задавать тип и стиль, которые разделяются запятыми. Допустимыми значениями являются следующие типы : number, time, date, choice. Если указан тип number, то возможны следующие стили: integer, currency, percent. Кроме того, в качестве стиля может быть указан шаблон числового формата, например $,##0. Дополнительную информацию по данному вопросу можно найти в описании класса DecimalFormat. Для типа time и date может быть указан один из следующих стилей : short, medium, long, full. Аналогично числам, в качестве стиля может быть использован шаблон даты. Допустимые форматы подробно рассматриваются в описании класса SimpleDateFormat. Форматы выбора (тип choice) имеют более сложную структуру и подробно рассматриваются далее. Методы класса MessageFormat
Класс java.text.Format имеет метод String format (Object object), который форматирует заданный объект в соответствии с правилами, определенными посредством текущего объекта форматирования. В процессе работы данный метод обращается к методу format (object, new StringBuffer (), new FieldPosition (1)).toString (). Формат выбора choiceИспользование формата выбора предполагает определение последовательности пар значений, каждая из которых содержит нижнюю границу и строку подстановки. Нижняя граница и строка подстановки разделяются символами #, а для разделения пар значений используется символ '|'. Ниже приведен пример переменной с указанием формата выбора. {1, choice, 0#no houses | 1#one house | 21 houses} Результаты форматирования, в зависимости от значения {1}, представлены в следующей таблице.
Может возникнуть вопрос, а зачем в форматируемой строке дважды указывается переменная {1}? Когда для этой переменной применяется формат выбора и значение оказывается большим или равным 2, возвращается выражение "{1} houses". Оно форматируется снова и включается в результирующую строку. Данный пример показывает, что разработчики формата выбора приняли не самое лучшее решение. Если есть два варианта форматируемых строк, то для их разделения достаточно двух граничных значений, но согласно формату нужно задать три таких значения. Наименьшее из них никогда не используется. Синтаксис мог бы быть более понятным, если бы границы указывались между вариантами значений, например следующим образом: no houses | 1|one house | 2{1} houses // к сожалению данный формат не поддерживается С помощью символа '<' можно указать, что предполагаемый вариант должен быть выбран, если нижняя граница строго меньше значения. Завершая пример о последствиях стихийного бедствия, необходимо поместить строку с условиями выбора внутри исходной строки сообщения. В результате получится следующая конструкция: String pattern = "On {2, date, long}, {0} destroyed {1, choice, 0#no houses | 1#one house | 21 houses} " + " and caused {3, number, currency} of damage."; В немецком варианте она будет выглядет иначе. String pattern = "{0} zerstörte am {2, date, long} {1, choice, 0#kein Haus | 1#ein Haus | 21 Häuser} " + " und richtete einen Shaden von {3, number, currency} an."; Примечательно, что последовательность слов в английском и немецком вариантах разная, но методу format передается тот же самый массив объектов. Под требуемый порядок слов подстраивается только последовательность появления переменных. Наверх |