<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Evgenii Sokolov</title>
    <description>The latest articles on DEV Community by Evgenii Sokolov (@badtod).</description>
    <link>https://dev.to/badtod</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4044524%2F3630bb96-d6e3-4a9b-ae20-59fe3c0dade5.jpg</url>
      <title>DEV Community: Evgenii Sokolov</title>
      <link>https://dev.to/badtod</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/badtod"/>
    <language>en</language>
    <item>
      <title>5 ARB and ICU edge cases I wish I'd tested earlier</title>
      <dc:creator>Evgenii Sokolov</dc:creator>
      <pubDate>Thu, 23 Jul 2026 22:02:35 +0000</pubDate>
      <link>https://dev.to/badtod/5-arb-and-icu-edge-cases-i-wish-id-tested-earlier-4iph</link>
      <guid>https://dev.to/badtod/5-arb-and-icu-edge-cases-i-wish-id-tested-earlier-4iph</guid>
      <description>&lt;p&gt;When I first added localization-file conversion, the job looked almost embarrassingly small: parse a file, copy the strings into a map, serialize that map in another format.&lt;/p&gt;

&lt;p&gt;Then I tried it on real Flutter projects.&lt;/p&gt;

&lt;p&gt;The converted files parsed correctly, which felt like success, but some of them had quietly lost translator context. Others kept the text and damaged a placeholder. ICU plurals were the most misleading case: a message could be perfectly valid and still be wrong for the target language.&lt;/p&gt;

&lt;p&gt;These are the five fixtures I now reach for before I trust a converter.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. &lt;code&gt;@key&lt;/code&gt; is not another string
&lt;/h2&gt;

&lt;p&gt;An ARB message can have a companion metadata entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome, {name}!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Greeting shown after sign-in"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"placeholders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;welcome&lt;/code&gt; is the text. &lt;code&gt;@welcome&lt;/code&gt; tells Flutter and the translator what that text means.&lt;/p&gt;

