Skip to content

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

typescript
function checkTranslationFiles(path?: string, options?: Chki18nOptions): Promise<Chki18nResult>;
dart
Future<Chki18nResult> checkTranslationFiles({String? path, Chki18nOptions? options});
python
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

javascript
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.'
//   }
// ]
dart
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.',
// )
python
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:

javascript
await checkTranslationFiles(undefined, { path: './locales', target: 'en' });
dart
await checkTranslationFiles(
  options: const Chki18nOptions(path: './locales', target: 'en'),
);
python
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 verbose is 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:

javascript
await checkTranslationFiles('./locales', { target: 'en', verbose: true });
dart
await checkTranslationFiles(
  path: './locales',
  options: const Chki18nOptions(target: 'en', verbose: true),
);
python
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.

javascript
await checkTranslationFiles('./locales', { target: 'en', output: 'report.md' });
dart
await checkTranslationFiles(
  path: './locales',
  options: const Chki18nOptions(target: 'en', output: 'report.md'),
);
python
check_translation_files("./locales", Options(target="en", output="report.md"))

To render a result without printing or saving it, call formatResultformatResultformat_result yourself:

javascript
import { formatResult, resolveOptions } from 'chki18n';

formatResult(result, resolveOptions({ target: 'en', reporter: 'markdown' }).options);
dart
import 'package:chki18n/chki18n.dart';

formatResult(
  result,
  resolveOptions(
    const Chki18nOptions(target: 'en', reporter: Chki18nReporter.markdown),
  ).options,
);
python
from chki18n import Options, format_result, resolve_options

format_result(result, resolve_options(Options(target="en", reporter="markdown"))[0])

Failing a build

javascript
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);
}
dart
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;
}
python
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:

javascript
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 …" }]
dart
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 …")]
python
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

Released under the MIT License