Skip to content

chore(i18n): add en.meta.json generation script - #1761

Open
shuuji3 wants to merge 10 commits into
npmx-dev:mainfrom
shuuji3:feat/generate-en-meta-json
Open

chore(i18n): add en.meta.json generation script#1761
shuuji3 wants to merge 10 commits into
npmx-dev:mainfrom
shuuji3:feat/generate-en-meta-json

Conversation

@shuuji3

@shuuji3shuuji3 commented Feb 28, 2026

Copy link
Copy Markdown
Member

🔗 Linked issue

#1747

🧭 Context

Currently, there is no mechanism to detect changes in en.json. Previously, we had to choose one of the following approaches when English text needed an update:

  • Using a different key: Even a small change requires re-translation for all other languages.
  • Updating the existing text: It is difficult to track which keys were updated, making it hard to sync with other languages.

This first step records metadata, tracking which commit updated each English key, which allows us to monitor changes in the English locale file effectively.

📚 Description

Implemented the first two tasks for automated en.json change detection:

  • Added a new generation script:
    • This script generates or updates en.meta.json alongside en.json.
    • If en.json is changed (added, removed, or modified) since the previous update, the corresponding values in en.meta.json are updated with the latest commit hash and the new string value.
  • Added a GitHub Actions workflow:
    • The script is triggered when a new commit is pushed to the main branch, only when the commit modifies i18n/locales/en.json.
    • This is needed to record a commit hash in main branch history.
      • I also considered and experimented to add auto-commit during the PR or local branch, but it cannot find a reliable commit hash if PR is squash-merged.
      • It could record PR number but I wanted to be git platform agnostic.

