Flutter doctor error message lookup (#23889)

diff --git a/packages/flutter_tools/lib/src/android/android_studio_validator.dart b/packages/flutter_tools/lib/src/android/android_studio_validator.dart
index 268926a..3fc1b79 100644
--- a/packages/flutter_tools/lib/src/android/android_studio_validator.dart
+++ b/packages/flutter_tools/lib/src/android/android_studio_validator.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import '../base/user_messages.dart';
 import '../base/version.dart';
 import '../doctor.dart';
 import '../globals.dart';
@@ -34,9 +35,9 @@
 
     final String studioVersionText = _studio.version == Version.unknown
         ? null
-        : 'version ${_studio.version}';
+        : userMessages.androidStudioVersion(_studio.version.toString());
     messages
-        .add(ValidationMessage('Android Studio at ${_studio.directory}'));
+        .add(ValidationMessage(userMessages.androidStudioLocation(_studio.directory)));
 
     final IntelliJPlugins plugins = IntelliJPlugins(_studio.pluginsPath);
     plugins.validatePackage(messages, <String>['flutter-intellij', 'flutter-intellij.jar'],
@@ -51,11 +52,9 @@
       type = ValidationType.partial;
       messages.addAll(_studio.validationMessages
           .map<ValidationMessage>((String m) => ValidationMessage.error(m)));
-      messages.add(ValidationMessage(
-          'Try updating or re-installing Android Studio.'));
+      messages.add(ValidationMessage(userMessages.androidStudioNeedsUpdate));
       if (_studio.configured != null) {
-        messages.add(ValidationMessage(
-            'Consider removing your android-studio-dir setting by running:\nflutter config --android-studio-dir='));
+        messages.add(ValidationMessage(userMessages.androidStudioResetDir));
       }
     }
 
@@ -76,13 +75,9 @@
 
     final String cfgAndroidStudio = config.getValue('android-studio-dir');
     if (cfgAndroidStudio != null) {
-      messages.add(
-          ValidationMessage.error('android-studio-dir = $cfgAndroidStudio\n'
-              'but Android Studio not found at this location.'));
+      messages.add(ValidationMessage.error(userMessages.androidStudioMissing(cfgAndroidStudio)));
     }
-    messages.add(ValidationMessage(
-        'Android Studio not found; download from https://developer.android.com/studio/index.html\n'
-        '(or visit https://flutter.io/setup/#android-setup for detailed instructions).'));
+    messages.add(ValidationMessage(userMessages.androidStudioInstallation));
 
     return ValidationResult(ValidationType.notAvailable, messages,
         statusInfo: 'not installed');
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index f2e7725..6fccde7 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -11,6 +11,7 @@
 import '../base/platform.dart';
 import '../base/process.dart';
 import '../base/process_manager.dart';
+import '../base/user_messages.dart';
 import '../base/utils.dart';
 import '../base/version.dart';
 import '../doctor.dart';
@@ -49,13 +50,11 @@
 class AndroidValidator extends DoctorValidator {
   AndroidValidator(): super('Android toolchain - develop for Android devices',);
 
-  static const String _jdkDownload = 'https://www.oracle.com/technetwork/java/javase/downloads/';
-
   /// Returns false if we cannot determine the Java version or if the version
   /// is not compatible.
   Future<bool> _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) async {
     if (!processManager.canRun(javaBinary)) {
-      messages.add(ValidationMessage.error('Cannot execute $javaBinary to determine the version'));
+      messages.add(ValidationMessage.error(userMessages.androidCantRunJavaBinary(javaBinary)));
       return false;
     }
     String javaVersion;
@@ -71,10 +70,10 @@
     }
     if (javaVersion == null) {
       // Could not determine the java version.
-      messages.add(ValidationMessage.error('Could not determine java version'));
+      messages.add(ValidationMessage.error(userMessages.androidUnknownJavaVersion));
       return false;
     }
-    messages.add(ValidationMessage('Java version $javaVersion'));
+    messages.add(ValidationMessage(userMessages.androidJavaVersion(javaVersion)));
     // TODO(johnmccutchan): Validate version.
     return true;
   }
@@ -87,38 +86,26 @@
       // No Android SDK found.
       if (platform.environment.containsKey(kAndroidHome)) {
         final String androidHomeDir = platform.environment[kAndroidHome];
-        messages.add(ValidationMessage.error(
-          '$kAndroidHome = $androidHomeDir\n'
-          'but Android SDK not found at this location.'
-        ));
+        messages.add(ValidationMessage.error(userMessages.androidBadSdkDir(kAndroidHome, androidHomeDir)));
       } else {
-        messages.add(ValidationMessage.error(
-          'Unable to locate Android SDK.\n'
-          'Install Android Studio from: https://developer.android.com/studio/index.html\n'
-          'On first launch it will assist you in installing the Android SDK components.\n'
-          '(or visit https://flutter.io/setup/#android-setup for detailed instructions).\n'
-          'If Android SDK has been installed to a custom location, set \$$kAndroidHome to that location.\n'
-          'You may also want to add it to your PATH environment variable.\n'
-        ));
+        messages.add(ValidationMessage.error(userMessages.androidMissingSdkInstructions(kAndroidHome)));
       }
