From c6e5e1faa78d317caa8ea9bc57e5a72c67135551 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sat, 28 Feb 2026 23:06:56 +0900 Subject: [PATCH 1/5] feat(i18n): add `en.meta.json` generation script --- package.json | 3 + pnpm-lock.yaml | 11 ++ scripts/i18n-meta/cli.ts | 20 +++ scripts/i18n-meta/git-utils.ts | 58 +++++++++ scripts/i18n-meta/types.d.ts | 16 +++ scripts/i18n-meta/update-en-meta-json.ts | 62 +++++++++ scripts/tsconfig.json | 16 +++ .../scripts/generate-en-meta-json.spec.ts | 118 ++++++++++++++++++ vitest.config.ts | 1 + 9 files changed, 305 insertions(+) create mode 100644 scripts/i18n-meta/cli.ts create mode 100644 scripts/i18n-meta/git-utils.ts create mode 100644 scripts/i18n-meta/types.d.ts create mode 100644 scripts/i18n-meta/update-en-meta-json.ts create mode 100644 scripts/tsconfig.json create mode 100644 test/unit/scripts/generate-en-meta-json.spec.ts diff --git a/package.json b/package.json index 3904c2a1f..1561ac776 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "i18n:report": "node scripts/find-invalid-translations.ts", "i18n:report:fix": "node scripts/remove-unused-translations.ts", "i18n:schema": "node scripts/generate-i18n-schema.ts", + "i18n:meta:update-en-meta": "node scripts/i18n-meta/cli.ts update-en-meta", "knip": "knip", "knip:fix": "knip --fix", "lint": "oxlint && oxfmt --check", @@ -126,6 +127,7 @@ "@storybook/addon-a11y": "catalog:storybook", "@storybook/addon-docs": "catalog:storybook", "@storybook/addon-themes": "catalog:storybook", + "@types/dot-object": "2.1.6", "@types/node": "24.10.13", "@types/sanitize-html": "2.16.0", "@types/semver": "7.7.1", @@ -136,6 +138,7 @@ "chromatic": "15.1.0", "defu": "6.1.4", "devalue": "5.6.3", + "dot-object": "2.1.5", "eslint-plugin-regexp": "3.0.0", "fast-check": "4.5.3", "h3": "1.15.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff517ba80..1aa7a4abc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -250,6 +250,9 @@ importers: '@storybook/addon-themes': specifier: catalog:storybook version: 10.2.13(storybook@10.2.13(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)) + '@types/dot-object': + specifier: 2.1.6 + version: 2.1.6 '@types/node': specifier: 24.10.13 version: 24.10.13 @@ -277,6 +280,9 @@ importers: devalue: specifier: 5.6.3 version: 5.6.3 + dot-object: + specifier: 2.1.5 + version: 2.1.5 eslint-plugin-regexp: specifier: 3.0.0 version: 3.0.0(eslint@9.39.2(jiti@2.6.1)) @@ -4626,6 +4632,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/dot-object@2.1.6': + resolution: {integrity: sha512-G1e4SNPOuO72ZXv7wz/W2x29CzQtpxko3G9hBiHqGg/AvFIKoArCs2nbc/WPXnnUkO+1dmvX9WQCyj5gIlAzZg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -15364,6 +15373,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/dot-object@2.1.6': {} + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 diff --git a/scripts/i18n-meta/cli.ts b/scripts/i18n-meta/cli.ts new file mode 100644 index 000000000..f77631ba9 --- /dev/null +++ b/scripts/i18n-meta/cli.ts @@ -0,0 +1,20 @@ +import { parseArgs } from 'node:util' +import { updateEnMetaJson } from './update-en-meta-json.ts' + +function showHelp() { + const scriptName = process.env.npm_lifecycle_event || 'node scripts/i18n-meta/cli.ts' + console.log(`Usage: pnpm run ${scriptName} update-en-meta`) +} + +function main() { + const { positionals } = parseArgs({ allowPositionals: true }) + + if (positionals[0] !== 'update-en-meta') { + showHelp() + return + } + + updateEnMetaJson() +} + +main() diff --git a/scripts/i18n-meta/git-utils.ts b/scripts/i18n-meta/git-utils.ts new file mode 100644 index 000000000..149319e62 --- /dev/null +++ b/scripts/i18n-meta/git-utils.ts @@ -0,0 +1,58 @@ +import { execSync } from 'node:child_process' +import * as fs from 'node:fs' +import type { EnJson, EnMetaJson } from './types.d.ts' + +export function git(command: string) { + try { + return execSync(`git ${command}`, { encoding: 'utf-8' }).trim() + } catch { + console.error(`🚨 Git command failed: git ${command}`) + return null + } +} + +function readJson(filePath: string): T { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as T +} + +export function getCurrentCommitHash() { + return git('rev-parse HEAD') +} + +export function getNewEnJson(enJsonPath: string): EnJson { + if (fs.existsSync(enJsonPath)) { + return readJson(enJsonPath) + } + return {} as EnMetaJson +} + +export function getOldEnMetaJson(enMetaJsonPath: string): EnMetaJson { + if (fs.existsSync(enMetaJsonPath)) { + return readJson(enMetaJsonPath) + } + return {} as EnMetaJson +} + +function omitMeta(obj: T): Omit { + const { $meta: _, ...rest } = obj as T & { $meta?: unknown } + return rest +} + +export function checkTranslationChanges(oldMeta: EnMetaJson, newMeta: EnMetaJson): boolean { + const oldObj = omitMeta(oldMeta) + const newObj = omitMeta(newMeta) + return JSON.stringify(oldObj) !== JSON.stringify(newObj) +} + +export function createUpdatedEnMetaJson( + commitHash: string | null, + content: EnMetaJson, +): EnMetaJson { + return { + $meta: { + last_updated_commit: commitHash, + updated_at: new Date().toISOString(), + }, + ...content, + } as EnMetaJson +} diff --git a/scripts/i18n-meta/types.d.ts b/scripts/i18n-meta/types.d.ts new file mode 100644 index 000000000..7f94375f2 --- /dev/null +++ b/scripts/i18n-meta/types.d.ts @@ -0,0 +1,16 @@ +export type EnJson = { + [key: string]: string | EnJson +} + +export type MetaEntry = { + text: string + commit: string +} + +export type EnMetaJson = { + $meta?: { + last_updated_commit: string + updated_at: string + } + [key: string]: string | MetaEntry | EnMetaJson +} diff --git a/scripts/i18n-meta/update-en-meta-json.ts b/scripts/i18n-meta/update-en-meta-json.ts new file mode 100644 index 000000000..6ee219d59 --- /dev/null +++ b/scripts/i18n-meta/update-en-meta-json.ts @@ -0,0 +1,62 @@ +import { writeFileSync } from 'node:fs' +import { resolve } from 'node:path' +import dot from 'dot-object' +import { + checkTranslationChanges, + createUpdatedEnMetaJson, + getCurrentCommitHash, + getNewEnJson, + getOldEnMetaJson, +} from './git-utils.ts' +import type { EnJson, EnMetaJson, MetaEntry } from './types.d.ts' + +const enJsonPath = resolve('i18n/locales/en.json') +const enMetaJsonPath = resolve('i18n/locales/en.meta.json') + +/** + * Update a metadata JSON file for English translations. + */ +export function updateEnMetaJson() { + const newEnJson = getNewEnJson(enJsonPath) + const oldEnMetaJson = getOldEnMetaJson(enMetaJsonPath) + + const currentCommitHash = getCurrentCommitHash() + const enMetaJson = currentCommitHash + ? makeEnMetaJson(newEnJson, oldEnMetaJson, currentCommitHash) + : ({} as EnMetaJson) + + const hasChanges = checkTranslationChanges(oldEnMetaJson, enMetaJson) + if (!hasChanges) { + console.info('â„šī¸ No translation changes – en.meta.json left untouched') + return + } + + const finalMeta = createUpdatedEnMetaJson(currentCommitHash, enMetaJson) + + writeFileSync(enMetaJsonPath, JSON.stringify(finalMeta, null, 2) + '\n', 'utf-8') + console.log(`✅ Updated en.meta.json – last_updated_commit: ${currentCommitHash}`) +} + +export function makeEnMetaJson( + newEnJson: EnJson, + oldMetaEnJson: EnMetaJson, + latestCommitHash: string, +): EnMetaJson { + const newFlat = dot.dot(newEnJson) as Record + const oldMetaFlat = dot.dot(oldMetaEnJson) as Record + const metaFlat: Record = {} + + for (const key in newFlat) { + const newText = newFlat[key] + + const lastSeenText = oldMetaFlat[`${key}.text`] + const lastCommit = oldMetaFlat[`${key}.commit`] + + if (newText === lastSeenText) { + metaFlat[key] = { text: newText, commit: lastCommit ?? latestCommitHash } + } else { + metaFlat[key] = { text: newText, commit: latestCommitHash } + } + } + return dot.object(metaFlat) as EnMetaJson +} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 000000000..a2412111e --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "nodenext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "noEmit": true, + "allowImportingTsExtensions": true, + "declaration": true, + "types": ["node"], + "declarationMap": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/test/unit/scripts/generate-en-meta-json.spec.ts b/test/unit/scripts/generate-en-meta-json.spec.ts new file mode 100644 index 000000000..7170bad62 --- /dev/null +++ b/test/unit/scripts/generate-en-meta-json.spec.ts @@ -0,0 +1,118 @@ +import { describe, expect, it } from 'vitest' +import { makeEnMetaJson } from '#scripts/i18n-meta/update-en-meta-json' + +describe('Create en.meta.json', () => { + it('should handle an empty en.json file', () => { + const newEnJson = {} + const oldEnMetaJson = {} + + const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + expect(enMetaJson).toEqual({}) + }) + + it('should generate en.meta.json correctly for an initial en.json', () => { + const newEnJson = { + tagline: 'npmx - a fast, modern browser for the npm registry', + } + const oldEnMetaJson = {} + + const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + expect(enMetaJson).toEqual({ + tagline: { + text: 'npmx - a fast, modern browser for the npm registry', + commit: 'sha-new-12345', + }, + }) + }) + + it('should update existing keys and add new keys with the latest commit hash', () => { + const newEnJson = { + name: 'npmx', + tagline: 'npmx - a fast, modern browser for the npm registry', + description: 'Search, browse, and explore packages with a modern interface.', + } + const oldEnMetaJson = { + name: { + text: 'npmx', + commit: 'sha-old-12345', + }, + tagline: { + text: 'npmx - a better browser for the npm registry', + commit: 'sha-old-12345', + }, + } + + const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + expect(enMetaJson).toEqual({ + name: { + text: 'npmx', + commit: 'sha-old-12345', + }, + tagline: { + text: 'npmx - a fast, modern browser for the npm registry', + commit: 'sha-new-12345', + }, + description: { + text: 'Search, browse, and explore packages with a modern interface.', + commit: 'sha-new-12345', + }, + }) + }) + + it('should remove keys that are no longer in en.json', () => { + const newEnJson = { + tagline: 'npmx - a fast, modern browser for the npm registry', + } + const oldEnMetaJson = { + tagline: { + text: 'npmx - a fast, modern browser for the npm registry', + commit: 'sha-old-12345', + }, + toBeRemoved: { text: 'This will be gone', commit: 'sha-old-12345' }, + } + + const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + expect(enMetaJson).toEqual({ + tagline: { + text: 'npmx - a fast, modern browser for the npm registry', + commit: 'sha-old-12345', + }, + }) + }) + + it('should handle complex nested structures', () => { + const newEnJson = { + a: { + b: 'updated-value', + }, + c: 'value-c', + d: 'added-value', + } + + const oldEnMetaJson = { + a: { + b: { + text: 'value-b', + commit: 'sha-old-12345', + }, + }, + c: { + text: 'value-c', + commit: 'sha-old-12345', + }, + d: { + text: 'added-value', + commit: 'sha-new-12345', + }, + } + + const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + expect(enMetaJson).toEqual({ + a: { + b: { text: 'updated-value', commit: 'sha-new-12345' }, + }, + c: { text: 'value-c', commit: 'sha-old-12345' }, + d: { text: 'added-value', commit: 'sha-new-12345' }, + }) + }) +}) diff --git a/vitest.config.ts b/vitest.config.ts index ba3986903..b28e34c46 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -13,6 +13,7 @@ export default defineConfig({ alias: { '#shared': `${rootDir}/shared`, '#server': `${rootDir}/server`, + '#scripts': `${rootDir}/scripts`, }, }, test: { From 8f1acd78a72617c9063575f29129656ed00213cd Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sun, 1 Mar 2026 18:31:01 +0900 Subject: [PATCH 2/5] ci: add `i18n-meta` workflow to update `en.meta.json` --- .github/workflows/i18n-meta.yml | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/i18n-meta.yml diff --git a/.github/workflows/i18n-meta.yml b/.github/workflows/i18n-meta.yml new file mode 100644 index 000000000..ab836e238 --- /dev/null +++ b/.github/workflows/i18n-meta.yml @@ -0,0 +1,48 @@ +name: i18n-meta + +on: + push: + branches: + - main + paths: + - 'i18n/locales/en.json' + +permissions: + contents: write + +jobs: + update: + name: 🌐 Update en.meta.json + runs-on: ubuntu-24.04-arm + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 2 + + - uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 + with: + node-version: lts/* + + - uses: pnpm/action-setup@1e1c8eafbd745f64b1ef30a7d7ed7965034c486c # 1e1c8eafbd745f64b1ef30a7d7ed7965034c486c + name: 🟧 Install pnpm + with: + cache: true + + - name: đŸ“Ļ Install dependencies + run: pnpm install + + - name: 🌐 Update i18n metadata + run: pnpm i18n:meta:update-en-meta + + - name: âŦ†ī¸Ž Commit and Push changes + run: | + git config --global user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add i18n/locales/en.meta.json + if ! git diff --cached --quiet; then + git commit -m 'chore(i18n): update `en.meta.json` [skip ci]' + git push + else + echo "No changes in en.meta.json, skipping commit." + fi From 7ce664d568f46b12879fe5871e6e3b4290e5dc97 Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sun, 1 Mar 2026 19:13:29 +0900 Subject: [PATCH 3/5] refactor: swap makeEnMetaJson() args order --- scripts/i18n-meta/update-en-meta-json.ts | 4 +- .../scripts/generate-en-meta-json.spec.ts | 45 +++++++++---------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/scripts/i18n-meta/update-en-meta-json.ts b/scripts/i18n-meta/update-en-meta-json.ts index 6ee219d59..f3f2e630f 100644 --- a/scripts/i18n-meta/update-en-meta-json.ts +++ b/scripts/i18n-meta/update-en-meta-json.ts @@ -22,7 +22,7 @@ export function updateEnMetaJson() { const currentCommitHash = getCurrentCommitHash() const enMetaJson = currentCommitHash - ? makeEnMetaJson(newEnJson, oldEnMetaJson, currentCommitHash) + ? makeEnMetaJson(oldEnMetaJson, newEnJson, currentCommitHash) : ({} as EnMetaJson) const hasChanges = checkTranslationChanges(oldEnMetaJson, enMetaJson) @@ -38,8 +38,8 @@ export function updateEnMetaJson() { } export function makeEnMetaJson( - newEnJson: EnJson, oldMetaEnJson: EnMetaJson, + newEnJson: EnJson, latestCommitHash: string, ): EnMetaJson { const newFlat = dot.dot(newEnJson) as Record diff --git a/test/unit/scripts/generate-en-meta-json.spec.ts b/test/unit/scripts/generate-en-meta-json.spec.ts index 7170bad62..f044cb1db 100644 --- a/test/unit/scripts/generate-en-meta-json.spec.ts +++ b/test/unit/scripts/generate-en-meta-json.spec.ts @@ -3,20 +3,20 @@ import { makeEnMetaJson } from '#scripts/i18n-meta/update-en-meta-json' describe('Create en.meta.json', () => { it('should handle an empty en.json file', () => { - const newEnJson = {} const oldEnMetaJson = {} + const newEnJson = {} - const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + const enMetaJson = makeEnMetaJson(oldEnMetaJson, newEnJson, 'sha-new-12345') expect(enMetaJson).toEqual({}) }) it('should generate en.meta.json correctly for an initial en.json', () => { + const oldEnMetaJson = {} const newEnJson = { tagline: 'npmx - a fast, modern browser for the npm registry', } - const oldEnMetaJson = {} - const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + const enMetaJson = makeEnMetaJson(oldEnMetaJson, newEnJson, 'sha-new-12345') expect(enMetaJson).toEqual({ tagline: { text: 'npmx - a fast, modern browser for the npm registry', @@ -26,11 +26,6 @@ describe('Create en.meta.json', () => { }) it('should update existing keys and add new keys with the latest commit hash', () => { - const newEnJson = { - name: 'npmx', - tagline: 'npmx - a fast, modern browser for the npm registry', - description: 'Search, browse, and explore packages with a modern interface.', - } const oldEnMetaJson = { name: { text: 'npmx', @@ -41,8 +36,13 @@ describe('Create en.meta.json', () => { commit: 'sha-old-12345', }, } + const newEnJson = { + name: 'npmx', + tagline: 'npmx - a fast, modern browser for the npm registry', + description: 'Search, browse, and explore packages with a modern interface.', + } - const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + const enMetaJson = makeEnMetaJson(oldEnMetaJson, newEnJson, 'sha-new-12345') expect(enMetaJson).toEqual({ name: { text: 'npmx', @@ -60,9 +60,6 @@ describe('Create en.meta.json', () => { }) it('should remove keys that are no longer in en.json', () => { - const newEnJson = { - tagline: 'npmx - a fast, modern browser for the npm registry', - } const oldEnMetaJson = { tagline: { text: 'npmx - a fast, modern browser for the npm registry', @@ -70,8 +67,11 @@ describe('Create en.meta.json', () => { }, toBeRemoved: { text: 'This will be gone', commit: 'sha-old-12345' }, } + const newEnJson = { + tagline: 'npmx - a fast, modern browser for the npm registry', + } - const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + const enMetaJson = makeEnMetaJson(oldEnMetaJson, newEnJson, 'sha-new-12345') expect(enMetaJson).toEqual({ tagline: { text: 'npmx - a fast, modern browser for the npm registry', @@ -81,14 +81,6 @@ describe('Create en.meta.json', () => { }) it('should handle complex nested structures', () => { - const newEnJson = { - a: { - b: 'updated-value', - }, - c: 'value-c', - d: 'added-value', - } - const oldEnMetaJson = { a: { b: { @@ -105,8 +97,15 @@ describe('Create en.meta.json', () => { commit: 'sha-new-12345', }, } + const newEnJson = { + a: { + b: 'updated-value', + }, + c: 'value-c', + d: 'added-value', + } - const enMetaJson = makeEnMetaJson(newEnJson, oldEnMetaJson, 'sha-new-12345') + const enMetaJson = makeEnMetaJson(oldEnMetaJson, newEnJson, 'sha-new-12345') expect(enMetaJson).toEqual({ a: { b: { text: 'updated-value', commit: 'sha-new-12345' }, From e6e07010eaa819668c217d75f96b8673d089a45c Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Sun, 1 Mar 2026 21:03:01 +0900 Subject: [PATCH 4/5] refactor: mitigate coderabbit concerns and refine codes --- scripts/i18n-meta/update-en-meta-json.ts | 6 +-- scripts/i18n-meta/{git-utils.ts => utils.ts} | 42 ++++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) rename scripts/i18n-meta/{git-utils.ts => utils.ts} (91%) diff --git a/scripts/i18n-meta/update-en-meta-json.ts b/scripts/i18n-meta/update-en-meta-json.ts index f3f2e630f..84f98e0f0 100644 --- a/scripts/i18n-meta/update-en-meta-json.ts +++ b/scripts/i18n-meta/update-en-meta-json.ts @@ -7,7 +7,7 @@ import { getCurrentCommitHash, getNewEnJson, getOldEnMetaJson, -} from './git-utils.ts' +} from './utils.ts' import type { EnJson, EnMetaJson, MetaEntry } from './types.d.ts' const enJsonPath = resolve('i18n/locales/en.json') @@ -31,9 +31,9 @@ export function updateEnMetaJson() { return } - const finalMeta = createUpdatedEnMetaJson(currentCommitHash, enMetaJson) + const updatedEnMetaJson = createUpdatedEnMetaJson(currentCommitHash, enMetaJson) - writeFileSync(enMetaJsonPath, JSON.stringify(finalMeta, null, 2) + '\n', 'utf-8') + writeFileSync(enMetaJsonPath, JSON.stringify(updatedEnMetaJson, null, 2) + '\n', 'utf-8') console.log(`✅ Updated en.meta.json – last_updated_commit: ${currentCommitHash}`) } diff --git a/scripts/i18n-meta/git-utils.ts b/scripts/i18n-meta/utils.ts similarity index 91% rename from scripts/i18n-meta/git-utils.ts rename to scripts/i18n-meta/utils.ts index 149319e62..4160d8646 100644 --- a/scripts/i18n-meta/git-utils.ts +++ b/scripts/i18n-meta/utils.ts @@ -2,28 +2,15 @@ import { execSync } from 'node:child_process' import * as fs from 'node:fs' import type { EnJson, EnMetaJson } from './types.d.ts' -export function git(command: string) { - try { - return execSync(`git ${command}`, { encoding: 'utf-8' }).trim() - } catch { - console.error(`🚨 Git command failed: git ${command}`) - return null - } -} - -function readJson(filePath: string): T { - return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as T -} - export function getCurrentCommitHash() { return git('rev-parse HEAD') } export function getNewEnJson(enJsonPath: string): EnJson { if (fs.existsSync(enJsonPath)) { - return readJson(enJsonPath) + return readJson(enJsonPath) } - return {} as EnMetaJson + return {} as EnJson } export function getOldEnMetaJson(enMetaJsonPath: string): EnMetaJson { @@ -33,11 +20,6 @@ export function getOldEnMetaJson(enMetaJsonPath: string): EnMetaJson { return {} as EnMetaJson } -function omitMeta(obj: T): Omit { - const { $meta: _, ...rest } = obj as T & { $meta?: unknown } - return rest -} - export function checkTranslationChanges(oldMeta: EnMetaJson, newMeta: EnMetaJson): boolean { const oldObj = omitMeta(oldMeta) const newObj = omitMeta(newMeta) @@ -53,6 +35,24 @@ export function createUpdatedEnMetaJson( last_updated_commit: commitHash, updated_at: new Date().toISOString(), }, - ...content, + ...omitMeta(content), } as EnMetaJson } + +function git(command: string) { + try { + return execSync(`git ${command}`, { encoding: 'utf-8' }).trim() + } catch { + console.error(`🚨 Git command failed: git ${command}`) + return null + } +} + +function readJson(filePath: string): T { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as T +} + +function omitMeta(obj: T): Omit { + const { $meta: _, ...rest } = obj as T & { $meta?: unknown } + return rest +} From 8d6b2a6f6fed6f25df3f9b23bb60a453e14f8dbc Mon Sep 17 00:00:00 2001 From: TAKAHASHI Shuuji Date: Mon, 2 Mar 2026 15:05:59 +0900 Subject: [PATCH 5/5] fix: correct null handling --- scripts/i18n-meta/update-en-meta-json.ts | 8 +++++--- scripts/i18n-meta/utils.ts | 7 ++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scripts/i18n-meta/update-en-meta-json.ts b/scripts/i18n-meta/update-en-meta-json.ts index 84f98e0f0..67e3ceb82 100644 --- a/scripts/i18n-meta/update-en-meta-json.ts +++ b/scripts/i18n-meta/update-en-meta-json.ts @@ -21,9 +21,11 @@ export function updateEnMetaJson() { const oldEnMetaJson = getOldEnMetaJson(enMetaJsonPath) const currentCommitHash = getCurrentCommitHash() - const enMetaJson = currentCommitHash - ? makeEnMetaJson(oldEnMetaJson, newEnJson, currentCommitHash) - : ({} as EnMetaJson) + if (!currentCommitHash) { + console.error('❌ Commit hash missing. Skipping update to protect existing metadata.') + process.exit(1) + } + const enMetaJson = makeEnMetaJson(oldEnMetaJson, newEnJson, currentCommitHash) const hasChanges = checkTranslationChanges(oldEnMetaJson, enMetaJson) if (!hasChanges) { diff --git a/scripts/i18n-meta/utils.ts b/scripts/i18n-meta/utils.ts index 4160d8646..119ac5449 100644 --- a/scripts/i18n-meta/utils.ts +++ b/scripts/i18n-meta/utils.ts @@ -26,17 +26,14 @@ export function checkTranslationChanges(oldMeta: EnMetaJson, newMeta: EnMetaJson return JSON.stringify(oldObj) !== JSON.stringify(newObj) } -export function createUpdatedEnMetaJson( - commitHash: string | null, - content: EnMetaJson, -): EnMetaJson { +export function createUpdatedEnMetaJson(commitHash: string, content: EnMetaJson): EnMetaJson { return { $meta: { last_updated_commit: commitHash, updated_at: new Date().toISOString(), }, ...omitMeta(content), - } as EnMetaJson + } } function git(command: string) {