Dialpad's internationalization library
npm install @dialpad/i18nThis package contains all the logic needed to take care the i18n of a Dialpad
application extending from the common logic existing in the base locale manager
that you can find about on the
i18n-services documentation, based on different
bundle sources.
There are essentially two main concepts behind the scenes:
The LocaleManager extends the BaseLocaleManager to oversee the
internationalization (i18n) in a Vue3.js application using the
Fluent localization framework. Its
responsibilities include:
- Loading and caching translation bundles: Manages the .ftl data, which
maps keys to text for each locale.
- Switching the current locale: Provides methods to change the active
language dynamically.
- Handling bundle namespaces: Manages potential duplicate bundle requests
from multiple sources, though this feature is still being refined.
- Initializing the Vue plugin: Exposes translation methods ($t, $ta) to
Vue templates, enabling localized content within the application.
The second key concept is the BundleSource interface, which abstracts the
loading and management of localization resources. This interface has two primary
implementations: RawBundleSource and HTTPBundleSource, each designed for
different use cases.
- RawBundleSource: This implementation handles localization resources defined
within the application. It organizes these resources defined directly in the
application, and imported directly at runtime (for example, using
import(./[locale].ftl). This approach is ideal for applications with mostly
static content.
- HTTPBundleSource: This implementation fetches localization resources
dynamically from a server. It caches these resources locally, allowing for
efficient access while still being flexible enough to load translations on
demand.
Disclaimer: _Both implementations are provisional and may change as we
better understand our needs. Currently, RawBundleSource is the preferred
option for most applications._
#### Synchronous Resource Loading with builtResources
While the
dynamicResources
method is used for asynchronously loading resources, the
RawBundleSource
class also provides a
builtResources
method for synchronous resource loading. This is useful when you have the
localization resources available at compile time and prefer to import them
directly without dealing with promises.
##### How to use builtResources
Instead of dynamically importing .ftl files, you can import them directly and
use the
builtResources
method to process the resources. Here's an example:
``typescript
import { RawBundleSource } from '@dialpad/i18n-services';
// Import your .ftl files directly
import enUSResource from './locales/en-US.ftl';
import esResource from './locales/es.ftl';
// Create an array of BuiltResource objects
const builtResourcesArray: BuiltResource[] = [
['en-US', 'namespace', enUSResource],
['es', 'namespace', esResource],
// ... add other locales and namespaces as needed
];
// Convert BuiltResource objects to Resource objects using builtResources
const resources: Resource[] =
RawBundleSource.builtResources(builtResourcesArray);
// Now, resources can be used to instantiate RawBundleSource or for any other purpose
const bundleSource = new RawBundleSource({ resources });
`
`sh`
pnpm add @dialpad/i18n
When using LocaleManager within a Vue application, **(especially in theLocaleManager
context of a library, you should be aware that install needs to receive a
non-default namespace value)** it's important to properly integrate it using the
install
method. This method registers with the Vue instance, allowing
for global access to localization functionalities across the application.
#### Usage of install
After creating an instance of LocaleManager, you must call thenamespace
install
method. This method accepts an optional parameter, which isLocaleManager
particularly useful when you are developing a library and need to avoid
conflicts with other potential instances of in the consumer's
environment.
By using different namespaces, you can have multiple instances of
LocaleManager available globally in Vue. This is ideal for libraries that may
be used in conjunction with other Vue plugins or in larger applications where
scoped localization management is required.
##### Parameters
- namespace: (Optional) The namespace under which the LocaleManager will be'default'
registered. Defaults to if no namespace is provided.
##### Example
`typescript
import Vue from 'vue';
import { LocaleManager } from '@dialpad/i18n-vue2';
// Create a LocaleManager instance with your configuration options
const localeManager = new LocaleManager({
// ... configuration options, see on Dynamic resources configuration section
});
// Install the LocaleManager with a specific namespace to avoid conflicts
localeManager.install('my-namespace');
// Your library's users can now access the LocaleManager instance globally in Vue
new Vue({
// ... other options
}).$mount('#app');
`
We recommend you to include this in your own composable that handle every locale
and i18n set up logic, i.e.:
`ts
//src/localization/i18n.ts
import type { App } from 'vue';
import { LocaleManager, RawBundleSource } from '@dialpad/i18n';
export async function hostI18NManager({
vueApp,
}: {
vueApp: App;
}): Promise
const bundleSource = new RawBundleSource({
resources: await RawBundleSource.dynamicResources([
['en-US', 'your-app', import('./en-US.ftl?raw')],
]),
});
const manager = new LocaleManager({
bundleSource,
preferredLocale: 'en-US', // optional
allowedLocales: ['en-US'], // optional
fallbackLocale: 'en-US',
namespaces: ['your-app'],
});
await manager.ready;
vueApp.use(manager);
}
`
Important note: let's say that you have more than one allowedLocales, orfallbackLocale
that your /preferredLocale is different from what you'reallowedLocales
specifying on the prop to the LocaleManager instance. In
those cases you need to verify that you have specified a Bundle resource for
each locale, in order to have them working properly, i.e.:
`ts
//src/localization/i18n.ts
import type { App } from 'vue';
import { LocaleManager, RawBundleSource } from '@dialpad/i18n';
export async function hostI18NManager({
vueApp,
}: {
vueApp: App;
}): Promise
const bundleSource = new RawBundleSource({
resources: await RawBundleSource.dynamicResources([
// You must always have one resource per locale.
['dp-DP', 'your-app', import('./dp-DP.ftl?raw')],
// This one is for the fallbackLocale
['en-US', 'your-app', import('./en-US.ftl?raw')],
]),
});
const manager = new LocaleManager({
bundleSource,
// I'm using Dialpadistan as my locale
preferredLocale: 'dp-DP',
// But my fallback is en-US (if it was part of the locales this is the same concept, you need to add one resource per locale)
fallbackLocale: 'en-US',
namespaces: ['your-app'],
});
await manager.ready;
vueApp.use(manager);
}
`
After this, you should plug this into your main.ts. You will call it like:
`ts
// src/main.ts
import { createHost } from '@dialpad/host-entry';
import { hostI18NManager } from './localization/i18n';
import { hostEnv } from './constants';
await createHost(hostEnv)
// ... all your config
.vueCreated(hostI18NManager)
// ... some other config
.start();
`
You will find that this API is (and should always be) the same as the
i18n-vue2 tool.
When instantiating the LocaleManager (or when calling setI18N), the
preferredLocale and allowedLocales parameters are optional. BUT this is the
following criteria to select it.
If preferredLocale param is provided, use that. If allowedLocales param is
provided, use the first one. If localStorage contains the previously-used value,
use that. If navigator.language is available, use that. Use the fallbackLocale.
First of all you need to understand he difference between $t and $ta, which
mainly lies in their outputs:
There are three main functions exported: $t, $ta, and setI18N(), and twocurrentLocale
exported values: and allowedLocales.
(Keep in mind that you should have a fluent file (en-US.ftl in this case)
following the Fluent rules.)
#### $t
Returns a translated string based on a key and optional variables. It’s used for
simple text translations.
`ftl`src/localization/en-US.ftl
PARAGRAPH_KEY = Amazing paragraph, { $name }
`ts`
// Component file
const greeting = $t('PARAGRAPH_KEY', { name: 'John' });
// Output: "Amazing paragraph, John"
#### $ta
Returns an object containing translated attributes to pass directly as props to
components, it can contain aria-label, title, etc, rather than just a plain
text. It’s useful for handling element attributes in the UI.
`ftl`src/localization/en-US.ftl
BUTTON_NAME =
.aria-label = { $title } now
.title = { $title }
`ts
// Component file
const buttonAttrs = $ta('BUTTON_NAME', { title: 'Submit' });
// Output: { ariaLabel: "Submit now", title: "Submit" }
// Usage:
;
`
In both cases, any valid fluent syntax is allowed, so the usage of
it might include functions
or any other feature provided by the framework.
##### Vue component example - $t, $ta
Here's an example of how to use the LocaleManager in a Vue component.
`vue
{{ $t('PARAGRAPH_KEY') }}
v-bind="
$ta('BUTTON_NAME', {
name,
})
"
/>
`
using the following Fluent file as example:
`ftl`src/localization/en-US.ftl
PARAGRAPH_KEY = Amazing paragraph, { $name }
BUTTON_NAME =
.aria-label = { $title } now
.title = { $title }
#### setI18N
This function receives a Partial. This allows you to changeLocaleManager
any of the set up parameters after the initialization of the ,
but the main use case is to change locales dynamically.
You probably want to use this in addition to:
- currentLocale, determined by theallowedLocales
criteria defined in the usage section.
- , are all the allowed locale codes, sorted alphabetically,LocaleManager
defined during initialization of the .
##### Vue component example - setI18N, allowedLocales, currentLocales
`vue
{{ currentLocale }}
@click="
setI18N({ locales: locale });
close();
"
>
{{ locale }}
`
The
changeLocale
method provides the functionality to dynamically change the current locale of
your application. This method can target a specific
LocaleManager
by namespace or all
LocaleManager
instances globally.
#### changeLocale()
To change the locale settings, call the
changeLocale
method on your
LocaleManager
instance. You can pass optional parameters to update the locale settings and
optionally specify a namespace to target a specific
LocaleManager.
If no namespace is provided, the locale settings will be updated for all
LocaleManager
instances.
##### Parameters
- args: (Optional) A Partial object containing the newnamespace
locale settings to be applied.
- : (Optional) The namespace of the
LocaleManager
instance whose locale settings you want to change. If not provided, all
instances will be updated.
###### Behavior
- Retrieves the map of
LocaleManager
instances from the Vue prototype.
- If a namespace is specified, it targets the corresponding
LocaleManager
and throws an error if it does not exist.
- If no namespace is specified, it targets all registered
LocaleManager
instances.
- Updates the locale settings for the targeted
LocaleManager(s)
using the provided arguments.
###### Example
`typescript
// Assuming you have a LocaleManager instance
const manager = new LocaleManager(...);
// To change the locale settings for a specific namespace
manager.changeLocale({ preferredLocale: 'fr-FR' }, 'myNamespace');
// To change the locale settings for all LocaleManager instances
manager.changeLocale({ preferredLocale: 'fr-FR' });
#### API Method: addSources
The addSources method allows you to add multiple translation resources to the LocaleManager at runtime. Once all resources are loaded, it triggers the ready promise indicating that the locale manager is fully initialized and ready to handle localization requests.`
#### API Method: addSources
The addSources method allows you to add multiple translation resources to theLocaleManager at runtime. Once all resources are loaded, it triggers theready promise indicating that the locale manager is fully initialized and
ready to handle localization requests.
##### Parameters
- sources: An array of BuiltResource items where each item contains:locale
- : The locale code for the translation resource.namespace
- : The namespace associated with the resource.source
- : The actual source string or a promise that resolves to the source
string.
##### Example
`typescript
// Assuming you have a LocaleManager instance
const manager = new LocaleManager(...);
// Define your resources
const resourcesToAdd: BuiltResource[] = [
// Array of [locale, namespace, source] tuples
['en-US', 'namespace1', sourceStringOrPromise],
['es-ES', 'namespace1', anotherSourceStringOrPromise],
// Add more resources as needed
];
// Add the resources to the LocaleManager
manager.addSources(resourcesToAdd);
`
Context screenshots are, as their name suggest, screenshots that provide context
to recognize where each translation is used in each app.
Each screenshot should contain visual information of where the translation can
be found in the app with enough context to be easily located. If other dev looks
at the screenshot they should be able to locate it fairly easily in your app.
There should be one screenshot per each translation key on each .ftl file. You{translationKeyHere}.png
can reuse the same screenshot for multiple keys, but each file will correspond
to one key, so you must make one copy for each translation key. The screenshot
filename will be . We only support .png images for
now.
There is a PR check which will fail if any translation key is missing its
corresponding screenshot. There is an exception list for paths that should be
ignored by this check. Avoid using it unless it's completely necessary. If you
strictly need to use it, remember to mention in your commit message why you are
using it.
These screenshots will be located in a directory called context-screenshots.ftl
sibling to each file in your project. If you have an .ftl` file, it
should have a sibling screenshots directory. Each screenshot has a file size
limit of 20MB.
The screenshots are uploaded to smartling along with the CSV/FTL files through a
github action when a PR to main is merged.
- Fluent docs
- I18n best practices
- 18n-services docs
- i18n for vue2 docs