Build Tolgee Cloud

KINDAreplaces $56.52/mosaves $678.24/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

A localisation workspace whose two real problems are that plural and gender rules differ enormously between languages, and that a source string edited in English silently leaves twenty translations describing something else until someone notices in production.

estimated effort multi-daythe files for this build are in the project pack

FormatMessages stored in a standard format with named placeholdersPluralsFull plural category support per languageStalenessTranslations invalidated when their source changesDeliveryVersioned bundles per locale, fetched at build or runtimeRuntimeTypeScript with PostgreSQL

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the server and the bundle publisher.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. installfree locally

    Why Keys, versions and staleness relationships.

    Get it Docker or your package manager. open ↗

    Verify psql connects and creates a table

  3. have readyfree

    Why Designing around English gives two forms, and several languages need four to six, which cannot be retrofitted without revisiting every plural string.

    Get it Take the plural rules from the standard locale data for every language you intend to support, including at least one with more than two categories.

    Verify Category lists exist per locale and at least one locale has four or more

  4. have readyfree

    Why Placeholder reordering, plural complexity and length constraints all only appear when the targets are genuinely different from the source.

    Get it Choose targets that differ structurally, including one with more plural categories and one substantially longer than English.

    Verify Four locales are configured, one with four or more plural forms

  5. have readyfree

    Why Context problems, length constraints and unused keys only become visible against a real codebase with a few hundred keys and some accumulated cruft.

    Get it Export the strings from an application you actually work on, including the keys nobody is sure are still used.

    Verify At least three hundred keys are imported with their code locations

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- keys(id, namespace, name, description, screenshot_path, max_length, created_at, deprecated_at)
- source_strings(key_id, text, plural_forms_json, version, updated_at) · the source of truth
- translations(id, key_id, locale, text, plural_forms_json, source_version, state, translated_by, at)
- state · current, stale, missing or needs_review. Stale means the source moved after this was written
- locales(code, plural_categories_json, direction, fallback_locale)
- bundles(id, locale, version, content_hash, published_at) · immutable, so a client fetches a consistent set
- usages(key_id, file, line, last_seen_at) · so an unused key can be found and removed
- A translation records the source version it was made from, which is the whole mechanism for detecting staleness.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
DATABASE_URLsecretrequiredpostgres://localhost:5432/i18nKeys, translations and version history.
SOURCE_LOCALErequiredenThe source of truth. Edits to it mark other locales stale.
FALLBACK_CHAINrequiredregional,base,sourceDocumented, and never resulting in an empty string.
SERVE_STALE_TRANSLATIONSrequiredalwaysA stale translation beats an English string in a French interface.
BUNDLE_CDN_URLrequiredhttps://i18n.example.com/bundlesWhere immutable bundles are served from.
REQUIRE_KEY_CONTEXTrequiredwarnWhether a context-free key blocks or warns. A bare word is ambiguous.

The build, in order

  1. Messages that survive being translated

    A string can be translated into a language that orders words differently.

    1. Positional ones swap values in languages that reorder.

      Files src/messages.ts

    2. A pure function, unit tested with a dropped and an invented placeholder.

      Files src/validate.tssrc/validate.test.ts

    3. A missing placeholder is a runtime error in someone's language.

      Files src/translations.ts

    done when · tick each as it passes
    watch out
    • Positional placeholders assume the target language keeps the same word order, which many do not, so a sentence with two substitutions comes out with the values swapped and nobody testing in English ever sees it. Use named placeholders and validate the set matches between source and translation.
  2. Plurals in languages that are not English

    Every locale gets exactly the plural forms its language requires.

    1. From the standard data, not from an assumption about English.

      Files src/locales.ts

    2. Two forms is an English answer to a general question.

      Files src/plurals.tssrc/plurals.test.ts

    3. Tested across a range of numbers per locale, against expected output.

      Files src/select.ts

    done when · tick each as it passes
    watch out
    • Designing the plural model around English gives you two forms, and then Arabic needs six and Russian and Polish need four, so the translations for those languages are either wrong or impossible to express. Model the categories per locale from the outset, since retrofitting means revisiting every plural string in every language.
  3. A changed source invalidates its translations

    Editing English marks the other twenty languages as stale.

    1. The mechanism the whole phase rests on.

      Files src/versions.ts

    2. Invalidating on every edit gets the feature turned off.

      Files src/edits.ts

    3. A stale translation beats an English string in a French interface.

      Files src/serve.ts

    done when · tick each as it passes
    watch out
    • Treating every source edit as invalidating everything means fixing a typo sends twenty languages back for retranslation, and the team turns the feature off within a month. Let the editor mark a change as not affecting meaning, record who said so, and keep the invalidation for changes that actually alter what the string says.
  4. Context, because a word is not a sentence

    A translator can see where a string appears and how much room it has.

    1. A bare word is ambiguous in ways English speakers do not notice.

      Files src/keys.ts

    2. So unused keys can be removed and context can be found.

      Files src/usage.ts

    3. German is longer. The button is not.

      Files src/constraints.ts

    done when · tick each as it passes
    watch out
    • A translator handed a bare word with no context cannot tell whether it is a noun or a verb, and English is full of words that are both, which produces translations that are grammatically correct and completely wrong. Attach context to the key, and treat a key without any as an incomplete key rather than a normal one.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more localization.