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-next-intl

v0.0.1

Published

Serbian vocative for next-intl: write <voc>{name}</voc> in a Serbian message and the name is addressed properly.

Downloads

425

Readme

@lunmark/vokativ-next-intl

The Serbian vocative for next-intl: mark where a Serbian message addresses someone, and the name is put in the vocative.

// messages/sr.json
"greeting": "Dobar dan, <voc>{name}</voc>!"   // → "Dobar dan, Ivane!"
// messages/en.json, unchanged
"greeting": "Good afternoon, {name}!"          // → "Good afternoon, Ivan!"
t("greeting", { name: user.firstName }) // no change at any call site

It works everywhere next-intl formats a message: server and client components, t(), t.rich(), t.markup() and getTranslations(), with or without precompiled messages.

Setup

pnpm add @lunmark/vokativ-next-intl

In next.config.ts, use it in place of next-intl's plugin. next-intl's own setting goes second, unchanged:

import createVokativNextIntlPlugin from "@lunmark/vokativ-next-intl/plugin"

const withNextIntl = createVokativNextIntlPlugin(
  { languages: { auto: true } },
  "./i18n/request.ts"
)

export default withNextIntl(nextConfig)

Already wrapping your config another way? withVokativ(withNextIntl(nextConfig)) does the same after next-intl's plugin:

import { withVokativ } from "@lunmark/vokativ-next-intl/plugin"

Then put <voc>…</voc> around the name in your Serbian messages. That's all.

Languages

languages decides which locales get the vocative, and in which script the name is written.

{ languages: { auto: true } }                          // the default
{ languages: { "sr-Latn": "latin", sr: "cyrillic" } }  // explicit
{ 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 tag that slips into another language does no harm.

A misspelled setting ("cyrilic") stops the build with an error rather than doing nothing.

Gender

If the message also gets a gender value ("m"/"male" or "f"/"female"), which Serbian messages often need anyway for "Dragi/Draga", surnames are declined for men:

"dear": "{gender, select, f {Draga <voc>{name}</voc>} other {Dragi <voc>{name}</voc>}}"
// { name: "Marko Janković", gender: "m" } → "Dragi Marko Jankoviću"
// { name: "Ana Janković", gender: "f" }   → "Draga Ana Janković"

Without one, only the first name is declined; a surname is never wrong left as written.

Tests (Vitest)

Vitest doesn't run next.config, so add the same swap there, or a message with <voc> renders as its key:

// vitest.config.ts
import { vokativNextIntl } from "@lunmark/vokativ-next-intl/vitest"

export default defineConfig({
  plugins: [vokativNextIntl({ languages: { auto: true } })],
})

How it works

Every message next-intl formats goes through one function in use-intl, use-intl/format-message. The plugin points that import at a small stand-in, the way next-intl itself swaps it when it precompiles messages. The stand-in adds a voc handler and calls the original formatter.

  • A message without <voc> goes straight through: about 20 ns extra.
  • A voc you pass yourself always wins.
  • Names longer than 100 characters are left as written.
  • The declension is @lunmark/vokativ, about 3 KB gzipped with this package.

Good to know

  • Requirements: next-intl 4, and Next.js 15.3 or later, with Turbopack or webpack.
  • Typed messages generated from your Serbian file make t.rich() and t.markup() ask for a voc handler, since the tag is in their type. Plain t() doesn't. Most apps type their messages from English, where there's no tag.
  • t.markup() doesn't escape values, with or without <voc>; the name comes out exactly as {name} would. t() and t.rich() render through React, which escapes.
  • use-intl/format-message is a public entry point, but swapping it isn't a documented next-intl API. This package pins next-intl 4 and is tested against it.

License

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