
Product
Introducing Socket Fix for Safe, Automated Dependency Upgrades
Automatically fix and test dependency updates with socket fix—a new CLI tool that turns CVE alerts into safe, automated upgrades.
Resolve all your localization troubles by delegating locales edge cases to translators and be on rise to the occasion.
pnpm add nanointl
# or: npm install nanointl
Entrypoint of application localization in nanointl is intl
object. intl
object is immutable and represents exactly one locale.
import { makeIntl } from 'nanointl';
let intl = makeIntl('en', {
secondsPassed: '{passed, plural, one {1 second} other {# seconds}} passed',
switchLocale: 'Switching locale...',
});
const start = Date.now();
setInterval(() => {
console.log(intl.formatMessage('secondsPassed', { passed: (Date.now() - start) / 1000 }));
}, 1000);
setTimeout(() => {
console.log(intl.formatMessage('switchLocale'));
intl = makeIntl('es', {
secondsPassed: 'pasaron {passed, plural, one {1 segundo} other {# segundos}}',
switchLocale: 'Cambio de configuración regional...',
});
}, 3500);
@nanointl/react
package.pnpm add @nanointl/react
# or: npm install @nanointl/react
IntlProvider
component, useTranslation
and useIntlControls
hooks via makeReactIntl
:// src/i18n.ts
import { makeReactIntl } from '@nanointl/react/src/nanointl-react';
import enMessages from './locales/en.json';
import { tagsPlugin } from 'nanointl/tags';
export const { IntlProvider, useTranslation, useIntlControls } = makeReactIntl('en', enMessages);
IntlProvider
.// src/main.tsx
+ import { IntlProvider } from './i18n'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
+ <IntlProvider>
<App />
+ </IntlProvider>
</React.StrictMode>,
);
useTranslation
or switch locales via useIntlControls
.// src/App.tsx
...
export const App: React.FC = () => {
+ const t = useTranslation();
...
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
- clicked {count} time(s)
+ {t('counter', { count })}
</button>
<p>
- Edit <code>{filePath}</code> and save to test HMR
+ {t('description', {
+ filePath: 'src/App.tsx',
+ code: ({ children }) => <code key="code">{children}</code>,
+ })}
</p>
</div>
...
@nanointl/unplugin
allows you to bundle application for any specific locale and load other locales dynamically.
pnpm add @nanointl/unplugin
# or: npm install @nanointl/unplugin
Place localization json files into specific path of your project (like ./src/locales/en.json
, ./src/locales/es.json
and ./src/locales/fr.json
).
Import plugin for your bundler (available exports are nanointlVitePlugin
, nanointlEsbuildPlugin
,nanointlRollupPlugin
, nanointlWebpackPlugin
and just nanointlUnplugin
).
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
+ import { nanointlVitePlugin } from '@nanointl/unplugin';
export default defineConfig({
plugins: [
react(),
+ nanointlVitePlugin({
+ defaultLocale: 'en',
+ localesDir: './src/locales',
+ }),
],
});
// src/i18n.ts
import { makeReactIntl } from '@nanointl/react/src/nanointl-react';
- import enMessages from './locales/en.json';
+ import { initLocale, initMessages, loadMessages } from '@nanointl/unplugin/runtime';
- let intl = makeIntl('en', {
- secondsPassed: '{passed, plural, one {1 second} other {# seconds}} passed',
- switchLocale: 'Switching locale...',
- });
+ let intl = makeIntl(initLocale, initMessages);
+
+ loadMessages.fr().then((frMessages) => intl = makeIntl('fr', frMessages));
# Or, in React application:
- export const { IntlProvider, useTranslation, useIntlControls } = makeReactIntl('en', enMessages);
+ export const { IntlProvider, useTranslation, useIntlControls } = makeReactIntl(initLocale, initMessages, { loadMessages });
nanointl supports automatic ICU message syntax type inference in Typescript.
const intl = makeIntl('en', {
secondsPassed: '{passed, plural, one {1 second} other {# seconds}} passed',
switchLocale: 'Switching locale...',
} as const);
intl.formatMessage('secondsPassed', {});
// ^ Property 'passed' is missing in type '{}'
// but required in type '{ passed: number; }'
Messages object should be const
(without as const
messages object above would be like { secondsPassed: string, switchLocale: string }
what will provide no information for typescript about messages ICU expressions).
For messages stored in json files you can use typescript-json-as-const-plugin
typescript plugin. It changes the way how typescript inferencing JSON files typings to as const
behavior.
pnpm add -D typescript-json-as-const-plugin
# or: npm install --save-dev typescript-json-as-const-plugin
tsconfig.json
{
"compilerOptions": {
...
"plugins": [
+ { "name": "typescript-json-as-const-plugin", "include": ["./src/locales"] },
...
]
},
...
}
(VS Code only) switch to workspace version of Typescript
Restart typescript server.
Nanointl supports interpolation in ICU syntax.
Current account: {accountName}
will produce "Current account: Guest".List of brackets: '{}'[]()
will produce "List of brackets: {}[]()".John O''Connell
will produce "John O'Connell".Balance: {balance, number, ::.00}
with input balance=42
will produce "Balance: 42.00".Plurals mechanism is available in nanointl out of the box. In plurals you can specify how to write parts of text that depend on provided number variables: {passed, plural, one {1 second} other {# seconds}} passed
may produce "1 second passed" or "5 seconds passed" depending on value provided in passed
variable.
Following values are allowed: zero
, one
, two
, few
, many
, other
and exacts.
While English localization may require only one
and other
forms, other languages may require each of them.
Exacts syntax allows you to specify how to handle specific values of provided variables: {passed, plural, one {1 second} other {# seconds} =42{Answer to the Ultimate Question of Life, the Universe, and Everything seconds}} passed
You can use special #
symbol to insert parent variable.
Select mechanism is available in nanointl out of the box. It allows translators to specify how text may change depending on provided variables: Send money to {gender, select, male {him} female {her} other {them}} via {transactionProvider}.
.
In some cases rich text formatting like strong or emphasized text is supported in nanointl via plugins.
Provides partial support of Markdown syntax. May be enabled via markdown plugin.
+import { markdownPlugin } from 'nanointl/markdown';
const intl = makeIntl(locale, { markdownExample: `Hello **world**` },
+ { plugins: [markdownPlugin] },
);
Allows you to use strong syntax (via wrapping text into *text*
or **text**
), emphasis syntax (via wrapping text into _text_
or __text__
), code syntax (via wrapping text into backticks (`)) and link syntax (via syntax [link text](https://link_url)
).
Requires you to specify how to render markdown chunks in the second argument of formatMessage
call.
intl.formatMessage('markdownExample', { strong: ({ children }) => `<b>${children}</b>` }); // rendering to simple html
intl.formatMessage('markdownExample', { strong: ({ children }) => <b>{children}</b> }); // rendering to React element
Markdown syntax may be escaped with single quotes.
Provides partial support of Markdown syntax. May be enabled via tags plugin.
+import { tagsPlugin } from 'nanointl/tags';
const intl = makeIntl(locale, { tagExample: `Hello <b>world</b>` },
+ { plugins: [tagsPlugin] },
);
Requires you to specify how to render every used tag in the second argument of formatMessage
call.
intl.formatMessage('tagExample', { b: ({ children }) => `<b>${children}</b>` }); // rendering to simple html
intl.formatMessage('tagExample', { b: ({ children }) => <b>{children}</b> }); // rendering to React element
Provides powerful support of numbers formatting. May be enabled via numbers plugin.
+import { numbersPlugin } from 'nanointl/numbers';
const intl = makeIntl(locale, { numberExample: `Balance: {balance, number, ::.00 sign-always}` },
+ { plugins: [numbersPlugin] },
);
Available tokens:
percent
(alias is %
) outputs fraction as a percent. E.g. ::percent
with 0.25 as input will produce "25%".scale/100
(where 100
is a custom number) multiples values by provided number. The number may be a fraction.measure-unit/meter
(where meter
may be replaced with any environment supported unit, alias is unit/meter
) adds a measure unit to output.currency/USD
(where USD
may be replaced with any environment supported currency).unit-width-iso-code
enforces output of unit as a localized ISO symbol (such as €).unit-width-short
enforces output of unit as a short word (such as USD).unit-width-full-name
enforces output of unit as a full name (such as "US dollars").unit-width-narrow
enforces output of unit as a localized symbol (even if there is no in ISO, such as ₴).compact-short
(alias is K
) makes output compact by adding symbols like K, M, B, etc. after scaled number.compact-long
(alias is KK
) makes output compact by adding words like thousand, million, billion, etc. after scaled number.sign-auto
enforces displaying numbers sign (+
or -
) behaviour based on locale.sign-always
(alias is +!
) enforces always displaying of numbers sign (+
or -
).sign-never
(alias is +_
) enforces never displaying of numbers sign (+
or -
).sign-except-zero
(alias is +?
) enforces always displaying of numbers sign (+
or -
) for all numbers except zero.sign-accounting
(alias is ()
) enforces sign accounting for units based on locale default behaviour (such as wrapping into partnership negative value of USD).sign-accounting-always
(alias is ()!
) enforces sign accounting for units (such as wrapping into partnership negative value of USD).sign-accounting-except-zero
(alias is ()?
) enforces sign accounting for units that value is not equal to zero (such as wrapping into partnership negative value of USD).group-always
enforces to always group digits (like 100,000
).group-auto
enforces digits grouping behaviour based on locale.group-off
(alias is ,_
) disables digits grouping.group-min2
(alias is ,?
) enforces grouping of symbols with minimum 2 digits in each group.integer-width
enforces number output as an integer.You can use numbers template to limit minimal of maximum count of digits in number output.
Symbol 0
represents minimal count of digits while #
represents maximum count of digits.
You can also use *
symbol after minimal count of digits to mark that there is no maximum limit.
When template starts with a dot symbol (.
), fraction digits are affected. When template starts with slash symbol (/
), integer part digits are affected.
If template starts with integer-width/
, integer part digits and fraction part is hidden.
Examples:
.00##
means that number serializer will write at least 2 and at most 4 fraction digits..00*
means that number serializer will write at least 2 fraction digits..00
means that number serializer will write 2 fraction digits..00
means that number serializer will write 2 fraction digits.00.
(where count of 0
sign is not limited) sets minimal count of fraction digits. E.g. ::.00
with 25 as input will produce "25.00".Examples for en
locale :
::percent
with 0.25 as input will produce "25%"::%
with 0.25 as input will produce "25%"::.00
with 25 as input will produce "25.00"::percent .00
with 0.25 as input will produce "25.00%"::% .00
with 0.25 as input will produce "25.00%"::scale/100
with 0.3 as input will produce "30"::percent scale/100
with 0.003 as input will produce "30%"::%x100
with 0.003 as input will produce "30%"::measure-unit/meter
with 5 as input will produce "5 m"::unit/meter
with 5 as input will produce "5 m"::measure-unit/meter unit-width-full-name
with 5 as input will produce "5 meters"::unit/meter unit-width-full-name
with 5 as input will produce "5 meters"::currency/CAD
with 10 as input will produce "CA$10.00"::currency/CAD unit-width-narrow
with 10 as input will produce "$10.00"::compact-short
with 5000 as input will produce "5K"::K
with 5000 as input will produce "5K"::compact-long
with 5000 as input will produce "5 thousand"::KK
with 5000 as input will produce "5 thousand"::compact-short currency/CAD
with 5000 as input will produce "CA$5K"::K currency/CAD
with 5000 as input will produce "CA$5K"::group-off
with 5000 as input will produce "5000"::,\_
with 5000 as input will produce "5000"::group-always
with 15000 as input will produce "15,000"::,?
with 15000 as input will produce "15,000"::sign-always
with 60 as input will produce "+60"::+!
with 60 as input will produce "+60"::sign-always
with 0 as input will produce "+0"::+!
with 0 as input will produce "+0"::sign-except-zero
with 60 as input will produce "+60"::+?
with 60 as input will produce "+60"::sign-except-zero
with 0 as input will produce "0"::+?
with 0 as input will produce "0"::sign-accounting currency/CAD
with -40 as input will produce "(CA$40.00)"::() currency/CAD
with -40 as input will produce "(CA$40.00)"Provides powerful support of dates and times formatting. May be enabled via datetime plugin.
Unlike to dates focused libraries such as dayjs
or momentjs
, order of displayed parts is not controlled by provided pattern and delegated to environment localization mechanisms. Tokens in pattern controls only appearance of date/time part if it is suitable for current locale.
Params may be either pattern that starts with ::
with tokens after it or set of following values: short
, medium
, long
and full
.
+import { datetimePlugin } from 'nanointl/datetime';
const intl = makeIntl(locale, {
patternExample: `Will arrive at: {arriveTime, time, ::hh mm ss}`,
literalExample: `Will arrive at: {arriveTime, time, medium}`
},
+ { plugins: [datetimePlugin] },
);
Available date/time tokens:
G
(or GG
, GGG
, GGGG
) – Era designators.yy
(or yyyy
) – Years.M
(or MM
, MMM
, MMMM
, MMMMM
) – Months.d
(or dd
) – Days.E
(or EE
, EEE
) – Days of week.j
(or jj
) – Hours.h
(or hh
) – Hours [1-12].H
(or HH
) – Hours [0-23].m
(or mm
) – Minutes.s
(or ss
) – Seconds.z
(or zz
, zzz
, zzzz
) – Time Zones.To write you own plugin you should create an object that satisfies type NanointlPlugin
:
import { NanointlPlugin } from 'nanointl';
export const numberPlugin: NanointlPlugin<UserOptions> = {
name: 'my-awesome-plugin',
init({ addParser, addSerializer, addPostParser }) {
addParser('super-token', () => {...});
addSerializer('super-token', () => {...});
addPostParser(() => {...});
},
};
Adding parser and serializer from plugin enables support of named tokens: Hello, {username, super-token, custom-parameters}
.
Adding post parsers allows plugin to parse syntax unrelated to ICU (such as markdown and tags plugins do).
See built-in plugins for examples:
Better benchmarks are planned to be done.
Core bundle size:
lingUi | formatjs | nanointl |
---|---|---|
3526 B | 28322 B | 2714 B |
Formatting 1k messages on same machine:
lingUi | formatjs | nanointl |
---|---|---|
74521 ns | 90865 ns | 62899 ns |
If you found bug, want to add new feature or have question feel free to open an issue or fork repository for pull requests.
FAQs
Tiny and robust localization library.
We found that nanointl demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Automatically fix and test dependency updates with socket fix—a new CLI tool that turns CVE alerts into safe, automated upgrades.
Security News
CISA denies CVE funding issues amid backlash over a new CVE foundation formed by board members, raising concerns about transparency and program governance.
Product
We’re excited to announce a powerful new capability in Socket: historical data and enhanced analytics.