CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-stricli--core

Build complex CLIs with type safety and no dependencies

Overview
Eval results
Files

text-and-localization.mddocs/

Text and Localization

Comprehensive localization system with customizable text for all user-facing strings including help text, error messages, and documentation.

Quick Reference

import { text_en } from "@stricli/core";

const app = buildApplication(command, {
  name: "myapp",
  localization: {
    defaultLocale: "en",
    loadText: (locale) => {
      if (locale.startsWith("es")) return text_es;
      if (locale.startsWith("en")) return text_en;
      return undefined;
    }
  }
});

await run(app, inputs, { process, locale: process.env.LANG });

ApplicationText Interface

Full set of static text and text-returning callbacks necessary for Stricli output. Extends error formatting and documentation text.

/**
 * Complete text interface for Stricli output
 * Includes error messages, documentation text, and version warnings
 */
interface ApplicationText extends ApplicationErrorFormatting, DocumentationText {
  /**
   * Generate warning when latest version is not installed
   * @param args - Current version, latest version, upgrade command, color flag
   * @returns Formatted warning message
   */
  readonly currentVersionIsNotLatest: (args: {
    readonly currentVersion: string;
    readonly latestVersion: string;
    readonly upgradeCommand?: string;
    readonly ansiColor: boolean;
  }) => string;
}

/**
 * Default English text implementation
 */
const text_en: ApplicationText;

interface DocumentationKeywords {
  default: string;
  separator: string;
}

interface DocumentationHeaders {
  usage: string;
  aliases: string;
  commands: string;
  flags: string;
  arguments: string;
}

interface DocumentationBriefs {
  help: string;
  helpAll: string;
  version: string;
  argumentEscapeSequence: string;
}

interface ApplicationErrorFormatting extends CommandErrorFormatting {
  noCommandRegisteredForInput: (args: {
    input: string;
    corrections: readonly string[];
    ansiColor: boolean;
  }) => string;
  noTextAvailableForLocale: (args: {
    requestedLocale: string;
    defaultLocale: string;
    ansiColor: boolean;
  }) => string;
}

interface CommandErrorFormatting {
  exceptionWhileParsingArguments: (exc: unknown, ansiColor: boolean) => string;
  exceptionWhileLoadingCommandFunction: (exc: unknown, ansiColor: boolean) => string;
  exceptionWhileLoadingCommandContext: (exc: unknown, ansiColor: boolean) => string;
  exceptionWhileRunningCommand: (exc: unknown, ansiColor: boolean) => string;
  commandErrorResult: (err: Error, ansiColor: boolean) => string;
}

Default English Text

import { text_en } from "@stricli/core";

const app = buildApplication(command, {
  name: "myapp",
  localization: { defaultLocale: "en", loadText: (locale) => text_en }
});

Creating Custom Localization

Example 1: Spanish Localization

import { ApplicationText, text_en } from "@stricli/core";

const text_es: ApplicationText = {
  // Inherit English text as base
  ...text_en,

  // Override with Spanish text
  headers: {
    usage: "USO",
    aliases: "ALIAS",
    commands: "COMANDOS",
    flags: "OPCIONES",
    arguments: "ARGUMENTOS"
  },

  keywords: {
    default: "predeterminado =",
    separator: "separador ="
  },

  briefs: {
    help: "Mostrar información de ayuda y salir",
    helpAll: "Mostrar toda la información de ayuda y salir",
    version: "Mostrar información de versión y salir",
    argumentEscapeSequence: "Todas las entradas posteriores deben interpretarse como argumentos"
  },

  noCommandRegisteredForInput: ({ input, corrections }) => {
    const mensaje = `No hay comando registrado para \`${input}\``;
    if (corrections.length > 0) {
      return `${mensaje}, ¿quiso decir ${corrections.join(" o ")}?`;
    }
    return mensaje;
  },

  noTextAvailableForLocale: ({ requestedLocale, defaultLocale }) => {
    return `La aplicación no admite el idioma "${requestedLocale}", usando "${defaultLocale}" por defecto`;
  },

  exceptionWhileParsingArguments: (exc) => {
    return `No se pudieron analizar los argumentos: ${exc}`;
  },

  exceptionWhileLoadingCommandFunction: (exc) => {
    return `No se pudo cargar la función de comando: ${exc}`;
  },

  exceptionWhileLoadingCommandContext: (exc) => {
    return `No se pudo cargar el contexto del comando: ${exc}`;
  },

  exceptionWhileRunningCommand: (exc) => {
    return `El comando falló: ${exc}`;
  },

  commandErrorResult: (err) => {
    return err.message;
  },

  currentVersionIsNotLatest: ({ currentVersion, latestVersion, upgradeCommand }) => {
    if (upgradeCommand) {
      return `La última versión disponible es ${latestVersion} (actualmente ejecutando ${currentVersion}), actualice con "${upgradeCommand}"`;
    }
    return `La última versión disponible es ${latestVersion} (actualmente ejecutando ${currentVersion})`;
  }
};

