chitfund/luckychit/tool/sync_l10n.mjs

179 lines
5.2 KiB
JavaScript

#!/usr/bin/env node
/**
* Merges lib/l10n/app_{en,te}.arb with app_{en,te}_more.arb (in-memory) and
* writes lib/l10n/app_localizations.dart, app_localizations_en.dart, app_localizations_te.dart.
*
* Run from package root: node tool/sync_l10n.mjs
*/
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const root = path.join(__dirname, "..");
const l10nDir = path.join(root, "lib", "l10n");
function loadArb(name) {
const p = path.join(l10nDir, name);
return JSON.parse(fs.readFileSync(p, "utf8"));
}
function merge(base, extra) {
return { ...base, ...extra };
}
function messageKeys(arb) {
return Object.keys(arb).filter((k) => !k.startsWith("@") && k !== "@@locale");
}
function placeholdersFor(arb, key) {
const meta = arb[`@${key}`];
if (!meta || !meta.placeholders) return null;
return Object.keys(meta.placeholders);
}
function dartEscapeSingle(s) {
return s
.replace(/\\/g, "\\\\")
.replace(/\r\n/g, "\n")
.replace(/\n/g, "\\n")
.replace(/'/g, "\\'");
}
/** Build Dart string literal expr from ICU-ish template (e.g. "Hi, {name}!"). */
function dartReturnExpr(template, params) {
let d = template;
for (const p of params) {
d = d.split(`{${p}}`).join(`$${p}`);
}
return `'${dartEscapeSingle(d)}'`;
}
function writeLocalizations(en, te) {
const keys = [...new Set([...messageKeys(en), ...messageKeys(te)])].sort();
const abstractDecls = [];
const enImpl = [];
const teImpl = [];
for (const key of keys) {
const ph = placeholdersFor(en, key) ?? placeholdersFor(te, key);
if (ph && ph.length) {
const args = ph.map((p) => `String ${p}`).join(", ");
abstractDecls.push(` String ${key}(${args});`);
const tplEn = en[key];
const tplTe = te[key] ?? tplEn;
enImpl.push(` @override\n String ${key}(${args}) {\n return ${dartReturnExpr(tplEn, ph)};\n }`);
teImpl.push(` @override\n String ${key}(${args}) {\n return ${dartReturnExpr(tplTe, ph)};\n }`);
} else {
abstractDecls.push(` String get ${key};`);
const vEn = en[key] ?? "";
const vTe = te[key] ?? vEn;
enImpl.push(` @override\n String get ${key} => '${dartEscapeSingle(vEn)}';`);
teImpl.push(` @override\n String get ${key} => '${dartEscapeSingle(vTe)}';`);
}
}
const header = `// GENERATED FILE — do not edit by hand.
// Source: lib/l10n/app_en.arb + app_en_more.arb (merged). Regenerate: node tool/sync_l10n.mjs
`;
const main = `${header}import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'app_localizations_en.dart';
import 'app_localizations_te.dart';
abstract class AppLocalizations {
AppLocalizations(String locale) : localeName = locale;
final String localeName;
static AppLocalizations? of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
static const List<Locale> supportedLocales = <Locale>[
Locale('en'),
Locale('te'),
];
${abstractDecls.join("\n\n")}
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) =>
<String>['en', 'te'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
switch (locale.languageCode) {
case 'en':
return AppLocalizationsEn();
case 'te':
return AppLocalizationsTe();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "\$locale".',
);
}
`;
const enFile = `${header}import 'app_localizations.dart';
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
${enImpl.join("\n\n")}
}
`;
const teFile = `${header}import 'app_localizations.dart';
class AppLocalizationsTe extends AppLocalizations {
AppLocalizationsTe([String locale = 'te']) : super(locale);
${teImpl.join("\n\n")}
}
`;
fs.writeFileSync(path.join(l10nDir, "app_localizations.dart"), main);
fs.writeFileSync(path.join(l10nDir, "app_localizations_en.dart"), enFile);
fs.writeFileSync(path.join(l10nDir, "app_localizations_te.dart"), teFile);
}
const baseEn = loadArb("app_en.arb");
const baseTe = loadArb("app_te.arb");
const moreEn = loadArb("app_en_more.arb");
const moreTe = loadArb("app_te_more.arb");
const en = merge(baseEn, moreEn);
const te = merge(baseTe, moreTe);
writeLocalizations(en, te);
console.log("Wrote app_localizations*.dart from merged ARBs.");