Skip to content
fluenti

Complex Languages

Languages differ wildly in grammar. English has 2 plural forms; Arabic has 6. Russian mixes gender with plurals. Japanese has no plurals at all but uses counters and honorifics. Fluenti handles all of these through ICU MessageFormat with full support for nested messages — a capability many i18n libraries lack.

The Unicode CLDR defines 6 plural categories. Each language uses a different subset:

LanguageCategories usedExample rule
Englishone, other1 → one, 2+ → other
Frenchone, many, other0–1 → one, 1M+ → many
Arabiczero, one, two, few, many, otherAll 6 categories
Russianone, few, many, other1 → one, 2–4 → few, 5–20 → many
Polishone, few, many, otherSimilar to Russian with different ranges
JapaneseotherOnly 1 category — no plural forms
ChineseotherOnly 1 category
KoreanotherOnly 1 category
Welshzero, one, two, few, many, otherAll 6 categories

Fluenti uses the browser’s Intl.PluralRules which implements the full CLDR specification. You only need to provide the categories your target language requires.

Arabic uses all 6 CLDR plural categories with different noun forms for each.

PO catalog (locales/ar.po):

msgid "{count, plural, one {# message} other {# messages}}"
msgstr "{count, plural, zero {لا رسائل} one {رسالة واحدة} two {رسالتان} few {# رسائل} many {# رسالة} other {# رسالة}}"

The source English message has 2 branches; the Arabic translation expands to 6. Fluenti compiles both correctly — translators add the branches their language needs.

Component usage:

<template>
<!-- Source locale (en): "# message" / "# messages" -->
<!-- Arabic locale: all 6 categories from the PO catalog -->
<Plural :value="unreadCount" one="# message" other="# messages" />
</template>
<script setup>
import { Plural } from '@fluenti/vue/components'
const props = defineProps<{ unreadCount: number }>()
</script>

The source code only needs one and other (English rules). When the locale is ar, Fluenti loads the compiled Arabic catalog which contains all 6 forms.

CountCategoryArabic output
0zeroلا رسائل
1oneرسالة واحدة
2twoرسالتان
3–10few٣ رسائل
11–99many١١ رسالة
100+other١٠٠ رسالة

Russian has 4 plural categories and grammatical gender. When a message combines quantity with a gendered noun, the translation needs nesting — but your source code stays simple:

<template>
<!-- Your code: plain English, no ICU syntax -->
<p v-t>{{ count }} people arrived</p>
</template>
<script setup>
const props = defineProps<{ count: number }>()
</script>

The CLI extracts this as a simple message. The translator adds all the complexity in the PO catalog — your code never changes:

PO catalog (locales/ru.po):

msgid "{count} people arrived"
msgstr "{count, plural, one {Пришёл # человек} few {Пришли # человека} many {Пришло # человек} other {Пришло # человек}}"

When gender matters, use <Select> with <Plural> inside:

<template>
<Select :value="gender"
male="He arrived"
female="She arrived"
other="They arrived"
/>
</template>
<script setup>
import { Select } from '@fluenti/vue/components'
const props = defineProps<{ gender: string }>()
</script>

The Russian translator nests plural inside each gender branch:

msgid "{gender, select, male {He arrived} female {She arrived} other {They arrived}}"
msgstr "{gender, select, male {{count, plural, one {# мужчина пришёл} few {# мужчины пришли} many {# мужчин пришло} other {# мужчин пришло}}} female {{count, plural, one {# женщина пришла} few {# женщины пришли} many {# женщин пришло} other {# женщин пришло}}} other {{count, plural, one {# человек пришёл} few {# человека пришли} many {# человек пришло} other {# человек пришло}}}}"

The verb ending, noun form, and plural category all agree — impossible without nesting.

Japanese has no grammatical plurals (only other), but uses counters (助数詞) and formality levels. Your code stays in English:

<template>
<p v-t>{{ fileCount }} files</p>
</template>
<script setup>
const props = defineProps<{ fileCount: number }>()
</script>

The translator adds the correct counter (件) in the PO catalog:

msgid "{fileCount} files"
msgstr "{fileCount}件のファイル"

For formality levels, write natural English — the translator handles the formality branching:

<template>
<p v-t>You have {{ count }} files</p>
</template>
<script setup>
const props = defineProps<{ count: number }>()
</script>

The Japanese translator can add formality via select — complexity stays in the catalog, not your code:

msgid "You have {count} files"
msgstr "{count}件のファイルがあります"

Many European languages require gender agreement across the sentence. Your source code uses <Plural> — the translator handles gender in the catalog.

<template>
<Plural :value="count" one="# friend is online" other="# friends are online" />
</template>
<script setup>
import { Plural } from '@fluenti/vue/components'
const props = defineProps<{ count: number }>()
</script>

The English source is gender-neutral. The French translator nests select inside plural to get the correct adjective endings:

PO catalog (locales/fr.po):

msgid "{count, plural, one {# friend is online} other {# friends are online}}"
msgstr "{count, plural, one {{gender, select, male {# ami est connecté} female {# amie est connectée} other {# ami(e) est connecté(e)}}} other {{gender, select, male {# amis sont connectés} female {# amies sont connectées} other {# ami(e)s sont connecté(e)s}}}}"