// Use in application
const app = buildApplication(command, {
  name: "myapp",
  localization: {
    defaultLocale: "es",
    loadText: (locale) => {
      if (locale.startsWith("es")) {
        return text_es;
      }
      if (locale.startsWith("en")) {
        return text_en;
      }
      return undefined;
    }
  }
});

Example 2: French Localization

import { ApplicationText, text_en } from "@stricli/core";

const text_fr: ApplicationText = {
  ...text_en,

  headers: {
    usage: "UTILISATION",
    aliases: "ALIAS",
    commands: "COMMANDES",
    flags: "OPTIONS",
    arguments: "ARGUMENTS"
  },

  keywords: {
    default: "par défaut =",
    separator: "séparateur ="
  },

  briefs: {
    help: "Afficher les informations d'aide et quitter",
    helpAll: "Afficher toutes les informations d'aide et quitter",
    version: "Afficher les informations de version et quitter",
    argumentEscapeSequence: "Toutes les entrées suivantes doivent être interprétées comme des arguments"
  },

  noCommandRegisteredForInput: ({ input, corrections }) => {
    const message = `Aucune commande enregistrée pour \`${input}\``;
    if (corrections.length > 0) {
      return `${message}, vouliez-vous dire ${corrections.join(" ou ")} ?`;
    }
    return message;
  },

  noTextAvailableForLocale: ({ requestedLocale, defaultLocale }) => {
    return `L'application ne prend pas en charge la locale "${requestedLocale}", utilisation de "${defaultLocale}" par défaut`;
  },

  exceptionWhileParsingArguments: (exc) => {
    return `Impossible d'analyser les arguments : ${exc}`;
  },

  exceptionWhileLoadingCommandFunction: (exc) => {
    return `Impossible de charger la fonction de commande : ${exc}`;
  },

  exceptionWhileLoadingCommandContext: (exc) => {
    return `Impossible de charger le contexte de la commande : ${exc}`;
  },

  exceptionWhileRunningCommand: (exc) => {
    return `La commande a échoué : ${exc}`;
  },

  commandErrorResult: (err) => {
    return err.message;
  },

  currentVersionIsNotLatest: ({ currentVersion, latestVersion, upgradeCommand }) => {
    if (upgradeCommand) {
      return `La dernière version disponible est ${latestVersion} (actuellement ${currentVersion}), mise à jour avec "${upgradeCommand}"`;
    }
    return `La dernière version disponible est ${latestVersion} (actuellement ${currentVersion})`;
  }
};

Example 3: Dynamic Locale Loading

import { buildApplication, text_en, ApplicationText } from "@stricli/core";

// Lazy-load translations
async function loadLocale(locale: string): Promise<ApplicationText | undefined> {
  if (locale.startsWith("en")) {
    return text_en;
  }

  try {
    // Dynamic import based on locale
    const module = await import(`./locales/${locale}.js`);
    return module.default;
  } catch {
    // Locale not available
    return undefined;
  }
}

const app = buildApplication(command, {
  name: "myapp",
  localization: {
    defaultLocale: "en",
    loadText: (locale) => {
      // Synchronous loading from cache or return undefined
      const cached = localeCache.get(locale);
      if (cached) {
        return cached;
      }

      // For runtime, we need synchronous loading
      // Pre-load locales during build
      return undefined;
    }
  }
});

// Pre-load common locales
const localeCache = new Map<string, ApplicationText>();
localeCache.set("en", text_en);

(async () => {
  const es = await loadLocale("es");
  if (es) localeCache.set("es", es);

  const fr = await loadLocale("fr");
  if (fr) localeCache.set("fr", fr);
})();

Example 4: Custom Error Messages

import { ApplicationText, text_en, formatMessageForArgumentScannerError, ArgumentScannerError } from "@stricli/core";

