» Главная
eXcode.ru » Статьи » JAVA / JavaScript » Мобильная Java
» Новости
» Опросы
» Файлы
» Журнал



Пользователей: 0
Гостей: 6





Способ локализации мидлетов




Разрабатывая мидлеты, можно писать код, "зашивая" строки сообщений, выводимых на экран, внутрь кода. При этом мидлет сможет общаться с пользователем только на одном языке. Во многих случаях это вполне приемлемо. Если же требуется, чтобы мидлет адаптировался к настройкам устройства и мог выводить сообщения на нескольких языках, то для этого нужно принять дополнительные меры. Как известно, библиотека MIDP не содержит классов, предназначенных для локализации программ, и разработчикам приходится решать эту задачу самостоятельно. В этой статье я описал способ, которым я пользовался при локализации своих мидлетов.

Краткое описание предлагаемого метода

Суть способа, который я использовал, не нова. Строки, выводимые на экран, выносятся во внешний файл. Для сообщений на разных языках создаются разные файлы. В зависимости от языка, на который настроено мобильное устройство, выбирается тот или иной файл, и строки сообщений берутся из него.

Код локализуемой программы

Сначала приведу пример кода программы, которая нуждается в локализации:

import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;

public class test extends MIDlet  {

  private TextBox textbox = null;
  private final static String HELLO_WORLD = "Hello World!!!";

  public test() {
    textbox = new TextBox(null, HELLO_WORLD, 20, 0);
  }

  public void startApp() {
    Display.getDisplay(this).setCurrent(textbox);
  }

  public void pauseApp() {}

  public void destroyApp(boolean unconditional) {}
}

В примере для простоты предполагается, что для того, чтобы поприветствовать мир на любом языке, вполне достаточно 20 символов. Теперь посмотрим, как потребуется изменить код для того, чтобы он мог выводить сообщения на разных языках.

1) В строковой константе оставляем ключ, по которому в дальнейшем будем идентифицировать сообщение:

private final static String HELLO_WORLD = "Hello";
2)

Получаем ссылку на экземпляр класса, который отвечает за работу со всеми сообщениями, выводимыми на экран. Этот класс будет рассмотрен ниже.

LocalStrings ls = LocalStrings.getInstance();
3)

Далее заменяем все обращения к строковой константе на вызов метода класса LocalStrings:

textbox = new TextBox("", ls.getString(HELLO_WORLD), 20, 0);

В результате внесенных изменений получился следующий класс:

import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import ru.quadrate.localization.LocalStrings;

public class test1 extends MIDlet  {

  private TextBox textbox = null;
  private final static String HELLO_WORLD = "Hello";

  public test1() {
    LocalStrings ls = LocalStrings.getInstance();
    textbox = new TextBox("", ls.getString(HELLO_WORLD), 20, 0);
  }

  public void startApp() {
    Display.getDisplay(this).setCurrent(textbox);
  }

  public void pauseApp() {}

  public void destroyApp(boolean unconditional) {}
}

Это то, что касается изменения кода локализуемой программы. При этом читаемость программы не ухудшается. Конструкторы и сигнатуры методов не изменяются.

Класс LocalStrings

Класс реализует дизайн-паттерн singleton, который гарантирует, что в нашей программе будет только один экземпляр этого класса. Класс несет ответственность за выбор языка для сообщений, считывание содержимого соответствующего файла ресурса, и затем служит посредником между ключами, прописанными в программе, и реальными сообщениями.

package ru.quadrate.localization;

import java.util.*;
import java.io.*;

