an explicit per-line exemption, and command hq keyed (383 -> 344)
Adds `// i18n-exempt: <reason>`, which exempts the next line. The reason is mandatory -- the regex will not match a bare marker -- so somebody has to write down why, and `grep -rn i18n-exempt` lists every one for review. It exists because CommandHQ's startBrief is a prompt written FOR THE MODEL: the person sees a short displayText instead, and translating it would change how the agent behaves. No syntactic rule can tell that from prose, and the alternative was a seventh scanner heuristic. <Untranslated> covers JSX, this covers everything else, and together they should mean the scanner needs no further special cases. The new-commander guide was module-level data whose `body` paragraphs the scanner never saw -- eight paragraphs of real user-facing prose. It carries titleKey and bodyKeys now and the renderer resolves them. Four implicit-return arrows needed bodies to hold the hook.
This commit is contained in:
@@ -126,6 +126,21 @@ const stripComments = (source: string): string =>
|
||||
const stripCodeElements = (source: string): string =>
|
||||
source.replace(/<(code|kbd|pre|samp)\b[^>]*>[\s\S]*?<\/\1>/g, '<$1/>');
|
||||
|
||||
/**
|
||||
* An explicit, per-line opt-out: `// i18n-exempt: <reason>` exempts the NEXT line.
|
||||
*
|
||||
* For the cases no syntactic rule can reach — chiefly a prompt written for a model rather than a
|
||||
* reader. `<Untranslated>` covers JSX; this covers the rest, and the two together mean the scanner does
|
||||
* not need another heuristic every time a new shape of never-translate string turns up.
|
||||
*
|
||||
* A reason is mandatory: the regex will not match a bare `// i18n-exempt`. That is the whole safeguard —
|
||||
* somebody has to write down why, and `grep -rn "i18n-exempt"` lists every one for review.
|
||||
*/
|
||||
const stripExemptLines = (source: string): string => {
|
||||
const lines = source.split('\n');
|
||||
return lines.map((line, i) => (/\/\/\s*i18n-exempt:\s*\S/.test(lines[i - 1] ?? '') ? '' : line)).join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* `<Untranslated reason="…">` marks text that must stay in one language — today, a label quoted from
|
||||
* another product's UI, which docs/localization.md classifies as an identifier.
|
||||
@@ -169,7 +184,8 @@ function scan(root: string): { files: number; hits: Hit[] } {
|
||||
files++;
|
||||
let source: string;
|
||||
try {
|
||||
source = stripUntranslated(stripCodeElements(stripComments(readFileSync(file, 'utf8'))));
|
||||
// Exempt lines go FIRST: stripComments would remove the marker they depend on.
|
||||
source = stripUntranslated(stripCodeElements(stripComments(stripExemptLines(readFileSync(file, 'utf8')))));
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user