Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion config/docs.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
1 => ['1.0', '1.1'],
2 => ['2.0'],
3 => ['3.0', '3.1', '3.2', '3.3'],
4 => ['4.0', '4.1', '4.2', '4.3', '4.4', '4.5'],
4 => ['4.0', '4.1', '4.2', '4.3', '4.4', '4.5', '4.6'],
],
],

Expand Down
144 changes: 144 additions & 0 deletions resources/views/docs/mobile/4/digging-deeper/localization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
title: Localization
order: 95
---

## Overview

<x-docs.version-badge since="4.6" />

Tell NativePHP which languages your app supports and your users can pick one for your app, separately from the
language of their device. On Android your app appears in the system's per-app language picker. On iOS it gets a
Preferred Language setting, and the App Store lists those languages on your product page.

You declare the languages in one config key. Translating your app still happens the Laravel way, with `lang/` files
and `__()`.

## Declaring your languages

List the languages in the `supported_locales` array in `config/nativephp.php`:

```php
'supported_locales' => ['fr', 'nl', 'zh-Hans'],
```

Your app's own language, `config('app.locale')`, is always included and always comes first, so users can switch back
to it. You don't list it yourself. If `app.locale` is `en`, the config above gives your app four languages: `en`,
`fr`, `nl` and `zh-Hans`.

