Files
lobehub/scripts/i18nWorkflow/flattenLocaleKeys.ts
Innei fcdaf9d814 🔧 chore: update eslint v2 configuration and suppressions (#12133)
* v2 init

* chore: update eslint suppressions and package dependencies

- Removed several eslint suppressions related to array sorting and reversing from eslint-suppressions.json to clean up the configuration.
- Updated @lobehub/lint package version from 2.0.0-beta.6 to 2.0.0-beta.7 in package.json for improvements and bug fixes.
- Made minor formatting adjustments in vitest.config.mts and various SKILL.md files for better readability and consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* fix: clean up import statements and formatting

- Removed unnecessary whitespace in replaceComponentImports.ts for improved readability.
- Standardized import statements in contextEngineering.ts and createAgentExecutors.ts by adding missing spaces for consistency.

Signed-off-by: Innei <tukon479@gmail.com>

* chore: update eslint suppressions and clean up code formatting

* 🐛 fix: use vi.hoisted for mock variable initialization

Fix TDZ error in persona service test by using vi.hoisted() to ensure
mock variables are available when vi.mock factory runs.

---------

Signed-off-by: Innei <tukon479@gmail.com>
2026-02-11 13:04:48 +08:00

140 lines
4.1 KiB
TypeScript

import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import prettier from '@prettier/sync';
import { consola } from 'consola';
import { colors } from 'consola/utils';
import { toLodashPath } from '../../src/locales/utils';
import { localeDir, localeDirJsonList, localesDir, srcDefaultLocales } from './const';
const prettierOptions = prettier.resolveConfig(resolve(__dirname, '../../.prettierrc.js')) ?? {};
const DEFAULT_SKIP_FILES = new Set(['index.ts', 'models.ts', 'providers.ts']);
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
if (!value || typeof value !== 'object') return false;
return Object.prototype.toString.call(value) === '[object Object]';
};
const shouldPreserveObject = (value: Record<string, unknown>) => {
const keys = Object.keys(value);
if (keys.length === 0) return true;
return keys.every((key) => /^\d+$/.test(key));
};
const flattenObject = (input: Record<string, unknown>) => {
const output: Record<string, unknown> = {};
const addEntry = (pathSegments: Array<number | string>, value: unknown) => {
const key = toLodashPath(pathSegments);
if (Object.prototype.hasOwnProperty.call(output, key)) {
throw new Error(`Duplicate i18n key detected: ${key}`);
}
output[key] = value;
};
const visit = (value: unknown, pathSegments: Array<number | string>) => {
if (Array.isArray(value)) {
addEntry(pathSegments, value);
return;
}
if (isPlainObject(value)) {
if (shouldPreserveObject(value)) {
addEntry(pathSegments, value);
return;
}
const entries = Object.entries(value);
if (entries.length === 0) {
addEntry(pathSegments, value);
return;
}
for (const [childKey, childValue] of entries) {
visit(childValue, [...pathSegments, childKey]);
}
return;
}
addEntry(pathSegments, value);
};
for (const [key, value] of Object.entries(input)) {
visit(value, [key]);
}
return output;
};
const writeTs = (filePath: string, data: Record<string, unknown>) => {
const content = `export default ${JSON.stringify(data, null, 2)};\n`;
const formatted = prettier.format(content, {
...prettierOptions,
parser: 'typescript',
});
writeFileSync(filePath, formatted, 'utf8');
};
const writeJson = (filePath: string, data: Record<string, unknown>) => {
const json = JSON.stringify(data, null, 2);
const formatted = prettier.format(json, {
...prettierOptions,
parser: 'json',
});
writeFileSync(filePath, formatted, 'utf8');
};
const flattenDefaultLocales = async () => {
const files = readdirSync(srcDefaultLocales).filter((file) => file.endsWith('.ts'));
for (const file of files) {
if (DEFAULT_SKIP_FILES.has(file)) continue;
const filePath = resolve(srcDefaultLocales, file);
const fileUrl = pathToFileURL(filePath).href;
const loaded = await import(fileUrl);
const data = loaded.default ?? loaded;
const flat = flattenObject(data as Record<string, unknown>);
writeTs(filePath, flat);
consola.success(colors.cyan(file), colors.gray('flattened'));
}
};
const flattenLocaleJsons = () => {
const localeFolders = readdirSync(localesDir).filter((dir) =>
statSync(localeDir(dir)).isDirectory(),
);
for (const locale of localeFolders) {
const jsonFiles = localeDirJsonList(locale);
for (const jsonFile of jsonFiles) {
const filePath = resolve(localeDir(locale), jsonFile);
const raw = readFileSync(filePath, 'utf8');
const data = JSON.parse(raw);
const flat = flattenObject(data);
writeJson(filePath, flat);
consola.success(colors.cyan(`${locale}/${jsonFile}`), colors.gray('flattened'));
}
}
};
const run = async () => {
consola.start('Flattening src/locales/default...');
await flattenDefaultLocales();
consola.start('Flattening locales JSON files...');
flattenLocaleJsons();
consola.success('Flattening completed.');
};
run().catch((error) => {
consola.error(error);
process.exit(1);
});