&lt;p&gt;My first parser skipped every key beginning with &lt;code&gt;@&lt;/code&gt;. That was convenient because metadata should not appear in the translation list. It also meant the conversion was not lossless. A round trip restored the greeting but forgot its description and the type of &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The model I use internally now looks more like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id: "welcome"
value: "Welcome, {name}!"
description: "Greeting shown after sign-in"
placeholders: ["name"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does not magically solve export. CSV, for example, may have nowhere to put a placeholder type. The important part is knowing what was lost. Depending on the target format, I can keep metadata in comments, write a sidecar file, or show a warning.&lt;/p&gt;

&lt;p&gt;I also test &lt;code&gt;@missing&lt;/code&gt; without a matching &lt;code&gt;missing&lt;/code&gt; message. Orphan metadata looks odd, but it turns up in repositories after keys are renamed or deleted.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A valid plural can still be wrong for the locale
&lt;/h2&gt;

&lt;p&gt;This is enough for most English cardinal plurals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{count, plural,
  one {# file}
  other {# files}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the same branches for Russian loses information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{count, plural,
  one {# файл}
  few {# файла}
  many {# файлов}
  other {# файла}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;English normally needs &lt;code&gt;one&lt;/code&gt; and &lt;code&gt;other&lt;/code&gt;. Russian uses &lt;code&gt;one&lt;/code&gt;, &lt;code&gt;few&lt;/code&gt;, &lt;code&gt;many&lt;/code&gt;, plus the required &lt;code&gt;other&lt;/code&gt; fallback. Arabic may use all six named categories: &lt;code&gt;zero&lt;/code&gt;, &lt;code&gt;one&lt;/code&gt;, &lt;code&gt;two&lt;/code&gt;, &lt;code&gt;few&lt;/code&gt;, &lt;code&gt;many&lt;/code&gt;, and &lt;code&gt;other&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I treat validation as two separate questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the ICU message parse?&lt;/li&gt;
&lt;li&gt;Does it cover the categories used by this locale?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Checking only the first question catches unbalanced braces and a missing &lt;code&gt;other&lt;/code&gt;, but it will not tell you that the Russian &lt;code&gt;few&lt;/code&gt; branch is absent.&lt;/p&gt;

&lt;p&gt;Exact selectors need to survive as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{count, plural,
  =0 {No files}
  one {One file}
  other {# files}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;=0&lt;/code&gt; is an exact-number override. It is not the same thing as the locale category &lt;code&gt;zero&lt;/code&gt;, and a converter should not merge the two.&lt;/p&gt;

&lt;p&gt;Then there is nesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{gender, select,
  female {{count, plural, one {She has one task} other {She has # tasks}}}
  male   {{count, plural, one {He has one task} other {He has # tasks}}}
  other  {{count, plural, one {They have one task} other {They have # tasks}}}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was the point where I stopped trying to count braces with regular expressions.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. One value has to satisfy both JSON and ICU
&lt;/h2&gt;

&lt;p&gt;ARB is JSON, but its string values may contain another language: ICU MessageFormat. The converter has to get through both layers without "helping" too much.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"receipt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Line 1&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Line 2: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;{total}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Exports&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;{locale}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;messages.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"emoji"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Saved ✓ 🚀"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After JSON parsing, &lt;code&gt;receipt&lt;/code&gt; contains a real newline. &lt;code&gt;path&lt;/code&gt; contains backslashes and an ICU-style placeholder. The last value is a quick check that the pipeline is genuinely Unicode-safe.&lt;/p&gt;

&lt;p&gt;The bugs here are small and annoying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;\n&lt;/code&gt; becomes the visible characters &lt;code&gt;\&lt;/code&gt; and &lt;code&gt;n&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;an escaped backslash is escaped a second time;&lt;/li&gt;
&lt;li&gt;UTF-8 is saved with an unexpected byte-order mark;&lt;/li&gt;
&lt;li&gt;ICU apostrophe quoting changes in one direction of the conversion;&lt;/li&gt;
&lt;li&gt;Unicode normalization changes a value that looks identical on screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I compare decoded values, not their JSON spelling. These two strings represent the same character:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="s2"&gt;2713"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="s2"&gt;"✓"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Byte equality would fail that test for no useful reason. On the other hand, a changed non-breaking space can look fine in a diff and still break the UI.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Not every brace is a placeholder
&lt;/h2&gt;

&lt;p&gt;Localization projects rarely agree on one placeholder dialect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello, {name}
Downloaded %s of %d files
You have :count messages
Welcome, {{user}}
Price: %(amount)s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unless the user explicitly asks for a syntax migration, I leave those tokens alone. I also compare them as a multiset rather than a set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input:  ["{name}", "{count}", "{count}"]
output: ["{name}", "{count}", "{count}"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repeated &lt;code&gt;{count}&lt;/code&gt; matters.&lt;/p&gt;

&lt;p&gt;The opposite problem is detecting placeholders where there are none:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSS: .button { color: red; }
Discount: 20%
Object: {"id": 42}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single broad regular expression will eventually eat ordinary text. Format-aware parsing is better. If that is not possible, I would rather ask which placeholder dialect a file uses than silently guess.&lt;/p&gt;

&lt;p&gt;ARB gives us one extra consistency check. Tokens in the message should agree with the &lt;code&gt;placeholders&lt;/code&gt; object in &lt;code&gt;@key&lt;/code&gt;. If the text contains &lt;code&gt;{name}&lt;/code&gt; but the metadata only declares &lt;code&gt;count&lt;/code&gt;, that deserves a warning.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. "It parses" is a very low bar for a round trip
&lt;/h2&gt;

&lt;p&gt;Consider this ARB file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@@locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"save"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Save"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@save"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Button label, not a noun"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple CSV export might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key,value
save,Save
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CSV is valid. Turning it back into this ARB is also valid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"save"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Save"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the locale and the translator's context have disappeared.&lt;/p&gt;

&lt;p&gt;For a round-trip test, I compare message IDs, decoded values, placeholder counts, ICU selectors, metadata, and locale information. I do not require the same key order, whitespace, or Unicode escape style.&lt;/p&gt;

&lt;p&gt;When the target format cannot represent something, I want the result described plainly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lossless
lossy (with a reason)
unsupported
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returning a green checkmark merely because the output parses is how quiet data loss reaches a production translation file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small fixture I keep around
&lt;/h2&gt;

&lt;p&gt;This one file covers most of the cases above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@@locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Welcome, {name}!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;You have {count, plural, =0 {no tasks} one {# task} other {# tasks}}."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"@welcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dashboard greeting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"placeholders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"int"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exportPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Exports&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;{name}&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;✓"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each target format, I verify what it can preserve and expect an explicit limitation for everything else. Then I convert it back and compare meaning rather than bytes.&lt;/p&gt;

&lt;p&gt;I put two tools I use while chasing these cases online: an &lt;a href="https://localization.one/tools/arb-to-json/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=organic_launch" rel="noopener noreferrer"&gt;ARB-to-JSON converter&lt;/a&gt; and an &lt;a href="https://localization.one/tools/icu-plural-validator/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=organic_launch" rel="noopener noreferrer"&gt;ICU plural validator&lt;/a&gt;. They work without signup.&lt;/p&gt;

&lt;p&gt;One honest caveat: the converter currently extracts translatable keys and values but does not carry ARB &lt;code&gt;@key&lt;/code&gt; metadata into flat target formats. Treat that conversion as lossy.&lt;/p&gt;

&lt;p&gt;Disclosure: both tools are part of &lt;a href="https://localization.one/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=organic_launch" rel="noopener noreferrer"&gt;Localization.One&lt;/a&gt;, a product I am building. If you have a file that breaks either tool, a minimal reproducible example would be genuinely useful.&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
  </channel>
</rss>
