Migration Guide
This guide provides step-by-step instructions for migrating translation resources from other translation management systems to LingoTracker.
Table of Contents
- Overview
- Before You Begin
- Migrating from Transloco
- Migrating from i18next
- Migrating from Other Systems
- Post-Migration Steps
- Troubleshooting
Overview
What is Migration?
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
Supported Source Formats
- JSON: Flat or hierarchical structures (Transloco, i18next, etc.)
- XLIFF: Industry standard format from CAT tools
- Custom Formats: Transform to JSON or XLIFF first
What Gets Migrated
- Translation keys and values (all locales)
- Base locale values (source language)
- Comments (if present in source format)
- Tags (if present in source format)
What Doesn't Get Migrated
- Translation history
- Translator assignments
- Review workflows
- Custom metadata (not in standard formats)
Before You Begin
1. Initialize LingoTracker
Create a new LingoTracker project or ensure you have one initialized:
lingo-tracker init
Configure .lingo-tracker.json with your base locale and target locales:
{
"baseLocale": "en",
"locales": ["en", "es", "fr", "de"],
"collections": {
"Main": {
"translationsFolder": "translations"
}
}
}
2. Backup Source Data
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.backup
If something goes wrong, restore with:
cp -r ./src/i18n.backup ./src/i18n
3. Choose Migration Strategy
LingoTracker'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
Migrating from Transloco
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.
Source Format
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"
}
}
Step 1: Import Base Locale
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 \
--verbose
Note: 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.
Step 2: Import Target Locales
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-tags
If 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.
Step 3: Handle Transloco References
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.
Step 4: Verify Migration
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.md
Verify a few resources were created correctly:
cat translations/common/buttons/resource_entries.json
Step 5: Update Transloco Configuration
After migration, update your Transloco loader to use LingoTracker bundles:
Before:
loader: TranslocoHttpLoader // Loads from src/assets/i18n
After:
// 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 files
Migrating from i18next
i18next 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.
Source Format
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"
}
}
Step 1: Combine Namespaces
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.js
Step 2: Import Base Locale
Import 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 \
--verbose
Step 3: Import Target Locales
Import 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-tags
Step 4: Update i18next Configuration
After 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 bundle
Update 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.
Migrating from Other Systems
From JSON Files (Generic)
If your system uses JSON files in a custom format:
Step 1: Transform to LingoTracker 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"
}
}
}
Step 2: Import with Migration Strategy
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-missing
From XLIFF Files
If your source system exports XLIFF files (common for CAT tools like SDL Trados, MemoQ, Smartling):
Step 1: Ensure XLIFF 1.2 Format
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
Step 2: Import XLIFF Files
# Import each locale
lingo-tracker import \
--source ./es.xliff \
--locale es \
--strategy migration \
--create-missing \
--verbose
XLIFF files include both source and target values, so LingoTracker automatically creates resources with proper base values.
From PO Files (gettext)
If your source system uses PO files (gettext format):
Step 1: Convert PO to JSON
Use a conversion tool like po2json:
npm install -g po2json
# Convert PO to JSON
po2json es.po es.json --format mf
Step 2: Transform to LingoTracker Format
// 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));
Step 3: Import
lingo-tracker import \
--source ./es-transformed.json \
--locale es \
--strategy migration \
--create-missing
From CSV Files
If you have translations in CSV format:
Step 1: Convert CSV to JSON
// 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,OK
Step 2: Import
lingo-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-missing
Post-Migration Steps
1. Verify Import Summary
The 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.md
Look for:
- Resources imported count
- Warnings (e.g., missing base values)
- Errors (e.g., malformed keys)
- ICU auto-fixes applied
2. Run Normalization
Ensure all resources have entries for all configured locales:
lingo-tracker normalize --all
This adds missing locale entries with the base value as a placeholder.
3. Review Missing Translations
Export resources with new or stale status to identify missing translations:
lingo-tracker export --format json --status new,stale --output ./review
If you have multiple collections, add --collection <name> to target a specific one.
4. Configure Bundles
Set up bundle generation for your framework:
{
"bundles": {
"app": {
"bundleName": "{locale}",
"dist": "./src/assets/i18n",
"collections": "All"
}
}
}
Generate bundles:
lingo-tracker bundle
5. Update Build Scripts
Add bundle generation to your build pipeline:
{
"scripts": {
"prebuild": "lingo-tracker bundle",
"build": "ng build",
"dev": "lingo-tracker bundle && ng serve"
}
}
6. Test Application
- Start your application
- Verify translations load correctly
- Test language switching
- Check for console errors
- Verify ICU message format placeholders work
7. Remove Old Translation Files
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.old
8. Update Version Control
Add 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"
Troubleshooting
Import Fails: "Cannot create resource: base value not provided"
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.
Import Warning: "Base value mismatch"
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.
Keys Not Created: Hierarchical conflict
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"
}
}
ICU Placeholders Not Working
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 placeholders
Duplicate Keys Warning
Cause: 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.
Missing Translations After Migration
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
Application Loads Old 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
Import Summary Shows Skipped Resources
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-missing
Summary
Migrating 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
Quick Reference
# 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.md
For additional help:
Need Help? Open an issue at github.com/lingotracker/lingotracker/issues