{"id":290,"date":"2026-06-11T15:16:35","date_gmt":"2026-06-11T15:16:35","guid":{"rendered":"https:\/\/www.forethought-studio.com\/translate\/arb\/"},"modified":"2026-06-11T18:13:31","modified_gmt":"2026-06-11T18:13:31","slug":"arb","status":"publish","type":"page","link":"https:\/\/www.forethought-studio.com\/translate\/arb\/","title":{"rendered":"ARB translator that runs in your browser"},"content":{"rendered":"<p class=\"dft-page-lede\">Drop a .arb file in below, pick a target language, download the translated file. The ARB file stays on your device.<\/p>\n<div class=\"dft-mount dft-app\" data-format=\"arb\"><noscript><p class=\"df-noscript-message\">This translator requires JavaScript in your browser.<\/p><\/noscript><section class=\"dft-step dft-step-file dft-skeleton\" data-state=\"active\" aria-hidden=\"true\"><header class=\"dft-step-header\"><span class=\"dft-step-number\">1<\/span><h2 class=\"dft-step-title\">Add your ARB file<\/h2><span class=\"dft-step-hint\">or paste anywhere<\/span><\/header><div class=\"dft-dropzone-wrap\"><div class=\"dft-dropzone\"><div class=\"dft-dropzone-icon\" aria-hidden=\"true\"><svg viewBox=\"0 0 24 24\" width=\"48\" height=\"48\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M14 3v4a1 1 0 0 0 1 1h4\"\/><path d=\"M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2z\"\/><path d=\"M12 11v6\"\/><path d=\"M9 14l3-3 3 3\"\/><\/svg><\/div><p class=\"dft-dropzone-primary\">Drop a ARB file anywhere, <span class=\"dft-dropzone-click\">click to choose<\/span>, or paste contents<\/p><\/div><p class=\"dft-dropzone-secondary\">ARB files only. Files stay on your device.<\/p><\/div><\/section><section class=\"dft-step dft-step-language dft-skeleton\" data-state=\"future\" aria-hidden=\"true\"><header class=\"dft-step-header\"><span class=\"dft-step-number\">2<\/span><h2 class=\"dft-step-title\">Choose target language<\/h2><\/header><div class=\"dft-language-body\"><label class=\"dft-target-label\">Translate to<\/label><select class=\"dft-target-select\" disabled aria-hidden=\"true\"><option>Select language<\/option><\/select><\/div><\/section><section class=\"dft-step dft-step-progress dft-skeleton\" data-state=\"future\" aria-hidden=\"true\"><header class=\"dft-step-header\"><span class=\"dft-step-number\">3<\/span><h2 class=\"dft-step-title\">Translating<\/h2><\/header><\/section><section class=\"dft-step dft-step-review dft-skeleton\" data-state=\"future\" aria-hidden=\"true\"><header class=\"dft-step-header\"><span class=\"dft-step-number\">4<\/span><h2 class=\"dft-step-title\">Review<\/h2><\/header><\/section><div class=\"df-loading-overlay\" role=\"status\" aria-live=\"polite\"><span class=\"df-loading-overlay-spinner\" aria-hidden=\"true\"><\/span><span class=\"df-loading-overlay-label\">Loading translator...<\/span><\/div><\/div>\n<h2>How it works<\/h2>\n<p>Drop a .arb file on the page, click to choose one, or paste the contents straight in. The parser reads the file in your browser and tells you how many strings still need translation. Pick a target language and translation starts immediately. The translated file is reassembled from the same parsed structure so @key metadata, placeholder definitions, and the original key order all survive the round trip.<\/p>\n<h2>Quality Warning<\/h2>\n<p>Please don&#8217;t rely on the quality of these translations to ship a finalized product. This is just a starting point. It&#8217;s machine translation and there are no guarantees about its quality.<\/p>\n<h2>What we do not do<\/h2>\n<p>The file itself never leaves your device. We do not see what you are translating, we do not store any of the strings, and there is nothing about your file in our logs. The privacy wedge in DropFormat is structural: the ARB parser, the Google Translate caller, and the file writer all run inside your browser tab.<\/p>\n<p>What does leave your device: the individual strings go to <code>translate.googleapis.com<\/code> the same way they would if you pasted them into translate.google.com yourself. We are not in the middle. If your file contains internal product strings you do not want a third party to see, do not translate it here; use an offline tool.<\/p>\n<h2>What it supports<\/h2>\n<p>ARB is the Application Resource Bundle format Flutter and Google Gallery use for localization. It is plain JSON: each key is a message identifier, each <code>@key<\/code> entry holds metadata for the matching message (description, placeholders), and keys starting with <code>@@<\/code> (like <code>@@locale<\/code>) are file-level metadata. We preserve all of it on round trip: insertion order, descriptions, placeholder definitions, and any <code>@@<\/code> fields we did not write to.<\/p>\n<p>ICU plural messages of the shape <code>{count, plural, =0 {No items} other {# items}}<\/code> are first-class. The parser detects pure ICU plural patterns and fans each case out into its own row in the review pane. Translate each case independently; on download the cases are re-assembled into one ICU pattern under the original key. The plural variable name and the case labels (<code>=0<\/code>, <code>one<\/code>, <code>other<\/code>, etc.) are preserved verbatim.<\/p>\n<p>Placeholders like <code>{userName}<\/code> pass through the translator unchanged. The protection layer that masks placeholders before translation and restores them after is the same one used for the PO and XLIFF translators.<\/p>\n<p>Not yet: ICU select messages (<code>{gender, select, ...}<\/code>), nested ICU patterns embedded inside surrounding prose, and CLDR-aware plural-case expansion (today the case set in the output matches the case set in the input).<\/p>\n<p><!-- TOLERANCES:START --><\/p>\n<h2>What we tolerate, and what we reject<\/h2>\n<p>Some malformed files we repair on the way in. Others we refuse to load so we do not make things worse. This list is the parser&#8217;s actual behaviour, generated from the format module itself, not aspirational copy.<\/p>\n<p><strong>Repaired automatically:<\/strong><\/p>\n<ul class=\"dft-tolerance-list dft-tolerance-repairs\">\n<li data-tolerance-id=\"bom-stripped\">UTF-8 byte order mark (BOM) at the start of the file<\/li>\n<li data-tolerance-id=\"crlf-normalized\">Windows (CRLF) or mixed line endings<\/li>\n<li data-tolerance-id=\"trailing-comma\">Trailing JSON commas before } or ] (JSON5-style)<\/li>\n<\/ul>\n<p><strong>Refused with an explanation:<\/strong><\/p>\n<ul class=\"dft-tolerance-list dft-tolerance-rejects\">\n<li data-tolerance-id=\"ARB_EMPTY\"><strong>Empty file.<\/strong> Add at least one key\/value pair and re-upload.<\/li>\n<li data-tolerance-id=\"ARB_NOT_JSON\"><strong>Does not parse as JSON.<\/strong> Fix the JSON syntax so the file parses, then re-upload.<\/li>\n<li data-tolerance-id=\"ARB_NOT_ARB\"><strong>Top-level value is not a JSON object (e.g. an array).<\/strong> ARB requires a top-level object; wrap the array in an object keyed by string ids.<\/li>\n<li data-tolerance-id=\"ARB_NO_ENTRIES\"><strong>No translatable entries (only @@metadata keys).<\/strong> Add at least one non-@-prefixed key\/value pair.<\/li>\n<\/ul>\n<p><!-- TOLERANCES:END --><\/p>\n<h2>Common questions<\/h2>\n<p><strong>Why is the translation in some cases worse than the Google Translate website?<\/strong> The free endpoint we use is the same one the Google Translate website uses, but the API does not always know about the context the website&#8217;s interface provides. For names, branded terms, or jargon, expect to override in the review pane.<\/p>\n<p><strong>What if I am translating something Google&#8217;s endpoint will not accept?<\/strong> The free endpoint occasionally returns the source unchanged for content it would rather not translate. Those entries show &#8220;kept original&#8221; in the review pane so you can hand-translate just those.<\/p>\n<p><strong>Does it cache anything?<\/strong> No. Each run starts fresh.<\/p>\n<p><strong>Will the output be valid JSON?<\/strong> Yes. The compiler serializes through <code>JSON.stringify<\/code>, so the file is always parseable. It will load cleanly in Flutter&#8217;s intl_utils or any ARB-aware tool.<\/p>\n<p><strong>What happens to keys whose value is not a string?<\/strong> ARB values that are not strings (e.g., an accidentally-typed number or array) are left untouched. Only string values are sent for translation.<\/p>\n<h2>Why this exists<\/h2>\n<p>Most Flutter localization tooling expects you to either pay for a managed service (Phrase, Crowdin, Lokalise) or run a CLI against your local files. This tool sits in between: free, browser-based, no upload, designed for the &#8220;I just need a fast first pass on this ARB&#8221; workflow. Drop the .arb, pick the language, get back a translated file in the same shape.<\/p>\n<h2>What is ARB?<\/h2>\n<p>ARB (&#8220;Application Resource Bundle&#8221;) is a JSON-based format Google maintains for localizing Flutter apps and other internationalized projects. Each translatable string is a top-level JSON key, with optional <code>@key<\/code> entries holding metadata: a <code>description<\/code> for translator context, and a <code>placeholders<\/code> map describing variables embedded in the message. Plural messages use the ICU MessageFormat syntax (<code>{count, plural, =0 {...} other {...}}<\/code>) so a single key can carry every plural form a target language needs. Flutter&#8217;s <code>intl_utils<\/code> and <code>flutter_localizations<\/code> read ARB at build time to generate type-safe localization classes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Drop a .arb file in below, pick a target language, download the translated file. The ARB file stays on your device. How it works Drop a .arb file on the page, click to choose one, or paste the contents straight in. The parser reads the file in your browser and tells you how many strings [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"parent":274,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-290","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/pages\/290","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/types\/page"}],"replies":[{"embeddable":true,"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/comments?post=290"}],"version-history":[{"count":1,"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/pages\/290\/revisions"}],"predecessor-version":[{"id":305,"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/pages\/290\/revisions\/305"}],"up":[{"embeddable":true,"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/pages\/274"}],"wp:attachment":[{"href":"https:\/\/www.forethought-studio.com\/convert\/wp-json\/wp\/v2\/media?parent=290"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}