/**
* 

Title: LocalStrings

*

Description: Class is responsible for * program messages localization.

*

Copyright: Copyright Quadrate (c) 2003

* @author Quadrate * @version 1.0 */
public final class LocalStrings { private final String EXTENSION = "strings"; private Hashtable stringsTable = null; private boolean isNotFound; // if no resource files found private static LocalStrings instance = null; /** * Private constructor */ private LocalStrings() { isNotFound = false; loadStrings(); } /** * Creates this class instance if it is not created yet and * returns the link. * @return */ public static LocalStrings getInstance() { if (instance == null) { instance = new LocalStrings(); } return instance; } /** * Returns real message in some language by the * key string specified. * @param key key string * @return message string matching the key specified. * If no value * has been found for the key returns the key itself. * @return */ public String getString(String key) { Object result = isNotFound? key: stringsTable.get(key); return (result == null)? key: (String) result; } /** * Reads device′s locale and loads appropriate messages from * resource file. If there is no file for the locale detected * loads file with messages in English. */ private void loadStrings() { String locale = System.getProperty ("microedition.locale"); // get two first letters which describe language String language = locale.substring(0, 2).toLowerCase(); InputStream in = null; DataInputStream inReader = null; try { in = openResource(language + "." + EXTENSION); if (in == null) { // there is no resource file for this language in = openResource("en" + "." + EXTENSION); isNotFound = in == null; } if (!isNotFound) { // we have opened one of the resource files inReader = new DataInputStream (in); stringsTable = new Hashtable(); String key; String value; while (((key = inReader.readUTF()) != null) && ((value = inReader.readUTF()) != null)) { // read key and value stringsTable.put(key, value); } } } catch (EOFException ex) { // end of file reached } catch (Exception ex) { isNotFound = true; stringsTable = null; } finally { if (inReader != null) { try { inReader.close(); // close DataInputStream } catch (IOException ex) { } } if (in != null) { try { in.close(); // close InputStream } catch (IOException ex) { } } } // if there is no key-value pairs if (stringsTable != null && stringsTable.isEmpty()) { stringsTable = null; isNotFound = true; } } /** * Opens resource file by file name. * @param fileName * @return */ private InputStream openResource (String fileName) { return this.getClass().getResourceAsStream(fileName); } }

Как явствует из кода, класс предполагает, что файлы ресурсов, соответствующие разным языкам, имеют определенные имена и расширения. Кроме того, файлы должны располагаться "рядом" с классом, чтобы он мог их найти. Сами файлы должны быть организованы определенным образом. О том, как подготовить файлы ресурсов, пойдет речь ниже.Сейчас можно только сказать, что файлы созданы с помощью специально написанной программы и выведены в файл методом writeUTF класса DataOutputStream. Сделано это для того, чтобы файл можно было прочитать методом readUTF класса DataInputStream. При чтении предполагается также, что пары key/value располагаются друг за другом. Т.е. все сделано для того, чтобы мобильное устройство не производило никакого парсинга или валидации файла ресурса. Его задача - только прочитать файл и заполнить Hashtable.

Следует отметить, что мы не знаем точно, сколько строк будет считано из файла. При попытке считать строку, когда достигнут конец файла, возникает EOFException. Я не смог найти более "цивилизованного" метода анализа достижения конца файла при чтении методом DataInputStream.readUTF(), чем перехват этого exception-a. Если кто нибудь подскажет - буду благодарен.

Имя файла ресурса соответствует языку, строки для которого он содержит. Точнее, аббревиатуре языка, которая используется в наименовании локали. Например, если устройство настроено на локаль de_DE, то будут взяты первые два символа от названия локали (они определяют язык), будет добавлено расширение "strings" и класс будет искать файл ресурса "de.strings". Таким образом для всех локалей, где используется немецкий язык (DE_CH - Switzerland; DE_AT - Austria; DE_DE - Germany), будут показываться сообщения на немецком. Если файл ресурсов для требуемого языка не найден, то будет предпринята попытка найти файл "en.strings" с сообщениями на английком языке. Если ни одного из файлов ресурсов не найдено, то класс будет в качестве сообщений возвращать значение ключей, прописанных в программе. Поэтому следует писать краткие, но более или менее понятные значения ключей. Это может пригодиться какому-нибудь пользователю, имеющему какое-либо "экстремальное" устройство, которое не смогло считать ни один из файлов ресурсов. В этом случае он увидит по крайней мере ключи и сможет догадаться, что ему хочет сказать программа. Например, для игр этого может быть вполне достаточно.

Как подготовить файл ресурса

Создание файла ресурса подразделяется на две задачи:

