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 _PLURALS_QUANTITY_MAP = { |
| 106 '=0': 'zero', |
| 107 'zero': 'zero', |
| 108 '=1': 'one', |
| 109 'one': 'one', |
| 110 '=2': 'two', |
| 111 'two': 'two', |
| 112 'few': 'few', |
| 113 'many': 'many', |
| 114 'other': 'other', |
| 115 } |
| 116 |
| 117 |
99 def Format(root, lang='en', output_dir='.'): | 118 def Format(root, lang='en', output_dir='.'): |
100 yield ('<?xml version="1.0" encoding="utf-8"?>\n' | 119 yield ('<?xml version="1.0" encoding="utf-8"?>\n' |
101 '<resources ' | 120 '<resources ' |
102 'xmlns:android="http://schemas.android.com/apk/res/android">\n') | 121 'xmlns:android="http://schemas.android.com/apk/res/android">\n') |
103 | 122 |
104 tagged_only = _TAGGED_ONLY_DEFAULT | 123 tagged_only = _TAGGED_ONLY_DEFAULT |
105 if _TAGGED_ONLY_ENV_VAR in os.environ: | 124 if _TAGGED_ONLY_ENV_VAR in os.environ: |
106 tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower() | 125 tagged_only = os.environ[_TAGGED_ONLY_ENV_VAR].lower() |
107 if tagged_only == 'true': | 126 if tagged_only == 'true': |
108 tagged_only = True | 127 tagged_only = True |
(...skipping 15 matching lines...) Expand all Loading... |
124 """Returns true if node should be outputted. | 143 """Returns true if node should be outputted. |
125 | 144 |
126 Args: | 145 Args: |
127 node: a Node from the grd dom | 146 node: a Node from the grd dom |
128 tagged_only: true, if only tagged messages should be outputted | 147 tagged_only: true, if only tagged messages should be outputted |
129 """ | 148 """ |
130 return (isinstance(node, message.MessageNode) and | 149 return (isinstance(node, message.MessageNode) and |
131 (not tagged_only or _EMIT_TAG in node.formatter_data)) | 150 (not tagged_only or _EMIT_TAG in node.formatter_data)) |
132 | 151 |
133 | 152 |
| 153 def _FormatPluralMessage(message): |
| 154 """Compiles ICU plural syntax to the body of an Android <plurals> element. |
| 155 |
| 156 1. In a .grd file, we can write a plural string like this: |
| 157 |
| 158 <message name="IDS_THINGS"> |
| 159 {NUM_THINGS, plural, |
| 160 =1 {1 thing} |
| 161 other {# things}} |
| 162 </message> |
| 163 |
| 164 2. The Android equivalent looks like this: |
| 165 |
| 166 <plurals name="things"> |
| 167 <item quantity="one">1 thing</item> |
| 168 <item quantity="other">%d things</item> |
| 169 </plurals> |
| 170 |
| 171 This method takes the body of (1) and converts it to the body of (2). |
| 172 |
| 173 If the message is *not* a plural string, this function returns `None`. |
| 174 If the message includes quantities without an equivalent format in Android, |
| 175 it raises an exception. |
| 176 """ |
| 177 ret = {} |
| 178 plural_match = _PLURALS_PATTERN.match(message) |
| 179 if not plural_match: |
| 180 return None |
| 181 body_in = plural_match.group('items').strip() |
| 182 lines = [] |
| 183 for item_match in _PLURALS_ITEM_PATTERN.finditer(body_in): |
| 184 quantity_in = item_match.group('quantity') |
| 185 quantity_out = _PLURALS_QUANTITY_MAP.get(quantity_in) |
| 186 value_in = item_match.group('value') |
| 187 value_out = '"' + value_in.replace('#', '%d') + '"' |
| 188 if quantity_out: |
| 189 lines.append(_PLURALS_ITEM_TEMPLATE % (quantity_out, value_out)) |
| 190 else: |
| 191 raise Exception('Unsupported plural quantity for android ' |
| 192 'strings.xml: %s' % quantity_in) |
| 193 return ''.join(lines) |
| 194 |
| 195 |
134 def _FormatMessage(item, lang): | 196 def _FormatMessage(item, lang): |
135 """Writes out a single string as a <resource/> element.""" | 197 """Writes out a single string as a <resource/> element.""" |
136 | 198 |
137 value = item.ws_at_start + item.Translate(lang) + item.ws_at_end | 199 value = item.ws_at_start + item.Translate(lang) + item.ws_at_end |
138 # Replace < > & with < > & to ensure we generate valid XML and | 200 # Replace < > & with < > & to ensure we generate valid XML and |
139 # replace ' " with \' \" to conform to Android's string formatting rules. | 201 # replace ' " with \' \" to conform to Android's string formatting rules. |
140 value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'}) | 202 value = xml.sax.saxutils.escape(value, {"'": "\\'", '"': '\\"'}) |
| 203 plurals = _FormatPluralMessage(value) |
141 # Wrap the string in double quotes to preserve whitespace. | 204 # Wrap the string in double quotes to preserve whitespace. |
142 value = '"' + value + '"' | 205 value = '"' + value + '"' |
143 | 206 |
144 mangled_name = item.GetTextualIds()[0] | 207 mangled_name = item.GetTextualIds()[0] |
145 match = _NAME_PATTERN.match(mangled_name) | 208 match = _NAME_PATTERN.match(mangled_name) |
146 if not match: | 209 if not match: |
147 raise Exception('Unexpected resource name: %s' % mangled_name) | 210 raise Exception('Unexpected resource name: %s' % mangled_name) |
148 name = match.group('name').lower() | 211 name = match.group('name').lower() |
149 product = match.group('product') | 212 product = match.group('product') |
150 | 213 |
151 # Override product or name with values in formatter_data, if any. | 214 # Override product or name with values in formatter_data, if any. |
152 product = item.formatter_data.get(_PRODUCT_TAG, product) | 215 product = item.formatter_data.get(_PRODUCT_TAG, product) |
153 name = item.formatter_data.get(_NAME_TAG, name) | 216 name = item.formatter_data.get(_NAME_TAG, name) |
154 | 217 |
155 if product: | 218 if plurals: |
| 219 return _PLURALS_TEMPLATE % (name, plurals) |
| 220 elif product: |
156 return _PRODUCT_TEMPLATE % (name, product, value) | 221 return _PRODUCT_TEMPLATE % (name, product, value) |
157 else: | 222 else: |
158 return _SIMPLE_TEMPLATE % (name, value) | 223 return _SIMPLE_TEMPLATE % (name, value) |
OLD | NEW |