OLD | NEW |
---|---|
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Produces localized strings.xml files for Android. | 6 """Produces localized strings.xml files for Android. |
7 | 7 |
8 In cases where an "android" type output file is requested in a grd, the classes | 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 | 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. | 10 strings.xml that is properly localized with the specified language. |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
51 <message name="IDS_FOO_DEFAULT" formatter_data="android_java_product=default | 51 <message name="IDS_FOO_DEFAULT" formatter_data="android_java_product=default |
52 android_java_name=foo">has card</message> | 52 android_java_name=foo">has card</message> |
53 | 53 |
54 would generate | 54 would generate |
55 | 55 |
56 <string name="foo" product="nosdcard">"no card"</string> | 56 <string name="foo" product="nosdcard">"no card"</string> |
57 <string name="foo" product="default">"has card"</string> | 57 <string name="foo" product="default">"has card"</string> |
58 """ | 58 """ |
59 | 59 |
60 import os | 60 import os |
61 import re | |
61 import types | 62 import types |
62 import xml.sax.saxutils | 63 import xml.sax.saxutils |
63 | 64 |
64 from grit import lazy_re | 65 from grit import lazy_re |
65 from grit.node import message | 66 from grit.node import message |
66 | 67 |
67 | 68 |
68 # When this environmental variable has value "true", only tagged messages will | 69 # When this environmental variable has value "true", only tagged messages will |
69 # be outputted. | 70 # be outputted. |
70 _TAGGED_ONLY_ENV_VAR = 'ANDROID_JAVA_TAGGED_ONLY' | 71 _TAGGED_ONLY_ENV_VAR = 'ANDROID_JAVA_TAGGED_ONLY' |
(...skipping 18 matching lines...) Expand all Loading... | |
89 | 90 |
90 | 91 |
91 # In most cases we only need a name attribute and string value. | 92 # In most cases we only need a name attribute and string value. |
92 _SIMPLE_TEMPLATE = u'<string name="%s">%s</string>\n' | 93 _SIMPLE_TEMPLATE = u'<string name="%s">%s</string>\n' |
93 | 94 |
94 | 95 |
95 # In a few cases a product attribute is needed. | 96 # In a few cases a product attribute is needed. |
96 _PRODUCT_TEMPLATE = u'<string name="%s" product="%s">%s</string>\n' | 97 _PRODUCT_TEMPLATE = u'<string name="%s" product="%s">%s</string>\n' |
97 | 98 |
98 | 99 |
100 # Some strings have a plural equivalent | |
101 _PLURALS_TEMPLATE = '<plurals name="%s">\n%s</plurals>\n' | |
102 _PLURALS_ITEM_TEMPLATE = ' <item quantity="%s">%s</item>\n' | |
103 _PLURALS_PATTERN = lazy_re.compile(r'\{[A-Z_]+,\s*plural,(?P<items>.*)\}$', flag s=re.S) | |
104 _PLURALS_ITEM_PATTERN = lazy_re.compile(r'(?P<quantity>\S+)\s*\{(?P<value>.*?)\} ') | |
105 | |
106 | |
99 def Format(root, lang='en', output_dir='.'): | 107 def Format(root, lang='en', output_dir='.'): |
100 yield ('<?xml version="1.0" encoding="utf-8"?>\n' | 108 yield ('<?xml version="1.0" encoding="utf-8"?>\n' |
101 '<resources ' | 109 '<resources ' |
102 'xmlns:android="http://schemas.android.com/apk/res/android">\n') | 110 'xmlns:android="http://schemas.android.com/apk/res/android">\n') |
103 | 111 |
104 tagged_only = _TAGGED_ONLY_DEFAULT | 112 tagged_only = _TAGGED_ONLY_DEFAULT |
105 if _TAGGED_ONLY_ENV_VAR in os.environ: | 113 if _TAGGED_ONLY_ENV_VAR in os.environ: |
106 tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower() | 114 tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower() |
107 if tagged_only == 'true': | 115 if tagged_only == 'true': |
108 tagged_only = True | 116 tagged_only = True |
(...skipping 15 matching lines...) Expand all Loading... | |
124 """Returns true if node should be outputted. | 132 """Returns true if node should be outputted. |
125 | 133 |
126 Args: | 134 Args: |
127 node: a Node from the grd dom | 135 node: a Node from the grd dom |
128 tagged_only: true, if only tagged messages should be outputted | 136 tagged_only: true, if only tagged messages should be outputted |
129 """ | 137 """ |
130 return (isinstance(node, message.MessageNode) and | 138 return (isinstance(node, message.MessageNode) and |
131 (not tagged_only or _EMIT_TAG in node.formatter_data)) | 139 (not tagged_only or _EMIT_TAG in node.formatter_data)) |
132 | 140 |
133 | 141 |
142 def _FormatPluralMessage(message): | |
143 """Compile ICU plural syntax to the body of an Android <plurals> element. | |
newt (away)
2015/08/04 03:14:26
nit: "Compiles"
conleyo
2015/08/04 16:53:02
Done.
| |
144 | |
145 1. In a .grd file, we can write a plural string like this: | |
146 | |
147 <message name="IDS_THINGS"> | |
148 {NUM_THINGS, plural, | |
149 =1 {1 thing} | |
150 other {# things}} | |
151 </message> | |
152 | |
153 2. The Android equivalent looks like this: | |
154 | |
155 <plurals name="things"> | |
156 <item quantity="one">1 thing</item> | |
157 <item quantity="other">%d things</item> | |
158 </plurals> | |
159 | |
160 This method takes the body of (1) and converts it to the body of (2). | |
161 | |
162 If the message is *not* a plural string, this function returns `None`. | |
163 If the message includes quantities without an equivalent format in Android, | |
164 it raises an exception. | |
165 """ | |
166 ret = {} | |
167 plural_match = _PLURALS_PATTERN.match(message) | |
168 if not plural_match: | |
169 return None | |
170 body_in = plural_match.group('items').strip() | |
171 body_out = u'' | |
newt (away)
2015/08/04 03:14:26
A better idiom for concatenating strings in python
conleyo
2015/08/04 16:53:02
Done.
| |
172 for item_match in _PLURALS_ITEM_PATTERN.finditer(body_in): | |
173 quantity = item_match.group('quantity') | |
174 value = item_match.group('value').replace('#', '%d') | |
175 if quantity == '=0': | |
176 body_out += _PLURALS_ITEM_TEMPLATE % ('zero', value) | |
177 elif quantity == '=1': | |
178 body_out += _PLURALS_ITEM_TEMPLATE % ('one', value) | |
179 elif quantity == 'other': | |
180 body_out += _PLURALS_ITEM_TEMPLATE % ('other', value) | |
181 else: | |
newt (away)
2015/08/04 03:14:26
Why not handle "=2", "few" and "many"?
conleyo
2015/08/04 16:53:02
Done.
| |
182 raise Exception('Unsupported plural quantity for android ' | |
183 'strings.xml: %s' % quantity) | |
184 return body_out | |
185 | |
186 | |
134 def _FormatMessage(item, lang): | 187 def _FormatMessage(item, lang): |
135 """Writes out a single string as a <resource/> element.""" | 188 """Writes out a single string as a <resource/> element.""" |
136 | 189 |
137 value = item.ws_at_start + item.Translate(lang) + item.ws_at_end | 190 value = item.ws_at_start + item.Translate(lang) + item.ws_at_end |
138 # Replace < > & with < > & to ensure we generate valid XML and | 191 # Replace < > & with < > & to ensure we generate valid XML and |
139 # replace ' " with \' \" to conform to Android's string formatting rules. | 192 # replace ' " with \' \" to conform to Android's string formatting rules. |
140 value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'}) | 193 value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'}) |
194 plurals = _FormatPluralMessage(value) | |
141 # Wrap the string in double quotes to preserve whitespace. | 195 # Wrap the string in double quotes to preserve whitespace. |
142 value = '"' + value + '"' | 196 value = '"' + value + '"' |
143 | 197 |
144 mangled_name = item.GetTextualIds()[0] | 198 mangled_name = item.GetTextualIds()[0] |
145 match = _NAME_PATTERN.match(mangled_name) | 199 match = _NAME_PATTERN.match(mangled_name) |
146 if not match: | 200 if not match: |
147 raise Exception('Unexpected resource name: %s' % mangled_name) | 201 raise Exception('Unexpected resource name: %s' % mangled_name) |
148 name = match.group('name').lower() | 202 name = match.group('name').lower() |
149 product = match.group('product') | 203 product = match.group('product') |
150 | 204 |
151 # Override product or name with values in formatter_data, if any. | 205 # Override product or name with values in formatter_data, if any. |
152 product = item.formatter_data.get(_PRODUCT_TAG, product) | 206 product = item.formatter_data.get(_PRODUCT_TAG, product) |
153 name = item.formatter_data.get(_NAME_TAG, name) | 207 name = item.formatter_data.get(_NAME_TAG, name) |
154 | 208 |
155 if product: | 209 if plurals: |
210 return _PLURALS_TEMPLATE % (name, plurals) | |
211 elif product: | |
156 return _PRODUCT_TEMPLATE % (name, product, value) | 212 return _PRODUCT_TEMPLATE % (name, product, value) |
157 else: | 213 else: |
158 return _SIMPLE_TEMPLATE % (name, value) | 214 return _SIMPLE_TEMPLATE % (name, value) |
OLD | NEW |