const customText: ApplicationText = {
  ...text_en,

  exceptionWhileParsingArguments: (exc, ansiColor) => {
    if (exc instanceof ArgumentScannerError) {
      // Custom formatting for specific errors
      return formatMessageForArgumentScannerError(exc, {
        FlagNotFoundError: (err) => {
          const flag = ansiColor ? `\x1B[1m${err.input}\x1B[22m` : err.input;
          if (err.corrections.length > 0) {
            return `Unknown option: ${flag}\nDid you mean: ${err.corrections.join(", ")}?`;
          }
          return `Unknown option: ${flag}`;
        },
        ArgumentParseError: (err) => {
          const value = ansiColor ? `\x1B[1m${err.input}\x1B[22m` : err.input;
          const param = ansiColor
            ? `\x1B[1m${err.externalFlagNameOrPlaceholder}\x1B[22m`
            : err.externalFlagNameOrPlaceholder;
          return `Invalid value ${value} for ${param}`;
        }
      });
    }
    return text_en.exceptionWhileParsingArguments(exc, ansiColor);
  },

  exceptionWhileRunningCommand: (exc, ansiColor) => {
    // Add more context to errors
    const prefix = ansiColor ? "\x1B[31mError:\x1B[39m" : "Error:";
    if (exc instanceof Error) {
      return `${prefix} ${exc.message}\n\nRun with --help for usage information.`;
    }
    return `${prefix} ${String(exc)}`;
  }
};

Example 5: Branded Text

import { ApplicationText, text_en } from "@stricli/core";

const brandedText: ApplicationText = {
  ...text_en,

  currentVersionIsNotLatest: ({ currentVersion, latestVersion, upgradeCommand }) => {
    // Custom branding
    const message = `🎉 New version available!\n` +
      `   Current: v${currentVersion}\n` +
      `   Latest:  v${latestVersion}\n`;

    if (upgradeCommand) {
      return message + `   Run: ${upgradeCommand}`;
    }
    return message;
  },

  exceptionWhileRunningCommand: (exc, ansiColor) => {
    // Friendly error messages
    const prefix = ansiColor ? "😞 \x1B[31mOops!\x1B[39m" : "Oops!";
    if (exc instanceof Error) {
      return `${prefix} Something went wrong:\n\n  ${exc.message}\n\n` +
        `💡 Tip: Run with --help for usage information`;
    }
    return `${prefix} ${String(exc)}`;
  }
};

Multi-Language Support

const locales = { en: text_en, es: text_es, fr: text_fr };

const app = buildApplication(command, {
  name: "myapp",
  localization: {
    defaultLocale: "en",
    loadText: (locale) => {
      // Try exact match
      if (locale in locales) return locales[locale];
      // Try language prefix (e.g., "en-US" -> "en")
      const lang = locale.split("-")[0];
      if (lang in locales) return locales[lang];
      // Fall back to English
      return text_en;
    }
  }
});

await run(app, inputs, {
  process,
  locale: process.env.LANG || "en"
});

Complete Example

Here's a comprehensive example with multiple locales:

import {
  buildApplication,
  buildCommand,
  run,
  text_en,
  type ApplicationText
} from "@stricli/core";

// Define Spanish text
const text_es: ApplicationText = {
  ...text_en,
  headers: {
    usage: "USO",
    aliases: "ALIAS",
    commands: "COMANDOS",
    flags: "OPCIONES",
    arguments: "ARGUMENTOS"
  },
  briefs: {
    help: "Mostrar ayuda",
    helpAll: "Mostrar toda la ayuda",
    version: "Mostrar versión",
    argumentEscapeSequence: "Interpretar como argumentos"
  },
  // ... other translations
};

// Build application with localization
const app = buildApplication(
  buildCommand({
    func: async function(flags) {
      if (flags.verbose) {
        this.process.stdout.write("Modo detallado activado\n");
      }
      this.process.stdout.write("¡Hola mundo!\n");
    },
    parameters: {
      flags: {
        verbose: {
          kind: "boolean",
          brief: "Salida detallada"
        }
      }
    },
    docs: {
      brief: "Comando de saludo"
    }
  }),
  {
    name: "myapp",
    versionInfo: {
      currentVersion: "1.0.0"
    },
    localization: {
      defaultLocale: "en",
      loadText: (locale) => {
        if (locale.startsWith("es")) {
          return text_es;
        }
        if (locale.startsWith("en")) {
          return text_en;
        }
        return undefined;
      }
    }
  }
);

// Run with locale from environment
await run(app, process.argv.slice(2), {
  process,
  locale: process.env.LANG?.split(".")[0] || "en"
});

Related Documentation

  • Configuration and Context - Localization configuration
  • Documentation and Help - Help text generation
  • Error Handling - Error message formatting

Install with Tessl CLI

npx tessl i tessl/npm-stricli--core

docs

application.md

commands-and-routing.md

configuration-and-context.md

documentation-and-help.md

error-handling.md

exit-codes.md

flag-parameters.md

index.md

parameter-parsers.md

positional-parameters.md

text-and-localization.md

tile.json