- Entries are BCP 47 codes such as `nl`, `pt-BR` or `zh-Hans`. Laravel-style `nl_NL` is accepted and becomes `nl-NL`.
- Duplicates are dropped, ignoring case.
- Invalid entries are skipped with a [build warning](#build-warnings).
- An empty array means your app supports one language, `app.locale`.

<aside>

The list is read at build time. The base language of a build is whatever `APP_LOCALE` was on the machine that built
it.

</aside>

## What your users see

### Android

On Android 13 and later, your app appears in the system's per-app language picker under Settings → Apps → your app →
Language. Before 4.6, a NativePHP app could only follow the system language.

To do this, NativePHP writes `res/xml/locales_config.xml` and points your app's manifest at it. This only happens when
your app supports two or more languages. With one language the app is left untouched and no Language entry shows.

### iOS

NativePHP declares the languages as `CFBundleLocalizations` in your app's `Info.plist`. Your app then gets a Preferred
Language row under Settings → your app.

<aside>

iOS only shows the Preferred Language row when the device has more than one preferred language set under
Settings → General → Language & Region. If the row is missing while you test, add a second language there.

</aside>

### App Store

The App Store lists exactly these languages on your app's product page.

## Translating your app

Listing a locale only lets the user pick it. Translating your app is still your job. Add
[Laravel language files](https://laravel.com/docs/localization) as you would in any Laravel app, either
`lang/fr/...` or `lang/fr.json`, and use `__()` for your strings.

## Using the chosen language

NativePHP doesn't set Laravel's locale for you. The language the user picked is reported by
[`Device::getInfo()`](../the-basics/device#device-info) in the `language` field, as a BCP 47 tag such as `fr-FR`.

Read it early, for example in `AppServiceProvider::boot()`, and pass it to `App::setLocale()`:

```php
use Illuminate\Support\Facades\App;
use Native\Mobile\Facades\Device;

// AppServiceProvider::boot()
$info = Device::getInfo(); // JSON string, or null when not on a device

if ($info !== null) {
$language = json_decode($info, true)['language'] ?? ''; // 'fr-FR'
$locale = explode('-', $language)[0]; // 'fr'

if (in_array($locale, ['fr', 'nl'], true)) {
App::setLocale($locale);
}
}
```

The tag includes a region (`fr-FR`), while a Laravel `lang/` folder is usually just `fr`, so the example keeps only the
language part. It also only switches to a locale the app has translations for. Anything else stays on `app.locale`.

<aside>

`boot()` runs once each time PHP starts. iOS quits your app when the user changes its language, so `boot()` runs again
when they reopen it. Android only rebuilds the screen and, with the default
[persistent runtime](../getting-started/configuration#persistent-runtime), keeps PHP running. There, a change made
while your app is open applies the next time the app starts.

</aside>

## Permission strings

On iOS, translated permission strings are only written for the languages your app supports. That covers your own
`permission_localizations` and the translations your plugins ship. Entries for any other locale are skipped.

A plugin can bring translations for its permission strings, but it can't add a language to your app. See
[Localizing iOS Permission Strings](../getting-started/configuration#localizing-ios-permission-strings).

## Build warnings

NativePHP prints these warnings while it builds your app, during `native:run` and when you package it.

```
Translations exist for de but nativephp.supported_locales does not list them, so the app will not offer them
```

Your `lang/` directory has a folder or `.json` file for a locale that isn't in `supported_locales`. Add the locale to
the list if you want users to be able to pick it. `lang/vendor` is ignored.

NativePHP doesn't scan `lang/` to decide which languages ship. The list stays explicit, so a translation you've only
just started isn't offered to users by accident.

```
Ignoring invalid locale 'english' in nativephp.supported_locales
```

An entry in `supported_locales` isn't a locale code. The entry is skipped and the rest of the list is used.

## Removing a language

Take the locale out of `supported_locales` and rebuild. You don't need to run `native:install --force`.

- **Android:** the locale is removed from the locale config. If only one language is left, NativePHP deletes the locale
config and takes the attribute back out of the manifest.
- **iOS:** `CFBundleLocalizations` is rewritten and the `.lproj` folder NativePHP generated for that language is
deleted. An `.lproj` folder holding anything besides the generated `InfoPlist.strings` is left alone.
33 changes: 33 additions & 0 deletions resources/views/docs/mobile/4/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,30 @@ Set your Apple Developer Team ID for code signing:
This is typically detected from your installed certificates, but you can override it here. Find your Team ID
in your Apple Developer account under Membership details.

## Supported Locales

<x-docs.version-badge since="4.6" />

The `supported_locales` array lists the languages your app supports:

```php
'supported_locales' => ['fr', 'nl', 'zh-Hans'],
```

On Android 13 and later, this puts your app in the system's per-app language picker. On iOS, it gives your app a
Preferred Language setting and decides which languages the App Store lists on your product page.

- `config('app.locale')` is always included and always first, so users can switch back to it. You don't list it
yourself.
- Entries are BCP 47 codes (`nl`, `pt-BR`, `zh-Hans`). Laravel-style `nl_NL` is accepted and becomes `nl-NL`.
- Duplicates are dropped, ignoring case. Invalid entries are skipped with a build warning.
- An empty array means your app supports one language, `app.locale`.
- The list is read at build time, so the base language of a build is whatever `APP_LOCALE` was on the machine that
built it.

Listing a locale only lets the user pick it. Translating your app is still up to you and your `lang/` files. See
[Localization](../digging-deeper/localization) for the full guide.

## iOS Permission Strings

Plugins declare their own iOS `Info.plist` usage descriptions through their manifests (see
Expand Down Expand Up @@ -343,6 +367,8 @@ are shown at runtime by app code, so there's no equivalent override.

## Localizing iOS Permission Strings

<x-docs.version-badge changed="4.6" />

The strings in `permissions` go straight into `Info.plist`, which iOS treats as the **development region**
fallback. Users running their device in another language see those same strings unless you ship a localized
override.
Expand All @@ -351,6 +377,8 @@ Add per-locale strings under `permission_localizations`. Each key is a BCP 47 lo
(e.g. `nl`, `fr`, `zh-Hans`, `pt-BR`) and its value mirrors the `permissions` shape:

```php
'supported_locales' => ['nl', 'fr'],

'permissions' => [
'NSCameraUsageDescription' => 'Used to take a profile photo.',
],
Expand All @@ -369,6 +397,9 @@ At build time NativePHP writes one `{locale}.lproj/InfoPlist.strings` file per l
and registers the locale with the Xcode project so it ships with the app. iOS then picks the right string
at runtime based on the user's preferred language, falling back to the value in `permissions`.

Strings are only written for locales your app supports: the ones in [`supported_locales`](#supported-locales), plus
`app.locale`. Entries for any other locale are skipped.

<aside>

You only need to localize the keys that change between languages. Any `NS*UsageDescription` not listed in
Expand All @@ -380,6 +411,8 @@ Plugins can ship their own per-locale strings — see
[Permissions & Dependencies](../plugins/permissions-dependencies#localizing-infoplist-strings) — and
app-level entries always win on key collisions, same as the merge rules for flat `permissions`.

Plugins bring translations, not languages. A plugin's strings for a locale your app doesn't support are skipped too.

## App Store Connect

Configure automated iOS uploads with the App Store Connect API:
Expand Down
28 changes: 28 additions & 0 deletions resources/views/docs/mobile/4/getting-started/upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,34 @@ title: Upgrade Guide
order: 3
---

## Upgrading To 4.6 From 4.5

### Translated iOS permission strings need `supported_locales`

Before 4.6, any locale in `permission_localizations`, or in a plugin's `info_plist_localizations`, shipped with your
iOS app automatically. From 4.6, translated permission strings are only written for locales your app supports: the
ones in the new `supported_locales` config key, plus `config('app.locale')`.

If your app relies on translated permission strings, add those locales to `config/nativephp.php`. A config file
published before 4.6 won't have the key yet:

```php
'supported_locales' => ['nl', 'fr'], // [tl! add]

'permission_localizations' => [
'nl' => [
'NSCameraUsageDescription' => 'Gebruikt om een profielfoto te maken.',
],
'fr' => [
'NSCameraUsageDescription' => 'Utilisé pour prendre une photo de profil.',
],
],
```

The upside is that an English-only app that installs a well-translated plugin no longer advertises all of that
plugin's languages on the App Store. See [Localization](../digging-deeper/localization) for everything
`supported_locales` does.

## Upgrading To 4.5 From 4.4

### Public releases need `APP_ENV=production`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ rejection. Explain *why* you need the permission.

### Localizing Info.plist Strings

<x-docs.version-badge changed="4.6" />

Values in `ios.info_plist` are written to the bundle's `Info.plist` and shown by iOS in whichever language
they were authored. To translate them for users on other system languages, add a sibling
`ios.info_plist_localizations` block. Each key is a BCP 47 locale code (e.g. `nl`, `fr`, `zh-Hans`,
Expand Down Expand Up @@ -111,6 +113,13 @@ At build time NativePHP writes one `{locale}.lproj/InfoPlist.strings` file per l
iOS project, and iOS picks the right string at runtime based on the user's preferred language. Any key not
present in a locale block falls back to the value from `info_plist`.

The host app decides which languages it supports, through its
[`supported_locales`](../getting-started/configuration#supported-locales) config. Your plugin's strings are only
written for those locales, and a locale the app doesn't support is skipped. Your plugin brings translations, not
languages.

For the same reason, a `CFBundleLocalizations` key in your `ios.info_plist` is ignored.

App-level overrides set in the host app's `config('nativephp.permission_localizations')` win over the values
declared here — useful when an app developer needs to resolve collisions between plugins that ship
localizations for the same key.
Expand Down
Loading