@lunmark/vokativ-i18next
v0.0.1
Published
Serbian vocative for i18next: write {{name, voc}} in a Serbian message and the name is addressed properly.
Maintainers
Readme
@lunmark/vokativ-i18next
The Serbian vocative for i18next: mark where a Serbian message addresses someone, and the name is put in the vocative.
// locales/sr/translation.json
"greeting": "Dobar dan, {{name, voc}}!" // → "Dobar dan, Ivane!"
// locales/en/translation.json, unchanged
"greeting": "Good afternoon, {{name}}!" // → "Good afternoon, Ivan!"t("greeting", { name: user.firstName }) // no change at any call siteIt works everywhere i18next formats a message: t(), react-i18next's useTranslation and
<Trans>, next-i18next, i18next-vue and the rest, on the server and in the browser. Messages
written in ICU or MessageFormat 2 work too.
Setup
pnpm add @lunmark/vokativ-i18nextAdd the plugin where you set up i18next:
import i18next from "i18next"
import vokativ from "@lunmark/vokativ-i18next"
i18next
.use(vokativ({ languages: { auto: true } }))
.init({
// your options, unchanged
})With react-i18next it goes next to .use(initReactI18next), in either order. With
next-i18next, put it in the use array of its config: use: [vokativ()].
Then write {{name, voc}} where your Serbian messages address someone. That's all.
Languages
languages decides which locales get the vocative, and in which script the name is written. It
works exactly as in @lunmark/vokativ-next-intl.
vokativ() // { auto: true }, the default
vokativ({ languages: { "sr-Latn": "latin", sr: "cyrillic" } }) // explicit
vokativ({ languages: { auto: true, "sr-ME": false } }) // all Serbian but oneauto: truecovers every Serbian locale (sr,sr-*). A locale code that names a script gets it (sr-Latn-RS→ Latin,sr-Cyrl→ Cyrillic); a baresrkeeps the name's own script.- A locale key covers that locale and the ones under it (
"sr-Latn"coverssr-Latn-RS); the most specific key wins."latin"or"cyrillic"writes the name in that script (Ivan → Иване on a Cyrillic page),truekeeps the name's own script,falseturns the vocative off. - A locale code that names its own script keeps it when the matching key doesn't name one:
with
{ sr: "cyrillic" },sr-Latnis still Latin. - Everywhere else,
vocleaves the name as it is, so a Serbian message reached throughfallbackLngon an English page, or a tag that slips into another language, does no harm.
The locale that counts is the one t() formats for, not the one the message was found in.
A misspelled setting ("cyrilic") throws when vokativ() is called, rather than doing nothing.
A message can name the script itself, which wins over the setting:
"greeting": "Zdravo, {{name, voc(script: cyrillic)}}!"Gender
A woman's surname never changes ("gospođo Janković"), so surnames are declined only for men.
The plugin reads the gender from i18next's own
context, which Serbian messages often
use anyway for "Dragi/Draga", or from a gender value ("m"/"male" or "f"/"female"):
"dear_male": "Dragi {{name, voc}}",
"dear_female": "Draga {{name, voc}}"
// t("dear", { name: "Marko Janković", context: "male" }) → "Dragi Marko Jankoviću"
// t("dear", { name: "Ana Janković", context: "female" }) → "Draga Ana Janković"Two people in one message? Give each their own with formatParams:
t("pair", { a, b, formatParams: { a: { gender: "m" }, b: { gender: "f" } } })Without a gender, only the first name is declined; a surname is never wrong left as written.
ICU and MessageFormat 2
With i18next-icu or i18next-mf2, the same plugin works in their syntax. Add it next to theirs, in either order:
i18next.use(ICU).use(vokativ()).init({ ... })| Messages | Syntax | Gender and script |
| --- | --- | --- |
| i18next's own | {{name, voc}} | context or gender value; voc(script: latin) |
| i18next-icu | <voc>{name}</voc> | gender value |
| i18next-mf2 | {$name :voc} | {$name :voc gender=$gender script=latin} |
// ICU: the same tag as in vokativ-next-intl
"dear": "{gender, select, f {Draga <voc>{name}</voc>} other {Dragi <voc>{name}</voc>}}"
// MF2
"dear": "Dragi {$name :voc gender=$gender}"In ICU, the tag holds exactly one argument (<voc>{name}</voc>). Around anything else it's
dropped, so it doesn't show up in the text, with a warning when i18next's debug is on. In MF2,
a voc function you register yourself wins.
TypeScript
With i18next's typed t(), {{name, voc}} is
checked like any other value, with no setup: leave name out of the options, misspell it, or
pass a number, and it's a type error.
For MF2 messages (parseInterpolation: "mf2"), tell i18next that :voc takes a string:
declare module "i18next" {
interface CustomTypeOptions {
interpolationFormatTypeMap: { voc: string }
}
}How it works
The plugin adds a voc
formatter,
i18next's documented way to format a value. So the name is declined before i18next escapes it,
and wherever i18next formats a message.
- For ICU,
<voc>{name}</voc>becomes an argument of its own, holding the declined name, before i18next-icu parses the message. For MF2,:vocis added to i18next-mf2's functions. - Messages without
vocaren't touched. - Names longer than 100 characters are left as written.
- The declension is
@lunmark/vokativ, about 3.5 KB minified and gzipped with this package.
Good to know
- Requirements: i18next 23 or later. ICU and MF2 are tested with i18next-icu 2.6 and i18next-mf2 2.1.
- Call it:
.use(vokativ()). Without the parentheses, i18next rejects it as "a wrong module". - Without the plugin,
{{name, voc}}comes out as the plain name, and i18next'sdebuglogs "there was no format function for voc". A missing setup never breaks the text. - The ICU and MF2 support relies on parts of those plugins that aren't documented APIs
(i18next-icu's
parse, i18next-mf2's options); i18next's own format uses only the documented formatter API. This package is tested against the current versions of all three.
