Getting started

From an empty application to the first translated string in about five minutes.

1. Create an application

Sign in with Google and create an application. Every application belongs to your organisation and has its own keys, translations and API keys.

  • Slug identifies the application in every API URL. Keep it short and stable, it cannot be reused later.
  • Source language is the language your developers write defaults in. DeepL suggestions and bulk translation start from it.
  • Enabled languages are the languages you ship. Only these appear in the translate queue and are served by the API. You can add more at any time.

After saving, open the Connect screen. It shows the public API key of the application together with a ready-made snippet. The public key may only read translations and report missing keys, so it is safe to ship in a browser bundle.

2. Install the plugin

The plugin wraps i18next-http-backend with the right URLs and headers and adds usage tracking. It works wherever i18next works.

Terminal
yarn add @sprachrohr/i18next i18next i18next-http-backend

Plain JavaScript

i18n.ts
import i18next from 'i18next'
import { sprachrohr } from '@sprachrohr/i18next'

await i18next.use(sprachrohr({ slug: 'my-app', apiKey: process.env.SPRACHROHR_KEY! })).init({ lng: 'de_DE' })

document.title = i18next.t('page.title', 'My app')

Always pass a default as the second argument. It is what your users see until the string is translated, and it is what gets stored as the source text when the key is created.

React (react-i18next)

i18n.tsx
import i18next from 'i18next'
import { initReactI18next } from 'react-i18next'
import { sprachrohr } from '@sprachrohr/i18next'

void i18next
  .use(initReactI18next)
  .use(sprachrohr({ slug: 'my-app', apiKey: process.env.SPRACHROHR_KEY! }))
  .init({ lng: 'de_DE', fallbackLng: 'en_GB' })

// in a component
const { t } = useTranslation()
return <h1>{t('scene.home.title', 'Welcome')}</h1>

Next.js (App Router)

Server components need an i18next instance per request. Client components use the React setup above. Both talk to the same application, so keys created on the server are translated once and used everywhere.

server.ts
// src/i18n/server.ts – one instance per request in server components
import { createInstance } from 'i18next'
import { sprachrohr } from '@sprachrohr/i18next'

export async function withT(lng: string) {
  const instance = createInstance()
  await instance.use(sprachrohr({ slug: 'my-app', apiKey: process.env.SPRACHROHR_KEY! })).init({ lng })
  return instance.getFixedT(lng)
}

// src/app/page.tsx
export default async function Page() {
  const t = await withT('de_DE')
  return <h1>{t('scene.home.title', 'Welcome')}</h1>
}

Without a plugin

Translations are plain JSON per language and namespace, so any HTTP client works. Point i18next-http-backend or your own loader at this URL:

Terminal
curl 'https://app.sprachrohr.net/api/translations/<slug>/de_DE/default' \
  -H 'Authorization: Bearer <public api key>'

The response is a flat object of key to translation. Languages are written as de_DE, the default namespace is default.

3. Let keys appear as you code

The plugin enables saveMissing. Whenever your code renders a key the API does not know yet, i18next posts it to Sprachrohr and the key shows up in the application within seconds, with the default text as its source translation.

This is meant for development and staging. In production the strings should already exist, but nothing breaks if a new key slips through: it is created, users see the default, translators fill it.

To turn it off, pass saveMissing: false to sprachrohr() itself. Setting it only in init() is not enough, because the plugin configures i18next after your options are applied.

i18n.ts
// pass the option to the plugin, not only to init()
i18next.use(sprachrohr({ slug: 'my-app', apiKey: process.env.SPRACHROHR_KEY!, saveMissing: false })).init({ lng: 'de_DE' })
Missing keys are always created in the default namespace. i18next itself starts with a namespace called translation; the plugin remaps that untouched default to default so that the keys land where the API serves them. Namespaces you configure yourself are left alone.

4. Usage tracking

In production the plugin collects the keys that were actually rendered and reports them in batches. Every key in Sprachrohr shows when it was last used; keys that never show up can be removed with the cleanup action, including their translations.

Tips

  1. See key names in the UI. i18next ships a pseudo language that returns the key instead of the translation. Switch to it to find out which key a text belongs to:
    i18next.changeLanguage('cimode') // every t() now returns its key
  2. Deep link to a key. The translations table of an application accepts a query parameter, so you can link straight to a key from bug reports or code reviews: https://app.sprachrohr.net/applications/<id>?query=<key>
  3. Namespaces are optional. Most applications live happily in the namespace default, which is also what the URL of the translation endpoint ends with. Use your own namespaces when you want to load parts of a large app separately.
Keep the public key out of server logs and never use the import key in a browser. The import key can create and overwrite translations, the public key cannot.