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
12 changes: 12 additions & 0 deletions docs/localization.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,18 @@ source hash attached to every translated value. If English copy changes, the
old translation fails the check instead of silently looking current. Missing
translations remain allowed because the runtime has an English fallback.

## Mobile apps

The iOS app catalog, `ios/App/Localizable.xcstrings`, ships every key
translated, not only in English: a missing key shows up in English on a phone
set to another language. It carries every language the in-app picker offers
(`ios/App/AppLanguage.swift`: pt-BR, zh-Hans and zh-Hant) for every key, and
`scripts/ios-strings.test.mjs` fails, naming the keys, when one lacks a
translated value or a translation's format arguments differ from the English.
The widget catalog, `ios/Widgets/Localizable.xcstrings`, is English-only for
now and is not checked. Android's `values-b+zh+Hans` and `values-b+zh+Hant`
match `values/strings.xml` key for key, checked by `ChineseLocalizationTest`.

## Optional model-assisted draft

The repository includes a maintainer tool that sends missing or stale English
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 6 additions & 5 deletions ios/App/AppLanguage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
//
// English is the source catalog: every key in `Localizable.xcstrings` *is* the
// English literal from the Swift, so a key a translation omits falls back to
// copy that reads correctly rather than to a key name. A partial pack is a
// usable pack, which is the same contract `docs/localization.md` sets for the
// renderer.
// copy that reads correctly rather than to a key name. That fallback is a
// safety net, not the contract: unlike the renderer, the app ships every key
// in every language, and `scripts/ios-strings.test.mjs` reads the cases below
// and fails on a key one of them lacks (see `docs/localization.md`).
//
// Adding a language is one case here and one column in the catalog. Nothing
// else in the app has to know.
// Adding a language is one case here and one complete column in the catalog.
// Nothing else in the app has to know.
import Foundation
import SwiftUI

Expand Down
28 changes: 15 additions & 13 deletions ios/App/LiveCallBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ struct RemoteLiveCallBar: View {
.accessibilityLabel("Hang up")
.accessibilityIdentifier("live-call-remote-hangup")
}
Text(LiveCallNotice.fromDevice(call.client))
LiveCallNotice.fromDevice(call.client)
.font(.system(size: 13))
.foregroundStyle(LiveCallColor.detail)
.lineLimit(1)
Expand Down Expand Up @@ -378,7 +378,7 @@ extension LiveCallNotice {
case .needsKey:
return Text("Set up Live calls on your computer first.")
case let .busy(client, botName):
return Text("\(Self.deviceName(client)) is on a call with \(botName ?? String(localized: "a bot")).")
return Text("\(Self.deviceName(client)) is on a call with \(botName.map { Text(verbatim: $0) } ?? Text("a bot")).")
case let .unreachable(detail):
return Text("Can't reach your computer. \(detail)")
case let .refused(message):
Expand Down Expand Up @@ -416,25 +416,27 @@ extension LiveCallNotice {
}

/// "Your computer", for the start of a sentence. Not "your Mac": the
/// harness runs on Linux too.
static func deviceName(_ client: String) -> String {
/// harness runs on Linux too. A `Text`, not a `String(localized:)`, so it
/// follows the in-app language like the sentence it sits in; a String
/// would follow the phone's and mix two languages in one line.
static func deviceName(_ client: String) -> Text {
switch client {
case "desktop": return String(localized: "Your computer")
case "ios": return String(localized: "An iPhone")
case "android": return String(localized: "An Android phone")
default: return String(localized: "Another device")
case "desktop": return Text("Your computer")
case "ios": return Text("An iPhone")
case "android": return Text("An Android phone")
default: return Text("Another device")
}
}

/// Where a call another device holds is: the remote bar's second line.
/// The desktop's remote bar says it the same way ("Pepper is on a Live
/// call from an iPhone"), and so does Android's.
static func fromDevice(_ client: String) -> String {
static func fromDevice(_ client: String) -> Text {
switch client {
case "desktop": return String(localized: "From your computer")
case "ios": return String(localized: "From an iPhone")
case "android": return String(localized: "From an Android phone")
default: return String(localized: "From another device")
case "desktop": return Text("From your computer")
case "ios": return Text("From an iPhone")
case "android": return Text("From an Android phone")
default: return Text("From another device")
}
}
}
Loading
Loading