  • вынесение ключей и самих сообщений в текстовый файл в кодировке UTF-8
  • конвертация полученного файла, приемлемого для чтения и редактирования человеком, в файл, удобный для мидлета.

Сначала создадим файл со строками, пригодный для конвертации. Формат текстового файла я выбрал следующий:

key=value

key - ключевая строка, зашитая в код мидлета
value - сообщение, выводимое на экран, которое соответствует ключу

Каждая строка файла должна содержать только одну пару ключ/значение. Если требуется, чтобы выводимая на экран строка содержала символ перевода строки, то для этого нужно вписать несколько строк подряд с одинаковым ключом. Такие строки будут объединены, и в местах разрыва строк будет вставлен перевод строки. Например:
siteAdr=My website address:
siteAdr=http://quadrate.astware.com

В конечном итоге, строка для ключа "siteAdr" будет иметь вид: "My website address: http://quadrate.astware.com". Если одинаковые ключи будут располагаться не подряд, то это будет воспринято конвертором как попытка продублировать ключ, а это уже ошибочная ситуация.

Для рассматриваемого примера файл с англоязычными сообщениями будет иметь вид:

Hello=Hello World!!!

А файл с русскоязычными сообщениями будет выглядеть так:

Hello=Здравствуй, мир!!!

Конвертор предполагает, что исходный файл имеет кодировку UTF-8. Работая с файлом в этой кодировке, можно видеть все специфические символы для любого европейского языка, поэтому с редактированием файла проблем быть не должно. Могу подсказать маленькую хитрость, которую я использовал: если вы не владеете тем языком, на который нужно перевести сообщения, и кто-либо из иностранцев согласился сделать это для вас, то создайте файл в кодировке UTF-8, запишите туда исходные сообщения и пошлите этот файл по email с просьбой вписать перевод именно в этот файл. В результате в ответ вы получите, скорее всего то, что нужно. Останется только отредактировать полученный файл. Если послать текст для перевода прямо в теле письма, то наверняка у вас возникнут сложности с кодировкой переведенных сообщений.

Как я уже упоминал, логика работы класса LocalStrings такова, что если для какого-либо ключа соответствующая строка не найдена, то будет возвращен сам данный ключ. Пользуясь этим, в файлы можно не включать те строки, которые совпадают со своими ключами. Это часто происходит для пунктов меню. "Help", "About", "Start", "End", "Pause" и т.д. Эти строки можно удалить по крайней мере из файла ресурса, содержащего сообщения на английском языке, при условии, что они же являются ключами.

Также нужно отметить небольшую особенность в подготовке текстового файла. Обратите внимание на концевые пробелы в строках. Например, если в программе есть форма, на которой есть поле для ввода с label-ом: "Game Field Width: [5 ]". Если вы хотите, чтобы между label-ом и полем ввода был пробел, то лучше всего вписать его в код программы, а не выносить в файл ресурса. Концевые пробелы не видны, и в одном из файлов для какого-нибудь языка вы его обязательно забудете. Я не говорю уже о том, что многие редакторы просто-напросто обрезают концевые пробелы. Но и лишних пробелов тоже вставлять не следует, т.к. они останутся в составе строки и будут показаны на экране.

Конвертор

Будем считать, что вы подготовили файлы с сообщениями в описанном выше формате. Далее нужно просто запустить конвертор, указав в качестве параметров путь к исходному файлу и путь к результирующему файлу. Конвертор работает в соответствии с правилами, приведенными в предыдущем разделе, где описывается, как должен быть отформатирован исходный файл. Проверок на корректность файла в конверторе нет.

package ru.quadrate.localization;

import java.io.*;
import java.util.*;
import java.text.*;

/**
* 

Title: Helper

*

Description: Converts files with messages to * display from human-readable format to * midlet-readable format.

*

Copyright: Copyright Quadrate(c) 2003

* @author Quadrate * @version 1.0 */
public class Helper { private final static String FILE_CHARACTER_SET = "UTF-8"; private final static String DELIMITER = "="; /** * No instances */ private Helper() { } public static void main(String[] args) { if (args.length != 2) { System.out.println("Illegal parameters. Please specify source file and destination file names."); } else { // Open in and out streams File sourceFile = new File(args[0]); File destFile = new File(args[1]); FileInputStream in = null; BufferedReader inReader = null; FileOutputStream out = null; DataOutputStream outWriter = null; try { // open source in = new FileInputStream(sourceFile); in.skip(3); inReader = new BufferedReader(new InputStreamReader (in, FILE_CHARACTER_SET)); // open destination out = new FileOutputStream(destFile); outWriter = new DataOutputStream(out); Hashtable stringsTable = new Hashtable(); readSourceContent(inReader, stringsTable); writeContent(outWriter, stringsTable); } catch (Exception ex) { ex.printStackTrace(); } finally { try { if (in != null) { in.close(); } if (out != null) { out.close(); } } catch (Exception ex) { ex.printStackTrace(); } } } } /** * Writes hashtable content into the stream specified. * @param outWriter * @param stringsTable * @throws IOException */ private static void writeContent(DataOutputStream outWriter, Hashtable stringsTable) throws IOException { for (Enumeration en = stringsTable.keys(); en.hasMoreElements();) { String key = (String) en.nextElement(); String value = (String) stringsTable.get(key); outWriter.writeUTF(key); outWriter.writeUTF(value); } } /** * Reads and parses content of the source file. * @param inReader * @param stringsTable * @throws IOException */ private static void readSourceContent(BufferedReader inReader, Hashtable stringsTable) throws IOException, ParseException { String line = null; String prevKey = null; while ((line = inReader.readLine()) != null) { int delimiterPos = line.indexOf(DELIMITER); if (delimiterPos != -1) { String key = line.substring (0, delimiterPos); String value = line.substring (delimiterPos + 1); if (stringsTable.containsKey(key)) { if (prevKey.equals(key)) { StringBuffer buf = new StringBuffer((String) stringsTable.get(prevKey)); buf.append(" ").append(value); value = buf.toString(); } else { throw new ParseException("Duplicate key: " + key, 0); } } prevKey = key; stringsTable.put(key, value); System.out.println("key: " + key + "; value: " + value); } } } }

В коде можно заметить, что первые три байта из входящего файла просто отбрасываются. Насколько я понял, эти три байта содержат информацию о формате и кодировке файла. Поскольку мы точно знаем, в какой кодировке сделан наш файл, то эти байты можно пропустить. Если я ошибаюсь - напишите мне, как можно было бы сделать более правильно. На самом деле, мне самому не очень нравится это решение, и я занес его в "минусы" предложенного метода (см. ниже).

После обработки файла, сделанного вручную, мы получим файл ресурса, готовый для восприятия мидлетом. Остается положить его в ту же директорию, где будет лежать LocalStrings.class внутри jar файла.

Проверка работы мидлета

Откомпилируем и соберем мидлет. Предположим, что рядом с LocalStrings.class в jar файле находятся два файла ресурсов en.strings и ru.strings. Теперь при запуске этой программы на реальном устройстве или на эмуляторе, в зависимости от выбранной локали, мы будем видеть сообщения на английском или на русском языке.

Резюме

В завершение описания предложенного метода хотелось бы остановиться на его сильных и слабых сторонах, а также дать несколько советов. Некоторые из тезисов уже были изложены в статье.

Минусы:

