71 lines
3.2 KiB
Dart
71 lines
3.2 KiB
Dart
// 원격 배포 다국어 덮어쓰기 층 생성기 — 요구사항 FR-I18N-01 「언어 리소스를 원격 배포 방식으로 갱신」.
|
|
//
|
|
// `flutter gen-l10n` 이 만드는 AppLocalizations 는 문자열이 컴파일 상수라, 재배포 없이
|
|
// 문구를 고칠 길이 없다. 여기서 그 클래스를 감싸는 [RemoteAppLocalizations] 를 만든다.
|
|
// 게터마다 Firestore `i18n/{locale}` 문서의 같은 키를 먼저 보고, 없으면 번들 문자열을 쓴다.
|
|
//
|
|
// 실행: `dart run tool/gen_l10n_remote.dart` (app/ 에서). arb 키를 더하거나 바꾸면 다시 돌린다.
|
|
// 생성물이 arb 와 어긋나면 test/i18n_test.dart 가 실패한다.
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
const templatePath = 'lib/l10n/app_ko.arb';
|
|
const outputPath = 'lib/l10n/remote_app_localizations.dart';
|
|
|
|
void main() {
|
|
final arb =
|
|
jsonDecode(File(templatePath).readAsStringSync()) as Map<String, dynamic>;
|
|
File(outputPath).writeAsStringSync(generateRemoteL10n(arb));
|
|
stdout.writeln('wrote $outputPath');
|
|
}
|
|
|
|
String generateRemoteL10n(Map<String, dynamic> arb) {
|
|
final out = StringBuffer()
|
|
..writeln('// GENERATED by tool/gen_l10n_remote.dart — 직접 고치지 말 것.')
|
|
..writeln("import 'app_localizations.dart';")
|
|
..writeln()
|
|
..writeln('/// 번들 문자열 위에 원격 문자열(`i18n/{locale}` 문서)을 덮는다.')
|
|
..writeln('/// 원격에 없는 키는 번들 → 번들에도 없으면 ko(template) 순으로 폴백한다.')
|
|
..writeln('class RemoteAppLocalizations extends AppLocalizations {')
|
|
..writeln(
|
|
' RemoteAppLocalizations(this._base, this._remote) : super(_base.localeName);',
|
|
)
|
|
..writeln()
|
|
..writeln(' final AppLocalizations _base;')
|
|
..writeln(' final Map<String, String> _remote;')
|
|
..writeln()
|
|
..writeln(' /// 자리표시자를 한 번에 치환한다 — 인자 값 안의 `{name}` 은 다시 바꾸지 않는다.')
|
|
..writeln(' String? _fill(String key, Map<String, String> args) =>')
|
|
..writeln(' _remote[key]?.replaceAllMapped(')
|
|
..writeln(" RegExp(r'\\{(\\w+)\\}'),")
|
|
..writeln(' (m) => args[m[1]] ?? m[0]!,')
|
|
..writeln(' );');
|
|
for (final key
|
|
in arb.keys.where((k) => !k.startsWith('@')).toList()..sort()) {
|
|
final message = arb[key] as String;
|
|
final meta = arb['@$key'] as Map<String, dynamic>?;
|
|
final names = ((meta?['placeholders'] as Map<String, dynamic>?) ?? const {})
|
|
.keys
|
|
.toList();
|
|
out.writeln();
|
|
out.writeln(' @override');
|
|
if (names.isEmpty) {
|
|
out.writeln(" String get $key => _remote['$key'] ?? _base.$key;");
|
|
continue;
|
|
}
|
|
final params = names.map((n) => 'String $n').join(', ');
|
|
final args = names.join(', ');
|
|
// ICU select·plural 은 원격 문자열로 해석하지 않는다 — 번들만 쓴다.
|
|
if (message.contains(', select,') || message.contains(', plural,')) {
|
|
out.writeln(' String $key($params) => _base.$key($args);');
|
|
continue;
|
|
}
|
|
final map = names.map((n) => "'$n': $n").join(', ');
|
|
out.writeln(
|
|
" String $key($params) => _fill('$key', {$map}) ?? _base.$key($args);",
|
|
);
|
|
}
|
|
out.writeln('}');
|
|
return out.toString();
|
|
}
|