![Free-eBooks.net](/resources/img/logo-nfe.png)
![All New Design](/resources/img/allnew.png)
Introduction
If you are displaying text, it would be good if you design your program so that all text can be internationalised.
This means that instead of using text that can't be changed to accommodate other languages, you design your program to use tokens, which will then display text based on the users language settings.
An example use could be something like :
DDgui_widget(“hello”,language$(“{{hello}}”),0,0)
with the function language$ taking the token string “{{hello}}” and converting it to the word “hello” in English, French, German or any other language.
Thus, I present the following internationalisation routine. It was converted from another BASIC language, and is designed to convert tokens to text.
Additional language files would go in Media/Language/x/LANGUAGE.INI, where “x” is the LOCALE string (returned from PLATFORMINFO(“LOCALE”)
REQUIRE "defaultLanguage.c"
INLINE
}
typedef int size_t;
extern char *defaultLanguage[];
extern "C" size_t strlen(const char *s);
namespace __GLBASIC__ {
ENDINLINE
CONSTANT LANGUAGE_ACTIVE% = 1
TYPE TLocalisation
flags%
languageDef$
languageVer$
languageAuth$
map AS TMap
SECTION_NAME$ = "LanguageDefinition"
FUNCTION Initialise%:flags%=0
IF self.map.Initialise()
self.flags%=flags%
self.languageDef$=""
self.languageVer$=""
self.languageAuth$=""
RETURN TRUE
ELSE
RETURN FALSE
ENDIF
ENDFUNCTION
FUNCTION Destroy%:
self.map.Destroy()
ENDFUNCTION
FUNCTION SetLanguageDetails%:langDef$,langVer$,langAuth$
self.languageDef$=langDef$
self.languageVer$=langVer$
self.languageAuth$=langAuth$
ENDFUNCTION
FUNCTION AddLanguage%:key$,value$
self.map.Add(key$,value$)
ENDFUNCTION
FUNCTION LoadLanguage%:override$=""
LOCAL path$,language$,temp$
LOCAL loop%
LOCAL split$[]
path$="Media/Language/"
IF override$=""
language$=PLATFORMINFO$("LOCALE")
ELSE
language$=override$
ENDIF
path$=path$+language$+"/LANGUAGE.INI"
IF DOESFILEEXIST(path$)
INIOPEN path$
self.languageDef$=INIGET$(self.SECTION_NAME$,"LanguageID")
self.languageVer$=INIGET$(self.SECTION_NAME$,"LanguageVersion")
self.languageAuth$=INIGET$(self.SECTION_NAME$,"LanguageAuthor")
loop%=0
temp$=INIGET$(self.SECTION_NAME$,loop%,"")
DEBUG "T:"+temp$+"\n"
KEYWAIT
WHILE temp$<>""
DIM split$[0]
IF SPLITSTR(temp$,split$[],",")>=2
self.map.Add(split$[0],split$[1])
ENDIF
INC loop%
temp$=INIGET$(self.SECTION_NAME$,loop%,"")
WEND
INIOPEN ""
RETURN TRUE
ELSE
RETURN FALSE
ENDIF
ENDFUNCTION
FUNCTION GetLanguageID$:
RETURN self.languageDef$
ENDFUNCTION
FUNCTION GetLanguageVersion$:
RETURN self.languageVer$
ENDFUNCTION
FUNCTION GetLanguageAuthor$:
RETURN self.languageAuth$
ENDFUNCTION
FUNCTION LocaliseText$:string$
LOCAL loop%
LOCAL one$,tmpPrevChar$,tmpText$
LOCAL tmpCount%
LOCAL tmpOpening%[]
IF bAND(self.flags%,LANGUAGE_ACTIVE%)
DIM tmpOpening%[LEN(string$)/2]
FOR loop%=0 TO BOUNDS(tmpOpening%[],0)-1; tmpOpening%[loop%]=0; NEXT
loop%=0
tmpPrevChar$=""
tmpCount%=0
WHILE loop%<LEN(string$)
one$=MID$(string$,loop%,1)
SELECT one$
CASE "{" // Start of token
IF tmpPrevChar$ = one$
tmpOpening%[tmpCount%] = loop%+1
INC tmpCount%
tmpPrevChar$=""
ELSE
tmpPrevChar$=one$
ENDIF
CASE "}" // End of token
IF tmpPrevChar$ = one$
DEC tmpCount%
tmpText$=MID$(string$,tmpOpening%[tmpCount%],loop%-tmpOpening%[tmpCount%]-1)
SELECT UCASE$(tmpText$)
CASE "DOCS"
tmpText$=PLATFORMINFO$("DOCUMENTS")
CASE "WHAT"
tmpText$=PLATFORMINFO$("")
CASE "ID"
tmpText$=PLATFORMINFO$("ID")
CASE "DEVICE"
tmpText$=PLATFORMINFO$("DEVICE")
CASE "COMPILED"
tmpText$=PLATFORMINFO$("COMPILED")
DEFAULT
DEBUG "Tmp : "+tmpText$+"\n"
tmpText$ = self.LocaliseText$(self.map.GetValue$(tmpText$))
ENDSELECT
string$=MID$(string$,0,tmpOpening%[tmpCount%]-2)+tmpText$+MID$(string$,loop%+1)
INC loop%,LEN(tmpText$)-(loop%+3-tmpOpening%[tmpCount%])
tmpPrevChar$=""
ELSE
tmpPrevChar$=one$
ENDIF
DEFAULT
tmpPrevChar$=""
ENDSELECT
INC loop%
WEND
ENDIF
RETURN string$
ENDFUNCTION
ENDTYPE
FUNCTION Localise_TextHelper%:BYREF index%,BYREF token$,BYREF value$
token$=""
value$=""
INLINE
if ((defaultLanguage[index]) && (strlen(defaultLanguage[index])>0))
{
token_Str=DGStr(defaultLanguage[(int) index]);
INC(index);
if ((defaultLanguage[(int) index]) && (strlen(defaultLanguage[index])>0))
{
value_Str=DGStr(defaultLanguage[(int) index]);
INC(index);
}
return (DGNat) TRUE;
}
return (DGNat) FALSE;
ENDINLINE
ENDFUNCTION
//! Use default text if locale isn't available
FUNCTION Localise_UseDefault%:localise AS TLocalisation
LOCAL result%,index%,token$,value$
IF localise.LoadLanguage()=FALSE
index%=0
result%=Localise_TextHelper(index%,token$,value$)
WHILE result%=TRUE
localise.AddLanguage(token$,value$)
result%=Localise_TextHelper(index%,token$,value$)
WEND
localise.SetLanguageDetails("English (British)","1.0.0.0","Default Language")
RETURN FALSE
ELSE
RETURN TRUE
ENDIF
ENDFUNCTION
An example of use would be :
LOCAL localise as Tlocalise
IF localise.Initialise(LANGUAGE_ACTIVE%)=FALSE
DEBUG "Unable to initialise TLocalisation"
RETURN FALSE
ENDIF
localise_UseDefault(localise)
DEBUG “Result : “+localise.LocaliseText$(“{{hello}}”)
As it stands, the routine could not be compiled as a default language string is need.
First, you need the Tmap routine. This is covered in slightly more detail in the GLBasic Programmers Reference Guide, but is included here for completeness :
// --------------------------------- //
// Project: ddd
// Start: Tuesday, October 05, 2010
// IDE Version: 8.120
TYPE tKeyValue
key$
value$
ENDTYPE
TYPE TMap
list[] AS tKeyValue
//! Initialise type
//\param None
//\return TRUE
FUNCTION Initialise%:
DIM self.list[0]
RETURN TRUE
ENDFUNCTION
//! Destroy type
//\param None
//\return TRUE
FUNCTION Destroy%:
DIM self.list[0]
RETURN TRUE
ENDFUNCTION
//! Add a key and value to the internal array, and sort array afterwards
//\param key$ - Key to add
//\param value$ - Value of key
//\return TRUE if the key and value has been added, FALSE otherwise
FUNCTION Add%:key$,value$
LOCAL temp AS tKeyValue
IF self.search(key$)<0
// Found
temp.key$=key$
temp.value$=value$
DIMPUSH self.list[],temp
SORTARRAY self.list[],0
RETURN TRUE
ENDIF
RETURN FALSE
ENDFUNCTION
FUNCTION search%:key$
LOCAL up%,down%,mid%
up%=0
down%=BOUNDS(self.list[],0)-1
WHILE up%<down%
mid%=(up%+down%)/2
IF self.list[mid%].key$>key$
down%=MAX(mid%-1,up%)
ELSEIF self.list[mid%].key$<key$
up%=MIN(down%,mid%+1)
ELSE
RETURN mid% // Found
ENDIF
WEND
IF BOUNDS(self.list[],0)>0 AND self.list[up%].key$=key$
RETURN up%
ELSE
RETURN -1
ENDIF
ENDFUNCTION
FUNCTION Debug%:
LOCAL loop AS tKeyValue
FOREACH loop IN self.list[]
DEBUG "Key : "+loop.key$+" Value : "+loop.value$+"\n"
NEXT
DEBUG "Number of keys and values : "+BOUNDS(self.list[],0)+"\n"
ENDFUNCTION
FUNCTION GetValue$:key$,notFound$="NOT_FOUND"
LOCAL index%
index%=self.search(key$)
IF index%>=0
RETURN self.list[index%].value$
ELSE
RETURN notFound$
ENDIF
ENDFUNCTION
FUNCTION DeleteKey%:key$
LOCAL index%
index%=self.search(key$)
IF index%>=0
DIMDEL self.list[],index%
RETURN TRUE
ELSE
RETURN FALSE
ENDIF
ENDFUNCTION
FUNCTION Count%:
RETURN BOUNDS(self.list[],0)
ENDFUNCTION
ENDTYPE
Usually, the text would be added during compilation. Its a simple procedure to add to your programs – save the following as a .c file into the same place as your project (calling it defaultLanguage.c) :
char *defaultLanguage[]={
"startgame", "Start Game",
"continuegame", "Continue Game",
"options", "Options",
"instructions", "Instructions",
"hiscores", "Hiscores",
"quitgame", "Quit Game",
"arcadegame", "Player V Computer Game",
"standardgame", "Standard Game",
"upto3", "Up To 3 Wins",
"prev", "Previous Menu",
"\0", "\0"
};
And now, if you use “{{startgame}}”, you could get “Start Game” as the resulting string. If you had a French language file setup, “{{startgame}}” could return “Commencez le jeu”.