Skip to content
kododo-devPublic

About

Self-hosted translation management for apps on any platform: a web editor with roles and history, and a read-only JSON API with an OpenAPI document.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

60 Commits

Folders and files

Repository files navigation

Polyglot

CI Demo

Self-hosted translation management for apps on any platform. Translations are edited in a web UI with sign-in and roles, and apps read them as plain JSON through a read-only HTTP API described by an OpenAPI document, so any stack can use them. Translations can be imported and exported as flat JSON or Excel files, for example to hand them to a translator. It runs as a Docker image on top of your own PostgreSQL. Polyglot itself is built on the CultureWay packages.

A live demo is available at kododo.dev/polyglot/demo.

Early development.

The translation editor: keys grouped by namespace, one column per language
The history of changes, with a diff and an undo button for each The API reference sending a request with the demo key and showing the JSON response
Every change is recorded and can be undone. Apps read plain JSON. Try it in the built-in API reference.

Quick start

Polyglot is published as ghcr.io/kododo-dev/polyglot for linux/amd64 and linux/arm64. Save this as compose.yaml:

services:
  polyglot:
    image: ghcr.io/kododo-dev/polyglot:latest
    ports:
      - "8080:8080"
    environment:
      ConnectionStrings__Default: "Host=db;Port=5432;Database=polyglot;Username=polyglot;Password=polyglot;Gss Encryption Mode=Disable"
      Polyglot__Cultures: "en,pl,de"
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: polyglot
      POSTGRES_USER: polyglot
      POSTGRES_PASSWORD: polyglot
    volumes:
      - polyglot-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U polyglot -d polyglot"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  polyglot-db:

Then start it and read the password of the initial administrator:

docker compose up -d
docker compose logs polyglot | grep "initial administrator"

The password of the initial admin user is printed once. Open http://localhost:8080, sign in, and open the editor at /translations. Set Polyglot__Auth__Bootstrap__Password to choose the password yourself.

Image tags: latest is the newest release, 1.2 follows the newest patch of 1.2, and 1.2.3 is one release. Pin a version in production.

Configuration

Everything is configured through environment variables (or appsettings.json).

Instance

Variable Default Description
ConnectionStrings__Default none PostgreSQL connection string. Required unless authentication is disabled; without it translations live in memory.
Polyglot__Cultures en Comma-separated cultures the instance starts with, e.g. en,pl,de. Languages added in the UI are persisted by the store.
Polyglot__DefaultCulture en Default culture. Added to the list if missing.
Polyglot__EditorPath /translations Path the editor is mounted at.
Polyglot__PathBase empty Path base when served under a sub-path behind a reverse proxy.

The container listens on port 8080 and trusts X-Forwarded-* headers. GET /health returns 200 when the app is up, for a liveness probe. GET /health/ready also checks that PostgreSQL can be reached and returns 503 when it cannot, for a readiness probe.

Authentication

Variable Default Description
Polyglot__Auth__Enabled true Set to false to run without authentication, only for instances protected some other way. Anyone who can reach the instance can then edit translations.
Polyglot__Auth__Local__Enabled true Username and password sign-in.
Polyglot__Auth__Bootstrap__Username admin Initial administrator, created when the instance has no users.
Polyglot__Auth__Bootstrap__Password generated Its password. When empty, a random one is generated and logged once.
Polyglot__Auth__LoginAttemptsPerMinute 10 Sign-in attempts allowed per client address per minute.

Roles: Admin (editor plus user management), Editor (translation editor), None (signed in, no access). Role changes and disabling a user take effect on the next request. The last active administrator cannot be demoted or disabled. Administrators manage users on the Users page, and everyone can change their own password on the account page their name at the bottom of the menu links to.

Data Protection keys (which protect the sign-in cookie) are stored in the database, so sessions survive restarts and work across replicas. They are stored unencrypted; protect access to the database accordingly.

OpenID Connect

Any OIDC provider works (Keycloak, Entra ID, Authentik, Google, ...). The app uses the authorization code flow with PKCE. Register https://<your-host>/signin-oidc as the redirect URI.

Variable Default Description
Polyglot__Auth__Oidc__Authority none Issuer URL. OIDC is enabled when this and ClientId are set.
Polyglot__Auth__Oidc__ClientId / ClientSecret none Client credentials.
Polyglot__Auth__Oidc__DisplayName Single sign-on Label of the sign-in button.
Polyglot__Auth__Oidc__ExtraScopes empty Comma-separated scopes requested in addition to openid profile email, e.g. a groups scope.
Polyglot__Auth__Oidc__RequireHttpsMetadata true Set to false only for a local provider without TLS.
Polyglot__Auth__Oidc__GroupsClaim groups Claim (in the ID token) that carries the user's groups.
Polyglot__Auth__Oidc__AdminGroup / EditorGroup none Members become Admin / Editor. With either set, the role is recomputed at every sign-in and users in neither group get None.
Polyglot__Auth__Oidc__DefaultRole Editor Role of a new user when no group is configured. Afterwards administrators manage it.

Users are created on their first sign-in. To use OIDC only, set Polyglot__Auth__Local__Enabled=false together with AdminGroup (so that someone can administer the instance).

History

Every change to a translation is recorded with its old and new value, who made it and when. Editors and administrators see the history on the History page, can filter it by key, language, user or the days of the change (in UTC), and can undo a change. An undo sets the translation back to its value before that change, and is recorded as a change of its own.

