@babelize/engine 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -34,9 +34,18 @@ const TRANSLATE_CONCURRENCY = 3; // Max concurrent locales/batches
|
|
|
34
34
|
* many sentences (the translator service's batch limit).
|
|
35
35
|
*/
|
|
36
36
|
const BRANCH_BATCH_SIZE = 50;
|
|
37
|
-
/**
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
/**
|
|
38
|
+
* A plain source whose stored translation is ICU only fits a project whose
|
|
39
|
+
* runtime formats ICU. A stored ICU translation stored before a check it now
|
|
40
|
+
* fails (e.g. Czech without its `few` branch) is translated again.
|
|
41
|
+
*/
|
|
42
|
+
function reusableTranslation(source, translated, targetPlurals, targetLocale) {
|
|
43
|
+
if (!targetPlurals && !(0, icu_message_1.isIcuMessage)(source) && (0, icu_message_1.isIcuMessage)(translated))
|
|
44
|
+
return false;
|
|
45
|
+
if (!(0, icu_message_1.isIcuMessage)(translated))
|
|
46
|
+
return true;
|
|
47
|
+
const pluralizable = (0, icu_message_1.isIcuMessage)(source) ? [] : (0, icu_message_1.countArguments)(source);
|
|
48
|
+
return !(0, icu_message_1.compareIcuMessages)(source, translated, targetLocale, { pluralizable }).some((i) => i.severity === 'error');
|
|
40
49
|
}
|
|
41
50
|
/** Per-entry instruction: pluralize a count argument if the target language needs it. */
|
|
42
51
|
function pluralHint(args, targetLocale) {
|
|
@@ -419,7 +428,7 @@ async function translateEntries(entries, sourceLang, targetLang, projectId, onPr
|
|
|
419
428
|
const cacheProblem = cached.hit && cached.data !== null ? reuseProblem(text, cached.data) : null;
|
|
420
429
|
if (cacheProblem)
|
|
421
430
|
logger_1.engineLogger.info('Cached translation breaks the glossary; translating again', { key, targetLocale, problem: cacheProblem });
|
|
422
|
-
if (cached.hit && cached.data !== null && !cacheProblem && reusableTranslation(text, cached.data, !!options.targetPlurals) && !(0, untranslated_1.isUntranslatedInto)(text, cached.data, targetLocale)) {
|
|
431
|
+
if (cached.hit && cached.data !== null && !cacheProblem && reusableTranslation(text, cached.data, !!options.targetPlurals, targetLocale) && !(0, untranslated_1.isUntranslatedInto)(text, cached.data, targetLocale)) {
|
|
423
432
|
result[key] = cached.data;
|
|
424
433
|
cacheHits++;
|
|
425
434
|
// Reused, not translated this run (billing counts only fresh output).
|
|
@@ -497,7 +506,7 @@ async function translateEntries(entries, sourceLang, targetLang, projectId, onPr
|
|
|
497
506
|
const tmProblem = lookup.hit && lookup.translatedText ? reuseProblem(entry.value, lookup.translatedText) : null;
|
|
498
507
|
if (tmProblem)
|
|
499
508
|
logger_1.engineLogger.info('TM translation breaks the glossary; translating again', { key: entry.key, targetLocale, problem: tmProblem });
|
|
500
|
-
if (lookup.hit && lookup.translatedText && !tmProblem && reusableTranslation(entry.value, lookup.translatedText, !!options.targetPlurals) && !(0, untranslated_1.isUntranslatedInto)(entry.value, lookup.translatedText, targetLocale)) {
|
|
509
|
+
if (lookup.hit && lookup.translatedText && !tmProblem && reusableTranslation(entry.value, lookup.translatedText, !!options.targetPlurals, targetLocale) && !(0, untranslated_1.isUntranslatedInto)(entry.value, lookup.translatedText, targetLocale)) {
|
|
501
510
|
result[entry.key] = lookup.translatedText;
|
|
502
511
|
tmHits++;
|
|
503
512
|
metadata[entry.key] = { fromMemory: true, inTm: true };
|
|
@@ -38,8 +38,8 @@ export interface IcuIssue {
|
|
|
38
38
|
/**
|
|
39
39
|
* Compare a translated ICU message to its source. Errors mean the message is
|
|
40
40
|
* broken at runtime (unparseable, arguments renamed, select branches changed,
|
|
41
|
-
* no `other
|
|
42
|
-
* falls back to `other` for
|
|
41
|
+
* no `other`, a target plural category whole numbers use). A missing category
|
|
42
|
+
* only fractions use is a warning: ICU falls back to `other` for it.
|
|
43
43
|
*/
|
|
44
44
|
export declare function compareIcuMessages(source: string, translated: string, targetLocale: string, options?: {
|
|
45
45
|
/**
|
|
@@ -190,11 +190,29 @@ function pluralCategories(locale, type) {
|
|
|
190
190
|
return [...CLDR_ORDER];
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Whole numbers (0–999) a plural category is used for, e.g. Czech few → 2, 3, 4,
|
|
195
|
+
* Czech many → none (only fractions like 1.5). CLDR integer rules repeat
|
|
196
|
+
* within every hundred, so this range covers them.
|
|
197
|
+
*/
|
|
198
|
+
function integersOf(locale, type, category) {
|
|
199
|
+
try {
|
|
200
|
+
const rules = new Intl.PluralRules(locale, { type });
|
|
201
|
+
const out = [];
|
|
202
|
+
for (let n = 0; n < 1000; n++)
|
|
203
|
+
if (rules.select(n) === category)
|
|
204
|
+
out.push(n);
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
193
211
|
/**
|
|
194
212
|
* Compare a translated ICU message to its source. Errors mean the message is
|
|
195
213
|
* broken at runtime (unparseable, arguments renamed, select branches changed,
|
|
196
|
-
* no `other
|
|
197
|
-
* falls back to `other` for
|
|
214
|
+
* no `other`, a target plural category whole numbers use). A missing category
|
|
215
|
+
* only fractions use is a warning: ICU falls back to `other` for it.
|
|
198
216
|
*/
|
|
199
217
|
function compareIcuMessages(source, translated, targetLocale, options = {}) {
|
|
200
218
|
let sourceAst;
|
|
@@ -291,11 +309,29 @@ function compareIcuMessages(source, translated, targetLocale, options = {}) {
|
|
|
291
309
|
}
|
|
292
310
|
const missing = allowed.filter((c) => !target.keys.includes(c));
|
|
293
311
|
if (missing.length) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
312
|
+
// A missing category whole numbers reach (Czech few: 2–4) shows the
|
|
313
|
+
// wrong form for common counts ("2 souborů"): an error. One only
|
|
314
|
+
// fractions reach (Czech many: 1.5), or whose numbers all have `=n`
|
|
315
|
+
// branches, falls back to `other` rarely: a warning.
|
|
316
|
+
const type = target.kind === 'selectordinal' ? 'ordinal' : 'cardinal';
|
|
317
|
+
const reached = missing
|
|
318
|
+
.map((c) => ({ c, ns: integersOf(targetLocale, type, c).filter((n) => !target.keys.includes(`=${n}`)) }))
|
|
319
|
+
.filter((m) => m.ns.length > 0);
|
|
320
|
+
if (reached.length) {
|
|
321
|
+
const examples = (ns) => `${ns.slice(0, 3).join(', ')}${ns.length > 3 ? ', …' : ''}`;
|
|
322
|
+
issues.push({
|
|
323
|
+
severity: 'error',
|
|
324
|
+
code: 'icu_plural_category_missing',
|
|
325
|
+
message: `{${target.name}, ${target.kind}} has no ${reached.map((m) => `${m.c} branch (${examples(m.ns)})`).join(', ')} for ${targetLocale}; those counts would get the "other" form`,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
issues.push({
|
|
330
|
+
severity: 'warning',
|
|
331
|
+
code: 'icu_plural_category_missing',
|
|
332
|
+
message: `{${target.name}, ${target.kind}} has no [${missing.join(', ')}] branch for ${targetLocale}; "other" will be used`,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
299
335
|
}
|
|
300
336
|
}
|
|
301
337
|
return issues;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@babelize/engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Babelize translation pipeline (extract, keygen, translate, inject). Infrastructure is injected through provider/store interfaces.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"babelize",
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
],
|
|
12
12
|
"license": "Apache-2.0",
|
|
13
13
|
"homepage": "https://babelize.co",
|
|
14
|
-
"repository": {
|
|
15
|
-
"type": "git",
|
|
16
|
-
"url": "git+https://github.com/babelize/babelize-dashboard.git"
|
|
17
|
-
},
|
|
18
14
|
"main": "./dist/index.js",
|
|
19
15
|
"types": "./dist/index.d.ts",
|
|
20
16
|
"files": [
|