@another-art/i18n-checker 0.0.1

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.
@@ -0,0 +1,25 @@
1
+ export interface Issue {
2
+ locale: string;
3
+ key: string;
4
+ type: 'missing' | 'extra' | 'placeholder_mismatch';
5
+ detail?: string;
6
+ referenceValue?: string;
7
+ localeValue?: string;
8
+ }
9
+ export interface CheckResult {
10
+ i18nPath: string;
11
+ referenceLocale: string;
12
+ allLocales: string[];
13
+ totalKeys: number;
14
+ issues: Issue[];
15
+ translations: Record<string, Record<string, string>>;
16
+ }
17
+ export interface ScanResult {
18
+ root: string;
19
+ results: CheckResult[];
20
+ totalMissing: number;
21
+ totalExtra: number;
22
+ totalPlaceholder: number;
23
+ }
24
+ export declare function scan(root: string): Promise<ScanResult>;
25
+ //# sourceMappingURL=checker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checker.d.ts","sourceRoot":"","sources":["../src/checker.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,KAAK;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,sBAAsB,CAAC;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AA4LD,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAe5D"}
@@ -0,0 +1,182 @@
1
+ import { readFile, readdir, access } from 'node:fs/promises';
2
+ import { resolve, relative, dirname, basename } from 'node:path';
3
+ const PLACEHOLDER_RE = /\{(\w+)}/g;
4
+ function flatten(obj, prefix = '') {
5
+ const result = new Map();
6
+ if (typeof obj === 'string') {
7
+ result.set(prefix, obj);
8
+ return result;
9
+ }
10
+ for (const [k, v] of Object.entries(obj)) {
11
+ const fullKey = prefix ? `${prefix}.${k}` : k;
12
+ if (typeof v === 'string') {
13
+ result.set(fullKey, v);
14
+ }
15
+ else {
16
+ for (const [fk, fv] of flatten(v, fullKey)) {
17
+ result.set(fk, fv);
18
+ }
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+ function extractPlaceholders(value) {
24
+ const placeholders = new Set();
25
+ let match;
26
+ while ((match = PLACEHOLDER_RE.exec(value)) !== null) {
27
+ placeholders.add(match[1]);
28
+ }
29
+ return placeholders;
30
+ }
31
+ async function findI18nDirs(root) {
32
+ const dirs = new Map();
33
+ const allEntries = await readdir(root, { recursive: true });
34
+ const allFiles = allEntries
35
+ .map(e => e.replaceAll('\\', '/'))
36
+ .filter(e => !e.includes('node_modules'));
37
+ // Format A: **/i18n/<locale>/index.json
38
+ for (const file of allFiles) {
39
+ const parts = file.split('/');
40
+ if (parts.length >= 3 && parts[parts.length - 1] === 'index.json' && parts[parts.length - 3] === 'i18n') {
41
+ const i18nDir = resolve(root, parts.slice(0, parts.length - 2).join('/'));
42
+ if (!dirs.has(i18nDir))
43
+ dirs.set(i18nDir, { path: i18nDir, format: 'folder' });
44
+ }
45
+ }
46
+ // Format B: **/<dir>/<locale>.json (e.g. i18n/messages/en.json)
47
+ const candidates = new Map();
48
+ for (const file of allFiles) {
49
+ if (!file.endsWith('.json'))
50
+ continue;
51
+ const dir = resolve(root, dirname(file));
52
+ const name = basename(file, '.json');
53
+ if (dirs.has(dir))
54
+ continue;
55
+ if (!/^[a-z]{2,3}$/.test(name))
56
+ continue;
57
+ if (!candidates.has(dir))
58
+ candidates.set(dir, []);
59
+ candidates.get(dir).push(name);
60
+ }
61
+ for (const [dir, locales] of candidates) {
62
+ if (locales.length >= 2) {
63
+ dirs.set(dir, { path: dir, format: 'flat' });
64
+ }
65
+ }
66
+ return [...dirs.values()].sort((a, b) => a.path.localeCompare(b.path));
67
+ }
68
+ async function getLocales(dir) {
69
+ const entries = await readdir(dir.path);
70
+ if (dir.format === 'folder') {
71
+ const locales = [];
72
+ for (const entry of entries) {
73
+ try {
74
+ await access(resolve(dir.path, entry, 'index.json'));
75
+ locales.push(entry);
76
+ }
77
+ catch {
78
+ // not a locale directory
79
+ }
80
+ }
81
+ return locales.sort();
82
+ }
83
+ return entries
84
+ .filter(e => e.endsWith('.json'))
85
+ .map(e => basename(e, '.json'))
86
+ .filter(n => /^[a-z]{2,3}$/.test(n))
87
+ .sort();
88
+ }
89
+ async function loadTranslations(dir, locale) {
90
+ const filePath = dir.format === 'folder'
91
+ ? resolve(dir.path, locale, 'index.json')
92
+ : resolve(dir.path, `${locale}.json`);
93
+ try {
94
+ const content = await readFile(filePath, 'utf-8');
95
+ const raw = JSON.parse(content);
96
+ return flatten(raw);
97
+ }
98
+ catch {
99
+ return new Map();
100
+ }
101
+ }
102
+ function pickReferenceLocale(locales) {
103
+ if (locales.includes('en'))
104
+ return 'en';
105
+ return locales[0];
106
+ }
107
+ async function checkI18nDir(root, dir) {
108
+ const relPath = relative(root, dir.path);
109
+ const locales = await getLocales(dir);
110
+ const referenceLocale = pickReferenceLocale(locales);
111
+ const translations = new Map();
112
+ for (const locale of locales) {
113
+ translations.set(locale, await loadTranslations(dir, locale));
114
+ }
115
+ const refMap = translations.get(referenceLocale);
116
+ const issues = [];
117
+ for (const locale of locales) {
118
+ if (locale === referenceLocale)
119
+ continue;
120
+ const localeMap = translations.get(locale);
121
+ for (const [key, refValue] of refMap) {
122
+ if (!localeMap.has(key)) {
123
+ issues.push({ locale, key, type: 'missing', referenceValue: refValue });
124
+ continue;
125
+ }
126
+ const localeValue = localeMap.get(key);
127
+ const refPlaceholders = extractPlaceholders(refValue);
128
+ const localePlaceholders = extractPlaceholders(localeValue);
129
+ if (refPlaceholders.size > 0 || localePlaceholders.size > 0) {
130
+ const missingPh = [...refPlaceholders].filter(p => !localePlaceholders.has(p));
131
+ const extraPh = [...localePlaceholders].filter(p => !refPlaceholders.has(p));
132
+ if (missingPh.length > 0 || extraPh.length > 0) {
133
+ const parts = [];
134
+ if (missingPh.length)
135
+ parts.push(`missing: {${missingPh.join('}, {')}}`);
136
+ if (extraPh.length)
137
+ parts.push(`extra: {${extraPh.join('}, {')}}`);
138
+ issues.push({
139
+ locale,
140
+ key,
141
+ type: 'placeholder_mismatch',
142
+ detail: parts.join('; '),
143
+ referenceValue: refValue,
144
+ localeValue,
145
+ });
146
+ }
147
+ }
148
+ }
149
+ for (const [key, val] of localeMap) {
150
+ if (!refMap.has(key)) {
151
+ issues.push({ locale, key, type: 'extra', localeValue: val });
152
+ }
153
+ }
154
+ }
155
+ const translationsObj = {};
156
+ for (const [locale, map] of translations) {
157
+ translationsObj[locale] = Object.fromEntries(map);
158
+ }
159
+ return {
160
+ i18nPath: relPath,
161
+ referenceLocale,
162
+ allLocales: locales,
163
+ totalKeys: refMap.size,
164
+ issues,
165
+ translations: translationsObj,
166
+ };
167
+ }
168
+ export async function scan(root) {
169
+ const i18nDirs = await findI18nDirs(root);
170
+ const results = [];
171
+ for (const d of i18nDirs) {
172
+ results.push(await checkI18nDir(root, d));
173
+ }
174
+ return {
175
+ root,
176
+ results,
177
+ totalMissing: results.reduce((s, r) => s + r.issues.filter(i => i.type === 'missing').length, 0),
178
+ totalExtra: results.reduce((s, r) => s + r.issues.filter(i => i.type === 'extra').length, 0),
179
+ totalPlaceholder: results.reduce((s, r) => s + r.issues.filter(i => i.type === 'placeholder_mismatch').length, 0),
180
+ };
181
+ }
182
+ //# sourceMappingURL=checker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checker.js","sourceRoot":"","sources":["../src/checker.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ import { createServer } from 'node:http';
3
+ import { resolve } from 'node:path';
4
+ import { scan } from './checker.js';
5
+ import { buildHtml } from './ui.js';
6
+ const PROJECT_ROOT = process.argv[2] ? resolve(process.argv[2]) : process.cwd();
7
+ const START_PORT = parseInt(process.env.PORT || '3999', 10);
8
+ function listen(server, port) {
9
+ return new Promise((res, rej) => {
10
+ server.once('error', (err) => {
11
+ if (err.code === 'EADDRINUSE') {
12
+ res(listen(server, port + 1));
13
+ }
14
+ else {
15
+ rej(err);
16
+ }
17
+ });
18
+ server.listen(port, () => res(port));
19
+ });
20
+ }
21
+ async function main() {
22
+ console.log('Scanning i18n directories...');
23
+ const data = await scan(PROJECT_ROOT);
24
+ console.log(`Found ${data.results.length} i18n directories, ${data.totalMissing + data.totalExtra + data.totalPlaceholder} issues`);
25
+ const html = buildHtml(data);
26
+ const server = createServer((_req, res) => {
27
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
28
+ res.end(html);
29
+ });
30
+ const port = await listen(server, START_PORT);
31
+ console.log(`UI: http://localhost:${port}`);
32
+ }
33
+ main();
34
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEpC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;AAChF,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;AAE5D,SAAS,MAAM,CAAC,MAAuC,EAAE,IAAY;IACnE,OAAO,IAAI,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC9B,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;YAClD,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;gBAC9B,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,GAAG,CAAC,CAAC;YACX,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,MAAM,sBAAsB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,gBAAgB,SAAS,CAAC,CAAC;IAEpI,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAE7B,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QACxC,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,0BAA0B,EAAE,CAAC,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,IAAI,EAAE,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { scan } from './checker.js';
2
+ export type { Issue, CheckResult, ScanResult } from './checker.js';
3
+ export { buildHtml } from './ui.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { scan } from './checker.js';
2
+ export { buildHtml } from './ui.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
package/dist/ui.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { ScanResult } from './checker.js';
2
+ export declare function buildHtml(data: ScanResult): string;
3
+ //# sourceMappingURL=ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM/C,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CA+kBlD"}
package/dist/ui.js ADDED
@@ -0,0 +1,594 @@
1
+ function escapeHtml(str) {
2
+ return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
3
+ }
4
+ export function buildHtml(data) {
5
+ const totalIssues = data.totalMissing + data.totalExtra + data.totalPlaceholder;
6
+ const jsonData = JSON.stringify(data)
7
+ .replace(/</g, '\\u003c')
8
+ .replace(/>/g, '\\u003e')
9
+ .replace(/&/g, '\\u0026');
10
+ return `<!DOCTYPE html>
11
+ <html lang="en" class="dark">
12
+ <head>
13
+ <meta charset="utf-8">
14
+ <meta name="viewport" content="width=device-width, initial-scale=1">
15
+ <title>i18n Checker</title>
16
+ <script src="https://cdn.tailwindcss.com"><\/script>
17
+ <script>
18
+ tailwind.config = {
19
+ darkMode: 'class',
20
+ theme: {
21
+ extend: {
22
+ colors: {
23
+ surface: { 0: '#09090b', 1: '#18181b', 2: '#27272a', 3: '#3f3f46' },
24
+ },
25
+ fontFamily: {
26
+ mono: ['"JetBrains Mono"', '"SF Mono"', '"Fira Code"', 'Consolas', 'monospace'],
27
+ },
28
+ },
29
+ },
30
+ }
31
+ <\/script>
32
+ <style>
33
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
34
+ ::-webkit-scrollbar-track { background: transparent; }
35
+ ::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 3px; }
36
+ ::-webkit-scrollbar-thumb:hover { background: #52525b; }
37
+ .tooltip-wrap:hover .tooltip-box { display: block; }
38
+ .matrix-table { border-spacing: 0; }
39
+ </style>
40
+ </head>
41
+ <body class="bg-surface-0 text-zinc-300 min-h-screen antialiased">
42
+
43
+ <!-- Top Bar -->
44
+ <header class="sticky top-0 z-50 bg-surface-0/80 backdrop-blur-xl border-b border-zinc-800/60">
45
+ <div class="max-w-[1600px] mx-auto px-6 py-4 flex items-center justify-between">
46
+ <div class="flex items-center gap-3">
47
+ <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-violet-500 to-blue-500 flex items-center justify-center">
48
+ <svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
49
+ <path stroke-linecap="round" stroke-linejoin="round" d="M3 5h12M9 3v2m1.048 9.5A18.022 18.022 0 016.412 9m6.088 9h7M11 21l5-10 5 10M12.751 5C11.783 10.77 8.07 15.61 3 18.129"/>
50
+ </svg>
51
+ </div>
52
+ <div>
53
+ <h1 class="text-sm font-semibold text-zinc-100 tracking-tight">i18n Translation Checker</h1>
54
+ <p class="text-xs text-zinc-500 font-mono mt-0.5">${escapeHtml(data.root)}</p>
55
+ </div>
56
+ </div>
57
+ <div class="flex items-center gap-2 text-xs">
58
+ <span class="px-2.5 py-1 rounded-full bg-surface-2 text-zinc-400 font-medium">${data.results.length} ${data.results.length === 1 ? 'directory' : 'directories'}</span>
59
+ <span class="px-2.5 py-1 rounded-full ${totalIssues === 0 ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400'} font-medium">${totalIssues} ${totalIssues === 1 ? 'issue' : 'issues'}</span>
60
+ </div>
61
+ </div>
62
+ </header>
63
+
64
+ <main class="max-w-[1600px] mx-auto px-6 py-6 space-y-6">
65
+
66
+ <!-- Summary Cards -->
67
+ <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
68
+ <div class="bg-surface-1 border border-zinc-800/60 rounded-xl p-4">
69
+ <div class="text-[11px] uppercase tracking-wider text-zinc-500 font-medium mb-2">Directories</div>
70
+ <div class="text-2xl font-bold text-blue-400 tabular-nums">${data.results.length}</div>
71
+ </div>
72
+ <div class="bg-surface-1 border border-zinc-800/60 rounded-xl p-4">
73
+ <div class="text-[11px] uppercase tracking-wider text-zinc-500 font-medium mb-2">Total Issues</div>
74
+ <div class="text-2xl font-bold ${totalIssues === 0 ? 'text-emerald-400' : 'text-red-400'} tabular-nums">${totalIssues}</div>
75
+ </div>
76
+ <div class="bg-surface-1 border border-zinc-800/60 rounded-xl p-4">
77
+ <div class="text-[11px] uppercase tracking-wider text-zinc-500 font-medium mb-2">Missing</div>
78
+ <div class="text-2xl font-bold ${data.totalMissing === 0 ? 'text-emerald-400' : 'text-red-400'} tabular-nums">${data.totalMissing}</div>
79
+ </div>
80
+ <div class="bg-surface-1 border border-zinc-800/60 rounded-xl p-4">
81
+ <div class="text-[11px] uppercase tracking-wider text-zinc-500 font-medium mb-2">Extra</div>
82
+ <div class="text-2xl font-bold ${data.totalExtra === 0 ? 'text-emerald-400' : 'text-violet-400'} tabular-nums">${data.totalExtra}</div>
83
+ </div>
84
+ <div class="bg-surface-1 border border-zinc-800/60 rounded-xl p-4">
85
+ <div class="text-[11px] uppercase tracking-wider text-zinc-500 font-medium mb-2">Placeholder</div>
86
+ <div class="text-2xl font-bold ${data.totalPlaceholder === 0 ? 'text-emerald-400' : 'text-amber-400'} tabular-nums">${data.totalPlaceholder}</div>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- Toolbar -->
91
+ <div class="space-y-3">
92
+ <div class="flex flex-wrap items-center gap-3">
93
+ <div class="relative">
94
+ <svg class="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-zinc-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
95
+ <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
96
+ </svg>
97
+ <input id="search" type="text" placeholder="Filter keys..."
98
+ class="bg-surface-1 border border-zinc-800 rounded-lg pl-9 pr-4 py-2 text-sm text-zinc-200 placeholder-zinc-500 w-64 outline-none focus:border-zinc-600 focus:ring-1 focus:ring-zinc-700 transition" />
99
+ </div>
100
+
101
+ <div class="flex gap-1.5" id="type-filters">
102
+ <button data-filter="all" class="filter-btn active px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-zinc-800 border-zinc-600 text-zinc-200">All</button>
103
+ <button data-filter="issues_only" class="filter-btn px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">Issues only</button>
104
+ <button data-filter="missing" class="filter-btn px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">Missing</button>
105
+ <button data-filter="extra" class="filter-btn px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">Extra</button>
106
+ <button data-filter="placeholder_mismatch" class="filter-btn px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">Placeholder</button>
107
+ </div>
108
+
109
+ <div class="h-5 w-px bg-zinc-800 mx-1 hidden sm:block"></div>
110
+
111
+ <button id="toggle-values" class="px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">
112
+ Show values
113
+ </button>
114
+ <button id="toggle-hide-translated" class="px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">
115
+ Hide translated
116
+ </button>
117
+ </div>
118
+
119
+ <!-- Locale Tags -->
120
+ <div class="flex flex-wrap items-center gap-1.5">
121
+ <div class="flex items-center gap-1.5 mr-1">
122
+ <span class="text-[11px] text-zinc-500 font-medium">REF:</span>
123
+ <select id="ref-locale-select"
124
+ class="bg-surface-1 border border-zinc-800 rounded-md px-2 py-1 text-[11px] font-semibold text-blue-400 outline-none focus:border-zinc-600 cursor-pointer appearance-none pr-5"
125
+ style="background-image: url('data:image/svg+xml;utf8,<svg fill=\\"%2364748b\\" viewBox=\\"0 0 20 20\\" xmlns=\\"http://www.w3.org/2000/svg\\"><path fill-rule=\\"evenodd\\" d=\\"M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z\\" clip-rule=\\"evenodd\\"/></svg>'); background-repeat: no-repeat; background-position: right 4px center; background-size: 14px;">
126
+ </select>
127
+ </div>
128
+ <div class="h-4 w-px bg-zinc-800 mx-1"></div>
129
+ <button id="select-all-locales" class="px-2.5 py-1 rounded-md text-[11px] font-semibold border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">Select all</button>
130
+ <button id="deselect-all-locales" class="px-2.5 py-1 rounded-md text-[11px] font-semibold border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300">Deselect all</button>
131
+ <div class="h-4 w-px bg-zinc-800 mx-1"></div>
132
+ <div class="flex flex-wrap gap-1.5" id="locale-filters"></div>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Directory Sections -->
137
+ <div class="space-y-3" id="sections">
138
+ ${data.results.map((r, idx) => {
139
+ const issueCount = r.issues.length;
140
+ const isOpen = issueCount > 0;
141
+ return `
142
+ <div class="dir-section bg-surface-1 border border-zinc-800/60 rounded-xl overflow-hidden" data-index="${idx}">
143
+ <button class="dir-toggle w-full px-5 py-3.5 flex items-center justify-between text-left hover:bg-surface-2/40 transition-colors" data-open="${isOpen}" onclick="toggleSection(this)">
144
+ <div class="flex items-center gap-3 min-w-0">
145
+ <svg class="dir-chevron w-4 h-4 text-zinc-500 shrink-0 transition-transform ${isOpen ? 'rotate-90' : ''}" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
146
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7"/>
147
+ </svg>
148
+ <span class="text-sm font-semibold text-zinc-100 truncate font-mono">${escapeHtml(r.i18nPath)}</span>
149
+ </div>
150
+ <div class="flex items-center gap-2.5 shrink-0 ml-4">
151
+ <span class="text-[11px] text-zinc-500">ref: <span class="text-zinc-400">${escapeHtml(r.referenceLocale)}</span></span>
152
+ <span class="text-[11px] text-zinc-500">${r.allLocales.length} locales</span>
153
+ <span class="text-[11px] text-zinc-500">${r.totalKeys} keys</span>
154
+ <span class="inline-flex items-center px-2 py-0.5 rounded-full text-[11px] font-semibold ${issueCount === 0 ? 'bg-emerald-500/10 text-emerald-400' : 'bg-red-500/10 text-red-400'}">${issueCount === 0 ? 'OK' : `${issueCount} issues`}</span>
155
+ </div>
156
+ </button>
157
+ <div class="dir-content border-t border-zinc-800/40 ${isOpen ? '' : 'hidden'}">
158
+ <div class="overflow-x-auto max-h-[70vh] overflow-y-auto relative"><div class="matrix-container" data-section="${idx}"></div></div>
159
+ </div>
160
+ </div>`;
161
+ }).join('')}
162
+ </div>
163
+
164
+ </main>
165
+
166
+ <script>
167
+ var __DATA__ = ${jsonData};
168
+
169
+ var activeTypeFilter = 'all';
170
+ var activeLocales = new Set();
171
+ var searchQuery = '';
172
+ var activeRefLocale = null;
173
+ var showValues = false;
174
+ var hideTranslated = false;
175
+
176
+ // ---- Toggle values button ----
177
+ (function() {
178
+ var btn = document.getElementById('toggle-values');
179
+ btn.addEventListener('click', function() {
180
+ showValues = !showValues;
181
+ btn.textContent = showValues ? 'Hide values' : 'Show values';
182
+ btn.className = showValues
183
+ ? 'px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-zinc-800 border-zinc-600 text-zinc-200'
184
+ : 'px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300';
185
+ renderAllMatrices();
186
+ });
187
+ })();
188
+
189
+ // ---- Toggle hide translated button ----
190
+ (function() {
191
+ var btn = document.getElementById('toggle-hide-translated');
192
+ btn.addEventListener('click', function() {
193
+ hideTranslated = !hideTranslated;
194
+ btn.textContent = hideTranslated ? 'Show translated' : 'Hide translated';
195
+ btn.className = hideTranslated
196
+ ? 'px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-zinc-800 border-zinc-600 text-zinc-200'
197
+ : 'px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300';
198
+ renderAllMatrices();
199
+ });
200
+ })();
201
+
202
+ var allLocalesSet = new Set();
203
+ __DATA__.results.forEach(function(r) { r.allLocales.forEach(function(l) { allLocalesSet.add(l); }); });
204
+ var allLocalesSorted = Array.from(allLocalesSet).sort();
205
+
206
+ // ---- Ref locale selector ----
207
+ (function() {
208
+ var sel = document.getElementById('ref-locale-select');
209
+ var defaultOpt = document.createElement('option');
210
+ defaultOpt.value = '';
211
+ defaultOpt.textContent = 'auto (en)';
212
+ sel.appendChild(defaultOpt);
213
+ allLocalesSorted.forEach(function(l) {
214
+ var opt = document.createElement('option');
215
+ opt.value = l;
216
+ opt.textContent = l;
217
+ sel.appendChild(opt);
218
+ });
219
+ sel.addEventListener('change', function() {
220
+ activeRefLocale = sel.value || null;
221
+ renderAllMatrices();
222
+ });
223
+ })();
224
+
225
+ var INACTIVE_CLS = 'locale-tag px-2.5 py-1 rounded-md text-[11px] font-semibold border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300';
226
+ var ACTIVE_CLS = 'locale-tag px-2.5 py-1 rounded-md text-[11px] font-semibold border transition-all cursor-pointer bg-blue-500/10 border-blue-500/40 text-blue-400';
227
+
228
+ var localeBtns = {};
229
+ (function() {
230
+ var el = document.getElementById('locale-filters');
231
+ allLocalesSorted.forEach(function(locale) {
232
+ var btn = document.createElement('button');
233
+ btn.textContent = locale;
234
+ btn.dataset.locale = locale;
235
+ btn.className = INACTIVE_CLS;
236
+ btn.addEventListener('click', function() {
237
+ if (activeLocales.has(locale)) {
238
+ activeLocales.delete(locale);
239
+ btn.className = INACTIVE_CLS;
240
+ } else {
241
+ activeLocales.add(locale);
242
+ btn.className = ACTIVE_CLS;
243
+ }
244
+ renderAllMatrices();
245
+ });
246
+ el.appendChild(btn);
247
+ localeBtns[locale] = btn;
248
+ });
249
+ })();
250
+
251
+ function syncLocaleButtons() {
252
+ allLocalesSorted.forEach(function(l) {
253
+ localeBtns[l].className = activeLocales.has(l) ? ACTIVE_CLS : INACTIVE_CLS;
254
+ });
255
+ }
256
+
257
+ document.getElementById('select-all-locales').addEventListener('click', function() {
258
+ allLocalesSorted.forEach(function(l) { activeLocales.add(l); });
259
+ syncLocaleButtons();
260
+ renderAllMatrices();
261
+ });
262
+
263
+ document.getElementById('deselect-all-locales').addEventListener('click', function() {
264
+ activeLocales.clear();
265
+ syncLocaleButtons();
266
+ renderAllMatrices();
267
+ });
268
+
269
+ var FILTER_INACTIVE = 'filter-btn px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-surface-1 border-zinc-800 text-zinc-500 hover:border-zinc-600 hover:text-zinc-300';
270
+ var FILTER_ACTIVE = 'filter-btn active px-3 py-1.5 rounded-lg text-xs font-medium border transition-all cursor-pointer bg-zinc-800 border-zinc-600 text-zinc-200';
271
+
272
+ document.querySelectorAll('#type-filters .filter-btn').forEach(function(btn) {
273
+ btn.addEventListener('click', function() {
274
+ document.querySelectorAll('#type-filters .filter-btn').forEach(function(b) { b.className = FILTER_INACTIVE; });
275
+ btn.className = FILTER_ACTIVE;
276
+ activeTypeFilter = btn.dataset.filter;
277
+ renderAllMatrices();
278
+ });
279
+ });
280
+
281
+ document.getElementById('search').addEventListener('input', function(e) {
282
+ searchQuery = e.target.value.toLowerCase();
283
+ renderAllMatrices();
284
+ });
285
+
286
+ function toggleSection(btn) {
287
+ var content = btn.nextElementSibling;
288
+ var chevron = btn.querySelector('.dir-chevron');
289
+ var isOpen = btn.dataset.open === 'true';
290
+ btn.dataset.open = String(!isOpen);
291
+ if (isOpen) {
292
+ content.classList.add('hidden');
293
+ chevron.classList.remove('rotate-90');
294
+ } else {
295
+ content.classList.remove('hidden');
296
+ chevron.classList.add('rotate-90');
297
+ }
298
+ }
299
+
300
+ function createIconCell(issue) {
301
+ var wrap = document.createElement('div');
302
+ wrap.className = 'tooltip-wrap relative inline-flex items-center justify-center';
303
+ var indicator = document.createElement('div');
304
+
305
+ if (!issue) {
306
+ indicator.className = 'w-7 h-7 rounded-md flex items-center justify-center bg-emerald-500/10 border border-emerald-500/20';
307
+ indicator.innerHTML = '<svg class="w-3.5 h-3.5 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>';
308
+ wrap.appendChild(indicator);
309
+ } else if (issue.type === 'missing') {
310
+ indicator.className = 'w-7 h-7 rounded-md flex items-center justify-center bg-red-500/10 border border-red-500/20';
311
+ indicator.innerHTML = '<svg class="w-3.5 h-3.5 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>';
312
+ wrap.appendChild(indicator);
313
+ if (issue.referenceValue) {
314
+ var tip = document.createElement('div');
315
+ tip.className = 'tooltip-box hidden absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 rounded-lg bg-zinc-900 border border-red-500/30 text-xs text-zinc-300 max-w-xs whitespace-pre-wrap z-50 shadow-xl pointer-events-none';
316
+ tip.textContent = 'Ref: ' + issue.referenceValue;
317
+ wrap.appendChild(tip);
318
+ }
319
+ } else if (issue.type === 'extra') {
320
+ indicator.className = 'w-7 h-7 rounded-md flex items-center justify-center bg-violet-500/10 border border-violet-500/20';
321
+ var plus = document.createElement('span');
322
+ plus.className = 'text-[11px] font-bold text-violet-400';
323
+ plus.textContent = '+';
324
+ indicator.appendChild(plus);
325
+ wrap.appendChild(indicator);
326
+ } else if (issue.type === 'placeholder_mismatch') {
327
+ indicator.className = 'w-7 h-7 rounded-md flex items-center justify-center bg-amber-500/10 border border-amber-500/20';
328
+ indicator.innerHTML = '<svg class="w-3.5 h-3.5 text-amber-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4.5c-.77-.833-2.694-.833-3.464 0L3.34 16.5c-.77.833.192 2.5 1.732 2.5z"/></svg>';
329
+ wrap.appendChild(indicator);
330
+ if (issue.detail) {
331
+ var tip2 = document.createElement('div');
332
+ tip2.className = 'tooltip-box hidden absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-3 py-2 rounded-lg bg-zinc-900 border border-amber-500/30 text-xs text-zinc-300 max-w-xs whitespace-pre-wrap z-50 shadow-xl pointer-events-none';
333
+ tip2.textContent = issue.detail;
334
+ wrap.appendChild(tip2);
335
+ }
336
+ }
337
+ return wrap;
338
+ }
339
+
340
+ function createCell(localeValue, issue) {
341
+ var td = document.createElement('td');
342
+
343
+ if (showValues) {
344
+ td.className = 'px-2 py-1.5 border-b border-zinc-800/30 align-top min-w-[200px] max-w-[300px]';
345
+
346
+ if (issue && issue.type === 'missing') {
347
+ var badge = document.createElement('div');
348
+ badge.className = 'inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-red-500/10 border border-red-500/20';
349
+ badge.innerHTML = '<svg class="w-3 h-3 text-red-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/></svg>';
350
+ var span = document.createElement('span');
351
+ span.className = 'text-[11px] text-red-400 font-medium';
352
+ span.textContent = 'missing';
353
+ badge.appendChild(span);
354
+ td.appendChild(badge);
355
+ } else if (issue && issue.type === 'extra') {
356
+ var badge2 = document.createElement('div');
357
+ badge2.className = 'inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-violet-500/10 border border-violet-500/20';
358
+ var span2 = document.createElement('span');
359
+ span2.className = 'text-[11px] text-violet-400 font-medium truncate max-w-[200px]';
360
+ span2.textContent = localeValue || 'extra';
361
+ badge2.appendChild(span2);
362
+ td.appendChild(badge2);
363
+ } else if (issue && issue.type === 'placeholder_mismatch') {
364
+ var inner = document.createElement('div');
365
+ inner.className = 'tooltip-wrap relative inline-flex items-center gap-1.5 px-2 py-1 rounded-md bg-amber-500/10 border border-amber-500/20';
366
+ inner.innerHTML = '<svg class="w-3 h-3 text-amber-400 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01"/></svg>';
367
+ var span3 = document.createElement('span');
368
+ span3.className = 'text-[11px] text-zinc-400 truncate max-w-[200px]';
369
+ span3.textContent = localeValue || '';
370
+ inner.appendChild(span3);
371
+ if (issue.detail) {
372
+ var tip3 = document.createElement('div');
373
+ tip3.className = 'tooltip-box hidden absolute bottom-full left-0 mb-2 px-3 py-2 rounded-lg bg-zinc-900 border border-amber-500/30 text-xs text-zinc-300 max-w-sm whitespace-pre-wrap z-50 shadow-xl pointer-events-none';
374
+ tip3.textContent = issue.detail;
375
+ inner.appendChild(tip3);
376
+ }
377
+ td.appendChild(inner);
378
+ } else if (localeValue) {
379
+ var text = document.createElement('span');
380
+ text.className = 'text-[12px] text-zinc-400 leading-tight line-clamp-2';
381
+ text.textContent = localeValue;
382
+ text.title = localeValue;
383
+ td.appendChild(text);
384
+ } else {
385
+ var dash = document.createElement('span');
386
+ dash.className = 'text-zinc-600';
387
+ dash.textContent = '\u2014';
388
+ td.appendChild(dash);
389
+ }
390
+ } else {
391
+ td.className = 'px-1.5 py-1.5 border-b border-zinc-800/30 text-center';
392
+ td.appendChild(createIconCell(issue));
393
+ }
394
+
395
+ return td;
396
+ }
397
+
398
+ function buildMatrix(result, sectionIdx) {
399
+ var container = document.querySelector('.matrix-container[data-section="' + sectionIdx + '"]');
400
+ if (!container) return;
401
+
402
+ var translations = result.translations || {};
403
+ var refLocale = activeRefLocale && result.allLocales.indexOf(activeRefLocale) !== -1
404
+ ? activeRefLocale
405
+ : result.referenceLocale;
406
+ var refTranslations = translations[refLocale] || {};
407
+
408
+ var issueMap = new Map();
409
+ if (refLocale === result.referenceLocale) {
410
+ result.issues.forEach(function(issue) {
411
+ if (!issueMap.has(issue.key)) issueMap.set(issue.key, new Map());
412
+ issueMap.get(issue.key).set(issue.locale, issue);
413
+ });
414
+ } else {
415
+ var refKeys = Object.keys(refTranslations);
416
+ var refKeySet = new Set(refKeys);
417
+ var PH_RE = /\{(\w+)\}/g;
418
+ result.allLocales.forEach(function(locale) {
419
+ if (locale === refLocale) return;
420
+ var lt = translations[locale] || {};
421
+ var ltKeys = new Set(Object.keys(lt));
422
+ refKeys.forEach(function(key) {
423
+ if (!ltKeys.has(key)) {
424
+ if (!issueMap.has(key)) issueMap.set(key, new Map());
425
+ issueMap.get(key).set(locale, { locale: locale, key: key, type: 'missing', referenceValue: refTranslations[key] });
426
+ } else {
427
+ var refVal = refTranslations[key];
428
+ var locVal = lt[key];
429
+ var refPh = new Set(); var locPh = new Set();
430
+ var m;
431
+ PH_RE.lastIndex = 0;
432
+ while ((m = PH_RE.exec(refVal)) !== null) refPh.add(m[1]);
433
+ PH_RE.lastIndex = 0;
434
+ while ((m = PH_RE.exec(locVal)) !== null) locPh.add(m[1]);
435
+ if (refPh.size > 0 || locPh.size > 0) {
436
+ var missingPh = Array.from(refPh).filter(function(p) { return !locPh.has(p); });
437
+ var extraPh = Array.from(locPh).filter(function(p) { return !refPh.has(p); });
438
+ if (missingPh.length > 0 || extraPh.length > 0) {
439
+ var parts = [];
440
+ if (missingPh.length) parts.push('missing: {' + missingPh.join('}, {') + '}');
441
+ if (extraPh.length) parts.push('extra: {' + extraPh.join('}, {') + '}');
442
+ if (!issueMap.has(key)) issueMap.set(key, new Map());
443
+ issueMap.get(key).set(locale, { locale: locale, key: key, type: 'placeholder_mismatch', detail: parts.join('; '), referenceValue: refVal, localeValue: locVal });
444
+ }
445
+ }
446
+ }
447
+ });
448
+ Object.keys(lt).forEach(function(key) {
449
+ if (!refKeySet.has(key)) {
450
+ if (!issueMap.has(key)) issueMap.set(key, new Map());
451
+ issueMap.get(key).set(locale, { locale: locale, key: key, type: 'extra', localeValue: lt[key] });
452
+ }
453
+ });
454
+ });
455
+ }
456
+
457
+ var allKeysSet = new Set(Object.keys(refTranslations));
458
+ for (var issEntry of issueMap) { allKeysSet.add(issEntry[0]); }
459
+ var allKeys = Array.from(allKeysSet).sort();
460
+
461
+ var filteredKeys = allKeys;
462
+
463
+ if (searchQuery) {
464
+ filteredKeys = filteredKeys.filter(function(k) { return k.toLowerCase().includes(searchQuery); });
465
+ }
466
+
467
+ if (activeTypeFilter === 'issues_only') {
468
+ filteredKeys = filteredKeys.filter(function(k) { return issueMap.has(k); });
469
+ } else if (activeTypeFilter === 'missing' || activeTypeFilter === 'extra' || activeTypeFilter === 'placeholder_mismatch') {
470
+ filteredKeys = filteredKeys.filter(function(k) {
471
+ var locales = issueMap.get(k);
472
+ if (!locales) return false;
473
+ for (var e of locales) {
474
+ if (e[1].type === activeTypeFilter) return true;
475
+ }
476
+ return false;
477
+ });
478
+ }
479
+
480
+ var nonRefLocales = result.allLocales.filter(function(l) { return l !== refLocale; });
481
+ var displayLocales;
482
+ if (activeLocales.size > 0) {
483
+ displayLocales = nonRefLocales.filter(function(l) { return activeLocales.has(l); });
484
+ } else {
485
+ displayLocales = nonRefLocales;
486
+ }
487
+
488
+ if (hideTranslated) {
489
+ filteredKeys = filteredKeys.filter(function(k) {
490
+ var keyIssues = issueMap.get(k);
491
+ if (!keyIssues) return false;
492
+ for (var i = 0; i < displayLocales.length; i++) {
493
+ var issue = keyIssues.get(displayLocales[i]);
494
+ if (issue && issue.type === 'missing') return true;
495
+ }
496
+ return false;
497
+ });
498
+ }
499
+
500
+ if (filteredKeys.length === 0) {
501
+ container.textContent = '';
502
+ var empty = document.createElement('div');
503
+ empty.className = 'py-8 text-center text-zinc-500 text-sm';
504
+ empty.textContent = 'No matching keys';
505
+ container.appendChild(empty);
506
+ return;
507
+ }
508
+
509
+ var table = document.createElement('table');
510
+ table.className = 'matrix-table w-full text-xs';
511
+
512
+ var thead = document.createElement('thead');
513
+ var headRow = document.createElement('tr');
514
+
515
+ var colMinW = showValues ? 'min-w-[200px] max-w-[300px]' : 'min-w-[60px]';
516
+
517
+ var thKey = document.createElement('th');
518
+ thKey.className = 'sticky left-0 top-0 z-30 bg-surface-1 text-left px-4 py-2.5 text-[11px] uppercase tracking-wider text-zinc-500 font-medium border-b border-zinc-800/60 min-w-[220px]';
519
+ thKey.textContent = 'Key';
520
+ headRow.appendChild(thKey);
521
+
522
+ var thRef = document.createElement('th');
523
+ thRef.className = 'sticky top-0 z-20 text-left px-2 py-2.5 text-[11px] uppercase tracking-wider font-medium border-b border-zinc-800/60 text-blue-400/70 bg-blue-500/[0.03] ' + (showValues ? 'min-w-[200px] max-w-[300px]' : 'min-w-[100px]');
524
+ thRef.textContent = refLocale + ' (ref)';
525
+ headRow.appendChild(thRef);
526
+
527
+ displayLocales.forEach(function(locale) {
528
+ var th = document.createElement('th');
529
+ th.className = 'sticky top-0 z-20 bg-surface-1 text-left px-2 py-2.5 text-[11px] uppercase tracking-wider text-zinc-500 font-medium border-b border-zinc-800/60 ' + colMinW;
530
+ th.textContent = locale;
531
+ headRow.appendChild(th);
532
+ });
533
+
534
+ thead.appendChild(headRow);
535
+ table.appendChild(thead);
536
+
537
+ var tbody = document.createElement('tbody');
538
+
539
+ filteredKeys.forEach(function(key, ki) {
540
+ var keyIssues = issueMap.get(key);
541
+ var tr = document.createElement('tr');
542
+ var hasIssue = !!keyIssues;
543
+ tr.className = (ki % 2 === 0 ? 'bg-transparent' : 'bg-white/[0.01]') + ' hover:bg-zinc-800/30 transition-colors';
544
+
545
+ var tdKey = document.createElement('td');
546
+ tdKey.className = 'sticky left-0 z-10 bg-surface-1 px-4 py-1.5 font-mono text-zinc-300 border-b border-zinc-800/30 truncate max-w-[300px] ' + (hasIssue ? 'text-zinc-100' : '');
547
+ tdKey.title = key;
548
+ tdKey.textContent = key;
549
+ tr.appendChild(tdKey);
550
+
551
+ var tdRef = document.createElement('td');
552
+ tdRef.className = 'px-2 py-1.5 border-b border-zinc-800/30 bg-blue-500/[0.03] align-top ' + (showValues ? 'max-w-[300px]' : '');
553
+ var refVal = refTranslations[key];
554
+ if (refVal) {
555
+ var refSpan = document.createElement('span');
556
+ refSpan.className = 'text-[12px] text-blue-300/70 leading-tight line-clamp-2';
557
+ refSpan.textContent = refVal;
558
+ refSpan.title = refVal;
559
+ tdRef.appendChild(refSpan);
560
+ } else {
561
+ var dash = document.createElement('span');
562
+ dash.className = 'text-zinc-600';
563
+ dash.textContent = '\u2014';
564
+ tdRef.appendChild(dash);
565
+ }
566
+ tr.appendChild(tdRef);
567
+
568
+ displayLocales.forEach(function(locale) {
569
+ var localeTranslations = translations[locale] || {};
570
+ var localeValue = localeTranslations[key] || null;
571
+ var issue = keyIssues ? (keyIssues.get(locale) || null) : null;
572
+ tr.appendChild(createCell(localeValue, issue));
573
+ });
574
+
575
+ tbody.appendChild(tr);
576
+ });
577
+
578
+ table.appendChild(tbody);
579
+ container.textContent = '';
580
+ container.appendChild(table);
581
+ }
582
+
583
+ function renderAllMatrices() {
584
+ __DATA__.results.forEach(function(result, idx) {
585
+ buildMatrix(result, idx);
586
+ });
587
+ }
588
+
589
+ renderAllMatrices();
590
+ <\/script>
591
+ </body>
592
+ </html>`;
593
+ }
594
+ //# sourceMappingURL=ui.js.map
package/dist/ui.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@another-art/i18n-checker",
3
+ "version": "0.0.1",
4
+ "description": "Scan i18n translation files for missing keys, extra keys, and placeholder mismatches",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "i18n-checker": "dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "check": "bun run src/cli.ts",
23
+ "prepublishOnly": "bun run build"
24
+ },
25
+ "keywords": [
26
+ "i18n",
27
+ "internationalization",
28
+ "translation",
29
+ "checker",
30
+ "missing-keys",
31
+ "locale"
32
+ ],
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^25.3.3",
39
+ "typescript": "^5.9.3"
40
+ }
41
+ }