Skip to content

Latest commit

 

History

History
653 lines (494 loc) · 18.1 KB

File metadata and controls

653 lines (494 loc) · 18.1 KB
name objectstack-i18n
description Author ObjectStack translation bundles — object/field labels, view text, app navigation strings, automation messages — and configure locale fallback, coverage reporting, and the per-locale source layout. Use when the user is adding `*.translation.ts` files, wiring a new locale, or resolving missing-translation warnings. Do not use for general i18n library questions unrelated to ObjectStack bundles.
license Apache-2.0
compatibility Requires @objectstack/spec Zod schemas (v4+)
metadata
author version domain tags
objectstack-ai
1.1
i18n
i18n, translation, locale, l10n, bundle, coverage

Internationalization — ObjectStack I18n Protocol

Expert instructions for designing internationalization (i18n) and localization (l10n) strategies using the ObjectStack specification. This skill covers translation bundle structures, locale configuration, object-first translation patterns, coverage detection, and integration with the I18nService.


When to Use This Skill

  • You are configuring i18n for a new ObjectStack project.
  • You need to create translation bundles for multiple locales.
  • You are designing object-first translation structures (per-object translation files).
  • You need to detect missing or stale translations (coverage analysis).
  • You are integrating AI-powered translation suggestions.
  • You are implementing locale-specific formatting (dates, numbers, currency).

    Workspace regional defaults — reference timezone, locale, and currency — live in the localization SETTINGS (tenant-scoped), are resolved onto every request's ExecutionContext, and are exposed to the client at GET /api/v1/auth/me/localization. localization.currency is the fallback a currency field/measure uses when it omits its own (ADR-0053).

  • You need to understand translation file organization strategies (bundled, per_locale, per_namespace).

Core Concepts

Translation Architecture Overview

ObjectStack follows an object-first translation model inspired by Salesforce and Dynamics 365:

  1. Object-First Aggregation: All translatable content for an object (labels, fields, options, views, actions) is grouped under a single namespace: o.{object_name}.

  2. Global Groups: Non-object-bound translations (apps, navigation, messages) live at the top level.

  3. Locale Files: Each locale has its own complete translation bundle (e.g., en.json, zh-CN.json).

  4. Coverage Detection: The system can compare translation bundles against source metadata to identify missing, redundant, or stale entries.


Translation Configuration

Stack-Level I18n Config

Configure i18n settings in your objectstack.config.ts:

import { defineStack } from '@objectstack/spec';

export default defineStack({
  i18n: {
    defaultLocale: 'en',
    supportedLocales: ['en', 'zh-CN', 'ja-JP', 'es-ES'],
    fallbackLocale: 'en',
    fileOrganization: 'per_locale',
    messageFormat: 'simple',      // or 'icu' for complex plurals
    lazyLoad: false,
    cache: true,
  },
});
Property Type Default Description
defaultLocale string 'en' Default BCP-47 locale code
supportedLocales string[] ['en'] All supported locales
fallbackLocale string same as defaultLocale Fallback when translation missing
fileOrganization 'bundled' | 'per_locale' | 'per_namespace' 'per_locale' How translation files are organized
messageFormat 'simple' | 'icu' 'simple' Interpolation format (ICU for plurals/gender)
lazyLoad boolean false Load translations on demand
cache boolean true Cache loaded translations in memory

BCP-47 Locale Codes: Use standard locale tags (e.g., en-US, zh-CN, pt-BR, en-GB).


File Organization Strategies

1. Bundled (Single File)

All locales in one file. Best for small projects with few objects.

src/translations/
  crm.translation.ts        # { en: {...}, "zh-CN": {...} }

When to use: Fewer than 5 objects, 2-3 locales, < 200 translation keys total.

2. Per-Locale (Recommended)

One file per locale containing all namespaces. Recommended when a single locale file stays under ~500 lines.

src/translations/
  en.ts                     # TranslationData for English
  zh-CN.ts                  # TranslationData for Chinese
  ja-JP.ts                  # TranslationData for Japanese

When to use: Medium projects (5-20 objects), 3-5 locales, organized by language.

3. Per-Namespace (Enterprise)

One file per namespace (object) per locale. Recommended for large projects with many objects/languages. Aligns with Salesforce DX and ServiceNow conventions.

