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 |