Getting Started
Internationalization
190 strings are baked into the components themselves — a Close on every dialog, a No data under every empty table, the whole of LoginForm . They ship translated into six languages. i18n is not a dependency: a language is a plain object you hand to App , and an app that hands it nothing renders English.
Try it
Every component below is stock — no label or placeholder props are passed. Switching the language re-renders the copy they carry internally, the month names Intl draws over the calendar, and the 24-hour clock most of Europe uses and English doesn't.
Language
Drop your file here
That switcher writes to this site's own useI18n() locale, which app.vue passes straight to App — the same four lines shown below. The docs prose stays English on purpose: this page localizes the library , not the site.
Setup
Wrap the app once in App and give it a locale. That's the entire integration — no module option, no plugin, and nothing to install.
<script setup lang="ts">
import { de } from "@jgastager/bundy-ui/locale";
</script>
<template>
<UiApp :locale="de">
<NuxtPage />
</UiApp>
</template>App also mounts the toast stack, so useToast() works anywhere without placing a Toaster by hand. It's the one component worth having in every project.
A locale is just an object
Six ship with the package, importable by name or all at once from @jgastager/bundy-ui/locale . Each carries its own name, the code Intl formats with, and a reading direction — App puts the last two on <html> for you.
Deutsch
de · ltr
English
en · ltr
Español
es · ltr
Français
fr · ltr
Italiano
it · ltr
Nederlands
nl · ltr
import { de } from "@jgastager/bundy-ui/locale";
// or: import * as locales from "@jgastager/bundy-ui/locale";
de.name; // "Deutsch" — its own name, for a picker
de.code; // "de" — handed to Intl for dates, times, numbers
de.dir; // "ltr" — applied to <html> by App
de.messages; // every string the components renderWith @nuxtjs/i18n
Nothing special — read the active language and index into the locales. The library never looks for vue-i18n itself, which is what keeps it from becoming a dependency of every project that installs this package.
<script setup lang="ts">
import * as locales from "@jgastager/bundy-ui/locale";
const { locale } = useI18n();
</script>
<template>
<UiApp :locale="locales[locale]">
<NuxtPage />
</UiApp>
</template>Changing one string
extendLocale deep-merges over a locale, so you name only what changes and everything else keeps its shipped wording.
import { en } from "@jgastager/bundy-ui/locale";
const locale = extendLocale(en, {
code: "en-AU",
messages: {
table: { empty: "Nothing here yet" },
loginForm: { submit: "Let me in" },
},
}); Where the wording is a product decision rather than a language one, there's usually a prop instead — a title on SettingsModal , an empty on Table — and an explicit prop always wins.
Adding a language
defineLocale takes the same shape, typed against Messages so a missing key is a compile error rather than a key path rendered on screen.
import type { Messages } from "@jgastager/bundy-ui";
export default defineLocale<Messages>({
name: "Polski",
code: "pl",
messages: {
modal: { close: "Zamknij" },
table: { empty: "Brak danych" },
// …the type lists the rest
},
});RTL comes from the locale
Each locale carries a `dir`, and `App` applies it to `<html>` — covering the portalled half of the UI too, which a wrapper element wouldn't reach. The six bundled locales are all `ltr`; a locale you define with `dir: 'rtl'` flips the document without any further wiring.
What isn't translated
Three things are deliberately left in place, because translating them would make them wrong.
Brand names
Visa, Mastercard, Google, GitHub. Proper nouns are spelled the same everywhere, and a translated one reads as a mistake.
Language names
LanguageSelect lists each language in its own words — Deutsch, Français, 日本語 — so someone can find their language without already reading yours.
Your data
Table rows, menu items, chart series, emoji categories. Those strings come from your app, so they're translated where the rest of your content is.
Country names in PhoneInput do follow the locale, and come from Intl.DisplayNames rather than from a file here — the ISO code is already in the data, so every language works, not just these six.