Internationalization (i18n) Guide

Overview

goopt provides comprehensive internationalization (i18n) support, allowing you to create command-line applications that can adapt to different languages. The i18n system is based on translation bundles that store message keys and their translations.

Built-in Language Support

goopt comes with translations for the following languages out of the box:

  • English (default)
  • French
  • German

The built-in translations cover all system messages, error descriptions, and usage-related text.

Core Concepts

Translation Bundles

Translations in goopt are managed through bundles:

  • A Bundle is a collection of translations for multiple languages
  • Each bundle maps message keys to translated strings
  • goopt uses two types of bundles:
    • Default Bundle: For system messages and error descriptions
    • User Bundle: For application-specific messages (optional)

Creating Translation Bundles

goopt provides three main ways to create translation bundles:

1. Using the Default Bundle

The default bundle is created automatically and includes all system translations:

// Get the default bundle (already populated with system translations)
bundle := i18n.Default()

2. Creating an Empty Bundle

You can create an empty bundle to add your own translations:

// Create a new empty bundle with English as the default language
bundle := i18n.NewEmptyBundle()

// Add translations to the bundle
bundle.AddLanguage(language.German, map[string]string{
    "my.command.description": "Befehlsbeschreibung",
    "my.flag.description": "Flaggenbeschreibung"
})

3. Loading from Embedded Files

For larger applications, you can organize translations in JSON files:

//go:embed translations/*.json
var translationsFS embed.FS

// Create a bundle from embedded files
bundle, err := i18n.NewBundleWithFS(translationsFS, "translations")
if err != nil {
    log.Fatal(err)
}

Example JSON file (translations/de.json):

{
  "my.command.description": "Eine neue Datei erstellen",
  "my.flag.description": "Ausführliche Ausgabe aktivieren"
}

Using Bundles with Parser

Once you have created bundles, you can use them with your parser:

Setting the Default Bundle

You can replace the default bundle with your own:

parser := goopt.NewParser()

// Create a custom bundle
customBundle, err := i18n.NewBundle()
if err != nil {
    log.Fatal(err)
}

// Replace the default bundle
parser.ReplaceDefaultBundle(customBundle)

Setting a User Bundle

You can add a separate bundle for your application-specific messages:

// Create a user bundle
userBundle := i18n.NewEmptyBundle()
userBundle.AddLanguage(language.German, germanTranslations)

// Set it as the user bundle
parser.SetUserBundle(userBundle)

Working with Translations

Adding Translations to a Bundle

Add translations for a specific language:

// Add German translations
bundle.AddLanguage(language.German, map[string]string{
    "my.command.description": "Befehlsbeschreibung",
    "my.flag.description": "Flaggenbeschreibung"
})

Using Translation Keys

Use translation keys when creating commands and flags:

// Create a command with a translatable description
parser.AddCommand(
    goopt.NewCommand(
        goopt.WithName("create"),
        goopt.WithCommandDescriptionKey("my.command.description"),
    ),
)

// Add a flag with a translatable description
parser.AddFlag("verbose", goopt.NewArg(
    goopt.WithShort("v"),
    goopt.WithDescriptionKey("my.flag.description"),
))

Translation Key Organization

Recommended patterns for organizing translation keys:

  • System keys (built-in): goopt.error.{error_type}, goopt.msg.{message_type}
  • Command descriptions: my.commands.{command_name}
  • Flag descriptions: my.flags.{flag_name}
  • Custom error messages: my.errors.{error_name}

Translatable Errors

All system errors in goopt are automatically translatable:

if !parser.Parse(os.Args) {
    // These errors will be in the current language
    for _, err := range parser.GetErrors() {
        fmt.Fprintf(os.Stderr, "Error: %s\n", err)
    }
}

Complete Example

package main

import (
    "embed"
    "fmt"
    "log"
    "os"
    
    "github.com/napalu/goopt"
    "github.com/napalu/goopt/i18n"
    "golang.org/x/text/language"
)

//go:embed translations/*.json
var translationsFS embed.FS

func main() {
    // Create a parser
    parser := goopt.NewParser()
    
    // Method 1: Create a user bundle from embedded files
    userBundle, err := i18n.NewBundleWithFS(translationsFS, "translations")
    if err != nil {
        log.Fatalf("Failed to load translations: %v", err)
    }
    
    // Method 2: Create an empty bundle and add translations programmatically
    // (This would be an alternative to Method 1)
    /*
    userBundle := i18n.NewEmptyBundle()
    userBundle.AddLanguage(language.German, map[string]string{
        "app.description": "Dateiverwaltungswerkzeug",
        "cmd.create": "Erstellt eine neue Datei",
        "flag.verbose": "Ausführliche Ausgabe aktivieren",
    })
    */
    
    // Set the user bundle
    parser.SetUserBundle(userBundle)
    
    // Create commands with translatable descriptions
    parser.AddCommand(
        goopt.NewCommand(
            goopt.WithName("create"),
            goopt.WithCommandDescriptionKey("cmd.create"),
        ),
    )
    
    // Add flags with translatable descriptions
    parser.AddFlag("verbose", goopt.NewArg(
        goopt.WithShortFlag("v"),
        goopt.WithDescriptionKey("flag.verbose"),
    ))
    
    // Parse arguments
    if !parser.Parse(os.Args) {
        // Errors will be in the current language
        for _, err := range parser.GetErrors() {
            fmt.Fprintf(os.Stderr, "Error: %s\n", err)
        }
        parser.PrintUsageWithGroups(os.Stdout)
        return
    }
    
    // Continue with application logic...
}

Example translation file (translations/de.json):

{
  "app.description": "Dateiverwaltungswerkzeug",
  "cmd.create": "Erstellt eine neue Datei",
  "flag.verbose": "Ausführliche Ausgabe aktivieren",
  "flag.output": "Ausgabedatei"
}