gen_date_localizations.dart 8.65 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/// This program extracts localized date symbols and patterns from the intl
/// package for the subset of locales supported by the flutter_localizations
/// package.
///
9
/// The extracted data is written into:
10
///   packages/flutter_localizations/lib/src/l10n/generated_date_localizations.dart
11
///
12 13 14
/// ## Usage
///
/// Run this program from the root of the git repository.
15 16 17 18
///
/// The following outputs the generated Dart code to the console as a dry run:
///
/// ```
19
/// dart dev/tools/localization/bin/gen_date_localizations.dart
20 21
/// ```
///
22
/// If the data looks good, use the `--overwrite` option to overwrite the
23 24 25
/// lib/src/l10n/date_localizations.dart file:
///
/// ```
26
/// dart dev/tools/localization/bin/gen_date_localizations.dart --overwrite
27 28 29 30 31 32 33 34
/// ```

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:path/path.dart' as path;

35
import '../localizations_utils.dart';
36

37 38
const String _kCommandName = 'gen_date_localizations.dart';

39 40 41 42
// Used to let _jsonToMap know what locale it's date symbols converting for.
// Date symbols for the Kannada locale ('kn') are handled specially because
// some of the strings contain characters that can crash Emacs on Linux.
// See packages/flutter_localizations/lib/src/l10n/README for more information.
43
String? currentLocale;
44

