| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2012 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 """Produces localized strings.xml files for Android. | |
| 7 | |
| 8 In cases where an "android" type output file is requested in a grd, the classes | |
| 9 in android_xml will process the messages and translations to produce a valid | |
| 10 strings.xml that is properly localized with the specified language. | |
| 11 | |
| 12 For example if the following output tag were to be included in a grd file | |
| 13 <outputs> | |
| 14 ... | |
| 15 <output filename="values-es/strings.xml" type="android" lang="es" /> | |
| 16 ... | |
| 17 </outputs> | |
| 18 | |
| 19 for a grd file with the following messages: | |
| 20 | |
| 21 <message name="IDS_HELLO" desc="Simple greeting">Hello</message> | |
| 22 <message name="IDS_WORLD" desc="The world">world</message> | |
| 23 | |
| 24 and there existed an appropriate xtb file containing the Spanish translations, | |
| 25 then the output would be: | |
| 26 | |
| 27 <?xml version="1.0" encoding="utf-8"?> | |
| 28 <resources xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 29 <string name="hello">"Hola"</string> | |
| 30 <string name="world">"mundo"</string> | |
| 31 </resources> | |
| 32 | |
| 33 which would be written to values-es/strings.xml and usable by the Android | |
| 34 resource framework. | |
| 35 | |
| 36 Advanced usage | |
| 37 -------------- | |
| 38 | |
| 39 To process only certain messages in a grd file, tag each desired message by | |
| 40 adding "android_java" to formatter_data. Then set the environmental variable | |
| 41 ANDROID_JAVA_TAGGED_ONLY to "true" when building the grd file. For example: | |
| 42 | |
| 43 <message name="IDS_HELLO" formatter_data="android_java">Hello</message> | |
| 44 | |
| 45 To generate Android plurals (aka "quantity strings"), use the ICU plural syntax | |
| 46 in the grd file. This will automatically be transformed into a <purals> element | |
| 47 in the output xml file. For example: | |
| 48 | |
| 49 <message name="IDS_CATS"> | |
| 50 {NUM_CATS, plural, | |
| 51 =1 {1 cat} | |
| 52 other {# cats}} | |
| 53 </message> | |
| 54 | |
| 55 will produce | |
| 56 | |
| 57 <plurals name="cats"> | |
| 58 <item quantity="one">1 Katze</item> | |
| 59 <item quantity="other">%d Katzen</item> | |
| 60 </plurals> | |
| 61 """ | |
| 62 | |
| 63 import os | |
| 64 import re | |
| 65 import types | |
| 66 import xml.sax.saxutils | |
| 67 | |
| 68 from grit import lazy_re | |
| 69 from grit.node import message | |
| 70 | |
| 71 | |
| 72 # When this environmental variable has value "true", only tagged messages will | |
| 73 # be outputted. | |
| 74 _TAGGED_ONLY_ENV_VAR = 'ANDROID_JAVA_TAGGED_ONLY' | |
| 75 _TAGGED_ONLY_DEFAULT = False | |
| 76 | |
| 77 # In tagged-only mode, only messages with this tag will be ouputted. | |
| 78 _EMIT_TAG = 'android_java' | |
| 79 | |
| 80 _NAME_PATTERN = lazy_re.compile('IDS_(?P<name>[A-Z0-9_]+)\Z') | |
| 81 | |
| 82 # Most strings are output as a <string> element. Note the double quotes | |
| 83 # around the value to preserve whitespace. | |
| 84 _STRING_TEMPLATE = u'<string name="%s">"%s"</string>\n' | |
| 85 | |
| 86 # Some strings are output as a <plurals> element. | |
| 87 _PLURALS_TEMPLATE = '<plurals name="%s">\n%s</plurals>\n' | |
| 88 _PLURALS_ITEM_TEMPLATE = ' <item quantity="%s">%s</item>\n' | |
| 89 _PLURALS_PATTERN = lazy_re.compile(r'\{[A-Z_]+,\s*plural,(?P<items>.*)\}$', flag
s=re.S) | |
| 90 _PLURALS_ITEM_PATTERN = lazy_re.compile(r'(?P<quantity>\S+)\s*\{(?P<value>.*?)\}
') | |
| 91 _PLURALS_QUANTITY_MAP = { | |
| 92 '=0': 'zero', | |
| 93 'zero': 'zero', | |
| 94 '=1': 'one', | |
| 95 'one': 'one', | |
| 96 '=2': 'two', | |
| 97 'two': 'two', | |
| 98 'few': 'few', | |
| 99 'many': 'many', | |
| 100 'other': 'other', | |
| 101 } | |
| 102 | |
| 103 | |
| 104 def Format(root, lang='en', output_dir='.'): | |
| 105 yield ('<?xml version="1.0" encoding="utf-8"?>\n' | |
| 106 '<resources ' | |
| 107 'xmlns:android="http://schemas.android.com/apk/res/android">\n') | |
| 108 | |
| 109 tagged_only = _TAGGED_ONLY_DEFAULT | |
| 110 if _TAGGED_ONLY_ENV_VAR in os.environ: | |
| 111 tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower() | |
| 112 if tagged_only == 'true': | |
| 113 tagged_only = True | |
| 114 elif tagged_only == 'false': | |
| 115 tagged_only = False | |
| 116 else: | |
| 117 raise Exception('env variable ANDROID_JAVA_TAGGED_ONLY must have value ' | |
| 118 'true or false. Invalid value: %s' % tagged_only) | |
| 119 | |
| 120 for item in root.ActiveDescendants(): | |
| 121 with item: | |
| 122 if ShouldOutputNode(item, tagged_only): | |
| 123 yield _FormatMessage(item, lang) | |
| 124 | |
| 125 yield '</resources>\n' | |
| 126 | |
| 127 | |
| 128 def ShouldOutputNode(node, tagged_only): | |
| 129 """Returns true if node should be outputted. | |
| 130 | |
| 131 Args: | |
| 132 node: a Node from the grd dom | |
| 133 tagged_only: true, if only tagged messages should be outputted | |
| 134 """ | |
| 135 return (isinstance(node, message.MessageNode) and | |
| 136 (not tagged_only or _EMIT_TAG in node.formatter_data)) | |
| 137 | |
| 138 | |
| 139 def _FormatPluralMessage(message): | |
| 140 """Compiles ICU plural syntax to the body of an Android <plurals> element. | |
| 141 | |
| 142 1. In a .grd file, we can write a plural string like this: | |
| 143 | |
| 144 <message name="IDS_THINGS"> | |
| 145 {NUM_THINGS, plural, | |
| 146 =1 {1 thing} | |
| 147 other {# things}} | |
| 148 </message> | |
| 149 | |
| 150 2. The Android equivalent looks like this: | |
| 151 | |
| 152 <plurals name="things"> | |
| 153 <item quantity="one">1 thing</item> | |
| 154 <item quantity="other">%d things</item> | |
| 155 </plurals> | |
| 156 | |
| 157 This method takes the body of (1) and converts it to the body of (2). | |
| 158 | |
| 159 If the message is *not* a plural string, this function returns `None`. | |
| 160 If the message includes quantities without an equivalent format in Android, | |
| 161 it raises an exception. | |
| 162 """ | |
| 163 ret = {} | |
| 164 plural_match = _PLURALS_PATTERN.match(message) | |
| 165 if not plural_match: | |
| 166 return None | |
| 167 body_in = plural_match.group('items').strip() | |
| 168 lines = [] | |
| 169 for item_match in _PLURALS_ITEM_PATTERN.finditer(body_in): | |
| 170 quantity_in = item_match.group('quantity') | |
| 171 quantity_out = _PLURALS_QUANTITY_MAP.get(quantity_in) | |
| 172 value_in = item_match.group('value') | |
| 173 value_out = '"' + value_in.replace('#', '%d') + '"' | |
| 174 if quantity_out: | |
| 175 lines.append(_PLURALS_ITEM_TEMPLATE % (quantity_out, value_out)) | |
| 176 else: | |
| 177 raise Exception('Unsupported plural quantity for android ' | |
| 178 'strings.xml: %s' % quantity_in) | |
| 179 return ''.join(lines) | |
| 180 | |
| 181 | |
| 182 def _FormatMessage(item, lang): | |
| 183 """Writes out a single string as a <resource/> element.""" | |
| 184 | |
| 185 mangled_name = item.GetTextualIds()[0] | |
| 186 match = _NAME_PATTERN.match(mangled_name) | |
| 187 if not match: | |
| 188 raise Exception('Unexpected resource name: %s' % mangled_name) | |
| 189 name = match.group('name').lower() | |
| 190 | |
| 191 value = item.ws_at_start + item.Translate(lang) + item.ws_at_end | |
| 192 # Replace < > & with < > & to ensure we generate valid XML and | |
| 193 # replace ' " with \' \" to conform to Android's string formatting rules. | |
| 194 value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'}) | |
| 195 | |
| 196 plurals = _FormatPluralMessage(value) | |
| 197 if plurals: | |
| 198 return _PLURALS_TEMPLATE % (name, plurals) | |
| 199 else: | |
| 200 return _STRING_TEMPLATE % (name, value) | |
| OLD | NEW |