openrouter-compliance-review
Review OpenRouter integration for regulatory compliance (SOC2, GDPR,
lokalise-local-dev-loop
Configure Lokalise local development with file sync and hot reload.
Full skill instructions
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.
Review OpenRouter integration for regulatory compliance (SOC2, GDPR,
Data synchronization, ETL patterns, and data management for MaintainX.
Execute Groq primary workflow: chat completions with tool use and JSON
Validate CSRF protection implementations for security gaps. Use when
Configure this skill should be used when the user asks about "API contract",
Execute Ideogram incident response with triage, mitigation, and postmortem.
Execute create, select, and transform features to improve machine learning
Reusable patterns for the Gamma REST API (no SDK exists).
Apply production-ready Deepgram SDK patterns for TypeScript and Python.
Migrate to OpenRouter from direct provider APIs or upgrade between SDK/model
Diagnose and fix common OpenRouter API errors. Use when encountering
Optimize Linear API usage, reduce unnecessary calls, and maximize
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience