Fallback language iOS (with incomplete Localizable.strings file) - ios

I have an iOS project that is localized into 16 languages. Only some words are not localized (Mainly those that go into an update and the localization office did not deliver in time).
For my keys I do not use the english wording, as this can also change if a translator wishes.
So now if I just don't have a translation for a language, if falls back to the key that I used. But as this key is not 'human readable' or at least not 'human enjoyable to read' this is a problem.
I did some research but couldn't find a solution to my exact problem.
I have fe.:
Localizable.strings in en.lproj
#"Key1" = #"Value 1"
#"Key2" = #"Value 2"
Localizable.strings in de.lproj
#"Key1" = #"Wert 1"
// Note that #"Key2" is missing here in my de.lproj
I would expect that if I make NSLocalizedString(#"Key2", ...)
and am running on a german phone, it falls back to the english
translation for this key as it exists...
So for now i just copied the english translation into the missing Localizable.strings files. But this is a big hack!
But also using the english words as keys seems to be a hack to me!
Is there any way to tell my app, that it should use f.e. english as the fallback if there is no value for a given key? I tried adding a base localization but this doesn't help...
Thanks a lot

As far as I know, there's no "official" way to do it, but I have implemented functions such as this before:
NSString * L(NSString * translation_key) {
NSString * s = NSLocalizedString(translation_key, nil);
if (![[[NSLocale preferredLanguages] objectAtIndex:0] isEqualToString:#"en"] && [s isEqualToString:translation_key]) {
NSString * path = [[NSBundle mainBundle] pathForResource:#"en" ofType:#"lproj"];
NSBundle * languageBundle = [NSBundle bundleWithPath:path];
s = [languageBundle localizedStringForKey:translation_key value:#"" table:nil];
}
return s;
}
borrowed from: https://stackoverflow.com/a/8784451/1403046
Basically, instead of NSLocalizedString(), which will return the input string, this version will fallback to English if necessary.

Inspired by this and this, my Swift code version:
public func LS(_ key: String) -> String {
let value = NSLocalizedString(key, comment: "")
if value != key || NSLocale.preferredLanguages.first == "en" {
return value
}
// Fall back to en
guard
let path = Bundle.main.path(forResource: "en", ofType: "lproj"),
let bundle = Bundle(path: path)
else { return value }
return NSLocalizedString(key, bundle: bundle, comment: "")
}
Many developers expect an incomplete translation to fallback on the development language.. but that's not the way Apple choose to behave. I have a pseudocode to help better understand how Apple choose to fallback.

For Swift project SwiftGen tool can be used. It generates string constants that will contain fallback strings from base localisation language. If a string key is not found in the localization file for currently selected language then the fallback string will be used.
Example of a generated constant for CommonTextClose localization key from Localizable.strings file:
internal static let commonTextClose = L10n.tr("Localizable", "CommonTextClose", fallback: "Close")

You can use a Base localization and all unlocalized strings will be taken from this.

Related

Adding general and specific localizable files for targets in Xcode

I have two targets in my project: target1 and target2. Each of them uses their localizable.strings resource for localization. But their localizable.strings files are same for 90%.
I want to realize such behavior:
Create general localizable file with common strings
Create localizable files for each target with their special strings that are different (e.x. app_name)
Application should search the string in target's localizable.strings file at first and if this string is not exists, search it in common file
I tried to add localizable.string files for each target and then created common localizable.string file(with membership for target1 and target2), but application uses only common file :(
Does anybody know how to fix it?
Thank you for help!
Maybe you can create a pre-build script to concat the specific strings inside global strings.
Or an other solution, you can create a function to search first in global and after in a specific file (this is an example, not tested) :
- (NSString*) localizedString:(NSString*) key {
//search in global file
NSString *transcription = NSLocalizedString(key, "");
if ([transcription isEqualToString:key]) {
//search in specific file
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"specific_file" ofType:#"strings"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
return [dict objectForKey:key];
}
return transcription;
}
With thanks to lafalex, here is code for swift 2:
if let path = NSBundle.mainBundle().pathForResource("specific_file", ofType: "strings"),
let dict : NSDictionary = NSDictionary(contentsOfFile: path) {
if let localizedString = dict.objectForKey(key) as? String {
return localizedString
}
}

Concatenating Two strings in Localization Objective-c

My Previous Code without Localization. It worked perfect.
case LOGIN_LOGOUT: ((Cell*)cell).lbl.text = [self isLoggedIn] ?
[NSString stringWithFormat:#"Logout %#", email]
:NSLocalizedString(#"Login", #"Message");
break;
But when I implement Localization in Logout the email will not show.
case LOGIN_LOGOUT: ((Cell*)cell).lbl.text = [self isLoggedIn] ?
[NSString stringWithFormat:NSLocalizedString(#"Logout", #"Message") ,"%#",
email] :NSLocalizedString(#"Login", #"Message");
break;
I know I am missing some basics in stringWithFormat but can anyone offer some guidance to me?
Let's assume, that you have .strings file and it contains entry named "Logout". You have:
[NSString stringWithFormat:NSLocalizedString(#"Logout", #"Message") ,"%#", email]
here you try to load format string via NSLocalizedString and use it with NSString. That means that you have to put correct format string into your .strings file, so, if currently you have:
"Logout" = "Logout";
In order to make it just like before localization, you need:
"Logout" = "Logout %#";
If you don't have a .strings file or don't have entry named "Logout", NSLocalizedString will return the key, i.e.
NSLocalizedString(#"key", #"comment") // returns "key"
That means, that your NSLocalizedString(#"Logout", #"Message") may return "Logout" if NSLocalizedString can't find correct entry in your .strings file.
There are more things that may go wrong, if you want some deeper insides on that, I have written great article on the whole topic: Understanding iOS internationalization.
Also I'd suggest to use +localizedStringWithFormat: instead of just plain +stringWithFormat:, because the former uses current locale.
You are looking up the localisation of "Logout". You are using that as a format string. That's not likely to work. Don't make statements that are too complex, it makes it impossible to debug.
I'd write
case LOGIN_LOGOUT: {
NSString* labelText;
if ([self isLoggedIn]) {
NSString* formatString = NSLocalizedString(#"Logout", #"Message");
labelText = [NSString stringWithFormat:formatString, "%#", email];
} else {
labelText = NSLocalizedString(#"Login", #"Message");
}
((Cell*)cell).lbl.text = labelText;
break;
}
And now you can actually debug that whole mess. The stringWithFormat parameters look very, very dodgy.

What is wrong with this NSLocalizableString code?

I have this code on Viewcontroller.m, on Xcode:
NSString *language = NSLocalizedString(#"es", #"language");
NSString *connector = NSLocalizedString(#"de", #"connector to link words");
And this one on the "Localizable.strings (English)":
"language" = "en";
"connector to link words" = "of";
The problem is that with every language I change on the iOs Simulator, I always get the first value, the value of the Viewcontroller.m, instead of get the strings values.
Does anyone know what is wrong?? Thank you so much!
UPDATE:
I have this:
NSString *language = NSLocalizedString(#"es", #"language");
NSString *connector = NSLocalizedString(#"de", #"connector to link words");
But it still doesn't work!! Why????
It only shows the key values!! In the strings I have:
"es" = "en";
"de" = "of";
on the english file, and on the spanish file:
"es" = "es";
"de" = "de";
SOLUTION:
I think I have already done everything right, so the problem must to be in the iOs simulator. If anyone can take advantage of that, my solution has been edit the scheme clicking in the image of the project in the superior task bar, and in the tab "Options" (on the Run part) set "Spanish" as my language by default.
Thanks everybody anyway.
The syntax of NSLocalizedString goes like the below.
NSString * NSLocalizedString(
NSString *key,
NSString *comment
)
The key should be used in your .strings file. The value of the key will be different for different languages .
So when you run the key will be replaced by the value provided in the language .strings file you set.
Look at this tutorial for more explanation.
syntax is NSLocalizedString(key, comment)
And this one on the "Localizable.strings (English)":
"language" = "en";
"connector to link words" = "of";
so "language" is the key and "en" is the value
so
NSString *language = NSLocalizedString(#"language", #"");
NSString *connector = NSLocalizedString(#"connector to link words",#"");

What characters are allowed in a iOS file name?

I'm looking for a way to make sure a string can be used as a file name under iOS. I'm currently in the section of the code that deletes incompatible characters. I'm wondering if I'm doing it right.
NSString *filename = #"A file name";
fileName = [fileName stringByTrimmingCharactersInSet: [NSCharacterSet controlCharacterSet]];
fileName = [fileName stringByTrimmingCharactersInSet: [NSCharacterSet newlineCharacterSet]];
I'm also wondering if there's already a method that validates a string as a file name.
Thank you for your advice!
Use RegEx:
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"[^a-zA-Z0-9_]+" options:0 error:nil];
filename = [regex stringByReplacingMatchesInString:filename options:0 range:NSMakeRange(0, filename.length) withTemplate:#"-"];
I find this to be cleaner and probably much more performant. This is based on Angel Naydenov's solution, but first constructing Character set with all invalid characters and then calling components(separatedBy:) just once.
Swift 3 & 4
var invalidCharacters = CharacterSet(charactersIn: ":/")
invalidCharacters.formUnion(.newlines)
invalidCharacters.formUnion(.illegalCharacters)
invalidCharacters.formUnion(.controlCharacters)
let newFilename = originalFilename
.components(separatedBy: invalidCharacters)
.joined(separator: "")
Swift 2
let invalidCharacters = NSMutableCharacterSet(charactersInString: ":/")
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.newlineCharacterSet())
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.illegalCharacterSet())
invalidCharacters.formUnionWithCharacterSet(NSCharacterSet.controlCharacterSet())
let filename = originalFilename
.componentsSeparatedByCharactersInSet(invalidCharacters)
.joinWithSeparator("")
First of all, you're using the wrong method. Trimming the string will only remove characters in the beginning and the end of the string.
What you're looking for is something more like:
fileName = [fileName stringByReplacingOccurrencesOfString:#"/" withString:#"_"];
However, that's a suboptimal solution, since you'll have to do that for every character you want to exclude, so maybe you want to keep looking or write you're own method for manipulating the string.
iOS is UNIX based and as such I suppose it supports almost any characters in filenames. UNIX allows white spaces, <, >, |, \, :, (, ), &, ;, as well as wildcards such as ? and *, to be quoted or escaped using \ symbol. However I wouldn't use any of those characters in my filenames. In fact, I would restrict the characters in my filenames to 'a'-'z', '0'-'9', '_' and '.'.
As I did not see a list with allowed characters in this question but the question wanted a list with such characters I am adding a bit more details on this topic.
First we need to know what is the file system that iOS devices use. Using multiple online sources this seems to be HFSX which is the HFS+ case sensitive version. And including one link here for reference: https://apple.stackexchange.com/questions/83671/what-filesystem-does-ios-use
Now that we know what the file system is we can look for what characters are not allowed. And these seem to be: colon (:) and slash (/). Here is a link for reference: http://www.comentum.com/File-Systems-HFS-FAT-UFS.html
Having this information and what others have written in this thread my personal preference for removing not allowed characters from file names is the following Swift code:
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()))
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.illegalCharacterSet()))
filename = "-".join(filename.componentsSeparatedByCharactersInSet(NSCharacterSet.controlCharacterSet()))
filename = "-".join(filename.componentsSeparatedByString(":"))
filename = "-".join(filename.componentsSeparatedByString("/"))
The reason I am not preferring the RegEx approach is that it seems too restrictive to me. I do not want to restrict my users only to Latin characters. They may as well wish to use some Chinese, Cyrillic or whatever else they like.
Happy coding!
I've had to save remote files locally with filenames containing other characters than basic alpha-numeric characters. I use the method below to strip out potential invalid characters, ensuring it's a valid filename for the filesystem when generating a NSURL using URLWithString:
filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] componentsJoinedByString:#"" ];
filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet illegalCharacterSet]] componentsJoinedByString:#"" ];
filename = [[filename componentsSeparatedByCharactersInSet:[NSCharacterSet symbolCharacterSet]] componentsJoinedByString:#"" ];
fileURLString = [NSTemporaryDirectory() stringByAppendingPathComponent:filename];
fileURL = [NSURL URLWithString:fileURLString];
You may also want to test for collision errors first using:
[[NSFileManager defaultManager] fileExistsAtPath:[fileURL absoluteString]]
This String extension (Swift 4.2) will help convert an invalid iOS file name to a valid iOS file name.
extension String {
func convertToValidFileName() -> String {
let invalidFileNameCharactersRegex = "[^a-zA-Z0-9_]+"
let fullRange = startIndex..<endIndex
let validName = replacingOccurrences(of: invalidFileNameCharactersRegex,
with: "-",
options: .regularExpression,
range: fullRange)
return validName
}
}
For example
"name.name?/!!23$$#1asd".convertToValudFileName() // "name-name-23-1asd"
"!Hello.312,^%-0//\r\r".convertToValidFileName() // "-Hello-312-0-"
"/foo/bar/pop?soda=yes|please".convertToValidFileName() // "-foo-bar-pop-soda-yes-please"
I'm pretty happy with this solution:
NSString *testString = #"This*is::/legal.😀,?縦書き 123";
NSString *result = [[[testString componentsSeparatedByCharactersInSet:[[NSCharacterSet alphanumericCharacterSet] invertedSet]] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"]] componentsJoinedByString:#"-"];
Output:
"This-is-legal-縦書き-123"
What is this sorcery?
Let me break it up into multiple lines so it's clear what's going on:
NSString *testString = #"This*is::/legal.😀,?縦書き 123";
// Get a character set for everything that's NOT alphanumeric.
NSCharacterSet *nonAlphanumericCharacterSet = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
// Split the string on each non-alphanumeric character, thus removing them.
NSArray *cleanedUpComponentsWithBlanks = [testString componentsSeparatedByCharactersInSet:nonAlphanumericCharacterSet];
// Filter out empty strings ("length" is a KVO-compliant property that the predicate can call on each NSString in the array).
NSArray *cleanedUpComponentsWithoutBlanks = [cleanedUpComponentsWithBlanks filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > 0"]];
// Put the components back together and join them with a "-".
NSString *result = [cleanedUpComponentsWithoutBlanks componentsJoinedByString:#"-"];
Enjoy!
Swift 4 Version
Added by john-pang on 2021-09-01 with Swift version:
let testString = "This*is::/legal.😀,?縦書き 123"
// Get a character set for everything that's NOT alphanumeric.
let nonAlphanumericCharacterSet = CharacterSet.alphanumerics.inverted
// Split the string on each non-alphanumeric character, thus removing them.
let cleanedUpComponentsWithBlanks = testString.components(separatedBy: nonAlphanumericCharacterSet)
// Filter out empty strings ("length" is a KVO-compliant property that the predicate can call on each NSString in the array).
let cleanedUpComponentsWithoutBlanks = cleanedUpComponentsWithBlanks.filter { $0.length > 0 }
// Put the components back together and join them with a "-".
let result = cleanedUpComponentsWithoutBlanks.joined(separator: "_")
I came up with the following solution. Works nice so far.
import Foundation
extension String {
func removeUnsupportedCharactersForFileName() -> String {
var cleanString = self
["?", "/", "\\", "*"].forEach {
cleanString = cleanString.replacingOccurrences(of: $0, with: "-")
}
return cleanString
}
}
let a = "***???foo.png"
let validString = a.removeUnsupportedCharactersForFileName()
Base on Marian Answers, here is a string extension to remove any unwanted characters.
extension String {
func stripCharacters() -> String {
var invalidCharacters = CharacterSet(charactersIn: ":/")
invalidCharacters.formUnion(.newlines)
invalidCharacters.formUnion(.illegalCharacters)
invalidCharacters.formUnion(.controlCharacters)
let newString = self
.components(separatedBy: invalidCharacters)
.joined(separator: "_")
return newString
}
}
Example:
let fileName = "Man(lop23/45"
let newFileName = fileName.stripCharacters()
print(newFileName)
Swift 5 extension:
I wanted to remove emojis as well and in windows \ is also an invalid character. So I added symbols charset and backslash \ as well.
extension String {
var validFilename: String {
let invalidCharsets = CharacterSet(charactersIn: ":/\\")
.union(.illegalCharacters)
.union(.controlCharacters)
.union(.symbols)
.union(.newlines)
return self.components(separatedBy: invalidCharsets).joined()
}
}

Getting current device language in iOS?

I'd like to show the current language that the device UI is using. What code would I use?
I want this as an NSString in fully spelled out format. (Not #"en_US")
EDIT: For those driving on by, there are a ton of useful comments here, as the answer has evolved with new iOS releases.
The solutions provided will actually return the current region of the device - not the currently selected language. These are often one and the same. However, if I am in North America and I set my language to Japanese, my region will still be English (United States). In order to retrieve the currently selected language, you can do:
NSString * language = [[NSLocale preferredLanguages] firstObject];
This will return a two letter code for the currently selected language. "en" for English, "es" for Spanish, "de" for German, etc. For more examples, please see this Wikipedia entry (in particular, the 639-1 column):
List of ISO 639-1 codes
Then it's a simple matter of converting the two letter codes to the string you would like to display. So if it's "en", display "English".
EDIT
Worth to quote the header information from NSLocale.h:
+ (NSArray *)preferredLanguages NS_AVAILABLE(10_5, 2_0); // note that this list does not indicate what language the app is actually running in; the [NSBundle mainBundle] object determines that at launch and knows that information
People interested in app language take a look at #mindvision's answer
The selected answer returns the current device language, but not the actual language used in the app. If you don't provide a localization in your app for the user's preferred language, the first localization available, ordered by the user's preferred order, is used.
To discover the current language selected within your localizations use
[[NSBundle mainBundle] preferredLocalizations];
Example:
NSString *language = [[[NSBundle mainBundle] preferredLocalizations] objectAtIndex:0];
Swift:
let language = NSBundle.mainBundle().preferredLocalizations.first as NSString
iOS13, Swift 5+
Locale.preferredLanguages.first
Solution for iOS 9:
NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
language = "en-US"
NSDictionary *languageDic = [NSLocale componentsFromLocaleIdentifier:language];
languageDic will have the needed components
NSString *countryCode = [languageDic objectForKey:#"kCFLocaleCountryCodeKey"];
countryCode = "US"
NSString *languageCode = [languageDic objectForKey:#"kCFLocaleLanguageCodeKey"];
languageCode = "en"
This will probably give you what you want:
NSLocale *locale = [NSLocale currentLocale];
NSString *language = [locale displayNameForKey:NSLocaleIdentifier
value:[locale localeIdentifier]];
It will show the name of the language, in the language itself.
For example:
Français (France)
English (United States)
The accepted, and the other answers all don't take into account that the preferred language can be another language than the device language.
The device language is the language in which operating system elements and Apple apps are presented.
The preferred language is the language the user would like to have apps localized in. Apple only provides a limited set of translations. If the preferred language is one language Apple translated their apps to, it will also be the device language. However if the user prefers a language for which Apple doesn't provide translations the device and preferred languages won't match. The device language will not be on first position in the preferred languages list.
The following function will go through the preferred languages list and check if there is a translation in the Apple frameworks. The first language to have a translation is the device language. The function will return its language code.
func deviceLanguage() -> String? {
let systemBundle: NSBundle = NSBundle(forClass: UIView.self)
let englishLocale: NSLocale = NSLocale(localeIdentifier: "en")
let preferredLanguages: [String] = NSLocale.preferredLanguages()
for language: String in preferredLanguages {
let languageComponents: [String : String] = NSLocale.componentsFromLocaleIdentifier(language)
guard let languageCode: String = languageComponents[NSLocaleLanguageCode] else {
continue
}
// ex: es_MX.lproj, zh_CN.lproj
if let countryCode: String = languageComponents[NSLocaleCountryCode] {
if systemBundle.pathForResource("\(languageCode)_\(countryCode)", ofType: "lproj") != nil {
// returns language and country code because it appears that the actual language is coded within the country code aswell
// for example: zh_CN probably mandarin, zh_HK probably cantonese
return language
}
}
// ex: English.lproj, German.lproj
if let languageName: String = englishLocale.displayNameForKey(NSLocaleIdentifier, value: languageCode) {
if systemBundle.pathForResource(languageName, ofType: "lproj") != nil {
return languageCode
}
}
// ex: pt.lproj, hu.lproj
if systemBundle.pathForResource(languageCode, ofType: "lproj") != nil {
return languageCode
}
}
return nil
}
This works if the preferred language list is:
Afrikaans (iOS is not translated into Afrikaans)
Spanish (Device Language)
The preferred language list can be edited in: Settings.app -> General -> Language & Region -> Preferred Language Order
You can than use the device language code and translate it into the language name. The following lines will print the device language in the device language. For example "Español" if the device is set to spanish.
if let deviceLanguageCode: String = deviceLanguage() {
let printOutputLanguageCode: String = deviceLanguageCode
let printOutputLocale: NSLocale = NSLocale(localeIdentifier: printOutputLanguageCode)
if let deviceLanguageName: String = printOutputLocale.displayNameForKey(NSLocaleIdentifier, value: deviceLanguageCode) {
// keep in mind that for some localizations this will print a language and a country
// see deviceLanguage() implementation above
print(deviceLanguageName)
}
}
iOS13, Swift 5+, WWDC2019
https://developer.apple.com/videos/play/wwdc2019/403/
Users can select the preferred language of an app independently from the OS language.
You can use these:
// Returns a list of the user's preferred languages.
// Maybe more than (or none of) your app supports!
Locale.preferredLanguages
// a subset of this bundle's localizations, re-ordered into the preferred order
// for this process's current execution environment; the main bundle's preferred localizations
// indicate the language (of text) the user is most likely seeing in the UI
Bundle.main.preferredLocalizations
// The current running app language
Bundle.main.preferredLocalizations.first
// list of language names this bundle appears to be localized to
Bundle.main.localizations
i use this
NSArray *arr = [NSLocale preferredLanguages];
for (NSString *lan in arr) {
NSLog(#"%#: %# %#",lan, [NSLocale canonicalLanguageIdentifierFromString:lan], [[[NSLocale alloc] initWithLocaleIdentifier:lan] displayNameForKey:NSLocaleIdentifier value:lan]);
}
ignore memory leak..
and result is
2013-03-02 20:01:57.457 xx[12334:907] zh-Hans: zh-Hans 中文(简体中文)
2013-03-02 20:01:57.460 xx[12334:907] en: en English
2013-03-02 20:01:57.462 xx[12334:907] ja: ja 日本語
2013-03-02 20:01:57.465 xx[12334:907] fr: fr français
2013-03-02 20:01:57.468 xx[12334:907] de: de Deutsch
2013-03-02 20:01:57.472 xx[12334:907] nl: nl Nederlands
2013-03-02 20:01:57.477 xx[12334:907] it: it italiano
2013-03-02 20:01:57.481 xx[12334:907] es: es español
Translating language codes such as en_US into English (United States) is a built in feature of NSLocale and NSLocale does not care where you get the language codes from. So there really is no reason to implement your own translation as the accepted answer suggests.
// Example code - try changing the language codes and see what happens
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"];
NSString *l1 = [locale displayNameForKey:NSLocaleIdentifier value:#"en"];
NSString *l2 = [locale displayNameForKey:NSLocaleIdentifier value:#"de"];
NSString *l3 = [locale displayNameForKey:NSLocaleIdentifier value:#"sv"];
NSLog(#"%#, %#, %#", l1, l2, l3);
Prints: English, German, Swedish
Even there's a better way to get current device language. Let's try it by below code -
NSLog(#"Current Language - %#", [[NSLocale preferredLanguages] firstObject]);
Suggested by Abizern on here
You can use the displayNameForKey:value: method of NSLocale:
// get a French locale instance
NSLocale *frLocale = [[[NSLocale alloc] initWithLocaleIdentifier:#"fr_FR"] autorelease];
// use it to get translated display names of fr_FR and en_US
NSLog(#"%#", [frLocale displayNameForKey:NSLocaleIdentifier value:#"fr_FR"]);
NSLog(#"%#", [frLocale displayNameForKey:NSLocaleIdentifier value:#"en_US"]);
This will print out:
français (France)
anglais (États-Unis)
If you specify the same locale identifier for the initWithLocaleIdentifier: and also the displayNameForKey:value: method, then it will give you the native name of the language. I've discovered that if you remove the country code and use just fr and en, that it will also omit the country from the display name (on Mac OS X at least, not sure about iOS).
I tried to found out the right solution for myself. When I use Locale.preferredLanguages.first was returned the preferred language from your app settings.
If you want get to know language from user device settings, you should the use string below:
Swift 3
let currentDeviceLanguage = Locale.current.languageCode
// Will return the optional String
To unwrap and use look at the line below:
if let currentDeviceLanguage = Locale.current.languageCode {
print("currentLanguage", currentDeviceLanguage)
// For example
if currentDeviceLanguage == "he" {
UIView.appearance().semanticContentAttribute = .forceRightToLeft
} else {
UIView.appearance().semanticContentAttribute = .forceLeftToRight
}
}
Swift
To get current language of device
NSLocale.preferredLanguages()[0] as String
To get application language
NSBundle.mainBundle().preferredLocalizations[0] as NSString
Note:
It fetches the language that you have given in CFBundleDevelopmentRegion of info.plist
if CFBundleAllowMixedLocalizations is true in info.plist then first item of CFBundleLocalizations in info.plist is returned
For getting user device current language use the following it code it worked for me.
NSString * myString = [[NSLocale preferredlanguage]objectAtIndex:0];
If you're looking for preferred language code ("en", "de", "es" ...), and localized preferred language name (for current locale), here's a simple extension in Swift:
extension Locale {
static var preferredLanguageIdentifier: String {
let id = Locale.preferredLanguages.first!
let comps = Locale.components(fromIdentifier: id)
return comps.values.first!
}
static var preferredLanguageLocalizedString: String {
let id = Locale.preferredLanguages.first!
return Locale.current.localizedString(forLanguageCode: id)!
}
}
For MonoTouch C# developers use:
NSLocale.PreferredLanguages.FirstOrDefault() ?? "en"
Note: I know this was an iOS question, but as I am a MonoTouch developer, the answer on this page led me in the right direction and I thought I'd share the results.
In Swift:
let languageCode = NSLocale.currentLocale().objectForKey(NSLocaleLanguageCode) as? String
Swift 3
let locale = Locale.current
let code = (locale as NSLocale).object(forKey: NSLocale.Key.countryCode) as! String?
print(code!)
Simple Swift 3 function:
#discardableResult
func getLanguageISO() -> String {
let locale = Locale.current
guard let languageCode = locale.languageCode,
let regionCode = locale.regionCode else {
return "de_DE"
}
return languageCode + "_" + regionCode
}
-(NSString *)returnPreferredLanguage { //as written text
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSArray *preferredLanguages = [defaults objectForKey:#"AppleLanguages"];
NSString *preferredLanguageCode = [preferredLanguages objectAtIndex:0]; //preferred device language code
NSLocale *enLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en"]; //language name will be in English (or whatever)
NSString *languageName = [enLocale displayNameForKey:NSLocaleIdentifier value:preferredLanguageCode]; //name of language, eg. "French"
return languageName;
}
If you want to get only language here is my suggested answer:
NSString *langplusreg = [[NSLocale preferredLanguages] objectAtIndex:0];
NSString * langonly = [[langplusreg componentsSeparatedByString:#"-"]
objectAtIndex:0];
In my case i just wanted only Locale language not locale region.
Output:
If your Locale language is Japanese and locale region is Japan then:
langplusreg = ja-JP
langonly = ja
Obviously, the solutions relying, for example, on
[[NSLocale preferredLanguages] objectAtIndex:0]
usually work fine and return the current device language.
But it could be misleading in some cases :
If the app in which you want to get this value has already changed the language, for example with this kind of code :
NSString *lg = #"en"; // or anything like #"en", #"fr", etc.
[[NSUserDefaults standardUserDefaults]
setObject:[NSArray arrayWithObjects:lg, nil]
forKey:#"AppleLanguages"]
In this case, [NSLocale preferredLanguages] actually returns the preferred language set (and used) in this particular app, not the current device language !
And... in this case the only way to properly get the actual current device language (and not that previously set in the app), is to firstly clear the key #"appleLanguages" in NSUserDefaults, like this :
[[NSUserDefaults standardUserDefaults]removeObjectForKey:#"AppleLanguages"];
Then, [NSLocale preferredLanguages] now returns the correct value.
Hope this help.
In Swift 4.2 and Xcode 10.1
let language = NSLocale.preferredLanguages[0]
debugPrint(language)//en
In Swift 5.x
let langStr = Locale.current.languageCode
debugPrint(langStr ?? "") //en el
I actually misread the original question, thought it asked for the "app UI" language (that's what I had googled for), not the "device UI", in which case the best answers would be the ones using preferredLocalizations, but those answers still give you a code, there is one more step to get a nice string to display.
So, while the "device UI" language is already answered, if you want to display a nice string for which of the UI languages you support is currently in use, obviously the simplest solution is:
NSLocalizedString(#"currentLanguage", #"")
Where in every one of your UI localizations you have specified it exactly the way you want it shown. E.g. in the en version of your .strings file you'd have:
"currentLanguage"="English";
in your fr version of the .strings file you'd have:
"currentLanguage"="Francais";
etc. No messing with codes etc, you make your own strings to nicely match your UI.
SWIFT-4
// To get device default selected language. It will print like short name of zone. For english, en or spain, es.
let language = Bundle.main.preferredLocalizations.first! as NSString
print("device language",language)
#amir response in Swift :
// Get language prefered by user
let langageRegion = NSLocale.preferredLanguages().first!
let languageDic = NSLocale.componentsFromLocaleIdentifier(langageRegion)
let language = languageDic[NSLocaleLanguageCode]
According to Apple documentation
NSUserDefaults* defs = [NSUserDefaults standardUserDefaults];
NSArray* languages = [defs objectForKey:#"AppleLanguages"];
NSString* preferredLang = [languages objectAtIndex:0];
Two letters format. Apple uses the ISO standard ISO-3166.
NSString *localeCountryCode = [[NSLocale autoupdatingCurrentLocale] objectForKey:NSLocaleCountryCode];
For Swift 3:
NSLocale.preferredLanguages[0] as String
As of iOS 9, if you just want the language code without country code, you'll want this sort of helper function - since the language will contain the country code.
// gets the language code without country code in uppercase format, i.e. EN or DE
NSString* GetLanguageCode()
{
static dispatch_once_t onceToken;
static NSString* lang;
dispatch_once(&onceToken, ^
{
lang = [[[NSLocale preferredLanguages] objectAtIndex:0] uppercaseString];
NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:#"^[A-Za-z]+" options:0 error:nil];
NSTextCheckingResult* match = [regex firstMatchInString:lang options:0 range:NSMakeRange(0, lang.length)];
if (match.range.location != NSNotFound)
{
lang = [lang substringToIndex:match.range.length];
}
});
return lang;
}
Updated answer for Swift 4
let language = Bundle.main.preferredLocalizations.first

Resources