Index: remoting/tools/localize.py |
diff --git a/remoting/tools/localize.py b/remoting/tools/localize.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..5b8c4d0e1139c1e26b4c8210c776b10a47b4d115 |
--- /dev/null |
+++ b/remoting/tools/localize.py |
@@ -0,0 +1,375 @@ |
+#!/usr/bin/env python |
+# Copyright 2013 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+""" |
+localize.py -- Generates an output file from the given template replacing |
+variables and localizing strings. |
+ |
+The script uses Jinja2 template processing library (src/third_party/jinja2). |
+Variables available to the templates: |
+ - |languages| - the list of languages passed on the command line. ('-l'). |
+ - Each KEY=VALUE define ('-d') can be accesses as |KEY|. |
+ - |official_build| is set to '1' when CHROME_BUILD_TYPE environment variable |
+ is set to "_official". |
+ |
+Filters: |
+ - GetCodepage - returns the code page for the given language. |
+ - GetCodepageDecimal same as GetCodepage, but returns a decimal value. |
+ - GetLangId - returns Win32 LANGID. |
+ - GetPrimaryLanguage - returns a named Win32 constant specifing the primary |
+ language ID. |
+ - GetSublanguage - returns a named Win32 constant specifing the sublanguage |
+ ID. |
+ |
+Globals: |
+ - SelectLanguage(language) - allows to select the language to the used by |
+ {% trans %}{% endtrans %} statements. |
+ |
+""" |
+ |
+import io |
+import json |
+from optparse import OptionParser |
+import os |
+import sys |
+from string import Template |
+ |
+''' |
+See https://code.google.com/p/grit-i18n/source/browse/trunk/grit/format/rc.py |
+(revision r79) |
+ |
+This dictionary defines the language lookup table, which is used for replacing |
+the GRIT expand variables for language info in Product Version resource. The key |
+is the language ISO country code, and the value specifies the corresponding |
+locale identifier, code page, primary language and sublanguage. |
+ |
+LCID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx |
+Codepage resource: http://www.science.co.il/language/locale-codes.asp |
+Language ID resource: http://msdn.microsoft.com/en-us/library/ms776294.aspx |
+ |
+There is no appropriate sublang for Spanish (Latin America) [es-419], so we |
+use Mexico. SUBLANG_DEFAULT would incorrectly map to Spain. Unlike other |
+Latin American countries, Mexican Spanish is supported by VERSIONINFO: |
+http://msdn.microsoft.com/en-us/library/aa381058.aspx |
+ |
+''' |
+ |
+_LANGUAGE_LANGID = { |
+ # Language neutral LCID, unicode(1200) code page. |
+ 'neutral' : [ '0000', '04b0', 'LANG_NEUTRAL', 'SUBLANG_NEUTRAL' ], |
+ # LANG_USER_DEFAULT LCID, unicode(1200) code page. |
+ 'userdefault' : [ '0400', '04b0', 'LANG_NEUTRAL', 'SUBLANG_DEFAULT' ], |
+ 'ar' : [ '0401', '04e8', 'LANG_ARABIC', 'SUBLANG_DEFAULT' ], |
garykac
2013/07/09 00:34:12
Is there an order to this list? I expected it to
alexeypa (please no reviews)
2013/07/09 01:05:23
Done.
|
+ 'fi' : [ '040b', '04e4', 'LANG_FINNISH', 'SUBLANG_DEFAULT' ], |
+ 'ko' : [ '0412', '03b5', 'LANG_KOREAN', 'SUBLANG_KOREAN' ], |
+ 'es' : [ '040a', '04e4', 'LANG_SPANISH', 'SUBLANG_SPANISH_MODERN' ], |
+ 'bg' : [ '0402', '04e3', 'LANG_BULGARIAN', 'SUBLANG_DEFAULT' ], |
+ # No codepage for filipino, use unicode(1200). |
+ 'fil' : [ '0464', '04e4', '100', 'SUBLANG_DEFAULT' ], |
+ 'fr' : [ '040c', '04e4', 'LANG_FRENCH', 'SUBLANG_FRENCH' ], |
+ 'lv' : [ '0426', '04e9', 'LANG_LATVIAN', 'SUBLANG_DEFAULT' ], |
+ 'sv' : [ '041d', '04e4', 'LANG_SWEDISH', 'SUBLANG_SWEDISH' ], |
+ 'ca' : [ '0403', '04e4', 'LANG_CATALAN', 'SUBLANG_DEFAULT' ], |
+ 'de' : [ '0407', '04e4', 'LANG_GERMAN', 'SUBLANG_GERMAN' ], |
+ 'lt' : [ '0427', '04e9', 'LANG_LITHUANIAN', 'SUBLANG_LITHUANIAN' ], |
+ 'zh-CN' : [ '0804', '03a8', 'LANG_CHINESE', 'SUBLANG_CHINESE_SIMPLIFIED' ], |
+ 'zh-TW' : [ '0404', '03b6', 'LANG_CHINESE', 'SUBLANG_CHINESE_TRADITIONAL' ], |
+ 'zh-HK' : [ '0c04', '03b6', 'LANG_CHINESE', 'SUBLANG_CHINESE_HONGKONG' ], |
+ 'el' : [ '0408', '04e5', 'LANG_GREEK', 'SUBLANG_DEFAULT' ], |
+ 'nb' : [ '0414', '04e4', 'LANG_NORWEGIAN', 'SUBLANG_DEFAULT' ], |
+ 'no' : [ '0414', '04e4', 'LANG_NORWEGIAN', 'SUBLANG_DEFAULT' ], |
+ 'th' : [ '041e', '036a', 'LANG_THAI', 'SUBLANG_DEFAULT' ], |
+ 'he' : [ '040d', '04e7', 'LANG_HEBREW', 'SUBLANG_DEFAULT' ], |
+ 'iw' : [ '040d', '04e7', 'LANG_HEBREW', 'SUBLANG_DEFAULT' ], |
+ 'pl' : [ '0415', '04e2', 'LANG_POLISH', 'SUBLANG_DEFAULT' ], |
+ 'tr' : [ '041f', '04e6', 'LANG_TURKISH', 'SUBLANG_DEFAULT' ], |
+ 'hr' : [ '041a', '04e4', 'LANG_CROATIAN', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Hindi, use unicode(1200). |
+ 'hi' : [ '0439', '04b0', 'LANG_HINDI', 'SUBLANG_DEFAULT' ], |
+ 'pt-PT' : [ '0816', '04e4', 'LANG_PORTUGUESE', 'SUBLANG_PORTUGUESE' ], |
+ 'pt-BR' : [ '0416', '04e4', 'LANG_PORTUGUESE', 'SUBLANG_DEFAULT' ], |
+ 'uk' : [ '0422', '04e3', 'LANG_UKRAINIAN', 'SUBLANG_DEFAULT' ], |
+ 'cs' : [ '0405', '04e2', 'LANG_CZECH', 'SUBLANG_DEFAULT' ], |
+ 'hu' : [ '040e', '04e2', 'LANG_HUNGARIAN', 'SUBLANG_DEFAULT' ], |
+ 'ro' : [ '0418', '04e2', 'LANG_ROMANIAN', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Urdu, use unicode(1200). |
+ 'ur' : [ '0420', '04b0', 'LANG_URDU', 'SUBLANG_DEFAULT' ], |
+ 'da' : [ '0406', '04e4', 'LANG_DANISH', 'SUBLANG_DEFAULT' ], |
+ 'is' : [ '040f', '04e4', 'LANG_ICELANDIC', 'SUBLANG_DEFAULT' ], |
+ 'ru' : [ '0419', '04e3', 'LANG_RUSSIAN', 'SUBLANG_DEFAULT' ], |
+ 'vi' : [ '042a', '04ea', 'LANG_VIETNAMESE', 'SUBLANG_DEFAULT' ], |
+ 'nl' : [ '0413', '04e4', 'LANG_DUTCH', 'SUBLANG_DEFAULT' ], |
+ 'id' : [ '0421', '04e4', 'LANG_INDONESIAN', 'SUBLANG_DEFAULT' ], |
+ 'sr' : [ '081a', '04e2', 'LANG_SERBIAN', 'SUBLANG_SERBIAN_CYRILLIC' ], |
+ 'en-GB' : [ '0809', '040e', 'LANG_ENGLISH', 'SUBLANG_ENGLISH_UK' ], |
+ 'it' : [ '0410', '04e4', 'LANG_ITALIAN', 'SUBLANG_DEFAULT' ], |
+ 'sk' : [ '041b', '04e2', 'LANG_SLOVAK', 'SUBLANG_DEFAULT' ], |
+ 'et' : [ '0425', '04e9', 'LANG_ESTONIAN', 'SUBLANG_DEFAULT' ], |
+ 'ja' : [ '0411', '03a4', 'LANG_JAPANESE', 'SUBLANG_DEFAULT' ], |
+ 'sl' : [ '0424', '04e2', 'LANG_SLOVENIAN', 'SUBLANG_DEFAULT' ], |
+ 'en' : [ '0409', '04b0', 'LANG_ENGLISH', 'SUBLANG_ENGLISH_US' ], |
+ # LCID for Mexico; Windows does not support L.A. LCID. |
+ 'es-419' : [ '080a', '04e4', 'LANG_SPANISH', 'SUBLANG_SPANISH_MEXICAN' ], |
+ # No codepage for Bengali, use unicode(1200). |
+ 'bn' : [ '0445', '04b0', 'LANG_BENGALI', 'SUBLANG_DEFAULT' ], |
+ 'fa' : [ '0429', '04e8', 'LANG_PERSIAN', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Gujarati, use unicode(1200). |
+ 'gu' : [ '0447', '04b0', 'LANG_GUJARATI', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Kannada, use unicode(1200). |
+ 'kn' : [ '044b', '04b0', 'LANG_KANNADA', 'SUBLANG_DEFAULT' ], |
+ # Malay (Malaysia) [ms-MY] |
+ 'ms' : [ '043e', '04e4', 'LANG_MALAY', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Malayalam, use unicode(1200). |
+ 'ml' : [ '044c', '04b0', 'LANG_MALAYALAM', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Marathi, use unicode(1200). |
+ 'mr' : [ '044e', '04b0', 'LANG_MARATHI', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Oriya , use unicode(1200). |
+ 'or' : [ '0448', '04b0', 'LANG_ORIYA', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Tamil, use unicode(1200). |
+ 'ta' : [ '0449', '04b0', 'LANG_TAMIL', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Telugu, use unicode(1200). |
+ 'te' : [ '044a', '04b0', 'LANG_TELUGU', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Amharic, use unicode(1200). >= Vista. |
+ 'am' : [ '045e', '04b0', 'LANG_AMHARIC', 'SUBLANG_DEFAULT' ], |
+ 'sw' : [ '0441', '04e4', 'LANG_SWAHILI', 'SUBLANG_DEFAULT' ], |
+ 'af' : [ '0436', '04e4', 'LANG_AFRIKAANS', 'SUBLANG_DEFAULT' ], |
+ 'eu' : [ '042d', '04e4', 'LANG_BASQUE', 'SUBLANG_DEFAULT' ], |
+ 'fr-CA' : [ '0c0c', '04e4', 'LANG_FRENCH', 'SUBLANG_FRENCH_CANADIAN' ], |
+ 'gl' : [ '0456', '04e4', 'LANG_GALICIAN', 'SUBLANG_DEFAULT' ], |
+ # No codepage for Zulu, use unicode(1200). |
+ 'zu' : [ '0435', '04b0', 'LANG_ZULU', 'SUBLANG_DEFAULT' ], |
+ 'pa' : [ '0446', '04b0', 'LANG_PUNJABI', 'SUBLANG_PUNJABI_INDIA' ], |
+ 'sa' : [ '044f', '04b0', 'LANG_SANSKRIT', 'SUBLANG_SANSKRIT_INDIA' ], |
+ 'si' : [ '045b', '04b0', 'LANG_SINHALESE', 'SUBLANG_SINHALESE_SRI_LANKA' ], |
+ 'ne' : [ '0461', '04b0', 'LANG_NEPALI', 'SUBLANG_NEPALI_NEPAL' ], |
+ 'ti' : [ '0873', '04b0', 'LANG_TIGRIGNA', 'SUBLANG_TIGRIGNA_ERITREA' ], |
+ 'fake-bidi' : [ '040d', '04e7', 'LANG_HEBREW', 'SUBLANG_DEFAULT' ], |
+} |
+ |
+# Right-To-Left languages |
+_RTL_LANGUAGES = ( |
+ 'ar', # Arabic |
+ 'fa', # Farsi |
+ 'iw', # Hebrew |
+ 'ks', # Kashmiri |
+ 'ku', # Kurdish |
+ 'ps', # Pashto |
+ 'ur', # Urdu |
+ 'yi', # Yiddish |
+) |
+ |
+ |
+def GetCodepage(language): |
+ """ Returns the codepage for the given |language|. """ |
+ langid = _LANGUAGE_LANGID[language] |
+ return langid[1] |
+ |
+ |
+def GetCodepageDecimal(language): |
+ """ Returns the codepage for the given |language| as a decimal value. """ |
+ langid = _LANGUAGE_LANGID[language] |
+ return str(int(langid[1], 16)) |
+ |
+ |
+def GetLangId(language): |
+ """ Returns the language id for the given |language|. """ |
+ langid = _LANGUAGE_LANGID[language] |
+ return langid[0] |
+ |
+ |
+def GetPrimaryLanguage(language): |
+ """ Returns the primary language ID for the given |language|. """ |
+ langid = _LANGUAGE_LANGID[language] |
+ return langid[2] |
+ |
+ |
+def GetSublanguage(language): |
+ """ Returns the sublanguage ID for the given |language|. """ |
+ langid = _LANGUAGE_LANGID[language] |
+ return langid[3] |
+ |
+ |
+def IsRtlLanguage(language): |
+ return language in _RTL_LANGUAGES; |
+ |
+ |
+def NormalizeLanguageCode(language): |
+ return language.replace('_', '-', 1) |
+ |
+ |
+def ReadValuesFromFile(values_dict, file_name): |
+ """ |
+ Reads KEYWORD=VALUE settings from the specified file. |
+ |
+ Everything to the left of the first '=' is the keyword, |
+ everything to the right is the value. No stripping of |
+ white space, so beware. |
+ |
+ The file must exist, otherwise you get the Python exception from open(). |
+ """ |
+ for line in open(file_name, 'r').readlines(): |
+ key, val = line.rstrip('\r\n').split('=', 1) |
+ values_dict[key] = val |
+ |
+ |
+def ReadMessagesFromFile(file_name): |
+ """ |
+ Reads messages from a 'chrome_messages_json' file. |
+ |
+ The file must exist, otherwise you get the Python exception from open(). |
+ """ |
+ messages_file = io.open(file_name, encoding='utf-8-sig') |
+ messages = json.load(messages_file) |
+ messages_file.close() |
+ |
+ values = {} |
+ for key in messages.keys(): |
+ values[key] = unicode(messages[key]['message']); |
+ return values |
+ |
+ |
+def WriteIfChanged(file_name, contents, encoding='utf-16'): |
+ """ |
+ Writes the specified contents to the specified file_name |
+ iff the contents are different than the current contents. |
+ """ |
+ try: |
+ target = io.open(file_name, 'r') |
+ old_contents = target.read() |
+ except EnvironmentError: |
+ pass |
+ except UnicodeDecodeError: |
+ target.close() |
+ os.unlink(file_name) |
+ else: |
+ if contents == old_contents: |
+ return |
+ target.close() |
+ os.unlink(file_name) |
+ io.open(file_name, 'w', encoding=encoding).write(contents) |
+ |
+ |
+class MessageMap: |
+ """ Provides a dictionary of localized messages for each language.""" |
+ def __init__(self, languages, messages_path): |
+ self.language = None |
+ self.message_map = {} |
+ |
+ # Populate the message map |
+ if messages_path: |
+ for language in languages: |
+ file_name = os.path.join(messages_path, |
+ language.replace('-', '_', 1), |
+ 'messages.json') |
+ self.message_map[language] = ReadMessagesFromFile(file_name) |
+ |
+ def GetText(self, message): |
+ """ Returns a localized message for the current language. """ |
+ return self.message_map[self.language][message] |
+ |
+ def SelectLanguage(self, language): |
+ """ Selects the language to be used when retrieving localized messages. """ |
+ self.language = language |
+ |
+ def MakeSelectLanguage(self): |
+ """ Returns a function that can be used to select the current language. """ |
+ return lambda language: self.SelectLanguage(language) |
+ |
+ def MakeGetText(self): |
+ """ Returns a function that can be used to retrieve a localized message. """ |
+ return lambda message: self.GetText(message) |
+ |
+ |
+def Localize(source, target, options): |
+ # Load jinja2 library. |
+ if options.jinja2: |
+ jinja2_path = os.path.normpath(options.jinja2) |
+ else: |
+ jinja2_path = os.path.normpath(os.path.join(os.path.abspath(__file__), |
+ '../../../third_party/jinja2')) |
+ sys.path.append(os.path.split(jinja2_path)[0]) |
+ from jinja2 import Environment, FileSystemLoader |
+ |
+ # Create jinja2 environment. |
+ (template_path, template_name) = os.path.split(source) |
+ env = Environment(loader=FileSystemLoader(template_path), |
+ extensions=['jinja2.ext.do', 'jinja2.ext.i18n']) |
+ |
+ # Register custom filters. |
+ env.filters['GetCodepage'] = GetCodepage |
+ env.filters['GetCodepageDecimal'] = GetCodepageDecimal |
+ env.filters['GetLangId'] = GetLangId |
+ env.filters['GetPrimaryLanguage'] = GetPrimaryLanguage |
+ env.filters['GetSublanguage'] = GetSublanguage |
+ |
+ # Set the list of languages to use |
+ languages = map(NormalizeLanguageCode, options.languages) |
+ context = { 'languages' : languages } |
+ env.globals['IsRtlLanguage'] = IsRtlLanguage |
+ |
+ # Load the localized messages and register the message map with jinja2.i18n |
+ # extension. |
+ message_map = MessageMap(languages, options.messages_path) |
+ env.globals['SelectLanguage'] = message_map.MakeSelectLanguage() |
+ env.install_gettext_callables(message_map.MakeGetText(), |
+ message_map.MakeGetText()); |
+ |
+ # Add OFFICIAL_BUILD variable the same way chrome/tools/build/version.py |
+ # does. |
+ if os.environ.get('CHROME_BUILD_TYPE') == '_official': |
+ context['official_build'] = '1' |
+ else: |
+ context['official_build'] = '0' |
+ |
+ # Add all variables defined in the command line. |
+ if options.define: |
+ for define in options.define: |
+ context.update(dict([define.split('=', 1)])); |
+ |
+ # Read KEYWORD=VALUE variables from file. |
+ if options.input: |
+ for file_name in options.input: |
+ ReadValuesFromFile(context, file_name) |
+ |
+ template = env.get_template(template_name) |
+ WriteIfChanged(target, template.render(context), options.encoding); |
+ return 0; |
+ |
+ |
+def main(): |
+ usage = "Usage: localize [options] <input> <output>" |
+ parser = OptionParser(usage=usage) |
+ parser.add_option( |
+ '-d', '--define', dest='define', action='append', type='string', |
+ help='define a variable (VAR=VALUE).') |
+ parser.add_option( |
+ '-i', '--input', dest='input', action='append', type='string', |
+ help='read variables from INPUT.') |
+ parser.add_option( |
+ '-l', '--language', dest='languages', action='append', type='string', |
+ help='add LANGUAGE to the list of languages to use.') |
+ parser.add_option( |
+ '--encoding', dest='encoding', type='string', default='utf-16', |
+ help="set the encoding of <output>. 'utf-16' is the default.") |
+ parser.add_option( |
+ '--jinja2', dest='jinja2', type='string', |
+ help="specifies path to the jinja2 library.") |
+ parser.add_option( |
+ '--messages_path', dest='messages_path', type='string', |
+ help="set path to localized messages.") |
+ |
+ options, args = parser.parse_args() |
+ if len(args) != 2: |
+ parser.error('Two positional arguments (<input> and <output>) are expected') |
+ if not options.languages: |
+ parser.error('At least one language must be specified') |
+ if not options.messages_path: |
+ parser.error('--messages_path is required') |
+ |
+ return Localize(args[0], args[1], options) |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |
+ |