Last active
January 18, 2024 10:22
-
-
Save liamnichols/81d2e420067f431c59c97aeeb47ee19d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
public struct Localizer { | |
/// Returns the best match localized string for the given locale based on the languages available as part of the bundle. | |
/// | |
/// This method is similar to the system `NSLocalizedString(_:tableName:bundle:value:comment:)` function, but its resolution is much more granular. | |
/// | |
/// With `NSLocalizedString`, if the current language is missing a phrase for the specified `key`, it would return the `value`, or the `key` even if the bundle contained the phrase in a different (but relevant) language. | |
/// | |
/// Using this method, a lookup list will be made using `Bundle.preferredLocalizations(from:forPreferences:)` instead, and each language in the order of most preferable will be searched instead. | |
/// If the phrase isn't found in any of the preferred languages, the value will instead be obtained from the `developmentLocalization` instead. | |
/// Failure to find a phrase using any of the described approaches will result in the `key` being returned. | |
/// | |
/// This behaviour is much more preferred, especially when the `preferredLocale` has regional information such as `es_AR`. With a development language of `en`, the following languages will be checked in order of top to bottom: | |
/// | |
/// - `es_AR` | |
/// - `es_419` (Latin America) | |
/// - `es` | |
/// - `en` | |
/// | |
/// - Parameters: | |
/// - key: The key for a string in the table identified by `table`. | |
/// - table: The bundled’s string table to search. If `table` is `nil` or is an empty string, the method attempts to use the table in **Localizable.strings**. | |
/// - bundle: The bundle containing the table’s strings file. The main bundle is used if one isn’t specified. | |
/// - preferredLocale: The `Locale` containing the language, region and script information to indicate a preferred localisation. | |
/// - Returns: A localized string using the first language in the preferred order where the `key` exists, or the localisation from the development language otherwise the `key` if no localization could be found. | |
public static func string( | |
forKey key: String, | |
table: String? = nil, | |
bundle: Bundle = .main, | |
preferredLocale: Locale | |
) -> String { | |
// Figure out the preferred localizations based on what is available and what is requested | |
let preferredLocalizations = Bundle.preferredLocalizations( | |
from: bundle.localizations, | |
forPreferences: [preferredLocale.localizationIdentifier] | |
) | |
// Loop through the preferred localizations and try to find a match | |
for localization in preferredLocalizations { | |
if let value = bundle.localizedString(forKey: key, table: table, localization: localization) { | |
return value | |
} | |
} | |
// Otherwise, try the development language | |
if let localization = bundle.developmentLocalization, | |
let value = bundle.localizedString(forKey: key, table: table, localization: localization) { | |
return value | |
} | |
// Finally, just return the `key` if the value couldn't be resolved | |
return key | |
} | |
} | |
private extension Bundle { | |
private static let notLocalizedMarker = "__NOT_LOCALIZED__" | |
/// Returns a localised string for the specified localisation, or `nil` if either the given `localization` was not supported or the phrase with the given `key` was not part of it. | |
func localizedString(forKey key: String, table: String?, localization: String) -> String? { | |
// Load a Bundle representing the specified localization, exit early if it doesn't exist | |
guard let path = path(forResource: localization, ofType: "lproj"), let bundle = Bundle(path: path) else { return nil } | |
// Query the localized string from within | |
let value = bundle.localizedString(forKey: key, value: Bundle.notLocalizedMarker, table: table) | |
// Return the value as long as it was localized as expected, otherwise return nil | |
if value != Bundle.notLocalizedMarker { | |
return value | |
} else { | |
return nil | |
} | |
} | |
} | |
private extension Locale { | |
/// Returns an identifier used in an **.lproj** bundle to represent the given local. | |
var localizationIdentifier: String { | |
[languageCode, scriptCode, regionCode] | |
.compactMap { $0 } | |
.joined(separator: "-") | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
/// A closure used to provide the preferred locale for the `NSLocalizedString(_:tableName:bundle:value:comment:)` override used by GlobalResources. | |
public var NSLocalizedStringPreferredLocaleProvider: () -> Locale = { | |
Locale(identifier: "en_US_POSIX") | |
} | |
/// An overload of Foundation's `NSLocalizedString(_:tableName:bundle:value:comment:)` method that redirects to `Localizer` using the locale provided by `NSLocalizedStringPreferredLocaleProvider`. | |
/// | |
/// This exists in order to trick definitions in R.generated.swift to use `Localizer` without modifying the generated code directly. While a more stable solution would be preferred, this approach remains until we can gain greater control over `StringResource` generation. | |
func NSLocalizedString( | |
_ key: String, | |
tableName: String? = nil, | |
bundle: Bundle = Bundle.main, | |
value: String = "", | |
comment: String | |
) -> String { | |
Localizer.string( | |
forKey: key, | |
table: tableName, | |
bundle: bundle, | |
preferredLocale: NSLocalizedStringPreferredLocaleProvider() | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment