# JVM backend guide

This guide covers `i18n-flow` projects that use Java properties message bundles with Spring `MessageSource` or Java
`ResourceBundle`. Android and Kotlin Multiplatform XML resources are not part of this platform.

For installation, AI runner configuration, translation policies, and other shared concepts, see the main
[README](../README.md).

## Quick start

Run initialization from the backend repository root:

```sh
npx @rio-cloud/i18n init --platform jvm
```

The explicit option is optional when the project can be detected unambiguously. Gradle or Maven build files together
with `messages_*.properties` bundles are strong JVM signals. Interactive initialization displays the detected platform
and its evidence for confirmation.

After initialization, validate the existing bundles before translating them:

```sh
npx @rio-cloud/i18n validate
npx @rio-cloud/i18n translate
```

## Supported resource model

The JVM platform supports:

- Spring `MessageSource`
- Java `ResourceBundle`
- locale-specific English source bundles such as `messages_en.properties` and `messages_en_GB.properties`
- locale suffixes such as `de`, `de_DE`, and `pt_BR`
- Java properties escapes, Unicode escapes, continuation lines, comments, and blank lines
- Java `MessageFormat` arguments such as `{0}`, `{0,date,short}`, and `{1,number}`
- messages containing HTML, with tag-structure validation before writes

The writer changes only affected entries and preserves unchanged comments, ordering, line endings, and formatting.
Duplicate properties keys are rejected because their runtime resolution would otherwise be ambiguous.

## Initialization

JVM initialization:

1. discovers `messages_en.properties` and `messages_en_GB.properties` source candidates
2. discovers sibling target bundles
3. maps every configured BCP-47 locale explicitly to its filename suffix
4. detects Spring `MessageSource` from Gradle, Maven, or source usage, and detects direct Java `ResourceBundle` usage
5. classifies the non-locale-specific default bundle
6. optionally imports locale mappings from an existing Phrase configuration
7. asks for the shared i18n catalog and AI runner configuration
8. verifies remote catalog access and the Codex CLI connection before writing
9. writes `.i18nrc.json`, `.i18n/context/project.md`, and local artifact rules to `.gitignore`

Unlike Web initialization, JVM initialization does not install the frontend repository skill or add an npm package
script to the backend project.

The JSON result uses `phraseConfigImportedFrom` to document the one-time Phrase mapping source. Dry runs report
`plannedFiles`; completed machine-readable runs report only files actually written under `writtenFiles`, including
`.gitignore` when initialization adds local artifact rules.

Use the built-in help to see every option:

```sh
npx @rio-cloud/i18n init --platform jvm --help
npx @rio-cloud/i18n help jvm
npx @rio-cloud/i18n help config
```

### Locale-to-filename mapping

A filename suffix is not always a complete locale identifier. For example, `messages_de.properties` can represent
`de-DE`, while another project may use `messages_de_DE.properties` for the same locale. The generated configuration
therefore records the mapping explicitly:

```json
"fileLocales": {
  "de-DE": "de_DE",
  "pt-BR": "pt_BR"
}
```

The keys of `resources.fileLocales` are the configured target locales, so no separate `targetLocales` list is needed.
Every target locale must have one unique filename suffix, and the source locale is not included in the mapping.

### Default bundle strategy

The non-locale-specific file, normally `messages.properties`, is neither inferred as the source locale nor treated as a
translation target. Its role must be represented by one of these strategies:

| Strategy | Meaning |
| --- | --- |
| `mirror-source` | The default bundle must remain semantically identical to the locale-specific English source bundle. |
| `empty-anchor` | The file is a technical framework anchor. It may be empty or contain keys whose values are all empty. |
| `independent` | The file is an intentional, separately maintained fallback bundle. It is preserved but not translated. |

Initialization infers the strategy from the current content, explains the detected role in plain language, and asks for
confirmation. The detailed three-way selection appears only when the detection is rejected. The JVM platform requires
an explicit default bundle, so a missing file is reported during setup instead of silently inventing its role.

### Migrating Phrase mappings

Projects with `.phrase.yml`, `.phrase.yaml`, `.phraseapp.yml`, or `.phraseapp.yaml` can import their existing locale
mappings once during initialization:

```sh
npx @rio-cloud/i18n init --platform jvm --import-phrase-config
```

Phrase is not used at runtime. After reviewing the generated configuration and validating the bundles, preview removal
of the obsolete Phrase files and package scripts:

```sh
npx @rio-cloud/i18n cleanup-phrase --dry-run
npx @rio-cloud/i18n cleanup-phrase
```

## Configuration example

```json
{
  "platform": "jvm",
  "sourceLocale": "en-GB",
  "sourceFile": "src/main/resources/i18n/messages_en_GB.properties",
  "targetPathPattern": "src/main/resources/i18n/messages_{fileLocale}.properties",
  "resources": {
    "basename": "i18n/messages",
    "resolver": "spring-message-source",
    "root": "src/main/resources/i18n",
    "defaultFile": "src/main/resources/i18n/messages.properties",
    "defaultBundleStrategy": "empty-anchor",
    "fileLocales": {
      "de-DE": "de_DE",
      "pt-BR": "pt_BR"
    }
  },
  "stateFile": ".i18n/state.json",
  "catalog": {
    "url": "ssh://git@bitbucket.collaboration-man.com:7999/riofront/i18n-catalog.git"
  },
  "contextPaths": [".i18n/context/project.md"],
  "translation": {
    "batchSize": 80,
    "deltaLocalesPerCall": 6,
    "maxParallelLocales": 4
  },
  "ai": {
    "runner": "codex-cli",
    "command": "codex",
    "model": "gpt-5.6-terra",
    "reasoningEffort": "medium",
    "sandbox": "read-only",
    "timeoutMs": 600000
  },
  "checks": {
    "protectedTerms": "contextual"
  },
  "ignoredKeys": []
}
```

`targetPathPattern` must contain `{fileLocale}`. Relative resource paths are resolved from the project or configuration
directory in the same way as other `i18n-flow` paths.

`translate` and `retranslate` automatically bundle small workloads across target locales. With the default
`deltaLocalesPerCall` value of `6`, one changed source key across 24 locales uses four AI requests. Set the value to
`24` to permit one request when `batchSize` also allows all 24 key-locale combinations.

## Commands

The first JVM version intentionally exposes a focused command set:

| Command | Purpose |
| --- | --- |
| `translate` | Add translations only for missing or empty target values. Existing translations remain unchanged. |
| `retranslate` | Replace selected existing translations from the current source messages. |
| `improve` | Review selected translations in conservative or revise mode; optionally improve the source locale first. |
| `validate` | Validate existing bundles without calling AI or changing files. |
| `cleanup-phrase` | Remove obsolete Phrase configuration and translation scripts after migration. |
| `test-catalog-connection` | Verify access to the configured catalog. |
| `test-ai-connection` | Verify the configured AI runner. |

Examples:

```sh
# Translate all missing values
npx @rio-cloud/i18n translate

# Translate one message family
npx @rio-cloud/i18n translate --keys 'email.*'

# Retranslate a changed source message
npx @rio-cloud/i18n retranslate --keys email.subject

# Conservatively review selected German messages
npx @rio-cloud/i18n improve --locale de-DE --keys 'email.*'

# Improve English source messages first, then use them to improve German
npx @rio-cloud/i18n improve --locale de-DE --keys 'email.*' --include-source-locale

# Validate all configured bundles
npx @rio-cloud/i18n validate
```

Web-only key harmonization, unused-key removal, shared-message synchronization, UI component inspection, and repository
skills are intentionally unavailable for JVM projects.
