A modular localization toolchain for extracting translation keys from source code, syncing with translation management systems, and compiling translations into various output formats.
- Extract translation keys from multiple source types (JavaScript, TypeScript, Vue, Python, PHP, Android, iOS)
- Sync with translation management platforms (Lokalise)
- Compile translations into various output formats (JSON, gettext PO/MO, platform-specific)
- Validate translation completeness and format consistency
- Modular architecture - install only the plugins you need
# Install CLI and required plugins
npm install l10n-tools l10n-tools-extractor-vue l10n-tools-compiler-json l10n-tools-syncer-lokalise| Package | Description |
|---|---|
l10n-tools |
CLI for managing translations |
l10n-tools-core |
Core library and infrastructure |
| Extractors | |
l10n-tools-extractor-javascript |
Extract from JS/TS/JSX/TSX files |
l10n-tools-extractor-vue |
Extract from Vue.js files (vue-gettext, vue-i18n) |
l10n-tools-extractor-python |
Extract from Python gettext functions |
l10n-tools-plugin-android |
Extract and compile Android strings.xml |
l10n-tools-plugin-ios |
Extract and compile iOS .strings files |
| Compilers | |
l10n-tools-compiler-json |
Compile to JSON formats (vue-i18n, i18next, etc.) |
l10n-tools-compiler-gettext |
Compile to PO/MO files |
| Syncers | |
l10n-tools-syncer-lokalise |
Sync with Lokalise |
Create a .l10nrc file in your project root:
{
"$schema": "https://raw.githubusercontent.com/tpcint/l10n-tools/main/packages/core/l10nrc.schema.json",
"domains": {
"web": {
"type": "vue-i18n",
"tag": "web-app",
"locales": ["en", "ko", "ja"],
"src-dirs": ["src"],
"src-patterns": ["**/*.vue", "**/*.ts"],
"outputs": [
{
"type": "vue-i18n",
"target-dir": "src/locales"
}
]
}
},
"sync-target": "lokalise",
"lokalise": {
"token": "${LOKALISE_TOKEN}",
"projectId": "your-project-id"
}
}The CLI automatically loads a
.envfile from the current working directory on startup if one exists. Existing environment variables take precedence, so values exported in your shell are not overridden.
| Type | Description |
|---|---|
vue-gettext |
Vue.js with vue-gettext ($gettext, <translate>, v-translate) |
vue-i18n |
Vue.js with vue-i18n ($t, <i18n>, <i18n-t>) |
javascript |
JavaScript/TypeScript with custom keywords |
typescript |
Alias for javascript |
react |
React with i18n functions |
i18next |
i18next translation functions |
python |
Python gettext functions |
android |
Android strings.xml resources |
ios |
iOS Swift, Storyboard, and XIB files |
| Type | Description |
|---|---|
json |
Single JSON file with all locales |
json-dir |
Separate JSON file per locale |
vue-i18n |
JSON with vue-i18n plural format |
i18next |
JSON with i18next plural format |
node-i18n |
JSON with node-i18n plural format |
po-json |
JSON PO format |
mo |
Compiled gettext MO files |
node-gettext |
PO files for node-gettext |
android |
Android strings.xml |
ios |
iOS .strings files |
For Swift code using extension-based localization like "string".localized:
extension String {
var localized: String {
return NSLocalizedString(self, comment: "")
}
}
let message = "Hello, World!".localizedAdd the property name with a . prefix to the keywords option:
{
"type": "ios",
"keywords": [".localized", ".localizedFormat"],
"src-dir": "MyApp",
...
}This extracts keys from both single-line and multi-line string literals:
"Hello".localized
"""
Multi-line
string
""".localizedExtract keys from source, sync with remote, and compile outputs:
l10n updateExtract and upload new/changed keys without modifying local files:
l10n uploadBidirectional sync between local and remote:
l10n syncCheck for untranslated entries:
l10n check
l10n check --locales ko,ja
l10n check src/components/Header.vue # Check specific filesOptions:
-r, --rcfile <rcfile> Config file path (default: .l10nrc)
-d, --domains <domains> Domains to process (comma separated)
-s, --skip-validation Skip format validation
-b, --validation-base-locale Base locale for validation
-n, --dry-sync Simulate sync without changes
-v, --verbose Verbose output
-q, --quiet Minimal output
-t, --tags <tags> Additional tags for keys (comma separated)
These commands are prefixed with _ and are intended for scripting / automation, not day-to-day use. Their flags and output are kept stable but their purpose is to be composed by other tooling.
Query the sync target directly and report translation counts per locale, without downloading translations into the local cache. Useful for verifying upload completeness or driving dashboards.
# Sum across all domains (locale union)
l10n _remoteCount --source web
# Specific domain
l10n -d web _remoteCount --source web
# Multiple domains → one line per domain
l10n -d web,mobile _remoteCount --source web
# Filter by spec
l10n _remoteCount --source web -s untranslated
l10n _remoteCount --source web -s translated,untranslated
# Specific locales
l10n _remoteCount --source web -l en,koOptions:
| Option | Description |
|---|---|
--source <source> |
Source identifier to filter by. Omit to count all keys for the domain's tag. |
-l, --locales <locales> |
Comma-separated locales (default: each domain's configured locales). |
-s, --spec <spec> |
Comma-separated specs, ! prefix to negate. Supported: total (default), translated, untranslated. |
Output format:
- One line per domain:
<domain>,<locale>:<count>,<locale>:<count>,... - When
-d/--domainsis omitted, a single aggregate line is emitted with*as the domain name. Locales are the union of each domain's locales (insertion order); a locale's count is the sum across the domains that include it.
Notes:
- Requires a sync target that supports source listing (currently
l10n-storage). - Counts are by
(context, key)pair, matching_countsemantics — a key with multiple contexts is counted per context. - The l10n-storage backend has no
unverifiedflag, sounverified/!unverifiedspecs always behave as if all entries are verified.
- Node.js >= 22.19.0
- npm >= 10.9.0
MIT
Eungkyu Song eungkyu@gmail.com