| name | lokalise-migration-deep-dive |
| description | Execute major migration to Lokalise from other TMS platforms with data migration strategies.
Use when migrating to Lokalise from competitors, performing data imports,
or re-platforming existing translation management to Lokalise.
Trigger with phrases like "migrate to lokalise", "lokalise migration",
"switch to lokalise", "lokalise import", "lokalise replatform".
|
| allowed-tools | Read, Write, Edit, Bash(npm:*), Bash(lokalise2:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Lokalise Migration Deep Dive
Overview
Comprehensive guide for migrating to Lokalise from other TMS platforms or legacy systems.
Prerequisites
- Access to source system for export
- Lokalise account with appropriate plan
- Understanding of current translation workflow
- Rollback strategy defined
Migration Types
| Type | Complexity | Duration | Risk |
|---|
| Fresh start | Low | Days | Low |
| From Phrase | Medium | 1-2 weeks | Medium |
| From Crowdin | Medium | 1-2 weeks | Medium |
| From POEditor | Low | Days | Low |
| From spreadsheets | Medium | 1 week | Medium |
| Custom legacy | High | 2-4 weeks | High |
Instructions
Step 1: Pre-Migration Assessment
interface MigrationAssessment {
sourceSystem: string;
totalProjects: number;
totalKeys: number;
totalLanguages: number;
translationMemory: boolean;
glossary: boolean;
screenshots: boolean;
workflows: boolean;
integrations: string[];
customizations: string[];
}
async function assessCurrentState(): Promise<MigrationAssessment> {
return {
sourceSystem: "phrase",
totalProjects: 5,
totalKeys: 15000,
totalLanguages: 12,
translationMemory: true,
glossary: true,
screenshots: true,
workflows: true,
integrations: ["github", "figma", "slack"],
customizations: ["custom placeholders", ],
};
}
(): {
: | | ;
: ;
: [];
} {
baseEffort = assessment. > ? : ;
tmEffort = assessment. ? : ;
integrationEffort = assessment.. * ;
totalDays = baseEffort + tmEffort + integrationEffort;
{
: totalDays > ? : totalDays > ? : ,
: .(totalDays),
: [
assessment. > ? : ,
assessment. ? : ,
assessment. ? : ,
].() [],
};
}
Step 2: Export from Source System
phrase pull --format json --target ./export/phrase/
crowdin download --all --export-only-approved
Step 3: Data Transformation
interface SourceKey {
key: string;
value: string;
description?: string;
tags?: string[];
context?: string;
}
interface LokaliseImportKey {
key_name: string;
platforms: string[];
description?: string;
tags?: string[];
translations: Array<{
language_iso: string;
translation: string;
}>;
}
function transformKeys(
sourceKeys: SourceKey[],
translations: Record<string, Record<string, string>>,
languages: string[]
): LokaliseImportKey[] {
return sourceKeys.map(src => ({
key_name: src.key,
platforms: ["web"],
description: src. || src.,
: src. || [],
: languages.( ({
: lang,
: translations[lang]?.[src.] || ,
})).( t.),
}));
}
(): {
(sourceSystem) {
:
key;
:
key.(, );
:
key.(, ).();
:
key;
}
}
Step 4: Create Lokalise Project
import { LokaliseApi } from "@lokalise/node-api";
const client = new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
});
async function createMigrationProject(
name: string,
languages: string[],
baseLanguage: string
): Promise<string> {
const project = await client.projects().create({
name: `${name} (Migration)`,
description: `Migrated from legacy system on ${new Date().toISOString()}`,
languages: languages.map(lang => ({
lang_iso: lang,
})),
base_lang_iso: baseLanguage,
});
console.log(`Created project: ${project.project_id}`);
return project.project_id;
}
Step 5: Import Keys and Translations
async function importKeysToLokalise(
projectId: string,
keys: LokaliseImportKey[],
batchSize = 100
): Promise<{ imported: number; errors: string[] }> {
const results = { imported: 0, errors: [] as string[] };
for (let i = 0; i < keys.length; i += batchSize) {
const batch = keys.slice(i, i + batchSize);
try {
const response = await client.keys().create({
project_id: projectId,
keys: batch,
});
results.imported += response.items.length;
console.log(`Imported ${results.imported}/${keys.length} keys`);
} catch (error: any) {
results.errors.push(`Batch ${i / batchSize}: ${error.message}`);
console.error(, error.);
}
( (r, ));
}
results;
}
(): <> {
fileContent = fs.(filePath);
base64Content = fileContent.();
process = client.().(projectId, {
: base64Content,
: path.(filePath),
: langIso,
: ,
: ,
: ,
: [],
});
.();
(projectId, process.);
}
Step 6: Migrate Translation Memory
async function importTranslationMemory(
teamId: number,
tmxFilePath: string
): Promise<void> {
const tmxContent = fs.readFileSync(tmxFilePath);
const base64Content = tmxContent.toString("base64");
await client.translationStatuses().create(teamId, {
});
console.log("Translation memory imported");
}
Step 7: Post-Migration Validation
interface ValidationResult {
passed: boolean;
checks: Array<{
name: string;
passed: boolean;
details: string;
}>;
}
async function validateMigration(
projectId: string,
expectedKeys: number,
expectedLanguages: string[]
): Promise<ValidationResult> {
const checks: ValidationResult["checks"] = [];
const keys = await client.keys().list({
project_id: projectId,
limit: 1,
});
const keyCountMatch = keys.total_count >= expectedKeys * 0.95;
checks.push({
name: "Key count",
passed: keyCountMatch,
details: `Found ${keys.total_count}, expected ~${expectedKeys}`,
});
const languages = await client.().({ : projectId });
langCodes = languages..( l.);
languagesMatch = expectedLanguages.( langCodes.(l));
checks.({
: ,
: languagesMatch,
: ,
});
( lang expectedLanguages.( l !== )) {
langData = languages..( l. === lang);
coverage = langData?.?. ?? ;
checks.({
: ,
: coverage > ,
: ,
});
}
{
: checks.( c.),
checks,
};
}
Output
- Migration assessment complete
- Data exported and transformed
- Lokalise project created
- Keys and translations imported
- Migration validated
Error Handling
| Issue | Cause | Solution |
|---|
| Key name conflicts | Different naming conventions | Normalize keys before import |
| Missing translations | Export incomplete | Re-export from source |
| Encoding issues | Non-UTF8 files | Convert to UTF-8 |
| Rate limit during import | Too fast | Increase delays between batches |
| Placeholder mismatch | Different syntax | Transform placeholders |
Examples
Placeholder Transformation
function convertPlaceholders(
text: string,
fromFormat: "printf" | "icu" | "curly",
toFormat: "icu"
): string {
if (fromFormat === "printf") {
let index = 0;
return text.replace(/%(\d+\$)?[sd]/g, () => `{${index++}}`);
}
if (fromFormat === "curly") {
return text.replace(/\{\{(\w+)\}\}/g, "{$1}");
}
return text;
}
Migration Rollback
#!/bin/bash
lokalise2 --token "$LOKALISE_API_TOKEN" \
project delete --project-id "$NEW_PROJECT_ID"
echo "Migration rolled back. Continue using source system."
Full Migration Script
async function runMigration() {
console.log("=== Lokalise Migration ===\n");
const assessment = await assessCurrentState();
const estimate = estimateMigrationEffort(assessment);
console.log(`Estimated effort: ${estimate.effort} (~${estimate.estimatedDays} days)`);
const projectId = await createMigrationProject(
"My App",
["en", "es", "fr", "de"],
"en"
);
const keys = await loadTransformedKeys("./export/");
const importResult = await importKeysToLokalise(projectId, keys);
console.log(`Imported ${importResult.imported} keys`);
const validation = await validateMigration(projectId, keys.length, ["en", "es", "fr", "de"]);
(validation.) {
.();
} {
.();
validation..( !c.).( {
.();
});
}
{ projectId, validation };
}
Resources
Flagship+ Skills
For advanced troubleshooting, see lokalise-common-errors.