OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** | |
6 * This provides utilities for generating localized versions of | |
7 * messages. It does not stand alone, but expects to be given | |
8 * TranslatedMessage objects and generate code for a particular locale | |
9 * based on them. | |
10 * | |
11 * An example of usage can be found | |
12 * in test/message_extract/generate_from_json.dart | |
13 */ | |
14 library generate_localized; | |
15 | |
16 import 'intl.dart'; | |
17 import 'src/intl_message.dart'; | |
18 import 'dart:io'; | |
19 import 'package:path/path.dart' as path; | |
20 | |
21 /** | |
22 * If the import path following package: is something else, modify the | |
23 * [intlImportPath] variable to change the import directives in the generated | |
24 * code. | |
25 */ | |
26 var intlImportPath = 'intl'; | |
27 | |
28 /** | |
29 * If the path to the generated files is something other than the current | |
30 * directory, update the [generatedImportPath] variable to change the import | |
31 * directives in the generated code. | |
32 */ | |
33 var generatedImportPath = ''; | |
34 | |
35 /** | |
36 * Given a base file, return the file prefixed with the path to import it. | |
37 * By default, that is in the current directory, but if [generatedImportPath] | |
38 * has been set, then use that as a prefix. | |
39 */ | |
40 String importForGeneratedFile(String file) => | |
41 generatedImportPath.isEmpty ? file : "$generatedImportPath/$file"; | |
42 | |
43 /** | |
44 * A list of all the locales for which we have translations. Code that does | |
45 * the reading of translations should add to this. | |
46 */ | |
47 List<String> allLocales = []; | |
48 | |
49 /** | |
50 * If we have more than one set of messages to generate in a particular | |
51 * directory we may want to prefix some to distinguish them. | |
52 */ | |
53 String generatedFilePrefix = ''; | |
54 | |
55 /** | |
56 * Should we use deferred loading for the generated libraries. | |
57 */ | |
58 bool useDeferredLoading = true; | |
59 | |
60 /** | |
61 * This represents a message and its translation. We assume that the translation | |
62 * has some identifier that allows us to figure out the original message it | |
63 * corresponds to, and that it may want to transform the translated text in | |
64 * some way, e.g. to turn whatever format the translation uses for variables | |
65 * into a Dart string interpolation. Specific translation | |
66 * mechanisms are expected to subclass this. | |
67 */ | |
68 abstract class TranslatedMessage { | |
69 /** | |
70 * The identifier for this message. In the simplest case, this is the name | |
71 * parameter from the Intl.message call, | |
72 * but it can be any identifier that this program and the output of the | |
73 * translation can agree on as identifying a message. | |
74 */ | |
75 final String id; | |
76 | |
77 /** Our translated version of all the [originalMessages]. */ | |
78 final Message translated; | |
79 | |
80 /** | |
81 * The original messages that we are a translation of. There can | |
82 * be more than one original message for the same translation. | |
83 */ | |
84 List<MainMessage> originalMessages; | |
85 | |
86 /** | |
87 * For backward compatibility, we still have the originalMessage API. | |
88 */ | |
89 MainMessage get originalMessage => originalMessages.first; | |
90 set originalMessage(MainMessage m) => originalMessages = [m]; | |
91 | |
92 TranslatedMessage(this.id, this.translated); | |
93 | |
94 Message get message => translated; | |
95 | |
96 toString() => id.toString(); | |
97 } | |
98 | |
99 /** | |
100 * We can't use a hyphen in a Dart library name, so convert the locale | |
101 * separator to an underscore. | |
102 */ | |
103 String _libraryName(String x) => 'messages_' + x.replaceAll('-', '_'); | |
104 | |
105 /** | |
106 * Generate a file <[generated_file_prefix]>_messages_<[locale]>.dart | |
107 * for the [translations] in [locale] and put it in [targetDir]. | |
108 */ | |
109 void generateIndividualMessageFile(String basicLocale, | |
110 Iterable<TranslatedMessage> translations, String targetDir) { | |
111 var result = new StringBuffer(); | |
112 var locale = new MainMessage() | |
113 .escapeAndValidateString(Intl.canonicalizedLocale(basicLocale)); | |
114 result.write(prologue(locale)); | |
115 // Exclude messages with no translation and translations with no matching | |
116 // original message (e.g. if we're using some messages from a larger catalog) | |
117 var usableTranslations = translations | |
118 .where((each) => each.originalMessages != null && each.message != null) | |
119 .toList(); | |
120 for (var each in usableTranslations) { | |
121 for (var original in each.originalMessages) { | |
122 original.addTranslation(locale, each.message); | |
123 } | |
124 } | |
125 usableTranslations.sort((a, b) => | |
126 a.originalMessages.first.name.compareTo(b.originalMessages.first.name)); | |
127 for (var translation in usableTranslations) { | |
128 for (var original in translation.originalMessages) { | |
129 result | |
130 ..write(" ") | |
131 ..write(original.toCodeForLocale(locale)) | |
132 ..write("\n\n"); | |
133 } | |
134 } | |
135 result.write("\n final messages = const {\n"); | |
136 var entries = usableTranslations | |
137 .expand((translation) => translation.originalMessages) | |
138 .map((original) => original.name) | |
139 .map((name) => " \"$name\" : $name"); | |
140 result | |
141 ..write(entries.join(",\n")) | |
142 ..write("\n };\n}"); | |
143 | |
144 // To preserve compatibility, we don't use the canonical version of the locale | |
145 // in the file name. | |
146 var filename = | |
147 path.join(targetDir, "${generatedFilePrefix}messages_$basicLocale.dart"); | |
148 new File(filename).writeAsStringSync(result.toString()); | |
149 } | |
150 | |
151 /** | |
152 * This returns the mostly constant string used in | |
153 * [generateIndividualMessageFile] for the beginning of the file, | |
154 * parameterized by [locale]. | |
155 */ | |
156 String prologue(String locale) => """ | |
157 /** | |
158 * DO NOT EDIT. This is code generated via package:intl/generate_localized.dart | |
159 * This is a library that provides messages for a $locale locale. All the | |
160 * messages from the main program should be duplicated here with the same | |
161 * function name. | |
162 */ | |
163 | |
164 library ${_libraryName(locale)}; | |
165 import 'package:$intlImportPath/intl.dart'; | |
166 import 'package:$intlImportPath/message_lookup_by_library.dart'; | |
167 | |
168 final messages = new MessageLookup(); | |
169 | |
170 class MessageLookup extends MessageLookupByLibrary { | |
171 | |
172 get localeName => '$locale'; | |
173 """; | |
174 | |
175 /** | |
176 * This section generates the messages_all.dart file based on the list of | |
177 * [allLocales]. | |
178 */ | |
179 String generateMainImportFile() { | |
180 var output = new StringBuffer(); | |
181 output.write(mainPrologue); | |
182 for (var locale in allLocales) { | |
183 var baseFile = '${generatedFilePrefix}messages_$locale.dart'; | |
184 var file = importForGeneratedFile(baseFile); | |
185 output.write("import '$file' "); | |
186 if (useDeferredLoading) output.write("deferred "); | |
187 output.write("as ${_libraryName(locale)};\n"); | |
188 } | |
189 output.write("\n"); | |
190 output.write("\nMap<String, Function> _deferredLibraries = {\n"); | |
191 for (var rawLocale in allLocales) { | |
192 var locale = Intl.canonicalizedLocale(rawLocale); | |
193 var loadOperation = (useDeferredLoading) | |
194 ? " '$locale' : () => ${_libraryName(locale)}.loadLibrary(),\n" | |
195 : " '$locale' : () => new Future.value(null),\n"; | |
196 output.write(loadOperation); | |
197 } | |
198 output.write("};\n"); | |
199 output.write("\nMessageLookupByLibrary _findExact(localeName) {\n" | |
200 " switch (localeName) {\n"); | |
201 for (var rawLocale in allLocales) { | |
202 var locale = Intl.canonicalizedLocale(rawLocale); | |
203 output.write( | |
204 " case '$locale' : return ${_libraryName(locale)}.messages;\n"); | |
205 } | |
206 output.write(closing); | |
207 return output.toString(); | |
208 } | |
209 | |
210 /** | |
211 * Constant string used in [generateMainImportFile] for the beginning of the | |
212 * file. | |
213 */ | |
214 var mainPrologue = """ | |
215 /** | |
216 * DO NOT EDIT. This is code generated via package:intl/generate_localized.dart | |
217 * This is a library that looks up messages for specific locales by | |
218 * delegating to the appropriate library. | |
219 */ | |
220 | |
221 library messages_all; | |
222 | |
223 import 'dart:async'; | |
224 import 'package:$intlImportPath/message_lookup_by_library.dart'; | |
225 import 'package:$intlImportPath/src/intl_helpers.dart'; | |
226 import 'package:$intlImportPath/intl.dart'; | |
227 | |
228 """; | |
229 | |
230 /** | |
231 * Constant string used in [generateMainImportFile] as the end of the file. | |
232 */ | |
233 const closing = """ | |
234 default: return null; | |
235 } | |
236 } | |
237 | |
238 /** User programs should call this before using [localeName] for messages.*/ | |
239 Future initializeMessages(String localeName) { | |
240 initializeInternalMessageLookup(() => new CompositeMessageLookup()); | |
241 var lib = _deferredLibraries[Intl.canonicalizedLocale(localeName)]; | |
242 var load = lib == null ? new Future.value(false) : lib(); | |
243 return load.then((_) => | |
244 messageLookup.addLocale(localeName, _findGeneratedMessagesFor)); | |
245 } | |
246 | |
247 MessageLookupByLibrary _findGeneratedMessagesFor(locale) { | |
248 var actualLocale = Intl.verifiedLocale(locale, (x) => _findExact(x) != null, | |
249 onFailure: (_) => null); | |
250 if (actualLocale == null) return null; | |
251 return _findExact(actualLocale); | |
252 } | |
253 """; | |
OLD | NEW |