45
Future<void> main(List<String> rawArgs) async {
46
  checkCwdIsRepoRoot(_kCommandName);
47

48
  final bool writeToFile = parseArgs(rawArgs).writeToFile;
49

50
  final File dotPackagesFile = File(path.join('packages', 'flutter_localizations', '.packages'));
51 52 53
  final bool dotPackagesExists = dotPackagesFile.existsSync();

  if (!dotPackagesExists) {
54
    exitWithError(
55 56 57 58 59 60 61 62 63 64 65
      'File not found: ${dotPackagesFile.path}. $_kCommandName must be run '
      'after a successful "flutter update-packages".'
    );
  }

  final String pathToIntl = dotPackagesFile
    .readAsStringSync()
    .split('\n')
    .firstWhere(
      (String line) => line.startsWith('intl:'),
      orElse: () {
66
        exitWithError('intl dependency not found in ${dotPackagesFile.path}');
67
        return ''; // unreachable
68 69 70 71 72
      },
    )
    .split(':')
    .last;

73
  final Directory dateSymbolsDirectory = Directory(path.join(pathToIntl, 'src', 'data', 'dates', 'symbols'));
74
  final Map<String, File> symbolFiles = _listIntlData(dateSymbolsDirectory);
75
  final Directory datePatternsDirectory = Directory(path.join(pathToIntl, 'src', 'data', 'dates', 'patterns'));
76
  final Map<String, File> patternFiles = _listIntlData(datePatternsDirectory);
77
  final StringBuffer buffer = StringBuffer();
78
  final Set<String> supportedLocales = _supportedLocales();
79 80 81

  buffer.writeln(
'''
Ian Hickson's avatar
Ian Hickson committed
82
// Copyright 2014 The Flutter Authors. All rights reserved.
83 84 85
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

86
// This file has been automatically generated. Please do not edit it manually.
87
// To regenerate run (omit --overwrite to print to console instead of the file):
88
// dart --enable-asserts dev/tools/localization/bin/gen_date_localizations.dart --overwrite
89

90 91
import 'package:intl/date_symbols.dart' as intl;

92 93
'''
);
94 95 96
  buffer.writeln('''
/// The subset of date symbols supported by the intl package which are also
/// supported by flutter_localizations.''');
97
  buffer.writeln('final Map<String, intl.DateSymbols> dateSymbols = <String, intl.DateSymbols> {');
98
  symbolFiles.forEach((String locale, File data) {
99
    currentLocale = locale;
100 101 102 103
    if (supportedLocales.contains(locale)) {
      final Map<String, Object?> objData =  json.decode(data.readAsStringSync()) as Map<String, Object?>;
      buffer.writeln("'$locale': intl.DateSymbols(");
      objData.forEach((String key, Object? value) {
104
        if (value == null) {
105
          return;
106
        }
107 108 109 110
        buffer.writeln(_jsonToConstructorEntry(key, value));
      });
      buffer.writeln('),');
    }
111
  });
112
  currentLocale = null;
113 114
  buffer.writeln('};');

115
  // Code that uses datePatterns expects it to contain values of type
116
  // Map<String, String> not Map<String, dynamic>.
117 118 119
  buffer.writeln('''
/// The subset of date patterns supported by the intl package which are also
/// supported by flutter_localizations.''');
120
  buffer.writeln('const Map<String, Map<String, String>> datePatterns = <String, Map<String, String>> {');
121
  patternFiles.forEach((String locale, File data) {
122
    if (supportedLocales.contains(locale)) {
123
      final Map<String, dynamic> patterns = json.decode(data.readAsStringSync()) as Map<String, dynamic>;
124
      buffer.writeln("'$locale': <String, String>{");
125 126 127 128 129 130
      patterns.forEach((String key, dynamic value) {
        assert(value is String);
        buffer.writeln(_jsonToMapEntry(key, value));
      });
      buffer.writeln('},');
    }
131 132 133 134
  });
  buffer.writeln('};');

  if (writeToFile) {
135
    final File dateLocalizationsFile = File(path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n', 'generated_date_localizations.dart'));
136
    dateLocalizationsFile.writeAsStringSync(buffer.toString());
137 138 139
    final String extension = Platform.isWindows ? '.exe' : '';
    final ProcessResult result = Process.runSync(path.join('bin', 'cache', 'dart-sdk', 'bin', 'dart$extension'), <String>[
      'format',
140 141
      dateLocalizationsFile.path,
    ]);
142 143 144 145 146
    if (result.exitCode != 0) {
      print(result.exitCode);
      print(result.stdout);
      print(result.stderr);
    }
147 148 149 150 151
  } else {
    print(buffer);
  }
}

152 153 154 155
String _jsonToConstructorEntry(String key, dynamic value) {
  return '$key: ${_jsonToObject(value)},';
}

156 157 158 159
String _jsonToMapEntry(String key, dynamic value) {
  return "'$key': ${_jsonToMap(value)},";
}

160
String _jsonToObject(dynamic json) {
161
  if (json == null || json is num || json is bool) {
162
    return '$json';
163
  }
164

165
  if (json is String) {
166
    return generateEncodedString(currentLocale, json);
167
  }
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

  if (json is Iterable<Object?>) {
    final String type = json.first.runtimeType.toString();
    final StringBuffer buffer = StringBuffer('const <$type>[');
    for (final dynamic value in json) {
      buffer.writeln('${_jsonToMap(value)},');
    }
    buffer.write(']');
    return buffer.toString();
  }

  if (json is Map<String, dynamic>) {
    final StringBuffer buffer = StringBuffer('<String, Object>{');
    json.forEach((String key, dynamic value) {
      buffer.writeln(_jsonToMapEntry(key, value));
    });
    buffer.write('}');
    return buffer.toString();
  }

  throw 'Unsupported JSON type ${json.runtimeType} of value $json.';
}

191
String _jsonToMap(dynamic json) {
192
  if (json == null || json is num || json is bool) {
193
    return '$json';
194
  }
195

196
  if (json is String) {
197
    return generateEncodedString(currentLocale, json);
198
  }
199

200
  if (json is Iterable) {
201
    final StringBuffer buffer = StringBuffer('<String>[');
202 203 204 205 206 207
    for (final dynamic value in json) {
      buffer.writeln('${_jsonToMap(value)},');
    }
    buffer.write(']');
    return buffer.toString();
  }
208

209
  if (json is Map<String, dynamic>) {
210
    final StringBuffer buffer = StringBuffer('<String, Object>{');
211 212 213 214 215 216 217 218 219 220
    json.forEach((String key, dynamic value) {
      buffer.writeln(_jsonToMapEntry(key, value));
    });
    buffer.write('}');
    return buffer.toString();
  }

  throw 'Unsupported JSON type ${json.runtimeType} of value $json.';
}

221
Set<String> _supportedLocales() {
222 223 224 225 226 227 228 229
  // Assumes that en_US is a supported locale by default. Without this, usage
  // of the intl package APIs before Flutter populates its set of supported i18n
  // date patterns and symbols may cause problems.
  //
  // For more context, see https://github.com/flutter/flutter/issues/67644.
  final Set<String> supportedLocales = <String>{
    'en_US',
  };
230 231
  final RegExp filenameRE = RegExp(r'(?:material|cupertino)_(\w+)\.arb$');
  final Directory supportedLocalesDirectory = Directory(path.join('packages', 'flutter_localizations', 'lib', 'src', 'l10n'));
232
  for (final FileSystemEntity entity in supportedLocalesDirectory.listSync()) {
233
    final String filePath = entity.path;
234
    if (FileSystemEntity.isFileSync(filePath) && filenameRE.hasMatch(filePath)) {
235
      supportedLocales.add(filenameRE.firstMatch(filePath)![1]!);
236
    }
237
  }
238

239
  return supportedLocales;
240 241 242
}

Map<String, File> _listIntlData(Directory directory) {
243
  final Map<String, File> localeFiles = <String, File>{};
244 245 246 247
  final Iterable<File> files = directory
    .listSync()
    .whereType<File>()
    .where((File file) => file.path.endsWith('.json'));
248
  for (final File file in files) {
249 250
    final String locale = path.basenameWithoutExtension(file.path);
    localeFiles[locale] = file;
251
  }
252 253 254

  final List<String> locales = localeFiles.keys.toList(growable: false);
  locales.sort();
255
  return Map<String, File>.fromIterable(locales, value: (dynamic locale) => localeFiles[locale]!);
256
}