npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lunmark/vokativ-i18next

v0.0.1

Published

Serbian vocative for i18next: write {{name, voc}} in a Serbian message and the name is addressed properly.

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 site

It 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-i18next

Add 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 one
  • auto: true covers every Serbian locale (sr, sr-*). A locale code that names a script gets it (sr-Latn-RS → Latin, sr-Cyrl → Cyrillic); a bare sr keeps the name's own script.
  • A locale key covers that locale and the ones under it ("sr-Latn" covers sr-Latn-RS); the most specific key wins. "latin" or "cyrillic" writes the name in that script (Ivan → Иване on a Cyrillic page), true keeps the name's own script, false turns 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-Latn is still Latin.
  • Everywhere else, voc leaves the name as it is, so a Serbian message reached through fallbackLng on 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, :voc is added to i18next-mf2's functions.
  • Messages without voc aren'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's debug logs "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.

License

MIT. Name data: "Vokativi" by Startit, CC BY 4.0, modified.