i18n/
  en/
    account.json            # ObjectTranslationData
    contact.json
    project_task.json
    common.json             # messages + app labels
  zh-CN/
    account.json
    contact.json
    project_task.json
    common.json

When to use: Large projects (20+ objects), 5+ locales, team collaboration, CI/CD pipelines.


Object-First Translation Bundle

AppTranslationBundle Structure

The AppTranslationBundle is the canonical format for a single locale:

const zh: AppTranslationBundle = {
  _meta: {
    locale: 'zh-CN',
    direction: 'ltr',
  },

  // Object-first translations
  o: {
    account: {
      label: '客户',
      pluralLabel: '客户',
      description: '客户管理对象',
      fields: {
        name: { label: '客户名称', help: '公司或组织的法定名称' },
        industry: {
          label: '行业',
          options: { tech: '科技', finance: '金融', retail: '零售' }
        },
        website: { label: '网站', placeholder: '输入网站地址' },
      },
      _options: {
        status: { active: '活跃', inactive: '停用' },
      },
      _views: {
        all_accounts: { label: '全部客户' },
        my_accounts: { label: '我的客户' },
      },
      _sections: {
        basic_info: { label: '基本信息' },
        contact_info: { label: '联系方式' },
      },
      _actions: {
        convert_lead: { label: '转换线索', confirmMessage: '确认转换为客户?' },
        merge: { label: '合并客户', confirmMessage: '此操作无法撤销,确认合并?' },
      },
    },
  },

  // Global picklist options (not object-specific)
  _globalOptions: {
    currency: { usd: '美元', eur: '欧元', cny: '人民币' },
  },

  // App-level translations
  app: {
    crm: { label: '客户关系管理', description: '管理销售流程' },
    helpdesk: { label: '服务台', description: '客户支持系统' },
  },

  // Navigation menu
  nav: {
    home: '首页',
    settings: '设置',
    reports: '报表',
    admin: '管理',
  },

  // Dashboard translations
  dashboard: {
    sales_overview: { label: '销售概览', description: '销售漏斗与目标' },
  },

  // Report translations
  reports: {
    pipeline_report: { label: '管道报表' },
  },

  // Page translations
  pages: {
    landing: { title: '欢迎', description: '开始使用 ObjectStack' },
  },

  // UI messages (supports ICU MessageFormat if enabled)
  messages: {
    'common.save': '保存',
    'common.cancel': '取消',
    'common.delete': '删除',
    'common.confirm': '确认',
    'validation.required': '此字段为必填项',
    'pagination.showing': '显示 {start} 到 {end},共 {total} 条',
  },

  // Validation error messages
  validationMessages: {
    discount_limit: '折扣不能超过40%',
    end_date_after_start: '结束日期必须晚于开始日期',
  },

  // Global notifications
  notifications: {
    record_created: { title: '创建成功', body: '记录已创建' },
  },

  // Global error messages
  errors: {
    'ERR_NETWORK': '网络连接失败',
    'ERR_PERMISSION': '权限不足',
  },
};

Object-Level Translation Structure

All translatable content for a single object is aggregated under o.{object_name} with these sub-keys:

Sub-key Holds
label / pluralLabel / description / helpText Object-level text
fields.{field_name} label, help, placeholder, options per field
_options.{picklist_name} Object-scoped picklist option labels
_views.{view_name} View label / description
_sections.{section_name} Form section / tab labels
_actions.{action_name} Action label + confirmMessage
_notifications.{key} / _errors.{code} Per-object messages

For the exact Zod shape (and any field that may have been added since), read node_modules/@objectstack/spec/src/system/translation.zod.tsObjectTranslationNodeSchema and FieldTranslationSchema.


Naming Conventions

Context Convention Example
Locale codes BCP-47 en, en-US, zh-CN, pt-BR
Object keys in o.* snake_case o.project_task, o.support_case
Field keys snake_case fields.first_name, fields.due_date
Option values lowercase options.status.in_progress
Message keys dot-separated common.save, validation.required

Critical: Object names and field keys in translation bundles must match the snake_case names defined in your Object and Field schemas.


Translation Coverage & Diff Detection

Coverage Analysis

