Translation Adapter 
Gunshi provides built-in internationalization support, but you might want to integrate it with existing translation systems or libraries. This guide explains how to create a translation adapter to connect Gunshi with your preferred i18n solution.
Why Use a Translation Adapter? 
A translation adapter offers several benefits:
- Integration: Connect Gunshi with your existing i18n infrastructure
 - Consistency: Use the same translation system across your entire application
 - Advanced features: Leverage features of specialized i18n libraries like message formatting
 - Resource management: Let your i18n library manage translation resources directly
 
IMPORTANT
Gunshi has a built-in translation adapter that supports simple interpolation. It does not support complex forms such as plurals.
Understanding the TranslationAdapter Interface 
Gunshi defines a TranslationAdapter interface that allows you to integrate with any i18n library. The interface is designed to let the i18n library manage resources directly:
interface TranslationAdapter<MessageResource = string> {
  /**
   * Get a resource of locale
   * @param locale A Locale at the time of command execution (BCP 47)
   * @returns A resource of locale. if resource not found, return `undefined`
   */
  getResource(locale: string): Record<string, string> | undefined
  /**
   * Set a resource of locale
   * @param locale A Locale at the time of command execution (BCP 47)
   * @param resource A resource of locale
   */
  setResource(locale: string, resource: Record<string, string>): void
  /**
   * Get a message of locale
   * @param locale A Locale at the time of command execution (BCP 47)
   * @param key A key of message resource
   * @returns A message of locale. if message not found, return `undefined`
   */
  getMessage(locale: string, key: string): MessageResource | undefined
  /**
   * Translate a message
   * @param locale A Locale at the time of command execution (BCP 47)
   * @param key A key of message resource
   * @param values A values to be resolved in the message
   * @returns A translated message, if message is not translated, return `undefined`
   */
  translate(locale: string, key: string, values?: Record<string, unknown>): string | undefined
}Creating a Translation Adapter Factory 
To use a custom translation adapter with Gunshi, you need to create a translation adapter factory function that returns an implementation of the TranslationAdapter interface:
import { cli } from 'gunshi'
// Create a translation adapter factory
function createTranslationAdapterFactory(options) {
  // options contains locale and fallbackLocale
  return new MyTranslationAdapter(options)
}
// Implement the TranslationAdapter interface
class MyTranslationAdapter {
  #resources = new Map()
  #options
  constructor(options) {
    this.#options = options
    // Initialize with empty resources for the locale and fallback locale
    this.#resources.set(options.locale, {})
    if (options.locale !== options.fallbackLocale) {
      this.#resources.set(options.fallbackLocale, {})
    }
  }
  getResource(locale) {
    return this.#resources.get(locale)
  }
  setResource(locale, resource) {
    this.#resources.set(locale, resource)
  }
  getMessage(locale, key) {
    const resource = this.getResource(locale)
    if (resource) {
      return resource[key]
    }
    return
  }
  translate(locale, key, values = {}) {
    // Try to get the message from the specified locale
    let message = this.getMessage(locale, key)
    // Fall back to the fallback locale if needed
    if (message === undefined && locale !== this.#options.fallbackLocale) {
      message = this.getMessage(this.#options.fallbackLocale, key)
    }
    if (message === undefined) {
      return
    }
    // Simple interpolation for example
    return message.replaceAll(/\{\{(\w+)\}\}/g, (_, name) => {
      return values[name] === undefined ? `{{${name}}}` : values[name]
    })
  }
}
// Define your command
const command = {
  name: 'greeter',
  options: {
    name: {
      type: 'string',
      short: 'n'
    }
  },
  // Define a resource fetcher to provide translations
  resource: async ctx => {
    if (ctx.locale.toString() === 'ja-JP') {
      return {
        description: '挨拶アプリケーション',
        name: '挨拶する相手の名前',
        greeting: 'こんにちは、{{name}}さん!'
      }
    }
    return {
      description: 'Greeting application',
      name: 'Name to greet',
      greeting: 'Hello, {{name}}!'
    }
  },
  run: ctx => {
    const { name = 'World' } = ctx.values
    // Use the translation function
    const message = ctx.translate('greeting', { name })
    console.log(message)
  }
}
// Run the command with the custom translation adapter
await cli(process.argv.slice(2), command, {
  name: 'translation-adapter-example',
  version: '1.0.0',
  locale: new Intl.Locale(process.env.MY_LOCALE || 'en-US'),
  translationAdapterFactory: createTranslationAdapterFactory
})Integrating with MessageFormat2 (Intl.MessageFormat) 
MessageFormat2 is a Unicode standard for localizable dynamic message strings, designed to make it simple to create natural sounding localized messages. Here's how to create a translation adapter for MessageFormat:
WARNING
MessageFormat2 is work in progress. MessageFormat2 is currently being standardized and can be provided as an Intl.MessageFormat in the future. About see TC39 proposal
import { cli } from 'gunshi'
import { MessageFormat } from 'messageformat' // need to install `npm install --save messageformat@next`
// Create a MessageFormat translation adapter factory
function createMessageFormatAdapterFactory(options) {
  return new MessageFormatTranslation(options)
}
class MessageFormatTranslation {
  #resources = new Map()
  #options
  #formatters = new Map()
  constructor(options) {
    this.#options = options
    // Initialize with empty resources
    this.#resources.set(options.locale, {})
    if (options.locale !== options.fallbackLocale) {
      this.#resources.set(options.fallbackLocale, {})
    }
  }
  getResource(locale) {
    return this.#resources.get(locale)
  }
  setResource(locale, resource) {
    this.#resources.set(locale, resource)
  }
  getMessage(locale, key) {
    const resource = this.getResource(locale)
    if (resource) {
      return resource[key]
    }
    return
  }
  translate(locale, key, values = {}) {
    // Try to get the message from the specified locale
    let message = this.getMessage(locale, key)
    // Fall back to the fallback locale if needed
    if (message === undefined && locale !== this.#options.fallbackLocale) {
      message = this.getMessage(this.#options.fallbackLocale, key)
    }
    if (message === undefined) {
      return
    }
    // Create a formatter for this message if it doesn't exist
    const cacheKey = `${locale}:${key}:${message}`
    let detectError = false
    const onError = err => {
      console.error('[gunshi] messageformat2 error', err.message)
      detectError = true
    }
    if (this.#formatters.has(cacheKey)) {
      const format = this.#formatters.get(cacheKey)
      const formatted = format(values, onError)
      return detectError ? undefined : formatted
    }
    const messageFormat = new MessageFormat(locale, message)
    const format = (values, onError) => {
      return messageFormat.format(values, err => {
        onError(err)
      })
    }
    this.#formatters.set(cacheKey, format)
    const formatted = format(values, onError)
    return detectError ? undefined : formatted
  }
}
// Define your command
const command = {
  name: 'greeter',
  options: {
    name: {
      type: 'string',
      short: 'n'
    },
    count: {
      type: 'number',
      short: 'c',
      default: 1
    }
  },
  // Define a resource fetcher with MessageFormat syntax
  resource: async ctx => {
    if (ctx.locale.toString() === 'ja-JP') {
      return {
        description: '挨拶アプリケーション',
        name: '挨拶する相手の名前',
        count: '挨拶の回数',
        greeting: `.input {$count :number}
.input {$name :string}
.match $count
one {{こんにちは、{$name}さん!}}
*   {{こんにちは、{$name}さん!({$count}回)}}`
      }
    }
    return {
      description: 'Greeting application',
      name: 'Name to greet',
      count: 'Number of greetings',
      greeting: `.input {$count :number}
.input {$name :string}
.match $count
one {{Hello, {$name}!}}
*   {{Hello, {$name}! ({$count} times)}}`
    }
  },
  run: ctx => {
    const { name = 'World', count } = ctx.values
    // Use the translation function with MessageFormat
    const message = ctx.translate('greeting', { name, count })
    console.log(message)
  }
}
// Run the command with the MessageFormat translation adapter
await cli(process.argv.slice(2), command, {
  name: 'messageformat-example',
  version: '1.0.0',
  locale: new Intl.Locale(process.env.MY_LOCALE || 'en-US'),
  translationAdapterFactory: createMessageFormatAdapterFactory
})Integrating with Intlify (Vue I18n Core) 
Intlify is the core of Vue I18n, but it can be used independently. Here's how to create a translation adapter for Intlify:
import { cli } from 'gunshi'
import {
  createCoreContext,
  getLocaleMessage,
  NOT_REOSLVED,
  setLocaleMessage,
  translate as intlifyTranslate
} from '@intlify/core' // need to install `npm install --save @intlify/core@next`
// Create an Intlify translation adapter factory
function createIntlifyAdapterFactory(options) {
  return new IntlifyTranslation(options)
}
class IntlifyTranslation {
  #options
  #context
  constructor(options) {
    this.#options = options
    const { locale, fallbackLocale } = options
    const messages = {
      [locale]: {}
    }
    if (locale !== fallbackLocale) {
      messages[fallbackLocale] = {}
    }
    // Create the Intlify core context
    this.#context = createCoreContext({
      locale,
      fallbackLocale,
      messages
    })
  }
  getResource(locale) {
    return getLocaleMessage(this.#context, locale)
  }
  setResource(locale, resource) {
    setLocaleMessage(this.#context, locale, resource)
  }
  getMessage(locale, key) {
    const resource = this.getResource(locale)
    if (resource) {
      return resource[key]
    }
    return
  }
  translate(locale, key, values = {}) {
    // Check if the message exists in the specified locale or fallback locale
    const message =
      this.getMessage(locale, key) || this.getMessage(this.#options.fallbackLocale, key)
    if (message === undefined) {
      return
    }
    // Use Intlify's translate function
    const result = intlifyTranslate(this.#context, key, values)
    return typeof result === 'number' && result === NOT_REOSLVED ? undefined : result
  }
}
// Define your command
const command = {
  name: 'greeter',
  options: {
    name: {
      type: 'string',
      short: 'n'
    }
  },
  // Define a resource fetcher with Intlify syntax
  resource: async ctx => {
    if (ctx.locale.toString() === 'ja-JP') {
      return {
        description: '挨拶アプリケーション',
        name: '挨拶する相手の名前',
        greeting: 'こんにちは、{name}さん!'
      }
    }
    return {
      description: 'Greeting application',
      name: 'Name to greet',
      greeting: 'Hello, {name}!'
    }
  },
  run: ctx => {
    const { name = 'World' } = ctx.values
    // Use the translation function with Intlify
    const message = ctx.translate('greeting', { name })
    console.log(message)
  }
}
// Run the command with the Intlify translation adapter
await cli(process.argv.slice(2), command, {
  name: 'intlify-example',
  version: '1.0.0',
  locale: new Intl.Locale(process.env.MY_LOCALE || 'en-US'),
  translationAdapterFactory: createIntlifyAdapterFactory
})How It Works 
Here's how the translation adapter works with Gunshi:
- You provide a 
translationAdapterFactoryfunction in the CLI options - Gunshi calls this factory with locale information to create a translation adapter
 - When a command has a 
resourcefunction, Gunshi fetches the resources and passes them to the translation adapter usingsetResource - When 
ctx.translate(key, values)is called in your command, Gunshi uses the translation adapter to translate the key with the values 
This architecture allows you to:
- Use any i18n library with Gunshi
 - Let the i18n library manage resources directly
 - Use advanced features like pluralization and formatting
 - Share translation adapters across your projects
 