-
       return ValidationResult(ValidationType.missing, messages);
     }
 
-    messages.add(ValidationMessage('Android SDK at ${androidSdk.directory}'));
+    messages.add(ValidationMessage(userMessages.androidSdkLocation(androidSdk.directory)));
 
     messages.add(ValidationMessage(androidSdk.ndk == null
-          ? 'Android NDK location not configured (optional; useful for native profiling support)'
-          : 'Android NDK at ${androidSdk.ndk.directory}'));
+          ? userMessages.androidMissingNdk
+          : userMessages.androidNdkLocation(androidSdk.ndk.directory)));
 
     String sdkVersionText;
     if (androidSdk.latestVersion != null) {
-      sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
+      sdkVersionText = userMessages.androidStatusInfo(androidSdk.latestVersion.buildToolsVersionName);
 
-      messages.add(ValidationMessage(
-        'Platform ${androidSdk.latestVersion.platformName}, '
-        'build-tools ${androidSdk.latestVersion.buildToolsVersionName}'
-      ));
+      messages.add(ValidationMessage(userMessages.androidSdkPlatformToolsVersion(
+        androidSdk.latestVersion.platformName,
+        androidSdk.latestVersion.buildToolsVersionName)));
     }
 
     if (platform.environment.containsKey(kAndroidHome)) {
@@ -137,22 +124,17 @@
       messages.addAll(validationResult.map<ValidationMessage>((String message) {
         return ValidationMessage.error(message);
       }));
-      messages.add(ValidationMessage(
-          'Try re-installing or updating your Android SDK,\n'
-          'visit https://flutter.io/setup/#android-setup for detailed instructions.'));
+      messages.add(ValidationMessage(userMessages.androidSdkInstallHelp));
       return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
     }
 
     // Now check for the JDK.
     final String javaBinary = AndroidSdk.findJavaBinary();
     if (javaBinary == null) {
-      messages.add(ValidationMessage.error(
-          'No Java Development Kit (JDK) found; You must have the environment '
-          'variable JAVA_HOME set and the java binary in your PATH. '
-          'You can download the JDK from $_jdkDownload.'));
+      messages.add(ValidationMessage.error(userMessages.androidMissingJdk));
       return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
     }
-    messages.add(ValidationMessage('Java binary at: $javaBinary'));
+    messages.add(ValidationMessage(userMessages.androidJdkLocation(javaBinary)));
 
     // Check JDK version.
     if (! await _checkJavaVersion(javaBinary, messages)) {
@@ -178,21 +160,21 @@
       return ValidationResult(ValidationType.missing, messages);
     }
 
-    final String sdkVersionText = 'Android SDK ${androidSdk.latestVersion.buildToolsVersionName}';
+    final String sdkVersionText = userMessages.androidStatusInfo(androidSdk.latestVersion.buildToolsVersionName);
 
     // Check for licenses.
     switch (await licensesAccepted) {
       case LicensesAccepted.all:
-        messages.add(ValidationMessage('All Android licenses accepted.'));
+        messages.add(ValidationMessage(userMessages.androidLicensesAll));
         break;
       case LicensesAccepted.some:
-        messages.add(ValidationMessage.hint('Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses'));
+        messages.add(ValidationMessage.hint(userMessages.androidLicensesSome));
         return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
       case LicensesAccepted.none:
-        messages.add(ValidationMessage.error('Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses'));
+        messages.add(ValidationMessage.error(userMessages.androidLicensesNone));
         return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
       case LicensesAccepted.unknown:
-        messages.add(ValidationMessage.error('Android license status unknown.'));
+        messages.add(ValidationMessage.error(userMessages.androidLicensesUnknown));
         return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText);
     }
     return ValidationResult(ValidationType.installed, messages, statusInfo: sdkVersionText);
@@ -263,7 +245,7 @@
     try {
       await Future.wait<void>(<Future<void>>[output, errors]).timeout(const Duration(seconds: 30));
     } catch (TimeoutException) {
-      printTrace('Intentionally killing ${androidSdk.sdkManagerPath}');
+      printTrace(userMessages.androidLicensesTimeout(androidSdk.sdkManagerPath));
       processManager.killPid(process.pid);
     }
     return status ?? LicensesAccepted.unknown;
