StringLane opens your project folder and shows every missing key across all locales — highlighted before gen_l10n runs, before Xcode build fails, before the 1-star review.
Saves directly to your files · No account · No cloud
14-day free trial · No email · No account · Works offline
Sound familiar?
You already have a system. Here is why it keeps failing you.
Your toolchain silently skips missing keys
gen_l10n silently skips missing translation keys — no error, no warning, no failed build. Your release pipeline looks like it worked. Your users find out otherwise.
Split panes work at 2 locales. They collapse at 4.
Four locales isn't inconvenient — it's a different problem entirely. You're not comparing files anymore, you're doing archaeology. And the worst failure mode doesn't look like a failure: the key exists, the value is still in English, the build passes, and a German speaker gets English UI — and you find out from a review.
Cloud TMS tools weren't built for you
Lokalise starts at $120/month. Both assume import steps, export steps, review queues, and a team to justify the overhead. You have one developer, source files on disk, and a release in two days.
Ship localization you can trust
Before it ships
en.arb
{
"@@locale": "en",
"appTitle": "My Flutter App",
"@appTitle": {
"description": "The title of the application"
},
"welcomeMessage": "Welcome, {name}!",
"@welcomeMessage": {
"description": "Greeting shown on the home screen",
"placeholders": {
"name": {
"type": "String",
"example": "Alice"
}
}
},
"itemCount": "{count, plural, one {# item} other {# items}}",
"@itemCount": {
"description": "Number of items in the cart",
"placeholders": {
"count": {
"type": "int",
"example": "3"
}
}
},
"lastSeen": "Last seen {when}",
"@lastSeen": {
"description": "Relative timestamp shown in the activity feed",
"placeholders": {
"when": {
"type": "String",
"example": "2 hours ago"
}
}
},
"settingsTitle": "Settings",
"@settingsTitle": {
"description": "Settings screen title"
},
"saveButton": "Save",
"@saveButton": {
"description": "Primary save action button"
},
"cancelButton": "Cancel",
"@cancelButton": {
"description": "Cancel action button"
},
"deleteConfirm": "Are you sure you want to delete "{label}"?",
"@deleteConfirm": {
"description": "Confirmation prompt before deleting an item",
"placeholders": {
"label": {
"type": "String",
"example": "My note"
}
}
},
"errorGeneric": "Something went wrong. Please try again.",
"@errorGeneric": {
"description": "Fallback error message"
},
"notificationCount": "{count, plural, zero {No} notifications} one {# notification} other {# notifications}}",
"@notificationCount": {
"description": "Notification badge label",
"placeholders": {
"count": {
"type": "int",
"example": "5"
}
}
},
"signOut": "Sign out",
"@signOut": {
"description": "Sign out button label"
}
}
de.arb
{
"@@locale": "de",
"appTitle": "Meine Flutter-App",
"@appTitle": {
"description": "Der Titel der Anwendung"
},
"welcomeMessage": "Willkommen, {name}!",
"itemCount": "{count, plural, one {# Artikel} other {# Artikel}}",
"lastSeen": "Zuletzt gesehen {when}",
"settingsTitle": "Einstellungen",
"saveButton": "Speichern",
"cancelButton": "Abbrechen",
"deleteConfirm": "Möchten Sie "{label}" wirklich löschen?",
"errorGeneric": "",
"notificationCount": "{count, plural, zero {Keine Benachrichtigungen} one {# Benachrichtigung} other {# Benachrichtigungen}}",
"signOut": "Abmelden"
}
fr.arb
{
"@@locale": "fr",
"appTitle": "Mon application Flutter",
"welcomeMessage": "Bienvenue, {name} !",
"itemCount": "{count, plural, one {# article} other {# articles}}",
"lastSeen": "Vu pour la dernière fois {when}",
"settingsTitle": "Paramètres",
"saveButton": "Enregistrer",
"cancelButton": "Annuler",
"deleteConfirm": "Voulez-vous vraiment supprimer « {label} » ?",
"errorGeneric": "Une erreur est survenue. Veuillez réessayer.",
"notificationCount": "{count, plural, zero {Aucune notification} one {# notification} other {# notifications}}",
"signOut": "Se déconnecter"
}
uk.arb
{
"@@locale": "uk",
"appTitle": "Мій додаток Flutter",
"cancelButton": "Скасувати",
"deleteConfirm": "Ви впевнені, що хочете видалити «{label}»?",
"errorGeneric": "Щось пішло не так. Будь ласка, спробуйте ще раз.",
"itemCount": "{count, plural, one {# елемент} other {# елементів}}",
"lastSeen": "Останній візит {when}",
"notificationCount": "{count, plural, zero {Немає} сповіщень} one {# сповіщення} other {# сповіщень}}",
"saveButton": "Зберегти",
"settingsTitle": "Налаштування",
"signOut": "Вийти",
"welcomeMessage": "Ласкаво просимо, {name}!"
}
ja.arb
{
"@@locale": "ja",
"appTitle": "マイフラッターアプリ",
"cancelButton": "キャンセル",
"deleteConfirm": "「{label}」を削除してもよろしいですか?",
"errorGeneric": "問題が発生しました。もう一度お試しください。",
"itemCount": "{count, plural, one {# 件} other {# 件}}",
"lastSeen": "最終確認:{when}",
"notificationCount": "{count, plural, zero {通知はありません} 通知} one {# 通知} other {# 通知}}",
"saveButton": "保存",
"settingsTitle": "設定",
"signOut": "ログアウト",
"welcomeMessage": "ようこそ、{name}さん!"
}
en.arb
{
"@@locale": "en",
"appTitle": "My Flutter App",
"@appTitle": {
"description": "The title of the application"
},
"welcomeMessage": "Welcome, {name}!",
"@welcomeMessage": {
"description": "Greeting shown on the home screen",
"placeholders": {
"name": {
"type": "String",
"example": "Alice"
}
}
},
"itemCount": "{count, plural, one {# item} other {# items}}",
"@itemCount": {
"description": "Number of items in the cart",
"placeholders": {
"count": {
"type": "int",
"example": "3"
}
}
},
"lastSeen": "Last seen {when}",
"@lastSeen": {
"description": "Relative timestamp shown in the activity feed",
"placeholders": {
"when": {
"type": "String",
"example": "2 hours ago"
}
}
},
"settingsTitle": "Settings",
"@settingsTitle": {
"description": "Settings screen title"
},
"saveButton": "Save",
"@saveButton": {
"description": "Primary save action button"
},
"cancelButton": "Cancel",
"@cancelButton": {
"description": "Cancel action button"
},
"deleteConfirm": "Are you sure you want to delete "{label}"?",
"@deleteConfirm": {
"description": "Confirmation prompt before deleting an item",
"placeholders": {
"label": {
"type": "String",
"example": "My note"
}
}
},
"errorGeneric": "Something went wrong. Please try again.",
"@errorGeneric": {
"description": "Fallback error message"
},
"notificationCount": "{count, plural, zero {No} notifications} one {# notification} other {# notifications}}",
"@notificationCount": {
"description": "Notification badge label",
"placeholders": {
"count": {
"type": "int",
"example": "5"
}
}
},
"signOut": "Sign out",
"@signOut": {
"description": "Sign out button label"
}
}
de.arb
{
"@@locale": "de",
"appTitle": "Meine Flutter-App",
"@appTitle": {
"description": "Der Titel der Anwendung"
},
"welcomeMessage": "Willkommen, {name}!",
"itemCount": "{count, plural, one {# Artikel} other {# Artikel}}",
"lastSeen": "Zuletzt gesehen {when}",
"settingsTitle": "Einstellungen",
"saveButton": "Speichern",
"cancelButton": "Abbrechen",
"deleteConfirm": "Möchten Sie "{label}" wirklich löschen?",
"errorGeneric": "",
"notificationCount": "{count, plural, zero {Keine Benachrichtigungen} one {# Benachrichtigung} other {# Benachrichtigungen}}",
"signOut": "Abmelden"
}
fr.arb
{
"@@locale": "fr",
"appTitle": "Mon application Flutter",
"welcomeMessage": "Bienvenue, {name} !",
"itemCount": "{count, plural, one {# article} other {# articles}}",
"lastSeen": "Vu pour la dernière fois {when}",
"settingsTitle": "Paramètres",
"saveButton": "Enregistrer",
"cancelButton": "Annuler",
"deleteConfirm": "Voulez-vous vraiment supprimer « {label} » ?",
"errorGeneric": "Une erreur est survenue. Veuillez réessayer.",
"notificationCount": "{count, plural, zero {Aucune notification} one {# notification} other {# notifications}}",
"signOut": "Se déconnecter"
}
uk.arb
{
"@@locale": "uk",
"appTitle": "Мій додаток Flutter",
"cancelButton": "Скасувати",
"deleteConfirm": "Ви впевнені, що хочете видалити «{label}»?",
"errorGeneric": "Щось пішло не так. Будь ласка, спробуйте ще раз.",
"itemCount": "{count, plural, one {# елемент} other {# елементів}}",
"lastSeen": "Останній візит {when}",
"notificationCount": "{count, plural, zero {Немає} сповіщень} one {# сповіщення} other {# сповіщень}}",
"saveButton": "Зберегти",
"settingsTitle": "Налаштування",
"signOut": "Вийти",
"welcomeMessage": "Ласкаво просимо, {name}!"
}
ja.arb
{
"@@locale": "ja",
"appTitle": "マイフラッターアプリ",
"cancelButton": "キャンセル",
"deleteConfirm": "「{label}」を削除してもよろしいですか?",
"errorGeneric": "問題が発生しました。もう一度お試しください。",
"itemCount": "{count, plural, one {# 件} other {# 件}}",
"lastSeen": "最終確認:{when}",
"notificationCount": "{count, plural, zero {通知はありません} 通知} one {# 通知} other {# 通知}}",
"saveButton": "保存",
"settingsTitle": "設定",
"signOut": "ログアウト",
"welcomeMessage": "ようこそ、{name}さん!"
}
en.arb
{
"@@locale": "en",
"appTitle": "My Flutter App",
"@appTitle": {
"description": "The title of the application"
},
"welcomeMessage": "Welcome, {name}!",
"@welcomeMessage": {
"description": "Greeting shown on the home screen",
"placeholders": {
"name": {
"type": "String",
"example": "Alice"
}
}
},
"itemCount": "{count, plural, one {# item} other {# items}}",
"@itemCount": {
"description": "Number of items in the cart",
"placeholders": {
"count": {
"type": "int",
"example": "3"
}
}
},
"lastSeen": "Last seen {when}",
"@lastSeen": {
"description": "Relative timestamp shown in the activity feed",
"placeholders": {
"when": {
"type": "String",
"example": "2 hours ago"
}
}
},
"settingsTitle": "Settings",
"@settingsTitle": {
"description": "Settings screen title"
},
"saveButton": "Save",
"@saveButton": {
"description": "Primary save action button"
},
"cancelButton": "Cancel",
"@cancelButton": {
"description": "Cancel action button"
},
"deleteConfirm": "Are you sure you want to delete "{label}"?",
"@deleteConfirm": {
"description": "Confirmation prompt before deleting an item",
"placeholders": {
"label": {
"type": "String",
"example": "My note"
}
}
},
"errorGeneric": "Something went wrong. Please try again.",
"@errorGeneric": {
"description": "Fallback error message"
},
"notificationCount": "{count, plural, zero {No} notifications} one {# notification} other {# notifications}}",
"@notificationCount": {
"description": "Notification badge label",
"placeholders": {
"count": {
"type": "int",
"example": "5"
}
}
},
"signOut": "Sign out",
"@signOut": {
"description": "Sign out button label"
}
}
de.arb
{
"@@locale": "de",
"appTitle": "Meine Flutter-App",
"@appTitle": {
"description": "Der Titel der Anwendung"
},
"welcomeMessage": "Willkommen, {name}!",
"itemCount": "{count, plural, one {# Artikel} other {# Artikel}}",
"lastSeen": "Zuletzt gesehen {when}",
"settingsTitle": "Einstellungen",
"saveButton": "Speichern",
"cancelButton": "Abbrechen",
"deleteConfirm": "Möchten Sie "{label}" wirklich löschen?",
"errorGeneric": "",
"notificationCount": "{count, plural, zero {Keine Benachrichtigungen} one {# Benachrichtigung} other {# Benachrichtigungen}}",
"signOut": "Abmelden"
}
fr.arb
{
"@@locale": "fr",
"appTitle": "Mon application Flutter",
"welcomeMessage": "Bienvenue, {name} !",
"itemCount": "{count, plural, one {# article} other {# articles}}",
"lastSeen": "Vu pour la dernière fois {when}",
"settingsTitle": "Paramètres",
"saveButton": "Enregistrer",
"cancelButton": "Annuler",
"deleteConfirm": "Voulez-vous vraiment supprimer « {label} » ?",
"errorGeneric": "Une erreur est survenue. Veuillez réessayer.",
"notificationCount": "{count, plural, zero {Aucune notification} one {# notification} other {# notifications}}",
"signOut": "Se déconnecter"
}
uk.arb
{
"@@locale": "uk",
"appTitle": "Мій додаток Flutter",
"cancelButton": "Скасувати",
"deleteConfirm": "Ви впевнені, що хочете видалити «{label}»?",
"errorGeneric": "Щось пішло не так. Будь ласка, спробуйте ще раз.",
"itemCount": "{count, plural, one {# елемент} other {# елементів}}",
"lastSeen": "Останній візит {when}",
"notificationCount": "{count, plural, zero {Немає} сповіщень} one {# сповіщення} other {# сповіщень}}",
"saveButton": "Зберегти",
"settingsTitle": "Налаштування",
"signOut": "Вийти",
"welcomeMessage": "Ласкаво просимо, {name}!"
}
ja.arb
{
"@@locale": "ja",
"appTitle": "マイフラッターアプリ",
"cancelButton": "キャンセル",
"deleteConfirm": "「{label}」を削除してもよろしいですか?",
"errorGeneric": "問題が発生しました。もう一度お試しください。",
"itemCount": "{count, plural, one {# 件} other {# 件}}",
"lastSeen": "最終確認:{when}",
"notificationCount": "{count, plural, zero {通知はありません} 通知} one {# 通知} other {# 通知}}",
"saveButton": "保存",
"settingsTitle": "設定",
"signOut": "ログアウト",
"welcomeMessage": "ようこそ、{name}さん!"
}
en.arb
{
"@@locale": "en",
"appTitle": "My Flutter App",
"@appTitle": {
"description": "The title of the application"
},
"welcomeMessage": "Welcome, {name}!",
"@welcomeMessage": {
"description": "Greeting shown on the home screen",
"placeholders": {
"name": {
"type": "String",
"example": "Alice"
}
}
},
"itemCount": "{count, plural, one {# item} other {# items}}",
"@itemCount": {
"description": "Number of items in the cart",
"placeholders": {
"count": {
"type": "int",
"example": "3"
}
}
},
"lastSeen": "Last seen {when}",
"@lastSeen": {
"description": "Relative timestamp shown in the activity feed",
"placeholders": {
"when": {
"type": "String",
"example": "2 hours ago"
}
}
},
"settingsTitle": "Settings",
"@settingsTitle": {
"description": "Settings screen title"
},
"saveButton": "Save",
"@saveButton": {
"description": "Primary save action button"
},
"cancelButton": "Cancel",
"@cancelButton": {
"description": "Cancel action button"
},
"deleteConfirm": "Are you sure you want to delete "{label}"?",
"@deleteConfirm": {
"description": "Confirmation prompt before deleting an item",
"placeholders": {
"label": {
"type": "String",
"example": "My note"
}
}
},
"errorGeneric": "Something went wrong. Please try again.",
"@errorGeneric": {
"description": "Fallback error message"
},
"notificationCount": "{count, plural, zero {No} notifications} one {# notification} other {# notifications}}",
"@notificationCount": {
"description": "Notification badge label",
"placeholders": {
"count": {
"type": "int",
"example": "5"
}
}
},
"signOut": "Sign out",
"@signOut": {
"description": "Sign out button label"
}
}
de.arb
{
"@@locale": "de",
"appTitle": "Meine Flutter-App",
"@appTitle": {
"description": "Der Titel der Anwendung"
},
"welcomeMessage": "Willkommen, {name}!",
"itemCount": "{count, plural, one {# Artikel} other {# Artikel}}",
"lastSeen": "Zuletzt gesehen {when}",
"settingsTitle": "Einstellungen",
"saveButton": "Speichern",
"cancelButton": "Abbrechen",
"deleteConfirm": "Möchten Sie "{label}" wirklich löschen?",
"errorGeneric": "",
"notificationCount": "{count, plural, zero {Keine Benachrichtigungen} one {# Benachrichtigung} other {# Benachrichtigungen}}",
"signOut": "Abmelden"
}
fr.arb
{
"@@locale": "fr",
"appTitle": "Mon application Flutter",
"welcomeMessage": "Bienvenue, {name} !",
"itemCount": "{count, plural, one {# article} other {# articles}}",
"lastSeen": "Vu pour la dernière fois {when}",
"settingsTitle": "Paramètres",
"saveButton": "Enregistrer",
"cancelButton": "Annuler",
"deleteConfirm": "Voulez-vous vraiment supprimer « {label} » ?",
"errorGeneric": "Une erreur est survenue. Veuillez réessayer.",
"notificationCount": "{count, plural, zero {Aucune notification} one {# notification} other {# notifications}}",
"signOut": "Se déconnecter"
}
uk.arb
{
"@@locale": "uk",
"appTitle": "Мій додаток Flutter",
"cancelButton": "Скасувати",
"deleteConfirm": "Ви впевнені, що хочете видалити «{label}»?",
"errorGeneric": "Щось пішло не так. Будь ласка, спробуйте ще раз.",
"itemCount": "{count, plural, one {# елемент} other {# елементів}}",
"lastSeen": "Останній візит {when}",
"notificationCount": "{count, plural, zero {Немає} сповіщень} one {# сповіщення} other {# сповіщень}}",
"saveButton": "Зберегти",
"settingsTitle": "Налаштування",
"signOut": "Вийти",
"welcomeMessage": "Ласкаво просимо, {name}!"
}
ja.arb
{
"@@locale": "ja",
"appTitle": "マイフラッターアプリ",
"cancelButton": "キャンセル",
"deleteConfirm": "「{label}」を削除してもよろしいですか?",
"errorGeneric": "問題が発生しました。もう一度お試しください。",
"itemCount": "{count, plural, one {# 件} other {# 件}}",
"lastSeen": "最終確認:{when}",
"notificationCount": "{count, plural, zero {通知はありません} 通知} one {# 通知} other {# 通知}}",
"saveButton": "保存",
"settingsTitle": "設定",
"signOut": "ログアウト",
"welcomeMessage": "ようこそ、{name}さん!"
}
See every locale gap at a glance
StringLane opens your folder, groups keys by namespace in the sidebar, and renders every locale for the active key side-by-side in the detail pane. No more toggling between files or holding three editor tabs in your head at once.
StringLane writes directly to your ARB, .strings, XML, or JSON files on every keystroke. Your git workflow stays exactly as it is. There is no export step, no sync button, no intermediate format to manage.
Fill all missing translations — with any AI model you already use, cloud or local
Bring your own API key and bulk-translate every missing string across every locale at once. OpenAI, Anthropic, Google Gemini, Groq, Mistral, DeepSeek, xAI — or run fully offline against any OpenAI-compatible local runner (Ollama, LM Studio, Jan, llama.cpp). The Issues Panel's "Fix all with AI" sweep covers ICU errors and placeholder mismatches in the same pass. No per-character credits, no subscription layered on top.
Point StringLane at your existing project directory. It finds every ARB, .strings, Android XML, or i18next JSON file automatically — no import dialog, no configuration file, no file picker for each locale.
See Every Locale at Once
Keys live in the sidebar, every locale for the active key fills the detail pane — Grid for short strings, List for long ones. Missing entries highlight immediately, and the Issues Panel (⌘J) lists every validation problem across the project so nothing slips through.
Fill Gaps with AI or Type Directly
Click any empty cell and type, or sweep every missing cell across every locale via the Command Palette (⌘K) → Translate all missing. Bring your own key for OpenAI, Anthropic, Gemini, Groq, Mistral, DeepSeek, xAI — or run fully local against Ollama / LM Studio / Jan via the OpenAI-compatible Local LLM provider. No credits, no middleman.
Changes Save to Your Source Files
Every edit writes directly back to the original file on disk the moment you leave the cell. No Save button, no Export step — your git diff shows exactly what changed and nothing else.
Build and Ship, Nothing Else to Do
Close StringLane and run your build. The source file is already the right file — gen_l10n, Xcode, and the Android resource compiler find exactly what they expect.
Architecture note: StringLane runs entirely on your machine — no account, no cloud, no server your files pass through. Your locale files stay in your repo, in your exact format. A git diff before and after shows only the lines you changed.
Features
Purpose-built for localization — not bolted onto a generic editor
Sidebar + per-key detail pane
Namespace-grouped key sidebar on the left, every locale for the active key on the right. Grid or List sub-toggle for short vs long strings. Missing translations highlight the moment they exist — no more opening files one by one.
Issues Panel + AI fix-all
⌘J opens a panel listing every validation problem across the project — placeholder mismatches, malformed ICU plurals, missing keys, length overruns. Fix one row, fix all rows with AI, or jump straight to the offending cell. Caught before gen_l10n runs, before the build fails.
AI translation (BYOK or local)
Bulk-translate every gap across every locale in one action. Bring your own key for OpenAI, Anthropic, Google Gemini, Groq, Mistral, DeepSeek, or xAI — or run fully offline against any OpenAI-compatible local runner (Ollama, LM Studio, Jan). CLDR-aware plural expansion handles Ukrainian, Polish, Arabic out of the box.
Multi-format support
Flutter ARB, iOS .strings, Xcode 15+ .xcstrings, Android XML, i18next JSON — all first-class. Native parsers built and tested per format, plurals in the format's own shape, not a generic JSON editor with an ARB plugin bolted on.
Pricing
One price. No subscription. Yours forever.
Free Trial
$0/ 14 days
Full app, all features. No email, no account, works offline.
All five formats: ARB, .strings, .xcstrings, XML, JSON