# Localization

> The library brings no i18n stack of its own: the locale, the dictionaries and where the choice is stored stay with the application. Three languages ship.

- Page: https://granularity.tech/docs/localization
- Last reviewed: 2026-10-16

The package neither imposes a separate i18n stack on the application nor creates
an instance of its own. The locale, the fallback locale, where the chosen
language is stored and how the dictionaries are merged are the application's job;
the library only asks it for a translation by key.

## How it works

:::steps
1. The component asks for a string by a key such as `gr.pagination.prev`.
2. If a translation exists, it is used.
3. If the layer is not wired up or returned the key itself, the component falls back to its built-in text.
:::

The third step matters more than it looks: **a missing translation does not break
the interface**. The package can be adopted gradually and some service strings
moved into dictionaries later — the components keep working in English.

## What ships

The library carries dictionaries for three languages: **English, Spanish and
Russian**. All three are complete — the key sets are compared by one of the
package's gates rather than by eye.

:::callout{type="info"}
The portal you are reading speaks two languages while the library speaks three.
That is not an error: the Spanish dictionary of the core is complete and working,
there is simply no Spanish documentation yet.
:::

## Wiring it up

The recommended layer is `@feugene/fint-i18n`: it is declared as an optional peer
dependency and sits well with an approach where the application owns everything.
If the project already has a mature i18n, the package fits into that one too.

```ts
import { createFintI18n } from '@feugene/fint-i18n/core'
import { installI18n } from '@feugene/fint-i18n/vue'
import { GRANULARITY_I18N_BLOCK, en, ru } from '@feugene/granularity/i18n'

const i18n = createFintI18n({
  locale: 'ru',
  fallbackLocale: 'en',
  loaders: [en, ru],
})

i18n.registerBlocks([GRANULARITY_I18N_BLOCK])
await i18n.loadUsedBlocks('ru')

// Without this the package cannot find the instance through provide/inject
// and stays on its built-in texts.
installI18n(app, i18n)
```

The subpath `@feugene/granularity/i18n` hands out the `gr` block, the lazy `en`,
`es` and `ru` loaders and the adapter key with its types. Per-locale loaders are
exactly what you want in production: the bundler shakes out the unused languages.
The aggregate of every locale at once lives separately, in
`@feugene/granularity/i18n/all`, and is meant for demos, e2e and tooling.

## Companion packages

Every companion has a string block of its own — `GR_CHRONO_I18N_BLOCK`
(`grChrono`), `GR_MEDIA_I18N_BLOCK` (`grMedia`) and so on — and they are wired
into **the same instance**, next to the main one:

```ts
import { GRANULARITY_I18N_BLOCK, en as coreEn, ru as coreRu } from '@feugene/granularity/i18n'
import { GR_CHRONO_I18N_BLOCK, en, ru } from '@feugene/granularity-chrono/i18n'

const i18n = createFintI18n({ locale: 'ru', fallbackLocale: 'en', loaders: [coreEn, coreRu, en, ru] })
i18n.registerBlocks([GRANULARITY_I18N_BLOCK, GR_CHRONO_I18N_BLOCK])
```

One i18n layer for the whole application — the product screens and the design
system alike.

## What does not come from dictionaries

The calendar, the time and relative dates take everything locale-dependent from
`Intl`: month and weekday names, the first day of the week, the parsing order of
a date, the 12- or 24-hour default. The dictionary carries interface strings
only — labels, `aria-label`s, panel titles.

The practical consequence: the date components speak **every** language the
engine knows, not the three listed above.

## Overriding strings

Labels, buttons, `aria-label`s and service messages are changed **in the
application's dictionary**, not by forking the package. That keeps the
terminology under a single control and stops upgrades from turning into conflict
resolution.

## Changing the language, and the provider

`GrConfigProvider` takes a `locale` prop, but that is **a request for the adapter
to switch**, not the source of truth: the provider neither stores the locale nor
substitutes it. An adapter of its own contract is asked through `syncLocale`; on a
fint-i18n instance, which has no such method, the provider calls `setLocale()` —
the same call that switches the language directly: `i18n.setLocale('en')`. An adapter passed in the `i18n` prop is handed down through a
facade rather than by value — so an adapter created asynchronously reaches the
components once it appears, and swapping the adapter re-renders the strings.
Without the prop the facade delegates to the adapter found higher up the tree,
including one installed through `installI18n`. See
[configuration](/docs/configuration).
