Chromium Code Reviews| 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 |