Your Lovable app can be fully translated and still say "5 элемент" or show a price as "1,234.50" to a German user. The cause is plural and format logic that was written for English and left in your components. globalize.now is AI-powered localization infrastructure that keeps your strings in committed catalogs, and the fix below puts plural rules inside those catalogs instead of your code.
Why do plurals break when Lovable translates my app?
Because the plural rule lives in the component, not in the translation. A common generated pattern looks like this:
<p>{count} {count === 1 ? "item" : "items"} in your cart</p>
That line hard-codes English grammar. A translator can change the words but not the === 1 test, and word order is fixed too, with the number always first. English has two forms. Russian has four, so "1 элемент", "2 элемента", "5 элементов" and "1,5 элемента" all need different endings.
If you set up routing first (see how to make a Lovable app multilingual), plurals are the next bug users notice, because nothing crashes. The sentence is just wrong.
Step 1: How do I write a plural that translators can change?
Use an ICU plural message, so the rule moves into the translation. With Lingui, the Plural component and the plural macro both produce one.
import { Plural } from "@lingui/react/macro";
<p>
<Plural value={count} one="# item in your cart" other="# items in your cart" />
</p>
For strings outside JSX, use the macro from the core package:
import { plural } from "@lingui/core/macro";
const label = plural(count, { one: "# file", other: "# files" });
# is replaced by the number. Per Lingui's docs, you write only the forms your source language needs, and other is required. Exact matches such as _0="Your cart is empty" handle the zero case in English without a separate branch.
Lovable's default stacks differ in setup but not in this API, so the same component works in the Vite SPA and the TanStack Start projects.
What does the catalog look like once plurals are extracted?
One message per string, in standard ICU syntax, stored as a PO file in your repository. The translator, human or AI, supplies the forms their language needs.
msgid "{count, plural, one {# item in your cart} other {# items in your cart}}"
msgstr "{count, plural, one {# товар в корзине} few {# товара в корзине} many {# товаров в корзине} other {# товара в корзине}}"
The Russian other form covers fractions such as 1,5, which is why it matches the genitive singular. Because the catalog is a committed file, you review plural changes in a pull request like any other code. For the syntax itself, see what ICU MessageFormat is.
Which languages need more than one and other?
Unicode CLDR defines six plural categories: zero, one, two, few, many and other. Each language uses a subset, and other is required everywhere.
| Language | Categories used |
|---|---|
| Japanese, Chinese | other |
| English | one, other |
| Russian | one, few, many, other |
| Arabic | zero, one, two, few, many, other |
The CLDR spec lists Welsh as using all six as well. You do not need to memorize this. You need a catalog format that lets each language fill its own forms, which PO with ICU messages does.
How do I stop Lovable from writing ternary plurals again?
Put the rule where Lovable reads instructions. Lovable regenerates components when you prompt for new features, and a new feature written without the rule brings the ternary back.
Add this to your Knowledge or AGENTS.md:
Never hand-build plurals or number formats. Use the Lingui Plural component
or plural() macro for every count. Use Intl with the active locale for every
date, number and price.
The lovable-i18n skill writes rules of this kind for you. How it does that is covered in what the Lovable i18n skill does. After your next generated feature, search the diff for === 1 and !== 1 to confirm the rule held.
Step 2: How do I format dates and currency by locale?
Use Intl with the active locale. Lingui's i18n object also offers i18n.date() and i18n.number(), but calling Intl directly with i18n.locale keeps the formatting options visible in your own code.
import { useLingui } from "@lingui/react";
export function Price({ amount, currency }: { amount: number; currency: string }) {
const { i18n } = useLingui();
return (
<span>
{new Intl.NumberFormat(i18n.locale, { style: "currency", currency }).format(amount)}
</span>
);
}
export function OrderDate({ date }: { date: Date }) {
const { i18n } = useLingui();
return <time>{new Intl.DateTimeFormat(i18n.locale, { dateStyle: "long" }).format(date)}</time>;
}
The same 1234.5 euros renders as a different string in each locale, with its own separators and symbol position. Formatting never converts money. A price stored in euros stays euros in every language, so keep the currency code next to each price in your database.
Step 3: How do I test every plural category before shipping?
Ask the browser which category a number falls into, then check each one has a form.
const ru = new Intl.PluralRules("ru");
[1, 2, 5, 21, 1.5].map((n) => ru.select(n));
// ["one", "few", "many", "one", "other"]
const ar = new Intl.PluralRules("ar");
[0, 1, 2, 3, 11, 100].map((n) => ar.select(n));
// ["zero", "one", "two", "few", "many", "other"]
Add one test per language you ship: render the component with a number from each category and assert none falls back to a missing form. Then read the Russian and Arabic pages in the browser once. A missing few in a catalog shows up as the wrong ending, not as an error.
What changes if I used a runtime widget before?
Runtime widgets translate the rendered text after load, so they cannot see a plural rule in your code. That is part of why moving translations into the repository matters. Lovalingo closed on 2026-08-31; the migration guide and the comparison page cover what changes when catalogs live in your repo.
With globalize.now, you connect the repository once and the codebase is converted in the app. After that, push jobs translate new catalog units and the result arrives as a pull request. Lovable syncs from GitHub, so the plural forms reach your app the same way as any other change. The setup is on the Lovable integration page, and the no-dashboard workflow is in localize a Lovable app without a dashboard.
globalize.now turns hardcoded app copy into translation-ready locale files and keeps them updated as you ship.
Try globalize.now free