Skip to content

Internationalization (i18n) Support ​

The micro app template has a built-in lightweight internationalization (i18n) solution, supporting multi-language translation, placeholder replacement, config references, etc. The i18n feature is integrated into the @sun-panel/micro-app SDK. The component base class (SunPanelWidgetElement / SunPanelPageElement) will automatically initialize i18n in the onConnected lifecycle; developers do not need to manually call initI18n.

Requirements:

  • Sun-Panel main version upgraded to v2.0.0-dev-14+
  • Micro app template upgraded to v1.1.0+
  • SDK module (@sun-panel/micro-app) upgraded to v1.0.18+
  • app.config.js configured with {appJsonVersion: '1.1'}, version 1.1 or above

Directory Structure ​

your-app/
├── locales/                    # i18n language files (the only translation source)
│   ├── zh-CN.js               # Chinese language pack
│   └── en-US.js               # English language pack
├── config/
│   └── app.config.js          # App config (declares language file list)
└── ...

Quick Start ​

1. Configure App Info ​

In config/app.config.js, reference translation keys using the $t:KEY format, and declare the language file mapping in the locales object:

js
export default {
  // Config file format version
  // 1.1: appInfo uses $t: reference, translation files in locales/ directory
  appJsonVersion: '1.1',

  // App info (uses $t: variable reference for translation)
  appInfo: {
    appName: '$t:APP_NAME',
    description: '$t:APP_DESCRIPTION',
    networkDescription: '$t:NETWORK_DESCRIPTION',
  },

  // Language file mapping (format: { language code: file name })
  // File name relative to locales/ directory, multiple language codes can point to the same file
  locales: {
    'zh-CN': 'zh-CN.json',
    'en-US': 'en-US.json',
    // 'zh-TW': 'zh-CN.json',  // Example: Traditional Chinese can point to the Simplified Chinese file
  },

  // Default language (fallback when the main app's language doesn't exist in the micro app)
  // If not set, defaults to 'en-US'
  defaultLocale: 'en-US',

  // ...other config
};

TIP