@@ -272,7 +254,7 @@
   /// Run the Android SDK manager tool in order to accept SDK licenses.
   static Future<bool> runLicenseManager() async {
     if (androidSdk == null) {
-      printStatus('Unable to locate Android SDK.');
+      printStatus(userMessages.androidSdkShort);
       return false;
     }
 
@@ -281,10 +263,7 @@
     final Version sdkManagerVersion = Version.parse(androidSdk.sdkManagerVersion);
     if (sdkManagerVersion == null || sdkManagerVersion.major < 26)
       // SDK manager is found, but needs to be updated.
-      throwToolExit(
-        'A newer version of the Android SDK is required. To update, run:\n'
-        '${androidSdk.sdkManagerPath} --update\n'
-      );
+      throwToolExit(userMessages.androidSdkOutdated(androidSdk.sdkManagerPath));
 
     final Process process = await runCommand(
       <String>[androidSdk.sdkManagerPath, '--licenses'],
@@ -309,10 +288,6 @@
     assert(androidSdk != null);
     final String sdkManagerPath = androidSdk.sdkManagerPath;
     if (!processManager.canRun(sdkManagerPath))
-      throwToolExit(
-        'Android sdkmanager tool not found ($sdkManagerPath).\n'
-        'Try re-installing or updating your Android SDK,\n'
-        'visit https://flutter.io/setup/#android-setup for detailed instructions.'
-      );
+      throwToolExit(userMessages.androidMissingSdkManager(sdkManagerPath));
   }
 }
diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart
new file mode 100644
index 0000000..4e0e4a8
--- /dev/null
+++ b/packages/flutter_tools/lib/src/base/user_messages.dart
@@ -0,0 +1,183 @@
+import 'context.dart';
+
+UserMessages get userMessages => context[UserMessages];
+
+/// Class containing message strings that can be produced by Flutter tools.
+class UserMessages {
+  // Messages used in FlutterValidator
+  String flutterStatusInfo(String channel, String version, String os, String locale) =>
+      'Channel $channel, v$version, on $os, locale $locale';
+  String flutterVersion(String version, String flutterRoot) =>
+      'Flutter version $version at $flutterRoot';
+  String flutterRevision(String revision, String age, String date) =>
+      'Framework revision $revision ($age), $date';
+  String engineRevision(String revision) => 'Engine revision $revision';
+  String dartRevision(String revision) => 'Dart version $revision';
+  String get flutterBinariesDoNotRun =>
+      'Downloaded executables cannot execute on host.\n'
+      'See https://github.com/flutter/flutter/issues/6207 for more information';
+  String get flutterBinariesLinuxRepairCommands =>
+      'On Debian/Ubuntu/Mint: sudo apt-get install lib32stdc++6\n'
+      'On Fedora: dnf install libstdc++.i686\n'
+      'On Arch: pacman -S lib32-libstdc++5';
+
+  // Messages used in NoIdeValidator
+  String get noIdeStatusInfo => 'No supported IDEs installed';
+  String get noIdeInstallationInfo => 'IntelliJ - https://www.jetbrains.com/idea/';
+
+  // Messages used in IntellijValidator
+  String intellijStatusInfo(String version) => 'version $version';
+  String get intellijPluginInfo =>
+      'For information about installing plugins, see\n'
+      'https://flutter.io/intellij-setup/#installing-the-plugins';
+  String intellijMinimumVersion(String minVersion) =>
+      'This install is older than the minimum recommended version of $minVersion.';
+  String intellijLocation(String installPath) => 'IntelliJ at $installPath';
+
+  // Message used in IntellijValidatorOnMac
+  String get intellijMacUnknownResult => 'Cannot determine if IntelliJ is installed';
+
+  // Messages used in DeviceValidator
+  String get devicesMissing => 'No devices available';
+  String devicesAvailable(int devices) => '$devices available';
+
+  // Messages used in AndroidValidator
+  String androidCantRunJavaBinary(String javaBinary) => 'Cannot execute $javaBinary to determine the version';
+  String get androidUnknownJavaVersion => 'Could not determine java version';
+  String androidJavaVersion(String javaVersion) => 'Java version $javaVersion';
+  String androidBadSdkDir(String envKey, String homeDir) =>
+      '$envKey = $homeDir\n'
+      'but Android SDK not found at this location.';
+  String androidMissingSdkInstructions(String envKey) =>
+      'Unable to locate Android SDK.\n'
+      'Install Android Studio from: https://developer.android.com/studio/index.html\n'
+      'On first launch it will assist you in installing the Android SDK components.\n'
+      '(or visit https://flutter.io/setup/#android-setup for detailed instructions).\n'
+      'If Android SDK has been installed to a custom location, set $envKey to that location.\n'
+      'You may also want to add it to your PATH environment variable.\n';
+  String androidSdkLocation(String directory) => 'Android SDK at $directory';
+  String androidSdkPlatformToolsVersion(String platform, String tools) =>
+      'Platform $platform, build-tools $tools';
+  String get androidSdkInstallHelp =>
+      'Try re-installing or updating your Android SDK,\n'
+      'visit https://flutter.io/setup/#android-setup for detailed instructions.';
+  String get androidMissingNdk => 'Android NDK location not configured (optional; useful for native profiling support)';
+  String androidNdkLocation(String directory) => 'Android NDK at $directory';
+  // Also occurs in AndroidLicenseValidator
+  String androidStatusInfo(String version) => 'Android SDK version $version';
+
+  // Messages used in AndroidLicenseValidator
+  String get androidMissingJdk =>
+      'No Java Development Kit (JDK) found; You must have the environment '
+      'variable JAVA_HOME set and the java binary in your PATH. '
+      'You can download the JDK from https://www.oracle.com/technetwork/java/javase/downloads/.';
+  String androidJdkLocation(String location) => 'Java binary at: $location';
+  String get androidLicensesAll => 'All Android licenses accepted.';
+  String get androidLicensesSome => 'Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses';
+  String get androidLicensesNone => 'Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses';
+  String get androidLicensesUnknown => 'Android license status unknown.';
+  String androidSdkOutdated(String managerPath) =>
+      'A newer version of the Android SDK is required. To update, run:\n'
+      '$managerPath --update\n';
+  String androidLicensesTimeout(String managerPath) => 'Intentionally killing $managerPath';
+  String get androidSdkShort => 'Unable to locate Android SDK.';
+  String androidMissingSdkManager(String sdkManagerPath) =>
+      'Android sdkmanager tool not found ($sdkManagerPath).\n'
+      'Try re-installing or updating your Android SDK,\n'
+      'visit https://flutter.io/setup/#android-setup for detailed instructions.';
+
+  // Messages used in AndroidStudioValidator
+  String androidStudioVersion(String version) => 'version $version';
+  String androidStudioLocation(String location) => 'Android Studio at $location';
+  String get androidStudioNeedsUpdate => 'Try updating or re-installing Android Studio.';
+  String get androidStudioResetDir =>
+      'Consider removing your android-studio-dir setting by running:\n'
+      'flutter config --android-studio-dir=';
+
+  // Messages used in NoAndroidStudioValidator
+  String androidStudioMissing(String location) =>
+      'android-studio-dir = $location\n'
+      'but Android Studio not found at this location.';
+  String get androidStudioInstallation =>
+      'Android Studio not found; download from https://developer.android.com/studio/index.html\n'
+      '(or visit https://flutter.io/setup/#android-setup for detailed instructions).';
+
+  // Messages used in IOSValidator
+  String iOSXcodeLocation(String location) => 'Xcode at $location';
+  String iOSXcodeOutdated(int versionMajor, int versionMinor) =>
+      'Flutter requires a minimum Xcode version of $versionMajor.$versionMinor.0.\n'
+      'Download the latest version or update via the Mac App Store.';
+  String get iOSXcodeEula => 'Xcode end user license agreement not signed; open Xcode or run the command \'sudo xcodebuild -license\'.';
+  String get iOSXcodeMissingSimct =>
+      'Xcode requires additional components to be installed in order to run.\n'
+      'Launch Xcode and install additional required components when prompted.';
+  String get iOSXcodeMissing =>
+      'Xcode not installed; this is necessary for iOS development.\n'
+      'Download at https://developer.apple.com/xcode/download/.';
+  String get iOSXcodeIncomplete =>
+      'Xcode installation is incomplete; a full installation is necessary for iOS development.\n'
+      'Download at: https://developer.apple.com/xcode/download/\n'
+      'Or install Xcode via the App Store.\n'
+      'Once installed, run:\n'
+      '  sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer';
+  String get iOSIMobileDeviceMissing =>
+      'libimobiledevice and ideviceinstaller are not installed. To install with Brew, run:\n'
+      '  brew update\n'
+      '  brew install --HEAD usbmuxd\n'
+      '  brew link usbmuxd\n'
+      '  brew install --HEAD libimobiledevice\n'
+      '  brew install ideviceinstaller';
+  String get iOSIMobileDeviceBroken =>
+      'Verify that all connected devices have been paired with this computer in Xcode.\n'
+      'If all devices have been paired, libimobiledevice and ideviceinstaller may require updating.\n'
+      'To update with Brew, run:\n'
+      '  brew update\n'
+      '  brew uninstall --ignore-dependencies libimobiledevice\n'
+      '  brew uninstall --ignore-dependencies usbmuxd\n'
+      '  brew install --HEAD usbmuxd\n'
+      '  brew unlink usbmuxd\n'
+      '  brew link usbmuxd\n'
+      '  brew install --HEAD libimobiledevice\n'
+      '  brew install ideviceinstaller';
+  String get iOSDeviceInstallerMissing =>
+      'ideviceinstaller is not installed; this is used to discover connected iOS devices.\n'
+      'To install with Brew, run:\n'
+      '  brew install --HEAD usbmuxd\n'
+      '  brew link usbmuxd\n'
+      '  brew install --HEAD libimobiledevice\n'
+      '  brew install ideviceinstaller';
+  String iOSDeployVersion(String version) => 'ios-deploy $version';
+  String iOSDeployOutdated(String minVersion) =>
+      'ios-deploy out of date ($minVersion is required). To upgrade with Brew:\n'
+      '  brew upgrade ios-deploy';
+  String get iOSDeployMissing =>
+      'ios-deploy not installed. To install:\n'
+      '  brew install ios-deploy';
+  String get iOSBrewMissing =>
+      'Brew can be used to install tools for iOS device development.\n'
+      'Download brew at https://brew.sh/.';
+
+  // Messages used in CocoaPodsValidator
+  String cocoaPodsVersion(String version) => 'CocoaPods version $version';
+  String cocoaPodsUninitialized(String consequence) =>
+      'CocoaPods installed but not initialized.\n'
+      '$consequence\n'
+      'To initialize CocoaPods, run:\n'
+      '  pod setup\n'
+      'once to finalize CocoaPods\' installation.';
+  String cocoaPodsMissing(String consequence, String installInstructions) =>
+      'CocoaPods not installed.\n'
+      '$consequence\n'
+      'To install:\n'
+      '$installInstructions';
+  String cocoaPodsOutdated(String recVersion, String consequence, String upgradeInstructions) =>
+      'CocoaPods out of date ($recVersion is recommended).\n'
+      '$consequence\n'
+      'To upgrade:\n'
+      '$upgradeInstructions';
+
+  // Messages used in VsCodeValidator
+  String vsCodeVersion(String version) => 'version $version';
+  String vsCodeLocation(String location) => 'VS Code at $location';
+  String vsCodeFlutterExtensionMissing(String url) => 'Flutter extension not installed; install from\n$url';
+}
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart
index d9dff8a..31a88b4 100644
--- a/packages/flutter_tools/lib/src/context_runner.dart
+++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -18,6 +18,7 @@
 import 'base/os.dart';
 import 'base/platform.dart';
 import 'base/time.dart';
