Add an Intl transformer to automatically add name/args for messages.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=113212757
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e043b7..fba78b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
## 0.12.7
* Update SDK dependency to 1.12.0, to reflect use of null-aware operators.
+ * Add a transformer to automatically add the "name" and "args" parameters to
+ Intl.message and related calls. This removes a lot of tedious repetition.
## 0.12.6
* Update links in README.md to point to current dartdocs.
diff --git a/README.md b/README.md
index 3ea5442..d26b39f 100644
--- a/README.md
+++ b/README.md
@@ -96,13 +96,20 @@
description for translators, the arguments used in the message, and
examples. The `name` and `args` parameters are required, and must
match the name (or ClassName_methodName) and arguments list of the
-function respectively. In the future we
-hope to have these provided automatically.
+function respectively. However, there is a transformer provided that
+will automatically insert those parameters for you. In pubspec.yaml,
+add a section like
-This can be run in the program before any translation has been done,
-and will just return the message string. It can also be extracted to a
-file and then be made to return a translated version without modifying
-the original program. See "Extracting Messages" below for more
+ transformers:
+ - intl:
+ $include: some_file.dart
+
+and then you can omit the name and args.
+
+A function with an Intl.message call can be run in the program before any
+translation has been done, and will just return the message string. It can also
+be extracted to a file and then be made to return a translated version without
+modifying the original program. See "Extracting Messages" below for more
details.
The purpose of wrapping the message in a function is to allow it to
diff --git a/bin/extract_to_arb.dart b/bin/extract_to_arb.dart
index 18b18f1..2b95437 100644
--- a/bin/extract_to_arb.dart
+++ b/bin/extract_to_arb.dart
@@ -10,13 +10,16 @@
import 'dart:convert';
import 'dart:io';
-import 'package:intl/extract_messages.dart';
-import 'package:path/path.dart' as path;
-import 'package:intl/src/intl_message.dart';
+
import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+
+import 'package:intl/extract_messages.dart';
+import 'package:intl/src/intl_message.dart';
main(List<String> args) {
var targetDir;
+ bool transformer;
var parser = new ArgParser();
parser.addFlag("suppress-warnings",
defaultsTo: false,
@@ -30,7 +33,12 @@
defaultsTo: true,
callback: (x) => allowEmbeddedPluralsAndGenders = x,
help: 'Allow plurals and genders to be embedded as part of a larger '
- 'string, otherwise they must be at the top level.');
+ 'string, otherwise they must be at the top level.');
+ parser.addFlag("transformer",
+ defaultsTo: false,
+ callback: (x) => transformer = x,
+ help: "Assume that the transformer is in use, so name and args "
+ "don't need to be specified for messages.");
parser.addOption("output-dir",
defaultsTo: '.',
@@ -45,7 +53,7 @@
}
var allMessages = {};
for (var arg in args.where((x) => x.contains(".dart"))) {
- var messages = parseFile(new File(arg));
+ var messages = parseFile(new File(arg), transformer);
messages.forEach((k, v) => allMessages.addAll(toARB(v)));
}
var file = new File(path.join(targetDir, 'intl_messages.arb'));
@@ -116,7 +124,8 @@
}
if (chunk is Message) {
return chunk.expanded((message, chunk) => turnInterpolationIntoICUForm(
- message, chunk, shouldEscapeICU: shouldEscapeICU));
+ message, chunk,
+ shouldEscapeICU: shouldEscapeICU));
}
throw new FormatException("Illegal interpolation: $chunk");
}
diff --git a/bin/generate_from_arb.dart b/bin/generate_from_arb.dart
index 338067a..06d0809 100644
--- a/bin/generate_from_arb.dart
+++ b/bin/generate_from_arb.dart
@@ -20,12 +20,14 @@
import 'dart:convert';
import 'dart:io';
-import 'package:intl/extract_messages.dart';
-import 'package:intl/src/icu_parser.dart';
-import 'package:intl/src/intl_message.dart';
-import 'package:intl/generate_localized.dart';
-import 'package:path/path.dart' as path;
+
import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
+
+import 'package:intl/extract_messages.dart';
+import 'package:intl/generate_localized.dart';
+import 'package:intl/src/intl_message.dart';
+import 'package:intl/src/icu_parser.dart';
/// Keeps track of all the messages we have processed so far, keyed by message
/// name.
@@ -63,9 +65,11 @@
}
// We're re-parsing the original files to find the corresponding messages,
- // so if there are warnings extracting the messages, suppress them.
+ // so if there are warnings extracting the messages, suppress them, and
+ // always pretend the transformer was in use so we don't fail for missing
+ // names/args.
suppressWarnings = true;
- var allMessages = dartFiles.map((each) => parseFile(new File(each)));
+ var allMessages = dartFiles.map((each) => parseFile(new File(each), true));
messages = new Map();
for (var eachMap in allMessages) {
diff --git a/bin/rewrite_intl_messages.dart b/bin/rewrite_intl_messages.dart
new file mode 100644
index 0000000..f983c72
--- /dev/null
+++ b/bin/rewrite_intl_messages.dart
@@ -0,0 +1,45 @@
+#!/usr/bin/env dart
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A main program that imitates the action of the transformer, adding
+/// name and args parameters to Intl.message calls automatically.
+///
+/// This is mainly intended to test the transformer logic outside of barback.
+/// It takes as input a single source Dart file and rewrites any
+/// Intl.message or related calls to automatically include the name and args
+/// parameters and writes the result to stdout.
+///
+import 'dart:io';
+
+import 'package:args/args.dart';
+
+import 'package:intl/src/message_rewriter.dart';
+
+String outputFile = 'transformed_output.dart';
+
+main(args) {
+ var parser = new ArgParser();
+ parser.addOption('output',
+ defaultsTo: 'transformed_output.dart',
+ callback: (x) => outputFile = x,
+ help: 'Specify the output file.');
+ print(args);
+ parser.parse(args);
+ if (args.length == 0) {
+ print('Accepts a single Dart file and adds "name" and "args" parameters '
+ ' to Intl.message calls.');
+ print('Primarily useful for exercising the transformer logic.');
+ print('Usage: rewrite_intl_messages [options] [file.dart]');
+ print(parser.usage);
+ exit(0);
+ }
+ var dartFile = args.where((x) => x.endsWith(".dart")).last;
+ var file = new File(dartFile);
+ var content = file.readAsStringSync();
+ var newSource = rewriteMessages(content, '$file');
+ print('Writing new source to $outputFile');
+ var out = new File(outputFile);
+ out.writeAsStringSync(newSource);
+}
diff --git a/lib/extract_messages.dart b/lib/extract_messages.dart
index a2a8809..1b5928c 100644
--- a/lib/extract_messages.dart
+++ b/lib/extract_messages.dart
@@ -50,34 +50,38 @@
/// Parse the source of the Dart program file [file] and return a Map from
/// message names to [IntlMessage] instances.
-Map<String, MainMessage> parseFile(File file) {
+///
+/// If [transformer] is true, assume the transformer will supply any "name"
+/// and "args" parameters required in Intl.message calls.
+Map<String, MainMessage> parseFile(File file, [transformer = false]) {
try {
- _root = parseDartFile(file.path);
+ root = parseDartFile(file.path);
} on AnalyzerErrorGroup catch (e) {
print("Error in parsing ${file.path}, no messages extracted.");
print(" $e");
return {};
}
- _origin = file.path;
+ origin = file.path;
var visitor = new MessageFindingVisitor();
- _root.accept(visitor);
+ visitor.generateNameAndArgs = transformer;
+ root.accept(visitor);
return visitor.messages;
}
/// The root of the compilation unit, and the first node we visit. We hold
/// on to this for error reporting, as it can give us line numbers of other
/// nodes.
-CompilationUnit _root;
+CompilationUnit root;
/// An arbitrary string describing where the source code came from. Most
/// obviously, this could be a file path. We use this when reporting
/// invalid messages.
-String _origin;
+String origin;
String _reportErrorLocation(AstNode node) {
var result = new StringBuffer();
- if (_origin != null) result.write(" from $_origin");
- var info = _root.lineInfo;
+ if (origin != null) result.write(" from $origin");
+ var info = root.lineInfo;
if (info != null) {
var line = info.getLocation(node.offset);
result.write(" line: ${line.lineNumber}, column: ${line.columnNumber}");
@@ -95,6 +99,10 @@
/// Accumulates the messages we have found, keyed by name.
final Map<String, MainMessage> messages = new Map<String, MainMessage>();
+ /// Should we generate the name and arguments from the function definition,
+ /// meaning we're running in the transformer.
+ bool generateNameAndArgs = false;
+
/// We keep track of the data from the last MethodDeclaration,
/// FunctionDeclaration or FunctionExpression that we saw on the way down,
/// as that will be the nearest parent of the Intl.message invocation.
@@ -134,7 +142,8 @@
}
var arguments = node.argumentList.arguments;
var instance = _expectedInstance(node.methodName.name);
- return instance.checkValidity(node, arguments, name, parameters);
+ return instance.checkValidity(node, arguments, name, parameters,
+ nameAndArgsGenerated: generateNameAndArgs);
}
/// Record the parameters of the function or method declaration we last
@@ -205,7 +214,15 @@
MainMessage _messageFromNode(
MethodInvocation node, Function extract, Function setAttribute) {
var message = new MainMessage();
- message.name = name;
+ message.sourcePosition = node.offset;
+ message.endPosition = node.end;
+ if (generateNameAndArgs) {
+ // Always try for class_method if this is a class method and transforming.
+ // It will be overwritten below if the message specifies it explicitly.
+ message.name = Message.classPlusMethodName(node, name) ?? name;
+ } else {
+ message.name = name;
+ }
message.arguments =
parameters.parameters.map((x) => x.identifier.name).toList();
var arguments = node.argumentList.arguments;
@@ -271,7 +288,7 @@
return message;
}
- void setAttribute(MainMessage msg, String fieldName, String fieldValue) {
+ void setAttribute(MainMessage msg, String fieldName, fieldValue) {
if (msg.attributeNames.contains(fieldName)) {
msg[fieldName] = fieldValue;
}
diff --git a/lib/generate_localized.dart b/lib/generate_localized.dart
index 62aa394..1824cdb 100644
--- a/lib/generate_localized.dart
+++ b/lib/generate_localized.dart
@@ -163,6 +163,8 @@
final messages = new MessageLookup();
+final _keepAnalysisHappy = Intl.defaultLocale;
+
class MessageLookup extends MessageLookupByLibrary {
get localeName => '$locale';
diff --git a/lib/src/intl_message.dart b/lib/src/intl_message.dart
index 8800a98..15068ae 100644
--- a/lib/src/intl_message.dart
+++ b/lib/src/intl_message.dart
@@ -65,8 +65,7 @@
String _evaluateAsString(expression) {
var result = expression.accept(_evaluator);
- if (result == ConstantEvaluator.NOT_A_CONSTANT ||
- result is! String) {
+ if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! String) {
return null;
} else {
return result;
@@ -74,11 +73,12 @@
}
String checkValidity(MethodInvocation node, List arguments, String outerName,
- FormalParameterList outerArgs) {
+ FormalParameterList outerArgs,
+ {bool nameAndArgsGenerated: false}) {
var hasArgs = arguments.any(
(each) => each is NamedExpression && each.name.label.name == 'args');
var hasParameters = !outerArgs.parameters.isEmpty;
- if (!hasArgs && hasParameters) {
+ if (!nameAndArgsGenerated && !hasArgs && hasParameters) {
return "The 'args' argument for Intl.message must be specified";
}
@@ -86,30 +86,26 @@
(eachArg) =>
eachArg is NamedExpression && eachArg.name.label.name == 'name',
orElse: () => null);
- if (messageName == null) {
+ if (!nameAndArgsGenerated && messageName == null) {
return "The 'name' argument for Intl.message must be specified";
}
- var givenName = _evaluateAsString(messageName.expression);
- if (givenName == null) {
+
+ var givenName =
+ messageName == null ? null : _evaluateAsString(messageName.expression);
+ if (messageName != null && givenName == null) {
return "The 'name' argument for Intl.message must be a string literal";
}
var hasOuterName = outerName != null;
- var simpleMatch = outerName == givenName;
- ClassDeclaration classNode(n) {
- if (n == null) return null;
- if (n is ClassDeclaration) return n;
- return classNode(n.parent);
- }
- var classDeclaration = classNode(node);
- var classPlusMethod = classDeclaration == null
- ? null
- : "${classDeclaration.name.token.toString()}_$outerName";
+ var simpleMatch = outerName == givenName || givenName == null;
+
+ var classPlusMethod = Message.classPlusMethodName(node, outerName);
var classMatch = classPlusMethod != null && (givenName == classPlusMethod);
if (!(hasOuterName && (simpleMatch || classMatch))) {
return "The 'name' argument for Intl.message must match either "
"the name of the containing function or <className>_<methodName> ("
- "'$givenName' vs. '$outerName')";
+ "was '$givenName' but must be '$outerName' or '$classPlusMethod')";
}
+
var simpleArguments = arguments.where((each) => each is NamedExpression &&
["desc", "name"].contains(each.name.label.name));
var values = simpleArguments.map((each) => each.expression).toList();
@@ -121,6 +117,23 @@
return null;
}
+ /// Return the name of the enclosing class (if any) plus method name, or null
+ /// if there's no enclosing class.
+ ///
+ /// For a method foo in class Bar we allow either "foo" or "Bar_Foo" as the
+ /// name.
+ static String classPlusMethodName(MethodInvocation node, String outerName) {
+ ClassDeclaration classNode(n) {
+ if (n == null) return null;
+ if (n is ClassDeclaration) return n;
+ return classNode(n.parent);
+ }
+ var classDeclaration = classNode(node);
+ return classDeclaration == null
+ ? null
+ : "${classDeclaration.name.token}_$outerName";
+ }
+
/// Turn a value, typically read from a translation file or created out of an
/// AST for a source program, into the appropriate
/// subclass. We expect to get literal Strings, variable substitutions
@@ -145,7 +158,8 @@
/// code.
String toCode();
- /// Escape the string for use in generated Dart code and validate that it
+ /// Escape the string for use in generated Dart code and
+ /// optionally validate that it
/// doesn't doesn't contain any illegal interpolations. We only allow
/// simple variables ("$foo", but not "${foo}") and Intl.gender/plural
/// calls.
@@ -165,13 +179,16 @@
_escape(String s) => (escapes[s] == null) ? s : escapes[s];
var escaped = value.splitMapJoin("", onNonMatch: _escape);
+ return disallowInvalidInterpolations(escaped);
+ }
- // We don't allow any ${} expressions, only $variable to avoid malicious
- // code. Disallow any usage of "${". If that makes a false positive
- // on a translation that legitimately contains "\\${" or other variations,
- // we'll live with that rather than risk a false negative.
+ /// Disallow ${} expressions, only allow $variable so as to avoid malicious
+ /// code. Disallow any usage of "${". If that makes a false positive
+ /// on a translation that legitimately contains "\\${" or other variations,
+ /// we'll live with that rather than risk a false negative.
+ String disallowInvalidInterpolations(String input) {
var validInterpolations = new RegExp(r"(\$\w+)|(\${\w+})");
- var validMatches = validInterpolations.allMatches(escaped);
+ var validMatches = validInterpolations.allMatches(input);
escapeInvalidMatches(Match m) {
var valid = validMatches.any((x) => x.start == m.start);
if (valid) {
@@ -180,7 +197,7 @@
return "\\${m.group(0)}";
}
}
- return escaped.replaceAllMapped("\$", escapeInvalidMatches);
+ return input.replaceAllMapped("\$", escapeInvalidMatches);
}
/// Expand this string out into a printed form. The function [f] will be
@@ -301,14 +318,22 @@
/// printing it for See [expanded], [toCode].
List<Message> messagePieces = [];
+ /// The position in the source at which this message starts.
+ int sourcePosition;
+
+ /// The position in the source at which this message ends.
+ int endPosition;
+
/// Verify that this looks like a correct Intl.message invocation.
String checkValidity(MethodInvocation node, List arguments, String outerName,
- FormalParameterList outerArgs) {
+ FormalParameterList outerArgs,
+ {nameAndArgsGenerated: false}) {
if (arguments.first is! StringLiteral) {
return "Intl.message messages must be string literals";
}
- return super.checkValidity(node, arguments, outerName, outerArgs);
+ return super.checkValidity(node, arguments, outerName, outerArgs,
+ nameAndArgsGenerated: nameAndArgsGenerated);
}
void addPieces(List<Message> messages) {
@@ -339,6 +364,9 @@
/// The arguments list from the Intl.message call.
List arguments;
+ /// The locale argument from the Intl.message call
+ String locale;
+
/// When generating code, we store translations for each locale
/// associated with the original message.
Map<String, String> translations = new Map();
@@ -382,6 +410,24 @@
return out.toString();
}
+ turnInterpolationBackIntoStringForm(Message message, chunk) {
+ if (chunk is String) return escapeAndValidateString(chunk);
+ if (chunk is int) return r"${" + message.arguments[chunk] + "}";
+ if (chunk is Message) return chunk.toCode();
+ throw new ArgumentError.value(chunk, "Unexpected value in Intl.message");
+ }
+
+ String toOriginalCode() {
+ var out = new StringBuffer()..write('Intl.message("');
+ out.write(expanded(turnInterpolationBackIntoStringForm));
+ out.write('", ');
+ out.write('name: "$name", ');
+ out.write(locale == null ? "" : 'locale: "$locale", ');
+ out.write("args: [${arguments.join(', ')}]");
+ out.write(")");
+ return out.toString();
+ }
+
/// The AST node will have the attribute names as strings, so we translate
/// between those and the fields of the class.
void operator []=(attributeName, value) {
@@ -402,6 +448,9 @@
case "meaning":
meaning = value;
return;
+ case "locale":
+ locale = value;
+ return;
default:
return;
}
@@ -504,6 +553,7 @@
/// with "male", "female", and "other" as the possible options.
class Gender extends SubMessage {
Gender();
+
/// Create a new Gender providing [mainArgument] and the list of possible
/// clauses. Each clause is expected to be a list whose first element is a
/// variable name and whose second element is either a [String] or
@@ -640,6 +690,7 @@
/// with arbitrary options.
class Select extends SubMessage {
Select();
+
/// Create a new [Select] providing [mainArgument] and the list of possible
/// clauses. Each clause is expected to be a list whose first element is a
/// variable name and whose second element is either a String or
diff --git a/lib/src/message_rewriter.dart b/lib/src/message_rewriter.dart
new file mode 100644
index 0000000..711c05a
--- /dev/null
+++ b/lib/src/message_rewriter.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Code to rewrite Intl.message calls adding the name and args parameters
+/// automatically, primarily used by the transformer.
+import 'package:analyzer/analyzer.dart';
+
+import 'package:intl/extract_messages.dart';
+
+/// Rewrite all Intl.message/plural/etc. calls in [source], adding "name"
+/// and "args" parameters if they are not provided.
+///
+/// Return the modified source code. If there are errors parsing, list
+/// [sourceName] in the error message.
+String rewriteMessages(String source, String sourceName) {
+ var messages = findMessages(source, sourceName);
+ messages.sort((a, b) => a.sourcePosition.compareTo(b.sourcePosition));
+
+ var start = 0;
+ var newSource = new StringBuffer();
+ for (var message in messages) {
+ newSource.write(source.substring(start, message.sourcePosition));
+ // TODO(alanknight): We could generate more efficient code than the
+ // original here, dispatching more directly to the MessageLookup.
+ newSource.write(message.toOriginalCode());
+ start = message.endPosition;
+ }
+ newSource.write(source.substring(start));
+ return newSource.toString();
+}
+
+/// Find all the messages in the [source] text.
+///
+/// Report errors as coming from [sourceName]
+List<MainMessage> findMessages(String source, String sourceName) {
+ try {
+ root = parseCompilationUnit(source, name: sourceName);
+ } on AnalyzerErrorGroup catch (e) {
+ print("Error in parsing $sourceName, no messages extracted.");
+ print(" $e");
+ return '';
+ }
+ origin = sourceName;
+ var visitor = new MessageFindingVisitor();
+ visitor.generateNameAndArgs = true;
+ root.accept(visitor);
+ return visitor.messages.values.toList();
+}
diff --git a/lib/transformer.dart b/lib/transformer.dart
new file mode 100644
index 0000000..aa0d303
--- /dev/null
+++ b/lib/transformer.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// A transformer for Intl messages, supplying the name and arguments
+/// automatically.
+library intl_transformer;
+
+import 'package:barback/barback.dart';
+
+import 'extract_messages.dart';
+import 'src/message_rewriter.dart';
+
+/// Rewrites Intl.message calls to automatically insert the name and args
+/// parameters.
+class IntlMessageTransformer extends Transformer {
+ IntlMessageTransformer.asPlugin();
+
+ String get allowedExtensions => ".dart";
+
+ Future apply(Transform transform) async {
+ var content = await transform.primaryInput.readAsString();
+ var id = transform.primaryInput.id;
+ var newContent = rewriteMessages(content, '$id');
+ transform.addOutput(new Asset.fromString(id, newContent));
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index d0ff58c..aac05c9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -12,6 +12,7 @@
path: '>=0.9.0 <2.0.0'
petitparser: '>=1.1.3 <2.0.0'
dev_dependencies:
+ barback: ^0.15.2
fixnum: '>=0.9.0 <0.11.0'
unittest: '>=0.10.0 <0.12.0'
transformers:
@@ -29,3 +30,6 @@
- test/message_extraction/message_extraction_test.dart
- test/message_extraction/really_fail_extraction_test.dart
- test/intl_message_basic_example_test.dart # invalid import under pub's package-layout
+- intl:
+ $include:
+ - test/messages_with_transformer/main.dart
diff --git a/test/messages_with_transformer/messages_all.dart b/test/messages_with_transformer/messages_all.dart
new file mode 100644
index 0000000..b51ae4c
--- /dev/null
+++ b/test/messages_with_transformer/messages_all.dart
@@ -0,0 +1,38 @@
+/// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+/// This is a library that looks up messages for specific locales by
+/// delegating to the appropriate library.
+
+import 'dart:async';
+import 'package:intl/message_lookup_by_library.dart';
+import 'package:intl/src/intl_helpers.dart';
+import 'package:intl/intl.dart';
+
+import 'messages_zz.dart' deferred as messages_zz;
+
+
+Map<String, Function> _deferredLibraries = {
+ 'zz' : () => messages_zz.loadLibrary(),
+};
+
+MessageLookupByLibrary _findExact(localeName) {
+ switch (localeName) {
+ case 'zz' : return messages_zz.messages;
+ default: return null;
+ }
+}
+
+/// User programs should call this before using [localeName] for messages.
+Future initializeMessages(String localeName) {
+ initializeInternalMessageLookup(() => new CompositeMessageLookup());
+ var lib = _deferredLibraries[Intl.canonicalizedLocale(localeName)];
+ var load = lib == null ? new Future.value(false) : lib();
+ return load.then((_) =>
+ messageLookup.addLocale(localeName, _findGeneratedMessagesFor));
+}
+
+MessageLookupByLibrary _findGeneratedMessagesFor(locale) {
+ var actualLocale = Intl.verifiedLocale(locale, (x) => _findExact(x) != null,
+ onFailure: (_) => null);
+ if (actualLocale == null) return null;
+ return _findExact(actualLocale);
+}
diff --git a/test/messages_with_transformer/messages_zz.dart b/test/messages_with_transformer/messages_zz.dart
new file mode 100644
index 0000000..1c2d054
--- /dev/null
+++ b/test/messages_with_transformer/messages_zz.dart
@@ -0,0 +1,20 @@
+/// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+/// This is a library that provides messages for a zz locale. All the
+/// messages from the main program should be duplicated here with the same
+/// function name.
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+class MessageLookup extends MessageLookupByLibrary {
+
+ get localeName => 'zz';
+ final messages = _notInlinedMessages(_notInlinedMessages);
+ static _notInlinedMessages(_) => {
+ "foo" : MessageLookupByLibrary.simpleMessage("bar")
+ };
+}
\ No newline at end of file
diff --git a/test/messages_with_transformer/regenerate_translated_libraries.sh b/test/messages_with_transformer/regenerate_translated_libraries.sh
new file mode 100755
index 0000000..5f02a29
--- /dev/null
+++ b/test/messages_with_transformer/regenerate_translated_libraries.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# This test is just trying to verify that the transformer runs,
+# so we generate the translated messages once and just use them.
+# If the transformer successfully invokes them then we're good.
+#
+# To regenerate the code you can run the lines in this script,
+# although translation_zz.arb must be manually created.
+dart ../../bin/extract_to_arb.dart --transformer main.dart
+# manually edit to create translation_zz.arb
+dart ../../bin/generate_from_arb.dart translation_zz.arb transformer_test.dart
diff --git a/test/messages_with_transformer/transformer_test.dart b/test/messages_with_transformer/transformer_test.dart
new file mode 100644
index 0000000..08d902b
--- /dev/null
+++ b/test/messages_with_transformer/transformer_test.dart
@@ -0,0 +1,16 @@
+import 'package:unittest/unittest.dart';
+
+import 'package:intl/intl.dart';
+
+import 'messages_all.dart';
+
+foo() => Intl.message("foo");
+
+main() async {
+ await initializeMessages("zz");
+
+test("Message without name/args", () {
+ Intl.defaultLocale = "zz";
+ expect(foo(), "bar");
+ });
+}
\ No newline at end of file