Java 2 Micro Edition (J2ME)

       

Использование текстовых файлов приложения для определения локализованных ресурсов


Второй подход к локализации использует программно определяемые текстовые файлы, которые содержат локализуемые атрибуты. Приложение с помощью данной схемы может, например, определить один файл локализованных атрибутов для каждой региональной настройки. Схема имен может соответствовать обозначению региональной настройки, например, en_US.txt для английской, fr_FR.txt для французской, ja_JP.txt для японской и так далее. В листинге 9.4 показан один пример такого файла, содержащего пары «ключ-значение» локализованных строк.

Листинг 9.4. Имя данного файла - fr_FR.txt. Он состоит из франкоязычных версий строк приложения

alert: Alerte

alert_title: Bouton Presse alert_text: Le bouton a ete presse

cancel: Quitter exit: Sortie

greeting: Mon troisieme MIDlet!

help: Aider

item: Item

menu: Menu

ok: OK

sayhi: Dis bonjour

screen: Ecran

stop: Arreter



Mtle: A116, tout le Monde

Этот подход по существу тот же самый, что и предыдущий, за исключением того, что теперь вы должны создавать и поддерживать файл ресурса. Любые файлы ресурсов, которые вы создаете, должны быть частью JAR приложения. Вспомните, что спецификация MIDP запрещает прямой доступ к файлам на родной платформе.

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

В листингах 9.5 и 9.6 содержится код, который реализует одну возможную разработку, которая использует файлы текстовых ресурсов. Этот код считывает файлы, отформатированные подобно тому, что показан в листинге 9.4.

Листинг 9.5. Класс I18NDemo2 использует потоки для чтения файлов текстового ресурса. Реализация getResource () теперь отражает новую разработку для извлечения ресурсов из файлов, находящихся в файле JAR приложения


1 import javax.microedition.midlet.MIDlet;

2

3 import javax.microedition.Icdui.Display;

4 import javax.microedition.Icdui.Displayable;

5 import javax.microedition.Icdui.Form;

6

7 import java.io.DatalnputStream;

8 import Java.io.InputStream;

9 import Java.io.InputStreamReader;

10 import Java . io . EOFException;

11 import Java.io.lOException;

12 import Java.io.Reader;

13 import Java.io.UTFDataFormatException;

14 import Java.io.UnsupportedEncodingException;

15

16 import Java.util.Hashtable;

17

18 /**

19 Вторая версия приложения HSNDemo.

20

21 <р>Эта версия'также дехонстрирует простой способ определения

22 локализованных ресурсов. Она считывает файл, который является

23 частью файла JAR приложения (не файла JAD)

24 для загрузки локализованных ресурсов. Файл

25 состоит из набора пар «ключ-значение», одной на строку,

26 которые представляют локализованные строки.

27 MID-лет должен затем проанализировать содержимое файла

28 и создать внутреннюю хэшированную таблицу для поиска.

29

30 <р>Этот подход требует слишком много усилий по обработке

31 потока, который содержит файл

32 локализованных ресурсов. Более того, этот подход

33 не отвечает за локализацию ресурсов, которые

34 не являются строками.

35 */

36 public class I18NDemo2 extends MIDlet

