Providers & models
locallingo translates through RubyLLM, so any provider it supports works — chosen by config.
Provider-agnostic via RubyLLM#
Every translation and quality-review call goes through RubyLLM, so locallingo isn't tied to one vendor. You pick the provider and models in .locallingo.yml; the rest of the toolchain is identical regardless of which you choose.
Choosing a provider#
Set provider to a RubyLLM provider symbol, and the per-task models under translate and quality.
defaults:
provider: openai # or anthropic, gemini, ...
translate:
model: gpt-5-mini
quality:
model: gpt-4o-minidefaults:
provider: anthropic
translate:
model: claude-haiku-4-5
quality:
model: claude-sonnet-4-6Credentials#
locallingo looks for the configured provider's API key in three places, in order — the first non-blank key wins:
Locallingo.configure— a key set on the gem itself.RubyLLM.configure— a key the host app set on RubyLLM directly (a Rails initializer, typically).- ENV —
OPENAI_API_KEY,ANTHROPIC_API_KEY,GEMINI_API_KEY, and so on.
A plain ENV var is all most setups need. When your key lives somewhere else — Rails credentials, an app config object, a vault — configure the gem from Ruby. Values can be Strings or callables; callables are resolved fresh on every LLM call, never cached:
Locallingo.configure do |config|
config.anthropic_api_key = Rails.application.credentials.anthropic_api_key
config.openai_api_key = -> { AppConf.openai_api_key } # resolved lazily
endconfigure publishes the settings frozen, so a running thread never sees half of them: call it again to change a key, never write to Locallingo.settings (that raises FrozenError). With String keys the settings are Ractor-shareable; a callable is kept as it is, which keeps them readable on the main Ractor only.
Standalone CLI runs
lingo doesn't boot Rails, so an initializer never runs for it. Put the configure call in a .locallingo.rb file next to .locallingo.yml — the CLI loads it before dispatch:
require_relative "config/app_conf"
Locallingo.configure do |config|
config.anthropic_api_key = -> { AppConf.anthropic_api_key }
endlocallingo fails fast with a clear message when no source yields a key, before making any network call.
translate and the optional quality --ai pass call the provider. Everything else runs offline.Two models, two jobs#
translate.modeldoes the bulk translation work — favour a fast, inexpensive model, since it runs over every missing/changed key in batches (translate.batch_size, default 20). If a reply is not valid JSON, the batch is split in half and retried, down to single keys, so one bad value fails only its own key.quality.modelpowers the optional AI review of existing translations — a stronger model pays off here because it runs on a sample, not the whole corpus.