release: WRNexusJS 0.8.0
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import type { Messages } from "./index.ts";
|
||||
|
||||
export interface ExtractedTranslationKey {
|
||||
key: string;
|
||||
file?: string;
|
||||
offset: number;
|
||||
}
|
||||
|
||||
export function extractTranslationKeys(source: string, file?: string): ExtractedTranslationKey[] {
|
||||
const found = new Map<string, ExtractedTranslationKey>();
|
||||
const patterns = [
|
||||
/(?:\b(?:t|\$t)|\bi18n\.t)\s*\(\s*(["'])([^"']+)\1/g,
|
||||
/\bdata-i18n\s*=\s*(["'])([^"']+)\1/g,
|
||||
/\{t:([A-Za-z0-9_.:-]+)\}/g,
|
||||
];
|
||||
for (const pattern of patterns) {
|
||||
for (const match of source.matchAll(pattern)) {
|
||||
const key = (match[2] ?? match[1])!.trim();
|
||||
if (key && !found.has(key)) found.set(key, { key, file, offset: match.index });
|
||||
}
|
||||
}
|
||||
return [...found.values()].sort((left, right) => left.key.localeCompare(right.key));
|
||||
}
|
||||
|
||||
export function extractTranslationKeysFromFiles(
|
||||
files: Iterable<string>,
|
||||
): ExtractedTranslationKey[] {
|
||||
const found = new Map<string, ExtractedTranslationKey>();
|
||||
for (const file of files) {
|
||||
for (const item of extractTranslationKeys(readFileSync(file, "utf8"), file)) {
|
||||
found.set(`${item.file}:${item.key}`, item);
|
||||
}
|
||||
}
|
||||
return [...found.values()].sort(
|
||||
(left, right) =>
|
||||
String(left.file).localeCompare(String(right.file)) || left.key.localeCompare(right.key),
|
||||
);
|
||||
}
|
||||
|
||||
export function flattenMessageKeys(messages: Messages, prefix = ""): string[] {
|
||||
const keys: string[] = [];
|
||||
for (const [name, value] of Object.entries(messages)) {
|
||||
const key = prefix ? `${prefix}.${name}` : name;
|
||||
if (value && typeof value === "object" && !Array.isArray(value)) {
|
||||
keys.push(...flattenMessageKeys(value as Messages, key));
|
||||
} else keys.push(key);
|
||||
}
|
||||
return keys.sort();
|
||||
}
|
||||
|
||||
export function auditLocaleKeys(
|
||||
messages: Record<string, Messages>,
|
||||
referenceLocale: string,
|
||||
): Record<string, { missing: string[]; extra: string[] }> {
|
||||
const reference = new Set(flattenMessageKeys(messages[referenceLocale] ?? {}));
|
||||
const result: Record<string, { missing: string[]; extra: string[] }> = {};
|
||||
for (const [locale, value] of Object.entries(messages)) {
|
||||
const keys = new Set(flattenMessageKeys(value));
|
||||
result[locale] = {
|
||||
missing: [...reference].filter((key) => !keys.has(key)).sort(),
|
||||
extra: [...keys].filter((key) => !reference.has(key)).sort(),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const ACCENTS: Record<string, string> = {
|
||||
a: "à",
|
||||
e: "ë",
|
||||
i: "ï",
|
||||
o: "ô",
|
||||
u: "ü",
|
||||
A: "À",
|
||||
E: "Ë",
|
||||
I: "Ï",
|
||||
O: "Ô",
|
||||
U: "Ü",
|
||||
};
|
||||
|
||||
export function pseudoLocalize(value: string, options: { rtl?: boolean } = {}): string {
|
||||
const parts = value.split(/(\{[^{}]+\}|<[^>]+>)/g);
|
||||
const transformed = parts
|
||||
.map((part) =>
|
||||
/^\{[^{}]+\}$|^<[^>]+>$/.test(part)
|
||||
? part
|
||||
: part.replace(/[aeiouAEIOU]/g, (character) => ACCENTS[character] ?? character),
|
||||
)
|
||||
.join("");
|
||||
return options.rtl ? `\u202e[${transformed}]\u202c` : `[${transformed}~~~]`;
|
||||
}
|
||||
|
||||
export function createPseudoLocale(messages: Messages, options: { rtl?: boolean } = {}): Messages {
|
||||
const output: Messages = Object.create(null) as Messages;
|
||||
for (const [key, value] of Object.entries(messages)) {
|
||||
output[key] =
|
||||
typeof value === "string"
|
||||
? pseudoLocalize(value, options)
|
||||
: value && typeof value === "object" && !Array.isArray(value)
|
||||
? createPseudoLocale(value as Messages, options)
|
||||
: value;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
Reference in New Issue
Block a user