Four French adjective endings (connecté / connectée / connectés / connectées) — all derived from the same <Plural> component.

For ordinal numbers (1st, 2nd, 3rd), write the position naturally with t` `:

<template>
<span v-t>Finished in {{ position }} place</span>
</template>
<script setup>
const props = defineProps<{ position: number }>()
</script>

The English translator uses selectordinal in the PO catalog to produce “1st”, “2nd”, “3rd”, etc.:

msgid "Finished in {position} place"
msgstr "Finished in {position, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} place"

Japanese uses a completely different pattern — no ordinal branching needed:

msgid "Finished in {position} place"
msgstr "第{position}位でフィニッシュ"

This uses Intl.PluralRules with type: 'ordinal' — rules vary by language.

Register custom formatters for language-specific formatting needs:

import { createFluenti } from '@fluenti/vue'
const i18n = createFluenti({
locale: 'ar',
formatters: {
// Arabic-Indic numerals
arabicNum: (value, _style, locale) =>
new Intl.NumberFormat(locale, {
numberingSystem: 'arab',
}).format(Number(value)),
// Japanese era-based dates
japaneseDate: (value, style, locale) =>
new Intl.DateTimeFormat(locale, {
calendar: 'japanese',
dateStyle: style || 'long',
}).format(value as Date),
// Locale-aware list formatting
list: (value, style, locale) =>
new Intl.ListFormat(locale, {
type: (style as Intl.ListFormatType) || 'conjunction',
}).format(value as string[]),
},
})

Then use them in your components with t` `:

<template>
<p v-t>Your balance: {{ balance }}</p>
<p v-t>Date: {{ today }}</p>
<p v-t>Attendees: {{ attendees }}</p>
</template>
<script setup>
const balance = 1234.56
const today = new Date()
const attendees = ['Alice', 'Bob', 'Charlie']
</script>

The translator applies the custom formatters in the PO catalog:

msgid "Your balance: {balance}"
msgstr "رصيدك: {balance, arabicNum}"
msgid "Date: {today}"
msgstr "日付: {today, japaneseDate, long}"
msgid "Attendees: {attendees}"
msgstr "参加者: {attendees, list, conjunction}"

Fluenti provides getDirection() for right-to-left languages (Arabic, Hebrew, Persian, Urdu). Set it on your root layout:

<template>
<html :lang="locale" :dir="dir">
<body>
<slot />
</body>
</html>
</template>
<script setup>
import { computed } from 'vue'
import { getDirection } from '@fluenti/core'
import { useI18n } from '@fluenti/vue'
const { locale } = useI18n()
const dir = computed(() => getDirection(locale.value))
</script>

When embedding LTR content inside RTL text (or vice versa), use the <bdi> element to prevent garbled display:

<!-- Isolate user-generated LTR content inside RTL context -->
<p dir="rtl">المستخدم: <bdi>{{ username }}</bdi> أرسل رسالة</p>

Full Example — Photo Sharing Notification

Section titled “Full Example — Photo Sharing Notification”

A real-world notification component. The source code is clean and readable — all linguistic complexity lives in the PO catalogs:

<template>
<div class="notification" :dir="dir">
<img :src="avatar" :alt="name" />
<p v-t>{{ name }} shared {{ photoCount }} photos with you</p>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { getDirection } from '@fluenti/core'
import { useI18n } from '@fluenti/vue'
const { locale } = useI18n()
const props = defineProps<{
name: string
avatar: string
photoCount: number
}>()
const dir = computed(() => getDirection(locale.value))
</script>

The source message is plain English. Each target language adds the grammatical complexity it needs:

English (locales/en.po) — 2 plural forms:

msgid "{name} shared {photoCount} photos with you"
msgstr "{name} shared {photoCount, plural, one {# photo} other {# photos}} with you"

Arabic (locales/ar.po) — 6 plural forms × gender = 18 variants:

msgid "{name} shared {photoCount} photos with you"
msgstr "{gender, select, male {{photoCount, plural, zero {لم يشارك {name} أي صورة معك} one {شارك {name} صورة واحدة معك} two {شارك {name} صورتين معك} few {شارك {name} # صور معك} many {شارك {name} # صورة معك} other {شارك {name} # صورة معك}}} female {{photoCount, plural, zero {لم تشارك {name} أي صورة معك} one {شاركت {name} صورة واحدة معك} two {شاركت {name} صورتين معك} few {شاركت {name} # صور معك} many {شاركت {name} # صورة معك} other {شاركت {name} # صورة معك}}} other {{photoCount, plural, zero {لم تتم مشاركة أي صورة من {name} معك} one {تمت مشاركة صورة واحدة من {name} معك} two {تمت مشاركة صورتين من {name} معك} few {تمت مشاركة # صور من {name} معك} many {تمت مشاركة # صورة من {name} معك} other {تمت مشاركة # صورة من {name} معك}}}}"

Japanese (locales/ja.po) — no plurals, just a counter:

msgid "{name} shared {photoCount} photos with you"
msgstr "{name}さんが写真を{photoCount}枚共有しました"