| title | Migration |
|---|---|
| sidebar_position | 2 |
This guide provides step-by-step instructions for migrating translation resources from other translation management systems to LingoTracker.
- Overview
- Before You Begin
- Migrating from Transloco
- Migrating from i18next
- Migrating from Other Systems
- Post-Migration Steps
- Troubleshooting
Migration is the process of importing existing translation resources from another system into LingoTracker. The import feature supports two migration workflows:
- Direct Migration: Import translations directly using the
migrationstrategy - Two-Step Migration: Export from source system, transform format, then import
- JSON: Flat or hierarchical structures (Transloco, i18next, etc.)
- XLIFF: Industry standard format from CAT tools
- Custom Formats: Transform to JSON or XLIFF first
- Translation keys and values (all locales)
- Base locale values (source language)
- Comments (if present in source format)
- Tags (if present in source format)
- Translation history
- Translator assignments
- Review workflows
- Custom metadata (not in standard formats)
Create a new LingoTracker project or ensure you have one initialized:
lingo-tracker initConfigure .lingo-tracker.json with your base locale and target locales:
{
"baseLocale": "en",
"locales": ["en", "es", "fr", "de"],
"collections": {
"Main": {
"translationsFolder": "translations"
}
}
}Always create a backup of your original translation files before migration:
# Copy your translation files to a backup location
cp -r ./src/i18n ./src/i18n.backupIf something goes wrong, restore with:
cp -r ./src/i18n.backup ./src/i18nLingoTracker's import feature has a dedicated migration strategy that:
- Creates new resources when
--create-missingis specified - Resolves Transloco-style key references (
{{t('key')}}) - Updates comments and tags when
--update-comments/--update-tagsare specified - Sets all imported translations to
translatedstatus
Tip: Always do a dry run first to preview what will be created before committing to the import:
lingo-tracker import --source en.json --locale en --strategy migration \ --create-missing --dry-run
Transloco is Angular's internationalization library. LingoTracker provides specialized support for Transloco migrations.
After completing the steps below, follow the Post-Migration Steps to normalize resources, configure bundles, and test your application.
Transloco typically uses one JSON file per locale:
src/assets/i18n/
├── en.json
├── es.json
├── fr.json
└── de.json
Example en.json (hierarchical):
{
"common": {
"buttons": {
"ok": "OK",
"cancel": "Cancel"
}
},
"dashboard": {
"title": "Dashboard",
"welcome": "Welcome back"
}
}Import the base locale first to create the resource structure. If you have multiple collections, add --collection <name>:
lingo-tracker import \
--source ./src/assets/i18n/en.json \
--locale en \
--strategy migration \
--create-missing \
--update-comments \
--update-tags \
--verboseNote: Importing into the base locale is only supported with the migration strategy. It creates resources with the base locale value set from the JSON values.
Import each target locale:
# Import Spanish
lingo-tracker import \
--source ./src/assets/i18n/es.json \
--locale es \
--strategy migration \
--create-missing \
--update-comments \
--update-tags \
--verbose
# Import French
lingo-tracker import \
--source ./src/assets/i18n/fr.json \
--locale fr \
--strategy migration \
--create-missing \
--update-comments \
--update-tags
# Import German
lingo-tracker import \
--source ./src/assets/i18n/de.json \
--locale de \
--strategy migration \
--create-missing \
--update-comments \
--update-tagsIf your target locale files are missing base values, you can add them with a transformation script before importing. See Migrating from Other Systems > Generic JSON for an example transform.
If your Transloco files use references like {{t('other.key')}}, LingoTracker automatically resolves them during migration:
Before (with references):
{
"greeting": "Hello",
"welcome": "{{t('greeting')}}, welcome back!"
}After (resolved):
{
"greeting": "Hello",
"welcome": "Hello, welcome back!"
}This happens automatically with the migration strategy.
Check the import summary — the exact path is printed at the end of each import command output (typically {translationsFolder}/import-summary.md):
# Example — adjust path to match your translationsFolder config
cat translations/import-summary.mdVerify a few resources were created correctly:
cat translations/common/buttons/resource_entries.jsonAfter migration, update your Transloco loader to use LingoTracker bundles:
Before:
loader: TranslocoHttpLoader // Loads from src/assets/i18nAfter:
// 1. Add bundle configuration to .lingo-tracker.json
{
"bundles": {
"transloco": {
"bundleName": "{locale}",
"dist": "./src/assets/i18n",
"collections": "All"
}
}
}
// 2. Generate bundles
// Add to package.json:
{
"scripts": {
"prebuild": "lingo-tracker bundle"
}
}
// 3. Transloco config stays the same
// Bundle files replace old i18n filesi18next is a popular internationalization framework for JavaScript. LingoTracker can import i18next JSON files directly.
After completing the steps below, follow the Post-Migration Steps to normalize resources, configure bundles, and test your application.
i18next typically organizes translations by namespace:
public/locales/
├── en/
│ ├── common.json
│ ├── dashboard.json
│ └── translation.json
├── es/
│ ├── common.json
│ ├── dashboard.json
│ └── translation.json
└── fr/
├── common.json
├── dashboard.json
└── translation.json
Example en/common.json:
{
"buttons": {
"ok": "OK",
"cancel": "Cancel"
},
"errors": {
"required": "This field is required",
"invalid": "Invalid input"
}
}i18next uses separate files per namespace. LingoTracker works with a single file per locale, so combine namespaces:
# Create migration directory
mkdir -p migration
# Combine English namespaces
node -e "
const fs = require('fs');
const common = JSON.parse(fs.readFileSync('./public/locales/en/common.json', 'utf8'));
const dashboard = JSON.parse(fs.readFileSync('./public/locales/en/dashboard.json', 'utf8'));
const translation = JSON.parse(fs.readFileSync('./public/locales/en/translation.json', 'utf8'));
const combined = {
common,
dashboard,
translation
};
fs.writeFileSync('./migration/en.json', JSON.stringify(combined, null, 2));
console.log('Created migration/en.json');
"Or create a more robust script:
// combine-namespaces.js
const fs = require('fs');
const path = require('path');
const localesDir = './public/locales';
const outputDir = './migration';
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
const locales = fs.readdirSync(localesDir);
for (const locale of locales) {
const localePath = path.join(localesDir, locale);
if (!fs.statSync(localePath).isDirectory()) continue;
const combined = {};
const namespaces = fs.readdirSync(localePath);
for (const namespace of namespaces) {
if (!namespace.endsWith('.json')) continue;
const namespaceName = namespace.replace('.json', '');
const content = JSON.parse(fs.readFileSync(path.join(localePath, namespace), 'utf8'));
combined[namespaceName] = content;
}
fs.writeFileSync(
path.join(outputDir, `${locale}.json`),
JSON.stringify(combined, null, 2)
);
console.log(`Created migration/${locale}.json`);
}Run the script:
node combine-namespaces.jsImport the base locale (usually English). If you have multiple collections, add --collection <name>:
lingo-tracker import \
--source ./migration/en.json \
--locale en \
--strategy migration \
--create-missing \
--update-comments \
--update-tags \
--verboseImport each target locale:
# Import Spanish
lingo-tracker import \
--source ./migration/es.json \
--locale es \
--strategy migration \
--create-missing \
--update-comments \
--update-tags
# Import French
lingo-tracker import \
--source ./migration/fr.json \
--locale fr \
--strategy migration \
--create-missing \
--update-comments \
--update-tagsAfter migration, update your i18next configuration to use LingoTracker bundles:
Add bundle configuration (.lingo-tracker.json):
{
"bundles": {
"i18next": {
"bundleName": "{locale}/translation",
"dist": "./public/locales",
"collections": "All"
}
}
}Generate bundles:
lingo-tracker bundleUpdate package.json:
{
"scripts": {
"prebuild": "lingo-tracker bundle",
"dev": "lingo-tracker bundle && vite"
}
}Your i18next configuration stays the same - it will load from the generated bundle files.
If your system uses JSON files in a custom format:
Create a transformation script to convert your format to LingoTracker's expected structure:
Flat structure with simple values:
{
"app.title": "My App",
"app.welcome": "Welcome",
"buttons.ok": "OK"
}Hierarchical with rich objects (recommended for migration):
{
"app": {
"title": {
"value": "My App",
"baseValue": "My App",
"comment": "Application title"
},
"welcome": {
"value": "Welcome",
"baseValue": "Welcome"
}
},
"buttons": {
"ok": {
"value": "OK",
"baseValue": "OK"
}
}
}lingo-tracker import \
--source ./transformed-en.json \
--locale en \
--strategy migration \
--create-missing
lingo-tracker import \
--source ./transformed-es.json \
--locale es \
--strategy migration \
--create-missingIf your source system exports XLIFF files (common for CAT tools like SDL Trados, MemoQ, Smartling):
LingoTracker supports XLIFF 1.2. If you have XLIFF 2.0, convert it first:
# Using xliff-tool (install via npm)
npm install -g xliff-tool
xliff-tool convert --from xliff2 --to xliff es.xlf es-converted.xliff# Import each locale
lingo-tracker import \
--source ./es.xliff \
--locale es \
--strategy migration \
--create-missing \
--verboseXLIFF files include both source and target values, so LingoTracker automatically creates resources with proper base values.
If your source system uses PO files (gettext format):
Use a conversion tool like po2json:
npm install -g po2json
# Convert PO to JSON
po2json es.po es.json --format mf// transform-po-json.js
const fs = require('fs');
const poJson = JSON.parse(fs.readFileSync('./es.json', 'utf8'));
const baseJson = JSON.parse(fs.readFileSync('./en.json', 'utf8'));
const transformed = {};
for (const [key, value] of Object.entries(poJson)) {
// Skip metadata entries
if (key === '') continue;
// Create rich format with base value
const dotKey = key.replace(/\//g, '.'); // Convert slashes to dots
transformed[dotKey] = {
value: value,
baseValue: baseJson[key] || value
};
}
fs.writeFileSync('./es-transformed.json', JSON.stringify(transformed, null, 2));lingo-tracker import \
--source ./es-transformed.json \
--locale es \
--strategy migration \
--create-missingIf you have translations in CSV format:
// csv-to-json.js
const fs = require('fs');
const csv = require('csv-parser');
const locales = ['es', 'fr', 'de'];
const results = {};
locales.forEach(locale => results[locale] = {});
fs.createReadStream('./translations.csv')
.pipe(csv())
.on('data', (row) => {
const key = row.key;
const baseValue = row.en;
locales.forEach(locale => {
if (row[locale]) {
results[locale][key] = {
value: row[locale],
baseValue: baseValue
};
}
});
})
.on('end', () => {
locales.forEach(locale => {
fs.writeFileSync(
`./${locale}.json`,
JSON.stringify(results[locale], null, 2)
);
console.log(`Created ${locale}.json`);
});
});Expected CSV format:
key,en,es,fr,de
app.title,My App,Mi Aplicación,Mon Application,Meine App
buttons.ok,OK,Aceptar,OK,OKlingo-tracker import --source ./es.json --locale es --strategy migration --create-missing
lingo-tracker import --source ./fr.json --locale fr --strategy migration --create-missing
lingo-tracker import --source ./de.json --locale de --strategy migration --create-missingThe import command prints the summary file path at the end of its output. Open it to check each locale import:
# Path depends on your translationsFolder config — check the import output
cat translations/import-summary.mdLook for:
- Resources imported count
- Warnings (e.g., missing base values)
- Errors (e.g., malformed keys)
- ICU auto-fixes applied
Ensure all resources have entries for all configured locales:
lingo-tracker normalize --allThis adds missing locale entries with the base value as a placeholder.
Export resources with new or stale status to identify missing translations:
lingo-tracker export --format json --status new,stale --output ./reviewIf you have multiple collections, add --collection <name> to target a specific one.
Set up bundle generation for your framework:
{
"bundles": {
"app": {
"bundleName": "{locale}",
"dist": "./src/assets/i18n",
"collections": "All"
}
}
}Generate bundles:
lingo-tracker bundleAdd bundle generation to your build pipeline:
{
"scripts": {
"prebuild": "lingo-tracker bundle",
"build": "ng build",
"dev": "lingo-tracker bundle && ng serve"
}
}- Start your application
- Verify translations load correctly
- Test language switching
- Check for console errors
- Verify ICU message format placeholders work
Once migration is verified:
# Backup old files first
mv ./src/assets/i18n ./src/assets/i18n.old
# Generate fresh bundles
lingo-tracker bundle
# Test application thoroughly
# If everything works, remove backup
rm -rf ./src/assets/i18n.oldAdd LingoTracker files to version control:
# Add translations
git add translations/
# Add configuration
git add .lingo-tracker.json
# Optionally gitignore generated bundles
echo "src/assets/i18n/*.json" >> .gitignore
# Commit
git commit -m "feat: migrate to LingoTracker"Cause: JSON file doesn't include base values for new resources.
Solution: Use rich JSON format with baseValue field:
{
"app.title": {
"value": "My App",
"baseValue": "My App"
}
}Or import base locale first, then import target locales.
Cause: The baseValue in your import file differs from the existing base locale value in LingoTracker.
Effect: LingoTracker preserves the existing base value and warns you.
Solution: This is expected during migration. Review warnings to ensure base values are correct. If needed, update base values manually after migration.
Cause: A key exists as both a parent and a leaf. For example:
{
"app": "My App",
"app": {
"title": "App Title"
}
}Solution: Restructure keys to avoid conflicts:
{
"app": {
"root": "My App",
"title": "App Title"
}
}Cause: Translators modified placeholder names during translation.
Effect: LingoTracker's ICU auto-fixer corrects common placeholder errors automatically.
Check: Review import summary's "ICU Auto-Fixes" section to see what was corrected.
Manual Fix: If auto-fix failed, edit the translation manually:
# Find the resource
cat translations/path/to/resource/resource_entries.json
# Edit the translation to fix placeholdersCause: Same key appears multiple times in import file.
Effect: Last occurrence wins (matches JSON parsing behavior).
Solution: Remove duplicate keys from source file before import.
Cause: Not all locales were imported, or some translations were empty in source.
Solution:
- Run normalization:
lingo-tracker normalize --all - Export missing translations:
lingo-tracker export --status new --format xliff - Send to translators or translate manually
- Import completed translations
Cause: Browser cache or build cache serving old bundle files.
Solution:
- Clear browser cache (hard refresh: Cmd+Shift+R or Ctrl+Shift+R)
- Clear build cache:
rm -rf dist/ .angular/cache/ - Rebuild:
npm run build - Verify bundle files in
dist/are up-to-date
Cause: Using wrong strategy or missing --create-missing. Default translation-service strategy doesn't create new resources.
Solution: Use --strategy migration --create-missing for migration imports:
lingo-tracker import --source file.json --locale es --strategy migration --create-missingMigrating to LingoTracker involves:
- Prepare source files (transform format if needed)
- Dry run with
--dry-runto preview what will be created - Import with
--strategy migration --create-missing - Normalize to add missing locale entries
- Configure bundles for your framework
- Test thoroughly before removing old files
# Migration workflow (base locale first, then target locales)
lingo-tracker import --source en.json --locale en --strategy migration \
--create-missing --update-comments --update-tags
lingo-tracker import --source es.json --locale es --strategy migration \
--create-missing --update-comments --update-tags
lingo-tracker import --source fr.json --locale fr --strategy migration \
--create-missing --update-comments --update-tags
# Post-migration
lingo-tracker normalize --all
lingo-tracker bundle
# Verify (path printed at end of each import command)
cat translations/import-summary.mdFor additional help:
Need Help? Open an issue at github.com/lingotracker/lingotracker/issues