checkTranslationFiles
Reads a directory of translation files and compares every language against the target language, in one call. It does what the CLI does and returns the result as a value you can act on. Reach for it when a directory is checked once.
Signature
function checkTranslationFiles(path?: string, options?: Chki18nOptions): Promise<Chki18nResult>;Future<Chki18nResult> checkTranslationFiles({String? path, Chki18nOptions? options});def check_translation_files(
path: str | None = None,
options: Options | None = None,
) -> Result: ...Synchronous, unlike the JavaScript package. Python's file reads are synchronous, so an async signature would promise concurrency this package cannot deliver.
Usage
import { checkTranslationFiles } from 'chki18n';
const result = await checkTranslationFiles('./locales', { target: 'en' });
result.success; // false
result.summary.error; // 1
result.issues;
// [
// {
// code: 'NO_KEY',
// level: 'error',
// locale: 'ko',
// key: 'attr.folder',
// group: '',
// targetValue: 'Folder',
// file: '/project/locales/ko.json',
// message: 'The key exists in the target language but is missing here.'
// }
// ]import 'package:chki18n/chki18n.dart';
final result = await checkTranslationFiles(
path: './locales',
options: const Chki18nOptions(target: 'en'),
);
result.success; // false
result.summary.error; // 1
result.of(Chki18nCheckCode.noKey).first;
// Chki18nIssue(
// code: Chki18nCheckCode.noKey,
// level: Chki18nLevel.error,
// locale: 'ko',
// key: 'attr.folder',
// group: '',
// targetValue: 'Folder',
// file: '/project/locales/ko.json',
// message: 'The key exists in the target language but is missing here.',
// )from chki18n import Options, check_translation_files
result = check_translation_files("./locales", Options(target="en"))
result.success # False
result.summary.error # 1
result.of("NO_KEY")[0]
# Issue(
# code="NO_KEY",
# level="error",
# locale="ko",
# key="attr.folder",
# group="",
# target_value="Folder",
# file="/project/locales/ko.json",
# message="The key exists in the target language but is missing here.",
# )The path can also be given as an option, which is how the CLI passes its positional argument:
await checkTranslationFiles(undefined, { path: './locales', target: 'en' });await checkTranslationFiles(
options: const Chki18nOptions(path: './locales', target: 'en'),
);check_translation_files(options=Options(path="./locales", target="en"))Every option is on Options, and the result on The result object.
Printing and exiting
Two things this function deliberately does not do:
- It prints nothing unless
verboseis set. Importing the module cannot pollute a host application's output. - It never exits the process. A failing check comes back as
result.success === falseresult.success == falseresult.success is False. Exiting is the CLI's job, and it does it after this function returns.
Turn the output on when you want the CLI's report from your own script:
await checkTranslationFiles('./locales', { target: 'en', verbose: true });await checkTranslationFiles(
path: './locales',
options: const Chki18nOptions(target: 'en', verbose: true),
);check_translation_files("./locales", Options(target="en", verbose=True))reporter and groupBy shape that report exactly as they shape the CLI's. output writes it to a file whether or not verbose is set, because saving a file is something the caller asked for explicitly.
await checkTranslationFiles('./locales', { target: 'en', output: 'report.md' });await checkTranslationFiles(
path: './locales',
options: const Chki18nOptions(target: 'en', output: 'report.md'),
);check_translation_files("./locales", Options(target="en", output="report.md"))To render a result without printing or saving it, call formatResultformatResultformat_result yourself:
import { formatResult, resolveOptions } from 'chki18n';
formatResult(result, resolveOptions({ target: 'en', reporter: 'markdown' }).options);import 'package:chki18n/chki18n.dart';
formatResult(
result,
resolveOptions(
const Chki18nOptions(target: 'en', reporter: Chki18nReporter.markdown),
).options,
);from chki18n import Options, format_result, resolve_options
format_result(result, resolve_options(Options(target="en", reporter="markdown"))[0])Failing a build
import { checkTranslationFiles } from 'chki18n';
const result = await checkTranslationFiles('./locales', { target: 'en' });
if (!result.success) {
for (const issue of result.issues.filter((one) => one.level === 'error')) {
console.error(`${issue.locale} ${issue.key}: ${issue.message}`);
}
process.exit(1);
}import 'dart:io';
import 'package:chki18n/chki18n.dart';
final result = await checkTranslationFiles(
path: './locales',
options: const Chki18nOptions(target: 'en'),
);
if (!result.success) {
for (final issue in result.issues.where((one) => one.level == Chki18nLevel.error)) {
stderr.writeln('${issue.locale} ${issue.key}: ${issue.message}');
}
exitCode = 1;
}import sys
from chki18n import Options, check_translation_files
result = check_translation_files("./locales", Options(target="en"))
if not result.success:
for issue in (one for one in result.issues if one.level == "error"):
print(f"{issue.locale} {issue.key}: {issue.message}", file=sys.stderr)
sys.exit(1)success is false when at least one issue is at error level. Warnings never make it false, so promote one with levels if your project treats it as a blocker.
How errors are reported
A missing directory, an unreadable file, JSON that does not parse, a target language that is nowhere in the files: none of these raise. They come back as issues, so one bad file does not hide everything else that was found:
const result = await checkTranslationFiles('./does-not-exist');
result.success; // false
result.issuesByCode.INVALID_FILE;
// [{ code: 'INVALID_FILE', level: 'error', message: "Failed to read the directory …" }]final result = await checkTranslationFiles(path: './does-not-exist');
result.success; // false
result.of(Chki18nCheckCode.invalidFile);
// [Chki18nIssue(code: INVALID_FILE, level: error, message: "Failed to read the directory …")]result = check_translation_files("./does-not-exist")
result.success # False
result.of("INVALID_FILE")
# [Issue(code="INVALID_FILE", level="error", message="Failed to read the directory …")]Calling it with no path at all is reported the same way, as an INVALID_OPTIONS error.
Timing
result.elapsedMsresult.elapsedMsresult.elapsed_ms covers the whole call: the scan, the parse and the comparison. Checking the same directory more than once means scanning it more than once, so use loadTranslations for that kind of work.See also
analyzeTranslations— the same comparison, on data you already hold.loadTranslations— scan once, then check as often as you like.- Command line — the same thing, as a command.