Skip to main content

Migration Guide

This guide provides step-by-step instructions for migrating translation resources from other translation management systems to LingoTracker.

Table of Contents​

  1. Overview
  2. Before You Begin
  3. Migrating from Transloco
  4. Migrating from i18next
  5. Migrating from Other Systems
  6. Post-Migration Steps
  7. 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:

  1. Direct Migration: Import translations directly using the migration strategy
  2. 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-missing is specified
  • Resolves Transloco-style key references ({{t('key')}})
  • Updates comments and tags when --update-comments / --update-tags are specified
  • Sets all imported translations to translated status

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​

  1. Start your application
  2. Verify translations load correctly
  3. Test language switching
  4. Check for console errors
  5. 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:

  1. Run normalization: lingo-tracker normalize --all
  2. Export missing translations: lingo-tracker export --status new --format xliff
  3. Send to translators or translate manually
  4. Import completed translations

Application Loads Old Translations​

Cause: Browser cache or build cache serving old bundle files.

Solution:

  1. Clear browser cache (hard refresh: Cmd+Shift+R or Ctrl+Shift+R)
  2. Clear build cache: rm -rf dist/ .angular/cache/
  3. Rebuild: npm run build
  4. 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:

  1. Prepare source files (transform format if needed)
  2. Dry run with --dry-run to preview what will be created
  3. Import with --strategy migration --create-missing
  4. Normalize to add missing locale entries
  5. Configure bundles for your framework
  6. 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