Skip to content

Conversation

@serhalp
Copy link
Contributor

@serhalp serhalp commented Feb 9, 2026

Summary

Generate a JSON Schema from the canonical en.json that provides some cool editor DX niceties (autocomplete, typo detection via additionalProperties: false) for all i18n locale files.

Screenshot 2026-02-08 at 15 00 16 Screenshot 2026-02-08 at 19 35 28

Changes

  • Add scripts/generate-i18n-schema.ts that generates a JSON Schema from the structure of en.json and writes it to i18n/schema.json.
  • Add "$schema" reference to this schema to all 27 locale files
  • Add pnpm i18n:schema and run it in the pre-commit hook (updates schema) and in CI (fails if schema is out of date)

Notes

  • In the pre-commit hooks I called the node script directly because in my testing on my machine pnpm adds a ~250ms overhead and it's important for good DX for these hooks to be as fast as possible.
  • I initially thought we could generate the JSON Schema from the static analysis stuff we already do to detect unused keys. But it turned out to be much simpler and less wasteful to just use the en.json reference file as the source of truth.

Generate a JSON Schema from the canonical `en.json` that provides some cool editor DX niceties
(autocomplete, typo detection via `additionalProperties: false`) for all i18n locale files.

- Add `scripts/generate-i18n-schema.ts` to generate `i18n/schema.json` from en.json. This is a JSON
  Schema... schema.
- Add `"$schema"` reference to this schema to all 27 locale files
- Add `pnpm i18n:schema` and run it in the pre-commit hook (updates schema) and in CI (fails if
  schema is out of date)

Note: in the pre-commit hooks I called the node script directly because pnpm adds a ~250ms overhead
(on my machine) and it's important for DX for these hooks to be fast.
@vercel
Copy link

vercel bot commented Feb 9, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 9, 2026 1:14am
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 9, 2026 1:14am
npmx-lunaria Ignored Ignored Feb 9, 2026 1:14am

Request Review

@github-actions
Copy link

github-actions bot commented Feb 9, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
lunaria/files/ar-EG.json Localization changed, will be marked as complete.
lunaria/files/az-AZ.json Localization changed, will be marked as complete.
lunaria/files/cs-CZ.json Localization changed, will be marked as complete.
lunaria/files/de-DE.json Localization changed, will be marked as complete.
lunaria/files/en-GB.json Localization changed, will be marked as complete.
lunaria/files/en-US.json Source changed, localizations will be marked as outdated.
lunaria/files/es-419.json Localization changed, will be marked as complete.
lunaria/files/es-ES.json Localization changed, will be marked as complete.
lunaria/files/fr-FR.json Localization changed, will be marked as complete.
lunaria/files/hi-IN.json Localization changed, will be marked as complete.
lunaria/files/hu-HU.json Localization changed, will be marked as complete.
lunaria/files/id-ID.json Localization changed, will be marked as complete.
lunaria/files/it-IT.json Localization changed, will be marked as complete.
lunaria/files/ja-JP.json Localization changed, will be marked as complete.
lunaria/files/mr-IN.json Localization changed, will be marked as complete.
lunaria/files/ne-NP.json Localization changed, will be marked as complete.
lunaria/files/no-NO.json Localization changed, will be marked as complete.
lunaria/files/pl-PL.json Localization changed, will be marked as complete.
lunaria/files/pt-BR.json Localization changed, will be marked as complete.
lunaria/files/ru-RU.json Localization changed, will be marked as complete.
lunaria/files/te-IN.json Localization changed, will be marked as complete.
lunaria/files/uk-UA.json Localization changed, will be marked as complete.
lunaria/files/zh-CN.json Localization changed, will be marked as complete.
lunaria/files/zh-TW.json Localization changed, will be marked as complete.
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

@codecov
Copy link

codecov bot commented Feb 9, 2026

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!

