# Internationalization (i18n)

Relaticle ships English-only in the main repository. The codebase is fully i18n-ready: every user-facing string in the authenticated app UI is wrapped in `__()` and keyed against `lang/en/`. Forks and self-hosters that want to translate Relaticle drop their `lang/<locale>/` directory in place — no source-file modifications needed.

## Quick start for fork maintainers

1. Copy `lang/en/` to `lang/<locale>/`:
    ```bash
    cp -r lang/en lang/fr
    ```

2. Translate the strings in `lang/fr/**/*.php`.

3. Refresh the Laravel-Lang framework translations for your locale (auth, validation, pagination, password reset):
    ```bash
    php artisan lang:update
    ```
    Re-run this whenever you add a new locale or want to refresh framework strings.

4. Record a baseline of the English values you just translated against:
    ```bash
    php artisan locale:diff fr --update-snapshot
    ```
    Commit `lang/.snapshots/fr.json` alongside your `lang/fr/` files so teammates share translation state.

5. Set the locale in your `.env`:
    ```
    APP_LOCALE=fr
    ```

6. Filament's admin chrome (~70 locales) is translated automatically — only the strings under `lang/<locale>/` are yours to maintain.

## Staying in sync after upstream merges

After every `git pull upstream main`, run:

```bash
php artisan locale:diff fr
```

The command reports three kinds of drift:

- `Missing in fr: <key>` — upstream added a new key; you need to translate it.
- `Orphaned in fr: <key>` — upstream removed the key; safe to delete from your locale file.
- `Stale in fr: <key> (current en: "...")` — the English value changed since you last ran `--update-snapshot`. Your translation may now be out of date.

Once you've reviewed and translated, refresh your baseline:

```bash
php artisan locale:diff fr --update-snapshot
```

### Machine-readable output

For tooling (CI checks, IDE plugins, translation dashboards), pass `--format=json`:

```bash
php artisan locale:diff fr --format=json
```

Schema:

```jsonc
{
  "locale": "fr",
  "missing":  [{ "key": "filament/resources/foo.bar", "kind": "php" }],
  "orphaned": [{ "key": "Sign out", "kind": "json" }],
  "stale":    [{ "key": "filament/resources/company.label", "kind": "php", "current_en_value": "company" }]
}
```

Exit code is 0 when all three lists are empty, 1 otherwise.

## Locale codes

We follow ISO 15897 — underscores, not hyphens. Use `pt_BR`, `zh_CN`, `en_GB`. Mismatches break Carbon and PHP's `intl` silently.

## Plural forms

Laravel's pipe syntax (`{0} none|[1,*] some`) handles English-style plurals. For Slavic languages with multiple plural forms (Polish: one/few/many/other), use ICU `MessageFormatter` patterns in JSON values. The `ext-intl` extension is required (already in `composer.json`).

## Out of scope

These are **not** translated and probably shouldn't be:

- Marketing pages (homepage, pricing, contact, public landing pages) — translation breaks SEO without per-locale routing
- Exception messages and log output — internal/dev-facing
- Validation rule messages already covered by Laravel's shipped translations (override only what we customize via `lang/vendor/`)

## Distribution

Relaticle does not operate a Crowdin/Weblate hub. If your fork develops well-maintained translations and you want to share them with the community, open a discussion on GitHub — we'll point future translators at your repo.

## CI guardrails

A pair of custom Larastan rules (`HardcodedUserFacingStringRule`, `HardcodedStaticPropertyRule`) prevents new code from introducing hardcoded user-facing strings in Filament components and class properties.

Missing-key typos (e.g. `__('typo.key')` referencing a key that doesn't exist) are not caught by static analysis — they surface as the literal key string in the rendered UI during QA.

If `composer test:types` flags i18n violations, fix them before merging — do not add to the baseline.

## Example fixture

`lang/fr/filament/resources/company.php` is committed as a working example of how a fork's translation file looks. It demonstrates the override path end-to-end and is verified by `tests/Browser/I18nCompanyResourceTest.php`.
