> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/sinelaw/fresh/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin Internationalization

> Add multi-language support to your Fresh plugins

Plugins can provide translations for their user-facing strings, making them accessible to international users. Fresh provides a simple i18n system based on JSON translation files.

## Creating Translation Files

Create a `.i18n.json` file alongside your plugin with the same base name:

```
plugins/
  my_plugin.ts
  my_plugin.i18n.json
```

### Translation File Structure

The translation file uses a simple two-level structure: locale → key → translation.

```json theme={null}
{
  "en": {
    "cmd.do_thing": "My Plugin: Do Thing",
    "cmd.do_thing_desc": "Description of the command",
    "status.ready": "My plugin ready",
    "status.found": "Found %{count} items",
    "prompt.search": "Search:"
  },
  "es": {
    "cmd.do_thing": "Mi Plugin: Hacer Cosa",
    "cmd.do_thing_desc": "Descripción del comando",
    "status.ready": "Mi plugin listo",
    "status.found": "Encontrados %{count} elementos",
    "prompt.search": "Buscar:"
  }
}
```

### Key Conventions

Organize your translation keys using prefixes:

<CardGroup cols={2}>
  <Card title="cmd.*" icon="terminal">
    Command names and descriptions
  </Card>

  <Card title="status.*" icon="message">
    Status bar messages
  </Card>

  <Card title="prompt.*" icon="keyboard">
    Prompt labels
  </Card>

  <Card title="error.*" icon="circle-exclamation">
    Error messages
  </Card>
</CardGroup>

### Interpolation

Use `%{variable}` syntax for variable interpolation:

```json theme={null}
{
  "en": {
    "status.found": "Found %{count} items in %{time}ms",
    "error.file_not_found": "File not found: %{path}"
  }
}
```

## Using Translations

### Translating Status Messages

Use `editor.t()` to translate status messages:

```typescript theme={null}
// Simple message
editor.setStatus(editor.t("status.ready"));

// With interpolation
editor.setStatus(editor.t("status.found", {
  count: String(results.length)
}));
```

<Note>
  The `editor.t()` method is available through the `pluginTranslate` API function.
</Note>

### Translating Command Registration

Use `%` prefix for command names and descriptions to enable automatic translation:

<CodeGroup>
  ```typescript Before (hardcoded) theme={null}
  editor.registerCommand(
    "My Plugin: Search",
    "Search through files",
    "my_search",
    "normal",
    "plugin"
  );
  ```

  ```typescript After (i18n-enabled) theme={null}
  editor.registerCommand(
    "%cmd.search",
    "%cmd.search_desc",
    "my_search",
    "normal",
    "plugin"
  );
  ```
</CodeGroup>

### Translating Prompt Labels

```typescript theme={null}
// Before
editor.startPrompt("Search:", "my-search");

// After
editor.startPrompt(editor.t("prompt.search"), "my-search");
```

## Translation API

### pluginTranslate

Translate a string for a plugin using the current locale.

```typescript theme={null}
pluginTranslate(
  plugin_name: string,
  key: string,
  args: Record<string, unknown>
): string
```

<ParamField path="plugin_name" type="string" required>
  Name of the plugin (matches the .ts file name)
</ParamField>

<ParamField path="key" type="string" required>
  Translation key (e.g., "status.ready")
</ParamField>

<ParamField path="args" type="Record<string, unknown>" required>
  Variables for interpolation
</ParamField>

<ResponseField name="returns" type="string">
  Translated string with variables interpolated
</ResponseField>

**Example:**

```typescript theme={null}
const message = editor.pluginTranslate(
  "my_plugin",
  "status.found",
  { count: "42", time: "125" }
);
// Result (en): "Found 42 items in 125ms"
// Result (es): "Encontrados 42 elementos en 125ms"
```

### getCurrentLocale

Get the currently active locale.

```typescript theme={null}
getCurrentLocale(): string
```

<ResponseField name="returns" type="string">
  Current locale code (e.g., "en", "es", "fr")
</ResponseField>

**Example:**