The example auto-commit of en.meta.json can be found in this CI test branch: https://github.com/shuuji3/npmx.dev/commits/feat/generate-en-meta-json-ci-test
(Note: In the test branch, I pushed directly to main branch and lunaria/files/* is included. But they are usually merged along with PR, so they should not be included in auto-commit)

You can also check the Vitest file to see how this script records commit hash and text.

@vercel

vercelBot commented Feb 28, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
docs.npmx.devReadyReadyPreview, CommentJul 17, 2026 10:40am
npmx.devReadyReadyPreview, CommentJul 17, 2026 10:40am
1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
npmx-lunariaIgnoredIgnoredJul 17, 2026 10:40am

Request Review

@codecov

codecovBot commented Feb 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@coderabbitai

coderabbitaiBot commented Mar 1, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds tooling and automation to maintain English i18n metadata. Introduces a GitHub Actions workflow that triggers on changes to i18n/locales/en.json and commits updates to i18n/locales/en.meta.json. Adds a CLI, utilities, and a script to compute en.meta.json from en.json (tracking per-key commit hashes and a $meta block), TypeScript types and a scripts tsconfig, an npm script, and unit tests for the metadata generation. Also adds a vitest alias to resolve script imports.

Possibly related issues

  • npmx-dev/npmx.dev issue 1747 — This PR implements en.meta.json generation and CI automation (CLI, utils, types, tests, and workflow) described in the issue.

Possibly related PRs

Suggested reviewers

  • userquin
  • danielroe
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description check✅ PassedThe pull request description is directly related to the changeset. It explains the context, objectives, and implementation details of the i18n metadata generation feature.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
scripts/tsconfig.json (1)

8-12: Minor configuration inconsistency.

The declaration and declarationMap options have no effect when noEmit: true is set, as no output files are generated. Consider removing these redundant options for clarity.

♻️ Suggested fix
 "skipLibCheck": true,
"noEmit": true,
"allowImportingTsExtensions": true,
- "declaration": true,- "types": ["node"],- "declarationMap": true+ "types": ["node"]
scripts/i18n-meta/cli.ts (1)

10-15: Add explicit type guard for array access.

Accessing positionals[0] without a guard is not strictly type-safe. While the current logic works correctly (showing help when undefined), an explicit check aligns better with the coding guidelines.

♻️ Suggested fix
 function main() {
const { positionals } = parseArgs({ allowPositionals: true })
+ const command = positionals[0]- if (positionals[0] !== 'update-en-meta') {+ if (command !== 'update-en-meta') {
showHelp()
return
}
updateEnMetaJson()
}

As per coding guidelines: "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index".

.github/workflows/i18n-meta.yml (1)

38-48: Consider adding error handling for the commit step.

If the pnpm i18n:meta:update-en-meta step fails or exits unexpectedly, the workflow continues to the commit step. Consider adding explicit error handling or using set -e in the shell script.

♻️ Suggested improvement
 - name: ⬆︎ Commit and Push changes
run: |
+ set -e
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
scripts/i18n-meta/update-en-meta-json.ts (1)

23-27: Silent fallback to empty object when no commit hash is available.

If getCurrentCommitHash() returns null or empty, the function silently uses an empty object as metadata. This could mask issues in environments where Git isn't properly configured. Consider logging a warning.

♻️ Suggested improvement
 const currentCommitHash = getCurrentCommitHash()
+ if (!currentCommitHash) {+ console.warn('⚠️ Could not determine current commit hash – metadata will be empty')+ }
const enMetaJson = currentCommitHash
? makeEnMetaJson(oldEnMetaJson, newEnJson, currentCommitHash)
: ({} as EnMetaJson)
scripts/i18n-meta/git-utils.ts (1)

41-44: Use order-insensitive comparison for translation change detection.

At Line 44, JSON.stringify(oldObj) !== JSON.stringify(newObj) is key-order sensitive, so equivalent objects with different key order will be treated as changed.

Suggested fix
+function sortKeysRecursively(value: unknown): unknown {+ if (Array.isArray(value)) {+ return value.map(sortKeysRecursively)+ }+ if (value && typeof value === 'object') {+ return Object.fromEntries(+ Object.entries(value as Record<string, unknown>)+ .sort(([a], [b]) => a.localeCompare(b))+ .map(([k, v]) => [k, sortKeysRecursively(v)]),+ )+ }+ return value+}+
export function checkTranslationChanges(oldMeta: EnMetaJson, newMeta: EnMetaJson): boolean {
const oldObj = omitMeta(oldMeta)
const newObj = omitMeta(newMeta)
- return JSON.stringify(oldObj) !== JSON.stringify(newObj)+ return JSON.stringify(sortKeysRecursively(oldObj)) !== JSON.stringify(sortKeysRecursively(newObj))
}

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f18d64e and 7ce664d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • .github/workflows/i18n-meta.yml
  • package.json
  • scripts/i18n-meta/cli.ts
  • scripts/i18n-meta/git-utils.ts
  • scripts/i18n-meta/types.d.ts
  • scripts/i18n-meta/update-en-meta-json.ts
  • scripts/tsconfig.json
  • test/unit/scripts/generate-en-meta-json.spec.ts
  • vitest.config.ts

Comment threadscripts/i18n-meta/git-utils.ts Outdated
Comment threadscripts/i18n-meta/utils.ts
Comment threadscripts/i18n-meta/utils.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce664d and e6e0701.

📒 Files selected for processing (2)
  • scripts/i18n-meta/update-en-meta-json.ts
  • scripts/i18n-meta/utils.ts

Comment threadscripts/i18n-meta/update-en-meta-json.ts
Comment threadscripts/i18n-meta/utils.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
scripts/i18n-meta/update-en-meta-json.ts (1)

47-61: Consider adding explicit return type annotation for clarity.

The logic is sound: flattening both structures, comparing text values, and preserving or updating commit hashes as appropriate. The nullish coalescing at line 58 correctly handles new keys where lastCommit would be undefined.

One minor note: the as Record<string, string> casts at lines 47-48 are imprecise since accessing a missing key returns undefined at runtime. The current logic handles this correctly, but for stricter type-safety you could use a safer accessor pattern.

♻️ Optional: safer key access pattern
- const lastSeenText = oldMetaFlat[`${key}.text`]- const lastCommit = oldMetaFlat[`${key}.commit`]+ const lastSeenText = oldMetaFlat[`${key}.text`] as string | undefined+ const lastCommit = oldMetaFlat[`${key}.commit`] as string | undefined

This makes the potential undefined explicit, aligning runtime behaviour with the type system.

scripts/i18n-meta/utils.ts (2)

5-7: Add explicit return type annotation for getCurrentCommitHash.

The function can return string | null (from the git helper), but this isn't explicitly declared. Adding the return type improves API clarity for callers.

♻️ Suggested improvement
-export function getCurrentCommitHash() {+export function getCurrentCommitHash(): string | null {
return git('rev-parse HEAD')
}

48-50: Consider wrapping JSON.parse in try-catch for clearer error messages.

If the file contains invalid JSON, the error will propagate with a generic parse error. A wrapper could provide context about which file failed.

♻️ Optional: improved error context
 function readJson<T>(filePath: string): T {
- return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as T+ try {+ return JSON.parse(fs.readFileSync(filePath, 'utf-8')) as T+ } catch (error) {+ throw new Error(`Failed to parse JSON from ${filePath}: ${error instanceof Error ? error.message : error}`)+ }
}

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6e0701 and 8d6b2a6.

📒 Files selected for processing (2)
  • scripts/i18n-meta/update-en-meta-json.ts
  • scripts/i18n-meta/utils.ts

@serhalpserhalp added the needs review This PR is waiting for a review from a maintainer label Mar 15, 2026
@serhalpserhalp changed the title feat(i18n): add en.meta.json generation scriptchore(i18n): add en.meta.json generation scriptMar 16, 2026
@serhalpserhalp added the i18n Related to internationalization label Apr 10, 2026
@serhalp
serhalp requested a review from userquinApril 10, 2026 20:16
@github-actionsgithub-actionsBot added stale This has become stale and may be closed soon and removed stale This has become stale and may be closed soon labels May 11, 2026
@github-actionsgithub-actionsBot added the stale This has become stale and may be closed soon label Jun 11, 2026
@trueberryless

Copy link
Copy Markdown
Member

Hi, @shuuji3!

Sorry to be that guy, but it seems this PR is quite outdated. Do you know the current state of it? Do we still aim to provide more info for translation strings or is the current setup with schema and en.json enough?

I took the liberty to help you out and resolve the merge conflicts, so this is up-to-date again 👍

@socket-security

socket-securityBot commented Jun 13, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

DiffPackageSupply Chain
Security
VulnerabilityQualityMaintenanceLicense
Added@​types/​dot-object@​2.1.61001007280100

View full report

@github-actions

github-actionsBot commented Jun 13, 2026

Copy link
Copy Markdown

e18e dependency analysis

No dependency warnings found.

@github-actionsgithub-actionsBot removed the stale This has become stale and may be closed soon label Jun 14, 2026
@github-actionsgithub-actionsBot added the stale This has become stale and may be closed soon label Jul 15, 2026
# Conflicts:
#	package.json
#	pnpm-lock.yaml
@github-actionsgithub-actionsBot removed the stale This has become stale and may be closed soon label Jul 18, 2026
@github-actionsgithub-actionsBot added the stale This has become stale and may be closed soon label Aug 18, 2026
@shuuji3shuuji3 reopened this Sep 4, 2026
@github-actionsgithub-actionsBot removed stale-to-close stale This has become stale and may be closed soon labels Sep 4, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

i18nRelated to internationalizationneeds reviewThis PR is waiting for a review from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@shuuji3@trueberryless@serhalp