Overview
The @proton/i18n package is a command-line tool for extracting and validating translations from Proton application codebases. It works with the ttag library to manage internationalization (i18n) in JavaScript and TypeScript projects.
Installation
npm install -D @proton/i18n
Key Features
Extract translatable strings from source code
Validate translation files
Lint translation function usage
Support for ttag library
POT/PO file generation and validation
TypeScript and JavaScript support
CLI Usage
The package provides a proton-i18n command-line tool.
Commands
Extract translatable strings from your codebase to create POT files.
# Extract from default locations
proton-i18n extract
# Extract with custom configuration
proton-i18n extract --config ./i18n.config.js
Configuration
Create an i18n.config.js file:
module . exports = {
// Source files to extract from
source: './src' ,
// Output POT file
output: './locales/template.pot' ,
// File patterns to include
include: [ '**/*.ts' , '**/*.tsx' , '**/*.js' , '**/*.jsx' ],
// File patterns to exclude
exclude: [ '**/*.test.ts' , '**/*.spec.ts' , 'node_modules/**' ],
};
Validating Translations
Validate translation files and usage patterns.
# Validate all translations
proton-i18n validate
# Validate specific directory
proton-i18n validate lint-functions ./src
# Verbose output
proton-i18n validate --verbose
Validation Commands
Validates that translation functions (c, t, jt, etc.) are used correctly proton-i18n validate lint-functions ./src
Checks for:
Correct function usage
String literals (no template literals)
Proper context usage
Missing translations
Validates PO/POT file structure and syntax proton-i18n validate check-files ./locales
Checks for:
Valid PO file format
Consistent msgid/msgstr pairs
Proper headers
Encoding issues
Usage in Applications
With ttag Library
The i18n tool works with the ttag library for runtime translations.
import { c , t , jt , msgid , ngettext } from 'ttag' ;
// Simple translation
const translated = t `Hello, world!` ;
// Translation with context
const withContext = c ( 'Button label' ). t `Save` ;
// Translation with variable
const userName = 'Alice' ;
const greeting = t `Hello, ${ userName } !` ;
// Plural translations
const count = 5 ;
const message = ngettext (
msgid `You have ${ count } message` ,
`You have ${ count } messages` ,
count
);
// JSX translation with components
const jsxMessage = jt `Click ${
< a href = "/settings" > here </ a >
} to update settings` ;
Setting Up in a Project
// app/i18n.ts
import { addLocale , useLocale } from 'ttag' ;
export async function loadLocale ( locale : string ) {
if ( locale === 'en' ) {
// English is the default
return ;
}
try {
const translations = await import (
`../locales/ ${ locale } .json`
);
addLocale ( locale , translations );
useLocale ( locale );
} catch ( error ) {
console . error ( `Failed to load locale: ${ locale } ` , error );
}
}
// app/App.tsx
import { useEffect } from 'react' ;
import { loadLocale } from './i18n' ;
function App () {
useEffect (() => {
const userLocale = navigator . language . split ( '-' )[ 0 ];
loadLocale ( userLocale );
}, []);
return < div >{ /* Your app */ } </ div > ;
}
Translation Workflow
Extract strings
Run extraction to create POT template file
Create PO files
Create language-specific PO files from template msginit -i locales/template.pot -o locales/fr.po -l fr
Translate
Use a PO editor (Poedit, Lokalize) or translation platform to translate strings
Validate
Validate translations before committing
Compile
Convert PO files to JSON for runtime use ttag po2json locales/fr.po > locales/fr.json
Best Practices
Writing Translatable Strings
Always use string literals, never template literals or concatenated strings
// ✅ Good - String literal
const msg = t `Welcome to Proton` ;
// ❌ Bad - Template literal
const msg = t `Welcome to ${ appName } ` ; // Won't be extracted
// ✅ Good - Variable interpolation
const msg = t `Welcome to ${ appName } ` ; // If appName is in scope
// ✅ Good - With context
const msg = c ( 'Navigation' ). t `Home` ;
Providing Context
Use context to disambiguate identical strings with different meanings:
// "Close" as verb
const closeAction = c ( 'Action' ). t `Close` ;
// "Close" as adjective (near)
const closeDistance = c ( 'Distance' ). t `Close` ;
Pluralization
Handle plural forms correctly:
import { ngettext , msgid } from 'ttag' ;
function ItemCount ({ count } : { count : number }) {
const text = ngettext (
msgid ` ${ count } item` ,
` ${ count } items` ,
count
);
return < span >{ text } </ span > ;
}
Variables in Translations
// ✅ Simple variable
const msg = t `Hello, ${ userName } ` ;
// ✅ Multiple variables
const msg = t ` ${ userName } sent you ${ count } messages` ;
// ✅ With formatting
const msg = t `Last login: ${ formatDate ( date ) } ` ;
Package Scripts
Add these scripts to your package.json:
{
"scripts" : {
"i18n:extract" : "proton-i18n extract" ,
"i18n:validate" : "proton-i18n validate lint-functions" ,
"i18n:validate:files" : "proton-i18n validate check-files ./locales" ,
"i18n:compile" : "ttag po2json locales/*.po"
}
}
Directory Structure
Recommended project structure:
project/
├── src/
│ ├── app/
│ │ └── i18n.ts # i18n setup
│ └── components/
│ └── *.tsx # Components with translations
├── locales/
│ ├── template.pot # Extraction template
│ ├── en.po # English translations
│ ├── fr.po # French translations
│ ├── de.po # German translations
│ └── *.json # Compiled translations
└── i18n.config.js # i18n configuration
Linting Rules
The validator checks for common mistakes:
// ❌ Bad
const msg = t `Hello ${ name } ` ; // If name is dynamic
// ✅ Good
const msg = t `Hello ${ userName } ` ; // If userName is in scope at extraction
// ❌ Bad
const msg = t `Hello ` + name ;
// ✅ Good
const msg = t `Hello ${ name } ` ;
Use context for ambiguous strings
// ❌ Bad - Ambiguous
const label = t `Close` ;
// ✅ Good - Clear context
const label = c ( 'Button' ). t `Close` ;
CI Integration
Add validation to your CI pipeline:
# .github/workflows/i18n.yml
name : i18n
on : [ push , pull_request ]
jobs :
validate :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- uses : actions/setup-node@v3
with :
node-version : 22
- run : yarn install
- run : yarn i18n:validate
- run : yarn i18n:validate:files
Verbose Output
Get detailed information during validation:
proton-i18n validate --verbose
This will show:
Files being processed
Specific validation errors
Line numbers and context
Suggestions for fixes
Dependencies
ttag-cli - ttag CLI tools
gettext-parser - PO/POT file parsing
glob - File pattern matching
execa - Process execution
Node.js Requirements
Requires Node.js >= 22.14.0
# Check your Node version
node --version
# Should output v22.14.0 or higher
Testing
# Run test suite
yarn test
# Run tests in CI mode
yarn test:ci
ttag Documentation Official ttag library documentation
gettext Manual GNU gettext documentation
Common Issues
Extraction not finding strings
Use --verbose flag to see detailed errors
Check for template literals in translation calls
Verify PO file format is correct
Missing translations at runtime
Ensure PO files are compiled to JSON
Check locale is loaded before use
Verify JSON files are imported correctly
ttag Runtime translation library
@proton/shared Shared utilities including i18n helpers