| 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 |