  • В мидлете считывание строк из файла ресурса всегда заканчивается Exception-ом.
  • При конвертации текстового файла в файл ресурса происходит skip первых трех байт.
  • Имеет место некоторая избыточность - строки ключей повторяются и в коде программы, и в каждом файле ресурса.
  • Нужно аккуратно обращаться с концевыми пробелами в текстовом файле.
  • На мой взгляд, самое неприятное то, что в процессе работы мидлета все строки висят в памяти независимо от того, нужны они будут в ближайшее время или нет. Например, нет смысла постоянно держать в памяти устройства текст help-a к игре. Подавляющее время работы мидлета этот текст не будет нужен. Я полагаю, что этот недостаток является веским основанием для усовершенствования метода. По крайней мере, необходимо разделить строки на "часто используемые" и "редко используемые", при этом первые должны храниться в оперативной памяти, а вторые - считываться из файла ресурса по мере надобности. Для этого, правда, придется несколько усложнить класс LocalStrings.

Плюсы:

  • Пользователь всегда увидит какие-либо строки и сможет работать с программой. В худшем случае это будут ключи, "зашитые" в коде.
  • Если ключ короткий и совпадает с выводимой строкой, то можно убрать эту пару из файла ресурса.
  • Ключи в виде строк облегчают работу. Не теряется читаемость кода и текстовых файлов. Если доступ сделать, например, через цифровой индекс, то писать код и создавать файлы ресурсов будет намного сложнее.
  • Облегчено получение ссылки на класс LocalStrings. Не требуется изменять сигнатуры методов и конструкторов.

Рекомендации:

  • При работе с иностранными языками, имеющими специфические символы, пользуйтесь текстовыми файлами в кодировке UTF-8. Если кто-то из иностранцев согласится помочь вам с переводом сообщений, пошлите ему файл в кодировке UTF-8, содержащий оригинальные строки. Он впишет туда перевод и пришлет его вам обратно. Это снимет проблему с кодировкой.
  • Для того, чтобы проверить соответствие программного кода и файлов ресурсов, можно временно изменить код класса LocalStrings так, чтобы в случае, если для ключа не найдено значение, то возвращался бы не сам ключ, а какая нибудь заметная строка, например "***". Если вы забыли прописать какой либо ключ в файле ресурса, то при работе программы вы обязательно обнаружите это место.
Автор: Антон Чумак
К началу статьи




Добавил: MadvEXДата публикации: 0000-00-00 00:00:00
Рейтинг статьи:3.00 [Голосов 5]Кол-во просмотров: 6918

Комментарии читателей

Всего комментариев: 31

2017-03-17 16:21:38
ELAPPYWEILE
Провод нагревательный - это стальная, медная или алюминиевая жила или несколько жил,
заключенных в оболочку из полимера. Применяется, в основном, в строительстве.
Например, при помощи таких проводов осуществляется подогрев бетона при монолитном строительстве,
обустройство теплых полов, крыш, теплиц и т.д.
Наш сайт: http://www.profmetiz.ru
Предлагаем следующие типы проводов: провод ПНСВ нагревательный, провод ПТПЖ 2,0х1,2; 2,0х0,6 и 2,0х1,8.
(4862)301-015, 307-018

2016-09-20 02:28:35
nnethsa
Металлоизделия: тросы, проволока оцинкованная, электроды, сетка тканая, крепежные изделия.
Изделия из металла: канаты, проволока оцинкованная, сварочные электроды, сетка тканая, крепежные изделия со склада и на заказ. Отгрузка в сжатые сроки автомобильным и железнодорожным транспортом до вашего склада или непосредственно на объекты.
тел./факс (4862) 307-018
телефон (4862) 301-015
http://profmetiz.ru

2016-07-30 05:49:00
tevengof
Предлагаем купить от производителя: полимерная сетка рабица, сетка рабица оцинкованная ?
Производство и поставка: сетка сварная оцинкованная, цены на сетку сварную от лучших производителей на территории России.
Вся продукция сертифицирована и имеет необходимые маркировки.

2016-07-08 13:06:49
homasmi
Доброго времени суток!
Думаю взять крепеж. Кто может что-то посоветовать?
Какая фирма поставляет качественные крепеж?
Находил вот на этом сайте: http://www.stigroup.ru
Цены по-моему нормальные, но я в этом не спец, поэтому если видели дешевле, подскажите где. Где можно найти хорошее соотношение цена-качество?

2012-07-06 10:45:33
Ethictendoche
Ищете изготовление электродов?
Компания Мегапром предлагает купить электроды сварочные цена от лучших производителей на территории России.
Электроды обеспечивют высокие механические свойства сварного соединения и высокую производительность процесса сварки.
Вся продукция сертифицирована и имеет необходимые маркировки.

2012-02-24 19:10:19
Drasiatuith
Предлагаем все размеры фундаментных анкерных болтов ГОСТ 24379.1-80 из марок сталей ст.3, ст.09Г2С и других по пожеланию заказчиков.
Болты фундаментные (анкерный болт) - крепёжная деталь,
в виде прута с резьбовой частью на одном конце и специального приспособления,
подерживающее фундаментный болт внутри фундамента, предназначенная для крепления
строительных конструкций и оборудования.
Фундаментные (анкерные) болты - элементы строительной конструкции,
позволяющие прикрепить её к основанию (фундаменту).
Используются фундаментные болты на всех типах строительства,
от стандартного здания до дамб и атомных электростанций.
Обеспечивают надёжное крепление только к прочным, нехрупким и неэластичным основаниям.
Анкерные болты используются в виде закладных деталей в железобетонных основаниях
для дальнейшего крепления на фундаменте металлоконструкций и оборудования.

2012-01-23 22:24:58
ideriendurn
Привет!
Хочу взять шпильки резьбовые . Кто может что-то посоветовать?
Какая фирма поставляет качественные шпильки?
Цены по-моему умеренные, но я не очень разбираюсь, поэтому если находили дешевле, подскажите где. Где можно найти оптимальное соотношение цена-качество?

2010-10-23 22:29:21
edulliemacefe
Ищете, где купить заклепки стальные, заклепки прайс, ГОСТ 10299, 10300?
Компания ООО Мегапром - поставщик - заклепки прайс лист от лучших производителей на территории России.
Вся продукция сертифицирована и имеет необходимые маркировки.

2010-10-23 04:43:23
piembence
Доброго времени суток!
Думаю взять куплю шпильку . Кто может что-то посоветовать?
Какая фирма поставляет качественные купить шпильки ?
Цены по-моему невысокие, но я не очень разбираюсь, поэтому если покупали дешевле, покажите где. Где можно найти хорошее соотношение цена-качество?

2010-10-22 03:49:50
HyncWentony
Компания Мегапром предлагает калиброванный прокат производства Северсталь метиз ОСПАЗ
со склада г.Орел - производство чугуна ст.10, 20, 35, стА12, 40Х и др.
В наличии огромный ассортимент калиброванного металла - пруток и шестигранник.
Возможно изготовление под заказ дробных размеров и мерных длинн.
Вы можете купить прокат калиброванный разных диаметров и длинн - круги, мотки, прутки.
Ваше имя: *
Текст записи: *
Имя:

Пароль:



Регистрация

Как вы относитесь к блогам?
Не знаю что это такое!
17% (13)
ничего особенного
35% (27)
иногда читаю чужие блоги
27% (21)
постоянно читаю блоги
1% (1)
веду свой блог
5% (4)
считаю блоги двигателем интернета
6% (5)
ЖЖ рулит, фсе остальное ф топку!
9% (7)

Проголосовало: 78
Звонок в службу технической поддержки:
-Как узнать есть ли у меня "WinCih"?
-Переведите дату на 26 апреля...
Рейтинг: 0/10 (0)
Посмотреть все анекдоты