+import 'base/user_messages.dart';
 import 'base/utils.dart';
 import 'cache.dart';
 import 'compile.dart';
@@ -81,6 +82,7 @@
       SystemClock: () => const SystemClock(),
       Stdio: () => const Stdio(),
       Usage: () => Usage(),
+      UserMessages: () => UserMessages(),
       Xcode: () => Xcode(),
       XcodeProjectInterpreter: () => XcodeProjectInterpreter(),
     },
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index 52b540e..e9a34a0 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -15,6 +15,7 @@
 import 'base/platform.dart';
 import 'base/process_manager.dart';
 import 'base/terminal.dart';
+import 'base/user_messages.dart';
 import 'base/utils.dart';
 import 'base/version.dart';
 import 'cache.dart';
@@ -432,32 +433,26 @@
 
     final FlutterVersion version = FlutterVersion.instance;
 
-    messages.add(ValidationMessage('Flutter version ${version.frameworkVersion} at ${Cache.flutterRoot}'));
-    messages.add(ValidationMessage(
-      'Framework revision ${version.frameworkRevisionShort} '
-      '(${version.frameworkAge}), ${version.frameworkDate}'
-    ));
-    messages.add(ValidationMessage('Engine revision ${version.engineRevisionShort}'));
-    messages.add(ValidationMessage('Dart version ${version.dartSdkVersion}'));
+    messages.add(ValidationMessage(userMessages.flutterVersion(version.frameworkVersion, Cache.flutterRoot)));
+    messages.add(ValidationMessage(userMessages.flutterRevision(version.frameworkRevisionShort, version.frameworkAge, version.frameworkDate)));
+    messages.add(ValidationMessage(userMessages.engineRevision(version.engineRevisionShort)));
+    messages.add(ValidationMessage(userMessages.dartRevision(version.dartSdkVersion)));
     final String genSnapshotPath =
       artifacts.getArtifactPath(Artifact.genSnapshot);
 
     // Check that the binaries we downloaded for this platform actually run on it.
     if (!_genSnapshotRuns(genSnapshotPath)) {
       final StringBuffer buf = StringBuffer();
-      buf.writeln('Downloaded executables cannot execute on host.');
-      buf.writeln('See https://github.com/flutter/flutter/issues/6207 for more information');
+      buf.writeln(userMessages.flutterBinariesDoNotRun);
       if (platform.isLinux) {
-        buf.writeln('On Debian/Ubuntu/Mint: sudo apt-get install lib32stdc++6');
-        buf.writeln('On Fedora: dnf install libstdc++.i686');
-        buf.writeln('On Arch: pacman -S lib32-libstdc++5');
+        buf.writeln(userMessages.flutterBinariesLinuxRepairCommands);
       }
       messages.add(ValidationMessage.error(buf.toString()));
       valid = ValidationType.partial;
     }
 
     return ValidationResult(valid, messages,
-      statusInfo: 'Channel ${version.channel}, v${version.frameworkVersion}, on ${os.name}, locale ${platform.localeName}'
+      statusInfo: userMessages.flutterStatusInfo(version.channel, version.frameworkVersion, os.name, platform.localeName)
     );
   }
 }
