GetUserLanguage()

Got an idea for enhancing PureBasic? New command(s) you'd like to see?
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

GetUserLanguage()

Post by Rescator »

A GetUserLanguage() "should" be possible to implement on all platforms. (and not just Windows)

What is the expected result?
If the language locale (in this example on Windows) is English then it will return: English
If it was Norwegian then it will return: Norwegian

Code: Select all

;The following example was put into the public domain by Roger "Rescator" Hågensen in 2003.

EnableExplicit

;code.w is only for testing purposes, remove it if you do not need it.
;Normaly you would always use the constant #LOCALE_USER_DEFAULT.

Procedure.s GetUserLanguage(code.w=#LOCALE_USER_DEFAULT)
	Define result$,dll.i,text$,len.i,*GetLocaleInfo
	dll=OpenLibrary(#PB_Any,"kernel32.dll")
	If dll
		CompilerIf #PB_Compiler_Unicode
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoW")
		CompilerElse
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoA")
		CompilerEndIf
		If *GetLocaleInfo
			len=CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SENGLANGUAGE,#Null,#Null)
			If len
				text$=Space(len-1)
				If CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SENGLANGUAGE,@text$,len)
					result$=text$
				EndIf
			EndIf
		EndIf
		CloseLibrary(dll)
	EndIf
	ProcedureReturn result$
EndProcedure ;Returns "" if the call fails.

Procedure.s GetUserLanguageLocalized(code.w=#LOCALE_USER_DEFAULT)
	Define result$,dll.i,text$,len.i,*GetLocaleInfo
	dll=OpenLibrary(#PB_Any,"kernel32.dll")
	If dll
		CompilerIf #PB_Compiler_Unicode
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoW")
		CompilerElse
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoA")
		CompilerEndIf
		If *GetLocaleInfo
			len=CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SNATIVELANGNAME,#Null,#Null)
			If len
				text$=Space(len-1)
				If CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SNATIVELANGNAME,@text$,len)
					result$=text$
				EndIf
			EndIf
		EndIf
		CloseLibrary(dll)
	EndIf
	ProcedureReturn result$
EndProcedure ;Returns "" if the call fails.

#LOCALE_SISO639LANGNAME=$0059
Procedure.s GetUserLanguageISO(code.w=#LOCALE_USER_DEFAULT)
	Define result$,dll.i,text$,len.i,*GetLocaleInfo
	dll=OpenLibrary(#PB_Any,"kernel32.dll")
	If dll
		CompilerIf #PB_Compiler_Unicode
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoW")
		CompilerElse
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoA")
		CompilerEndIf
		If *GetLocaleInfo
			len=CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SISO639LANGNAME,#Null,#Null)
			If len
				text$=Space(len-1)
				If CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SISO639LANGNAME,@text$,len)
					result$=text$
				EndIf
			EndIf
		EndIf
		CloseLibrary(dll)
	EndIf
	ProcedureReturn result$
EndProcedure ;Returns "" if the call fails.

Procedure.s GetUserLanguageRegion(code.w=#LOCALE_USER_DEFAULT)
	Define result$,dll.i,text$,len.i,*GetLocaleInfo
	dll=OpenLibrary(#PB_Any,"kernel32.dll")
	If dll
		CompilerIf #PB_Compiler_Unicode
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoW")
		CompilerElse
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoA")
		CompilerEndIf
		If *GetLocaleInfo
			len=CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SLANGUAGE,#Null,#Null)
			If len
				text$=Space(len-1)
				If CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SLANGUAGE,@text$,len)
					result$=text$
				EndIf
			EndIf
		EndIf
		CloseLibrary(dll)
	EndIf
	ProcedureReturn result$
EndProcedure ;Returns "" if the call fails.

;http://msdn.microsoft.com/en-us/library/dd373814%28v=vs.85%29.aspx
;http://en.wikipedia.org/wiki/IETF_language_tag
#LOCALE_SISO639LANGNAME=$0059
#LOCALE_SISO3166CTRYNAME=$005a
#LOCALE_NAME_MAX_LENGTH=85
Procedure.s GetUserLanguageIETF(code.w=#LOCALE_USER_DEFAULT) ;well IETF compatible at least, not sure if "lang-script-country(number)" results will occur or not.
	Define result$,dll.i,text$,text2$,len.i,*GetLocaleInfo,*GetUserDefaultLocaleName
	dll=OpenLibrary(#PB_Any,"kernel32.dll")
	If dll
		CompilerIf #PB_Compiler_Unicode
			If code=#LOCALE_USER_DEFAULT
				*GetUserDefaultLocaleName=GetFunction(dll,"GetUserDefaultLocaleName") ;Vista+
			EndIf
			If *GetUserDefaultLocaleName
				text$=Space(#LOCALE_NAME_MAX_LENGTH-1)
				len=CallFunctionFast(*GetUserDefaultLocaleName,@text$,#LOCALE_NAME_MAX_LENGTH)
				If len
					result$=Left(text$,len-1)
				EndIf
			Else ;GetUserDefaultLocaleName not available use fallback.
				*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoW")
			EndIf
		CompilerElse ;GetUserDefaultLocaleName does not exist for ANSI, use fallback.
			*GetLocaleInfo=GetFunction(dll,"GetLocaleInfoA")
		CompilerEndIf
		If *GetLocaleInfo
			len=CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SISO639LANGNAME,#Null,#Null)
			If len
				text$=Space(len-1)
				If CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SISO639LANGNAME,@text$,len)
					text2$=text$
				EndIf
			EndIf
			If text2$
				len=CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SISO3166CTRYNAME,#Null,#Null)
				If len
					text$=Space(len-1)
					If CallFunctionFast(*GetLocaleInfo,code,#LOCALE_SISO3166CTRYNAME,@text$,len)
						result$=text2$+"-"+text$+""
					EndIf
				EndIf
			EndIf
		EndIf
		CloseLibrary(dll)
	EndIf
	ProcedureReturn result$
EndProcedure ;Returns "" if both the call fails.

Procedure test(code.w=#LOCALE_USER_DEFAULT)
	Debug "GetUserLanguage: "+GetUserLanguage(code)
	Debug "GetUserLanguageISO: "+GetUserLanguageISO(code)
	Debug "GetUserLanguageIETF: "+GetUserLanguageIETF(code)
	Debug "GetUserLanguageLocalized: "+GetUserLanguageLocalized(code)
	Debug "GetUserLanguageRegion: "+GetUserLanguageRegion(code)
	Debug "testcode: $"+RSet(Hex(code),4,"0")
	Debug ""
EndProcedure

;List of language codes.
;http://msdn.microsoft.com/en-us/goglobal/bb896001.aspx

test() ;This/your system right now.

test($0C09) ;Australia (AU)
test($2809) ;Belize (BZ)
test($1009) ;Canada (CA)
test($2409) ;Caribbean (029)
test($4009) ;India (IN)
test($1809) ;Ireland (IE)
test($2009) ;Jamaica (JM)
test($4409) ;Malaysia (MY)
test($1409) ;New Zealand (NZ)
test($3409) ;Philippines (PH)
test($4809) ;Singapore (SG)
test($1c09) ;South Africa (ZA)
test($2C09) ;Trinidad and Tobago (TT)
test($0809) ;United Kingdom (GB)
test($0409) ;United States (US)
test($3009) ;Zimbabwe (ZW)
test($0014) ;Norwegian
test($0414) ;Norwegian Bokmål
test($0814) ;Norwegian Nynorsk
test($103b) ;Norwegian Sami
test($043b) ;Norwegian Sami
test($183b) ;Norwegian Sami
test($0400) ;English
test($1000) ;Unspecified custom locale language
test($0800) ;System default locale language

;The Chinese localized names may not display properly, it depends on the font used.
test($0C04) ;Chinese (Traditional, Hong Kong S.A.R.)
test($1404) ;Chinese (Traditional, Macao S.A.R.)
test($1004) ;Chinese (Simplified, Singapore)
test($0004) ;Chinese (Simplified, PRC)
test($7c04) ;Chinese (Traditional, Hong Kong S.A.R.)

test($0C07) ;German (Austria)
test($0407) ;German (Germany)
test($1407) ;German (Liechtenstein)
test($1007) ;German (Luxembourg)
test($0807) ;German (Switzerland)

test($080c) ;French (Belgium)
test($0c0c) ;French (Canada)

test($040c) ;French (France)
test($140c) ;French (Luxembourg)

test($180c) ;French (Monaco)
test($100c) ;French (Switzerland)
Haven't had much time to test this or full OS compatibility (i.e. XP and W2K).
It should reflect the user selected language rather than keyboard input language. For example I have Norwegian Keyboard input but English language and I get "English" as the result.
This is the correct/intended behavior, in my opinion.

If one where to have language support files in a application then I'd suggest to use GetUserLanguage() or as it is much more broad and neutral/international and easily readable by someone. (not everyone know that "nb" and "nn" are both Norwegian for example which GetUserLanguageISO() might return rather than "no".)

While if support for sub languages/language variants then I'd suggest GetUserLanguageIETF() and on Vista+ this is actually the advised standard by Microsoft going forward. Please be aware though that the result returned can be a tad complex and may require some parsing.

If you just wish to display the currently detected language to the user, then GetUserLanguageLocalized() is the obvious choice. (be aware of the current selected font that might have limitations with certain character sets).

And if the regional language info is wanted then GetUserLanguageRegion() might be of interest, might be best paired with GetUserLanguageLocalized().

EDIT:
Made a more complete example based on the function ideas at http://www.purebasic.fr/english/viewtop ... 63#p419763
Renamed/changed the function names.
Some more information added.
Last edited by Rescator on Sat Aug 03, 2013 1:35 am, edited 4 times in total.
User avatar
Danilo
Addict
Addict
Posts: 3037
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: GetUserLanguage()

Post by Danilo »

For Mac OS X you have to translate language codes like "en", "th", and "de" yourself to the full name by using a string table/dictionary.

Table of language codes: http://www.loc.gov/standards/iso639-2/php/English_list.php
Get short language code on Mac OS X: GetDefaultLanguage()
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Re: GetUserLanguage()

Post by Rescator »

Danilo wrote:For Mac OS X you have to translate language codes like "en", "th", and "de" yourself to the full name by using a string table/dictionary.

Table of language codes: http://www.loc.gov/standards/iso639-2/php/English_list.php
Get short language code on Mac OS X: GetDefaultLanguage()
Hmm, why mess with a huge list like that?
I found this:

Code: Select all

If you need to display the actual name of a language to a user, you can use the NSLocale method displayNameForKey:value: to get the correct display name for the language or locale ID.
NSString *identifier = [[NSLocale currentLocale] localeIdentifier];

NSString *displayName = [[NSLocale currentLocale] displayNameForKey:NSLocaleIdentifier value:identifier];
from https://developer.apple.com/library/mac ... tions.html
Under "Getting Language Names from Designators"

No idea what that means but hopefully it makes sense to the mac coders here.

And you may wonder why I suggested just the international display form?
Because you got the mess that is: en, eng, en-en (!), en-gb, en-us
Which one of those would map to "English" in the case of a translation file or language folder in your program? (all of them actually)
Also, "English" will never get changed (unless the language itself dies out), the language codes do change from time to time for a country.

Now if a function returns English then it is very easy to map that to English.xml for example (just do a lowercase match).
With 2 or 3 character language codes with multiple subcodes that may or may not be there it gets silly very quickly.

But sure, additional functions like GetUserLanguageLocale() and GetUserLanguageISO() but the former may not always be available and not all fonts may support the needed characters, and the latter tend to get messy, IMO.
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: GetUserLanguage()

Post by Shardik »

Rescator wrote:No idea what that means but hopefully it makes sense to the mac coders here.
This example code displays "Deutsch" on my iMac:

Code: Select all

CurrentLocale = CocoaMessage(0, 0, "NSLocale currentLocale")
LanguageCode = CocoaMessage(0, CurrentLocale, "objectForKey:$",
  @"kCFLocaleLanguageCodeKey")
Debug PeekS(CocoaMessage(0, CocoaMessage(0, CurrentLocale, "displayNameForKey:$",
  @"kCFLocaleLanguageCodeKey", "value:", LanguageCode), "UTF8String"),
  -1, #PB_UTF8)
And this example code displays "Deutsch (Deutschland)" on my iMac:

Code: Select all

CurrentLocale = CocoaMessage(0, 0, "NSLocale currentLocale")
LocaleID = CocoaMessage(0, CurrentLocale, "objectForKey:$", @"kCFLocaleIdentifierKey")
Debug PeekS(CocoaMessage(0, CocoaMessage(0, CurrentLocale, "displayNameForKey:$",
  @"kCFLocaleIdentifierKey", "value:", LocaleID), "UTF8String"), -1, #PB_UTF8)
User avatar
Danilo
Addict
Addict
Posts: 3037
Joined: Sat Apr 26, 2003 8:26 am
Location: Planet Earth

Re: GetUserLanguage()

Post by Danilo »

Shardik wrote:
Rescator wrote:No idea what that means but hopefully it makes sense to the mac coders here.
This example code displays "Deutsch" on my iMac:

Code: Select all

CurrentLocale = CocoaMessage(0, 0, "NSLocale currentLocale")
LanguageCode = CocoaMessage(0, CurrentLocale, "objectForKey:$",
  @"kCFLocaleLanguageCodeKey")
Debug PeekS(CocoaMessage(0, CocoaMessage(0, CurrentLocale, "displayNameForKey:$",
  @"kCFLocaleLanguageCodeKey", "value:", LanguageCode), "UTF8String"),
  -1, #PB_UTF8)
And this example code displays "Deutsch (Deutschland)" on my iMac:

Code: Select all

CurrentLocale = CocoaMessage(0, 0, "NSLocale currentLocale")
LocaleID = CocoaMessage(0, CurrentLocale, "objectForKey:$", @"kCFLocaleIdentifierKey")
Debug PeekS(CocoaMessage(0, CocoaMessage(0, CurrentLocale, "displayNameForKey:$",
  @"kCFLocaleIdentifierKey", "value:", LocaleID), "UTF8String"), -1, #PB_UTF8)
Thanks Rescator and Shardik! :)

Made procedures out of it: GetUserLanguage() & GetUserLanguageIdentifier()
User avatar
Rescator
Addict
Addict
Posts: 1769
Joined: Sat Feb 19, 2005 5:05 pm
Location: Norway

Re: GetUserLanguage()

Post by Rescator »

Edited the first post.
Made a more complete example based on the function ideas at http://www.purebasic.fr/english/viewtop ... 63#p419763
Renamed/changed the function names.

If somebody could make a Linux variant,
and also make sure the mac one matches this then we'd have a nice cross platform example that people can use or Fred and Freak can consider for further evaluation.
Post Reply