The II18nService.getCoverage() method compares a translation bundle against source metadata to detect:

  1. Missing — Keys that exist in metadata but not in the translation bundle
  2. Redundant — Keys in the bundle that have no matching metadata
  3. Stale — Keys where the source metadata has changed since translation
const coverage = i18nService.getCoverage('zh-CN', 'account');

console.log(coverage);
// {
//   locale: 'zh-CN',
//   objectName: 'account',
//   totalKeys: 120,
//   translatedKeys: 105,
//   missingKeys: 12,
//   redundantKeys: 3,
//   staleKeys: 0,
//   coveragePercent: 87.5,
//   items: [
//     { key: 'o.account.fields.website.label', status: 'missing', locale: 'zh-CN' },
//     ...
//   ],
//   breakdown: [
//     { group: 'fields', totalKeys: 45, translatedKeys: 40, coveragePercent: 88.9 },
//     { group: 'views', totalKeys: 8, translatedKeys: 8, coveragePercent: 100 },
//   ],
// }

TranslationDiffItem

Each item in coverage.items carries key (dot path), status (missing | redundant | stale), objectName, locale, optional sourceHash for stale detection, and AI-enrichment fields (aiSuggested, aiConfidence) when suggestTranslations() has been run. Full Zod shape: node_modules/@objectstack/spec/src/system/translation.zod.tsTranslationDiffItemSchema.


AI-Powered Translation Suggestions

Using suggestTranslations()

The II18nService.suggestTranslations() method enriches diff items with AI-generated translations:

const missingItems = coverage.items.filter(item => item.status === 'missing');

const suggestions = await i18nService.suggestTranslations('zh-CN', missingItems);

suggestions.forEach(item => {
  console.log(`${item.key}: ${item.aiSuggested} (confidence: ${item.aiConfidence})`);
  // o.account.fields.website.label: 网站 (confidence: 0.95)
});

Best Practice: AI suggestions work best when:

  • You provide source locale context (e.g., English labels)
  • You include domain-specific glossaries
  • You review and approve suggestions before committing

Message Interpolation

Simple Format (Default)

Use {variable} placeholders:

{
  "messages": {
    "welcome": "Welcome, {userName}!",
    "pagination": "Showing {start} to {end} of {total} items"
  }
}

Usage:

i18n.t('messages.welcome', 'en', { userName: 'Alice' });
// "Welcome, Alice!"

ICU MessageFormat

For complex pluralization, gender, and select:

// Enable in stack config
i18n: { messageFormat: 'icu' }
{
  "messages": {
    "inbox": "{count, plural, =0 {No messages} one {1 message} other {# messages}}",
    "gender": "{gender, select, male {He} female {She} other {They}} replied"
  }
}

When to use ICU:

  • Languages with complex plural rules (Arabic, Slavic languages)
  • Gender-aware translations
  • Ordinal numbers (1st, 2nd, 3rd)
  • Date/time/number formatting

Integration with II18nService

Service Contract

II18nService is the kernel service that loads bundles, resolves keys with fallback chains, reports coverage, and (optionally) generates AI suggestions. Methods:

  • t(key, locale, params?) — resolve a single key with interpolation
  • getTranslations(locale) — full snapshot for a locale
  • loadTranslations(locale, bundle) — programmatic load
  • getLocales() / getDefaultLocale() / setDefaultLocale()
  • getAppBundle(locale) / loadAppBundle(locale, bundle) — object-first format
  • getCoverage(locale, objectName?) — diff vs metadata
  • suggestTranslations(locale, items) — AI-fill missing keys

Full TypeScript signature lives in node_modules/@objectstack/spec/src/contracts/i18n-service.ts — read it when you need exact parameter shapes.

Plugin Setup

import { ObjectKernel } from '@objectstack/core';
import { I18nServicePlugin } from '@objectstack/service-i18n';

const kernel = new ObjectKernel();
kernel.use(new I18nServicePlugin({
  defaultLocale: 'en',
  localesDir: './i18n',
  fallbackLocale: 'en',
  registerRoutes: true,  // Auto-register REST endpoints
  basePath: '/api/v1/i18n',
}));

await kernel.bootstrap();

const i18n = kernel.getService<II18nService>('i18n');

Translation Workflow Best Practices

