1 #!/usr/bin/env python 2 # Copyright 2013 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """ 7 localize.py -- Generates an output file from the given template replacing 8 variables and localizing strings. 9 10 The script uses Jinja2 template processing library (src/third_party/jinja2). 11 Variables available to the templates: 12 - |languages| - the list of languages passed on the command line. ('-l'). 13 - Each NAME=VALUE define ('-d') can be accesses as {{ NAME }}. 14 - |official_build| is set to '1' when CHROME_BUILD_TYPE environment variable 15 is set to "_official". 16 17 Filters: 18 - GetCodepage - returns the code page for the given language. 19 - GetCodepageDecimal same as GetCodepage, but returns a decimal value. 20 - GetLangId - returns Win32 LANGID. 21 - GetPrimaryLanguage - returns a named Win32 constant specifing the primary 22 language ID. 23 - GetSublanguage - returns a named Win32 constant specifing the sublanguage 24 ID. 25 26 Globals: 27 - IsRtlLanguage(language) - returns True if the language is right-to-left. 28 - SelectLanguage(language) - allows to select the language to the used by 29 {% trans %}{% endtrans %} statements. 30 31 """ 32 33 import io 34 import json 35 from optparse import OptionParser 36 import os 37 import sys 38 from string import Template 39 40 41 # Win32 primary languages IDs. 42 _LANGUAGE_PRIMARY = { 43 'LANG_NEUTRAL' : 0x00, 44 'LANG_INVARIANT' : 0x7f, 45 'LANG_AFRIKAANS' : 0x36, 46 'LANG_ALBANIAN' : 0x1c, 47 'LANG_ALSATIAN' : 0x84, 48 'LANG_AMHARIC' : 0x5e, 49 'LANG_ARABIC' : 0x01, 50 'LANG_ARMENIAN' : 0x2b, 51 'LANG_ASSAMESE' : 0x4d, 52 'LANG_AZERI' : 0x2c, 53 'LANG_BASHKIR' : 0x6d, 54 'LANG_BASQUE' : 0x2d, 55 'LANG_BELARUSIAN' : 0x23, 56 'LANG_BENGALI' : 0x45, 57 'LANG_BRETON' : 0x7e, 58 'LANG_BOSNIAN' : 0x1a, 59 'LANG_BULGARIAN' : 0x02, 60 'LANG_CATALAN' : 0x03, 61 'LANG_CHINESE' : 0x04, 62 'LANG_CORSICAN' : 0x83, 63 'LANG_CROATIAN' : 0x1a, 64 'LANG_CZECH' : 0x05, 65 'LANG_DANISH' : 0x06, 66 'LANG_DARI' : 0x8c, 67 'LANG_DIVEHI' : 0x65, 68 'LANG_DUTCH' : 0x13, 69 'LANG_ENGLISH' : 0x09, 70 'LANG_ESTONIAN' : 0x25, 71 'LANG_FAEROESE' : 0x38, 72 'LANG_FILIPINO' : 0x64, 73 'LANG_FINNISH' : 0x0b, 74 'LANG_FRENCH' : 0x0c, 75 'LANG_FRISIAN' : 0x62, 76 'LANG_GALICIAN' : 0x56, 77 'LANG_GEORGIAN' : 0x37, 78 'LANG_GERMAN' : 0x07, 79 'LANG_GREEK' : 0x08, 80 'LANG_GREENLANDIC' : 0x6f, 81 'LANG_GUJARATI' : 0x47, 82 'LANG_HAUSA' : 0x68, 83 'LANG_HEBREW' : 0x0d, 84 'LANG_HINDI' : 0x39, 85 'LANG_HUNGARIAN' : 0x0e, 86 'LANG_ICELANDIC' : 0x0f, 87 'LANG_IGBO' : 0x70, 88 'LANG_INDONESIAN' : 0x21, 89 'LANG_INUKTITUT' : 0x5d, 90 'LANG_IRISH' : 0x3c, 91 'LANG_ITALIAN' : 0x10, 92 'LANG_JAPANESE' : 0x11, 93 'LANG_KANNADA' : 0x4b, 94 'LANG_KASHMIRI' : 0x60, 95 'LANG_KAZAK' : 0x3f, 96 'LANG_KHMER' : 0x53, 97 'LANG_KICHE' : 0x86, 98 'LANG_KINYARWANDA' : 0x87, 99 'LANG_KONKANI' : 0x57, 100 'LANG_KOREAN' : 0x12, 101 'LANG_KYRGYZ' : 0x40, 102 'LANG_LAO' : 0x54, 103 'LANG_LATVIAN' : 0x26, 104 'LANG_LITHUANIAN' : 0x27, 105 'LANG_LOWER_SORBIAN' : 0x2e, 106 'LANG_LUXEMBOURGISH' : 0x6e, 107 'LANG_MACEDONIAN' : 0x2f, 108 'LANG_MALAY' : 0x3e, 109 'LANG_MALAYALAM' : 0x4c, 110 'LANG_MALTESE' : 0x3a, 111 'LANG_MANIPURI' : 0x58, 112 'LANG_MAORI' : 0x81, 113 'LANG_MAPUDUNGUN' : 0x7a, 114 'LANG_MARATHI' : 0x4e, 115 'LANG_MOHAWK' : 0x7c, 116 'LANG_MONGOLIAN' : 0x50, 117 'LANG_NEPALI' : 0x61, 118 'LANG_NORWEGIAN' : 0x14, 119 'LANG_OCCITAN' : 0x82, 120 'LANG_ORIYA' : 0x48, 121 'LANG_PASHTO' : 0x63, 122 'LANG_PERSIAN' : 0x29, 123 'LANG_POLISH' : 0x15, 124 'LANG_PORTUGUESE' : 0x16, 125 'LANG_PUNJABI' : 0x46, 126 'LANG_QUECHUA' : 0x6b, 127 'LANG_ROMANIAN' : 0x18, 128 'LANG_ROMANSH' : 0x17, 129 'LANG_RUSSIAN' : 0x19, 130 'LANG_SAMI' : 0x3b, 131 'LANG_SANSKRIT' : 0x4f, 132 'LANG_SCOTTISH_GAELIC' : 0x91, 133 'LANG_SERBIAN' : 0x1a, 134 'LANG_SINDHI' : 0x59, 135 'LANG_SINHALESE' : 0x5b, 136 'LANG_SLOVAK' : 0x1b, 137 'LANG_SLOVENIAN' : 0x24, 138 'LANG_SOTHO' : 0x6c, 139 'LANG_SPANISH' : 0x0a, 140 'LANG_SWAHILI' : 0x41, 141 'LANG_SWEDISH' : 0x1d, 142 'LANG_SYRIAC' : 0x5a, 143 'LANG_TAJIK' : 0x28, 144 'LANG_TAMAZIGHT' : 0x5f, 145 'LANG_TAMIL' : 0x49, 146 'LANG_TATAR' : 0x44, 147 'LANG_TELUGU' : 0x4a, 148 'LANG_THAI' : 0x1e, 149 'LANG_TIBETAN' : 0x51, 150 'LANG_TIGRIGNA' : 0x73, 151 'LANG_TSWANA' : 0x32, 152 'LANG_TURKISH' : 0x1f, 153 'LANG_TURKMEN' : 0x42, 154 'LANG_UIGHUR' : 0x80, 155 'LANG_UKRAINIAN' : 0x22, 156 'LANG_UPPER_SORBIAN' : 0x2e, 157 'LANG_URDU' : 0x20, 158 'LANG_UZBEK' : 0x43, 159 'LANG_VIETNAMESE' : 0x2a, 160 'LANG_WELSH' : 0x52, 161 'LANG_WOLOF' : 0x88, 162 'LANG_XHOSA' : 0x34, 163 'LANG_YAKUT' : 0x85, 164 'LANG_YI' : 0x78, 165 'LANG_YORUBA' : 0x6a, 166 'LANG_ZULU' : 0x35, 167 } 168 169 170 # Win32 sublanguage IDs. 171 _LANGUAGE_SUB = { 172 'SUBLANG_NEUTRAL' : 0x00, 173 'SUBLANG_DEFAULT' : 0x01, 174 'SUBLANG_SYS_DEFAULT' : 0x02, 175 'SUBLANG_CUSTOM_DEFAULT' : 0x03, 176 'SUBLANG_CUSTOM_UNSPECIFIED' : 0x04, 177 'SUBLANG_UI_CUSTOM_DEFAULT' : 0x05, 178 'SUBLANG_AFRIKAANS_SOUTH_AFRICA' : 0x01, 179 'SUBLANG_ALBANIAN_ALBANIA' : 0x01, 180 'SUBLANG_ALSATIAN_FRANCE' : 0x01, 181 'SUBLANG_AMHARIC_ETHIOPIA' : 0x01, 182 'SUBLANG_ARABIC_SAUDI_ARABIA' : 0x01, 183 'SUBLANG_ARABIC_IRAQ' : 0x02, 184 'SUBLANG_ARABIC_EGYPT' : 0x03, 185 'SUBLANG_ARABIC_LIBYA' : 0x04, 186 'SUBLANG_ARABIC_ALGERIA' : 0x05, 187 'SUBLANG_ARABIC_MOROCCO' : 0x06, 188 'SUBLANG_ARABIC_TUNISIA' : 0x07, 189 'SUBLANG_ARABIC_OMAN' : 0x08, 190 'SUBLANG_ARABIC_YEMEN' : 0x09, 191 'SUBLANG_ARABIC_SYRIA' : 0x0a, 192 'SUBLANG_ARABIC_JORDAN' : 0x0b, 193 'SUBLANG_ARABIC_LEBANON' : 0x0c, 194 'SUBLANG_ARABIC_KUWAIT' : 0x0d, 195 'SUBLANG_ARABIC_UAE' : 0x0e, 196 'SUBLANG_ARABIC_BAHRAIN' : 0x0f, 197 'SUBLANG_ARABIC_QATAR' : 0x10, 198 'SUBLANG_ARMENIAN_ARMENIA' : 0x01, 199 'SUBLANG_ASSAMESE_INDIA' : 0x01, 200 'SUBLANG_AZERI_LATIN' : 0x01, 201 'SUBLANG_AZERI_CYRILLIC' : 0x02, 202 'SUBLANG_BASHKIR_RUSSIA' : 0x01, 203 'SUBLANG_BASQUE_BASQUE' : 0x01, 204 'SUBLANG_BELARUSIAN_BELARUS' : 0x01, 205 'SUBLANG_BENGALI_INDIA' : 0x01, 206 'SUBLANG_BENGALI_BANGLADESH' : 0x02, 207 'SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_LATIN' : 0x05, 208 'SUBLANG_BOSNIAN_BOSNIA_HERZEGOVINA_CYRILLIC' : 0x08, 209 'SUBLANG_BRETON_FRANCE' : 0x01, 210 'SUBLANG_BULGARIAN_BULGARIA' : 0x01, 211 'SUBLANG_CATALAN_CATALAN' : 0x01, 212 'SUBLANG_CHINESE_TRADITIONAL' : 0x01, 213 'SUBLANG_CHINESE_SIMPLIFIED' : 0x02, 214 'SUBLANG_CHINESE_HONGKONG' : 0x03, 215 'SUBLANG_CHINESE_SINGAPORE' : 0x04, 216 'SUBLANG_CHINESE_MACAU' : 0x05, 217 'SUBLANG_CORSICAN_FRANCE' : 0x01, 218 'SUBLANG_CZECH_CZECH_REPUBLIC' : 0x01, 219 'SUBLANG_CROATIAN_CROATIA' : 0x01, 220 'SUBLANG_CROATIAN_BOSNIA_HERZEGOVINA_LATIN' : 0x04, 221 'SUBLANG_DANISH_DENMARK' : 0x01, 222 'SUBLANG_DARI_AFGHANISTAN' : 0x01, 223 'SUBLANG_DIVEHI_MALDIVES' : 0x01, 224 'SUBLANG_DUTCH' : 0x01, 225 'SUBLANG_DUTCH_BELGIAN' : 0x02, 226 'SUBLANG_ENGLISH_US' : 0x01, 227 'SUBLANG_ENGLISH_UK' : 0x02, 228 'SUBLANG_ENGLISH_AUS' : 0x03, 229 'SUBLANG_ENGLISH_CAN' : 0x04, 230 'SUBLANG_ENGLISH_NZ' : 0x05, 231 'SUBLANG_ENGLISH_EIRE' : 0x06, 232 'SUBLANG_ENGLISH_SOUTH_AFRICA' : 0x07, 233 'SUBLANG_ENGLISH_JAMAICA' : 0x08, 234 'SUBLANG_ENGLISH_CARIBBEAN' : 0x09, 235 'SUBLANG_ENGLISH_BELIZE' : 0x0a, 236 'SUBLANG_ENGLISH_TRINIDAD' : 0x0b, 237 'SUBLANG_ENGLISH_ZIMBABWE' : 0x0c, 238 'SUBLANG_ENGLISH_PHILIPPINES' : 0x0d, 239 'SUBLANG_ENGLISH_INDIA' : 0x10, 240 'SUBLANG_ENGLISH_MALAYSIA' : 0x11, 241 'SUBLANG_ENGLISH_SINGAPORE' : 0x12, 242 'SUBLANG_ESTONIAN_ESTONIA' : 0x01, 243 'SUBLANG_FAEROESE_FAROE_ISLANDS' : 0x01, 244 'SUBLANG_FILIPINO_PHILIPPINES' : 0x01, 245 'SUBLANG_FINNISH_FINLAND' : 0x01, 246 'SUBLANG_FRENCH' : 0x01, 247 'SUBLANG_FRENCH_BELGIAN' : 0x02, 248 'SUBLANG_FRENCH_CANADIAN' : 0x03, 249 'SUBLANG_FRENCH_SWISS' : 0x04, 250 'SUBLANG_FRENCH_LUXEMBOURG' : 0x05, 251 'SUBLANG_FRENCH_MONACO' : 0x06, 252 'SUBLANG_FRISIAN_NETHERLANDS' : 0x01, 253 'SUBLANG_GALICIAN_GALICIAN' : 0x01, 254 'SUBLANG_GEORGIAN_GEORGIA' : 0x01, 255 'SUBLANG_GERMAN' : 0x01, 256 'SUBLANG_GERMAN_SWISS' : 0x02, 257 'SUBLANG_GERMAN_AUSTRIAN' : 0x03, 258 'SUBLANG_GERMAN_LUXEMBOURG' : 0x04, 259 'SUBLANG_GERMAN_LIECHTENSTEIN' : 0x05, 260 'SUBLANG_GREEK_GREECE' : 0x01, 261 'SUBLANG_GREENLANDIC_GREENLAND' : 0x01, 262 'SUBLANG_GUJARATI_INDIA' : 0x01, 263 'SUBLANG_HAUSA_NIGERIA_LATIN' : 0x01, 264 'SUBLANG_HEBREW_ISRAEL' : 0x01, 265 'SUBLANG_HINDI_INDIA' : 0x01, 266 'SUBLANG_HUNGARIAN_HUNGARY' : 0x01, 267 'SUBLANG_ICELANDIC_ICELAND' : 0x01, 268 'SUBLANG_IGBO_NIGERIA' : 0x01, 269 'SUBLANG_INDONESIAN_INDONESIA' : 0x01, 270 'SUBLANG_INUKTITUT_CANADA' : 0x01, 271 'SUBLANG_INUKTITUT_CANADA_LATIN' : 0x02, 272 'SUBLANG_IRISH_IRELAND' : 0x02, 273 'SUBLANG_ITALIAN' : 0x01, 274 'SUBLANG_ITALIAN_SWISS' : 0x02, 275 'SUBLANG_JAPANESE_JAPAN' : 0x01, 276 'SUBLANG_KANNADA_INDIA' : 0x01, 277 'SUBLANG_KASHMIRI_SASIA' : 0x02, 278 'SUBLANG_KASHMIRI_INDIA' : 0x02, 279 'SUBLANG_KAZAK_KAZAKHSTAN' : 0x01, 280 'SUBLANG_KHMER_CAMBODIA' : 0x01, 281 'SUBLANG_KICHE_GUATEMALA' : 0x01, 282 'SUBLANG_KINYARWANDA_RWANDA' : 0x01, 283 'SUBLANG_KONKANI_INDIA' : 0x01, 284 'SUBLANG_KOREAN' : 0x01, 285 'SUBLANG_KYRGYZ_KYRGYZSTAN' : 0x01, 286 'SUBLANG_LAO_LAO' : 0x01, 287 'SUBLANG_LATVIAN_LATVIA' : 0x01, 288 'SUBLANG_LITHUANIAN' : 0x01, 289 'SUBLANG_LOWER_SORBIAN_GERMANY' : 0x02, 290 'SUBLANG_LUXEMBOURGISH_LUXEMBOURG' : 0x01, 291 'SUBLANG_MACEDONIAN_MACEDONIA' : 0x01, 292 'SUBLANG_MALAY_MALAYSIA' : 0x01, 293 'SUBLANG_MALAY_BRUNEI_DARUSSALAM' : 0x02, 294 'SUBLANG_MALAYALAM_INDIA' : 0x01, 295 'SUBLANG_MALTESE_MALTA' : 0x01, 296 'SUBLANG_MAORI_NEW_ZEALAND' : 0x01, 297 'SUBLANG_MAPUDUNGUN_CHILE' : 0x01, 298 'SUBLANG_MARATHI_INDIA' : 0x01, 299 'SUBLANG_MOHAWK_MOHAWK' : 0x01, 300 'SUBLANG_MONGOLIAN_CYRILLIC_MONGOLIA' : 0x01, 301 'SUBLANG_MONGOLIAN_PRC' : 0x02, 302 'SUBLANG_NEPALI_INDIA' : 0x02, 303 'SUBLANG_NEPALI_NEPAL' : 0x01, 304 'SUBLANG_NORWEGIAN_BOKMAL' : 0x01, 305 'SUBLANG_NORWEGIAN_NYNORSK' : 0x02, 306 'SUBLANG_OCCITAN_FRANCE' : 0x01, 307 'SUBLANG_ORIYA_INDIA' : 0x01, 308 'SUBLANG_PASHTO_AFGHANISTAN' : 0x01, 309 'SUBLANG_PERSIAN_IRAN' : 0x01, 310 'SUBLANG_POLISH_POLAND' : 0x01, 311 'SUBLANG_PORTUGUESE' : 0x02, 312 'SUBLANG_PORTUGUESE_BRAZILIAN' : 0x01, 313 'SUBLANG_PUNJABI_INDIA' : 0x01, 314 'SUBLANG_QUECHUA_BOLIVIA' : 0x01, 315 'SUBLANG_QUECHUA_ECUADOR' : 0x02, 316 'SUBLANG_QUECHUA_PERU' : 0x03, 317 'SUBLANG_ROMANIAN_ROMANIA' : 0x01, 318 'SUBLANG_ROMANSH_SWITZERLAND' : 0x01, 319 'SUBLANG_RUSSIAN_RUSSIA' : 0x01, 320 'SUBLANG_SAMI_NORTHERN_NORWAY' : 0x01, 321 'SUBLANG_SAMI_NORTHERN_SWEDEN' : 0x02, 322 'SUBLANG_SAMI_NORTHERN_FINLAND' : 0x03, 323 'SUBLANG_SAMI_LULE_NORWAY' : 0x04, 324 'SUBLANG_SAMI_LULE_SWEDEN' : 0x05, 325 'SUBLANG_SAMI_SOUTHERN_NORWAY' : 0x06, 326 'SUBLANG_SAMI_SOUTHERN_SWEDEN' : 0x07, 327 'SUBLANG_SAMI_SKOLT_FINLAND' : 0x08, 328 'SUBLANG_SAMI_INARI_FINLAND' : 0x09, 329 'SUBLANG_SANSKRIT_INDIA' : 0x01, 330 'SUBLANG_SCOTTISH_GAELIC' : 0x01, 331 'SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_LATIN' : 0x06, 332 'SUBLANG_SERBIAN_BOSNIA_HERZEGOVINA_CYRILLIC' : 0x07, 333 'SUBLANG_SERBIAN_MONTENEGRO_LATIN' : 0x0b, 334 'SUBLANG_SERBIAN_MONTENEGRO_CYRILLIC' : 0x0c, 335 'SUBLANG_SERBIAN_SERBIA_LATIN' : 0x09, 336 'SUBLANG_SERBIAN_SERBIA_CYRILLIC' : 0x0a, 337 'SUBLANG_SERBIAN_CROATIA' : 0x01, 338 'SUBLANG_SERBIAN_LATIN' : 0x02, 339 'SUBLANG_SERBIAN_CYRILLIC' : 0x03, 340 'SUBLANG_SINDHI_INDIA' : 0x01, 341 'SUBLANG_SINDHI_PAKISTAN' : 0x02, 342 'SUBLANG_SINDHI_AFGHANISTAN' : 0x02, 343 'SUBLANG_SINHALESE_SRI_LANKA' : 0x01, 344 'SUBLANG_SOTHO_NORTHERN_SOUTH_AFRICA' : 0x01, 345 'SUBLANG_SLOVAK_SLOVAKIA' : 0x01, 346 'SUBLANG_SLOVENIAN_SLOVENIA' : 0x01, 347 'SUBLANG_SPANISH' : 0x01, 348 'SUBLANG_SPANISH_MEXICAN' : 0x02, 349 'SUBLANG_SPANISH_MODERN' : 0x03, 350 'SUBLANG_SPANISH_GUATEMALA' : 0x04, 351 'SUBLANG_SPANISH_COSTA_RICA' : 0x05, 352 'SUBLANG_SPANISH_PANAMA' : 0x06, 353 'SUBLANG_SPANISH_DOMINICAN_REPUBLIC' : 0x07, 354 'SUBLANG_SPANISH_VENEZUELA' : 0x08, 355 'SUBLANG_SPANISH_COLOMBIA' : 0x09, 356 'SUBLANG_SPANISH_PERU' : 0x0a, 357 'SUBLANG_SPANISH_ARGENTINA' : 0x0b, 358 'SUBLANG_SPANISH_ECUADOR' : 0x0c, 359 'SUBLANG_SPANISH_CHILE' : 0x0d, 360 'SUBLANG_SPANISH_URUGUAY' : 0x0e, 361 'SUBLANG_SPANISH_PARAGUAY' : 0x0f, 362 'SUBLANG_SPANISH_BOLIVIA' : 0x10, 363 'SUBLANG_SPANISH_EL_SALVADOR' : 0x11, 364 'SUBLANG_SPANISH_HONDURAS' : 0x12, 365 'SUBLANG_SPANISH_NICARAGUA' : 0x13, 366 'SUBLANG_SPANISH_PUERTO_RICO' : 0x14, 367 'SUBLANG_SPANISH_US' : 0x15, 368 'SUBLANG_SWAHILI_KENYA' : 0x01, 369 'SUBLANG_SWEDISH' : 0x01, 370 'SUBLANG_SWEDISH_FINLAND' : 0x02, 371 'SUBLANG_SYRIAC_SYRIA' : 0x01, 372 'SUBLANG_TAJIK_TAJIKISTAN' : 0x01, 373 'SUBLANG_TAMAZIGHT_ALGERIA_LATIN' : 0x02, 374 'SUBLANG_TAMIL_INDIA' : 0x01, 375 'SUBLANG_TATAR_RUSSIA' : 0x01, 376 'SUBLANG_TELUGU_INDIA' : 0x01, 377 'SUBLANG_THAI_THAILAND' : 0x01, 378 'SUBLANG_TIBETAN_PRC' : 0x01, 379 'SUBLANG_TIGRIGNA_ERITREA' : 0x02, 380 'SUBLANG_TSWANA_SOUTH_AFRICA' : 0x01, 381 'SUBLANG_TURKISH_TURKEY' : 0x01, 382 'SUBLANG_TURKMEN_TURKMENISTAN' : 0x01, 383 'SUBLANG_UIGHUR_PRC' : 0x01, 384 'SUBLANG_UKRAINIAN_UKRAINE' : 0x01, 385 'SUBLANG_UPPER_SORBIAN_GERMANY' : 0x01, 386 'SUBLANG_URDU_PAKISTAN' : 0x01, 387 'SUBLANG_URDU_INDIA' : 0x02, 388 'SUBLANG_UZBEK_LATIN' : 0x01, 389 'SUBLANG_UZBEK_CYRILLIC' : 0x02, 390 'SUBLANG_VIETNAMESE_VIETNAM' : 0x01, 391 'SUBLANG_WELSH_UNITED_KINGDOM' : 0x01, 392 'SUBLANG_WOLOF_SENEGAL' : 0x01, 393 'SUBLANG_XHOSA_SOUTH_AFRICA' : 0x01, 394 'SUBLANG_YAKUT_RUSSIA' : 0x01, 395 'SUBLANG_YI_PRC' : 0x01, 396 'SUBLANG_YORUBA_NIGERIA' : 0x01, 397 'SUBLANG_ZULU_SOUTH_AFRICA' : 0x01, 398 } 399 400 401 ''' 402 This dictionary defines the language lookup table. The key is the language ISO 403 country code, and the value specifies the corresponding code page, primary 404 language and sublanguage. 405 406 LCID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx 407 Codepage resource: http://www.science.co.il/language/locale-codes.asp 408 Language ID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx 409 410 There is no appropriate sublang for Spanish (Latin America) [es-419], so we 411 use Mexico. SUBLANG_DEFAULT would incorrectly map to Spain. Unlike other 412 Latin American countries, Mexican Spanish is supported by VERSIONINFO: 413 http://msdn.microsoft.com/en-us/library/aa381058.aspx 414 415 ''' 416 _LANGUAGE_MAP = { 417 # Language neutral LCID, unicode(1200) code page. 418 'neutral' : [ 1200, 'LANG_NEUTRAL', 'SUBLANG_NEUTRAL' ], 419 # LANG_USER_DEFAULT LCID, unicode(1200) code page. 420 'userdefault' : [ 1200, 'LANG_NEUTRAL', 'SUBLANG_DEFAULT' ], 421 'fake-bidi' : [ 1255, 'LANG_HEBREW', 'SUBLANG_DEFAULT' ], 422 'af' : [ 1252, 'LANG_AFRIKAANS', 'SUBLANG_DEFAULT' ], 423 'am' : [ 1200, 'LANG_AMHARIC', 'SUBLANG_DEFAULT' ], 424 'ar' : [ 1256, 'LANG_ARABIC', 'SUBLANG_DEFAULT' ], 425 'bg' : [ 1251, 'LANG_BULGARIAN', 'SUBLANG_DEFAULT' ], 426 'bn' : [ 1200, 'LANG_BENGALI', 'SUBLANG_DEFAULT' ], 427 'ca' : [ 1252, 'LANG_CATALAN', 'SUBLANG_DEFAULT' ], 428 'cs' : [ 1250, 'LANG_CZECH', 'SUBLANG_DEFAULT' ], 429 'da' : [ 1252, 'LANG_DANISH', 'SUBLANG_DEFAULT' ], 430 'de' : [ 1252, 'LANG_GERMAN', 'SUBLANG_GERMAN' ], 431 'el' : [ 1253, 'LANG_GREEK', 'SUBLANG_DEFAULT' ], 432 'en' : [ 1200, 'LANG_ENGLISH', 'SUBLANG_ENGLISH_US' ], 433 'en-GB' : [ 1038, 'LANG_ENGLISH', 'SUBLANG_ENGLISH_UK' ], 434 'es' : [ 1252, 'LANG_SPANISH', 'SUBLANG_SPANISH_MODERN' ], 435 # LCID for Mexico; Windows does not support L.A. LCID. 436 'es-419' : [ 1252, 'LANG_SPANISH', 'SUBLANG_SPANISH_MEXICAN' ], 437 'et' : [ 1257, 'LANG_ESTONIAN', 'SUBLANG_DEFAULT' ], 438 'eu' : [ 1252, 'LANG_BASQUE', 'SUBLANG_DEFAULT' ], 439 'fa' : [ 1256, 'LANG_PERSIAN', 'SUBLANG_DEFAULT' ], 440 'fi' : [ 1252, 'LANG_FINNISH', 'SUBLANG_DEFAULT' ], 441 'fil' : [ 1252, 'LANG_FILIPINO', 'SUBLANG_DEFAULT' ], 442 'fr' : [ 1252, 'LANG_FRENCH', 'SUBLANG_FRENCH' ], 443 'fr-CA' : [ 1252, 'LANG_FRENCH', 'SUBLANG_FRENCH_CANADIAN' ], 444 'gl' : [ 1252, 'LANG_GALICIAN', 'SUBLANG_DEFAULT' ], 445 'gu' : [ 1200, 'LANG_GUJARATI', 'SUBLANG_DEFAULT' ], 446 'he' : [ 1255, 'LANG_HEBREW', 'SUBLANG_DEFAULT' ], 447 'hi' : [ 1200, 'LANG_HINDI', 'SUBLANG_DEFAULT' ], 448 'hr' : [ 1252, 'LANG_CROATIAN', 'SUBLANG_DEFAULT' ], 449 'hu' : [ 1250, 'LANG_HUNGARIAN', 'SUBLANG_DEFAULT' ], 450 'id' : [ 1252, 'LANG_INDONESIAN', 'SUBLANG_DEFAULT' ], 451 'is' : [ 1252, 'LANG_ICELANDIC', 'SUBLANG_DEFAULT' ], 452 'it' : [ 1252, 'LANG_ITALIAN', 'SUBLANG_DEFAULT' ], 453 'iw' : [ 1255, 'LANG_HEBREW', 'SUBLANG_DEFAULT' ], 454 'ja' : [ 932, 'LANG_JAPANESE', 'SUBLANG_DEFAULT' ], 455 'kn' : [ 1200, 'LANG_KANNADA', 'SUBLANG_DEFAULT' ], 456 'ko' : [ 949, 'LANG_KOREAN', 'SUBLANG_KOREAN' ], 457 'lt' : [ 1257, 'LANG_LITHUANIAN', 'SUBLANG_LITHUANIAN' ], 458 'lv' : [ 1257, 'LANG_LATVIAN', 'SUBLANG_DEFAULT' ], 459 'ml' : [ 1200, 'LANG_MALAYALAM', 'SUBLANG_DEFAULT' ], 460 'mr' : [ 1200, 'LANG_MARATHI', 'SUBLANG_DEFAULT' ], 461 # Malay (Malaysia) [ms-MY] 462 'ms' : [ 1252, 'LANG_MALAY', 'SUBLANG_DEFAULT' ], 463 'nb' : [ 1252, 'LANG_NORWEGIAN', 'SUBLANG_NORWEGIAN_BOKMAL' ], 464 'ne' : [ 1200, 'LANG_NEPALI', 'SUBLANG_NEPALI_NEPAL' ], 465 'nl' : [ 1252, 'LANG_DUTCH', 'SUBLANG_DEFAULT' ], 466 'nn' : [ 1252, 'LANG_NORWEGIAN', 'SUBLANG_NORWEGIAN_NYNORSK' ], 467 'no' : [ 1252, 'LANG_NORWEGIAN', 'SUBLANG_DEFAULT' ], 468 'or' : [ 1200, 'LANG_ORIYA', 'SUBLANG_DEFAULT' ], 469 'pa' : [ 1200, 'LANG_PUNJABI', 'SUBLANG_PUNJABI_INDIA' ], 470 'pl' : [ 1250, 'LANG_POLISH', 'SUBLANG_DEFAULT' ], 471 'pt-BR' : [ 1252, 'LANG_PORTUGUESE', 'SUBLANG_DEFAULT' ], 472 'pt-PT' : [ 1252, 'LANG_PORTUGUESE', 'SUBLANG_PORTUGUESE' ], 473 'ro' : [ 1250, 'LANG_ROMANIAN', 'SUBLANG_DEFAULT' ], 474 'ru' : [ 1251, 'LANG_RUSSIAN', 'SUBLANG_DEFAULT' ], 475 'sa' : [ 1200, 'LANG_SANSKRIT', 'SUBLANG_SANSKRIT_INDIA' ], 476 'si' : [ 1200, 'LANG_SINHALESE', 'SUBLANG_SINHALESE_SRI_LANKA' ], 477 'sk' : [ 1250, 'LANG_SLOVAK', 'SUBLANG_DEFAULT' ], 478 'sl' : [ 1250, 'LANG_SLOVENIAN', 'SUBLANG_DEFAULT' ], 479 'sr' : [ 1250, 'LANG_SERBIAN', 'SUBLANG_SERBIAN_LATIN' ], 480 'sv' : [ 1252, 'LANG_SWEDISH', 'SUBLANG_SWEDISH' ], 481 'sw' : [ 1252, 'LANG_SWAHILI', 'SUBLANG_DEFAULT' ], 482 'ta' : [ 1200, 'LANG_TAMIL', 'SUBLANG_DEFAULT' ], 483 'te' : [ 1200, 'LANG_TELUGU', 'SUBLANG_DEFAULT' ], 484 'th' : [ 874, 'LANG_THAI', 'SUBLANG_DEFAULT' ], 485 'ti' : [ 1200, 'LANG_TIGRIGNA', 'SUBLANG_TIGRIGNA_ERITREA' ], 486 'tr' : [ 1254, 'LANG_TURKISH', 'SUBLANG_DEFAULT' ], 487 'uk' : [ 1251, 'LANG_UKRAINIAN', 'SUBLANG_DEFAULT' ], 488 'ur' : [ 1200, 'LANG_URDU', 'SUBLANG_DEFAULT' ], 489 'vi' : [ 1258, 'LANG_VIETNAMESE', 'SUBLANG_DEFAULT' ], 490 'zh-CN' : [ 936, 'LANG_CHINESE', 'SUBLANG_CHINESE_SIMPLIFIED' ], 491 'zh-HK' : [ 950, 'LANG_CHINESE', 'SUBLANG_CHINESE_HONGKONG' ], 492 'zh-TW' : [ 950, 'LANG_CHINESE', 'SUBLANG_CHINESE_TRADITIONAL' ], 493 'zu' : [ 1200, 'LANG_ZULU', 'SUBLANG_DEFAULT' ], 494 } 495 496 497 # Right-To-Left languages 498 _RTL_LANGUAGES = ( 499 'ar', # Arabic 500 'fa', # Farsi 501 'iw', # Hebrew 502 'ks', # Kashmiri 503 'ku', # Kurdish 504 'ps', # Pashto 505 'ur', # Urdu 506 'yi', # Yiddish 507 ) 508 509 510 def GetCodepage(language): 511 """ Returns the codepage for the given |language|. """ 512 lang = _LANGUAGE_MAP[language] 513 return "%04x" % lang[0] 514 515 516 def GetCodepageDecimal(language): 517 """ Returns the codepage for the given |language| as a decimal value. """ 518 lang = _LANGUAGE_MAP[language] 519 return "%d" % lang[0] 520 521 522 def GetLangId(language): 523 """ Returns the language id for the given |language|. """ 524 lang = _LANGUAGE_MAP[language] 525 return "%04x" % (_LANGUAGE_PRIMARY[lang[1]] | (_LANGUAGE_SUB[lang[2]] << 10)) 526 527 528 def GetPrimaryLanguage(language): 529 """ Returns the primary language ID for the given |language|. """ 530 lang = _LANGUAGE_MAP[language] 531 return _LANGUAGE_PRIMARY[lang[1]] 532 533 534 def GetSublanguage(language): 535 """ Returns the sublanguage ID for the given |language|. """ 536 lang = _LANGUAGE_MAP[language] 537 return _LANGUAGE_SUB[lang[2]] 538 539 540 def IsRtlLanguage(language): 541 return language in _RTL_LANGUAGES; 542 543 544 def NormalizeLanguageCode(language): 545 lang = language.replace('_', '-', 1) 546 if lang == 'en-US': 547 lang = 'en' 548 return lang 549 550 551 def GetDataPackageSuffix(language): 552 lang = NormalizeLanguageCode(language) 553 if lang == 'en': 554 lang = 'en-US' 555 return lang 556 557 558 def GetJsonSuffix(language): 559 return language.replace('-', '_', 1) 560 561 562 def ReadValuesFromFile(values_dict, file_name): 563 """ 564 Reads NAME=VALUE settings from the specified file. 565 566 Everything to the left of the first '=' is the keyword, 567 everything to the right is the value. No stripping of 568 white space, so beware. 569 570 The file must exist, otherwise you get the Python exception from open(). 571 """ 572 for line in open(file_name, 'r').readlines(): 573 key, val = line.rstrip('\r\n').split('=', 1) 574 values_dict[key] = val 575 576 577 def ReadMessagesFromFile(file_name): 578 """ 579 Reads messages from a 'chrome_messages_json' file. 580 581 The file must exist, otherwise you get the Python exception from open(). 582 """ 583 messages_file = io.open(file_name, encoding='utf-8-sig') 584 messages = json.load(messages_file) 585 messages_file.close() 586 587 values = {} 588 for key in messages.keys(): 589 values[key] = unicode(messages[key]['message']); 590 return values 591 592 593 def WriteIfChanged(file_name, contents, encoding='utf-16'): 594 """ 595 Writes the specified contents to the specified file_name 596 iff the contents are different than the current contents. 597 """ 598 try: 599 target = io.open(file_name, 'r') 600 old_contents = target.read() 601 except EnvironmentError: 602 pass 603 except UnicodeDecodeError: 604 target.close() 605 os.unlink(file_name) 606 else: 607 if contents == old_contents: 608 return 609 target.close() 610 os.unlink(file_name) 611 io.open(file_name, 'w', encoding=encoding).write(contents) 612 613 614 class MessageMap: 615 """ Provides a dictionary of localized messages for each language.""" 616 def __init__(self, languages, locale_dir): 617 self.language = None 618 self.message_map = {} 619 620 # Populate the message map 621 if locale_dir: 622 for language in languages: 623 file_name = os.path.join(locale_dir, 624 GetJsonSuffix(language), 625 'messages.json') 626 self.message_map[language] = ReadMessagesFromFile(file_name) 627 628 def GetText(self, message): 629 """ Returns a localized message for the current language. """ 630 return self.message_map[self.language][message] 631 632 def SelectLanguage(self, language): 633 """ Selects the language to be used when retrieving localized messages. """ 634 self.language = language 635 636 def MakeSelectLanguage(self): 637 """ Returns a function that can be used to select the current language. """ 638 return lambda language: self.SelectLanguage(language) 639 640 def MakeGetText(self): 641 """ Returns a function that can be used to retrieve a localized message. """ 642 return lambda message: self.GetText(message) 643 644 645 # Use '@' as a delimiter for string templates instead of '$' to avoid unintended 646 # expansion when passing the string from GYP. 647 class GypTemplate(Template): 648 delimiter = '@' 649 650 651 def Localize(source, locales, options): 652 # Set the list of languages to use. 653 languages = map(NormalizeLanguageCode, locales) 654 context = { 'languages' : languages } 655 656 # Load the localized messages. 657 message_map = MessageMap(languages, options.locale_dir) 658 659 # Add OFFICIAL_BUILD variable the same way chrome/tools/build/version.py 660 # does. 661 if os.environ.get('CHROME_BUILD_TYPE') == '_official': 662 context['official_build'] = '1' 663 else: 664 context['official_build'] = '0' 665 666 # Add all variables defined in the command line. 667 if options.define: 668 for define in options.define: 669 context.update(dict([define.split('=', 1)])); 670 671 # Read NAME=VALUE variables from file. 672 if options.variables: 673 for file_name in options.variables: 674 ReadValuesFromFile(context, file_name) 675 676 env = None 677 template = None 678 679 if source: 680 # Load jinja2 library. 681 if options.jinja2: 682 jinja2_path = os.path.normpath(options.jinja2) 683 else: 684 jinja2_path = os.path.normpath( 685 os.path.join(os.path.abspath(__file__), 686 '../../../../third_party/jinja2')) 687 sys.path.append(os.path.split(jinja2_path)[0]) 688 from jinja2 import Environment, FileSystemLoader 689 690 # Create jinja2 environment. 691 (template_path, template_name) = os.path.split(source) 692 env = Environment(loader=FileSystemLoader(template_path), 693 extensions=['jinja2.ext.do', 'jinja2.ext.i18n']) 694 695 # Register custom filters. 696 env.filters['GetCodepage'] = GetCodepage 697 env.filters['GetCodepageDecimal'] = GetCodepageDecimal 698 env.filters['GetLangId'] = GetLangId 699 env.filters['GetPrimaryLanguage'] = GetPrimaryLanguage 700 env.filters['GetSublanguage'] = GetSublanguage 701 702 # Register the message map with jinja2.i18n extension. 703 env.globals['IsRtlLanguage'] = IsRtlLanguage 704 env.globals['SelectLanguage'] = message_map.MakeSelectLanguage() 705 env.install_gettext_callables(message_map.MakeGetText(), 706 message_map.MakeGetText()); 707 708 template = env.get_template(template_name) 709 710 # Generate a separate file per each locale if requested. 711 outputs = [] 712 if options.locale_output: 713 target = GypTemplate(options.locale_output) 714 for lang in languages: 715 context['languages'] = [ lang ] 716 context['language'] = lang 717 context['pak_suffix'] = GetDataPackageSuffix(lang) 718 context['json_suffix'] = GetJsonSuffix(lang) 719 message_map.SelectLanguage(lang) 720 721 template_file_name = target.safe_substitute(context) 722 outputs.append(template_file_name) 723 if not options.print_only: 724 WriteIfChanged(template_file_name, template.render(context), 725 options.encoding) 726 else: 727 outputs.append(options.output) 728 if not options.print_only: 729 WriteIfChanged(options.output, template.render(context), options.encoding) 730 731 if options.print_only: 732 # Quote each element so filename spaces don't mess up gyp's attempt to parse 733 # it into a list. 734 return " ".join(['"%s"' % x for x in outputs]) 735 736 return 737 738 739 def DoMain(argv): 740 usage = "Usage: localize [options] locales" 741 parser = OptionParser(usage=usage) 742 parser.add_option( 743 '-d', '--define', dest='define', action='append', type='string', 744 help='define a variable (NAME=VALUE).') 745 parser.add_option( 746 '--encoding', dest='encoding', type='string', default='utf-16', 747 help="set the encoding of <output>. 'utf-16' is the default.") 748 parser.add_option( 749 '--jinja2', dest='jinja2', type='string', 750 help="specifies path to the jinja2 library.") 751 parser.add_option( 752 '--locale_dir', dest='locale_dir', type='string', 753 help="set path to localized message files.") 754 parser.add_option( 755 '--locale_output', dest='locale_output', type='string', 756 help='specify the per-locale output file name.') 757 parser.add_option( 758 '-o', '--output', dest='output', type='string', 759 help="specify the output file name.") 760 parser.add_option( 761 '--print_only', dest='print_only', action='store_true', 762 default=False, help='print the output file names only.') 763 parser.add_option( 764 '-t', '--template', dest='template', type='string', 765 help="specify the template file name.") 766 parser.add_option( 767 '--variables', dest='variables', action='append', type='string', 768 help='read variables (NAME=VALUE) from file.') 769 770 options, locales = parser.parse_args(argv) 771 if not locales: 772 parser.error('At least one locale must be specified') 773 if bool(options.output) == bool(options.locale_output): 774 parser.error( 775 'Either --output or --locale_output must be specified but not both') 776 if not options.template and not options.print_only: 777 parser.error('The template name is required unless --print_only is used') 778 779 return Localize(options.template, locales, options) 780 781 if __name__ == '__main__': 782 sys.exit(DoMain(sys.argv[1:])) 783 784