返回 Skill 列表
extension
分类: 开发与工程无需 API Key

lokalise-sdk-patterns

应用生产就绪的Lokalise SDK模式用于TypeScript和Node.js。在实现Lokalise集成、重构SDK使用或为Lokalise建立团队编码标准时使用。可以通过诸如“lokalise SDK模式”、“lokalise最佳实践”、“lokalise代码模式”、“地道的lokalise”等短语触发。

person作者: jakexiaohubgithub

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

// src/lokalise/client.ts
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,  // Enable gzip for large responses
    });
  }
  return instance;
}

// Reset for testing
export function resetLokaliseClient(): void {
  instance = null;
}

Step 2: Add Error Handling Wrapper

// src/lokalise/errors.ts
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 ApiError) {
    return {
      code: err.code,
      message: err.message,
      retryable: [429, 500, 502, 503, 504].includes(err.code),
    };
  }

  return {
    code: 0,
    message: err instanceof Error ? err.message : "Unknown error",
    retryable: false,
  };
}

Step 3: Implement Rate-Limited Queue

// src/lokalise/queue.ts
import PQueue from "p-queue";

// Lokalise: 6 requests/sec, 10 concurrent per project
const queue = new PQueue({
  concurrency: 5,  // Stay under concurrent limit
  interval: 1000,
  intervalCap: 5,  // Stay under rate limit
});

export async function queuedLokaliseCall<T>(
  operation: () => Promise<T>
): Promise<T> {
  return queue.add(operation) as Promise<T>;
}

// For batch operations
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

// src/lokalise/retry.ts
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;
      }

      // Exponential backoff with jitter
      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));
    }
  }
  throw new Error("Unreachable");
}

Step 5: Implement Cursor Pagination Helper

// src/lokalise/pagination.ts
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;  // Max 500 as of 2025
  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);
}

// Usage
async function getAllKeys(projectId: string) {
  const client = getLokaliseClient();
  const keys: any[] = [];

  for await (const key of paginateKeys(client, projectId)) {
    keys.push(key);
  }

  return 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)) {
    // Could use different tokens per project if needed
    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;
  },

  async updateTranslation(projectId, translationId, text) {
    const client = getLokaliseClient();
    return client.translations().update(translationId, {
      project_id: projectId,
      translation: text,
    });
  },
};

Branch-Aware Client

export function getProjectWithBranch(projectId: string, branch?: string): string {
  // Lokalise branch syntax: projectId:branchName
  return branch ? `${projectId}:${branch}` : projectId;
}

// Usage
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.