Skip to content

Internationalization (i18n) (Plugins)

The Tuff provides a comprehensive internationalization (i18n) system that allows plugins to support multiple languages. This guide explains how to implement localization in your plugins.

Language Files

Plugins can provide their own language files to support different locales. Language files are JSON files that map language keys to translated strings.

File Structure

Language files should be placed in a locales directory within your plugin:

my-plugin/
  locales/
    en.json
    es.json
    fr.json
    zh.json
  plugin.js
  manifest.json

Language File Format

Each language file contains key-value pairs where the key is a unique identifier for a string and the value is the translated string:

json
{
  "plugin-name": "My Plugin",
  "welcome-message": "Welcome to My Plugin",
  "settings-title": "Plugin Settings",
  "save-button": "Save",
  "cancel-button": "Cancel",
  "error-message": "An error occurred: {errorMessage}"
}

Placeholders

You can include placeholders in your translation strings that will be replaced with dynamic values:

json
{
  "file-saved": "File '{filename}' has been saved successfully",
  "items-selected": "{count} items selected"
}

Using Translations in Plugins

To use translations in your plugin, import the i18n object from the plugin SDK:

javascript
// In your plugin's main file or preload script
import { i18n } from '@polyglot-toolbox/plugin-sdk';

// Get a translated string
const welcomeMessage = i18n.t('welcome-message');
console.log(welcomeMessage); // "Welcome to My Plugin" (in the current language)

// Get a translated string with placeholders
const fileSavedMessage = i18n.t('file-saved', { filename: 'document.txt' });
console.log(fileSavedMessage); // "File 'document.txt' has been saved successfully"

// Get a translated string with pluralization
const itemsSelected = i18n.t('items-selected', { count: 5 });
console.log(itemsSelected); // "5 items selected"

Setting the Language

The application language can be set by the user in the application settings. Plugins automatically use the current application language.

Detecting Language Changes

You can listen for language changes to update your plugin's UI:

javascript
import { i18n } from '@polyglot-toolbox/plugin-sdk';

// Listen for language changes
i18n.onLanguageChange((newLanguage) => {
  console.log('Language changed to:', newLanguage);
  // Update your plugin's UI with the new language
  this.updateUI();
});

// Remove the listener when your plugin is unloaded
// i18n.offLanguageChange(listenerFunction);

Pluralization

The i18n system supports pluralization for languages with complex plural rules:

Simple Pluralization

For simple cases, you can use placeholders:

json
{
  "file-count": "{count} file",
  "file-count_plural": "{count} files"
}
javascript
// In a language with simple pluralization (like English)
const oneFile = i18n.t('file-count', { count: 1 }); // "1 file"
const manyFiles = i18n.t('file-count', { count: 5 }); // "5 files"

Complex Pluralization

For languages with more complex plural rules (like Russian or Arabic), you can define multiple plural forms:

json
{
  "file-count_0": "{count} файлов", // 0 files
  "file-count_1": "{count} файл",   // 1 file
  "file-count_2": "{count} файла",  // 2-4 files
  "file-count_3": "{count} файлов"  // 5+ files
}

The system will automatically select the correct plural form based on the language's rules.

Date and Number Formatting

The i18n system also provides utilities for formatting dates and numbers according to the current locale:

javascript
import { i18n } from '@polyglot-toolbox/plugin-sdk';

// Format a date
const formattedDate = i18n.formatDate(new Date(), 'short');
console.log(formattedDate); // "10/15/2023" (for en-US)

// Format a number
const formattedNumber = i18n.formatNumber(1234.56);
console.log(formattedNumber); // "1,234.56" (for en-US)

// Format a currency
const formattedCurrency = i18n.formatCurrency(1234.56, 'USD');
console.log(formattedCurrency); // "$1,234.56" (for en-US)

Best Practices for Internationalization

When implementing i18n in your plugin, consider the following best practices:

1. Use Descriptive Keys

Choose keys that clearly describe the string's purpose:

json
// Good
{
  "settings-save-button": "Save Settings",
  "settings-cancel-button": "Cancel"
}

// Avoid
{
  "button1": "Save Settings",
  "button2": "Cancel"
}

2. Provide Context

Include comments in your language files to provide context for translators:

json
{
  // Button text for saving user preferences
  "save-preferences": "Save Preferences",
  
  // Error message shown when a file cannot be saved
  "save-file-error": "Unable to save file: {errorMessage}"
}

3. Handle Pluralization Properly

Always consider how your strings will be pluralized in different languages:

json
// Good - Using separate keys for singular and plural
{
  "file-count": "{count} file",
  "file-count_plural": "{count} files"
}

// Avoid - Trying to handle pluralization in code
{
  "file-count": "{count} file{count > 1 ? 's' : ''}" // This doesn't work for all languages
}

4. Avoid Concatenating Translated Strings

Instead of concatenating translated strings, use placeholders:

javascript
// Good
const message = i18n.t('file-saved-at-time', {
  filename: 'document.txt',
  time: '14:30'
});

// Avoid
const message = i18n.t('file-saved') + ' at ' + '14:30'; // This breaks in RTL languages

5. Test with Different Languages

Test your plugin with different languages, especially those with different text directions (like Arabic or Hebrew) and different pluralization rules.

Example: Multilingual Plugin

Here's a complete example of a plugin that supports multiple languages:

javascript
// plugin.js
import { i18n, toast } from '@polyglot-toolbox/plugin-sdk';

class MultilingualPlugin {
  constructor() {
    this.init();
  }
  
  init() {
    // Set up UI with translated strings
    this.updateUI();
    
    // Listen for language changes
    i18n.onLanguageChange(() => {
      this.updateUI();
    });
    
    // Set up event listeners
    this.setupEventListeners();
  }
  
  updateUI() {
    // Update UI elements with translated strings
    const titleElement = document.getElementById('plugin-title');
    if (titleElement) {
      titleElement.textContent = i18n.t('plugin-title');
    }
    
    const descriptionElement = document.getElementById('plugin-description');
    if (descriptionElement) {
      descriptionElement.textContent = i18n.t('plugin-description');
    }
    
    const saveButton = document.getElementById('save-button');
    if (saveButton) {
      saveButton.textContent = i18n.t('save-button');
    }
    
    const cancelButton = document.getElementById('cancel-button');
    if (cancelButton) {
      cancelButton.textContent = i18n.t('cancel-button');
    }
  }
  
  setupEventListeners() {
    const saveButton = document.getElementById('save-button');
    if (saveButton) {
      saveButton.addEventListener('click', () => {
        this.saveSettings();
      });
    }
    
    const cancelButton = document.getElementById('cancel-button');
    if (cancelButton) {
      cancelButton.addEventListener('click', () => {
        this.cancelSettings();
      });
    }
  }
  
  saveSettings() {
    try {
      // Save settings logic here
      console.log('Settings saved');
      
      // Show success message in the user's language
      toast.success(i18n.t('settings-saved-success'));
    } catch (error) {
      console.error('Failed to save settings:', error);
      
      // Show error message in the user's language
      toast.error(i18n.t('settings-saved-error', {
        errorMessage: error.message
      }));
    }
  }
  
  cancelSettings() {
    console.log('Settings canceled');
    
    // Show info message in the user's language
    toast.info(i18n.t('settings-canceled'));
  }
}

// Initialize the plugin
new MultilingualPlugin();

Corresponding language files:

locales/en.json:

json
{
  "plugin-title": "Multilingual Plugin",
  "plugin-description": "A plugin that supports multiple languages",
  "save-button": "Save",
  "cancel-button": "Cancel",
  "settings-saved-success": "Settings saved successfully",
  "settings-saved-error": "Failed to save settings: {errorMessage}",
  "settings-canceled": "Settings changes have been canceled"
}

locales/es.json:

json
{
  "plugin-title": "Plugin Multilingüe",
  "plugin-description": "Un plugin que soporta múltiples idiomas",
  "save-button": "Guardar",
  "cancel-button": "Cancelar",
  "settings-saved-success": "Configuración guardada con éxito",
  "settings-saved-error": "Error al guardar la configuración: {errorMessage}",
  "settings-canceled": "Los cambios de configuración han sido cancelados"
}

locales/fr.json:

json
{
  "plugin-title": "Plugin Multilingue",
  "plugin-description": "Un plugin qui prend en charge plusieurs langues",
  "save-button": "Enregistrer",
  "cancel-button": "Annuler",
  "settings-saved-success": "Paramètres enregistrés avec succès",
  "settings-saved-error": "Échec de l'enregistrement des paramètres : {errorMessage}",
  "settings-canceled": "Les modifications de paramètres ont été annulées"
}

This plugin demonstrates proper use of the i18n system by:

  1. Providing language files for multiple locales
  2. Using descriptive keys for translations
  3. Handling placeholders in translated strings
  4. Listening for language changes to update the UI
  5. Using translated strings for user-facing messages, including toast notifications

For more information about the plugin SDK and available APIs, see the Plugin SDK documentation.