ui phase 12
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Lints client/messages/fa.json against the banned-orthography-variant rules in
|
||||
* client/messages/STYLE.md — enforces the phase-12 sweep so it cannot silently regress.
|
||||
* Exits non-zero (and prints every offending key) on any match.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, join } from 'node:path';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const FA_MESSAGES_PATH = join(__dirname, '..', 'messages', 'fa.json');
|
||||
|
||||
// Each rule scans the *raw value* of every leaf string in fa.json (flattened key path → value).
|
||||
// `pattern` is a plain substring (not regex) so ZWNJ/hamza characters stay literal and unambiguous.
|
||||
const RULES = [
|
||||
{
|
||||
name: 'brand name must use ZWNJ (بالینیار), never a plain space',
|
||||
pattern: 'بالین یار',
|
||||
},
|
||||
{
|
||||
name: 'تأیید must use hamza — تایید (hamza-less) is banned',
|
||||
pattern: 'تایید',
|
||||
},
|
||||
{
|
||||
name: 'جستجو is the one standard form — جستوجو is banned',
|
||||
pattern: 'جستوجو',
|
||||
},
|
||||
{
|
||||
name: 'جستجو is the one standard form — جست و جو (spaced) is banned',
|
||||
pattern: 'جست و جو',
|
||||
},
|
||||
{
|
||||
name: 'the indefinite «ی» misattachment bug — «بازی» (game) instead of «ی» on the right word',
|
||||
// Word-boundary trap: catches "... بازی " (space after) so it doesn't also flag words that
|
||||
// legitimately contain "بازی" as a substring of something else — in this catalog there are none,
|
||||
// but the trailing-space anchor keeps the check honest if one is ever added on purpose elsewhere.
|
||||
pattern: 'بازی ',
|
||||
},
|
||||
{
|
||||
// Leading space distinguishes the archaic passive auxiliary ("X میگردد" = "is X-ed") from the
|
||||
// unrelated, entirely legitimate verb «برمیگردد» (returns/comes back), which fuses «بر» directly
|
||||
// onto «میگردد» with no space and must never be flagged.
|
||||
name: 'archaic passive میگردد is banned — use میشود',
|
||||
pattern: ' میگردد',
|
||||
},
|
||||
];
|
||||
|
||||
function flattenStrings(value, path, out) {
|
||||
if (typeof value === 'string') {
|
||||
out.push([path, value]);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((item, index) => flattenStrings(item, `${path}[${index}]`, out));
|
||||
return;
|
||||
}
|
||||
if (value && typeof value === 'object') {
|
||||
for (const [key, child] of Object.entries(value)) {
|
||||
flattenStrings(child, path ? `${path}.${key}` : key, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const raw = readFileSync(FA_MESSAGES_PATH, 'utf8');
|
||||
const messages = JSON.parse(raw);
|
||||
const leaves = [];
|
||||
flattenStrings(messages, '', leaves);
|
||||
|
||||
const failures = [];
|
||||
for (const rule of RULES) {
|
||||
for (const [path, value] of leaves) {
|
||||
if (value.includes(rule.pattern)) {
|
||||
failures.push({ rule: rule.name, path, value });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length === 0) {
|
||||
console.log(`check-copy: ${leaves.length} strings checked, 0 banned variants found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`check-copy: found ${failures.length} banned copy variant(s):\n`);
|
||||
for (const failure of failures) {
|
||||
console.error(` [${failure.rule}]`);
|
||||
console.error(` fa.json → ${failure.path}: "${failure.value}"\n`);
|
||||
}
|
||||
process.exitCode = 1;
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user