Values in config/* use the $t:translation key name format for reference. At build time, the system automatically resolves these references and replaces them with the corresponding language's translation text.

2. Create Language Pack Files ​

Create the corresponding language pack files in the locales/ directory. The file name must match the value in the locales object (without the .json suffix, using the .js source file):

js
// locales/zh-CN.js
export default {
  APP_NAME: 'Hello World',
  APP_DESCRIPTION: 'Sun-Panel Demo Micro App',
  NETWORK_DESCRIPTION: 'No need to link to any third-party websites',
  WIDGET_HELLO_NAME: 'Hello World Widget',
  WIDGET_HELLO_DESCRIPTION: 'A demo widget',
  GREETING: 'Hello, {name}!',
  // ...more translations
};
js
// locales/en-US.js
export default {
  APP_NAME: 'Hello World',
  APP_DESCRIPTION: 'Sun-Panel Demo Micro App',
  NETWORK_DESCRIPTION: 'For demonstration purposes',
  WIDGET_HELLO_NAME: 'Hello World Widget',
  WIDGET_HELLO_DESCRIPTION: 'A demo widget',
  GREETING: 'Hello, {name}!',
  // ...more translations
};

3. Use in Components ​

After inheriting the component base class, i18n will be automatically initialized in the onConnected lifecycle, and developers can directly use translation via this.t():

js
// In widgetConfig.js
import { SunPanelWidgetElement } from '@sun-panel/micro-app';

class MyWidget extends SunPanelWidgetElement {
  render() {
    const config = {
      name: this.t('WIDGET_HELLO_NAME'),
      description: this.t('WIDGET_HELLO_DESCRIPTION'),
    };
    return config;
  }
}

No need to manually call initI18n

The component base class SunPanelWidgetElement / SunPanelPageElement automatically initializes i18n in the onConnected lifecycle, no need to manually import and call initI18n. Just use this.t() directly.

Using in Micro App Components ​

An instance method provided by the component base class. Translates the specified key according to the current language environment, supporting named placeholder replacement.

Parameters:

  • key (string) - Translation key name
  • args (Record<string, any>) - Optional named placeholder replacement parameter object

Return value: The translated string (returns the key itself if not found)

Example:

js
// Used inside a component
this.t('APP_NAME');                              // => 'Hello World'
this.t('GREETING', { name: 'John' });            // => 'Hello, John!'
this.t('WELCOME_USER_ROLE', { name: 'Li', role: 'Admin' });  // => 'Welcome Li, you are Admin'

t(key, args?) - Translation Function (Independently Exported) ​

Translates the specified key according to the current language environment, supporting named placeholder replacement.

Parameters:

  • key (string) - Translation key name
  • args (Record<string, any>) - Optional named placeholder replacement parameter object

Return value: The translated string (returns the key itself if not found)

Example:

js
import { t } from '@sun-panel/micro-app';

// Basic translation
t('APP_NAME');        // => 'Hello World'
t('SETTINGS');        // => 'Settings'

// Translation with named placeholders
t('GREETING', { name: 'John' });                    // => 'Hello, John!'
t('WELCOME_USER_ROLE', { name: 'Li', role: 'Admin' });  // => 'Welcome Li, you are Admin'

setLocale(locale) - Switch Language Independently ​

This approach is only suitable for manually and dynamically switching the current language environment independently from the main app's language, and re-initializing the resolver.

Parameters:

  • locale (string) - Target language identifier, such as 'en-US'

Example:

js
import { setLocale } from '@sun-panel/micro-app';

// Component code
class WidgetConfig extends SunPanelPageElement {
  // ...

  // Language switch handler
  async handleLanguageChange(locale) {
    try {
      await setLocale(locale);
      this.requestUpdate(); // Update page, important!!!
      console.log(`Language changed to: ${locale}`);
    } catch (error) {
      console.error('Failed to change language:', error);
    }
  }

  // ...
}

getLocale() - Get Current Language ​

Returns the identifier string of the current language environment.

Return value: string - Current language identifier

Placeholders ​

Placeholders use the {name} format, where name is the key in the parameter object:

js
// locales/zh-CN.js
export default {
  GREETING: 'Hello, {name}!',
  WELCOME_USER_ROLE: 'Welcome {name}, you are {role}',
};

// locales/en-US.js
export default {
  GREETING: 'Hello, {name}!',
  WELCOME_USER_ROLE: 'Welcome {name}, you are {role}',
};
js
t('GREETING', { name: 'John' });                                      // => 'Hello, John!'
t('WELCOME_USER_ROLE', { name: 'Li', role: 'Admin' });                // => 'Welcome Li, you are Admin'

If the parameter corresponding to a placeholder is missing, the placeholder is preserved as-is (such as {name}).

Language Fallback Mechanism ​

When a translation key is not found in the current language, the system falls back in the following order:

  1. Full language code - such as zh-CN
  2. Language short code - Remove the region suffix, such as zh
  3. Default language - Specified by the defaultLocale field in app.config.js, defaulting to 'en-US'
js
// Assume current language is zh-TW, but only zh-CN and en-US translations exist
// Lookup order: zh-TW → zh → en-US (assuming defaultLocale is 'en-US')
t('SETTINGS');  // If neither zh-TW nor zh has it, it will be fetched from en-US

Fallback Chain Example ​

Current Language (locale)defaultLocaleFallback ChainDescription
zh-CNen-US['zh-CN', 'zh', 'en-US']Chinese falls back to English
en-USen-US['en-US', 'en']English falls back to itself, defaultLocale not duplicated
zh-CNzh-CN['zh-CN', 'zh']defaultLocale not duplicated when same as current language

Configure Default Language ​

Set the defaultLocale field in config/app.config.js to specify the default fallback language:

js
export default {
  // ...other config
  
  // Language file mapping
  locales: {
    'zh-CN': 'zh-CN.json',
    'en-US': 'en-US.json',
  },
  
  // Default language (fallback when the main app's language doesn't exist in the micro app)
  // If not set, defaults to 'en-US'
  defaultLocale: 'en-US',
  
  // ...other config
};

How it works

The defaultLocale passing path:

  1. Declare the defaultLocale value in app.config.js
  2. main.js reads this value when initializing the SDK and passes it in: initSDK({ defaultLocale: appConfig.defaultLocale || 'en-US' })
  3. The SDK internally passes it to i18n initialization: initI18n({ locale, defaultLocale })
  4. During translation lookup, defaultLocale serves as the final fallback of the fallback chain, after all other fallbacks

Additionally, when the SDK asynchronously loads translation files, it loads both the current language and defaultLocale's translation files at the same time, ensuring fallback translation data is available.

Notes:

  • The value of defaultLocale must be one of the language codes already defined in the locales object
  • It is recommended to set it to the app's main supported language (e.g., a Chinese app can set it to 'zh-CN')
  • If not set, the default is 'en-US'

If the key doesn't exist in all fallback languages, the key itself is returned (such as MISSING_KEY).

Build Process ​

Development Mode ​

In development mode, the locales/*.js source files are loaded directly via Vite dynamic import:

locales/zh-CN.js → import() dynamic loading → i18n instance

Production Build ​

At build time, locales-generator.js compiles locales/*.js into JSON format, output to the dist/locales/ directory:

locales/zh-CN.js → locales-generator.js → dist/locales/zh-CN.json
locales/en-US.js → locales-generator.js → dist/locales/en-US.json

In production mode, i18n loads translation files via fetch from {staticPath}/locales/*.json.

Best Practices ​

1. Key Naming Convention ​

Translation keys are not strictly required to use a certain naming method; follow personal preference. Nested translation objects are not yet supported, so try not to use . in key names. The example below uses UPPER_SNAKE_CASE:

js
export default {
  APP_NAME: 'App Name',
  WIDGET_HELLO_NAME: 'Widget Name',
  SETTINGS_TITLE: 'Settings Title',
  WIDGET_CONFIG_BUTTON_SAVE: 'Save Widget',
};

2. File Naming ​

The language pack file name should correspond to the value of the locales object in app.config.js:

LanguageSource Filelocales Declaration
Simplified Chineselocales/zh-CN.js'zh-CN': 'zh-CN.json'
Englishlocales/en-US.js'en-US': 'en-US.json'
Traditional Chineselocales/zh-TW.js'zh-TW': 'zh-TW.json'

3. Keep Translations Synchronized ​

All language pack files should contain the same keys. When adding a translation, remember to update all language pack files synchronously.

4. Translate Only Text ​

Only translate user-visible text content. Do not internationalize the following:

  • Config key names (such as showLogo, textOption)
  • CSS class names
  • API paths
  • Data node identifiers

Notes ​

  1. Translation source location: All translations should be placed uniformly in the locales/ directory. Do not create language files in the src/ directory.
  2. Do not directly modify build artifacts: dist/locales/ is build output and modifications will be overwritten.
  3. Language identifier format: Follow the IETF BCP 47 standard (such as zh-CN, en-US).

Released under the MIT License.