From lokalise-pack
Sets up Lokalise local dev loop: i18n structure, TS exports, push/pull scripts, auto-sync watching, React/Vue integrations, mocks, pre-commit hooks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/lokalise-pack:lokalise-local-dev-loopThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
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.
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.
LOKALISE_API_TOKENLOKALISE_PROJECT_IDlokalise2 CLI installedbundle_structure and most i18n frameworks.project-root/
├── src/
│ └── locales/
│ ├── en.json # Base language (source of truth)
│ ├── fr.json # Downloaded from Lokalise
│ ├── de.json
│ ├── es.json
│ └── index.ts # Barrel export + type definitions
├── scripts/
│ ├── i18n-push.sh # Upload source to Lokalise
│ ├── i18n-pull.sh # Download translations from Lokalise
│ └── i18n-mock.ts # Generate mock translations
├── .env.local # LOKALISE_API_TOKEN, LOKALISE_PROJECT_ID
└── package.json # i18n:push, i18n:pull, i18n:sync scripts
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;
}
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
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)`);
Add to package.json:
{
"scripts": {
"i18n:watch": "npx tsx scripts/i18n-watch.ts"
}
}
// scripts/i18n-mock.ts
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
const source: Record<string, string> = JSON.parse(
readFileSync("./src/locales/en.json", "utf-8")
);
// Pseudo-localization: wraps text in brackets and adds length
function pseudoLocalize(text: string): string {
// Preserve ICU placeholders like {name}, {count, plural, ...}
return text.replace(/([^{}]+)/g, (match) => {
const padded = match.replace(/[a-zA-Z]/g, (c) => {
const base = c === c.toUpperCase() ? 65 : 97;
return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base);
});
return `[${padded}]`;
});
}
// Generate longer text to test layout overflow
function stretchLocalize(text: string): string {
return `[${text}${"~".repeat(Math.ceil(text.length * 0.3))}]`;
}
const pseudo: Record<string, string> = {};
const stretch: Record<string, string> = {};
for (const [key, value] of Object.entries(source)) {
pseudo[key] = pseudoLocalize(value);
stretch[key] = stretchLocalize(value);
}
mkdirSync("./src/locales", { recursive: true });
writeFileSync("./src/locales/pseudo.json", JSON.stringify(pseudo, null, 2));
writeFileSync("./src/locales/xx-long.json", JSON.stringify(stretch, null, 2));
console.log("Generated pseudo.json and xx-long.json for testing");
Use in development:
// In your app's locale config, add mock locales for dev only
const devLocales = process.env.NODE_ENV === "development"
? { pseudo: () => import("./locales/pseudo.json"), "xx-long": () => import("./locales/xx-long.json") }
: {};
// src/i18n.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import en from "./locales/en.json";
i18n.use(initReactI18next).init({
resources: {
en: { translation: en },
},
lng: "en",
fallbackLng: "en",
interpolation: { escapeValue: false },
});
// Lazy-load other languages
export async function changeLanguage(lng: string) {
if (!i18n.hasResourceBundle(lng, "translation")) {
const mod = await import(`./locales/${lng}.json`);
i18n.addResourceBundle(lng, "translation", mod.default);
}
await i18n.changeLanguage(lng);
}
export default i18n;
// src/i18n.ts
import { createI18n } from "vue-i18n";
import en from "./locales/en.json";
const i18n = createI18n({
legacy: false,
locale: "en",
fallbackLocale: "en",
messages: { en },
});
// Lazy-load translations
export async function loadLocaleMessages(locale: string) {
if (i18n.global.availableLocales.includes(locale)) {
i18n.global.locale.value = locale;
return;
}
const messages = await import(`./locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages.default);
i18n.global.locale.value = locale;
}
export default i18n;
#!/usr/bin/env bash
# .husky/pre-commit (or .git/hooks/pre-commit)
set -euo pipefail
# Only run if locale files are staged
STAGED_LOCALES=$(git diff --cached --name-only -- 'src/locales/*.json' || true)
if [ -z "$STAGED_LOCALES" ]; then
exit 0
fi
echo "Locale files staged — pulling latest translations from Lokalise..."
# Pull latest translations
npm run i18n:pull
# Check if pull changed any staged files
CHANGED=$(git diff --name-only -- 'src/locales/*.json' || true)
if [ -n "$CHANGED" ]; then
echo ""
echo "WARNING: Lokalise has newer translations for:"
echo "$CHANGED"
echo ""
echo "Review the changes, then: git add src/locales/ && git commit"
exit 1
fi
echo "Translations are up to date"
Install with Husky:
set -euo pipefail
npx husky add .husky/pre-commit "bash .husky/pre-commit"
chmod +x .husky/pre-commit
| Error | Cause | Solution |
|---|---|---|
LOKALISE_API_TOKEN not set | Missing env variable | Add to .env.local and source it |
LOKALISE_PROJECT_ID not set | Missing env variable | Get from Lokalise dashboard > Project Settings |
File not found for push | Wrong path in script | Verify --file path matches your project structure |
Rate limit 429 | Watch mode uploading too fast | Increase debounce timeout to 5+ seconds |
Polling timeout | Large file taking too long | Add --poll-timeout 120 to CLI commands |
git diff shows unexpected changes | Lokalise reformatted JSON | Use export_sort: "a_z" and consistent formatting |
#!/usr/bin/env bash
set -euo pipefail
# One-time setup for a new project
mkdir -p src/locales scripts
# Create base language file if it doesn't exist
if [ ! -f src/locales/en.json ]; then
echo '{}' > src/locales/en.json
echo "Created empty src/locales/en.json"
fi
# Create push/pull scripts
cat > scripts/i18n-push.sh << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
lokalise2 --token "$LOKALISE_API_TOKEN" file upload \
--project-id "$LOKALISE_PROJECT_ID" \
--file ./src/locales/en.json \
--lang-iso en \
--replace-modified \
--detect-icu-plurals \
--poll
echo "Pushed source strings"
SCRIPT
cat > scripts/i18n-pull.sh << 'SCRIPT'
#!/usr/bin/env bash
set -euo pipefail
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 \
--unzip-to ./src/locales
echo "Pulled translations"
SCRIPT
chmod +x scripts/i18n-push.sh scripts/i18n-pull.sh
# Add npm scripts (requires jq)
jq '.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"}' \
package.json > package.json.tmp && mv package.json.tmp package.json
echo "Setup complete. Add LOKALISE_API_TOKEN and LOKALISE_PROJECT_ID to .env.local"
See lokalise-sdk-patterns for production-ready code patterns and advanced SDK usage.
npx claudepluginhub jeremylongshore/claude-code-plugins-plus-skills --plugin lokalise-packImplements Lokalise reference architecture for i18n in Node.js web apps, including project layout, CI/CD translation flows, and OTA/build-time tradeoffs.
Manages the full i18n lifecycle: configure settings, scaffold translation files, extract strings, track coverage, and generate pseudo-localization. Useful for setting up i18n on new projects or retrofitting existing ones.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.