```typescript theme={null}
const locale = editor.getCurrentLocale();
editor.debug(`Current locale: ${locale}`);
```

## Translation Loading

Translations are automatically loaded when your plugin loads. If the user's locale isn't available in your translation file, English (`en`) is used as a fallback.

### Fallback Chain

1. User's configured locale (e.g., `es`)
2. English (`en`)
3. Original key string

**Example:**

```typescript theme={null}
// User has locale "fr" (French)
// Plugin only has "en" and "es" translations
// Falls back to "en"

const message = editor.t("status.ready");
// Returns English translation: "My plugin ready"
```

## Complete Example

### Plugin File (git\_grep.ts)

```typescript theme={null}
// Register command with translatable strings
editor.registerCommand(
  "%cmd.git_grep",
  "%cmd.git_grep_desc",
  "git_grep",
  "normal",
  "plugin"
);

// Command implementation
globalThis.git_grep = () => {
  editor.startPrompt(editor.t("prompt.search"), "git-grep");
};

// Handle search
globalThis.handleGitGrep = async (data) => {
  if (data.prompt_type === "git-grep" && data.confirmed) {
    editor.setStatus(editor.t("status.searching"));
    
    const result = await editor.spawnProcess("git", [
      "grep",
      "-n",
      data.value
    ]);
    
    if (result.exit_code === 0) {
      const count = result.stdout.split("\n").length - 1;
      editor.setStatus(editor.t("status.found", {
        count: String(count)
      }));
    } else {
      editor.setStatus(editor.t("status.not_found"));
    }
  }
};

editor.on("prompt_submit", "handleGitGrep");
```

### Translation File (git\_grep.i18n.json)

```json theme={null}
{
  "en": {
    "cmd.git_grep": "Git: Grep",
    "cmd.git_grep_desc": "Search for text in git-tracked files",
    "prompt.search": "Git grep:",
    "status.searching": "Searching...",
    "status.found": "Found %{count} matches",
    "status.not_found": "No matches found"
  },
  "es": {
    "cmd.git_grep": "Git: Buscar",
    "cmd.git_grep_desc": "Buscar texto en archivos rastreados por git",
    "prompt.search": "Buscar en git:",
    "status.searching": "Buscando...",
    "status.found": "Encontrados %{count} coincidencias",
    "status.not_found": "No se encontraron coincidencias"
  },
  "fr": {
    "cmd.git_grep": "Git: Rechercher",
    "cmd.git_grep_desc": "Rechercher du texte dans les fichiers suivis par git",
    "prompt.search": "Recherche git:",
    "status.searching": "Recherche en cours...",
    "status.found": "Trouvé %{count} correspondances",
    "status.not_found": "Aucune correspondance trouvée"
  }
}
```

## Best Practices

<AccordionGroup>
  <Accordion title="Always provide English translations">
    English (`en`) is the fallback locale. All plugins should include English translations.
  </Accordion>

  <Accordion title="Use consistent key naming">
    Follow the convention of prefixing keys by category: `cmd.*`, `status.*`, `prompt.*`, etc.
  </Accordion>

  <Accordion title="Keep translations concise">
    Status bar messages and command names should be short and clear in all languages.
  </Accordion>

  <Accordion title="Test with different locales">
    Test your plugin with different locale settings to ensure translations display correctly.

    ```bash theme={null}
    # Test with Spanish locale
    LANG=es_ES.UTF-8 fresh
    ```
  </Accordion>

  <Accordion title="Use type-safe interpolation">
    Always convert numbers to strings for interpolation:

    ```typescript theme={null}
    // Good
    editor.t("status.found", { count: String(results.length) });

    // May cause issues
    editor.t("status.found", { count: results.length });
    ```
  </Accordion>
</AccordionGroup>

## Example Plugins with i18n

See these plugins for complete examples:

* `plugins/git_grep.ts` + `plugins/git_grep.i18n.json`
* `plugins/git_find_file.ts` + `plugins/git_find_file.i18n.json`
* `plugins/git_gutter.ts` + `plugins/git_gutter.i18n.json`
