mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 10:29:58 +02:00
feat(l10n): replace legacy %@ format keys with semantic template keys
The Apple catalog carried four stringly-named passthrough keys ("%@",
"%@ · %@", "%@ %@ · %@", "%@%%") left over from the KMP port. They were
never referenced as keys — the composite strings were built inline with
hardcoded separators, so the middot/percent formatting wasn't localizable.
Renames them to proper semantic keys in strings.json:
- format_separated_pair(first:second:) "{first} · {second}"
- format_separated_triple(first:second:third:)
- battery_level_value(level:) "{level}%"
and drops the pure-identity "%@". Wires the inline compositions (size ·
status, count files · size, receivers pending · completed, battery level)
to the generated typed accessors. Catalog now validates with zero warnings.
This commit is contained in:
@@ -111,7 +111,10 @@ struct InvitationReviewPanel: View {
|
||||
let metadata = inspection.metadata
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(metadata.transferName).font(VniType.bodyLarge).lineLimit(2)
|
||||
Text("\(metadata.fileCount) \(String(localized: L10n.Metadata.files).lowercased()) · \(formatBytes(metadata.totalSize))")
|
||||
Text(L10n.Format.separatedTriple(
|
||||
first: "\(metadata.fileCount)",
|
||||
second: String(localized: L10n.Metadata.files).lowercased(),
|
||||
third: formatBytes(metadata.totalSize)))
|
||||
.foregroundStyle(colors.foregroundLighter)
|
||||
}
|
||||
.padding(16)
|
||||
|
||||
@@ -137,7 +137,7 @@ private struct ReceiveTransferRow: View {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
Text(transfer.transferName ?? String(localized: L10n.Receive.unknownTransfer))
|
||||
.font(.body).lineLimit(1)
|
||||
Text("\(formatBytes(transfer.totalSize)) · \(statusLabel(transfer.status))")
|
||||
Text(L10n.Format.separatedPair(first: formatBytes(transfer.totalSize), second: statusLabel(transfer.status)))
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
if transfer.status == .receiving, let progress {
|
||||
ProgressRow(labelKey: progress.labelKey, progress: progress.progress, detail: progress.detail)
|
||||
|
||||
@@ -160,7 +160,7 @@ private struct TransferListItem: View {
|
||||
Spacer()
|
||||
StatusPill(label: statusLabel(transfer.status), tone: transfer.status.pillTone)
|
||||
}
|
||||
Text("\(formatBytes(transfer.totalSize)) · \(accessPolicyLabel(transfer.accessPolicy))")
|
||||
Text(L10n.Format.separatedPair(first: formatBytes(transfer.totalSize), second: accessPolicyLabel(transfer.accessPolicy)))
|
||||
.font(.caption).foregroundStyle(.secondary).lineLimit(1)
|
||||
if let progress, transfer.status == .importing || transfer.status == .sharing {
|
||||
ProgressRow(labelKey: progress.labelKey, progress: progress.progress, detail: progress.detail, labelText: progress.label)
|
||||
|
||||
@@ -91,7 +91,9 @@ struct TransferDetailsView: View {
|
||||
|
||||
private func receiversDescription(_ pending: Int, _ completed: Int) -> String {
|
||||
if pending > 0 && completed > 0 {
|
||||
return "\(L10n.Transfer.receiversPending(count: pending)) · \(L10n.Transfer.receiversCompletedCount(count: completed))"
|
||||
return L10n.Format.separatedPair(
|
||||
first: L10n.Transfer.receiversPending(count: pending),
|
||||
second: L10n.Transfer.receiversCompletedCount(count: completed))
|
||||
}
|
||||
if pending > 0 { return L10n.Transfer.receiversPending(count: pending) }
|
||||
if completed > 0 { return L10n.Transfer.receiversCompletedCount(count: completed) }
|
||||
|
||||
@@ -514,6 +514,23 @@ enum L10n {
|
||||
/// - nl: Batterijniveau
|
||||
/// - ru: Уровень заряда
|
||||
static let levelTitle: String.LocalizationValue = "battery_level_title"
|
||||
/// {level}%
|
||||
///
|
||||
/// Key: `battery_level_value`
|
||||
/// Context: Device information: battery charge formatted as a percentage. {level} = integer percent value.
|
||||
///
|
||||
/// - en: {level}%
|
||||
/// - fr: {level} %
|
||||
/// - es: {level} %
|
||||
/// - it: {level}%
|
||||
/// - de: {level} %
|
||||
/// - pt: {level}%
|
||||
/// - pl: {level}%
|
||||
/// - nl: {level}%
|
||||
/// - ru: {level} %
|
||||
static func levelValue(level: String) -> String {
|
||||
String(format: String(localized: "battery_level_value"), level)
|
||||
}
|
||||
}
|
||||
enum Bug {
|
||||
/// name@example.com
|
||||
@@ -1684,6 +1701,42 @@ enum L10n {
|
||||
/// - ru: Готово
|
||||
static let statusWritable: String.LocalizationValue = "folder_status_writable"
|
||||
}
|
||||
enum Format {
|
||||
/// {first} · {second}
|
||||
///
|
||||
/// Key: `format_separated_pair`
|
||||
/// Context: Composes two already-localized values with a middot separator (e.g. size · status). {first} and {second} are the two values.
|
||||
///
|
||||
/// - en: {first} · {second}
|
||||
/// - fr: {first} · {second}
|
||||
/// - es: {first} · {second}
|
||||
/// - it: {first} · {second}
|
||||
/// - de: {first} · {second}
|
||||
/// - pt: {first} · {second}
|
||||
/// - pl: {first} · {second}
|
||||
/// - nl: {first} · {second}
|
||||
/// - ru: {first} · {second}
|
||||
static func separatedPair(first: String, second: String) -> String {
|
||||
String(format: String(localized: "format_separated_pair"), first, second)
|
||||
}
|
||||
/// {first} {second} · {third}
|
||||
///
|
||||
/// Key: `format_separated_triple`
|
||||
/// Context: Composes three already-localized values, the first two space-joined then a middot before the third (e.g. count files · size). {first}, {second}, {third} are the values.
|
||||
///
|
||||
/// - en: {first} {second} · {third}
|
||||
/// - fr: {first} {second} · {third}
|
||||
/// - es: {first} {second} · {third}
|
||||
/// - it: {first} {second} · {third}
|
||||
/// - de: {first} {second} · {third}
|
||||
/// - pt: {first} {second} · {third}
|
||||
/// - pl: {first} {second} · {third}
|
||||
/// - nl: {first} {second} · {third}
|
||||
/// - ru: {first} {second} · {third}
|
||||
static func separatedTriple(first: String, second: String, third: String) -> String {
|
||||
String(format: String(localized: "format_separated_triple"), first, second, third)
|
||||
}
|
||||
}
|
||||
enum Metadata {
|
||||
/// Files
|
||||
///
|
||||
|
||||
@@ -36,7 +36,7 @@ private struct IosDeviceInfoProvider: DeviceInfoProvider {
|
||||
device.isBatteryMonitoringEnabled = true
|
||||
defer { device.isBatteryMonitoringEnabled = wasMonitoring }
|
||||
let level = device.batteryLevel
|
||||
return level >= 0 ? "\(Int(level * 100))%" : nil
|
||||
return level >= 0 ? L10n.Battery.levelValue(level: "\(Int(level * 100))") : nil
|
||||
}()
|
||||
return DeviceInfo(
|
||||
deviceName: device.name,
|
||||
|
||||
@@ -1,134 +1,6 @@
|
||||
{
|
||||
"sourceLanguage": "en",
|
||||
"strings": {
|
||||
"%@": {
|
||||
"comment": "Apple format passthrough placeholder (single value). Legacy literal key — rename to a semantic key.",
|
||||
"extractionState": "manual"
|
||||
},
|
||||
"%@ %@ · %@": {
|
||||
"comment": "Apple format template composing three values with a middot separator (e.g. metadata rows). Legacy literal key — rename.",
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@ · %@": {
|
||||
"comment": "Apple format template composing two values with a middot separator. Legacy literal key — rename.",
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"%@%%": {
|
||||
"comment": "Apple format template appending a percent sign to a value (e.g. battery level). Legacy literal key — rename.",
|
||||
"extractionState": "manual"
|
||||
},
|
||||
"about_bug_report": {
|
||||
"comment": "About screen: button/link that opens the bug report form.",
|
||||
"extractionState": "manual",
|
||||
@@ -2109,6 +1981,66 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"battery_level_value": {
|
||||
"comment": "Device information: battery charge formatted as a percentage. {level} = integer percent value.",
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %%"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%1$@%%"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %%"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %%"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@%%"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@%%"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@%%"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@%%"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bug_report_contact_hint": {
|
||||
"comment": "Bug report form: placeholder text in the contact email field.",
|
||||
"extractionState": "manual",
|
||||
@@ -6729,6 +6661,126 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"format_separated_pair": {
|
||||
"comment": "Composes two already-localized values with a middot separator (e.g. size · status). {first} and {second} are the two values.",
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ · %2$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"format_separated_triple": {
|
||||
"comment": "Composes three already-localized values, the first two space-joined then a middot before the third (e.g. count files · size). {first}, {second}, {third} are the values.",
|
||||
"extractionState": "manual",
|
||||
"localizations": {
|
||||
"de": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"en": {
|
||||
"stringUnit": {
|
||||
"state": "translated",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"es": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"fr": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"it": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"nl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"pl": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"pt": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
},
|
||||
"ru": {
|
||||
"stringUnit": {
|
||||
"state": "needs_review",
|
||||
"value": "%1$@ %2$@ · %3$@"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"metadata_files": {
|
||||
"comment": "Transfer metadata label: number of files.",
|
||||
"extractionState": "manual",
|
||||
|
||||
@@ -13,76 +13,6 @@
|
||||
"ru"
|
||||
],
|
||||
"strings": {
|
||||
"%@": {
|
||||
"context": "Apple format passthrough placeholder (single value). Legacy literal key — rename to a semantic key.",
|
||||
"targets": [
|
||||
"apple"
|
||||
]
|
||||
},
|
||||
"%@ %@ · %@": {
|
||||
"context": "Apple format template composing three values with a middot separator (e.g. metadata rows). Legacy literal key — rename.",
|
||||
"targets": [
|
||||
"apple"
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "arg1",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "arg2",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "arg3",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"translations": {
|
||||
"en": "{arg1} {arg2} · {arg3}",
|
||||
"fr": "{arg1} {arg2} · {arg3}",
|
||||
"es": "{arg1} {arg2} · {arg3}",
|
||||
"it": "{arg1} {arg2} · {arg3}",
|
||||
"de": "{arg1} {arg2} · {arg3}",
|
||||
"pt": "{arg1} {arg2} · {arg3}",
|
||||
"pl": "{arg1} {arg2} · {arg3}",
|
||||
"nl": "{arg1} {arg2} · {arg3}",
|
||||
"ru": "{arg1} {arg2} · {arg3}"
|
||||
}
|
||||
},
|
||||
"%@ · %@": {
|
||||
"context": "Apple format template composing two values with a middot separator. Legacy literal key — rename.",
|
||||
"targets": [
|
||||
"apple"
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "arg1",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "arg2",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"translations": {
|
||||
"en": "{arg1} · {arg2}",
|
||||
"fr": "{arg1} · {arg2}",
|
||||
"es": "{arg1} · {arg2}",
|
||||
"it": "{arg1} · {arg2}",
|
||||
"de": "{arg1} · {arg2}",
|
||||
"pt": "{arg1} · {arg2}",
|
||||
"pl": "{arg1} · {arg2}",
|
||||
"nl": "{arg1} · {arg2}",
|
||||
"ru": "{arg1} · {arg2}"
|
||||
}
|
||||
},
|
||||
"%@%%": {
|
||||
"context": "Apple format template appending a percent sign to a value (e.g. battery level). Legacy literal key — rename.",
|
||||
"targets": [
|
||||
"apple"
|
||||
]
|
||||
},
|
||||
"about_bug_report": {
|
||||
"context": "About screen: button/link that opens the bug report form.",
|
||||
"translations": {
|
||||
@@ -567,6 +497,29 @@
|
||||
"ru": "Уровень заряда"
|
||||
}
|
||||
},
|
||||
"battery_level_value": {
|
||||
"context": "Device information: battery charge formatted as a percentage. {level} = integer percent value.",
|
||||
"targets": [
|
||||
"apple"
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "level",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"translations": {
|
||||
"en": "{level}%",
|
||||
"fr": "{level} %",
|
||||
"es": "{level} %",
|
||||
"it": "{level}%",
|
||||
"de": "{level} %",
|
||||
"pt": "{level}%",
|
||||
"pl": "{level}%",
|
||||
"nl": "{level}%",
|
||||
"ru": "{level} %"
|
||||
}
|
||||
},
|
||||
"bug_report_contact_hint": {
|
||||
"context": "Bug report form: placeholder text in the contact email field.",
|
||||
"translations": {
|
||||
@@ -1645,6 +1598,64 @@
|
||||
"ru": "Готово"
|
||||
}
|
||||
},
|
||||
"format_separated_pair": {
|
||||
"context": "Composes two already-localized values with a middot separator (e.g. size · status). {first} and {second} are the two values.",
|
||||
"targets": [
|
||||
"apple"
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "first",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "second",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"translations": {
|
||||
"en": "{first} · {second}",
|
||||
"fr": "{first} · {second}",
|
||||
"es": "{first} · {second}",
|
||||
"it": "{first} · {second}",
|
||||
"de": "{first} · {second}",
|
||||
"pt": "{first} · {second}",
|
||||
"pl": "{first} · {second}",
|
||||
"nl": "{first} · {second}",
|
||||
"ru": "{first} · {second}"
|
||||
}
|
||||
},
|
||||
"format_separated_triple": {
|
||||
"context": "Composes three already-localized values, the first two space-joined then a middot before the third (e.g. count files · size). {first}, {second}, {third} are the values.",
|
||||
"targets": [
|
||||
"apple"
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "first",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "second",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "third",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"translations": {
|
||||
"en": "{first} {second} · {third}",
|
||||
"fr": "{first} {second} · {third}",
|
||||
"es": "{first} {second} · {third}",
|
||||
"it": "{first} {second} · {third}",
|
||||
"de": "{first} {second} · {third}",
|
||||
"pt": "{first} {second} · {third}",
|
||||
"pl": "{first} {second} · {third}",
|
||||
"nl": "{first} {second} · {third}",
|
||||
"ru": "{first} {second} · {third}"
|
||||
}
|
||||
},
|
||||
"metadata_files": {
|
||||
"context": "Transfer metadata label: number of files.",
|
||||
"translations": {
|
||||
|
||||
Reference in New Issue
Block a user