Skip to main content
Technology & EngineeringI18n Services202 lines

Phrase

"Phrase (formerly PhraseApp): translation management, API, CLI, OTA, in-context editor, branching, webhooks, glossaries"

Quick Summary11 lines
Phrase (formerly PhraseApp) is a translation management platform that streamlines the localization workflow for development teams. It provides a centralized place to manage translation keys, collaborate with translators, and automate the sync between your codebase and translation files. Phrase supports branching to align translations with feature development, OTA (Over-The-Air) delivery for mobile and web apps, and an in-context editor that lets translators see strings in their actual UI context. The CLI and REST API enable full automation of upload/download cycles within CI/CD pipelines.

## Key Points

- Use branching to keep translation work isolated per feature and merge only when translations are reviewed and complete
- Configure fallback locales so untranslated keys display the source language rather than blank strings
- Tag keys by feature area or component so translators can work on focused batches with relevant context
- Forgetting to set `updateTranslations: true` on upload causes existing translations to be skipped, leading to stale content
- Using flat JSON keys (`welcome_title`) instead of nested keys (`welcome.title`) makes the key namespace harder to organize as the project scales
skilldb get i18n-services-skills/PhraseFull skill: 202 lines
Paste into your CLAUDE.md or agent config

Phrase

Core Philosophy

Phrase (formerly PhraseApp) is a translation management platform that streamlines the localization workflow for development teams. It provides a centralized place to manage translation keys, collaborate with translators, and automate the sync between your codebase and translation files. Phrase supports branching to align translations with feature development, OTA (Over-The-Air) delivery for mobile and web apps, and an in-context editor that lets translators see strings in their actual UI context. The CLI and REST API enable full automation of upload/download cycles within CI/CD pipelines.

Setup

CLI installation and configuration

// Install Phrase CLI
// npm install -g phrase-cli
// Or: brew install phrase-cli

// .phrase.yml at project root
// phrase:
//   access_token: PHRASE_ACCESS_TOKEN
//   project_id: "your-project-id"
//   file_format: nested_json
//   push:
//     sources:
//       - file: "./src/locales/en.json"
//         params:
//           locale_id: "en"
//           update_translations: true
//   pull:
//     targets:
//       - file: "./src/locales/<locale_code>.json"
//         params:
//           file_format: nested_json

API client setup

import { PhraseClient } from "phrase-js";

const client = new PhraseClient({
  token: process.env.PHRASE_ACCESS_TOKEN,
});

// List projects
const projects = await client.projects.list();

// List locales for a project
const locales = await client.locales.list({
  projectId: "your-project-id",
});

Core Patterns

Uploading source strings

// CLI push — uploads source files defined in .phrase.yml
// phrase push

// API upload
const upload = await client.uploads.create({
  projectId: "your-project-id",
  file: fs.createReadStream("./src/locales/en.json"),
  fileFormat: "nested_json",
  localeId: "en",
  updateTranslations: true,
  updateDescriptions: true,
});

console.log("Upload ID:", upload.id);
console.log("State:", upload.state); // "processing" | "completed"

Downloading translations

// CLI pull — downloads all locale files
// phrase pull

// API download for a specific locale
const download = await client.localeDownloads.create({
  projectId: "your-project-id",
  localeId: "fr",
  fileFormat: "nested_json",
  includeEmptyTranslations: false,
  fallbackLocaleEnabled: true,
});

// Write to file
fs.writeFileSync("./src/locales/fr.json", download);

Managing keys programmatically

// Create a key
const key = await client.keys.create({
  projectId: "your-project-id",
  name: "welcome.title",
  description: "Title shown on the welcome page",
  tags: ["onboarding", "v2"],
  maxCharactersAllowed: 50,
});

// Search keys by name or tag
const keys = await client.keys.search({
  projectId: "your-project-id",
  q: "tags:onboarding",
  sort: "updated_at",
  order: "desc",
});

// Create a translation for a key
await client.translations.create({
  projectId: "your-project-id",
  keyId: key.id,
  localeId: "de",
  content: "Willkommen",
});

Branching for feature development

// Create a branch to isolate translation work for a feature
// phrase branches create --project-id your-project-id --name feature/onboarding

const branch = await client.branches.create({
  projectId: "your-project-id",
  name: "feature/onboarding",
});

// Push to a specific branch
// phrase push --branch feature/onboarding

// Merge branch back when feature is complete
await client.branches.merge({
  projectId: "your-project-id",
  branchId: branch.id,
  strategy: "use_branch", // "use_main" | "use_branch"
});

OTA (Over-The-Air) distribution

// Create a distribution for OTA delivery
const distribution = await client.distributions.create({
  projectId: "your-project-id",
  name: "production-web",
  platforms: ["web"],
  localeIds: ["en", "fr", "de", "es"],
  fallbackLocaleEnabled: true,
  useLastReviewedVersion: true,
});

// Fetch translations at runtime via OTA
const response = await fetch(
  `https://ota.phrase.com/distributions/${distribution.id}/locales/fr.json`
);
const translations = await response.json();

Webhooks for CI/CD integration

// Register a webhook to trigger builds on translation completion
await client.webhooks.create({
  projectId: "your-project-id",
  callbackUrl: "https://ci.example.com/hooks/phrase",
  events: ["locales:download", "translations:complete"],
  active: true,
});

Best Practices

  • Use branching to keep translation work isolated per feature and merge only when translations are reviewed and complete
  • Configure fallback locales so untranslated keys display the source language rather than blank strings
  • Tag keys by feature area or component so translators can work on focused batches with relevant context

Common Pitfalls

  • Forgetting to set updateTranslations: true on upload causes existing translations to be skipped, leading to stale content
  • Using flat JSON keys (welcome_title) instead of nested keys (welcome.title) makes the key namespace harder to organize as the project scales

Anti-Patterns

Using the service without understanding its pricing model. Cloud services bill differently — per request, per GB, per seat. Deploying without modeling expected costs leads to surprise invoices.

Hardcoding configuration instead of using environment variables. API keys, endpoints, and feature flags change between environments. Hardcoded values break deployments and leak secrets.

Ignoring the service's rate limits and quotas. Every external API has throughput limits. Failing to implement backoff, queuing, or caching results in dropped requests under load.

Treating the service as always available. External services go down. Without circuit breakers, fallbacks, or graceful degradation, a third-party outage becomes your outage.

Coupling your architecture to a single provider's API. Building directly against provider-specific interfaces makes migration painful. Wrap external services in thin adapter layers.

Install this skill directly: skilldb add i18n-services-skills

Get CLI access →