Inconsistent locale detection on iPhones


Hi there,

I was trying to get a user locale as:

info = sys.get_sys_info()
locale =  info.language or info.device_language

On html5 this worked correctly and was returning 2-symbols locale by operating system language: “en”, “ru”, etc.

But on iOS (tested on iPhone7 and iPhoneXR) it always returns “en”, even when I’ve changed device language to Italian and removed all mentions of EN language in settings.

So, info.language returns something strange.

Going deeper, I figured out, that info.device_language returns string in such format - “it-RU” or “en-PL” (when language is it/en and region is RU/PL), so I can get the correct locale from it, BUT it looks different from what described in the docs:

string Two character ISO-639 format (i.e. “sr”) and, if applicable, followed by a dash (-) and an ISO 15924 script code (i.e. “sr-Cyrl” or “sr-Latn”). Reflects the device preferred language.

But here we see two-characters formatted region which does not look like “ISO 15924”


  • for Chinese, Simplified device_language = zh-Hans-RU,
  • for Chinese, Traditional device_language = zh-Hant-RU
  • for Chinese, Traditional (Hong Kong) device_language = zh-Hant-HK
  • for Portuguese (Brasil) device_language = pt-BR
    Where territory was set to Russia in all cases


I believe our documentation isn’t really correct when it comes to device_language.

But first a disclaimer: We try to return similarly formatted values for all fields in sys.get_sys_info() regardless of platform. Not all platforms provide the same granularity and values so there will sometimes be discrepancies.

In the case of device_language on iOS we use [NSLocale currentLocale].localeIdentifier where “The locale is formed from the settings for the current user’s chosen system locale overlaid with any custom settings the user has specified.”.

device_language can be a two character ISO-639 code, an additional ISO-15924 script code where applicable, and finally also a territory, which could as in your example either be something related to the language of choice, for instance BR and HK, or the territory of the user, like RU.

This sounds kind of strange. Both info.language and info.device_language comes from the same system value. In the case of info.language it returns the two letter code, ie the first part up to the -.



Thanks for the details! So I’d say it worths adding more details about the structure of what device_language returns on iOS to the documentation (I’ve left the suggestion on the doc page)

Can anyone check this on their iOS devices? Because as I said in first part of my message, for me info.language always returns en on my two devices with different language/terriotory settings, and it differs from “the first part up to the -” in device_language which is correct and equals to the actual device language.



I confirm that info.language doesn’t work on my iPhone 8.

This works:

local language = sys.get_sys_info().device_language
if language then
	local dash_start, dash_end = language:find('-', 1, true)
	if dash_start then
		language = language:sub(1, dash_start - 1)
	language = 'en'

Put an if language then just in case…

1 Like


Just a bump: the bug is reported by two developers now, so I believe it worths investigation @britzl



Started looking into it. It does indeed behave the wrong way on newer iOS versions:

I guess for now you should ignore language on iOS and instead use device_language.



Aha! In default info.plist we have CFBundleDevelopmentRegion set to en:


And according to some docs I found:

“iOS 11 applies this language selection mechanism more strictly than did previous versions of the operating system. Because of this, any iOS 11 app that does not explicitly declare its supported localizations – either by including .lproj folders or setting a value for CFBundleLocalizations – may display a different language in iOS 11 than it did in iOS 10.”

What this means is that on iOS 11+ the system will look for a CFBundleLocalizations array in info.plist. This array specifies the languages that the application supports. If the device is set to use a language that is missing from CFBundleLocalizations it will fall back to the value set by CFBundleDevelopmentRegion. The CFBundleLocalizations array does not exist in the default info.plist but if you were to add it it should look like this:

            ... more language keys here

There’s two options here:

  1. Remove the CFBundleDevelopmentRegion key from your info.plist file. If you remove the key sys.get_sys_info() will set the language to the device language (which you have defined in Settings app), not the application language.
  2. Keep CFBundleDevelopmentRegion and add CFBundleLocalizations array with the languages you support. In this case sys.get_sys_info() will set the language to the device language (which you have defined in Settings app) IF the language also exists in CFBundleLocalizations array. If it doesn’t exist the sys.get_sys_info() will have language set to the value of CFBundleDevelopmentRegion.