| name | lokalise-sdk-patterns |
| description | Apply production-ready Lokalise SDK patterns for TypeScript and Node.js.
Use when implementing Lokalise integrations, refactoring SDK usage,
or establishing team coding standards for Lokalise.
Trigger with phrases like "lokalise SDK patterns", "lokalise best practices",
"lokalise code patterns", "idiomatic lokalise".
|
| allowed-tools | Read, Write, Edit |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Lokalise SDK Patterns
Overview
Production-ready patterns for Lokalise SDK usage in TypeScript and Node.js applications.
Prerequisites
- Completed
lokalise-install-auth setup
- Familiarity with async/await and TypeScript
- Understanding of error handling best practices
Instructions
Step 1: Implement Singleton Client Pattern
import { LokaliseApi } from "@lokalise/node-api";
let instance: LokaliseApi | null = null;
export function getLokaliseClient(): LokaliseApi {
if (!instance) {
const apiKey = process.env.LOKALISE_API_TOKEN;
if (!apiKey) {
throw new Error("LOKALISE_API_TOKEN environment variable is required");
}
instance = new LokaliseApi({
apiKey,
enableCompression: true,
});
}
return instance;
}
export function resetLokaliseClient(): void {
instance = null;
}
Step 2: Add Error Handling Wrapper
import { ApiError } from "@lokalise/node-api";
interface LokaliseResult<T> {
data: T | null;
error: LokaliseError | null;
}
interface LokaliseError {
code: number;
message: string;
retryable: boolean;
}
export async function safeLokaliseCall<T>(
operation: () => Promise<T>
): Promise<LokaliseResult<T>> {
try {
const data = await operation();
return { data, error: null };
} catch (err) {
const error = parseLokaliseError(err);
console.error("[Lokalise]", error);
return { data: null, error };
}
}
function parseLokaliseError(err: unknown): LokaliseError {
if (err instanceof ) {
{
: err.,
: err.,
: [, , , , ].(err.),
};
}
{
: ,
: err ? err. : ,
: ,
};
}
Step 3: Implement Rate-Limited Queue
import PQueue from "p-queue";
const queue = new PQueue({
concurrency: 5,
interval: 1000,
intervalCap: 5,
});
export async function queuedLokaliseCall<T>(
operation: () => Promise<T>
): Promise<T> {
return queue.add(operation) as Promise<T>;
}
export async function batchLokaliseOperations<T, R>(
items: T[],
operation: (item: T) => Promise<R>
): Promise<R[]> {
return Promise.all(
items.map(item => queuedLokaliseCall(() => operation(item)))
);
}
Step 4: Add Retry Logic
export async function withRetry<T>(
operation: () => Promise<T>,
maxRetries = 3,
baseDelayMs = 1000
): Promise<T> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (err: any) {
const isRetryable = err.code === 429 || (err.code >= 500 && err.code < 600);
if (!isRetryable || attempt === maxRetries) {
throw err;
}
const delay = baseDelayMs * Math.pow(2, attempt - 1);
const jitter = Math.random() * 500;
console.log(`[Lokalise] Retry ${attempt}/${maxRetries} in ${delay}ms...`);
await new Promise(r => setTimeout(r, delay + jitter));
}
}
();
}
Step 5: Implement Cursor Pagination Helper
import { LokaliseApi, PaginatedResult } from "@lokalise/node-api";
export async function* paginateKeys(
client: LokaliseApi,
projectId: string,
options: { limit?: number } = {}
): AsyncGenerator<any> {
const limit = options.limit || 500;
let cursor: string | undefined;
do {
const result = await client.keys().list({
project_id: projectId,
limit,
pagination: "cursor",
cursor,
});
for (const key of result.items) {
yield key;
}
cursor = result.hasNextCursor() ? result.nextCursor : undefined;
} while (cursor);
}
async function getAllKeys(projectId: string) {
client = ();
: [] = [];
( key (client, projectId)) {
keys.(key);
}
keys;
}
Output
- Type-safe client singleton with compression
- Robust error handling with retryable detection
- Rate-limited request queue
- Automatic retry with exponential backoff
- Cursor pagination for large datasets
Error Handling
| Pattern | Use Case | Benefit |
|---|
| Safe wrapper | All API calls | Prevents uncaught exceptions |
| Request queue | Bulk operations | Respects rate limits |
| Retry logic | Transient failures | Improves reliability |
| Pagination | Large datasets | Memory efficient |
Examples
Factory Pattern (Multi-Project)
const clients = new Map<string, LokaliseApi>();
export function getClientForProject(projectId: string): LokaliseApi {
if (!clients.has(projectId)) {
clients.set(projectId, new LokaliseApi({
apiKey: process.env.LOKALISE_API_TOKEN!,
enableCompression: true,
}));
}
return clients.get(projectId)!;
}
Typed Response Wrapper
import { Key, Translation, Project } from "@lokalise/node-api";
interface LokaliseService {
getProject(id: string): Promise<Project>;
listKeys(projectId: string): Promise<Key[]>;
updateTranslation(projectId: string, translationId: number, text: string): Promise<Translation>;
}
export const lokaliseService: LokaliseService = {
async getProject(id) {
const client = getLokaliseClient();
return client.projects().get(id);
},
async listKeys(projectId) {
const client = getLokaliseClient();
const result = await client.keys().list({ project_id: projectId });
return result.items;
},
() {
client = ();
client.().(translationId, {
: projectId,
: text,
});
},
};
Branch-Aware Client
export function getProjectWithBranch(projectId: string, branch?: string): string {
return branch ? `${projectId}:${branch}` : projectId;
}
const projectId = getProjectWithBranch("123456.abcdef", "feature/new-ui");
const keys = await client.keys().list({ project_id: projectId });
Resources
Next Steps
Apply patterns in lokalise-core-workflow-a for real-world usage.