@@ -477,8 +472,8 @@
   @override
   Future<ValidationResult> validate() async {
     return ValidationResult(ValidationType.missing, <ValidationMessage>[
-      ValidationMessage('IntelliJ - https://www.jetbrains.com/idea/'),
-    ], statusInfo: 'No supported IDEs installed');
+      ValidationMessage(userMessages.noIdeInstallationInfo),
+    ], statusInfo: userMessages.noIdeStatusInfo);
   }
 }
 
@@ -509,7 +504,7 @@
   Future<ValidationResult> validate() async {
     final List<ValidationMessage> messages = <ValidationMessage>[];
 
-    messages.add(ValidationMessage('IntelliJ at $installPath'));
+    messages.add(ValidationMessage(userMessages.intellijLocation(installPath)));
 
     final IntelliJPlugins plugins = IntelliJPlugins(pluginsPath);
     plugins.validatePackage(messages, <String>['flutter-intellij', 'flutter-intellij.jar'],
@@ -517,10 +512,7 @@
     plugins.validatePackage(messages, <String>['Dart'], 'Dart');
 
     if (_hasIssues(messages)) {
-      messages.add(ValidationMessage(
-        'For information about installing plugins, see\n'
-        'https://flutter.io/intellij-setup/#installing-the-plugins'
-      ));
+      messages.add(ValidationMessage(userMessages.intellijPluginInfo));
     }
 
     _validateIntelliJVersion(messages, kMinIdeaVersion);
@@ -528,8 +520,7 @@
     return ValidationResult(
       _hasIssues(messages) ? ValidationType.partial : ValidationType.installed,
       messages,
-      statusInfo: 'version $version'
-    );
+      statusInfo: userMessages.intellijStatusInfo(version));
   }
 
   bool _hasIssues(List<ValidationMessage> messages) {
@@ -546,9 +537,7 @@
       return;
 
     if (installedVersion < minVersion) {
-      messages.add(ValidationMessage.error(
-        'This install is older than the minimum recommended version of $minVersion.'
-      ));
+      messages.add(ValidationMessage.error(userMessages.intellijMinimumVersion(minVersion.toString())));
     }
   }
 }
