analyzeTranslations
Compares translations you already hold in memory. It does no file system work at all, so it runs anywhere the language does: in a browser, in a Flutter web build, in a worker, in a sandbox with no disk. Reach for it when something else has already read the files.
Signature
function analyzeTranslations(input: Chki18nInput, options?: Chki18nOptions): Chki18nResult;Synchronous: there is nothing to await.
Chki18nResult analyzeTranslations(Chki18nInput input, {Chki18nOptions? options});Synchronous: there is nothing to await.
def analyze_translations(data: Input, options: Options | None = None) -> Result: ...Usage
Pass one set of locales:
import { analyzeTranslations } from 'chki18n';
const result = analyzeTranslations(
{
locales: {
en: { desc: { hello: 'Hello {name}', bye: 'Goodbye' } },
ko: { desc: { hello: '안녕하세요' } }
}
},
{ target: 'en' }
);
result.keyCount; // 2
result.issuesByCode.NO_KEY[0].key; // 'desc.bye'
result.issuesByCode.NO_INTERPOLATION_KEY[0].interpolation; // 'name'import 'package:chki18n/chki18n.dart';
final result = analyzeTranslations(
const Chki18nInput(
locales: {
'en': {
'desc': {'hello': 'Hello {name}', 'bye': 'Goodbye'},
},
'ko': {
'desc': {'hello': '안녕하세요'},
},
},
),
options: const Chki18nOptions(target: 'en'),
);
result.keyCount; // 2
result.of(Chki18nCheckCode.noKey).first.key; // 'desc.bye'
result.of(Chki18nCheckCode.noInterpolationKey).first.interpolation; // 'name'from chki18n import Input, Options, analyze_translations
result = analyze_translations(
Input(
locales={
"en": {"desc": {"hello": "Hello {name}", "bye": "Goodbye"}},
"ko": {"desc": {"hello": "안녕하세요"}},
}
),
Options(target="en"),
)
result.key_count # 2
result.of("NO_KEY")[0].key # 'desc.bye'
result.of("NO_INTERPOLATION_KEY")[0].interpolation # 'name'Nested objects are flattened before comparison, so desc.hello is what every issue reports.
Several translation files
Use groups when your project has more than one set of files, so each is compared on its own and a key missing from one is never reported against the other:
analyzeTranslations(
{
groups: {
'common.json': { en: { ok: 'OK' }, ko: { ok: '확인' } },
'errors.json': { en: { failed: 'Failed' }, ko: {} }
}
},
{ target: 'en' }
);
// the one NO_KEY issue carries group: 'errors.json'analyzeTranslations(
const Chki18nInput(
groups: {
'common.json': {
'en': {'ok': 'OK'},
'ko': {'ok': '확인'},
},
'errors.json': {
'en': {'failed': 'Failed'},
'ko': <String, Object?>{},
},
},
),
options: const Chki18nOptions(target: 'en'),
);
// the one NO_KEY issue carries group: 'errors.json'analyze_translations(
Input(
groups={
"common.json": {"en": {"ok": "OK"}, "ko": {"ok": "확인"}},
"errors.json": {"en": {"failed": "Failed"}, "ko": {}},
}
),
Options(target="en"),
)
# the one NO_KEY issue carries group="errors.json"locales is shorthand for a single group named ''. See File layouts for how a directory scan arrives at the same shape.
Skipping the flatten pass
If your keys are already flat, say so and the objects you pass are used exactly as they are, with nothing copied and nothing rebuilt:
const en = { 'desc.hello': 'Hello {name}' };
const ko = { 'desc.hello': '안녕하세요' };
analyzeTranslations({ locales: { en, ko } }, { target: 'en', flattened: true });This is the fast path. Comparing 5,000 keys across 5 locales takes about 17ms flattened, against about 22ms when the flatten pass runs.
const en = {'desc.hello': 'Hello {name}'};
const ko = {'desc.hello': '안녕하세요'};
analyzeTranslations(
const Chki18nInput(locales: {'en': en, 'ko': ko}),
options: const Chki18nOptions(target: 'en', flattened: true),
);This is the fast path: the maps you pass are the ones the comparison reads.
en = {"desc.hello": "Hello {name}"}
ko = {"desc.hello": "안녕하세요"}
analyze_translations(Input(locales={"en": en, "ko": ko}), Options(target="en", flattened=True))This is the fast path: the dictionaries you pass are the ones the comparison reads.
Input
interface Chki18nInput {
groups?: { [group: string]: { [locale: string]: TranslationMap } };
locales?: { [locale: string]: TranslationMap };
files?: Chki18nSourceFile[];
issues?: Chki18nIssue[];
fileFormat?: Chki18nFileFormat;
unusedKeys?: string[];
undefinedKeys?: Chki18nKeyUsage[];
}class Chki18nInput {
const Chki18nInput({
TranslationGroups? groups,
Map<String, TranslationMap>? locales,
List<Chki18nSourceFile>? files,
List<Chki18nIssue>? issues,
Chki18nFileFormat? fileFormat,
List<String>? unusedKeys,
List<Chki18nKeyUsage>? undefinedKeys,
});
}@dataclass(frozen=True, slots=True, kw_only=True)
class Input:
groups: TranslationGroups | None = None
locales: dict[str, TranslationMap] | None = None
files: list[SourceFile] = field(default_factory=list)
issues: list[Issue] = field(default_factory=list)
file_format: FileFormat | None = None
unused_keys: list[str] = field(default_factory=list)
undefined_keys: list[KeyUsage] = field(default_factory=list)files maps a group and locale onto the file it came from, so issues can carry a file path. issues lets whatever produced the input add its own problems to the same result, which is how a directory scan's unreadable-file errors end up in the same list as the comparison's findings. fileFormatfileFormatfile_format is carried through to the result untouched, and the last two answer UNUSED_KEY and UNDEFINED_KEY for an application that has already worked them out.
What the result says
result.files; // [] — nothing was read from disk
result.fileFormat; // null — unless you passed one in
result.locales; // every locale seen, across all groups
result.groups; // every group name, in input order
result.keyCount; // distinct keys comparedresult.files; // [] — nothing was read from disk
result.fileFormat; // null — unless you passed one in
result.locales; // every locale seen, across all groups
result.groups; // every group name, in input order
result.keyCount; // distinct keys comparedresult.files # [] — nothing was read from disk
result.file_format # None — unless you passed one in
result.locales # every locale seen, across all groups
result.groups # every group name, in input order
result.key_count # distinct keys comparedEverything else is the same shape checkTranslationFiles returns. See The result object.
A locale that is not an object
Reported as an issue rather than raised, like every other input problem:
analyzeTranslations({ locales: { en: { a: 'A' }, ko: null } }, { target: 'en' });
// issuesByCode.INVALID_FILE — "The translations of `ko` are not an object."Dart's type system settles this one before it runs. Chki18nInput.locales is a Map<String, TranslationMap>, so there is nothing to pass that is not a map of keys. A file on disk that holds something else is still reported as INVALID_FILE by the scanner.
analyze_translations(Input(locales={"en": {"a": "A"}, "ko": None}), Options(target="en"))
# result.of("INVALID_FILE") — "The translations of `ko` are not an object."See also
createAnalyzer— reuse one analyzer, and check a single key.- The core entry point — this function without the file system the scanner needs.