1. Extract Keys from Metadata

Use the CLI or API to extract all translatable keys from your metadata:

objectstack i18n extract --locale zh-CN --output i18n/zh-CN.json

This generates a skeleton bundle with all required keys.

2. Translate

Fill in the translations manually, or use AI suggestions:

objectstack i18n suggest --locale zh-CN --input i18n/zh-CN.json

3. Validate Coverage

Run coverage analysis to detect missing or stale translations:

objectstack i18n coverage --locale zh-CN

4. Commit & Deploy

Commit translation files to version control. ObjectStack automatically loads them at runtime.


CRM I18n Blueprint

Reference implementation shape:

  • Bundle entry: src/translations/index.ts (or crm.translation.ts)
  • Locale files: src/translations/{en,zh-CN,ja-JP,es-ES}.ts

Use this structure for metadata apps:

Layer CRM Pattern
Stack config i18n.fileOrganization = 'per_locale' with explicit locale list
Translation assembly One TranslationBundle that imports per-locale files
Locale content Object-scoped translations (objects.account.fields.*, _views, _actions) + global app/nav/messages
Naming integrity Translation object/field keys exactly match metadata machine names

For new locales, copy one locale file as a baseline, then run coverage before release.


Advanced Patterns

Namespace Isolation (Multi-Plugin)

When multiple plugins contribute translations, use namespaces to avoid collisions:

const crmBundle: AppTranslationBundle = {
  namespace: 'crm',
  o: {
    account: { label: '客户' },
  },
};

const helpdeskBundle: AppTranslationBundle = {
  namespace: 'helpdesk',
  o: {
    ticket: { label: '工单' },
  },
};

Keys are prefixed: crm.o.account.label, helpdesk.o.ticket.label.

Right-to-Left (RTL) Support

const ar: AppTranslationBundle = {
  _meta: {
    locale: 'ar',
    direction: 'rtl',
  },
  o: {
    account: { label: 'حساب' },
  },
};

UI frameworks can use _meta.direction to apply RTL CSS.

Translation Memory Integration

Implement custom II18nService.suggestTranslations() to integrate with:

  • Translation Management Systems (TMS) like Phrase, Crowdin, Lokalise
  • Machine translation APIs (Google Translate, DeepL)
  • Internal translation memory databases

Common Pitfalls

❌ Mismatched Object Names

Translation keys must match metadata exactly:

// Metadata
{ name: 'project_task' }

// Translation (WRONG)
{ o: { projectTask: { label: '项目任务' } } }

// Translation (CORRECT)
{ o: { project_task: { label: '项目任务' } } }

❌ Hardcoded Option Values

Always use lowercase machine values for options:

// Metadata
options: [
  { value: 'in_progress', label: 'In Progress' },
]

// Translation (WRONG)
options: { 'In Progress': '进行中' }

// Translation (CORRECT)
options: { in_progress: '进行中' }

❌ Ignoring Coverage Reports

Stale translations can cause confusion. Always run coverage analysis before releases.


Quick-Start Template

// i18n/zh-CN.ts
import type { AppTranslationBundle } from '@objectstack/spec';

export default {
  _meta: {
    locale: 'zh-CN',
    direction: 'ltr',
  },

  o: {
    account: {
      label: '客户',
      pluralLabel: '客户',
      fields: {
        name: { label: '客户名称' },
        email: { label: '邮箱', placeholder: '输入邮箱地址' },
        status: {
          label: '状态',
          options: {
            active: '活跃',
            inactive: '停用',
          },
        },
      },
      _views: {
        all_accounts: { label: '全部客户' },
      },
    },
  },

  app: {
    crm: { label: '客户关系管理' },
  },

  nav: {
    home: '首页',
    settings: '设置',
  },

  messages: {
    'common.save': '保存',
    'common.cancel': '取消',
  },
} satisfies AppTranslationBundle;

References

See references/_index.md for the full list of Zod schemas (with one-line descriptions) — pointers into node_modules/@objectstack/spec/src/. Always Read the source for exact field shapes; do not rely on memory of property names.

See Also

  • objectstack-data — For understanding object and field metadata structure
  • objectstack-ui — For view, app, and action translations
  • objectstack-automation — For workflow and flow message translations