Pontoon
"Mozilla Pontoon: open-source translation management, Fluent/FTL support, in-place editing, community localization, VCS sync"
Pontoon is Mozilla's open-source translation management system designed for community-driven localization. It supports the Fluent (FTL) localization format natively alongside traditional formats like PO, XLIFF, and JSON. Pontoon's standout feature is in-place translation — translators work directly on a live preview of the application rather than in a spreadsheet-style editor. It syncs with version control systems (Git, Mercurial) to keep translations aligned with the source repository. Pontoon is self-hostable and powers the localization of Firefox, MDN, and other Mozilla projects. ## Key Points - Use Fluent (FTL) format for new projects — it handles plurals, gender, and complex grammar far better than key-value JSON or gettext - Structure FTL files by feature or component so translators work on focused, contextually related strings - Enable in-place translation mode so translators see strings in context, reducing mistranslations caused by ambiguous keys - Placing variables inside Fluent terms (prefixed with `-`) — terms cannot accept external variables, only messages can - Forgetting that Pontoon's VCS sync overwrites local translation edits — always commit translation changes through Pontoon, not directly in the repository
skilldb get i18n-services-skills/PontoonFull skill: 243 linesPontoon
Core Philosophy
Pontoon is Mozilla's open-source translation management system designed for community-driven localization. It supports the Fluent (FTL) localization format natively alongside traditional formats like PO, XLIFF, and JSON. Pontoon's standout feature is in-place translation — translators work directly on a live preview of the application rather than in a spreadsheet-style editor. It syncs with version control systems (Git, Mercurial) to keep translations aligned with the source repository. Pontoon is self-hostable and powers the localization of Firefox, MDN, and other Mozilla projects.
Setup
Self-hosting with Docker
# Clone the Pontoon repository
git clone https://github.com/mozilla/pontoon.git
cd pontoon
# Copy environment template
cp .env.template .env
# Configure .env with required values:
# DATABASE_URL=postgres://pontoon:pontoon@db/pontoon
# SECRET_KEY=your-secret-key
# SITE_URL=https://pontoon.example.com
# ALLOWED_HOSTS=pontoon.example.com
# Start with Docker Compose
docker-compose up -d
Project configuration in Pontoon
# Pontoon syncs projects via VCS. Configure a project in the admin panel:
# 1. Admin -> Projects -> Add Project
# 2. Set repository URL (Git or Mercurial)
# 3. Define resource paths using the project's l10n structure
# Typical repository structure Pontoon expects:
# locales/
# en/
# messages.ftl (source locale)
# fr/
# messages.ftl
# de/
# messages.ftl
# Or for Django/gettext projects:
# locale/
# en/LC_MESSAGES/django.po
# fr/LC_MESSAGES/django.po
Fluent (FTL) file format
# messages.ftl — Mozilla's Fluent localization format
welcome-title = Welcome to { -brand-name }
welcome-description =
Discover features designed to put your
privacy first.
# Variables
login-greeting = Hello, { $userName }!
# Plurals with select expressions
emails-count =
{ $count ->
[one] You have { $count } new email.
*[other] You have { $count } new emails.
}
# Attributes for element properties
login-button =
.label = Log in
.accesskey = L
.title = Click to log in to your account
# Terms (reusable, prefixed with -)
-brand-name = Firefox
-company-name = Mozilla
Core Patterns
Using Fluent in a web application
import { FluentBundle, FluentResource } from "@fluent/bundle";
import { negotiateLanguages } from "@fluent/langneg";
// Load FTL content (fetched from your locales directory)
const ftlContent = `
welcome-title = Welcome to { -brand-name }
-brand-name = MyApp
login-greeting = Hello, { $userName }!
emails-count =
{ $count ->
[one] You have { $count } new email.
*[other] You have { $count } new emails.
}
`;
const bundle = new FluentBundle("en");
const resource = new FluentResource(ftlContent);
const errors = bundle.addResource(resource);
if (errors.length) {
console.error("Fluent parse errors:", errors);
}
// Format messages
const welcome = bundle.getMessage("welcome-title");
const formatted = bundle.formatPattern(welcome.value);
// "Welcome to MyApp"
const greeting = bundle.getMessage("login-greeting");
const greetingText = bundle.formatPattern(greeting.value, {
userName: "Alice",
});
// "Hello, Alice!"
const emails = bundle.getMessage("emails-count");
const emailText = bundle.formatPattern(emails.value, { count: 5 });
// "You have 5 new emails."
React integration with @fluent/react
import { LocalizationProvider, Localized } from "@fluent/react";
import { ReactLocalization } from "@fluent/react";
import { FluentBundle, FluentResource } from "@fluent/bundle";
function createLocalization(locale: string, ftlContent: string) {
const bundle = new FluentBundle(locale);
bundle.addResource(new FluentResource(ftlContent));
return new ReactLocalization([bundle]);
}
function App() {
const l10n = createLocalization("en", enFtl);
return (
<LocalizationProvider l10n={l10n}>
<Localized id="welcome-title">
<h1>Welcome</h1>
</Localized>
<Localized id="login-greeting" vars={{ userName: "Alice" }}>
<p>Hello!</p>
</Localized>
<Localized
id="emails-count"
vars={{ count: 3 }}
>
<p>You have emails.</p>
</Localized>
</LocalizationProvider>
);
}
Pontoon API for automation
// Pontoon exposes a GraphQL API for querying project and translation data
const PONTOON_URL = "https://pontoon.example.com";
// Fetch project translation status
const query = `
query {
project(slug: "my-project") {
name
localizations {
locale { code, name }
totalStrings
approvedStrings
missingStrings
}
}
}
`;
const response = await fetch(`${PONTOON_URL}/graphql`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
});
const { data } = await response.json();
// Report completion per locale
for (const loc of data.project.localizations) {
const pct = ((loc.approvedStrings / loc.totalStrings) * 100).toFixed(1);
console.log(`${loc.locale.name}: ${pct}% complete`);
}
VCS sync configuration
# Pontoon syncs translations to/from your repository on a schedule.
# Configure sync in the admin panel or via environment variables:
# SYNC_INTERVAL=3600 (seconds between syncs)
# Manual sync trigger via management command:
python manage.py sync_projects --projects=my-project
# Pontoon commits translations back to the repository with:
# Author: Mozilla Pontoon <pontoon@mozilla.com>
# Commit message pattern: "Update {locale} localization of {project}"
Best Practices
- Use Fluent (FTL) format for new projects — it handles plurals, gender, and complex grammar far better than key-value JSON or gettext
- Structure FTL files by feature or component so translators work on focused, contextually related strings
- Enable in-place translation mode so translators see strings in context, reducing mistranslations caused by ambiguous keys
Common Pitfalls
- Placing variables inside Fluent terms (prefixed with
-) — terms cannot accept external variables, only messages can - Forgetting that Pontoon's VCS sync overwrites local translation edits — always commit translation changes through Pontoon, not directly in the repository
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
Related Skills
Crowdin
"Crowdin: translation management, OTA content delivery, API, CLI, GitHub integration, in-context editing, glossaries"
I18next
"i18next: internationalization framework, React (react-i18next), namespaces, interpolation, plurals, backends, language detection"
Lokalise
"Lokalise: translation management, API, SDKs, OTA updates, branching, screenshots, plural forms, CLI"
Next Intl
"next-intl: Next.js internationalization, message catalogs, routing, middleware, server components, formatters, pluralization"
Phrase
"Phrase (formerly PhraseApp): translation management, API, CLI, OTA, in-context editor, branching, webhooks, glossaries"
Transifex
"Transifex: translation management, Native SDK, OTA delivery, API v3, CLI, GitHub/GitLab integration, ICU plurals"