@@ -648,7 +637,7 @@
       }
     } on FileSystemException catch (e) {
       validators.add(ValidatorWithResult(
-          'Cannot determine if IntelliJ is installed',
+          userMessages.intellijMacUnknownResult,
           ValidationResult(ValidationType.missing, <ValidationMessage>[
               ValidationMessage.error(e.message),
           ]),
@@ -691,7 +680,7 @@
       if (diagnostics.isNotEmpty) {
         messages = diagnostics.map<ValidationMessage>((String message) => ValidationMessage(message)).toList();
       } else {
-        messages = <ValidationMessage>[ValidationMessage.hint('No devices available')];
+        messages = <ValidationMessage>[ValidationMessage.hint(userMessages.devicesMissing)];
       }
     } else {
       messages = await Device.descriptions(devices)
@@ -701,7 +690,7 @@
     if (devices.isEmpty) {
       return ValidationResult(ValidationType.notAvailable, messages);
     } else {
-      return ValidationResult(ValidationType.installed, messages, statusInfo: '${devices.length} available');
+      return ValidationResult(ValidationType.installed, messages, statusInfo: userMessages.devicesAvailable(devices.length));
     }
   }
 }
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index c8bc17f..f7cdc35 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -8,6 +8,7 @@
 import '../base/os.dart';
 import '../base/platform.dart';
 import '../base/process.dart';
+import '../base/user_messages.dart';
 import '../base/version.dart';
 import '../doctor.dart';
 import 'cocoapods.dart';
@@ -81,7 +82,7 @@
     if (xcode.isInstalled) {
       xcodeStatus = ValidationType.installed;
 
-      messages.add(ValidationMessage('Xcode at ${xcode.xcodeSelectPath}'));
+      messages.add(ValidationMessage(userMessages.iOSXcodeLocation(xcode.xcodeSelectPath)));
 
       xcodeVersionInfo = xcode.versionText;
       if (xcodeVersionInfo.contains(','))
@@ -91,40 +92,25 @@
       if (!xcode.isInstalledAndMeetsVersionCheck) {
         xcodeStatus = ValidationType.partial;
         messages.add(ValidationMessage.error(
-          'Flutter requires a minimum Xcode version of $kXcodeRequiredVersionMajor.$kXcodeRequiredVersionMinor.0.\n'
-          'Download the latest version or update via the Mac App Store.'
+            userMessages.iOSXcodeOutdated(kXcodeRequiredVersionMajor, kXcodeRequiredVersionMinor)
         ));
       }
 
       if (!xcode.eulaSigned) {
         xcodeStatus = ValidationType.partial;
-        messages.add(ValidationMessage.error(
-          'Xcode end user license agreement not signed; open Xcode or run the command \'sudo xcodebuild -license\'.'
-        ));
+        messages.add(ValidationMessage.error(userMessages.iOSXcodeEula));
       }
       if (!xcode.isSimctlInstalled) {
         xcodeStatus = ValidationType.partial;
-        messages.add(ValidationMessage.error(
-          'Xcode requires additional components to be installed in order to run.\n'
-          'Launch Xcode and install additional required components when prompted.'
-        ));
+        messages.add(ValidationMessage.error(userMessages.iOSXcodeMissingSimct));
       }
 
     } else {
       xcodeStatus = ValidationType.missing;
       if (xcode.xcodeSelectPath == null || xcode.xcodeSelectPath.isEmpty) {
-        messages.add(ValidationMessage.error(
-            'Xcode not installed; this is necessary for iOS development.\n'
-            'Download at https://developer.apple.com/xcode/download/.'
-        ));
+        messages.add(ValidationMessage.error(userMessages.iOSXcodeMissing));
       } else {
-        messages.add(ValidationMessage.error(
-            'Xcode installation is incomplete; a full installation is necessary for iOS development.\n'
-            'Download at: https://developer.apple.com/xcode/download/\n'
-            'Or install Xcode via the App Store.\n'
-            'Once installed, run:\n'
-            '  sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer'
-        ));
+        messages.add(ValidationMessage.error(userMessages.iOSXcodeIncomplete));
       }
     }
 
@@ -133,63 +119,30 @@
     if (!iMobileDevice.isInstalled) {
       checksFailed += 3;
       packageManagerStatus = ValidationType.partial;
-      messages.add(ValidationMessage.error(
-        'libimobiledevice and ideviceinstaller are not installed. To install with Brew, run:\n'
-        '  brew update\n'
-        '  brew install --HEAD usbmuxd\n'
-        '  brew link usbmuxd\n'
-        '  brew install --HEAD libimobiledevice\n'
-        '  brew install ideviceinstaller'
-      ));
+      messages.add(ValidationMessage.error(userMessages.iOSIMobileDeviceMissing));
     } else if (!await iMobileDevice.isWorking) {
       checksFailed += 2;
       packageManagerStatus = ValidationType.partial;
-      messages.add(ValidationMessage.error(
-        'Verify that all connected devices have been paired with this computer in Xcode.\n'
-        'If all devices have been paired, libimobiledevice and ideviceinstaller may require updating.\n'
-        'To update with Brew, run:\n'
-        '  brew update\n'
-        '  brew uninstall --ignore-dependencies libimobiledevice\n'
-        '  brew uninstall --ignore-dependencies usbmuxd\n'
-        '  brew install --HEAD usbmuxd\n'
-        '  brew unlink usbmuxd\n'
-        '  brew link usbmuxd\n'
-        '  brew install --HEAD libimobiledevice\n'
-        '  brew install ideviceinstaller'
-      ));
+      messages.add(ValidationMessage.error(userMessages.iOSIMobileDeviceBroken));
     } else if (!await hasIDeviceInstaller) {
       checksFailed += 1;
       packageManagerStatus = ValidationType.partial;
-      messages.add(ValidationMessage.error(
-        'ideviceinstaller is not installed; this is used to discover connected iOS devices.\n'
-        'To install with Brew, run:\n'
-        '  brew install --HEAD usbmuxd\n'
-        '  brew link usbmuxd\n'
-        '  brew install --HEAD libimobiledevice\n'
-        '  brew install ideviceinstaller'
-      ));
+      messages.add(ValidationMessage.error(userMessages.iOSDeviceInstallerMissing));
     }
 
     final bool iHasIosDeploy = await hasIosDeploy;
 
     // Check ios-deploy is installed at meets version requirements.
     if (iHasIosDeploy) {
-      messages.add(
-        ValidationMessage('ios-deploy ${await iosDeployVersionText}'));
+      messages.add(ValidationMessage(userMessages.iOSDeployVersion(await iosDeployVersionText)));
     }
     if (!await _iosDeployIsInstalledAndMeetsVersionCheck) {
       packageManagerStatus = ValidationType.partial;
       if (iHasIosDeploy) {
-        messages.add(ValidationMessage.error(
-          'ios-deploy out of date ($iosDeployMinimumVersion is required). To upgrade with Brew:\n'
-          '  brew upgrade ios-deploy'
-        ));
+        messages.add(ValidationMessage.error(userMessages.iOSDeployOutdated(iosDeployMinimumVersion)));
       } else {
         checksFailed += 1;
-        messages.add(ValidationMessage.error(
-          'ios-deploy not installed. To install with Brew:\n'
-          '  brew install ios-deploy'
-        ));
+        messages.add(ValidationMessage.error(userMessages.iOSDeployMissing));
       }
     }
 
@@ -198,10 +151,7 @@
     if (checksFailed == totalChecks)
       packageManagerStatus = ValidationType.missing;
     if (checksFailed > 0 && !hasHomebrew) {
-      messages.add(ValidationMessage.error(
-        'Brew can be used to install tools for iOS device development.\n'
-        'Download brew at https://brew.sh/.'
-      ));
+      messages.add(ValidationMessage.error(userMessages.iOSBrewMissing));
     }
 
     return ValidationResult(
@@ -232,34 +182,20 @@
 
       if (cocoaPodsStatus == CocoaPodsStatus.recommended) {
         if (await cocoaPods.isCocoaPodsInitialized) {
-          messages.add(ValidationMessage('CocoaPods version ${await cocoaPods.cocoaPodsVersionText}'));
+          messages.add(ValidationMessage(userMessages.cocoaPodsVersion(await cocoaPods.cocoaPodsVersionText)));
         } else {
           status = ValidationType.partial;
-          messages.add(ValidationMessage.error(
-            'CocoaPods installed but not initialized.\n'
-            '$noCocoaPodsConsequence\n'
-            'To initialize CocoaPods, run:\n'
-            '  pod setup\n'
-            'once to finalize CocoaPods\' installation.'
-          ));
+          messages.add(ValidationMessage.error(userMessages.cocoaPodsUninitialized(noCocoaPodsConsequence)));
         }
       } else {
         if (cocoaPodsStatus == CocoaPodsStatus.notInstalled) {
           status = ValidationType.missing;
           messages.add(ValidationMessage.error(
-            'CocoaPods not installed.\n'
-            '$noCocoaPodsConsequence\n'
-            'To install:\n'
-            '$cocoaPodsInstallInstructions'
-          ));
+              userMessages.cocoaPodsMissing(noCocoaPodsConsequence, cocoaPodsInstallInstructions)));
         } else {
           status = ValidationType.partial;
           messages.add(ValidationMessage.hint(
-            'CocoaPods out of date (${cocoaPods.cocoaPodsRecommendedVersion} is recommended).\n'
-            '$noCocoaPodsConsequence\n'
-            'To upgrade:\n'
-            '$cocoaPodsUpgradeInstructions'
-          ));
+              userMessages.cocoaPodsOutdated(cocoaPods.cocoaPodsRecommendedVersion, noCocoaPodsConsequence, cocoaPodsUpgradeInstructions)));
         }
       }
     } else {
diff --git a/packages/flutter_tools/lib/src/vscode/vscode_validator.dart b/packages/flutter_tools/lib/src/vscode/vscode_validator.dart
index 8746ae1..f955c5f 100644
--- a/packages/flutter_tools/lib/src/vscode/vscode_validator.dart
+++ b/packages/flutter_tools/lib/src/vscode/vscode_validator.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import '../base/user_messages.dart';
 import '../base/version.dart';
 import '../doctor.dart';
 import 'vscode.dart';
@@ -24,7 +25,7 @@
   Future<ValidationResult> validate() async {
     final String vsCodeVersionText = _vsCode.version == Version.unknown
         ? null
-        : 'version ${_vsCode.version}';
+        : userMessages.vsCodeVersion(_vsCode.version.toString());
 
     final ValidationType validationType = _vsCode.isValid
         ? ValidationType.installed