Comment on lines +15 to +22
interface JsonSchema {
$schema?: string
title?: string
description?: string
type: string
properties?: Record<string, JsonSchema>
additionalProperties?: boolean
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically I could pull in a dev dep to get a proper type for this... and we could pull in the JSON Schema meta-schema to validate our schema against the meta-schema... but none of that seemed worth it here.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

This pull request introduces JSON Schema validation to the i18n locale infrastructure. It adds a "$schema" reference field pointing to a centralised schema file across all locale JSON files (ar-EG, ar, az-AZ, cs-CZ, de-DE, en-GB, en-US, en, es-419, es-ES, es, fr-FR, hi-IN, hu-HU, id-ID, it-IT, ja-JP, mr-IN, ne-NP, no-NO, pl-PL, pt-BR, ru-RU, te-IN, uk-UA, zh-CN, zh-TW). A new script generates this schema from the reference en.json file. The package.json and CI workflow are updated to support schema generation and validation, whilst existing i18n comparison and analysis scripts are modified to exclude the schema field from translation processing.

Possibly related PRs

Suggested reviewers

  • danielroe
  • shuuji3
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description clearly explains the objective of generating a JSON Schema from en.json for improved editor DX, lists the specific changes made, and provides relevant context and screenshots.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/locale-files-json-schema

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
lunaria/prepare-json-files.ts (1)

67-73: Consider adding a type guard for robustness.

If the JSON file contains a primitive, array, or null instead of an object, the destructuring on line 71 will throw at runtime. Given that this processes locale files with a known structure, this is unlikely, but a defensive check could improve robustness.

🛡️ Optional defensive check
 async function loadLocaleSourceJson<T = unknown>(name: string): Promise<T> {
   const rawJson = JSON.parse(await fs.readFile(path.resolve(`${localesFolder}/${name}`), 'utf8'))
+  if (typeof rawJson !== 'object' || rawJson === null || Array.isArray(rawJson)) {
+    throw new Error(`Expected locale file ${name} to contain a JSON object`)
+  }
   // Exclude $schema since it isn't useful in generated files and the relative path
   // would be wrong anyway
   const { $schema: _, ...rest } = rawJson
   return rest
 }

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
scripts/generate-i18n-schema.ts (1)

24-39: Avoid silently treating non-string leaf values as strings.

Right now, Line 27‑32 maps arrays/booleans/numbers/null to { type: 'string' }. If any non-string leaf slips into en.json, the generated schema will be misleading and IDE validation will flag correct values. Consider asserting string-only leaves (or explicitly mapping types) so mismatches fail fast.

💡 Suggested guard
   for (const [key, value] of Object.entries(obj)) {
     if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
       properties[key] = generateSubSchema(value as Json)
-    } else {
-      properties[key] = { type: 'string' }
-    }
+      continue
+    }
+    if (typeof value === 'string') {
+      properties[key] = { type: 'string' }
+      continue
+    }
+    throw new Error(`Unsupported i18n value type for "${key}"`)
   }

"lint-staged": {
"i18n/locales/*": [
"pnpm build:lunaria",
"node ./lunaria/lunaria.ts",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Calling node scripts directly where possible because in my testing on my machine pnpm ... adds a ~250ms overhead and it's important for good DX for these hooks to be as fast as possible.

I had to eliminate the teeny perf optimization that was `copy: true`. It
doesn't seem to affect the run time much.
(locale.file ? getFileName(locale.file) : undefined) ??
(files[0] ? getFileName(files[0]) : undefined)
if (!json) return undefined
if (copy) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a teeny minor perf optimization (for a script that is run on demand). I had to remove it in order to exclude $schema from the generated files.

It's still quite fast, so this seems fine to me.

@serhalp serhalp requested a review from a team February 9, 2026 01:35
@bluwy
Copy link
Contributor

bluwy commented Feb 9, 2026

I'm not sure if the review request for @npmx-dev/_ is automatic, but can we not ping it? Not all of us on that team are maintainers. Thanks 🙏

@danielroe danielroe added this pull request to the merge queue Feb 9, 2026
Merged via the queue into main with commit 91df60a Feb 9, 2026
20 checks passed
@danielroe danielroe deleted the chore/locale-files-json-schema branch February 9, 2026 07:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants