
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
@dialpad/i18n
Advanced tools
This 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:
.ftl data, which
maps keys to text for each locale.$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.
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.
Instead of dynamically importing .ftl files, you can import them directly and
use the
builtResources
method to process the resources. Here's an example:
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 });
pnpm add @dialpad/i18n
When using LocaleManager within a Vue application, (especially in the
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 LocaleManager with the Vue instance, allowing
for global access to localization functionalities across the application.
After creating an instance of LocaleManager, you must call the
install
method. This method accepts an optional namespace parameter, which is
particularly useful when you are developing a library and need to avoid
conflicts with other potential instances of LocaleManager 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.
namespace: (Optional) The namespace under which the LocaleManager will be
registered. Defaults to 'default' if no namespace is provided.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.:
//src/localization/i18n.ts
import type { App } from 'vue';
import { LocaleManager, RawBundleSource } from '@dialpad/i18n';
export async function hostI18NManager({
  vueApp,
}: {
  vueApp: App;
}): Promise<void> {
  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, or
that your fallbackLocale/preferredLocale is different from what you're
specifying on the allowedLocales 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.:
//src/localization/i18n.ts
import type { App } from 'vue';
import { LocaleManager, RawBundleSource } from '@dialpad/i18n';
export async function hostI18NManager({
  vueApp,
}: {
  vueApp: App;
}): Promise<void> {
  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:
// 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 two
exported values: currentLocale and allowedLocales.
(Keep in mind that you should have a fluent file (en-US.ftl in this case) following the Fluent rules.)
$tReturns a translated string based on a key and optional variables. It’s used for simple text translations.
# src/localization/en-US.ftl
PARAGRAPH_KEY = Amazing paragraph, { $name }
// Component file
const greeting = $t('PARAGRAPH_KEY', { name: 'John' });
// Output: "Amazing paragraph, John"
$taReturns 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.
# src/localization/en-US.ftl
BUTTON_NAME =
  .aria-label = { $title } now
  .title = { $title }
// Component file
const buttonAttrs = $ta('BUTTON_NAME', { title: 'Submit' });
// Output: { ariaLabel: "Submit now", title: "Submit" }
// Usage:
<template>
  <DtButton v-bind="$ta('BUTTON_NAME', { title: 'Submit' });" />
</template>;
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. (You can find real world examples on this file)
Here's an example of how to use the LocaleManager in a Vue component.
<script>
/* `useI18N` exposes the `$t` and `$ta` localization methods, **for use in the setup method**, and these are both available by default in templates with no extra calls needed. */
import { useI18N } from '@dialpad/i18n';
const { $t, $ta } = useI18N();
</script>
<template>
  <div>
    <p>{{ $t('PARAGRAPH_KEY') }}</p>
    <button
      v-bind="
        $ta('BUTTON_NAME', {
          name,
        })
      "
    />
  </div>
</template>
using the following Fluent file as example:
# src/localization/en-US.ftl
PARAGRAPH_KEY = Amazing paragraph, { $name }
BUTTON_NAME =
  .aria-label = { $title } now
  .title = { $title }
setI18NThis function receives a Partial<SetLocaleParams>. This allows you to change
any of the set up parameters after the initialization of the LocaleManager,
but the main use case is to change locales dynamically.
You probably want to use this in addition to:
currentLocale, determined by the
criteria defined in the usage section.allowedLocales, are all the allowed locale codes, sorted alphabetically,
defined during initialization of the LocaleManager.<script setup lang="ts">
// your imports ...
const { setI18N, allowedLocales, currentLocale } = useI18N();
</script>
<template>
  <DtDropdown placement="bottom-end">
    <template #anchor="{ attrs }">
      <DtButton v-bind="attrs" size="md" importance="clear">
        {{ currentLocale }}
      </DtButton>
    </template>
    <template #list="{ close }">
      <template v-for="locale in allowedLocales" :key="locale">
        <DtListItem
          navigation-type="tab"
          @click="
            setI18N({ locales: locale });
            close();
          "
        >
          {{ locale }}
        </DtListItem>
      </template>
    </template>
  </DtDropdown>
</template>
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.
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.
args: (Optional) A Partial<SetLocaleParams> object containing the new
locale settings to be applied.namespace: (Optional) The namespace of the
LocaleManager
instance whose locale settings you want to change. If not provided, all
instances will be updated.// 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' });
### Adding Resources at Runtime with `addSources`
#### 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.
addSourcesaddSourcesThe 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.
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.// 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
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 {translationKeyHere}.png. 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
sibling to each .ftl 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.
FAQs
Dialpad's internationalization library
We found that @dialpad/i18n demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 3 open source maintainers 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.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.