diff --git a/.rubocop.yml b/.rubocop.yml index fd109537d10c..a542feabfe99 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -27,6 +27,10 @@ Layout/EmptyLines: Style/AsciiComments: Exclude: *xfiles +Style/Documentation: + Exclude: + - '**/*_test.rb' + Naming/FileName: Exclude: - fastlane/Matchfile diff --git a/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SelectSiteIntent.swift b/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SelectSiteIntent.swift index a2cafa9a4b81..43f337f64dc9 100644 --- a/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SelectSiteIntent.swift +++ b/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SelectSiteIntent.swift @@ -8,28 +8,38 @@ import AppIntents /// users' widgets to the default site. /// /// The localization keys are the app-bundle names of the identifiers Xcode generated for the -/// legacy `.intentdefinition` ("gpCwrM", "ILcGmf"): GlotPress uploads them under the -/// `ios-widget.` prefix, and the downloaded translations land in the app's -/// `Localizable.strings` under those prefixed keys. +/// legacy `.intentdefinition` ("gpCwrM", "ILcGmf"): The code freeze extracts these call sites +/// into the GlotPress upload (`generate_app_intents_strings_for_glotpress`), and the downloaded +/// translations land in the app's `Localizable.strings` under the prefixed keys. /// -/// The keys must resolve in two bundles, because the OS picks a different one depending on -/// version (verified on simulators): iOS 26 resolves the widget configuration UI's strings -/// against the app bundle (whose GlotPress-managed `Localizable.strings` carries the prefixed -/// keys in every locale), while iOS 17 resolves against the widget extension bundle, which -/// ships static copies of the same prefixed keys in -/// `Sources/JetpackStatsWidgets/Resources/.lproj/Localizable.strings`. -/// A nested SPM package resource bundle is never consulted, so the strings cannot live in -/// this package. +/// iOS 18 and later resolve the widget configuration UI's strings against the app bundle, +/// whose GlotPress-managed `Localizable.strings` carries the prefixed keys in every locale +/// (verified on simulators; a nested SPM package resource bundle is never consulted). +/// iOS 17 resolves against the widget extension bundle only, with no fallback to the app +/// bundle, so it shows the English default values; we accept English there rather than +/// shipping per-locale copies in the extension. public struct SelectSiteIntent: WidgetConfigurationIntent, CustomIntentMigratedAppIntent { public static let intentClassName = "SelectSiteIntent" - public static let title = LocalizedStringResource("ios-widget.gpCwrM", defaultValue: "Select Site") + public static let title = LocalizedStringResource( + "ios-widget.gpCwrM", + defaultValue: "Select Site", + comment: + "This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for" + ) // The legacy intent was ineligible for Siri suggestions; keep this // configuration-only intent out of Shortcuts and Spotlight the same way. public static let isDiscoverable = false - @Parameter(title: LocalizedStringResource("ios-widget.ILcGmf", defaultValue: "Site")) + @Parameter( + title: LocalizedStringResource( + "ios-widget.ILcGmf", + defaultValue: "Site", + comment: + "This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for" + ) + ) public var site: SiteEntity? public init() {} diff --git a/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SiteEntity.swift b/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SiteEntity.swift index cf5dbaf3dd9f..5a7d5dbcb88c 100644 --- a/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SiteEntity.swift +++ b/Modules/Sources/JetpackStatsWidgetsCore/AppIntents/SiteEntity.swift @@ -7,11 +7,16 @@ import Foundation /// the `identifier` of the legacy SiriKit `Site` object so that widget configurations created /// before the App Intents migration keep resolving to the same site. /// -/// The "ios-widget.ILcGmf" localization key resolves against the app bundle on iOS 26 and -/// the widget extension bundle on iOS 17; see `SelectSiteIntent` for the details. +/// The "ios-widget.ILcGmf" localization key resolves against the app bundle on iOS 18 and +/// later; see `SelectSiteIntent` for the details. public struct SiteEntity: AppEntity { public static let typeDisplayRepresentation = TypeDisplayRepresentation( - name: LocalizedStringResource("ios-widget.ILcGmf", defaultValue: "Site") + name: LocalizedStringResource( + "ios-widget.ILcGmf", + defaultValue: "Site", + comment: + "This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for" + ) ) public static var defaultQuery: SiteEntityQuery { SiteEntityQuery() } diff --git a/Sources/JetpackStatsWidgets/Resources/ar.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/ar.lproj/Localizable.strings deleted file mode 100644 index a8f732c074ed..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/ar.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "تحديد موقع"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "الموقع"; diff --git a/Sources/JetpackStatsWidgets/Resources/bg.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/bg.lproj/Localizable.strings deleted file mode 100644 index 552c82bf3b20..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/bg.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Сайт"; diff --git a/Sources/JetpackStatsWidgets/Resources/cs.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/cs.lproj/Localizable.strings deleted file mode 100644 index 325ea1843745..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/cs.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Vybrat web"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Web"; diff --git a/Sources/JetpackStatsWidgets/Resources/cy.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/cy.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/cy.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/da.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/da.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/da.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/de.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/de.lproj/Localizable.strings deleted file mode 100644 index c5767a6451cf..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/de.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Website auswählen"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Website"; diff --git a/Sources/JetpackStatsWidgets/Resources/en-AU.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/en-AU.lproj/Localizable.strings deleted file mode 100644 index 6e01cd7104f1..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/en-AU.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/en-CA.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/en-CA.lproj/Localizable.strings deleted file mode 100644 index 6e01cd7104f1..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/en-CA.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/en-GB.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/en-GB.lproj/Localizable.strings deleted file mode 100644 index 6e01cd7104f1..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/en-GB.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/en.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/en.lproj/Localizable.strings deleted file mode 100644 index 6e01cd7104f1..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/en.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/es.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/es.lproj/Localizable.strings deleted file mode 100644 index 527eaa94847a..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/es.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Seleccionar sitio"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Sitio"; diff --git a/Sources/JetpackStatsWidgets/Resources/fr.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/fr.lproj/Localizable.strings deleted file mode 100644 index e6080f30a845..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/fr.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Sélectionnez le site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/he.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/he.lproj/Localizable.strings deleted file mode 100644 index ee7bf6f59ebf..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/he.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "יש לבחור אתר"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "אתר"; diff --git a/Sources/JetpackStatsWidgets/Resources/hr.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/hr.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/hr.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/hu.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/hu.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/hu.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/id.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/id.lproj/Localizable.strings deleted file mode 100644 index c62842496db1..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/id.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Pilih Situs"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Situs"; diff --git a/Sources/JetpackStatsWidgets/Resources/is.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/is.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/is.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/it.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/it.lproj/Localizable.strings deleted file mode 100644 index c5104f566177..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/it.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Seleziona sito"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Sito"; diff --git a/Sources/JetpackStatsWidgets/Resources/ja.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/ja.lproj/Localizable.strings deleted file mode 100644 index 90f0604bfdb2..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/ja.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "サイトを選択"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "サイト"; diff --git a/Sources/JetpackStatsWidgets/Resources/ko.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/ko.lproj/Localizable.strings deleted file mode 100644 index b81570c41edb..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/ko.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "사이트 선택"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "사이트"; diff --git a/Sources/JetpackStatsWidgets/Resources/nb.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/nb.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/nb.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/nl.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/nl.lproj/Localizable.strings deleted file mode 100644 index 81677301fb0c..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/nl.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Selecteer site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/pl.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/pl.lproj/Localizable.strings deleted file mode 100644 index 5e6e647d8cca..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/pl.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Witryna"; diff --git a/Sources/JetpackStatsWidgets/Resources/pt-BR.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/pt-BR.lproj/Localizable.strings deleted file mode 100644 index b2b742328c8d..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/pt-BR.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Selecionar site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/pt.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/pt.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/pt.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/ro.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/ro.lproj/Localizable.strings deleted file mode 100644 index bd59551e5426..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/ro.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Selectează site-ul"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/ru.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/ru.lproj/Localizable.strings deleted file mode 100644 index 4be547f037ce..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/ru.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Выбрать сайт"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Сайт"; diff --git a/Sources/JetpackStatsWidgets/Resources/sk.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/sk.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/sk.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/sq.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/sq.lproj/Localizable.strings deleted file mode 100644 index 746a1090d5d8..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/sq.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Përzgjidhni Sajt"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Sajt"; diff --git a/Sources/JetpackStatsWidgets/Resources/sv.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/sv.lproj/Localizable.strings deleted file mode 100644 index 6ffe3cc51457..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/sv.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Välj webbplats"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Webbplats"; diff --git a/Sources/JetpackStatsWidgets/Resources/th.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/th.lproj/Localizable.strings deleted file mode 100644 index d196ac9d5a8f..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/th.lproj/Localizable.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Select Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/tr.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/tr.lproj/Localizable.strings deleted file mode 100644 index 997c8668b402..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/tr.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "Site seçin"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "Site"; diff --git a/Sources/JetpackStatsWidgets/Resources/zh-Hans.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/zh-Hans.lproj/Localizable.strings deleted file mode 100644 index 1882a074f760..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/zh-Hans.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "选择站点"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "站点"; diff --git a/Sources/JetpackStatsWidgets/Resources/zh-Hant.lproj/Localizable.strings b/Sources/JetpackStatsWidgets/Resources/zh-Hant.lproj/Localizable.strings deleted file mode 100644 index ede8667b35b2..000000000000 --- a/Sources/JetpackStatsWidgets/Resources/zh-Hant.lproj/Localizable.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* Static copies of the widget configuration strings, under the same `ios-widget.`-prefixed - keys that GlotPress maintains in the app's Localizable.strings. iOS 17 resolves the widget - configuration UI's App Intents strings against this extension bundle (newer iOS versions - use the app bundle), so these files must ship here. To refresh after translations change, - copy the `ios-widget.gpCwrM` / `ios-widget.ILcGmf` entries from - WordPress/Resources/.lproj/Localizable.strings. */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"ios-widget.gpCwrM" = "選取網站"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ios-widget.ILcGmf" = "網站"; diff --git a/WordPress/JetpackStatsWidgets/en.lproj/Localizable.strings b/WordPress/JetpackStatsWidgets/en.lproj/Localizable.strings deleted file mode 100644 index cc870542d162..000000000000 --- a/WordPress/JetpackStatsWidgets/en.lproj/Localizable.strings +++ /dev/null @@ -1,14 +0,0 @@ -/* Source strings for the stats widget configuration UI, uploaded to GlotPress under the - "ios-widget." prefix (see MANUALLY_MAINTAINED_STRINGS_FILES in fastlane/lanes/localization.rb). - The keys are the identifiers Xcode generated for the legacy Sites.intentdefinition; they are - kept so the existing GlotPress translations keep matching after the migration to App Intents. - - This file is not shipped in any bundle. At runtime the prefixed keys are resolved from the - app's Localizable.strings (iOS 26) or from the static copies in - Sources/JetpackStatsWidgets/Resources/.lproj/Localizable.strings (iOS 17). */ - -/* This text is used when the user is configuring the iOS widget to suggest them to select the site to configure the widget for */ -"gpCwrM" = "Select Site"; - -/* This text is used when the user is configuring the iOS widget, as a label for the dropdown to select the site to configure it for */ -"ILcGmf" = "Site"; diff --git a/fastlane/lanes/app_intents_strings_helper.rb b/fastlane/lanes/app_intents_strings_helper.rb new file mode 100644 index 000000000000..3808a76bc65d --- /dev/null +++ b/fastlane/lanes/app_intents_strings_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Logic for the App Intents (`LocalizedStringResource`) leg of the GlotPress upload. Plain Ruby with no +# fastlane dependencies, so it's unit-testable directly — the lanes in `localization_catalog.rb` call into it. +module AppIntentsStrings + module_function + + # The build-free extraction cannot type interpolations (a defaultValue's `\(…)` segments), so it + # emits untyped `%arg` placeholders. Rewrite them as positional printf specifiers (`%1$@`, `%2$@`, + # …), which is what GlotPress translators and the runtime's format-style resolution expect. This is + # only correct for String-valued interpolations, so App Intents defaultValues must interpolate + # preformatted Strings, never raw numbers or dates (see docs/localization.md). + # + # Two bugs, red in the adjacent suite: the counter advances on placeholders that already carry an explicit + # position, and an escaped percent before "arg" is consumed as a placeholder. + def positionalize_untyped_arguments(value) + index = 0 + value.gsub(/%(\d+\$)?arg/) do + index += 1 + "%#{Regexp.last_match(1) || "#{index}$"}@" + end + end +end diff --git a/fastlane/lanes/app_intents_strings_helper_test.rb b/fastlane/lanes/app_intents_strings_helper_test.rb new file mode 100644 index 000000000000..95041b84e74d --- /dev/null +++ b/fastlane/lanes/app_intents_strings_helper_test.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'minitest/autorun' +require_relative 'app_intents_strings_helper' + +class AppIntentsStringsHelperTest < Minitest::Test + def positionalize(value) + AppIntentsStrings.positionalize_untyped_arguments(value) + end + + def test_leaves_a_value_without_placeholders_untouched + assert_equal 'Select Site', positionalize('Select Site') + end + + def test_numbers_a_single_untyped_placeholder + assert_equal 'Stats for %1$@', positionalize('Stats for %arg') + end + + def test_numbers_untyped_placeholders_left_to_right + assert_equal '%1$@ on %2$@', positionalize('%arg on %arg') + end + + def test_preserves_an_explicit_position + assert_equal '%1$@', positionalize('%1$arg') + end + + def test_preserves_explicit_positions_out_of_order + # Reordering is how translators adapt to a target language's word order, so it must survive the rewrite. + assert_equal '%2$@ then %1$@', positionalize('%2$arg then %1$arg') + end + + def test_an_escaped_percent_elsewhere_in_the_value_is_untouched + assert_equal '100%% done: %1$@', positionalize('100%% done: %arg') + end + + def test_leaves_an_already_typed_specifier_alone + # `%@`/`%d` come from hand-written keys, not the untyped extraction, and are already GlotPress-ready. + assert_equal '%1$@ of %2$d', positionalize('%1$@ of %2$d') + end + + def test_does_not_match_arg_outside_a_placeholder + assert_equal 'Largest argument', positionalize('Largest argument') + end + + def test_mixed_forms_are_correct_when_the_explicit_position_matches_the_counter + assert_equal '%1$@ and %2$@', positionalize('%1$arg and %arg') + end + + # FAILING. Asserted as an invariant, not an exact string: which index the untyped placeholder should take + # is the fixer's call, that it must not duplicate an explicit one is not. + def test_no_two_placeholders_resolve_to_the_same_argument + indices = positionalize('%2$arg and %arg').scan(/%(\d+)\$@/).flatten + + assert_equal indices.uniq, indices + end + + # FAILING. `%%` is a literal percent, so "100%arg" is prose, not an interpolation. + def test_an_escaped_percent_before_arg_is_not_a_placeholder + assert_equal '100%%arg', positionalize('100%%arg') + end +end diff --git a/fastlane/lanes/localization.rb b/fastlane/lanes/localization.rb index a6897b7dc1ac..12a30becac68 100644 --- a/fastlane/lanes/localization.rb +++ b/fastlane/lanes/localization.rb @@ -96,8 +96,7 @@ MANUALLY_MAINTAINED_STRINGS_FILES = { File.join('WordPress', 'Resources', 'en.lproj', 'InfoPlist.strings') => 'infoplist.', # For now WordPress and Jetpack share the same InfoPlist.strings File.join('WordPress', 'WordPressDraftActionExtension', 'en.lproj', 'InfoPlist.strings') => 'ios-sharesheet.', # CFBundleDisplayName for the "Save as Draft" share action - File.join('WordPress', 'JetpackDraftActionExtension', 'en.lproj', 'InfoPlist.strings') => 'ios-jetpack-sharesheet.', # CFBundleDisplayName for the "Save to Jetpack" share action - File.join('WordPress', 'JetpackStatsWidgets', 'en.lproj', 'Localizable.strings') => 'ios-widget.' # Strings for the App Intents UI used when configuring the iOS Widget; resolved at runtime via the prefixed keys in the app's Localizable.strings + File.join('WordPress', 'JetpackDraftActionExtension', 'en.lproj', 'InfoPlist.strings') => 'ios-jetpack-sharesheet.' # CFBundleDisplayName for the "Save to Jetpack" share action }.freeze # Remote Swift Packages whose localizable strings we want to extract (they're checked out under Derived Data @@ -195,6 +194,21 @@ def upload_to_app_store_common_params ) end + # Merge the App Intents strings into the same destination. These are extracted from code by + # xcstringstool (genstrings cannot parse LocalizedStringResource) and are transient like the plural + # originals above, but deliberately kept separate from the plurals plumbing — and unlike the plural + # step this is NOT allowed to fail softly: a silently missing App Intents string would ship + # English-only in every locale. + Dir.mktmpdir do |app_intents_tmp| + app_intents_originals = File.join(app_intents_tmp, 'AppIntents.strings') + File.write(app_intents_originals, generate_app_intents_strings_for_glotpress) + + ios_merge_strings_files( + paths_to_merge: { app_intents_originals => '' }, # keys are self-qualified with their ios-* prefixes + destination: File.join(WORDPRESS_EN_LPROJ, 'Localizable.strings') + ) + end + git_commit(path: [WORDPRESS_EN_LPROJ], message: 'Update strings for localization', allow_nothing_to_commit: true) unless skip_commit end diff --git a/fastlane/lanes/localization_catalog.rb b/fastlane/lanes/localization_catalog.rb index a4f45b614fcf..6e1888eeb251 100644 --- a/fastlane/lanes/localization_catalog.rb +++ b/fastlane/lanes/localization_catalog.rb @@ -3,7 +3,9 @@ require 'json' require 'tmpdir' require 'fileutils' +require_relative 'app_intents_strings_helper' require_relative 'catalog_helper' +require_relative 'plural_strings_helper' ################################################# # Catalog generation (forward / extraction) @@ -39,6 +41,17 @@ # The custom localization routine to additionally extract (same as the genstrings `routines:` today). CATALOG_LOCALIZATION_ROUTINE = 'AppLocalizedString' +# App Intents display strings (LocalizedStringResource) can't be parsed by genstrings, so the code +# freeze extracts them from these dirs with xcstringstool and merges them into the GlotPress upload +# (see generate_app_intents_strings_for_glotpress). Keys in code are self-qualified with these prefixes. +APP_INTENTS_STRINGS_ROOTS = [ + File.join(PROJECT_ROOT_FOLDER, 'Modules', 'Sources', 'JetpackStatsWidgetsCore', 'AppIntents') +].freeze + +APP_INTENTS_KEY_PREFIXES = [ + 'ios-widget.' +].freeze + platform :ios do # Extracts English source strings from code into Localizable.xcstrings (build-free; replaces genstrings). # @@ -172,19 +185,25 @@ def reconcile_changed_sources(stringsdata_dir:) reconciled.count end - # Current English value per key, by syncing the extraction into a throwaway empty catalog (every key is - # 'new', so its English is populated straight from source — which is what `sync` won't do for keys that - # already exist in the real catalog). - def current_english_values(stringsdata_dir) + # Syncs the extracted .stringsdata into a fresh throwaway catalog and returns it parsed. A fresh + # catalog means every key is 'new', so its English value is populated straight from source — which + # is what `sync` won't do for keys that already exist in a persistent catalog. + def synced_throwaway_catalog(stringsdata_dir:) Dir.mktmpdir do |tmp| fresh = File.join(tmp, 'Localizable.xcstrings') File.write(fresh, "#{JSON.pretty_generate('sourceLanguage' => 'en', 'strings' => {}, 'version' => '1.0')}\n") stringsdata = stringsdata_files(stringsdata_dir) + UI.user_error!('xcstringstool produced no .stringsdata') if stringsdata.empty? sh('xcrun', 'xcstringstool', 'sync', fresh, *stringsdata.flat_map { |f| ['--stringsdata', f] }) - english_values(JSON.parse(File.read(fresh))) + JSON.parse(File.read(fresh)) end end + # Current English value per key, from a throwaway-catalog sync of the extraction. + def current_english_values(stringsdata_dir) + english_values(synced_throwaway_catalog(stringsdata_dir: stringsdata_dir)) + end + # { key => English value } for every catalog entry that has one (skips key-as-source entries). def english_values(catalog) catalog['strings'].each_with_object({}) do |(key, entry), acc| @@ -200,4 +219,38 @@ def report_catalog(path, extracted_count:, reconciled_count:) message += " Re-flagged #{reconciled_count} for review (English source changed)." if reconciled_count.positive? UI.success(message) end + + # Extracts the App Intents LocalizedStringResource strings (key, English defaultValue, translator + # comment) from source and returns them as `.strings` file content for the GlotPress merge. The keys + # are already prefixed in code, so the caller merges this with an empty prefix, like the plural + # originals. Fails loudly rather than silently shipping an untranslated intent string. + # Declared as a lane like generate_plural_strings_for_glotpress: the freeze lane calls it as a + # function, and it can be run standalone as a smoke check (prints the count; errors on unprefixed keys). + desc 'Generates the App Intents strings content that the code freeze merges into the GlotPress upload' + lane :generate_app_intents_strings_for_glotpress do + files = catalog_source_files(APP_INTENTS_STRINGS_ROOTS) + UI.user_error!('No App Intents source files found — APP_INTENTS_STRINGS_ROOTS out of date?') if files.empty? + + entries = Dir.mktmpdir do |tmp| + extract_stringsdata(files: files, output_dir: tmp) + app_intents_entries(stringsdata_dir: tmp) + end + UI.user_error!('No App Intents strings extracted') if entries.empty? + + unprefixed = entries.keys.reject { |key| APP_INTENTS_KEY_PREFIXES.any? { |prefix| key.start_with?(prefix) } } + UI.user_error!("App Intents strings without a known prefix (add one, or extend APP_INTENTS_KEY_PREFIXES): #{unprefixed.sort}") unless unprefixed.empty? + + UI.message("Extracted #{entries.count} App Intents strings for the GlotPress upload.") + PluralStrings.serialize_legacy_strings(entries.sort.to_h) # sorted for stable output + end + + # { key => { value:, comment: } } from a scoped extraction, via `synced_throwaway_catalog`. + # Entries without an explicit English value are interpolation-only + # resources (e.g. DisplayRepresentation(title: "\(name)")) — not translatable text — and are skipped. + def app_intents_entries(stringsdata_dir:) + synced_throwaway_catalog(stringsdata_dir: stringsdata_dir)['strings'].each_with_object({}) do |(key, entry), acc| + value = entry.dig('localizations', 'en', 'stringUnit', 'value') + acc[key] = { value: AppIntentsStrings.positionalize_untyped_arguments(value), comment: entry['comment'] } unless value.nil? + end + end end