What you need
- Flarum 2.0 or later, on PHP 8.3 or later.
- Shell access to run Composer and
php flarumon the server. - An API key for one of the translation services, or a local model. See Choose a translation service.
Install in three steps
- Subscribe on Floxum and follow the instructions on your Floxum subscriptions page to give Composer access to Floxum's package repository.
- On the server, in the folder where Flarum is installed, run:
composer require karelialabs/flarum-translate
php flarum migrate
php flarum cache:clear
- In the admin panel, open Extensions, enable Karelia Translate and open its settings.
Set it up in two minutes
- Under Translation service, choose a service. Only the fields that service needs are shown.
- Paste the API key and, for an OpenAI-compatible service, the address and the model name.
- Choose Save Changes, then Test the connection. It translates one sentence with your saved settings and shows the result, the time it took and the service that answered.
- Under Permissions, check who may use Translate posts. Members have it by default. Guests do not, so bots cannot spend your budget.
- Optional: find the language of posts written before the extension was installed. This runs on your server and costs nothing:
php flarum karelia-translate:detect
Add --all to detect the language of every post again, for example after a large import.
How readers use it
Under every post written in another language than the reader's, a small bar shows the language of the post and a link such as Translate to English. One click shows the translation under the original. Hide translation folds it away, and Show translation brings it back without asking the service again.
The first post of a discussion is translated together with the discussion title. Every translation ends with a credit line, for example "Translated from Portuguese by DeepL". Translations from OpenAI-compatible services are credited to "AI", because model names mean little to readers.
The link is not shown:
- on posts that are already in the reader's language;
- on posts whose language could not be detected, unless Offer translation when the language is unknown is on;
- to readers who do not have the Translate posts permission;
- while a post is being edited.
Choose a translation service
| Service | Good for | What you need |
|---|---|---|
| DeepL | The best quality for European languages. DeepL API Free includes 500,000 characters a month | A DeepL API key. Free keys end with :fx and work too |
| Google Cloud Translation | The widest list of languages | A Google Cloud API key with the Cloud Translation API enabled |
| Anthropic Claude | Natural translations of slang and informal posts | An Anthropic API key. The model is set under Claude model |
| OpenAI-compatible | OpenAI, Mistral, Gemini, DeepSeek, OpenRouter, and local models in LM Studio, Ollama or llama.cpp | The address, the model name and, except for local models, a key |
| LibreTranslate | Free translation on your own server | The address of your LibreTranslate server, and a key if it requires one |
Addresses for OpenAI-compatible services
| Service | Address |
|---|---|
| OpenAI | https://api.openai.com/v1 |
| Mistral | https://api.mistral.ai/v1 |
| Gemini | https://generativelanguage.googleapis.com/v1beta/openai |
| OpenRouter | https://openrouter.ai/api/v1 |
| LM Studio on the same server | http://127.0.0.1:1234/v1 |
| Ollama on the same server | http://127.0.0.1:11434/v1 |
Use the model name exactly as the service writes it. Translation does not need the largest model; a small, fast one is usually enough. For local reasoning models (for example Gemma or Qwen in LM Studio), turn on Turn off model thinking if translations come back empty.
LibreTranslate with Docker
LibreTranslate is open source and keeps every word on your server. Load only the languages your forum needs, so it starts faster and uses less memory:
docker run -d --name libretranslate -p 127.0.0.1:5000:5000 \
-e LT_LOAD_ONLY=en,sv,de \
-v libretranslate-models:/home/libretranslate/.local \
libretranslate/libretranslate
Then set LibreTranslate address to http://127.0.0.1:5000. The first start downloads the language models and takes a few minutes. Quality is below DeepL and the large AI models, but it costs nothing.
Claude models
The default model is claude-opus-5-5. claude-haiku-5-5 costs a fraction of that and is usually good enough for translation.
Settings
| Setting | What it does |
|---|---|
| Translation service | The service that translates posts. Only its own fields are shown |
| Translate into | The reader's forum language (default) or The reader's browser language. With only one language pack installed, the browser language lets readers translate into their own language anyway |
| Character limit per month | Translation stops for the rest of the month when the limit is reached. Default 500,000. 0 means no limit |
| New translations per reader per hour | Default 30. Translations that are already saved do not count, and admins have no limit. 0 means no limit |
| Offer translation when the language is unknown | Very short posts often have no detected language. Turn on to show the link on them too |
| Never translate | One word or name per line, for example your forum's name or product names. They are sent as placeholders and come back exactly as written |
The Status box at the bottom shows how many characters have been used this month, and holds the connection test.
Keys in environment variables
An environment variable wins over the setting, so keys can stay out of the database:
| Service | Variable |
|---|---|
| DeepL | KARELIA_TRANSLATE_DEEPL_KEY |
KARELIA_TRANSLATE_GOOGLE_KEY | |
| OpenAI-compatible | KARELIA_TRANSLATE_OPENAI_KEY |
| Anthropic | KARELIA_TRANSLATE_ANTHROPIC_KEY |
| LibreTranslate | KARELIA_TRANSLATE_LIBRETRANSLATE_KEY |
Saved translations and cost
Every translation is saved per post, target language, service and version of the post. The next reader who asks for the same post in the same language gets the saved translation at once, and nothing is sent to the service. Editing a post makes a new translation the next time someone asks. After a change of service or model, new requests are translated by the new one. Earlier translations stay saved and are used again if you switch back.
Only new translations count towards the monthly limit and the hourly limit. The cost therefore follows how much your members read across languages, not how much they write.
Language detection
The language of a post is detected on your server when the post is saved, without sending it anywhere. Detection recognises 29 languages: English, Swedish, Danish, Norwegian, German, Dutch, French, Spanish, Italian, Portuguese, Finnish, Polish, Czech, Turkish, Romanian, Hungarian, Indonesian, Russian, Ukrainian, Belarusian, Greek, Arabic, Persian, Hebrew, Hindi, Thai, Chinese, Japanese and Korean.
Code, quotes, web addresses and @mentions are ignored when the language is detected. Posts that are too short to tell get no language; turn on Offer translation when the language is unknown to show the link on them anyway. Translation itself covers every language your service supports.
What is sent where
- Only the text of the post, and the title of the first post, is sent to the service you chose, and only when a reader asks for a translation.
- Code blocks, inline code, web addresses, @mentions and your "never translate" words are replaced by placeholders before sending. The visible text of a link is translated; its address is not sent.
- Translations are stored in your own database, in the table
karelialabs_translations, and deleted with their post. - Karelia Labs receives nothing: no posts, no keys, no usage data.
When something goes wrong
| Readers see | What it means | What to do |
|---|---|---|
| The translation limit for this month has been reached | The monthly character limit is used up | Raise Character limit per month, or wait for the next month |
| You have translated a lot in the last hour | The reader reached the hourly limit | Nothing, or raise New translations per reader per hour |
| The translation service did not answer | The service returned an error or did not answer in time | Run Test the connection in the settings. It shows the service's own message |
| Your language could not be recognised | The reader's language could not be worked out | Check Translate into, and that the reader's language pack or browser language is set |
Test the connection fails with an authentication error: check the key, and for DeepL that a free key (ending in :fx) is used as is. For an OpenAI-compatible service, check that the address ends in /v1 (or the address the service documents) and that the model name is exact.
A local model returns nothing: turn on Turn off model thinking, and check that the model is loaded in LM Studio or Ollama.
Remove the extension
Disabling the extension keeps its data. To remove the saved translations and the language column as well, run this before you remove the package:
php flarum migrate:reset --extension=karelialabs-translate
composer remove karelialabs/flarum-translate
Settings, including an API key stored in the database, stay in Flarum's settings table until you clear them.
Need help?
Write to support@karelialabs.atlassian.net or use the support portal. Include your Flarum version (php flarum info) and the message from Test the connection, if there is one. Never send your API key.