Add a flag to prohibit plurals/genders that don't take up the whole string.
BUG=
R=tjblasi@google.com
Review URL: https://codereview.chromium.org//771253002
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/intl@42071 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ca7920e..9dca66f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.11.11
+ * Add a -no-embedded-plurals flag to reject plurals and genders that
+ have either leading or trailing text around them. This follows the
+ ICU recommendation that a plural or gender should contain the
+ entire phrase/sentence, not just part of it.
+
## 0.11.10
* Fix some style glitches with naming. The only publicly visible one
is DateFormat.parseUtc, but the parseUTC variant is still retained
diff --git a/bin/extract_to_arb.dart b/bin/extract_to_arb.dart
index 970c0bc..a8fab7b 100644
--- a/bin/extract_to_arb.dart
+++ b/bin/extract_to_arb.dart
@@ -21,16 +21,22 @@
var targetDir;
var parser = new ArgParser();
parser.addFlag("suppress-warnings", defaultsTo: false, callback: (x) =>
- suppressWarnings = x);
+ suppressWarnings = x, help: 'Suppress printing of warnings.');
parser.addFlag("warnings-are-errors", defaultsTo: false, callback: (x) =>
- warningsAreErrors = x);
+ warningsAreErrors = x,
+ help: 'Treat all warnings as errors, stop processing ');
+ parser.addFlag("embedded-plurals", 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.');
parser.addOption("output-dir", defaultsTo: '.', callback: (value) => targetDir
- = value);
+ = value, help: 'Specify the output directory.');
parser.parse(args);
if (args.length == 0) {
- print('Usage: extract_to_arb [--output-dir=<dir>] [files.dart]');
print('Accepts Dart files and produces intl_messages.json');
+ print('Usage: extract_to_arb [options] [files.dart]');
+ print(parser.getUsage());
exit(0);
}
var allMessages = {};
diff --git a/bin/generate_from_arb.dart b/bin/generate_from_arb.dart
index 7156e23..b4e48d8 100644
--- a/bin/generate_from_arb.dart
+++ b/bin/generate_from_arb.dart
@@ -34,21 +34,26 @@
var targetDir;
var parser = new ArgParser();
parser.addFlag("suppress-warnings", defaultsTo: false,
- callback: (x) => suppressWarnings = x);
+ callback: (x) => suppressWarnings = x,
+ help: 'Suppress printing of warnings.');
parser.addOption("output-dir", defaultsTo: '.',
- callback: (x) => targetDir = x);
+ callback: (x) => targetDir = x,
+ help: 'Specify the output directory.');
parser.addOption("generated-file-prefix", defaultsTo: '',
- callback: (x) => generatedFilePrefix = x);
+ callback: (x) => generatedFilePrefix = x,
+ help: 'Specify a prefix to be used for the generated file names.');
parser.addFlag("use-deferred-loading", defaultsTo: true,
- callback: (x) => useDeferredLoading = x);
+ callback: (x) => useDeferredLoading = x,
+ help: 'Generate message code that must be loaded with deferred loading. '
+ 'Otherwise, all messages are eagerly loaded.');
parser.parse(args);
var dartFiles = args.where((x) => x.endsWith("dart")).toList();
var jsonFiles = args.where((x) => x.endsWith(".arb")).toList();
if (dartFiles.length == 0 || jsonFiles.length == 0) {
- print('Usage: generate_from_arb [--output-dir=<dir>]'
- ' [--[no-]use-deferred-loading]'
- ' [--generated-file-prefix=<prefix>] file1.dart file2.dart ...'
+ print('Usage: generate_from_arb [options]'
+ ' file1.dart file2.dart ...'
' translation1_<languageTag>.arb translation2.arb ...');
+ print(parser.getUsage());
exit(0);
}
diff --git a/lib/extract_messages.dart b/lib/extract_messages.dart
index 1f3a39e..88045be 100644
--- a/lib/extract_messages.dart
+++ b/lib/extract_messages.dart
@@ -47,6 +47,16 @@
/** Were there any warnings or errors in extracting messages. */
bool get hasWarnings => warnings.isNotEmpty;
+/** Are plural and gender expressions required to be at the top level
+ * of an expression, or are they allowed to be embedded in string literals.
+ *
+ * For example, the following expression
+ * 'There are ${Intl.plural(...)} items'.
+ * is legal if [allowEmbeddedPluralsAndGenders] is true, but illegal
+ * if [allowEmbeddedPluralsAndGenders] is false.
+ */
+bool allowEmbeddedPluralsAndGenders = true;
+
/**
* Parse the source of the Dart program file [file] and return a Map from
* message names to [IntlMessage] instances.
@@ -228,7 +238,8 @@
message.arguments = parameters.parameters.map(
(x) => x.identifier.name).toList();
var arguments = node.argumentList.arguments;
- extract(message, arguments);
+ var extractionResult = extract(message, arguments);
+ if (extractionResult == null) return null;
for (var namedArgument in arguments.where((x) => x is NamedExpression)) {
var name = namedArgument.name.label.name;
@@ -249,10 +260,19 @@
*/
MainMessage messageFromIntlMessageCall(MethodInvocation node) {
- void extractFromIntlCall(MainMessage message, List arguments) {
+ MainMessage extractFromIntlCall(MainMessage message, List arguments) {
try {
var interpolation = new InterpolationVisitor(message);
arguments.first.accept(interpolation);
+ if (interpolation.pieces.any((x) => x is Plural || x is Gender) &&
+ !allowEmbeddedPluralsAndGenders) {
+ if (interpolation.pieces.any((x) => x is String && x.isNotEmpty)) {
+ throw new IntlMessageExtractionException(
+ "Plural and gender expressions must be at the top level, "
+ "they cannot be embedded in larger string literals.\n"
+ "Error at $node");
+ }
+ }
message.messagePieces.addAll(interpolation.pieces);
} on IntlMessageExtractionException catch (e) {
message = null;
@@ -260,8 +280,9 @@
..writeAll(["Error ", e, "\nProcessing <", node, ">\n"])
..write(_reportErrorLocation(node));
print(err);
- warnings.add(err);
+ warnings.add(err.toString());
}
+ return message; // Because we may have set it to null on an error.
}
void setValue(MainMessage message, String fieldName, Object fieldValue) {
@@ -279,10 +300,11 @@
MainMessage messageFromDirectPluralOrGenderCall(MethodInvocation node) {
var pluralOrGender;
- void extractFromPluralOrGender(MainMessage message, _) {
+ MainMessage extractFromPluralOrGender(MainMessage message, _) {
var visitor = new PluralAndGenderVisitor(message.messagePieces, message);
node.accept(visitor);
pluralOrGender = message.messagePieces.last;
+ return message;
}
void setAttribute(MainMessage msg, String fieldName, String fieldValue) {
@@ -424,7 +446,7 @@
/**
* Create a MainMessage from [node] using the name and
- * parameters of the last function/method declaration we encountered e
+ * parameters of the last function/method declaration we encountered
* and the parameters to the Intl.message call.
*/
Message messageFromMethodInvocation(MethodInvocation node) {
@@ -450,7 +472,7 @@
..writeAll(["Error ", e, "\nProcessing <", node, ">"])
..write(_reportErrorLocation(node));
print(err);
- warnings.add(err);
+ warnings.add(err.toString());
}
});
var mainArg = node.argumentList.arguments.firstWhere(
diff --git a/pubspec.yaml b/pubspec.yaml
index 1f7f06d..c2bcbef 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: intl
-version: 0.11.10
+version: 0.11.11
author: Dart Team <misc@dartlang.org>
description: Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.
homepage: https://www.dartlang.org
diff --git a/test/message_extraction/embedded_plural_text_after.dart b/test/message_extraction/embedded_plural_text_after.dart
new file mode 100644
index 0000000..15704ae
--- /dev/null
+++ b/test/message_extraction/embedded_plural_text_after.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2014, 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 test library that should fail because there is a plural with text
+/// following the plural expression.
+library embedded_plural_text_after;
+
+import "package:intl/intl.dart";
+
+embeddedPlural2(n) => Intl.message(
+ "${Intl.plural(n, zero: 'none', one: 'one', other: 'some')} plus text.",
+ name: 'embeddedPlural2',
+ desc: 'An embedded plural',
+ args: [n]);
\ No newline at end of file
diff --git a/test/message_extraction/embedded_plural_text_after_test.dart b/test/message_extraction/embedded_plural_text_after_test.dart
new file mode 100644
index 0000000..64032b1
--- /dev/null
+++ b/test/message_extraction/embedded_plural_text_after_test.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2014, 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.
+
+library embedded_plural_text_after_test;
+
+import "failed_extraction_test.dart";
+import "package:unittest/unittest.dart";
+
+main() {
+ test("Expect failure because of embedded plural with text after it", () {
+ var specialFiles = ['embedded_plural_text_after.dart'];
+ runTestWithWarnings(warningsAreErrors: true, expectedExitCode: 1,
+ embeddedPlurals: false, sourceFiles: specialFiles);
+ });
+}
diff --git a/test/message_extraction/embedded_plural_text_before.dart b/test/message_extraction/embedded_plural_text_before.dart
new file mode 100644
index 0000000..854b495
--- /dev/null
+++ b/test/message_extraction/embedded_plural_text_before.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2014, 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 test library that should fail because there is a plural with text
+/// before the plural expression.
+library embedded_plural_text_before;
+
+import "package:intl/intl.dart";
+
+embeddedPlural(n) => Intl.message(
+ "There are ${Intl.plural(n, zero: 'nothing', one: 'one', other: 'some')}.",
+ name: 'embeddedPlural',
+ desc: 'An embedded plural',
+ args: [n]);
diff --git a/test/message_extraction/embedded_plural_text_before_test.dart b/test/message_extraction/embedded_plural_text_before_test.dart
new file mode 100644
index 0000000..0cc44f7
--- /dev/null
+++ b/test/message_extraction/embedded_plural_text_before_test.dart
@@ -0,0 +1,16 @@
+// Copyright (c) 2014, 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.
+
+library embedded_plural_text_before_test;
+
+import "failed_extraction_test.dart";
+import "package:unittest/unittest.dart";
+
+main() {
+ test("Expect failure because of embedded plural with text before it", () {
+ var files = ['embedded_plural_text_before.dart'];
+ runTestWithWarnings(warningsAreErrors: true, expectedExitCode: 1,
+ embeddedPlurals: false, sourceFiles: files);
+ });
+}
diff --git a/test/message_extraction/failed_extraction_test.dart b/test/message_extraction/failed_extraction_test.dart
index 430a43f..3a7d75e 100644
--- a/test/message_extraction/failed_extraction_test.dart
+++ b/test/message_extraction/failed_extraction_test.dart
@@ -13,7 +13,11 @@
});
}
-void runTestWithWarnings({bool warningsAreErrors, int expectedExitCode}) {
+const defaultFiles =
+ const ["sample_with_messages.dart", "part_of_sample_with_messages.dart"];
+
+void runTestWithWarnings({bool warningsAreErrors, int expectedExitCode,
+ bool embeddedPlurals: true, List<String> sourceFiles: defaultFiles}) {
void verify(ProcessResult result) {
try {
@@ -29,8 +33,10 @@
if (warningsAreErrors) {
args.add('--warnings-are-errors');
}
- var files = [asTempDirPath("sample_with_messages.dart"), asTempDirPath(
- "part_of_sample_with_messages.dart"),];
+ if (!embeddedPlurals) {
+ args.add('--no-embedded-plurals');
+ }
+ var files = sourceFiles.map(asTempDirPath).toList();
var allArgs = [program]
..addAll(args)
..addAll(files);
diff --git a/test/message_extraction/message_extraction_test.dart b/test/message_extraction/message_extraction_test.dart
index 03f5499..2428b58 100644
--- a/test/message_extraction/message_extraction_test.dart
+++ b/test/message_extraction/message_extraction_test.dart
@@ -85,6 +85,8 @@
asTestDirPath('part_of_sample_with_messages.dart'),
asTestDirPath('verify_messages.dart'),
asTestDirPath('run_and_verify.dart'),
+ asTestDirPath('embedded_plural_text_before.dart'),
+ asTestDirPath('embedded_plural_text_after.dart'),
asTestDirPath('print_to_list.dart')];
for (var filename in files) {
var file = new File(filename);
@@ -150,7 +152,7 @@
Future<ProcessResult> generateCodeFromTranslation(ProcessResult previousResult)
=> run(previousResult, [asTestDirPath('../../bin/generate_from_arb.dart'),
- deferredLoadArg, '--generated-file-prefix=foo_',
+ deferredLoadArg, '--generated-file-prefix=foo_',
'sample_with_messages.dart', 'part_of_sample_with_messages.dart',
'translation_fr.arb', 'translation_de_DE.arb']);