Configure Lokalise local development with file sync and hot reload.
Use when setting up a development environment, configuring translation sync,
or establishing a fast iteration cycle with Lokalise.
Trigger with phrases like "lokalise dev setup", "lokalise local development",
"lokalise dev environment", "develop with lokalise", "lokalise sync".
Set up a complete local development workflow with Lokalise: project structure for i18n files, CLI push/pull commands, file watching for auto-upload, mock translations for offline development, framework integration (React i18next, Vue i18n), and a pre-commit hook to keep translations synced.
Prerequisites
Lokalise API token exported as LOKALISE_API_TOKEN
Lokalise project ID exported as LOKALISE_PROJECT_ID
Node.js 18+ with npm or pnpm
lokalise2 CLI installed
Git (for pre-commit hook)
Instructions
Set up the project directory structure for i18n files, compatible with Lokalise's bundle_structure and most i18n frameworks.
Barrel export with type safety (src/locales/index.ts):
import en from "./en.json";
// Type derived from base language — all other locales must match this shape
export type TranslationKeys = typeof en;
export const defaultLocale = "en" as const;
export const supportedLocales = ["en", "fr", "de", "es"] as const;
export type Locale = (typeof supportedLocales)[number];
export async function loadLocale(locale: Locale): Promise<TranslationKeys> {
const mod = await import(`./${locale}.json`);
return mod.default;
}
Create CLI push/pull scripts for the upload-translate-download cycle.
Push script (scripts/i18n-push.sh):
#!/usr/bin/env bash
set -euo pipefail
# Upload source language file to Lokalise
lokalise2 --token "$LOKALISE_API_TOKEN" file upload \
--project-id "$LOKALISE_PROJECT_ID" \
--file ./src/locales/en.json \
--lang-iso en \
--replace-modified \
--include-path \
--detect-icu-plurals \
--poll \
--tag-inserted-keys \
--tag-updated-keys
echo "Source strings pushed to Lokalise"
Pull script (scripts/i18n-pull.sh):
#!/usr/bin/env bash
set -euo pipefail
# Download all translations from Lokalise
lokalise2 --token "$LOKALISE_API_TOKEN" file download \
--project-id "$LOKALISE_PROJECT_ID" \
--format json \
--original-filenames=false \
--bundle-structure "%LANG_ISO%.json" \
--export-empty-as base \
--export-sort a_z \
--replace-breaks=false \
--placeholder-format icu \
--unzip-to ./src/locales
echo "Translations pulled to ./src/locales/"
# Show what changed
git diff --stat src/locales/ || true
Package.json scripts:
{
"scripts": {
"i18n:push": "bash scripts/i18n-push.sh",
"i18n:pull": "bash scripts/i18n-pull.sh",
"i18n:sync": "npm run i18n:push && npm run i18n:pull"
}
}
Typical workflow:
# Edit source strings locally
vim src/locales/en.json
# Push changes to Lokalise
npm run i18n:push
# ... translators work in Lokalise UI ...
# Pull completed translations
npm run i18n:pull
# Full round-trip
npm run i18n:sync
Set up watch mode to auto-upload source strings when en.json changes during development.
// scripts/i18n-watch.ts — run with: npx tsx scripts/i18n-watch.ts
import { watch } from "node:fs";
import { execSync } from "node:child_process";
const SOURCE_FILE = "./src/locales/en.json";
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
function pushToLokalise() {
console.log(`[${new Date().toISOString()}] Uploading ${SOURCE_FILE}...`);
try {
execSync("npm run i18n:push", { stdio: "inherit" });
console.log("Upload complete\n");
} catch (err) {
console.error("Upload failed:", (err as Error).message);
}
}
watch(SOURCE_FILE, (eventType) => {
if (eventType !== "change") return;
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(pushToLokalise, 2000); // 2s debounce
});
console.log(`Watching ${SOURCE_FILE} for changes... (Ctrl+C to stop)`);