@courthive/i18n 0.4.9 → 0.5.0
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.
- package/README.md +29 -3
- package/dist/locales/ar.json +626 -20
- package/dist/locales/cs.json +626 -20
- package/dist/locales/de.json +626 -20
- package/dist/locales/en.json +331 -31
- package/dist/locales/es.json +626 -20
- package/dist/locales/fr.json +626 -20
- package/dist/locales/hr.json +626 -20
- package/dist/locales/pt-BR.json +626 -20
- package/dist/locales/zh-CN.json +626 -20
- package/dist/manifest.gen.d.ts +12 -2
- package/dist/manifest.gen.js +22 -9
- package/dist/manifest.json +37 -37
- package/package.json +2 -2
package/dist/manifest.gen.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build-time CLI: scans `dist/locales/*.json`, computes a SHA256 per
|
|
3
|
-
* file, counts keys and
|
|
3
|
+
* file, counts keys and translator sentinels, and emits
|
|
4
4
|
* `dist/manifest.json`.
|
|
5
5
|
*
|
|
6
6
|
* The manifest is the authoritative contract served by CFS at
|
|
@@ -27,6 +27,16 @@
|
|
|
27
27
|
* `keyCount` is therefore the *translatable* key count, which is ≥ the number of
|
|
28
28
|
* keys present in the served file. That is the useful reading of it, but it does
|
|
29
29
|
* mean keyCount cannot be used to validate a downloaded locale's key count.
|
|
30
|
+
*
|
|
31
|
+
* ## Completeness counts both sentinels
|
|
32
|
+
*
|
|
33
|
+
* `__TODO__` means no translation exists. `__STALE__<translation>` means one
|
|
34
|
+
* exists but the English it was made from has since changed, so it may now
|
|
35
|
+
* answer the wrong question — see `scripts/merge-source-en.cjs`. Both are
|
|
36
|
+
* outstanding translator work and both count against `completeness`; a stale
|
|
37
|
+
* string is not a correct one. The stale prefix is stripped by `copy-locales`
|
|
38
|
+
* on the way to dist, so it is only ever visible in `src` — which is exactly
|
|
39
|
+
* why the metric is read from `src`.
|
|
30
40
|
*/
|
|
31
41
|
export interface ManifestLocaleEntry {
|
|
32
42
|
/** BCP47 language tag (e.g. 'en', 'pt-BR', 'zh-CN'). */
|
|
@@ -41,7 +51,7 @@ export interface ManifestLocaleEntry {
|
|
|
41
51
|
size: number;
|
|
42
52
|
/** Total leaf-key count. */
|
|
43
53
|
keyCount: number;
|
|
44
|
-
/** 1 - (__TODO__ count / total keys). */
|
|
54
|
+
/** 1 - ((__TODO__ + __STALE__ count) / total keys). */
|
|
45
55
|
completeness: number;
|
|
46
56
|
/** Right-to-left rendering required. */
|
|
47
57
|
rtl: boolean;
|
package/dist/manifest.gen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Build-time CLI: scans `dist/locales/*.json`, computes a SHA256 per
|
|
3
|
-
* file, counts keys and
|
|
3
|
+
* file, counts keys and translator sentinels, and emits
|
|
4
4
|
* `dist/manifest.json`.
|
|
5
5
|
*
|
|
6
6
|
* The manifest is the authoritative contract served by CFS at
|
|
@@ -27,6 +27,16 @@
|
|
|
27
27
|
* `keyCount` is therefore the *translatable* key count, which is ≥ the number of
|
|
28
28
|
* keys present in the served file. That is the useful reading of it, but it does
|
|
29
29
|
* mean keyCount cannot be used to validate a downloaded locale's key count.
|
|
30
|
+
*
|
|
31
|
+
* ## Completeness counts both sentinels
|
|
32
|
+
*
|
|
33
|
+
* `__TODO__` means no translation exists. `__STALE__<translation>` means one
|
|
34
|
+
* exists but the English it was made from has since changed, so it may now
|
|
35
|
+
* answer the wrong question — see `scripts/merge-source-en.cjs`. Both are
|
|
36
|
+
* outstanding translator work and both count against `completeness`; a stale
|
|
37
|
+
* string is not a correct one. The stale prefix is stripped by `copy-locales`
|
|
38
|
+
* on the way to dist, so it is only ever visible in `src` — which is exactly
|
|
39
|
+
* why the metric is read from `src`.
|
|
30
40
|
*/
|
|
31
41
|
import { createHash } from 'node:crypto';
|
|
32
42
|
import { readFileSync, readdirSync, writeFileSync, statSync, existsSync } from 'node:fs';
|
|
@@ -35,6 +45,7 @@ import { dirname, join } from 'node:path';
|
|
|
35
45
|
import { localeLabels } from './locale-labels.js';
|
|
36
46
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
37
47
|
const TODO_SENTINEL = '__TODO__';
|
|
48
|
+
const STALE_SENTINEL = '__STALE__';
|
|
38
49
|
function countLeafKeys(obj) {
|
|
39
50
|
if (obj === null || typeof obj !== 'object' || Array.isArray(obj))
|
|
40
51
|
return 1;
|
|
@@ -44,14 +55,15 @@ function countLeafKeys(obj) {
|
|
|
44
55
|
}
|
|
45
56
|
return n;
|
|
46
57
|
}
|
|
47
|
-
|
|
58
|
+
/** Leaves carrying either sentinel — untranslated or translated-then-drifted. */
|
|
59
|
+
function countOutstanding(obj) {
|
|
48
60
|
if (typeof obj === 'string')
|
|
49
|
-
return obj === TODO_SENTINEL ? 1 : 0;
|
|
61
|
+
return obj === TODO_SENTINEL || obj.startsWith(STALE_SENTINEL) ? 1 : 0;
|
|
50
62
|
if (obj === null || typeof obj !== 'object' || Array.isArray(obj))
|
|
51
63
|
return 0;
|
|
52
64
|
let n = 0;
|
|
53
65
|
for (const key of Object.keys(obj)) {
|
|
54
|
-
n +=
|
|
66
|
+
n += countOutstanding(obj[key]);
|
|
55
67
|
}
|
|
56
68
|
return n;
|
|
57
69
|
}
|
|
@@ -77,16 +89,16 @@ function build() {
|
|
|
77
89
|
const raw = readFileSync(fullPath, 'utf8');
|
|
78
90
|
const sha = createHash('sha256').update(raw).digest('hex');
|
|
79
91
|
const size = statSync(fullPath).size;
|
|
80
|
-
// … while the translation metric is read from src, where
|
|
81
|
-
//
|
|
92
|
+
// … while the translation metric is read from src, where both sentinels
|
|
93
|
+
// still live. Fall back to the dist copy if src is unavailable (e.g. a consumer
|
|
82
94
|
// running the generator against an extracted package) rather than silently
|
|
83
95
|
// reporting 100% complete.
|
|
84
96
|
const srcPath = join(__dirname, '..', 'src', 'locales', file);
|
|
85
97
|
const metricSource = existsSync(srcPath) ? srcPath : fullPath;
|
|
86
98
|
const parsed = JSON.parse(readFileSync(metricSource, 'utf8'));
|
|
87
99
|
const keyCount = countLeafKeys(parsed);
|
|
88
|
-
const
|
|
89
|
-
const completeness = keyCount > 0 ? 1 -
|
|
100
|
+
const outstanding = countOutstanding(parsed);
|
|
101
|
+
const completeness = keyCount > 0 ? 1 - outstanding / keyCount : 1;
|
|
90
102
|
entries.push({
|
|
91
103
|
code,
|
|
92
104
|
label: labelEntry.label,
|
|
@@ -98,7 +110,8 @@ function build() {
|
|
|
98
110
|
rtl: labelEntry.rtl,
|
|
99
111
|
});
|
|
100
112
|
if (completeness < 1) {
|
|
101
|
-
console.warn(`manifest.gen: locale "${code}" is ${(completeness * 100).toFixed(1)}% complete
|
|
113
|
+
console.warn(`manifest.gen: locale "${code}" is ${(completeness * 100).toFixed(1)}% complete ` +
|
|
114
|
+
`(${outstanding}/${keyCount} outstanding)`);
|
|
102
115
|
}
|
|
103
116
|
}
|
|
104
117
|
// Deterministic order: alphabetical by code so manifest diffs are stable.
|
package/dist/manifest.json
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "@courthive/i18n@0.
|
|
3
|
-
"generatedAt": "2026-08-
|
|
2
|
+
"version": "@courthive/i18n@0.5.0",
|
|
3
|
+
"generatedAt": "2026-08-31T16:24:10.199Z",
|
|
4
4
|
"locales": [
|
|
5
5
|
{
|
|
6
6
|
"code": "ar",
|
|
7
7
|
"label": "Arabic",
|
|
8
8
|
"nativeLabel": "العربية",
|
|
9
|
-
"version": "sha256-
|
|
10
|
-
"size":
|
|
11
|
-
"keyCount":
|
|
12
|
-
"completeness":
|
|
9
|
+
"version": "sha256-f726c6bd2fa8f49155682e0e929dcf50762ef2808cd6c0d75bda90984819af3b",
|
|
10
|
+
"size": 169719,
|
|
11
|
+
"keyCount": 2888,
|
|
12
|
+
"completeness": 1,
|
|
13
13
|
"rtl": true
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"code": "cs",
|
|
17
17
|
"label": "Czech",
|
|
18
18
|
"nativeLabel": "Čeština",
|
|
19
|
-
"version": "sha256-
|
|
20
|
-
"size":
|
|
21
|
-
"keyCount":
|
|
22
|
-
"completeness":
|
|
19
|
+
"version": "sha256-472d4b81562a8b485f319de257f17107853d9a346e39bf05e006367f11971c3a",
|
|
20
|
+
"size": 141170,
|
|
21
|
+
"keyCount": 2888,
|
|
22
|
+
"completeness": 1,
|
|
23
23
|
"rtl": false
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"code": "de",
|
|
27
27
|
"label": "German",
|
|
28
28
|
"nativeLabel": "Deutsch",
|
|
29
|
-
"version": "sha256-
|
|
30
|
-
"size":
|
|
31
|
-
"keyCount":
|
|
32
|
-
"completeness":
|
|
29
|
+
"version": "sha256-52d8fd4c8278899b910ccf749a118dd47de255d4f86b2945d7e8c766274b4404",
|
|
30
|
+
"size": 144043,
|
|
31
|
+
"keyCount": 2888,
|
|
32
|
+
"completeness": 1,
|
|
33
33
|
"rtl": false
|
|
34
34
|
},
|
|
35
35
|
{
|
|
36
36
|
"code": "en",
|
|
37
37
|
"label": "English",
|
|
38
38
|
"nativeLabel": "English",
|
|
39
|
-
"version": "sha256-
|
|
40
|
-
"size":
|
|
41
|
-
"keyCount":
|
|
39
|
+
"version": "sha256-337885289fcb09e9519c4be1054c73f4aeb47547f3cd84cd7fa0b73cea1f7265",
|
|
40
|
+
"size": 130650,
|
|
41
|
+
"keyCount": 2888,
|
|
42
42
|
"completeness": 1,
|
|
43
43
|
"rtl": false
|
|
44
44
|
},
|
|
@@ -46,50 +46,50 @@
|
|
|
46
46
|
"code": "es",
|
|
47
47
|
"label": "Spanish",
|
|
48
48
|
"nativeLabel": "Español",
|
|
49
|
-
"version": "sha256-
|
|
50
|
-
"size":
|
|
51
|
-
"keyCount":
|
|
52
|
-
"completeness":
|
|
49
|
+
"version": "sha256-b0d9ce14f4ddc2281805f7245cd1b5d0fab75e6908568e4883530bd5e4bacf80",
|
|
50
|
+
"size": 143885,
|
|
51
|
+
"keyCount": 2888,
|
|
52
|
+
"completeness": 1,
|
|
53
53
|
"rtl": false
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
"code": "fr",
|
|
57
57
|
"label": "French",
|
|
58
58
|
"nativeLabel": "Français",
|
|
59
|
-
"version": "sha256-
|
|
60
|
-
"size":
|
|
61
|
-
"keyCount":
|
|
62
|
-
"completeness":
|
|
59
|
+
"version": "sha256-755c098b022da4cf4202b7af33a8132f98b57b928dcc332b49b6d61c522fc4c0",
|
|
60
|
+
"size": 147980,
|
|
61
|
+
"keyCount": 2888,
|
|
62
|
+
"completeness": 1,
|
|
63
63
|
"rtl": false
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"code": "hr",
|
|
67
67
|
"label": "Croatian",
|
|
68
68
|
"nativeLabel": "Hrvatski",
|
|
69
|
-
"version": "sha256-
|
|
70
|
-
"size":
|
|
71
|
-
"keyCount":
|
|
72
|
-
"completeness":
|
|
69
|
+
"version": "sha256-7f6dd49a642e65b67239ba356921b5d99149e2960a5cafa1182223f7c4ade20a",
|
|
70
|
+
"size": 138630,
|
|
71
|
+
"keyCount": 2888,
|
|
72
|
+
"completeness": 1,
|
|
73
73
|
"rtl": false
|
|
74
74
|
},
|
|
75
75
|
{
|
|
76
76
|
"code": "pt-BR",
|
|
77
77
|
"label": "Portuguese (Brazil)",
|
|
78
78
|
"nativeLabel": "Português (Brasil)",
|
|
79
|
-
"version": "sha256-
|
|
80
|
-
"size":
|
|
81
|
-
"keyCount":
|
|
82
|
-
"completeness":
|
|
79
|
+
"version": "sha256-8be7d6aaaf8d40ae225eb452f193823bbb36f15eb9f89a29fd9cd0718d5df96d",
|
|
80
|
+
"size": 142783,
|
|
81
|
+
"keyCount": 2888,
|
|
82
|
+
"completeness": 1,
|
|
83
83
|
"rtl": false
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
"code": "zh-CN",
|
|
87
87
|
"label": "Chinese (Simplified)",
|
|
88
88
|
"nativeLabel": "简体中文",
|
|
89
|
-
"version": "sha256-
|
|
90
|
-
"size":
|
|
91
|
-
"keyCount":
|
|
92
|
-
"completeness":
|
|
89
|
+
"version": "sha256-abc965d89a65ed427e1ab2fe70f577ff9d29b8f1a7d3296f3c9c4b2e7e5418dc",
|
|
90
|
+
"size": 128550,
|
|
91
|
+
"keyCount": 2888,
|
|
92
|
+
"completeness": 1,
|
|
93
93
|
"rtl": false
|
|
94
94
|
}
|
|
95
95
|
]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@courthive/i18n",
|
|
3
|
-
"packageManager": "pnpm@11.
|
|
4
|
-
"version": "0.
|
|
3
|
+
"packageManager": "pnpm@11.22.0",
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Translation locales for the CourtHive ecosystem. Source-of-truth bundle served at runtime by competition-factory-server to TMX clients.",
|
|
6
6
|
"author": "Charles Allen <charles@CourtHive.com> (CourtHive.com)",
|
|
7
7
|
"license": "MIT",
|