~1kB Type-safe i18n and l10n JavaScript utility.
Ukti uses ISO 639-1 for language codes and ISO 3166-1 alpha-2 for region codes by default.
"Ukti" from Sanskrit "उक्ति" translates Speech or Language.
For any ESM and CommonJS JavaScript environment. If TypeScript is used, version 4.5+ is required.
npm i ukti
For UMD version:
import { createUktiTranslator } from 'ukti/build/umd/ukti.umd.cjs'
<script src="https://cdn.jsdelivr.net/npm/ukti/build/umd/ukti.umd.cjs" />
<script src="https://unpkg.com/ukti/build/umd/ukti.umd.cjs" />
Ukti accepts any ISO 639-1 language codes for translations.
import { createUktiTranslator } from 'ukti'
type Definition = {
title: undefined
form: {
label: undefined
error: [{ name: string }]
}
}
const translator = createUktiTranslator<Definition>({
translations: {
en: {
title: 'Language',
form: {
label: 'Type your language',
error: 'The language {{name}} is invalid.'
}
},
es: {
title: 'Idioma',
form: {
label: 'Escribe tu idioma',
error: 'El idioma {{name}} no está soportado.'
}
}
}
})
const t = translator('en')
console.log(t.title()) // 'Language'
console.log(t.form.label()) // 'Type your language'
console.log(t.form.error({ name: 'Spanglish' })) // 'The language Spanglish is invalid.'
If the language used is not defined in the translations, the default language is used.
If no configured, 'en'
(English) is used.
The translations object definition can only have two levels of depth for simplicity.
The available languages and default language can be specified to constraint the translations. All translations are optional, except the default one. They can be any string but ISO 639-1 codes are recommended.
import { createUktiTranslator } from 'ukti'
type Definition = {
name: undefined
}
type Languages = 'es' | 'fr' | 'hi'
type LanguagesDefault = 'es'
const translator = createUktiTranslator<Definition, Languages, LanguagesDefault>({
languageDefault: 'es',
translations: {
es: {
name: 'Nombre'
},
fr: {
name: 'Nom'
}
}
})
const t = translator('hi')
console.log(t.name()) // 'Nombre'
If the specified language to be used is not defined ('hi'
) then the default
language ('es'
) is used.
Language translations are optional (except the default one) but all definition properties have to be specified in each one.
If there is an incomplete language translation defined (such as partially defined), an empty string is returned when trying to translate it. This is to prevent inconsistent translations with some parts in one language and others in another one.
Ukti can optionally have regions by language. ISO 3166-1 alpha-2 country codes list is used by default.
import { createUktiTranslator } from 'ukti'
type Definition = {
friend: undefined
}
const translator = createUktiTranslator<Definition>({
translations: {
en: {
friend: 'Friend',
regions: {
US: {
// United States
friend: 'Dude'
},
CA: {
// Canada
friend: 'Buddy'
}
}
},
es: {
friend: 'Amigo',
regions: {
CO: {
// Colombia
friend: 'Parce'
},
VN: {
// Venezuela
friend: 'Pana'
}
}
}
}
})
const t = translator('es', 'CO') // Spanish from Colombia
console.log(t.friend()) // 'Parce'
If an unknown region is specified, the general language translation is used.
Custom regions can be specified similar to the IETF language tag spec.
import { type UktiLanguages, createUktiTranslator } from 'ukti'
type Definition = {
friend: undefined
}
type LanguageDefault = 'en'
type Regions = 'USA' | 'Canada'
const translator = createUktiTranslator<Definition, UktiLanguages, LanguageDefault, Regions>({
translations: {
en: {
friend: 'Friend',
regions: {
USA: {
friend: 'Dude'
},
Canada: {
friend: 'Buddy'
}
}
}
}
})
const t = translator('en', 'Canada')
console.log(t.friend()) // 'Buddy'
Translations texts are templates supporting variables interpolations. e.g. displaying different words based on conditions.
import { createUktiTranslator } from 'ukti'
type Definition = {
stock: [{ qty: number }]
}
const translator = createUktiTranslator<Definition>({
translations: {
en: {
stock: "There {{qty == 1 ? 'is' : 'are'}} {{qty}} product{{qty == 1 ? '' : 's'}} available"
}
}
})
const t = translator('en')
console.log(t.stock({ qty: 1 })) // 'There is 1 product available'
console.log(t.stock({ qty: 3 })) // 'There are 3 products available'
The variables can be formatted using the native JavaScript Intl
object methods before providing them to the translator.
import { createUktiTranslator } from 'ukti'
type Definition = {
list: [{ items: string; length: number; location: string }]
}
const translator = createUktiTranslator<Definition>({
translations: {
en: {
list: 'The land vehicle{{length == 1 ? "" : "s"}} used {{length == 1 ? "is" : "are"}} {{items}} in the {{location}}.'
}
}
})
const t = translator('en')
const items = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' }).format([
'motorcycle',
'bus',
'car'
])
console.log(t.list({ items, length: items.length, location: 'countryside' }))
// 'The land vehicles used are motorcycle, bus, and car in the countryside.'
console.log(t.list({ items: 'car', length: 1, location: 'city' }))
// 'The land vehicle used is car in the city.'
Ukti supports the comparison operators ==
, ===
, !=
, !==
, >
, >=
, <
, <=
in the template conditionals. All comparators are strict, so ==
and ===
are interchangeably.
If the template requires variables but they are not provided or undefined
when
calling the translation, an empty string is returned to prevent incorrect translations.
An error message is logged in the console too.
Ukti provides some type utilities to allow modularization in translations.
import {
type UktiLanguages, // Language code list of ISO 639-1
type UktiRegions, // Countries code list of ISO 3166-1 alpha-2
type UktiTranslations, // UktiTranslations<Definition, Languages?, DefaultLocale = 'en', Regions?>
type UktiTranslation, // UktiTranslation<Definition, Regions?>
type UktiTranslationData, // UktiTranslationData<Definition>
type UktiTranslationDataPartial, // UktiTranslationDataPartial<Definition>
createUktiTranslator
} from 'ukti'
type Definition = {
friend: undefined
}
const translation_EN_Core: UktiTranslationData<Definition> = {
friend: 'Friend'
}
const translation_EN_US: UktiTranslationDataPartial<Definition> = {
friend: 'Dude'
}
const translation_EN_CA: UktiTranslationDataPartial<Definition> = {
friend: 'Buddy'
}
const translation_EN: UktiTranslation<Definition, UktiRegions> = {
...translation_EN_Core,
regions: {
US: translation_EN_US,
CA: translation_EN_CA
}
}
type LanguageDefault = 'en'
const translations: UktiTranslations<Definition, UktiLanguages, LanguageDefault, UktiRegions> = {
en: translation_EN
}
const translator = createUktiTranslator<Definition, UktiLanguages, LanguageDefault, UktiRegions>({
translations
})
const language = 'en' as const satisfies UktiLanguages
const region = 'CA' as const satisfies UktiRegions
const t = translator(language, region)
console.log(t.friend()) // 'Buddy'