A modern internationalization package designed specifically for the NativeDocument framework, featuring automatic translation key scanner and reactive observable support.
- Automatic Scanner: Automatically detects missing translation keys in your locale files
- NativeDocument Integration: Seamless integration with NativeDocument's reactive system
- Built on i18next: Leverages the power and flexibility of i18next
- Flexible Configuration: Configuration via JSON file
- Built-in CLI: Command-line tool to scan your projects
npm install native-document-i18n
Create an i18n.scanner.config.json
file at your project root:
{
"scan": {
"dir": "src",
"extensions": ["js", "tsx"]
},
"locales": "src/locales",
"save": "src/locales-reports"
}
Option | Type | Description | Default |
---|---|---|---|
scan.dir |
string | Directory to scan | "src" |
scan.extensions |
array | File extensions to analyze | ["js"] |
locales |
string | Path to locale files directory | - |
save |
string | Directory to save reports (optional) | - |
Organize your translation files as follows:
src/
└── locales/
├── en.json
├── fr.json
└── es.json
Example locale file (en.json
):
{
"welcome": "Welcome",
"user.greeting": "Hello {{name}}!",
"button.save": "Save",
"error.required": "This field is required"
}
import { I18nService } from 'native-document-i18n';
// Load your translation resources
const resources = {
en: { translation: require('./locales/en.json') },
fr: { translation: require('./locales/fr.json') }
};
// Initialize the service
I18nService.init(resources);
import { I18nService } from 'native-document-i18n';
// Simple translation
const message = I18nService.tr('welcome');
// Translation with parameters
const greeting = I18nService.tr('user.greeting', { name: 'John' });
import { tr } from 'native-document-i18n';
// Reactive translation
const welcomeMessage = tr('welcome');
console.log(welcomeMessage.val()); // "Welcome"
// Translation with observable parameters
import { Observable } from 'native-document';
const userName = Observable('John');
const greeting = tr('user.greeting', { name: userName });
// Translation updates automatically when userName changes
userName.set('Marie'); // greeting updates automatically
// String prototype extension
const message = 'welcome'.tr();
const greeting = 'user.greeting'.tr({ name: 'John' });
import { I18nService } from 'native-document-i18n';
// Listen to language changes
I18nService.current.subscribe((lang) => {
console.log('Current language:', lang);
});
// Change language
await I18nService.use('fr');
The package includes a CLI tool to automatically scan your files and detect missing keys.
# If installed globally
npx native-document-i18n
# Or directly with node
node node_modules/native-document-i18n/bin/scanner.js
- File Analysis: Scans all files according to configuration
- Key Extraction: Searches for
tr()
calls in your code - Comparison: Compares with your existing locale files
- Report: Generates a report of missing keys
:: scan locales
-- en : 3 keys are absent in locale file.
-- fr : 5 keys are absent in locale file.
-- save result to locales-reports/en.absent.json
The scanner generates JSON files with missing keys:
{
"user.profile": "user.profile",
"button.cancel": "button.cancel",
"error.network": "error.network"
}
The package uses the following environment variables:
Variable | Description | Default |
---|---|---|
VITE_LOCALE |
Default language | "en" |
VITE_FALLBACK_LANGUE |
Fallback language | "en" |
VITE_ENV |
Environment (enables debug if development ) |
- |
The scanner automatically searches for these patterns in your code:
// Automatically detected
tr('simple.key')
tr("double.quotes")
tr('key.with.params', { param: value })
// Not detected (dynamic keys)
const key = 'dynamic.key';
tr(key) // ⚠️ Not detected by scanner
- Development: Use
tr()
in your code - Scan: Run the scanner to detect missing keys
- Translation: Add missing translations to your locale files
- Validation: Re-scan to verify everything is complete
import { tr } from 'native-document-i18n';
const { Observable } = NativeDocument;
const { Div, H1, P, Input, Button } = NativeDocument.elements;
const WelcomeComponent = () => {
const userName = Observable('John');
const welcome = tr('welcome');
const greeting = tr('user.greeting', { name: userName });
return Div([
H1(welcome),
P(greeting),
Input({
type: "text",
value: userName,
placeholder: "Enter your name"
}),
Button("Change Language").nd.onClick(() => {
I18nService.use('fr');
})
]);
};
{
"scripts": {
"i18n:scan": "node node_modules/native-document-i18n/bin/scanner.js"
}
}
import { tr } from 'native-document-i18n';
import { Store, Observable } from 'native-document';
import { Div, H1, P, Button, ForEach } from 'native-document/elements';
// Create global store for user preferences
Store.create("userPrefs", {
language: "en",
theme: "light"
});
const MultiLanguageApp = () => {
const userPrefs = Store.use("userPrefs");
const menuItems = Observable([
{ id: 'home', key: 'menu.home' },
{ id: 'profile', key: 'menu.profile' },
{ id: 'settings', key: 'menu.settings' }
]);
return Div([
H1(tr('app.title')),
// Language selector
Div([
Button("English").nd.onClick(() => {
I18nService.use('en');
userPrefs.set(current => ({...current, language: 'en'}));
}),
Button("Français").nd.onClick(() => {
I18nService.use('fr');
userPrefs.set(current => ({...current, language: 'fr'}));
})
]),
// Dynamic menu with translations
ForEach(menuItems, (item) =>
Button(tr(item.key)).nd.onClick(() => {
console.log(`Navigate to ${item.id}`);
})
, 'id')
]);
};
document.body.appendChild(MultiLanguageApp());
import { tr } from 'native-document-i18n';
import { Observable } from 'native-document';
import { Div, H2, P, Button, ShowIf } from 'native-document/elements';
// Reactive translation with language switching
const LanguageSwitcher = () => {
const currentLang = I18nService.current;
return Div([
H2(tr('welcome')),
P(tr('app.description')),
ShowIf(
currentLang.check(lang => lang === 'en'),
Button("Switch to French").nd.onClick(() => I18nService.use('fr'))
),
ShowIf(
currentLang.check(lang => lang === 'fr'),
Button("Switch to English").nd.onClick(() => I18nService.use('en'))
)
]);
};
document.body.appendChild(LanguageSwitcher());
- Check that your files have the correct extensions in the configuration
- Make sure you're using string literals (not variables) in
tr()
- Verify you're using the
tr()
function that returns a NativeDocument observable - Ensure your component is properly bound to the observable changes
- Create the
i18n.scanner.config.json
file at your project root - Verify you're running the scanner from the correct directory
Contributions are welcome! Feel free to open issues or submit pull requests.
MIT