The history is kept in the database next to the users, so it needs authentication to be enabled. Nothing is removed from it yet.

Delivery API

A read-only HTTP API from which consuming apps fetch their translations. It authenticates with API keys, which administrators issue on the API keys page. A new key is shown once, when it is created. Requires authentication to be enabled.

curl -H "X-Api-Key: pg_7fk2ab91_..." https://polyglot.example.com/api/v1/cultures
curl -H "X-Api-Key: pg_7fk2ab91_..." "https://polyglot.example.com/api/v1/translations/pl?namespace=Checkout&namespace=Common"

GET /api/v1/translations/{culture} returns a flat map of full keys to values, for example {"Checkout.Pay": "Zapłać"}. Every key is resolved along the same chain CultureWay's IStringLocalizer uses: the culture, then its parents, then the default culture. namespace is optional and can be repeated. It selects keys in that namespace or under it, as the editor's namespace filter does.

Responses have an ETag. Send it back as If-None-Match and the API answers 304 when nothing has changed. An edit reaches consuming apps within SnapshotCacheSeconds plus their own poll interval.

There is no client library. To get a client, generate one from the OpenAPI 3.0 document at /openapi/v1.json (also available as .yaml). It is served without a key, and a copy is in docs/openapi/v1.json.

npx @openapitools/openapi-generator-cli generate \
  -i https://polyglot.example.com/openapi/v1.json -g typescript-fetch -o ./polyglot-client

To try the API in a browser, open /api-reference/. It is an interactive reference (Scalar) of the same document: paste a key and send requests, or copy a ready request for curl, C# and other languages. It is served without signing in, like the document, and it loads nothing from outside the instance.

Variable Default Description
Polyglot__Api__Enabled true The delivery API. Not mapped when authentication is disabled, because there is then no way to issue or revoke a key.
Polyglot__Api__RequestsPerMinute 120 Requests allowed per key per minute; requests without a usable key are counted per client address. Excess requests get 429 with Retry-After.
Polyglot__Api__SnapshotCacheSeconds 10 How long the API reuses one read of all translations. 0 reads the database on every request.
Polyglot__Api__OpenApi__Enabled true Serves the OpenAPI document and the API reference.

Design notes: docs/delivery-api.md.

Public demo

Runs an instance as a public demo. On its first start it creates the demo administrator, a few translators, the translations of a made-up shop in English, Polish and German (with some German ones missing), a history of their changes, and an API key. The sign-in page gets a "Try the demo" button, and every page a notice.

So that one visitor cannot spoil the demo for the next, users cannot be added or changed, the demo account's password cannot be changed, and the demo's API key cannot be disabled or deleted. Everything else works, including the editor, the history and new API keys. Imported files are limited to 100 KB.

The demo does not reset itself. Give it a database that is recreated on a schedule, for example PostgreSQL on a tmpfs restarted every night.

Variable Default Description
Polyglot__Demo__Enabled false Turns the demo on. Needs local sign-in.
Polyglot__Demo__Username / Password demo / polyglot-demo The demo administrator, shown on the sign-in page.
Polyglot__Demo__ApiKey a fixed public token The demo's API key, shown with example curl calls on the overview and API keys pages, and filled in on the API reference.
Polyglot__Demo__Notice a generic notice Shown above every page. Say here when the data is reset.

Development

Every page after sign-in (overview, translations, history, users, API keys, account) is a Way of one Way.Shell panel at the root of the app, so they share one menu, theme and language. The translations page is the CultureWay editor; the others are one React module in src/Polyglot.Web/UI, embedded in the app, with a JSON API per page (Ways/PolyglotWays.cs). Only sign-in and "access denied" are server-rendered pages.

cd src/Polyglot.Web/UI && npm ci && npm run build && cd ../../..   # the pages' module; dotnet build needs it
dotnet test src/Polyglot.slnx   # needs Docker (Testcontainers)
dotnet run --project src/Polyglot.Web
docker compose up --build       # builds the image from source, with PostgreSQL

npm run dev in src/Polyglot.Web/UI serves one page (?way=history) with hot reload, its API proxied to dotnet run. Browser scenarios for the demo are in e2e/check.mjs.

Adding an EF Core migration for the auth tables:

cd src/Polyglot.Web
dotnet ef migrations add <Name> --output-dir Data/Migrations

A test fails when docs/openapi/v1.json differs from the document the app serves. After changing the API, regenerate the file and commit it:

POLYGLOT_UPDATE_OPENAPI=1 dotnet test src/Polyglot.slnx --filter OpenApiTests

Releases

Pushing a v* tag (for example v0.1.0) runs CI and then publishes the image to GHCR for linux/amd64 and linux/arm64, with the version from the tag. A pre-release tag such as v0.2.0-rc.1 is published under its own tag only and does not move latest.

Roadmap

  1. Authentication and authorization: local users and OpenID Connect.
  2. API keys and a delivery API for consuming apps, with an OpenAPI document for generating clients (docs/delivery-api.md).
  3. A published Docker image, so Polyglot can be run without building it from source.
  4. History, import and export.
  5. A .NET client package that plugs a consuming app into Polyglot through IStringLocalizer.
  6. AI help with translation: connect an AI model that suggests translations into other languages.

License

MIT

About

Self-hosted translation management for apps on any platform: a web editor with roles and history, and a read-only JSON API with an OpenAPI document.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages