-
Notifications
You must be signed in to change notification settings - Fork 662
Add show stats CLI command to generate translation coverage reports (#1502) #1505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR adds a new CLI command npx lingo.dev@latest show stats to generate translation coverage reports across all locales. The command scans locale files, counts total and translated keys, calculates coverage percentages with color-coded indicators, and lists missing translation keys grouped by locale and file.
Key Changes
- Added new
stats.tscommand under the show command group - Registered the stats command in the show index
- Added
lingo.devpackage dependency to root package.json
Reviewed Changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
| packages/cli/src/cli/cmd/show/stats.ts | Implements the new stats command with translation coverage calculation and reporting |
| packages/cli/src/cli/cmd/show/index.ts | Registers the stats command with the show command group |
| package.json | Adds lingo.dev as a dependency |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,126 @@ | |||
| import { Command } from "interactive-commander"; | |||
| import { readConfig } from "../../utils/config"; | |||
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function readConfig does not exist in the config utility. Based on other show commands, you should use getConfig() instead, which returns I18nConfig | null and is synchronous.
| @@ -0,0 +1,126 @@ | |||
| import { Command } from "interactive-commander"; | |||
| import { readConfig } from "../../utils/config"; | |||
| import { getAllBucketFiles } from "../../utils/files"; | |||
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function getAllBucketFiles does not exist. Looking at similar commands like show files, you should use getBuckets(i18nConfig) from ../../utils/buckets to get bucket configurations with path patterns.
| import { Command } from "interactive-commander"; | ||
| import { readConfig } from "../../utils/config"; | ||
| import { getAllBucketFiles } from "../../utils/files"; | ||
| import { getLocales } from "../../utils/locales"; |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function getLocales does not exist. Based on other commands, you should access target locales via i18nConfig.locale.targets after loading the config.
| const sourceKeys = Object.keys(JSON.parse(sourceContent)); | ||
| const targetKeys = Object.keys(JSON.parse(targetContent)); |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Object.keys() only counts top-level keys in JSON files and will miss nested translation keys. The codebase uses bucket loaders that flatten nested structures (e.g., {\"user\": {\"name\": \"...\", \"email\": \"...\"}} becomes user/name and user/email). You should use createBucketLoader() and call loader.pull(locale) to get properly flattened keys, similar to how the status.ts command does it (see line 231-232 in status.ts).
| const targetKeys = Object.keys(JSON.parse(targetContent)); | ||
|
|
||
| totalKeys += sourceKeys.length; | ||
| translatedKeys += targetKeys.length; |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This counts all keys present in the target file, not just the ones that match source keys. If a target file has extra keys not in source, or is missing some source keys, the count will be incorrect. You should count targetKeys.filter(key => sourceKeys.includes(key)).length to only count keys that exist in both source and target.
| translatedKeys += targetKeys.length; | |
| translatedKeys += targetKeys.filter(key => sourceKeys.includes(key)).length; |
| const sourcePath = file.replace("[locale]", sourceLocale); | ||
| const targetPath = file.replace("[locale]", locale); |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This simple string replacement doesn't account for locale delimiters. The codebase uses resolveOverriddenLocale(locale, bucketConfig.delimiter) to handle different delimiter conventions (e.g., en-US vs en_US). See how files.ts does this at lines 40-46 and 50-53.
| missingKeys.push(...missing.map(key => `${path.basename(file)}: ${key}`)); | ||
| } | ||
| } catch (err) { | ||
| // Skip files that can't be parsed |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Silently skipping files that can't be parsed may hide legitimate errors. Consider logging a warning or debug message when files fail to parse so users understand why certain files aren't included in stats.
| // Skip files that can't be parsed | |
| // Skip files that can't be parsed | |
| console.warn(chalk.yellow(`Warning: Failed to process file "${file}" for locale "${locale}": ${err instanceof Error ? err.message : err}`)); |
| const sourceLocale = config.sourceLocale || "en"; | ||
| const bucketFiles = await getAllBucketFiles(); | ||
|
|
||
| const stats: TranslationStats[] = []; |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other show commands check if config exists before proceeding (see files.ts lines 29-35 and ignored-keys.ts lines 20-26). You should add a similar check: if (!config) { throw new CLIError({ message: 'i18n.json not found. Please run lingo.dev init to initialize the project.', docUrl: 'i18nNotFound' }); }
| "dependencies": { | ||
| "@changesets/changelog-github": "^0.5.0", | ||
| "@changesets/cli": "^2.27.10", | ||
| "lingo.dev": "^0.114.0", |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding lingo.dev as a dependency to the root package.json appears to be a circular dependency since this PR is modifying the lingo.dev CLI itself. This dependency is unnecessary and should be removed.
| "lingo.dev": "^0.114.0", |
| .helpOption("-h, --help", "Show help") | ||
| .action(async () => { | ||
| try { | ||
| const config = await readConfig(); |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Callee is not a function: it has type undefined.
Adds a new CLI command to Lingo.dev that reports translation coverage across all locales — helping developers and localization teams quickly spot missing translations before release.
✨ Features
New command:
npx lingo.dev@latest show statsScans locale files and computes:
Total / Translated / Missing keys per locale
Coverage % with color indicators
🟢 ≥90% 🟡 ≥70% 🔴 <70%
Lists missing keys grouped by locale and file
Non-blocking (exit code 0)
🛠 Implementation
Added packages/cli/src/commands/show/stats.tsRegistered under the show command group
Added utilities in i18n.ts for file discovery + key flattening
Added tests for key counting and missing-key detection
Updated CLI help and README with examples
🔮 Next Steps
Support additional formats (YAML, PO, TS)
Add
--ref-locale flagOption to fail CI on low coverage
✅ Compatibility
Additive change , no breaking updates to existing commands or APIs.