Reland "[dart][l10n] Ermine shell localization example."

This reverts commit 7eeb110ed7dca46f2ca5f686e10db298e9cc9eba.

Reason for revert: 2nd try, correcting the original error. Added a
regression test for the issue that caused the revert.  The new attempt
is updated with the new strings from the battery management status.

TESTED=
  fx set workstation.chromebook-x64 --with=//src/ermine:tests
  fx run-host-tests ermine_internationalization_unittests

Original change's description:
> Revert "[dart][l10n] Ermine shell localization example."
>
> This reverts commit 3af5f54564a3a6cc1993f5fb74d1420fa04464c1.
>
> Reason for revert: <INSERT REASONING HERE>
>
> Original change's description:
> > [dart][l10n] Ermine shell localization example.
> >
> > This change adds scaffolding that allows Ermine shell
> > to display some system messages in a language
> > other than English.
> >
> > At the moment there is no way to select the locale of
> > the shell.  This will be added in followup
> > changes.  The resources are also not translated
> > since currently there is no infrastructure to do so.
> >
> > Added the basic documentation that explains how the localization is
> > used and maintained.
> >
> > Bug: 37241
> > Change-Id: I6414acf26f894564575fa4222cc386ebb93cd2a8
>
> TBR=anwilson@google.com,sanjayc@google.com,maxbittker@google.com,kpozin@google.com,wleshner@google.com,fmil@google.com,cwhitten@google.com
>
> Change-Id: Ieadbf77e37c057780fd1527e7c98ef232bfb2d62
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Bug: 37241

TBR=anwilson@google.com,sanjayc@google.com,maxbittker@google.com,kpozin@google.com,wleshner@google.com,fmil@google.com,cwhitten@google.com

Change-Id: I0602a5a25d34cdb76c6293daafbc432c0233d565
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 37241
diff --git a/session_shells/BUILD.gn b/session_shells/BUILD.gn
index 20f1d2d..227e5c0 100644
--- a/session_shells/BUILD.gn
+++ b/session_shells/BUILD.gn
@@ -14,6 +14,7 @@
   deps = [
     "ermine/settings:ermine_settings_unittests($host_toolchain)",
     "ermine/shell:ermine_unittests($host_toolchain)",
+    "ermine/internationalization:ermine_internationalization_unittests($host_toolchain)",
     "//src/experiences/lib/quickui:quickui_unittests($host_toolchain)",
   ]
 }
diff --git a/session_shells/ermine/internationalization/BUILD.gn b/session_shells/ermine/internationalization/BUILD.gn
new file mode 100644
index 0000000..a3c0464
--- /dev/null
+++ b/session_shells/ermine/internationalization/BUILD.gn
@@ -0,0 +1,48 @@
+# 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("//src/modular/build/modular_config/modular_config.gni")
+import("//build/dart/dart_library.gni")
+import("//topaz/runtime/dart/flutter_test.gni")
+import("//topaz/runtime/flutter_runner/flutter_app.gni")
+
+dart_library("internationalization") {
+  package_name = "internationalization"
+  # Disabling analysis for the time being, this is mostly generated code to begin
+  # with, and there are issues like this one that make things harder than necessary:
+  # https://github.com/dart-lang/sdk/issues/38598.
+  disable_analysis = true
+
+  sources = [
+    "current_locale.dart",
+    "localization/messages_all.dart",
+    "localization/messages_messages.dart",
+    "localization/messages_nl.dart",
+    "localization/messages_sr.dart",
+    "localizations_delegate.dart",
+    "profile_provider.dart",
+    "strings.dart",
+    "supported_locales.dart",
+  ]
+
+  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",
+  ]
+}
+
+flutter_test("ermine_internationalization_unittests") {
+  sources = [
+    "current_locale_test.dart",
+  ]
+
+  deps = [
+    ":internationalization",
+    "//third_party/dart-pkg/git/flutter/packages/flutter_test",
+    "//third_party/dart-pkg/pub/mockito",
+    "//third_party/dart-pkg/pub/test",
+  ]
+}
diff --git a/session_shells/ermine/internationalization/README.md b/session_shells/ermine/internationalization/README.md
new file mode 100644
index 0000000..49ae79a
--- /dev/null
+++ b/session_shells/ermine/internationalization/README.md
@@ -0,0 +1,78 @@
+# Ermine internationalization and localization
+
+This Dart package contains the support for Ermine Shell localization and
+internationalization.
+
+At its current state the support is in its very early phase.  There is a lot of
+manual scaffolding that could be replaced by auto-generating the code of
+interest.  Doing so will be the topic of followup work.
+
+# How to use this package
+
+This package is purpose-built for the Ermine Shell.  The central part is the 
+file `lib/strings.dart` which by design contains all the strings that the Ermine
+Shell needs to vary based on the system locale.
+
+Add a function to this file whenever you need to introduce a new text message.
+
+# Adding a dependency
+
+To make the library available to other Ermine Shell packages, add a dependency:
+
+```
+"//src/experiences/session_shells/ermine/internationalization"
+```
+
+to the dependencies in the appropriate `BUILD.gn` rule of your package.
+
+# Importing the new dependency
+
+To import the new dependency, add the import:
+
+```dart
+import 'packages:internationalization/strings.dart' as strings;
+```
+
+to the import section of any Dart file that needs strings.
+
+# Referring to localized strings
+
+You can now refer to the messages defined in [strings.dart][lib/strings.dart].
+For example, you can refer to the string `Ask` by calling `strings.ask()`.
+This is done so that the localization system can substitute the word `Ask` for
+the appropriate word in the language defined by the current locale.  The approach
+extends in similar ways for most strings; with slight variations depending on 
+whether number or date, or plural or gender formatting is needed.
+
+# Generating localizable libraries
+
+Two scripts are provided in the `./scripts` directory.  One is
+`./scripts/run_extract_to_arb.sh`, which produces a "translation interchange
+form" in the form of [ARB][arb] files. The ARB files can be translated
+independently of the source code, and should be checked into the code base for
+now.
+
+## Translating
+
+The translation process amounts to making ARB files for each of the supported
+locales of interest and providing the translations for the messages and
+strings.  The basic tool for doing so would be a simple text editor. While
+there also exists a wealth of tools that help software translators, it is out
+of scope of this file to go into specific details.  So the choice of the
+translation environment is left to the developer and translator.
+
+## Generating Dart runtime format for the translations
+
+Once translated you can use the file `./scripts/run_generate_from_arb.sh` to
+generate the Dart code that wires up the translation.  You will need to rebuild
+the entire project to take the new translations in.
+
+# Further reading
+
+Internationalization and localization are topic unto themselves, and it is out
+of scope of this file to go into all the details.  Please see the section
+on [Internationalizing Flutter apps][1] for many more details that are directly
+relevant to Dart and Flutter app localization.
+
+[1]: https://flutter.dev/docs/development/accessibility-and-localization/internationalization 
+[arb]: https://github.com/google/app-resource-bundle 
diff --git a/session_shells/ermine/internationalization/analysis_options.yaml b/session_shells/ermine/internationalization/analysis_options.yaml
new file mode 100644
index 0000000..86fb3da
--- /dev/null
+++ b/session_shells/ermine/internationalization/analysis_options.yaml
@@ -0,0 +1,6 @@
+# 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/session_shells/ermine/internationalization/lib/current_locale.dart b/session_shells/ermine/internationalization/lib/current_locale.dart
new file mode 100644
index 0000000..d5a2688
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/current_locale.dart
@@ -0,0 +1,19 @@
+// 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:ui' show Locale;
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+
+/// Holds the current [Locale] and notifies any listeners of value changes.
+class CurrentLocale extends ValueNotifier<Locale> {
+  CurrentLocale(Locale value) : super(value);
+
+  /// Returns the Unicode locale of the current locale as a string.  E.g.
+  /// "en_US".
+  String unicode() =>
+      // Locale.toString() is for debugging purposes only, but it's the
+      // correct form.
+      Intl.canonicalizedLocale(super.value.toString());
+}
diff --git a/session_shells/ermine/internationalization/lib/localization/messages_all.dart b/session_shells/ermine/internationalization/lib/localization/messages_all.dart
new file mode 100644
index 0000000..2836a52
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/localization/messages_all.dart
@@ -0,0 +1,71 @@
+// 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.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:implementation_imports, file_names, unnecessary_new
+// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
+// ignore_for_file:argument_type_not_assignable, invalid_assignment
+// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
+// ignore_for_file:comment_references
+
+import 'dart:async';
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'package:intl/src/intl_helpers.dart';
+
+import 'messages_messages.dart' deferred as messages_messages;
+import 'messages_nl.dart' deferred as messages_nl;
+import 'messages_sr.dart' deferred as messages_sr;
+
+typedef Future<dynamic> LibraryLoader();
+Map<String, LibraryLoader> _deferredLibraries = {
+  'messages': messages_messages.loadLibrary,
+  'nl': messages_nl.loadLibrary,
+  'sr': messages_sr.loadLibrary,
+};
+
+MessageLookupByLibrary _findExact(String localeName) {
+  switch (localeName) {
+    case 'messages':
+      return messages_messages.messages;
+    case 'nl':
+      return messages_nl.messages;
+    case 'sr':
+      return messages_sr.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) {
+    return new Future.value(false);
+  }
+  var lib = _deferredLibraries[availableLocale];
+  await (lib == null ? new Future.value(false) : lib());
+  initializeInternalMessageLookup(() => new CompositeMessageLookup());
+  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
+  return new Future.value(true);
+}
+
+bool _messagesExistFor(String locale) {
+  try {
+    return _findExact(locale) != null;
+  } catch (e) {
+    return false;
+  }
+}
+
+MessageLookupByLibrary _findGeneratedMessagesFor(String locale) {
+  var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
+      onFailure: (_) => null);
+  if (actualLocale == null) return null;
+  return _findExact(actualLocale);
+}
diff --git a/session_shells/ermine/internationalization/lib/localization/messages_messages.dart b/session_shells/ermine/internationalization/lib/localization/messages_messages.dart
new file mode 100644
index 0000000..5c72e12
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/localization/messages_messages.dart
@@ -0,0 +1,75 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a messages locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
+// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
+// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
+// ignore_for_file:unused_import, file_names
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  String get localeName => 'messages';
+
+  static m0(numThreads) => "${Intl.plural(numThreads, zero: '${numThreads} THR', one: '${numThreads} THR', other: '${numThreads} THR')}";
+
+  static m1(numTasks) => "${Intl.plural(numTasks, zero: '${numTasks} RUNNING', one: '${numTasks} RUNNING', other: '${numTasks} RUNNING')}";
+
+  static m2(numTasks) => "${Intl.plural(numTasks, zero: '${numTasks}', one: '${numTasks}', other: '${numTasks}')}";
+
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+    "ask" : MessageLookupByLibrary.simpleMessage("ASK"),
+    "auto" : MessageLookupByLibrary.simpleMessage("Auto"),
+    "back" : MessageLookupByLibrary.simpleMessage("Back"),
+    "batt" : MessageLookupByLibrary.simpleMessage("Batt"),
+    "battery" : MessageLookupByLibrary.simpleMessage("Battery"),
+    "brightness" : MessageLookupByLibrary.simpleMessage("Brightness"),
+    "browser" : MessageLookupByLibrary.simpleMessage("Browser"),
+    "cancel" : MessageLookupByLibrary.simpleMessage("Cancel"),
+    "chrome" : MessageLookupByLibrary.simpleMessage("Chrome"),
+    "cpu" : MessageLookupByLibrary.simpleMessage("CPU"),
+    "date" : MessageLookupByLibrary.simpleMessage("Date"),
+    "done" : MessageLookupByLibrary.simpleMessage("Done"),
+    "fps" : MessageLookupByLibrary.simpleMessage("FPS"),
+    "ide" : MessageLookupByLibrary.simpleMessage("IDE"),
+    "max" : MessageLookupByLibrary.simpleMessage("Max"),
+    "mem" : MessageLookupByLibrary.simpleMessage("MEM"),
+    "memory" : MessageLookupByLibrary.simpleMessage("Memory"),
+    "min" : MessageLookupByLibrary.simpleMessage("Min"),
+    "mockWirelessNetwork" : MessageLookupByLibrary.simpleMessage("Wireless_Network"),
+    "music" : MessageLookupByLibrary.simpleMessage("Music"),
+    "name" : MessageLookupByLibrary.simpleMessage("Name"),
+    "nameThisStory" : MessageLookupByLibrary.simpleMessage("Name this story"),
+    "network" : MessageLookupByLibrary.simpleMessage("Network"),
+    "numThreads" : m0,
+    "overview" : MessageLookupByLibrary.simpleMessage("Overview"),
+    "pause" : MessageLookupByLibrary.simpleMessage("Pause"),
+    "pid" : MessageLookupByLibrary.simpleMessage("PID"),
+    "powerOff" : MessageLookupByLibrary.simpleMessage("Power Off"),
+    "recents" : MessageLookupByLibrary.simpleMessage("Recents"),
+    "restart" : MessageLookupByLibrary.simpleMessage("Restart"),
+    "runningTasks" : m1,
+    "settings" : MessageLookupByLibrary.simpleMessage("Settings"),
+    "shutdown" : MessageLookupByLibrary.simpleMessage("Shutdown"),
+    "signalStrong" : MessageLookupByLibrary.simpleMessage("Strong Signal"),
+    "skip" : MessageLookupByLibrary.simpleMessage("Skip"),
+    "sleep" : MessageLookupByLibrary.simpleMessage("Sleep"),
+    "sunny" : MessageLookupByLibrary.simpleMessage("Sunny"),
+    "tasks" : MessageLookupByLibrary.simpleMessage("TASKS"),
+    "topProcesses" : MessageLookupByLibrary.simpleMessage("Top Processes"),
+    "totalTasks" : m2,
+    "typeToAsk" : MessageLookupByLibrary.simpleMessage("TYPE TO ASK"),
+    "volume" : MessageLookupByLibrary.simpleMessage("Volume"),
+    "weather" : MessageLookupByLibrary.simpleMessage("Weather"),
+    "wireless" : MessageLookupByLibrary.simpleMessage("Wireless")
+  };
+}
diff --git a/session_shells/ermine/internationalization/lib/localization/messages_nl.dart b/session_shells/ermine/internationalization/lib/localization/messages_nl.dart
new file mode 100644
index 0000000..1d4fe32
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/localization/messages_nl.dart
@@ -0,0 +1,65 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a nl locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
+// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
+// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
+// ignore_for_file:unused_import, file_names
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  String get localeName => 'nl';
+
+  static m0(numThreads) => "${Intl.plural(numThreads, zero: '${numThreads} DRAA', one: '${numThreads} DRAA', other: '${numThreads} DRAA')}";
+
+  static m1(numTasks) => "${Intl.plural(numTasks, zero: '${numTasks} LOPENDE', one: '${numTasks} LOPEND', other: '${numTasks} LOPENDE')}";
+
+  static m2(numTasks) => "${Intl.plural(numTasks, zero: '${numTasks}', one: '${numTasks}', other: '${numTasks}')}";
+
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+    "ask" : MessageLookupByLibrary.simpleMessage("ZOEKT"),
+    "back" : MessageLookupByLibrary.simpleMessage("TERUG"),
+    "batt" : MessageLookupByLibrary.simpleMessage("BATT"),
+    "battery" : MessageLookupByLibrary.simpleMessage("Batterij"),
+    "brightness" : MessageLookupByLibrary.simpleMessage("HELDERHEID"),
+    "cancel" : MessageLookupByLibrary.simpleMessage("Annuleren"),
+    "cpu" : MessageLookupByLibrary.simpleMessage("CPU"),
+    "date" : MessageLookupByLibrary.simpleMessage("DATUM"),
+    "done" : MessageLookupByLibrary.simpleMessage("Klaar"),
+    "fps" : MessageLookupByLibrary.simpleMessage("FPS"),
+    "max" : MessageLookupByLibrary.simpleMessage("MAX"),
+    "memory" : MessageLookupByLibrary.simpleMessage("GEHEUGEN"),
+    "min" : MessageLookupByLibrary.simpleMessage("MIN"),
+    "music" : MessageLookupByLibrary.simpleMessage("MUZIEK"),
+    "name" : MessageLookupByLibrary.simpleMessage("Naam"),
+    "nameThisStory" : MessageLookupByLibrary.simpleMessage("Noem dit verhaal"),
+    "network" : MessageLookupByLibrary.simpleMessage("NETWERK"),
+    "numThreads" : m0,
+    "pause" : MessageLookupByLibrary.simpleMessage("PAUZE"),
+    "powerOff" : MessageLookupByLibrary.simpleMessage("UITSCHAKELEN"),
+    "restart" : MessageLookupByLibrary.simpleMessage("HERSTARTEN"),
+    "runningTasks" : m1,
+    "settings" : MessageLookupByLibrary.simpleMessage("INSTELLINGEN"),
+    "signalStrong" : MessageLookupByLibrary.simpleMessage("STERK SIGNAAL"),
+    "skip" : MessageLookupByLibrary.simpleMessage("OVERSLAAN"),
+    "sleep" : MessageLookupByLibrary.simpleMessage("SLAAP"),
+    "sunny" : MessageLookupByLibrary.simpleMessage("ZONNIG"),
+    "tasks" : MessageLookupByLibrary.simpleMessage("TAKEN"),
+    "topProcesses" : MessageLookupByLibrary.simpleMessage("TOP PROCESSEN"),
+    "totalTasks" : m2,
+    "typeToAsk" : MessageLookupByLibrary.simpleMessage("ZOEKT EN GIJ ZAL SPINAZIE ETEN"),
+    "volume" : MessageLookupByLibrary.simpleMessage("GELUID"),
+    "weather" : MessageLookupByLibrary.simpleMessage("WEER"),
+    "wireless" : MessageLookupByLibrary.simpleMessage("DRAADLOZE")
+  };
+}
diff --git a/session_shells/ermine/internationalization/lib/localization/messages_sr.dart b/session_shells/ermine/internationalization/lib/localization/messages_sr.dart
new file mode 100644
index 0000000..6706563
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/localization/messages_sr.dart
@@ -0,0 +1,65 @@
+// 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 issues from commonly used lints in this file.
+// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
+// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
+// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
+// ignore_for_file:unused_import, file_names
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  String get localeName => 'sr';
+
+  static m0(numThreads) => "${Intl.plural(numThreads, zero: '${numThreads} НИТИ', one: '${numThreads} НИТ', other: '${numThreads} НИТИ')}";
+
+  static m1(numTasks) => "${Intl.plural(numTasks, zero: '${numTasks} ЗАДАТАКА', one: '${numTasks} ЗАДАТАК', other: '${numTasks} ЗАДАТАКА')}";
+
+  static m2(numTasks) => "${Intl.plural(numTasks, zero: '${numTasks}', one: '${numTasks}', other: '${numTasks}')}";
+
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+    "ask" : MessageLookupByLibrary.simpleMessage("УПИТ"),
+    "back" : MessageLookupByLibrary.simpleMessage("НАЗАД"),
+    "batt" : MessageLookupByLibrary.simpleMessage("БАТ"),
+    "battery" : MessageLookupByLibrary.simpleMessage("Батерија"),
+    "brightness" : MessageLookupByLibrary.simpleMessage("ОСВЕТЉАЈ"),
+    "cancel" : MessageLookupByLibrary.simpleMessage("Откажи"),
+    "cpu" : MessageLookupByLibrary.simpleMessage("ПРОЦ"),
+    "date" : MessageLookupByLibrary.simpleMessage("ДАТУМ"),
+    "done" : MessageLookupByLibrary.simpleMessage("Готово"),
+    "fps" : MessageLookupByLibrary.simpleMessage("СЛ/СЕК"),
+    "max" : MessageLookupByLibrary.simpleMessage("МАКС"),
+    "memory" : MessageLookupByLibrary.simpleMessage("МЕМОРИЈА"),
+    "min" : MessageLookupByLibrary.simpleMessage("МИН"),
+    "music" : MessageLookupByLibrary.simpleMessage("МУЗИКА"),
+    "name" : MessageLookupByLibrary.simpleMessage("ИМЕ"),
+    "nameThisStory" : MessageLookupByLibrary.simpleMessage("Именуј ову причу"),
+    "network" : MessageLookupByLibrary.simpleMessage("МРЕЖА"),
+    "numThreads" : m0,
+    "pause" : MessageLookupByLibrary.simpleMessage("ПАУЗА"),
+    "powerOff" : MessageLookupByLibrary.simpleMessage("ИСКЉУЧИ"),
+    "restart" : MessageLookupByLibrary.simpleMessage("РЕСТАРТ"),
+    "runningTasks" : m1,
+    "settings" : MessageLookupByLibrary.simpleMessage("ПОДЕШАВАЊА"),
+    "signalStrong" : MessageLookupByLibrary.simpleMessage("ЈАК СИГНАЛ"),
+    "skip" : MessageLookupByLibrary.simpleMessage("ПРЕСКОЧИ"),
+    "sleep" : MessageLookupByLibrary.simpleMessage("СПАВАЊЕ"),
+    "sunny" : MessageLookupByLibrary.simpleMessage("СУНЧАНО"),
+    "tasks" : MessageLookupByLibrary.simpleMessage("ЗАДАЦИ"),
+    "topProcesses" : MessageLookupByLibrary.simpleMessage("ВРШНИ ПРОЦЕСИ"),
+    "totalTasks" : m2,
+    "typeToAsk" : MessageLookupByLibrary.simpleMessage("УНЕСИТЕ УПИТ"),
+    "volume" : MessageLookupByLibrary.simpleMessage("ЈАЧИНА"),
+    "weather" : MessageLookupByLibrary.simpleMessage("ВРЕМЕ"),
+    "wireless" : MessageLookupByLibrary.simpleMessage("БЕЖИЧНА")
+  };
+}
diff --git a/session_shells/ermine/internationalization/lib/localizations_delegate.dart b/session_shells/ermine/internationalization/lib/localizations_delegate.dart
new file mode 100644
index 0000000..f8d782d
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/localizations_delegate.dart
@@ -0,0 +1,47 @@
+// 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.
+
+// Should the localizations delegate be generated instead?
+
+import 'dart:async';
+import 'dart:ui' show Locale;
+
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'package:fuchsia_logger/logger.dart';
+
+import 'localization/messages_all.dart' as messages_all;
+import 'supported_locales.dart' as supported_locales;
+
+LocalizationsDelegate<void> delegate() => _LocalizationsDelegate();
+
+class _LocalizationsDelegate extends LocalizationsDelegate<void> {
+  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);
+    log.info(
+        '_LocalizationsDelegate: current default locale: ${Intl.defaultLocale}');
+  }
+
+  const _LocalizationsDelegate();
+
+  @override
+  Future<void> load(Locale locale) => loadLocale(locale);
+
+  // For the time being, never reload.
+  @override
+  bool shouldReload(_LocalizationsDelegate __) => false;
+
+  @override
+  bool isSupported(Locale locale) {
+    bool supported = supported_locales.locales.contains(locale);
+    log.finer(
+        '_LocalizationsDelegate: locale: ${locale.toString()}; isSupported: ${supported.toString()}');
+    return supported;
+  }
+}
diff --git a/session_shells/ermine/internationalization/lib/strings.dart b/session_shells/ermine/internationalization/lib/strings.dart
new file mode 100644
index 0000000..6ce95c5
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/strings.dart
@@ -0,0 +1,298 @@
+// 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.
+
+// This file is an L10N developer experience study fragment.
+// Let's see how it works when you offload all strings to a file.
+
+// The Intl.message texts have been offloaded to a separate file so as to
+// minimize the amount of code needed to be exported for localization.
+// While the individual string messages have been left in their original form,
+// the case-ness of the text is part of the presentation so should probably be
+// handled in the view code.
+
+import 'package:intl/intl.dart';
+
+String brightness() => Intl.message(
+      'Brightness',
+      name: 'brightness',
+      desc: 'The label for the screen brightness settings widget',
+    );
+
+String cancel() => Intl.message(
+      'Cancel',
+      name: 'cancel',
+      desc: 'The label for canceling an offered command',
+    );
+
+String done() => Intl.message(
+      'Done',
+      name: 'done',
+      desc: 'The label for saying we are done with a command',
+    );
+
+
+String sleep() => Intl.message(
+      'Sleep',
+      name: 'sleep',
+      desc: 'The label for the sleep settings widget',
+    );
+
+String restart() => Intl.message(
+      'Restart',
+      name: 'restart',
+      desc: 'The label for the restart button',
+    );
+
+String powerOff() => Intl.message(
+      'Power Off',
+      name: 'powerOff',
+      desc: 'The label for the restart button',
+    );
+
+String settings() => Intl.message(
+      'Settings',
+      name: 'settings',
+      desc: 'The label for the settings button',
+    );
+
+String volume() => Intl.message(
+      'Volume',
+      name: 'volume',
+      desc: 'The label for the settings button',
+    );
+
+String min() => Intl.message(
+      'Min',
+      name: 'min',
+      desc:
+          'The short label for "minimal" label for the volume button',
+    );
+
+String max() => Intl.message(
+      'Max',
+      name: 'max',
+      desc:
+          'The shortened label for "maximal" label for the volume button',
+    );
+
+String music() => Intl.message(
+      'Music',
+      name: 'music',
+      desc: 'The label used for the music button',
+    );
+
+String back() => Intl.message(
+      'Back',
+      name: 'back',
+      desc: 'The label for the "Back" button',
+    );
+
+String pause() => Intl.message(
+      'Pause',
+      name: 'pause',
+      desc: 'The label for the "Pause" button',
+    );
+
+String skip() => Intl.message(
+      'Skip',
+      name: 'skip',
+      desc: 'The label for the "Skip" button',
+    );
+
+String topProcesses() => Intl.message(
+      'Top Processes',
+      name: 'topProcesses',
+      desc: 'The label for the "Top processes" label',
+    );
+
+String shutdown() => Intl.message(
+      'Shutdown',
+      name: 'shutdown',
+      desc: 'The label for the "System shutdown" label',
+    );
+
+String memory() => Intl.message(
+      'Memory',
+      name: 'memory',
+      desc: 'The label for the "memory" label',
+    );
+
+String cpu() => Intl.message(
+      'CPU',
+      name: 'cpu',
+      desc: 'The short name for the "Central Processing Unit" label',
+    );
+
+String mem() => Intl.message(
+      'MEM',
+      name: 'mem',
+      desc: 'The short name for the "Memory" label',
+    );
+
+String ide() => Intl.message(
+      'IDE',
+      name: 'ide',
+      desc: 'The short name for the "Integrated Development Environment" label',
+    );
+
+String chrome() => Intl.message(
+      'Chrome',
+      name: 'chrome',
+      desc: 'The short name for the "Google Chrome" browser',
+    );
+
+
+String pid() => Intl.message(
+      'PID',
+      name: 'pid',
+      desc: 'The short name for the "Process identifier" label',
+    );
+
+
+String tasks() => Intl.message(
+      'TASKS',
+      name: 'tasks',
+      desc: 'The short name for the "Tasks" label',
+    );
+
+String weather() => Intl.message(
+      'Weather',
+      name: 'weather',
+      desc: 'The short name for the "Weather" label',
+    );
+
+String date() => Intl.message(
+      'Date',
+      name: 'date',
+      desc: 'The short name for the "date" label',
+    );
+
+String network() => Intl.message(
+      'Network',
+      name: 'network',
+      desc: 'The short name for the "network" label',
+    );
+
+String fps() => Intl.message(
+      'FPS',
+      name: 'fps',
+      desc: 'The short name for the "Frames per Second" label',
+    );
+
+String batt() => Intl.message(
+      'Batt',
+      name: 'batt',
+      desc: 'The short name for the "Battery level" label',
+    );
+
+String battery() => Intl.message(
+      'Battery',
+      name: 'battery',
+      desc: 'The long name for the "Battery level" label',
+    );
+
+String wireless() => Intl.message(
+      'Wireless',
+      name: 'wireless',
+      desc: 'The short name for the "Wireless network" label',
+    );
+
+String signalStrong() => Intl.message(
+      'Strong Signal',
+      name: 'signalStrong',
+      desc: 'The short name for the "Strong signal" label',
+    );
+
+String sunny() => Intl.message(
+      'Sunny',
+      name: 'sunny',
+      desc: 'The short name for the "Weather is sunny" label',
+    );
+
+String runningTasks(int numTasks) => Intl.plural(
+      numTasks,
+      zero: '$numTasks RUNNING',
+      one: '$numTasks RUNNING',
+      other: '$numTasks RUNNING',
+      name: 'runningTasks',
+      args: [numTasks],
+      desc: 'How many tasks are currently running',
+      examples: const {'numTasks': 42},
+    );
+
+String totalTasks(int numTasks) => Intl.plural(
+      numTasks,
+      zero: '$numTasks',
+      one: '$numTasks',
+      other: '$numTasks',
+      name: 'totalTasks',
+      args: [numTasks],
+      desc: 'How many tasks are currently running',
+      examples: const {'numTasks': 42},
+    );
+
+String numThreads(int numThreads) => Intl.plural(
+      numThreads,
+      zero: '$numThreads THR',
+      one: '$numThreads THR',
+      other: '$numThreads THR',
+      name: 'numThreads',
+      args: [numThreads],
+      desc: 'How many threads are currently there, short label',
+      examples: const {'numThreads': 42},
+    );
+
+String name() => Intl.message(
+      'Name',
+      name: 'name',
+      desc: 'A generic label for a name, sentence case.',
+    );
+
+String ask() => Intl.message(
+      'ASK',
+      name: 'ask',
+      desc: 'A generic short label for "query", sentence case.',
+    );
+
+String typeToAsk() => Intl.message(
+      'TYPE TO ASK',
+      name: 'typeToAsk',
+      desc: 'Shown in the ask text entry box in Ermine shell',
+    );
+
+String overview() => Intl.message(
+      'Overview',
+      name: 'overview',
+      desc: 'Shown in top bar on the Ermine shell',
+    );
+
+String recents() => Intl.message(
+      'Recents',
+      name: 'recents',
+      desc: 'A list of recent apps',
+    );
+
+String browser() => Intl.message(
+      'Browser',
+      name: 'browser',
+      desc: 'A button to invoke a browser with',
+    );
+
+String auto() => Intl.message(
+      'Auto',
+      name: 'auto',
+      desc: 'A shorthand for "automatic" setting.',
+    );
+
+String mockWirelessNetwork() => Intl.message(
+      'Wireless_Network',
+      name: 'mockWirelessNetwork',
+      desc: 'A mock label for a wireless network name that the device is connected to',
+    );
+
+String nameThisStory() => Intl.message(
+      'Name this story',
+      name: 'nameThisStory',
+      desc: 'A hint appearing in a window for naming a story. This text should not include padding, but now it does.',
+    );
diff --git a/session_shells/ermine/internationalization/lib/supported_locales.dart b/session_shells/ermine/internationalization/lib/supported_locales.dart
new file mode 100644
index 0000000..84d978f
--- /dev/null
+++ b/session_shells/ermine/internationalization/lib/supported_locales.dart
@@ -0,0 +1,10 @@
+// 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:ui' show Locale;
+
+List<Locale> locales = [
+  const Locale.fromSubtags(languageCode: 'sr'),
+  const Locale.fromSubtags(languageCode: 'nl'),
+];
diff --git a/session_shells/ermine/internationalization/pubspec.yaml b/session_shells/ermine/internationalization/pubspec.yaml
new file mode 100644
index 0000000..4c61966
--- /dev/null
+++ b/session_shells/ermine/internationalization/pubspec.yaml
@@ -0,0 +1,12 @@
+# 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: internationalization
+description: The internationalization library for Ermine.
+
+flutter:
+
+dependencies:
+  # Needed for the ARB extraction scripts until this is fully automated.
+  intl_translation: ^0.17.2
+
diff --git a/session_shells/ermine/internationalization/resources/intl_messages.arb b/session_shells/ermine/internationalization/resources/intl_messages.arb
new file mode 100644
index 0000000..9dc62d8
--- /dev/null
+++ b/session_shells/ermine/internationalization/resources/intl_messages.arb
@@ -0,0 +1,279 @@
+{
+  "@@last_modified": "2019-10-04T10:36:29.147518",
+  "brightness": "Brightness",
+  "@brightness": {
+    "description": "The label for the screen brightness settings widget",
+    "type": "text",
+    "placeholders": {}
+  },
+  "cancel": "Cancel",
+  "@cancel": {
+    "description": "The label for canceling an offered command",
+    "type": "text",
+    "placeholders": {}
+  },
+  "done": "Done",
+  "@done": {
+    "description": "The label for saying we are done with a command",
+    "type": "text",
+    "placeholders": {}
+  },
+  "sleep": "Sleep",
+  "@sleep": {
+    "description": "The label for the sleep settings widget",
+    "type": "text",
+    "placeholders": {}
+  },
+  "restart": "Restart",
+  "@restart": {
+    "description": "The label for the restart button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "powerOff": "Power Off",
+  "@powerOff": {
+    "description": "The label for the restart button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "settings": "Settings",
+  "@settings": {
+    "description": "The label for the settings button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "volume": "Volume",
+  "@volume": {
+    "description": "The label for the settings button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "min": "Min",
+  "@min": {
+    "description": "The short label for \"minimal\" label for the volume button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "max": "Max",
+  "@max": {
+    "description": "The shortened label for \"maximal\" label for the volume button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "music": "Music",
+  "@music": {
+    "description": "The label used for the music button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "back": "Back",
+  "@back": {
+    "description": "The label for the \"Back\" button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "pause": "Pause",
+  "@pause": {
+    "description": "The label for the \"Pause\" button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "skip": "Skip",
+  "@skip": {
+    "description": "The label for the \"Skip\" button",
+    "type": "text",
+    "placeholders": {}
+  },
+  "topProcesses": "Top Processes",
+  "@topProcesses": {
+    "description": "The label for the \"Top processes\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "shutdown": "Shutdown",
+  "@shutdown": {
+    "description": "The label for the \"System shutdown\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "memory": "Memory",
+  "@memory": {
+    "description": "The label for the \"memory\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "cpu": "CPU",
+  "@cpu": {
+    "description": "The short name for the \"Central Processing Unit\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "mem": "MEM",
+  "@mem": {
+    "description": "The short name for the \"Memory\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "ide": "IDE",
+  "@ide": {
+    "description": "The short name for the \"Integrated Development Environment\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "chrome": "Chrome",
+  "@chrome": {
+    "description": "The short name for the \"Google Chrome\" browser",
+    "type": "text",
+    "placeholders": {}
+  },
+  "pid": "PID",
+  "@pid": {
+    "description": "The short name for the \"Process identifier\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "tasks": "TASKS",
+  "@tasks": {
+    "description": "The short name for the \"Tasks\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "weather": "Weather",
+  "@weather": {
+    "description": "The short name for the \"Weather\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "date": "Date",
+  "@date": {
+    "description": "The short name for the \"date\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "network": "Network",
+  "@network": {
+    "description": "The short name for the \"network\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "fps": "FPS",
+  "@fps": {
+    "description": "The short name for the \"Frames per Second\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "batt": "Batt",
+  "@batt": {
+    "description": "The short name for the \"Battery level\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "battery": "Battery",
+  "@battery": {
+    "description": "The long name for the \"Battery level\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "wireless": "Wireless",
+  "@wireless": {
+    "description": "The short name for the \"Wireless network\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "signalStrong": "Strong Signal",
+  "@signalStrong": {
+    "description": "The short name for the \"Strong signal\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "sunny": "Sunny",
+  "@sunny": {
+    "description": "The short name for the \"Weather is sunny\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "runningTasks": "{numTasks,plural, =0{{numTasks} RUNNING}=1{{numTasks} RUNNING}other{{numTasks} RUNNING}}",
+  "@runningTasks": {
+    "description": "How many tasks are currently running",
+    "type": "text",
+    "placeholders": {
+      "numTasks": {
+        "example": 42
+      }
+    }
+  },
+  "totalTasks": "{numTasks,plural, =0{{numTasks}}=1{{numTasks}}other{{numTasks}}}",
+  "@totalTasks": {
+    "description": "How many tasks are currently running",
+    "type": "text",
+    "placeholders": {
+      "numTasks": {
+        "example": 42
+      }
+    }
+  },
+  "numThreads": "{numThreads,plural, =0{{numThreads} THR}=1{{numThreads} THR}other{{numThreads} THR}}",
+  "@numThreads": {
+    "description": "How many threads are currently there, short label",
+    "type": "text",
+    "placeholders": {
+      "numThreads": {
+        "example": 42
+      }
+    }
+  },
+  "name": "Name",
+  "@name": {
+    "description": "A generic label for a name, sentence case.",
+    "type": "text",
+    "placeholders": {}
+  },
+  "ask": "ASK",
+  "@ask": {
+    "description": "A generic short label for \"query\", sentence case.",
+    "type": "text",
+    "placeholders": {}
+  },
+  "typeToAsk": "TYPE TO ASK",
+  "@typeToAsk": {
+    "description": "Shown in the ask text entry box in Ermine shell",
+    "type": "text",
+    "placeholders": {}
+  },
+  "overview": "Overview",
+  "@overview": {
+    "description": "Shown in top bar on the Ermine shell",
+    "type": "text",
+    "placeholders": {}
+  },
+  "recents": "Recents",
+  "@recents": {
+    "description": "A list of recent apps",
+    "type": "text",
+    "placeholders": {}
+  },
+  "browser": "Browser",
+  "@browser": {
+    "description": "A button to invoke a browser with",
+    "type": "text",
+    "placeholders": {}
+  },
+  "auto": "Auto",
+  "@auto": {
+    "description": "A shorthand for \"automatic\" setting.",
+    "type": "text",
+    "placeholders": {}
+  },
+  "mockWirelessNetwork": "Wireless_Network",
+  "@mockWirelessNetwork": {
+    "description": "A mock label for a wireless network name that the device is connected to",
+    "type": "text",
+    "placeholders": {}
+  },
+  "nameThisStory": "Name this story",
+  "@nameThisStory": {
+    "description": "A hint appearing in a window for naming a story. This text should not include padding, but now it does.",
+    "type": "text",
+    "placeholders": {}
+  }
+}
\ No newline at end of file
diff --git a/session_shells/ermine/internationalization/resources/intl_nl.arb b/session_shells/ermine/internationalization/resources/intl_nl.arb
new file mode 100644
index 0000000..bb63915
--- /dev/null
+++ b/session_shells/ermine/internationalization/resources/intl_nl.arb
@@ -0,0 +1,218 @@
+{
+  "brightness": "HELDERHEID",
+  "@brightness": {
+    "description": "The label for the screen brightness settings widget (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "cancel": "Annuleren",
+  "@cancel": {
+    "description": "The label for canceling an offered command",
+    "type": "text",
+    "placeholders": {}
+  },
+  "done": "Klaar",
+  "@done": {
+    "description": "The label for saying we are done with a command",
+    "type": "text",
+    "placeholders": {}
+  },
+  "sleep": "SLAAP",
+  "@sleep": {
+    "description": "The label for the sleep settings widget (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "restart": "HERSTARTEN",
+  "@restart": {
+    "description": "The label for the restart button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "powerOff": "UITSCHAKELEN",
+  "@powerOff": {
+    "description": "The label for the restart button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "settings": "INSTELLINGEN",
+  "@settings": {
+    "description": "The label for the settings button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "volume": "GELUID",
+  "@volume": {
+    "description": "The label for the settings button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "min": "MIN",
+  "@min": {
+    "description": "The short label for \"minimal\" label for the volume button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "max": "MAX",
+  "@max": {
+    "description": "The shortened label for \"maximal\" label for the volume button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "music": "MUZIEK",
+  "@music": {
+    "description": "The label used for the music button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "back": "TERUG",
+  "@back": {
+    "description": "The label for the \"Back\" button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "pause": "PAUZE",
+  "@pause": {
+    "description": "The label for the \"Pause\" button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "skip": "OVERSLAAN",
+  "@skip": {
+    "description": "The label for the \"Skip\" button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "topProcesses": "TOP PROCESSEN",
+  "@topProcesses": {
+    "description": "The label for the \"Top processes\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "memory": "GEHEUGEN",
+  "@memory": {
+    "description": "The label for the \"memory\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "cpu": "CPU",
+  "@cpu": {
+    "description": "The short name for the \"Central Processing Unit\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "tasks": "TAKEN",
+  "@tasks": {
+    "description": "The short name for the \"Tasks\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "weather": "WEER",
+  "@weather": {
+    "description": "The short name for the \"Weather\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "date": "DATUM",
+  "@date": {
+    "description": "The short name for the \"date\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "network": "NETWERK",
+  "@network": {
+    "description": "The short name for the \"network\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "fps": "FPS",
+  "@fps": {
+    "description": "The short name for the \"Frames per Second\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "batt": "BATT",
+  "@batt": {
+    "description": "The short name for the \"Battery level\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "battery": "Batterij",
+  "@battery": {
+    "description": "The long name for the \"Battery level\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "wireless": "DRAADLOZE",
+  "@wireless": {
+    "description": "The short name for the \"Wireless network\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "signalStrong": "STERK SIGNAAL",
+  "@signalStrong": {
+    "description": "The short name for the \"Strong signal\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "sunny": "ZONNIG",
+  "@sunny": {
+    "description": "The short name for the \"Weather is sunny\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "runningTasks": "{numTasks, plural, =0 {{numTasks} LOPENDE} =1 {{numTasks} LOPEND} other {{numTasks} LOPENDE}}",
+  "@runningTasks": {
+    "description": "How many tasks are currently running",
+    "type": "text",
+    "placeholders": {
+      "numTasks": {
+        "example": "42"
+      }
+    }
+  },
+  "totalTasks": "{numTasks, plural, =0 {{numTasks}} =1 {{numTasks}} other {{numTasks}}}",
+  "@totalTasks": {
+    "description": "How many tasks are currently running",
+    "type": "text",
+    "placeholders": {
+      "numTasks": {
+        "example": "42"
+      }
+    }
+  },
+  "numThreads": "{numThreads, plural, =0 {{numThreads} DRAA} =1 {{numThreads} DRAA} other {{numThreads} DRAA}}",
+  "@numThreads": {
+    "description": "How many threads are currently there, short label",
+    "type": "text",
+    "placeholders": {
+      "numThreads": {
+        "example": "42"
+      }
+    }
+  },
+  "name": "Naam",
+  "@name": {
+    "description": "A generic label for a name, sentence case.",
+    "type": "text",
+    "placeholders": {}
+  },
+  "ask": "ZOEKT",
+  "@ask": {
+    "description": "A generic short label for \"query\", sentence case.",
+    "type": "text",
+    "placeholders": {}
+  },
+  "typeToAsk": "ZOEKT EN GIJ ZAL SPINAZIE ETEN",
+  "@typeToAsk": {
+    "description": "Shown in the ask text entry box in Ermine shell",
+    "type": "text",
+    "placeholders": {}
+  },
+  "nameThisStory": "Noem dit verhaal",
+  "@nameThisStory": {
+    "description": "A hint appearing in a window for naming a story. This text should not include padding, but now it does.",
+    "type": "text",
+    "placeholders": {}
+  }
+}
diff --git a/session_shells/ermine/internationalization/resources/intl_sr.arb b/session_shells/ermine/internationalization/resources/intl_sr.arb
new file mode 100644
index 0000000..88feb87
--- /dev/null
+++ b/session_shells/ermine/internationalization/resources/intl_sr.arb
@@ -0,0 +1,218 @@
+{
+  "brightness": "ОСВЕТЉАЈ",
+  "@brightness": {
+    "description": "The label for the screen brightness settings widget (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "cancel": "Откажи",
+  "@cancel": {
+    "description": "The label for canceling an offered command",
+    "type": "text",
+    "placeholders": {}
+  },
+  "done": "Готово",
+  "@done": {
+    "description": "The label for saying we are done with a command",
+    "type": "text",
+    "placeholders": {}
+  },
+  "sleep": "СПАВАЊЕ",
+  "@sleep": {
+    "description": "The label for the sleep settings widget (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "restart": "РЕСТАРТ",
+  "@restart": {
+    "description": "The label for the restart button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "powerOff": "ИСКЉУЧИ",
+  "@powerOff": {
+    "description": "The label for the restart button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "settings": "ПОДЕШАВАЊА",
+  "@settings": {
+    "description": "The label for the settings button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "volume": "ЈАЧИНА",
+  "@volume": {
+    "description": "The label for the settings button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "min": "МИН",
+  "@min": {
+    "description": "The short label for \"minimal\" label for the volume button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "max": "МАКС",
+  "@max": {
+    "description": "The shortened label for \"maximal\" label for the volume button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "music": "МУЗИКА",
+  "@music": {
+    "description": "The label used for the music button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "back": "НАЗАД",
+  "@back": {
+    "description": "The label for the \"Back\" button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "pause": "ПАУЗА",
+  "@pause": {
+    "description": "The label for the \"Pause\" button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "skip": "ПРЕСКОЧИ",
+  "@skip": {
+    "description": "The label for the \"Skip\" button (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "topProcesses": "ВРШНИ ПРОЦЕСИ",
+  "@topProcesses": {
+    "description": "The label for the \"Top processes\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "memory": "МЕМОРИЈА",
+  "@memory": {
+    "description": "The label for the \"memory\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "cpu": "ПРОЦ",
+  "@cpu": {
+    "description": "The short name for the \"Central Processing Unit\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "tasks": "ЗАДАЦИ",
+  "@tasks": {
+    "description": "The short name for the \"Tasks\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "weather": "ВРЕМЕ",
+  "@weather": {
+    "description": "The short name for the \"Weather\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "date": "ДАТУМ",
+  "@date": {
+    "description": "The short name for the \"date\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "network": "МРЕЖА",
+  "@network": {
+    "description": "The short name for the \"network\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "fps": "СЛ/СЕК",
+  "@fps": {
+    "description": "The short name for the \"Frames per Second\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "batt": "БАТ",
+  "@batt": {
+    "description": "The short name for the \"Battery level\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "battery": "Батерија",
+  "@battery": {
+    "description": "The long name for the \"Battery level\" label",
+    "type": "text",
+    "placeholders": {}
+  },
+  "wireless": "БЕЖИЧНА",
+  "@wireless": {
+    "description": "The short name for the \"Wireless network\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "signalStrong": "ЈАК СИГНАЛ",
+  "@signalStrong": {
+    "description": "The short name for the \"Strong signal\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "sunny": "СУНЧАНО",
+  "@sunny": {
+    "description": "The short name for the \"Weather is sunny\" label (ALL CAPS)",
+    "type": "text",
+    "placeholders": {}
+  },
+  "runningTasks": "{numTasks, plural, =0 {{numTasks} ЗАДАТАКА} =1 {{numTasks} ЗАДАТАК} other {{numTasks} ЗАДАТАКА}}",
+  "@runningTasks": {
+    "description": "How many tasks are currently running",
+    "type": "text",
+    "placeholders": {
+      "numTasks": {
+        "example": "42"
+      }
+    }
+  },
+  "totalTasks": "{numTasks, plural, =0 {{numTasks}} =1 {{numTasks}} other {{numTasks}}}",
+  "@totalTasks": {
+    "description": "How many tasks are currently running",
+    "type": "text",
+    "placeholders": {
+      "numTasks": {
+        "example": "42"
+      }
+    }
+  },
+  "numThreads": "{numThreads, plural, =0 {{numThreads} НИТИ} =1 {{numThreads} НИТ} other {{numThreads} НИТИ}}",
+  "@numThreads": {
+    "description": "How many threads are currently there, short label",
+    "type": "text",
+    "placeholders": {
+      "numThreads": {
+        "example": "42"
+      }
+    }
+  },
+  "name": "ИМЕ",
+  "@name": {
+    "description": "A generic label for a name, sentence case.",
+    "type": "text",
+    "placeholders": {}
+  },
+  "ask": "УПИТ",
+  "@ask": {
+    "description": "A generic short label for \"query\", sentence case.",
+    "type": "text",
+    "placeholders": {}
+  },
+  "typeToAsk": "УНЕСИТЕ УПИТ",
+  "@typeToAsk": {
+    "description": "Shown in the ask text entry box in Ermine shell",
+    "type": "text",
+    "placeholders": {}
+  },
+  "nameThisStory": "Именуј ову причу",
+  "@nameThisStory": {
+    "description": "A hint appearing in a window for naming a story. This text should not include padding, but now it does.",
+    "type": "text",
+    "placeholders": {}
+  }
+}
diff --git a/session_shells/ermine/internationalization/scripts/run_extract_to_arb.sh b/session_shells/ermine/internationalization/scripts/run_extract_to_arb.sh
new file mode 100755
index 0000000..d847f84
--- /dev/null
+++ b/session_shells/ermine/internationalization/scripts/run_extract_to_arb.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# 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.
+
+set -x
+
+$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
+  packages pub get intl_translation:extract_to_arb
+
+$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
+  packages pub run intl_translation:extract_to_arb \
+  --output-dir=resources lib/*strings.dart
+
+rm .packages pubspec.lock
+rm -fr .dart_tool
diff --git a/session_shells/ermine/internationalization/scripts/run_generate_from_arb.sh b/session_shells/ermine/internationalization/scripts/run_generate_from_arb.sh
new file mode 100755
index 0000000..93f7b81
--- /dev/null
+++ b/session_shells/ermine/internationalization/scripts/run_generate_from_arb.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+# 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.
+set -x
+
+$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
+  packages pub get intl_translation:generate_from_arb
+
+$FUCHSIA_DIR/third_party/dart-pkg/git/flutter/bin/flutter \
+  packages pub run intl_translation:generate_from_arb \
+  --output-dir=lib/localization \
+  lib/*strings.dart \
+  resources/*.arb
+
+rm .packages pubspec.lock
+rm -fr .dart_tool
diff --git a/session_shells/ermine/internationalization/test/current_locale_test.dart b/session_shells/ermine/internationalization/test/current_locale_test.dart
new file mode 100644
index 0000000..69c96b7
--- /dev/null
+++ b/session_shells/ermine/internationalization/test/current_locale_test.dart
@@ -0,0 +1,34 @@
+// 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/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+// ignore_for_file: implementation_imports
+import 'package:internationalization/current_locale.dart';
+
+void main() {
+  testWidgets('Check en_US from subtags', (tester) async {
+    final CurrentLocale current = CurrentLocale(
+        Locale.fromSubtags(languageCode: 'en', countryCode: 'US'));
+    expect(current.unicode(), 'en_US');
+    expect(current.value.toString(), 'en_US');
+  });
+  testWidgets('Check sr_RS from subtags', (tester) async {
+    final CurrentLocale current = CurrentLocale(
+        Locale.fromSubtags(languageCode: 'sr', countryCode: 'RS'));
+    expect(current.unicode(), 'sr_RS');
+    expect(current.value.toString(), 'sr_RS');
+  });
+  testWidgets('Check sr from constructor', (tester) async {
+    final CurrentLocale current = CurrentLocale(Locale('sr'));
+    expect(current.unicode(), 'sr');
+    expect(current.value.toString(), 'sr');
+  });
+  testWidgets('Check sr_RS from constructor', (tester) async {
+    final CurrentLocale current = CurrentLocale(Locale('sr_RS'));
+    expect(current.unicode(), 'sr_RS');
+    expect(current.value.toString(), 'sr_RS');
+  });
+}
diff --git a/session_shells/ermine/settings/BUILD.gn b/session_shells/ermine/settings/BUILD.gn
index b2e3462..1d0b709 100644
--- a/session_shells/ermine/settings/BUILD.gn
+++ b/session_shells/ermine/settings/BUILD.gn
@@ -20,6 +20,7 @@
     "//sdk/fidl/fuchsia.power",
     "//sdk/fidl/fuchsia.ui.brightness",
     "//src/experiences/lib/quickui",
+    "//src/experiences/session_shells/ermine/internationalization",
     "//third_party/dart-pkg/git/flutter/packages/flutter",
     "//topaz/public/dart/fuchsia_services",
   ]
diff --git a/session_shells/ermine/settings/lib/src/battery.dart b/session_shells/ermine/settings/lib/src/battery.dart
index 2c81f49..2b9acf9 100644
--- a/session_shells/ermine/settings/lib/src/battery.dart
+++ b/session_shells/ermine/settings/lib/src/battery.dart
@@ -9,11 +9,14 @@
 import 'package:fidl_fuchsia_power/fidl_async.dart';
 import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
 import 'package:fuchsia_services/services.dart' show StartupContext;
+import 'package:internationalization/strings.dart' as strings;
 import 'package:quickui/quickui.dart';
 
 /// Defines a [UiSpec] for visualizing battery.
 class Battery extends UiSpec {
-  static const _title = 'Battery';
+  // Localized strings.
+  static String get _title => strings.battery();
+
   static const _checkBatteryDuration = Duration(seconds: 1);
 
   BatteryModel model;
diff --git a/session_shells/ermine/settings/lib/src/brightness.dart b/session_shells/ermine/settings/lib/src/brightness.dart
index cbc99b2..c086b5a 100644
--- a/session_shells/ermine/settings/lib/src/brightness.dart
+++ b/session_shells/ermine/settings/lib/src/brightness.dart
@@ -9,6 +9,7 @@
 import 'package:fidl_fuchsia_ui_brightness/fidl_async.dart';
 import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
 import 'package:fuchsia_services/services.dart' show StartupContext;
+import 'package:internationalization/strings.dart' as strings;
 import 'package:quickui/quickui.dart';
 
 /// Defines a [UiSpec] for controlling screen brightness.
@@ -25,11 +26,13 @@
 ///         ----------------------
 /// * {A} - Auto icon, {b} Brightness min icon, {B} Brightness max icon.
 class Brightness extends UiSpec {
-  static const _title = 'Brightness';
-  static const _buttonLabel = 'Auto';
   static const progressAction = 1;
   static const autoAction = 2;
 
+  // Localized strings.
+  static String get _title => strings.brightness();
+  static String get _auto => strings.auto();
+
   _BrightnessModel _model;
 
   Brightness(ControlProxy control) {
@@ -69,7 +72,7 @@
       Group(title: _title, values: [
         Value.withIcon(IconValue(codePoint: Icons.brightness_auto.codePoint)),
         Value.withProgress(ProgressValue(value: value, action: progressAction)),
-        Value.withText(TextValue(text: _buttonLabel)),
+        Value.withText(TextValue(text: _auto)),
       ]),
     ]);
   }
@@ -80,7 +83,7 @@
         Value.withIcon(IconValue(codePoint: Icons.brightness_low.codePoint)),
         Value.withProgress(ProgressValue(value: value, action: progressAction)),
         Value.withIcon(IconValue(codePoint: Icons.brightness_high.codePoint)),
-        Value.withButton(ButtonValue(label: _buttonLabel, action: autoAction)),
+        Value.withButton(ButtonValue(label: _auto, action: autoAction)),
       ]),
     ]);
   }
diff --git a/session_shells/ermine/settings/lib/src/memory.dart b/session_shells/ermine/settings/lib/src/memory.dart
index 58d904d..7292b73 100644
--- a/session_shells/ermine/settings/lib/src/memory.dart
+++ b/session_shells/ermine/settings/lib/src/memory.dart
@@ -10,11 +10,14 @@
 import 'package:fidl_fuchsia_memory/fidl_async.dart';
 import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
 import 'package:fuchsia_services/services.dart' show StartupContext;
+import 'package:internationalization/strings.dart' as strings;
 import 'package:quickui/quickui.dart';
 
 /// Defines a [UiSpec] for visualizing memory.
 class Memory extends UiSpec {
-  static const _title = 'Memory';
+
+  // Localized strings.
+  static String get _memory => strings.memory();
 
   MemoryModel model;
 
@@ -48,7 +51,7 @@
     String usedString = (used).toStringAsPrecision(3);
     String totalString = (total).toStringAsPrecision(3);
     return Spec(groups: [
-      Group(title: _title, values: [
+      Group(title: _memory, values: [
         Value.withProgress(ProgressValue(value: value)),
         Value.withText(TextValue(text: '${usedString}GB / ${totalString}GB')),
       ]),
diff --git a/session_shells/ermine/shell/BUILD.gn b/session_shells/ermine/shell/BUILD.gn
index 693d0b2..e9211ef 100644
--- a/session_shells/ermine/shell/BUILD.gn
+++ b/session_shells/ermine/shell/BUILD.gn
@@ -109,9 +109,12 @@
     "//sdk/fidl/fuchsia.ui.views",
     "//src/experiences/lib/quickui",
     "//src/experiences/session_shells/ermine/settings",
+    "//src/experiences/session_shells/ermine/internationalization",
     "//third_party/dart-pkg/git/flutter/packages/flutter",
+    "//third_party/dart-pkg/git/flutter/packages/flutter_localizations",
     "//third_party/dart-pkg/pub/flutter_svg",
     "//third_party/dart-pkg/pub/uuid",
+    "//third_party/dart/third_party/pkg/intl",
     "//topaz/lib/tiler",
     "//topaz/public/dart/fuchsia_inspect",
     "//topaz/public/dart/fuchsia_logger",
diff --git a/session_shells/ermine/shell/lib/main.dart b/session_shells/ermine/shell/lib/main.dart
index 44c61d1..88e7df6 100644
--- a/session_shells/ermine/shell/lib/main.dart
+++ b/session_shells/ermine/shell/lib/main.dart
@@ -3,19 +3,60 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:lib.widgets/model.dart';
 
 import 'package:fuchsia_logger/logger.dart';
+import 'package:internationalization/localizations_delegate.dart'
+    as localizations;
+import 'package:internationalization/current_locale.dart';
+import 'package:internationalization/supported_locales.dart'
+    as supported_locales;
+import 'package:intl/intl.dart';
 
 import 'src/models/app_model.dart' show AppModel;
 import 'src/widgets/app.dart' show App;
 
 Future<void> main() async {
+  // TODO(fmil): Add a dynamically changing value of the locale, based on system
+  // preferences.
+  final providers = Providers()
+    ..provideValue(CurrentLocale(Locale.fromSubtags(languageCode: 'en')));
+
   setupLogger(name: 'ermine');
 
   final model = AppModel();
-  final app = App(model: model);
+  final app = _Localized(model);
 
-  runApp(app);
+  runApp(ProviderNode(providers: providers, child: app));
 
   await model.onStarted();
 }
+
+/// This is a localized version of the Ermine shell localized app.  It is the
+/// same as the original App, but it also has the current locale injected.
+class _Localized extends StatelessWidget {
+  final AppModel _model;
+  const _Localized(this._model);
+
+  @override
+  Widget build(BuildContext context) {
+    return Provide<CurrentLocale>(
+        builder: (BuildContext context, Widget child, currentLocale) {
+      // Changing the default locale here ensures that any non-Flutter
+      // code sees the locale change as well.
+      Intl.defaultLocale = currentLocale.unicode();
+      return MaterialApp(
+        home: App(model: _model),
+        debugShowCheckedModeBanner: false,
+        locale: currentLocale.value,
+        localizationsDelegates: [
+          localizations.delegate(),
+          GlobalMaterialLocalizations.delegate,
+          GlobalWidgetsLocalizations.delegate,
+        ],
+        supportedLocales: supported_locales.locales,
+      );
+    });
+  }
+}
diff --git a/session_shells/ermine/shell/lib/src/widgets/ask/ask_text_field.dart b/session_shells/ermine/shell/lib/src/widgets/ask/ask_text_field.dart
index 6541e4c..ffef0dd 100644
--- a/session_shells/ermine/shell/lib/src/widgets/ask/ask_text_field.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/ask/ask_text_field.dart
@@ -3,6 +3,9 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
+// Includes the strings used for the button labels.  Please only use the labels
+// defined there, and add new ones if you need them.
+import 'package:internationalization/strings.dart' as strings;
 
 import '../../models/ask_model.dart';
 import '../../utils/styles.dart';
@@ -59,7 +62,7 @@
             child: Padding(
               padding: EdgeInsets.only(right: 20),
               child: Text(
-                'TYPE TO ASK',
+                strings.typeToAsk().toUpperCase(),
                 style: Theme.of(context).textTheme.body1.copyWith(
                       color: Colors.white,
                       backgroundColor: Colors.black,
diff --git a/session_shells/ermine/shell/lib/src/widgets/status/status.dart b/session_shells/ermine/shell/lib/src/widgets/status/status.dart
index 95306d9..53696e8 100644
--- a/session_shells/ermine/shell/lib/src/widgets/status/status.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/status/status.dart
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flutter/material.dart';
 import 'package:fidl_fuchsia_ui_remotewidgets/fidl_async.dart';
+import 'package:flutter/material.dart';
+import 'package:internationalization/strings.dart' as strings;
 import 'package:quickui/uistream.dart';
 
 import '../../models/status_model.dart';
@@ -218,11 +219,11 @@
       height: kRowHeight,
       child: Row(
         children: <Widget>[
-          _buildButton('Restart', null),
+          _buildButton(strings.restart(), null),
           Padding(padding: EdgeInsets.only(right: kPadding)),
-          _buildButton('Shutdown', null),
+          _buildButton(strings.shutdown(), null),
           Spacer(),
-          _buildButton('Settings', model.launchSettings),
+          _buildButton(strings.settings(), model.launchSettings),
         ],
       ),
     );
diff --git a/session_shells/ermine/shell/lib/src/widgets/story/tile_chrome.dart b/session_shells/ermine/shell/lib/src/widgets/story/tile_chrome.dart
index 82801e8..6be7856 100644
--- a/session_shells/ermine/shell/lib/src/widgets/story/tile_chrome.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/story/tile_chrome.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'package:flutter/material.dart';
+import 'package:internationalization/strings.dart' as strings;
 
 import '../../utils/styles.dart';
 
@@ -116,7 +117,7 @@
 
           // Cancel edit button.
           if (editing)
-            _buildTitleBarTextButton(context, 'Cancel', onTapCancel),
+            _buildTitleBarTextButton(context, strings.cancel(), onTapCancel),
 
           // Story name.
           Expanded(
@@ -131,7 +132,8 @@
                     textAlign: TextAlign.center,
                     decoration: InputDecoration(
                       contentPadding: EdgeInsets.zero,
-                      hintText: '              NAME THIS STORY',
+                      // TODO(fmil): I18N There should be no hard-coded paddings in the text.
+                      hintText: '              ${strings.nameThisStory().toUpperCase()}',
                       border: InputBorder.none,
                       isDense: true,
                     ),
@@ -148,7 +150,7 @@
 
           // Done edit button.
           if (editing)
-            _buildTitleBarTextButton(context, 'Done', onTapDone),
+            _buildTitleBarTextButton(context, strings.done(), onTapDone),
 
           if (editing)
             Padding(
diff --git a/session_shells/ermine/shell/lib/src/widgets/topbar/topbar.dart b/session_shells/ermine/shell/lib/src/widgets/topbar/topbar.dart
index e4c3d3c..db0a5eb 100644
--- a/session_shells/ermine/shell/lib/src/widgets/topbar/topbar.dart
+++ b/session_shells/ermine/shell/lib/src/widgets/topbar/topbar.dart
@@ -2,8 +2,9 @@
 // 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';
 import 'package:flutter/material.dart';
+import 'package:internationalization/strings.dart' as strings;
+import 'package:intl/intl.dart';
 
 import '../../models/topbar_model.dart';
 import '../../utils/styles.dart';
@@ -36,7 +37,7 @@
               children: <Widget>[
                 // Overview.
                 Button(
-                  child: Text('OVERVIEW'),
+                  child: Text(strings.overview().toUpperCase()),
                   decoration: BoxDecoration(
                     border: Border(
                       right: BorderSide(
@@ -49,7 +50,7 @@
                 ),
                 // Recents.
                 Button(
-                  child: Text('RECENTS'),
+                  child: Text(strings.recents().toUpperCase()),
                   decoration: BoxDecoration(
                     border: Border(
                       right: BorderSide(
@@ -87,7 +88,7 @@
                 // Ask.
                 Button(
                   key: model.askButtonKey,
-                  child: Text('ASK'),
+                  child: Text(strings.ask().toUpperCase()),
                   decoration: BoxDecoration(
                     border: Border(
                       left: BorderSide(
@@ -120,7 +121,7 @@
             ),
             // Story title.
             // Center(
-            //   child: Text('BROWSER', textAlign: TextAlign.center),
+            //   child: Text(strings.browser().toUpperCase(), textAlign: TextAlign.center),
             // ),
           ],
         ),