[examples] migrate Topaz's localized_mod
- Copies localized_mod from topaz/examples/ui
- Renames the example to localized_flutter_app (it's a flutter app with
no Modular deps)
- Adds stub test targets to build files
Change-Id: If0803b1073d3681f341a4edf90d0a8446920f37b
diff --git a/examples/BUILD.gn b/examples/BUILD.gn
index 0ad0859..f4b74e6 100644
--- a/examples/BUILD.gn
+++ b/examples/BUILD.gn
@@ -5,6 +5,7 @@
group("examples") {
deps = [
"hello_experiences",
+ "localized_flutter_app",
]
}
@@ -20,6 +21,7 @@
testonly = true
deps = [
+ "localized_flutter_app:localized_flutter_app_unittests($host_toolchain)",
"hello_experiences:hello_experiences_unittests($host_toolchain)",
]
}
diff --git a/examples/localized_flutter_app/BUILD.gn b/examples/localized_flutter_app/BUILD.gn
new file mode 100644
index 0000000..f5364a8
--- /dev/null
+++ b/examples/localized_flutter_app/BUILD.gn
@@ -0,0 +1,37 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import("//topaz/runtime/flutter_runner/flutter_app.gni")
+import("//topaz/runtime/dart/flutter_test.gni")
+
+flutter_app("localized_flutter_app") {
+ main_dart = "lib/main.dart"
+ package_name = "localized_flutter_app"
+
+ sources = []
+
+ deps = [
+ "//third_party/dart-pkg/git/flutter/packages/flutter",
+ "//third_party/dart-pkg/git/flutter/packages/flutter_localizations",
+ "//third_party/dart/third_party/pkg/intl",
+ "//topaz/public/dart/widgets:lib.widgets",
+ ]
+
+ meta = [
+ {
+ path = rebase_path("meta/localized_flutter_app.cmx")
+ dest = "localized_flutter_app.cmx"
+ },
+ ]
+}
+
+flutter_test("localized_flutter_app_unittests") {
+ sources = [
+ "sample_test.dart",
+ ]
+
+ deps = [
+ "//third_party/dart-pkg/pub/test",
+ ]
+}
diff --git a/examples/localized_flutter_app/OWNERS b/examples/localized_flutter_app/OWNERS
new file mode 100644
index 0000000..71bb700
--- /dev/null
+++ b/examples/localized_flutter_app/OWNERS
@@ -0,0 +1,4 @@
+fmil@google.com
+kpozin@google.com
+shayba@google.com
+viktard@google.com
diff --git a/examples/localized_flutter_app/README.md b/examples/localized_flutter_app/README.md
new file mode 100644
index 0000000..7ea2893
--- /dev/null
+++ b/examples/localized_flutter_app/README.md
@@ -0,0 +1,10 @@
+# Localized Mod
+
+This is an example of a simple Flutter application that can display localized
+strings in several supported locales. It uses
+[`dart:intl_translation`](https://pub.dartlang.org/packages/intl_translation)
+for extracting localized strings from code and for generating the
+localization-loading classes.
+
+This is intended as a testbed for any efforts to integrate Fuchsia's Gerrit with
+a translation pipeline.
diff --git a/examples/localized_flutter_app/analysis_options.yaml b/examples/localized_flutter_app/analysis_options.yaml
new file mode 100644
index 0000000..bebf512
--- /dev/null
+++ b/examples/localized_flutter_app/analysis_options.yaml
@@ -0,0 +1,5 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+include: ../../analysis_options.yaml
diff --git a/examples/localized_flutter_app/lib/localization/README.md b/examples/localized_flutter_app/lib/localization/README.md
new file mode 100644
index 0000000..e608e3d
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/README.md
@@ -0,0 +1,46 @@
+# `localized_mod/lib/localization`
+
+Most of the files in this directory were generated. Here's how:
+
+## `intl_messages.arb`
+
+This is generated from `localized_mod/localized_mod_strings.dart` using
+
+```shell
+localized_mod$ $FUCHSIA/third_party/dart-pkg/git/flutter/bin/flutter \
+packages pub run \
+intl_translation:extract_to_arb \
+--output-dir=lib/localization \
+lib/localized_mod_strings.dart
+```
+
+This file should be checked in.
+
+## `intl_messages_*.arb`
+
+These are the result of _manually_ translating `intl_messages.arb` into each
+target locale.
+
+One option for generating these more easily is the
+[Google Translator Toolkit](https://translate.google.com/toolkit/).
+
+In production code, these would be imported from a translation pipeline.
+
+These files should be checked in.
+
+## `messages_*.dart`
+
+These are generated from the `.arb` files. The command is
+
+```shell
+localized_mod$ $FUCHSIA/third_party/dart-pkg/git/flutter/bin/flutter \
+packages pub run \
+intl_translation:generate_from_arb \
+--output-dir=lib/localization \
+lib/localized_mod_strings.dart lib/localization/intl_*.arb
+```
+
+These are checked into the repo _only as a temporary workaround_.
+
+In the future, there should be a GN build rule that will generate these code
+files at build time, so as to not pollute the source tree (FL-162).
diff --git a/examples/localized_flutter_app/lib/localization/intl_messages.arb b/examples/localized_flutter_app/lib/localization/intl_messages.arb
new file mode 100644
index 0000000..750d348
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/intl_messages.arb
@@ -0,0 +1,25 @@
+{
+ "@@last_modified": "2019-01-24T15:29:41.574074",
+ "appTitle": "Localized Mod",
+ "@appTitle": {
+ "description": "Title of the demo application. \"Mod\" is short for \"module\" (can also be translated as \"app\").",
+ "type": "text",
+ "placeholders": {}
+ },
+ "bodyText": "{itemCount,plural, =0{There are no messages.}=1{There is one message.}other{There are {itemCount} messages.}}",
+ "@bodyText": {
+ "description": "How many messages are in the list.",
+ "type": "text",
+ "placeholders": {
+ "itemCount": {
+ "example": 42
+ }
+ }
+ },
+ "footer": "Coming soon: actually reading those messages!",
+ "@footer": {
+ "description": "Footer text of the demo application. Implies that messages cannot currently be read.",
+ "type": "text",
+ "placeholders": {}
+ }
+}
\ No newline at end of file
diff --git a/examples/localized_flutter_app/lib/localization/intl_messages_he.arb b/examples/localized_flutter_app/lib/localization/intl_messages_he.arb
new file mode 100644
index 0000000..45043dd
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/intl_messages_he.arb
@@ -0,0 +1,25 @@
+{
+ "@@locale": "he",
+ "appTitle": "אפליקציה מתורגמת לשפה מקומית",
+ "@appTitle": {
+ "description": "Title of the demo application. \"Mod\" is short for \"module\" (can also be translated as \"app\").",
+ "type": "text",
+ "placeholders": {}
+ },
+ "bodyText": "{itemCount, plural, =0 {אין הודעות.} =1 {יש הודעה אחת.} other {יש {itemCount} הודעות.}}",
+ "@bodyText": {
+ "description": "How many messages are in the list.",
+ "type": "text",
+ "placeholders": {
+ "itemCount": {
+ "example": "42"
+ }
+ }
+ },
+ "footer": "בקרוב: קריאת ההודעות האלה!",
+ "@footer": {
+ "description": "Footer text of the demo application. Implies that messages cannot currently be read.",
+ "type": "text",
+ "placeholders": {}
+ }
+}
\ No newline at end of file
diff --git a/examples/localized_flutter_app/lib/localization/messages_all.dart b/examples/localized_flutter_app/lib/localization/messages_all.dart
new file mode 100755
index 0000000..0fc8910
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/messages_all.dart
@@ -0,0 +1,139 @@
+// 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/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+// ignore: implementation_imports
+import 'package:intl/src/intl_helpers.dart';
+
+import 'messages_ar-XB.dart' deferred as messages_ar_xb;
+import 'messages_en-XA.dart' deferred as messages_en_xa;
+import 'messages_en-XC.dart' deferred as messages_en_xc;
+import 'messages_he.dart' deferred as messages_he;
+import 'messages_sr-Latn.dart' deferred as messages_sr_latn;
+import 'messages_sr.dart' deferred as messages_sr;
+
+typedef Future<dynamic> LibraryLoader();
+Map<String, LibraryLoader> _deferredLibraries = {
+ 'ar_XB': messages_ar_xb.loadLibrary,
+ 'en_XA': messages_en_xa.loadLibrary,
+ 'en_XC': messages_en_xc.loadLibrary,
+ 'sr_Latn': messages_sr_latn.loadLibrary,
+ 'he': messages_he.loadLibrary,
+ 'sr': messages_sr.loadLibrary,
+};
+
+MessageLookupByLibrary _findExact(localeName) {
+ switch (localeName) {
+ case 'ar_XB':
+ return messages_ar_xb.messages;
+ case 'en_XA':
+ return messages_en_xa.messages;
+ case 'en_XC':
+ return messages_en_xc.messages;
+ case 'sr_Latn':
+ return messages_sr_latn.messages;
+ case 'sr':
+ return messages_sr.messages;
+ case 'he':
+ return messages_he.messages;
+ default:
+ return null;
+ }
+}
+
+/// User programs should call this before using [localeName] for messages.
+Future<bool> initializeMessages(String localeName) async {
+ var availableLocale = Intl.verifiedLocale(
+ localeName, (locale) => _deferredLibraries[locale] != null,
+ onFailure: (_) => null);
+ if (availableLocale == null) {
+ // ignore: unnecessary_new
+ return new Future.value(false);
+ }
+ var lib = _deferredLibraries[availableLocale];
+ // ignore: unnecessary_new
+ await (lib == null ? new Future.value(false) : lib());
+ // ignore: unnecessary_new
+ initializeInternalMessageLookup(() => new CompositeMessageLookup());
+ messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
+ // ignore: unnecessary_new
+ return new Future.value(true);
+}
+
+bool _messagesExistFor(String locale) {
+ try {
+ return _findExact(locale) != null;
+ } catch (e) {
+ return false;
+ }
+}
+
+MessageLookupByLibrary _findGeneratedMessagesFor(locale) {
+ var actualLocale =
+ Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null);
+ if (actualLocale == null) return null;
+ return _findExact(actualLocale);
+}
+
+/// Turn the JSON template into a string.
+///
+/// We expect one of the following forms for the template.
+/// * null -> null
+/// * String s -> s
+/// * int n -> '${args[n]}'
+/// * List list, one of
+/// * ['Intl.plural', int howMany, (templates for zero, one, ...)]
+/// * ['Intl.gender', String gender, (templates for female, male, other)]
+/// * ['Intl.select', String choice, { 'case' : template, ...} ]
+/// * ['text alternating with ', 0 , ' indexes in the argument list']
+String evaluateJsonTemplate(Object input, List<dynamic> args) {
+ if (input == null) return null;
+ if (input is String) return input;
+ if (input is int) {
+ return "${args[input]}";
+ }
+
+ List<dynamic> template = input;
+ var messageName = template.first;
+ if (messageName == "Intl.plural") {
+ var howMany = args[template[1]];
+ return evaluateJsonTemplate(
+ Intl.pluralLogic(howMany,
+ zero: template[2],
+ one: template[3],
+ two: template[4],
+ few: template[5],
+ many: template[6],
+ other: template[7]),
+ args);
+ }
+ if (messageName == "Intl.gender") {
+ var gender = args[template[1]];
+ return evaluateJsonTemplate(
+ Intl.genderLogic(gender,
+ female: template[2], male: template[3], other: template[4]),
+ args);
+ }
+ if (messageName == "Intl.select") {
+ var select = args[template[1]];
+ var choices = template[2];
+ return evaluateJsonTemplate(Intl.selectLogic(select, choices), args);
+ }
+
+ // If we get this far, then we are a basic interpolation, just strings and
+ // ints.
+ // ignore: unnecessary_new
+ var output = new StringBuffer();
+ for (var entry in template) {
+ if (entry is int) {
+ output.write("${args[entry]}");
+ } else {
+ output.write("$entry");
+ }
+ }
+ return output.toString();
+}
diff --git a/examples/localized_flutter_app/lib/localization/messages_ar-XB.dart b/examples/localized_flutter_app/lib/localization/messages_ar-XB.dart
new file mode 100755
index 0000000..2945433
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/messages_ar-XB.dart
@@ -0,0 +1,34 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a ar_XB locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// ignore_for_file: unnecessary_brace_in_string_interps
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'dart:convert';
+import 'messages_all.dart' show evaluateJsonTemplate;
+
+// ignore: unnecessary_new
+final messages = new MessageLookup();
+
+// ignore: unused_element
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+// ignore: non_constant_identifier_names
+typedef MessageIfAbsent(String message_str, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+ get localeName => 'ar_XB';
+
+ String evaluateMessage(translation, List<dynamic> args) {
+ return evaluateJsonTemplate(translation, args);
+ }
+
+ var _messages;
+ // ignore: unnecessary_new
+ get messages => _messages ??= new JsonDecoder().convert(messageText);
+ static final messageText = r'''
+{"appTitle":"Localized Mod","bodyText":["Intl.plural",0,"There are no messages.","There is one message.",["There are ",0," messages."],["There are ",0," messages."],["There are ",0," messages."],["There are ",0," messages."]],"footer":"Coming soon: actually reading those messages!"}''';
+}
diff --git a/examples/localized_flutter_app/lib/localization/messages_en-XA.dart b/examples/localized_flutter_app/lib/localization/messages_en-XA.dart
new file mode 100755
index 0000000..fdd77f1
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/messages_en-XA.dart
@@ -0,0 +1,34 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a en_XA locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// ignore_for_file: unnecessary_brace_in_string_interps
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'dart:convert';
+import 'messages_all.dart' show evaluateJsonTemplate;
+
+// ignore: unnecessary_new
+final messages = new MessageLookup();
+
+// ignore: unused_element
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+// ignore: non_constant_identifier_names
+typedef MessageIfAbsent(String message_str, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+ get localeName => 'en_XA';
+
+ String evaluateMessage(translation, List<dynamic> args) {
+ return evaluateJsonTemplate(translation, args);
+ }
+
+ var _messages;
+ // ignore: unnecessary_new
+ get messages => _messages ??= new JsonDecoder().convert(messageText);
+ static final messageText = r'''
+{"appTitle":"[Ļöçåļîžéð Möð one two]","bodyText":["Intl.plural",0,"[Ţĥéŕé åŕé ñö méššåĝéš. one two three four five]","[Ţĥéŕé îš öñé méššåĝé. one two three four five]",null,null,null,["[Ţĥéŕé åŕé ᐅ",0,"ᐊ méššåĝéš. one two three four five]"]],"footer":"[Çömîñĝ šööñ: åçţûåļļý ŕéåðîñĝ ţĥöšé méššåĝéš¡ one two three four five six seven eight nine]"}''';
+}
diff --git a/examples/localized_flutter_app/lib/localization/messages_en-XC.dart b/examples/localized_flutter_app/lib/localization/messages_en-XC.dart
new file mode 100755
index 0000000..dd7a49f
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/messages_en-XC.dart
@@ -0,0 +1,34 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a en_XC locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// ignore_for_file: unnecessary_brace_in_string_interps
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'dart:convert';
+import 'messages_all.dart' show evaluateJsonTemplate;
+
+// ignore: unnecessary_new
+final messages = new MessageLookup();
+
+// ignore: unused_element
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+// ignore: non_constant_identifier_names
+typedef MessageIfAbsent(String message_str, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+ get localeName => 'en_XC';
+
+ String evaluateMessage(translation, List<dynamic> args) {
+ return evaluateJsonTemplate(translation, args);
+ }
+
+ var _messages;
+ // ignore: unnecessary_new
+ get messages => _messages ??= new JsonDecoder().convert(messageText);
+ static final messageText = r'''
+{"appTitle":"Localized Mod","bodyText":["Intl.plural",0,"There are no messages.","There is one message.",null,null,null,["There are ",0," messages."]],"footer":"Coming soon: actually reading those messages!"}''';
+}
diff --git a/examples/localized_flutter_app/lib/localization/messages_he.dart b/examples/localized_flutter_app/lib/localization/messages_he.dart
new file mode 100644
index 0000000..0246077
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/messages_he.dart
@@ -0,0 +1,29 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a he 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';
+
+// ignore: unnecessary_new
+final messages = MessageLookup();
+
+// ignore: unused_element
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+// ignore: non_constant_identifier_names
+typedef MessageIfAbsent(String message_str, List args);
+
+class MessageLookup extends MessageLookupByLibrary {
+ get localeName => 'he';
+
+ static m0(itemCount) => "${Intl.plural(itemCount, zero: 'אין הודעות.', one: 'יש הודעה אחת.', other: 'יש ${itemCount} הודעות.')}";
+
+ final messages = _notInlinedMessages(_notInlinedMessages);
+ static _notInlinedMessages(_) => <String, Function> {
+ "appTitle" : MessageLookupByLibrary.simpleMessage("אפליקציה מתורגמת לשפה מקומית"),
+ "bodyText" : m0,
+ "footer" : MessageLookupByLibrary.simpleMessage("בקרוב: קריאת ההודעות האלה!")
+ };
+}
diff --git a/examples/localized_flutter_app/lib/localization/messages_sr-Latn.dart b/examples/localized_flutter_app/lib/localization/messages_sr-Latn.dart
new file mode 100755
index 0000000..8e0d562
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/messages_sr-Latn.dart
@@ -0,0 +1,34 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a sr_Latn locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// ignore_for_file: unnecessary_brace_in_string_interps
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'dart:convert';
+import 'messages_all.dart' show evaluateJsonTemplate;
+
+// ignore: unnecessary_new
+final messages = new MessageLookup();
+
+// ignore: unused_element
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+// ignore: non_constant_identifier_names
+typedef MessageIfAbsent(String message_str, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+ get localeName => 'sr_Latn';
+
+ String evaluateMessage(translation, List<dynamic> args) {
+ return evaluateJsonTemplate(translation, args);
+ }
+
+ var _messages;
+ // ignore: unnecessary_new
+ get messages => _messages ??= new JsonDecoder().convert(messageText);
+ static final messageText = r'''
+{"appTitle":"Lokalizovani mod","bodyText":["Intl.plural",0,"Nema poruka.","Jedna poruka.",null,[0," poruke."],null,[0," poruka."]],"footer":"Dolazi uskoro: zapravo čitanje ovih poruka!"}''';
+}
diff --git a/examples/localized_flutter_app/lib/localization/messages_sr.dart b/examples/localized_flutter_app/lib/localization/messages_sr.dart
new file mode 100755
index 0000000..c7aa735
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localization/messages_sr.dart
@@ -0,0 +1,34 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a sr locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// ignore_for_file: unnecessary_brace_in_string_interps
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'dart:convert';
+import 'messages_all.dart' show evaluateJsonTemplate;
+
+// ignore: unnecessary_new
+final messages = new MessageLookup();
+
+// ignore: unused_element
+final _keepAnalysisHappy = Intl.defaultLocale;
+
+// ignore: non_constant_identifier_names
+typedef MessageIfAbsent(String message_str, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+ get localeName => 'sr';
+
+ String evaluateMessage(translation, List<dynamic> args) {
+ return evaluateJsonTemplate(translation, args);
+ }
+
+ var _messages;
+ // ignore: unnecessary_new
+ get messages => _messages ??= new JsonDecoder().convert(messageText);
+ static final messageText = r'''
+{"appTitle":"Локализовани мод","bodyText":["Intl.plural",0,"Нема порука.","Једна порука.",null,[0," поруке."],null,[0," порука."]],"footer":"Долази ускоро: заправо читање ових порука!"}''';
+}
diff --git a/examples/localized_flutter_app/lib/localized_mod_localizations_delegate.dart b/examples/localized_flutter_app/lib/localized_mod_localizations_delegate.dart
new file mode 100644
index 0000000..df79eea
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localized_mod_localizations_delegate.dart
@@ -0,0 +1,50 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:ui' show Locale;
+
+import 'package:flutter/material.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:intl/intl.dart';
+
+import 'localization/messages_all.dart' as messages_all;
+import 'supported_locales.dart';
+
+/// A [LocalizationsDelegate] for Localized Mod that connects Flutter's
+/// localization updates to the `intl` library's translation loading.
+///
+/// Every mod that uses localized strings needs to have a
+/// `LocalizationsDelegate`.
+class LocalizedModLocalizationsDelegate extends LocalizationsDelegate<void> {
+ /// Loads the translations for the given locale.
+ static Future<void> loadLocale(Locale locale) async {
+ final String name =
+ (locale.countryCode == null || locale.countryCode.isEmpty)
+ ? locale.languageCode
+ : locale.toString();
+ final String localeName = Intl.canonicalizedLocale(name);
+ await messages_all.initializeMessages(localeName);
+ Intl.defaultLocale = localeName;
+ }
+
+ const LocalizedModLocalizationsDelegate();
+
+ @override
+ Future<void> load(Locale locale) => loadLocale(locale);
+
+ @override
+ bool shouldReload(LocalizedModLocalizationsDelegate _) => false;
+
+ @override
+ bool isSupported(Locale locale) => supportedLocales.contains(locale);
+}
+
+const List<LocalizationsDelegate> allLocalizationsDelegates = [
+ // Delegate containing all app-level messages
+ LocalizedModLocalizationsDelegate(),
+ // Flutter-provided delegates for Flutter UI messages
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+];
diff --git a/examples/localized_flutter_app/lib/localized_mod_strings.dart b/examples/localized_flutter_app/lib/localized_mod_strings.dart
new file mode 100644
index 0000000..b197e13
--- /dev/null
+++ b/examples/localized_flutter_app/lib/localized_mod_strings.dart
@@ -0,0 +1,37 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:intl/intl.dart';
+
+// ignore: avoid_classes_with_only_static_members
+
+/// A container for strings used by Localized Mod.
+/// If the app were larger, there would be a separate __Strings class for each
+/// package that needed one.
+class LocalizedModStrings {
+ static String get appTitle => Intl.message(
+ 'Localized Mod',
+ name: 'appTitle',
+ desc: 'Title of the demo application. '
+ '"Mod" is short for "module" (can also be translated as "app").',
+ );
+
+ static String bodyText(int itemCount) => Intl.plural(
+ itemCount,
+ zero: 'There are no messages.',
+ one: 'There is one message.',
+ other: 'There are $itemCount messages.',
+ name: 'bodyText',
+ args: [itemCount],
+ desc: 'How many messages are in the list.',
+ examples: const {'itemCount': 42},
+ );
+
+ static String get footer => Intl.message(
+ 'Coming soon: actually reading those messages!',
+ name: 'footer',
+ desc: 'Footer text of the demo application. '
+ 'Implies that messages cannot currently be read.',
+ );
+}
diff --git a/examples/localized_flutter_app/lib/main.dart b/examples/localized_flutter_app/lib/main.dart
new file mode 100644
index 0000000..2989ea5
--- /dev/null
+++ b/examples/localized_flutter_app/lib/main.dart
@@ -0,0 +1,81 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:math';
+
+import 'package:flutter/material.dart';
+import 'package:lib.widgets/model.dart';
+
+import 'localized_mod_localizations_delegate.dart' as localizations_delegate;
+import 'localized_mod_strings.dart';
+import 'supported_locales.dart' as supported_locales;
+
+void main() {
+ final providers = Providers()..provideValue(CurrentLocale(null));
+
+ runApp(ProviderNode(providers: providers, child: LocalizedMod()));
+ // TODO(FL-157): Receive i18n settings from the View interface.
+}
+
+/// The main application widget for Localized Mod.
+class LocalizedMod extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return Provide<CurrentLocale>(
+ builder: (BuildContext context, Widget child, currentLocale) =>
+ MaterialApp(
+ onGenerateTitle: (BuildContext context) =>
+ LocalizedModStrings.appTitle,
+ localizationsDelegates:
+ localizations_delegate.allLocalizationsDelegates,
+ supportedLocales: supported_locales.supportedLocales,
+ locale: currentLocale.value,
+ home: _LocalizedModHome(Random().nextInt(100)),
+ ));
+ }
+}
+
+/// The root layout widget for Localized Mod.
+class _LocalizedModHome extends StatelessWidget {
+ static final List<DropdownMenuItem<Locale>> dropdownItems = supported_locales
+ .supportedLocales
+ .map((Locale locale) =>
+ DropdownMenuItem(value: locale, child: Text(locale.toString())))
+ .toList();
+
+ final int messageCount;
+
+ const _LocalizedModHome(this.messageCount) : super();
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(LocalizedModStrings.appTitle),
+ ),
+ body: Center(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ DropdownButton<Locale>(
+ value: Localizations.localeOf(context),
+ items: dropdownItems,
+ onChanged: (newValue) =>
+ Provide.value<CurrentLocale>(context).value = newValue),
+ Container(
+ margin: EdgeInsets.symmetric(vertical: 40),
+ child: Text(LocalizedModStrings.bodyText(messageCount))),
+ Container(
+ margin: EdgeInsets.symmetric(vertical: 40),
+ child: Text(LocalizedModStrings.footer)),
+ ],
+ ),
+ ));
+ }
+}
+
+/// Holds the current [Locale] and notifies any listeners when it changes.
+class CurrentLocale extends ValueNotifier<Locale> {
+ CurrentLocale(Locale value) : super(value);
+}
diff --git a/examples/localized_flutter_app/lib/supported_locales.dart b/examples/localized_flutter_app/lib/supported_locales.dart
new file mode 100644
index 0000000..a6bed55
--- /dev/null
+++ b/examples/localized_flutter_app/lib/supported_locales.dart
@@ -0,0 +1,21 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/widgets.dart';
+
+// Ideally, this file should be generated at build time from a build variable.
+const List<Locale> supportedLocales = <Locale>[
+ Locale('en'),
+ Locale('ar', 'XB'),
+ Locale('en', 'XA'),
+ Locale('en', 'XC'),
+ // Note: There is no messages_es.dart checked in. Creating this file is
+ // left as an exercise for those working on integrating Fuchsia with a
+ // translation pipeline.
+ Locale('es'),
+ Locale('he'),
+ Locale('sr'),
+ // Here is how to to specify a locale with a script code.
+ Locale.fromSubtags(languageCode: 'sr', scriptCode: 'Latn', countryCode: 'RS')
+];
diff --git a/examples/localized_flutter_app/meta/localized_flutter_app.cmx b/examples/localized_flutter_app/meta/localized_flutter_app.cmx
new file mode 100644
index 0000000..b6a31f1
--- /dev/null
+++ b/examples/localized_flutter_app/meta/localized_flutter_app.cmx
@@ -0,0 +1,14 @@
+{
+ "program": {
+ "data": "data/localized_flutter_app"
+ },
+ "sandbox": {
+ "services": [
+ "fuchsia.accessibility.ToggleBroadcaster",
+ "fuchsia.fonts.Provider",
+ "fuchsia.modular.Clipboard",
+ "fuchsia.sys.Environment",
+ "fuchsia.ui.scenic.Scenic"
+ ]
+ }
+}
diff --git a/examples/localized_flutter_app/pubspec.yaml b/examples/localized_flutter_app/pubspec.yaml
new file mode 100644
index 0000000..0673f90
--- /dev/null
+++ b/examples/localized_flutter_app/pubspec.yaml
@@ -0,0 +1,17 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+name:
+ localized_flutter_app
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_localizations:
+ sdk: flutter
+ intl: ^0.15.7
+ intl_translation: ^0.17.2
+
+flutter:
+ uses-material-design: true
diff --git a/examples/localized_flutter_app/test/sample_test.dart b/examples/localized_flutter_app/test/sample_test.dart
new file mode 100644
index 0000000..c0596dd
--- /dev/null
+++ b/examples/localized_flutter_app/test/sample_test.dart
@@ -0,0 +1,11 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+void main() {
+ test('test should pass', () {
+ expect(1, 1);
+ });
+}