37 {

38 // Файл, который содержит ресурсы для

39 // активной локальной настройки.

40 private String resourceFile;

41

42 // Региональная настройка, указанная для выполнения данного

43 // MID-лета.

44 private String locale;

45

46 // Символьная кодировка, установленная по умолчанию,

47 // используемая данной платформой.

48 private String encoding;

49

50 // HashTable, которая содержит локализованные

51 // ресурсы.

52 private Hashtable resources = new Hashtable () ;

53

54 // Displayable. Этот компонент отображается

55 // на экране.

56 private HelloForm2 form;

57

58 // Экземпляр Display. Данный объект управляет всеми

59 // компонентами Displayable данного MID-лета.



60 private Display display;

61

62 // Экземпляр данного MID-лета.

63 private static I18NDemo2 instance;

64

65 /**

66 Конструктор No-arg.

67 */

68 public I18NDemo2()

69 {

70 super();

71 instance = this;

72 }

73

74 /*"

75 Получает экземпляр данного класса, который существует

76 в действующем приложении.

77

78 @выдает экземпляр, созданный при запуске

79 приложения..

80 */

81 public static I18NDemo2 getlnstance ()

82 {

83 return instance;

84 {

85

86 /**

87 Запускает MID-лет. Получает текущее название

88 региональной настройки. Использует его для создания

89 имени файла, который содержит локализованные

90 ресурсы региональной настройки. Файл ресурса

91 находится в файле JAR приложения.

92 */

93 public void startApp()

94 {

95 // Извлекает региональную настройку из программного

96 // обеспечения AMS. Региональная настройка должна быть

97 // установлена до выполнения данного MID-лета.

98 locale =

99 System.getProperty("microedition.locale");

100

101 // Названия файлов локализованных ресурсов, соответствующих

102 // форме: <язык>_<страна>.txt.

103 // Создает строку имени файла и передает ее в

104 // метод, который открывает файл и извлекает

105 // содержимое.

106 resourceFile = locale + ".txt";

107 int status = loadResources(resourceFile);

108

109 if (status < 0)

110 {

111 quit();

112 return;

113 }

114

115 // Создаем элемент Displayable. Получаем

116 // локализованную String, которая представляет

117 // заголовок Form.

118 String formTitle = getResource ("title" );

119 form = new HelloForm2(formTitle);

120

121 // Это приложение просто отображает одну .

122 // форму, созданную выше.

123 display = Display.getDisplay (this);

124 display.setCurrent(form);

125 }

126

127 /**

128 Загружает определенные пользователем ресурсы приложения

129 из указанного файла. Файл является частью файла JAR

130 приложения, расположенного на реальном устройстве.

131 J2MEWTK хранит файл в файле JAR приложения, расположенном



132 в директории приложения bin/.

133

134 @param file - имя определенного пользователем файла

135 ресурса приложения.

136 */

137 private int loadResources(String file)

138 {

139. Class с = getClass () ;

140

141 if (file == null)

142 {

143 return -1 ;

144 }

145 InputStream is = null;

146 is = с.getResourceAsStream(file);

147 if (is == null)

148 {

149 return -1;

150 }

151 Reader reader = new InputStreamReader(is);

152 processStream(reader);

153 return 0;

154 }

155

156 /**

157

158 */

159 private void processStream(Reader stream)

160 {

161 if (stream == null)

162 {

163 return;

164 }

165 StringBuffer key = new StringBuffer();;

166 StringBuffer value = new StringBuffer ();;

167 while (true)

168 {

169 // Считываем строку. Предполагается, что каждая строка

170 // содержит ключ и значение,

171 // отделенные двоеточием. Если -1, значит

172 // мы достигли конца файла.

173 key.deletef(), key.length());

174 value.delete(0, value.length());

175 int status = readLine(key, value, stream);

176 if (status == -1)

177 {

178 break;

179 }

180

181 // Вставляем этот ресурс в хэшированную таблицу

182 // ресурсов приложения.

183 resources.put(key, value);

184 }

185 }

186

187 /**

188 Считывает и обрабатывает следующую не пустую строку

189 из потока. Формат строки ожидается следующий

190 <ключ>[ \t]*:[ и]*<значение>, где

191 <ключ> and <значение> являются метками, состоящими

192 из буквенных символов или знаков пунктуации, но не

193 из пустых знаков пробела.

194 */

195 private int readLine(StringBuffer key,

196 StringBuffer value,

197 Reader stream)

198 {

199 if (key == null || value == null ||

200 stream == null)

201 {

202 return -1;

203 }

204

205 try

206 {

207 char c;

208 while (true)

209 {

210 // Пропускаем символы новой строки.

211 while (true)

212 {

213 с = (char) stream.read ();

214 if (c == r\n')

215 {

216 continue;

217 }

218 break;

219 }

220

221 if (lisWhiteSpace(c) Si !isDelimeter(c))



222 {

223 key.append(c);

224 }

225

226 // Пропускаем впередиидущий пробел.

227 while (true)

228 {

229 с = (char) stream.read();

230 if (isWhiteSpace (c))

231 {

232 continue;

233 }

234 break;

235 }

236

237 if (lisWhiteSpace(c) S& !isDelimeter(c))

238 {

239 key.append (с);

240 }

241

242 // Считываем ключ.

243 while (true)

244 {

245 с = (char) stream.read();

246 if (isWhiteSpace(c) II isDeliraeter(c))

247 {

248 break;

249 }

250 else

251 {

252 key.append(c);

253 }

254 }

255

256 // Пропускаем пробел, идущий перед или

257 // после символа.

258 while (true)

259 {

260 с = (char) stream.read();

261 if (isWhiteSpace(c) II isDelimeter(c))

262 {

263 continue;

264 }

265 value.append(c);

266 break;

267 }

268

269 // Считываем остальную часть значения метки.

270 while (true)

271 {

272 с = (char) stream.read();

273 if (c == '\n')

274 {

275 break;

276 }

277 else

278 {

279 value.append(c);

280 }

281 }

282 break;

283 }

284 }

285 catch (lOException ioe)

286 {

287 ioe.printStackTrace() ;

288 return -1;

289 }

290 return 0;

291 }

292

293 /**

294

295 */

296 private boolean isWhiteSpace(char c)

297 {

298 if (c == ' ' И с == '\t')

299 {

300 return true;

301 }

302 else

303 {

304 return false;

305 }

306 }

307

308 /**

309

310 */

311 private boolean isDelimeter(char c)

312 {

313 if (c == ':')

314 {

315 return true;

316 }

317 return false;

318 }

319

320 /**

321 Выдает значение, связанное с указанным

322 ключом из пакета ресурсов приложения.

323

324 @param key - ключ пары «ключ-значение».

325

326 @выдает значение, связанное с

327 указанным ключом.

328 */

329 private String getResource(String key)

330 {

331 if (resources == null)

332 {

333 return null;

334 }

335 return (String) resources .get (-key) ;

336 }

337

338 /**

339 Прекращает выполнение. Запрашивает реализацию

340 на завершение данного MID-лета.

341 */

342 public void quit()

343 {

344 notifyDestroyed ();



345 }

346

347 public void destroyApp(boolean destroy)

348 {

349

350 }

351

352 public void pauseApp()

353 {

354

355 }

356 }

Листинг 9.6. Класс HelloForm2 теперь использует API I18Nderao2.getResource() для извлечения локализованных ресурсов

1 import javax.microedition.midlet.MIDlet;

2

3 import javax.microedition.Icdui.Alert;

4 import javax.microedition.Icdui.AlertType;

5 import javax.microedition.Icdui.Command;

6 import javax.microedition.Icdui.CommandListener;

7 import javax.mi'croedition. Icdui .Display ;

8 import javax.microedition.Icdui.Displayable;

9 import javax.microedition.Icdui.Form;

10

11 /**

12 Данный класс определяет Form, которая отображает некоторый

13 простой текст и меню команд. Цель данного класса

14 продемонстрировать интернационализацию и локализацию

15 видимых пользователю атрибутов. Он работает с классом

16 I18NDemo2.

17 */

18 public class HelloForm2 extends Form

19 {

20 // Заголовок даннвй Form, устанавливаемый по умолчанию.

21 private static final String DEFAULTJTITLE =

22 "Hello, World";

23

24 // Блок прослушивания команд, который обрабатывает

25 // командные события в данной Form.

26 private MyCommandListener cl = new

27 MyCommandListener (1;

28

29 // Экземпляр дисплея, связанный с данным

30 // MID-летом.

31 Display display;

32

33 // Ссылка на связанный с данным объектом

34 // объект MID-лета.

35 IlSNDemo midlet;

36

37 // Уведомление, отображаемое в ответ на активацию

38 // некоторой из команд данной Form.

39 Alert alert;

40

41 private Command showAlert;

42 private Command sayHi;

43 private Command cancel;

44 private Command exit;

45 private Command help;

46 private Command item;

47 private Command ok;

48 private Command screen;

49 private Command stop;

50

51 /**

52 Конструктор No-arg. Устанавливает заголовок

53 по умолчанию данной формы.

54 */

55 HelloForm2()

56 {

57 this(DEFAULT_TITLE);

58 }

59

60 /**

61 Конструктор.

62

.63 @param title - Заголовок данной Form.



64 */

65 KelloForm2(String title)

66 {

67 super (title);

68

69 midlet = IlSNDemo.getlnstance();

70

71 // Добавляет строковый элемент в форму.

72 String msg = midlet.getResource("greeting");

73 append (msg);

74

75 display = Display.getDisplay(midlet);

76

77 // Добавляет MyCommandListener в Form для прослушивания

78 // события нажатия клавиши «Back», которое должно

79 // создавать всплывающее уведомление Alert.

80 setCommandLiscener (cl) ;

81

82 showAiert = new

83 Command(midlet.getResource("alert"),

84 Command.SCREEN, 1);

85 addCommand(showAlert);

86

87 sayHi = new

88 Command(midlet.getResource("sayhi") ,

89 Command.SCREEN, 1) ;

90 addCommand(sayHi);

91

92 cancel = new

93 Command(midlet.getResource("cancel"),

94 Command.SCREEN, 1);

95 addCommand(cancel);

96

97 exit = new

98 Command(midlet.getResource("exit"),

99 Command.SCREEN, 1);

100 addCommand(exit);

101

102 help = new

103 Command(midlet.getResource("help"),

104 Command.SCREEN, 1);

105 addCommand(help);

106

107 item = new

108 Command(midlet.getResource ("item"),

109 Command.SCREEN, 1);

110 addCommand(item);

111

112 ok = new

113 Command(midlet.getResource("ok"),

114 Command.SCREEN, 1) ;

115 addCommand(ok);

116

117 screen = new

118 Command(midlet.getResource("screen") ,

119 Command.SCREEN, 1);

120 addCommand(screen);

121

122 stop = new

123 Command(midlet.getResource("stop"),

124 Command.SCREEN, 1);

125 addCommand(stop);

126 }

127

128 // Данный класс просто прослушивает активацию

129 // какой-либо команды. Экземпляр HelloForm

130 // устанавливает экземпляр данного класса как

131 // свой блок прослушивания команд. Экземпляр

132 // объекта не проверяет информацию команды,

133 // а просто отображает модальное Alert, показывающее,

134 // что экранная клавиша была активирована пользователем.

135 public class MyCommandListener

136 реализует CommandListener



137 {

138 public void commandAction(Command c,

139 Displayable d)

140 {

141 String title =

142 midlet.getResource("alert_title") ;

143 String msg = midlet.getResource("alert_text");

144

145 if (с == showAlert)

146 {

147 alert = new Alert(title,

148 msg,

149 null, AlertType.INFO);

150 alert.setTimeout(Alert.FOREVER);

151 display.setCurrent(alert, HelloForm2.this);

152 }

153 else if (c == sayHi)

154 {

155 alert = new Alert(title,

156 msg,

157 null, AlertType.INFO);

158 alert.setTimeout(Alert.FOREVER);

159 display.setCurrent(alert, HelloForm2.this);

160 }

161

162 if (c == exit)

163 {

164 I18NDemo.getInstance-() .destroyApp (true) ;

165 }

166 }

167 }

168 }

Наиболее проблематичным аспектом данного подхода является то, что вы, разработчик, должны создать инфраструктуру, которая позволит вашим приложениям считывать и анализировать файлы ресурсов. Также приложение должно создавать структуры внутренних данных, которые содержат локализованные ресурсы, считанные из файлов. Самым проблематичным аспектом создания этой инфраструктуры является предоставление адекватной обработки потоков, особенно обработки потоков для поддержки считывания значений строковых атрибутов. Метод MIDlet.getAppProperty(), использовавшийся в предыдущей схеме, основанной на файле JAD, извлекает информацию об обработке потоков. Но в данной схеме вы должны проделать всю эту работу самостоятельно.

Метод Class.getResourceAsStream(String name) является единственным способом, с помощью которого MID-лет может считывать файл из JAR приложения. Параметр имени представляет собой имя файла без информации о пути. Этот метод выдает объект java.io.InputStream, который является байтовым потоком.

Вы должны преобразовать этот байтовый поток в символьный для того, чтобы считывать значения строковых атрибутов в вашей программе. Единственный практичный способ преобразовать байтовые потоки в символьные - это использовать класс java.io.InputStreamReader. Вы создаете экземпляр данного класса, пересылая ваш объект InputStream в конструктор InputStreamReader. В строках с 137 до 154 листинга 9.5 символьный поток срздает определяемый приложением метод loadResources ().



Чтобы преобразовывать из байтов в символы, вы должны знать символьную кодировку файла ресурса, который вы считываете. В листинге 9.5 происходит преобразование из кодировки ISO8859-1 (используемой файлом en_US.txt) в уникод. При считывании символьных данных в программу конечной кодировкой всегда является уникод. Java всегда представляет символы и строки внутренне с помощью уникода.

Первая форма конструктора InputStreamReader, показанная в таблице 9.1 и использованная в листинге 9.5, преобразует из символьной кодировки, устанавливаемой платформой по умолчанию, в уникод. Если ваш файл ресурсов использует кодировку, отличную от кодировки, используемой платформой по умолчанию, вы должны использовать второй конструктор InputStreamReader, который принимает аргумент, указывающий- кодировку потока, считываемого вами.

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

  • ключи и значения атрибутов должны быть кодированы с помощью одной и той же символьной кодировки;


  • все файлы ресурсов для всех региональных настроек должны использовать одну символьную кодировку.


  • Лучше всего использовать одну символьную кодировку для всего файла. В противном случае вам понадобится создать два символьных потока: один для анализа ключей атрибутов и второй для анализа значений. Подобная схема добавляет вашей обработке потоков ненужный уровень сложности.

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



    Двумя практичными вариантами символьных кодировок являются последовательности переключения кодов UTF-8 и Unicode Java. UTF-8 - это код изменяющейся ширины, который поддерживает определения символьной кодировки ASCII. Он вмещает все символы всех языков. К несчастью, потоковые классы MIDP не имеют удобных методов, таких, как DatalnputStream.readUTFO J2SE, для считывания строк UTF. Итак, вам все равно придется выполнять анализ потока собственноручно. Другая сложность заключается в том, что теперь вам придется записывать ваши файлы ресурса в формате UTF-8. Поэтому вам нужны текстовые редакторы и другие инструменты, которые поддерживают создание файлов, кодированных в UTF-8.

    Простейшее решение - использовать последовательности переключения кода Unicode Java для кодирования значений строковых атрибутов. Каждая последовательность переключения кода представляет собой уникальный символ уникода. У этого подхода есть два преимущества. Во-первых, вы можете записывать эти последовательности в файл как символы ASCII. Во-вторых, уникод поддерживает все языки. В листинге 9.4 используется ISO8859-1. Он адекватен для франкоязычных ресурсов, но в то же время он может не поддерживать корейский язык, например, тогда как уникод поддерживает. Вы можете использовать некоторые другие многобайтные кодировки, но затем вам придется положиться на редакторы методов ввода и другие инструменты для считывания и записи файла ресурса в этой кодировке.

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

    Когда вы создали ваш объект InputStreamReader, который является символьно ориентированным потоком, вы можете извлекать символы из него с помощью методов read(). Они происходят из класса, стоящего над ними, java.io.Reader. Эти методы выдают char уникода. В таблице 9.1 перечислены методы класса Reader.



    Таблица 9.1. Конструкторы и методы java.io.Reader

    Название Конструктора и метода java.io. Reader Элемент Описание
    InputStreamReader (InputStream is) Конструктор Создает поток, который преобразует из кодировки, устанавливаемой платформой по умолчанию, в уникод
    InputStreamReader (InputStream is, String enc) Конструктор Преобразует из указанной кодировки в уникод
    void closed Метод Закрывает поток
    void mark(int readAheadLimit) Метод Устанавливает лимит опережающего считывания для метки
    boolean markSupported() Метод Показывает, поддерживает ли данный поток разметку
    int read() Метод Считывает один символ
    int read (char [] cbuf, int off, int len) Метод Считывает количество «len» символов в части символьного массива, начиная от указанной сдвига
    boolean ready () Метод Показывает, должно ли здесь считываться что-либо
    void reset () Метод Переустанавливает поток в последнюю позицию метки
    long skip (long n) Метод Пропускает указанное число символов
    Единственной пропущенной задачей является анализ символов, которые вы считываете. К сожалению, MIDP не имеет удобных классов, таких, как класс StringTokenizer J2SE, который облегчает расстановку меток. Вы должны поэтому самостоятельно проанализировать локализованные ресурсы, один символ за раз, с помощью любой из двух форм перегрузки Reader.read(). Если вы использовали такой формат файла, как в листинге 9.4, самое меньшее, что вы должны сделать затем, это отделить поле ключа от поля значения для каждого атрибута, убрать пробелы и так далее. В листинге 9.5 весь код в строках 127-318 посвящен обработке потока.

    Одним из существенных недостатков данного проектирования интернационализации является дополнительное кодирование, необходимое для создания.анализаторов потоков. Кроме того, что это включает дополнительную работу по разработке данного кода, ваше приложение также ограничивается средой исполнения. Файл ввода/вывода может отнять много рабочих ресурсов и выдать при этом только минимально приемлемую производительность. Это важно рассматривать при разработке приложений MIDP.

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

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


    Содержание раздела