The simplest way would be like above with arrays. However if you want a key (name) based localization in a Map, I use this format which is also relatively common but IDK if it has a specific name. You can load from a text file or catch your language files from a Buffer (preferably store them with BOM); the code is not entire glitch free, when there's no BOM ascii is automatically assumed.
If you use XML dialogs for the GUI you could add an attribute to the gadget or window and other elements "lang-id" with the key as value. After loading the dialog XML file you go through all XML/GUI elements and when a "lang-id" appears you replace the default text with the localized text, before creating/opening the dialog windows.
Sorry if the code gets a little long, 90% is only about text file/buffer manipulation, the mini lib is at the bottom before the DataSection and the demo.
Code: Select all
; Text file helper procedures
; by benubi
; Public Domain / Free software
Prototype.i EnumBufferLinesCallback(*userdata, String$) ; Returns 0 to continue enumeration OR NON-ZERO (e.g. error number)
Procedure$ BytesToHex(*B.ascii, count)
Protected result$ = Space((count * 3) - 1)
Protected i
Protected *S = @result$
While i < count
PokeS(*S, RSet(Hex(*B\a), 2, "0"), 2, #PB_Unicode | #PB_String_NoZero)
*S + 6
*B + 1
i + 1
Wend
ProcedureReturn result$
EndProcedure
Procedure$ GuessEOL(*Start.Byte, *Limit, Type = -1) ; *Start: memory pointer, *Limit = *Start + ByteSize, Type = #PB_Any, #PB_Unicode, #PB_UTF8 or #PB_Ascii
;
; Guess & return EOL sequence of a text file (CRLF, LFCR, LF or CR)
; UTF8_BOM = $BFBBEF
; UTF16_BOM = $FEFF
Protected *C.Character, *A.Ascii , cr , lf
If type = -1 ; Default is PB internal format (unicode for all current PB versions, 2023)
type = #PB_Unicode
EndIf
If type = #PB_Unicode
*C = *start
;Debug "unicode"
While *Limit > *C
Select *C\C
Case 13
If cr
; Debug "EOL=CR"
ProcedureReturn #CR$
ElseIf lf
; Debug "EOL=LF"
ProcedureReturn #LFCR$
EndIf
cr + 1
Case 10
If lf
; Debug "EOL=LF"
ProcedureReturn #LF$
ElseIf cr
; Debug "EOL=CR"
ProcedureReturn #CRLF$
EndIf
lf + 1
Default
If lf
; Debug "EOL=LF"
ProcedureReturn #LF$
ElseIf cr
; Debug "EOL=CR"
ProcedureReturn #CR$
EndIf
EndSelect
*C + 2
Wend
Else
*A = *Start
; Debug "ascii/utf8"
While *Limit > *A
Select *a\a
Case #CR
If cr
; Debug "EOL=CR"
ProcedureReturn #CR$
ElseIf lf
; Debug "EOL=LF"
ProcedureReturn #LFCR$
EndIf
cr + 1
Case #LF
If lf
; Debug "EOL=LF"
ProcedureReturn #LF$
ElseIf cr
; Debug "EOL=CRLF"
ProcedureReturn #CRLF$
EndIf
lf + 1
Default
If lf
; Debug "EOL=LF"
ProcedureReturn #LF$
ElseIf cr
; Debug "EOL=CR"
ProcedureReturn #CR$
EndIf
EndSelect
*A + 1
Wend
EndIf
; If cr
; ProcedureReturn #CR$
; ElseIf lf
; ProcedureReturn #LF$
; Else
; ProcedureReturn #LFCR$
; EndIf
EndProcedure
Procedure.i CountBufferLines(*Buffer, BufferSize, Type = -1, Eol$ = #Null$)
Protected BOM , BOMLEN
Protected charsize
If Type = -1 And BufferSize >= 3
CopyMemory(*Buffer, @BOM, 3)
If $BFBBEF = BOM ; UTF8 check
Type = #PB_UTF8
BOMLEN = 3
EndIf
EndIf
If BOM & $FFFF = $FEFF ; UTF16 check
BOMLEN = 2
type = #PB_Unicode
EndIf
If type = -1 ; no BOM or defined character format => switch to ascii
type = #PB_Ascii ; use ascii
EndIf
If type = #PB_UTF16
charsize = 2
Else
charsize = 1
EndIf
If Eol$ = #Null$ ; = Empty$ or ""
Eol$ = GuessEOL(*Buffer, BufferSize + *Buffer, type)
EndIf
Protected QEOL.q
Protected eol_len = Len(Eol$)
Protected eol_blen = StringByteLength(eol$, Type)
Protected *Z1.Ascii, *Z2.Ascii, c
Protected *Lim, *start, *limeol, *QEOL
; Debug "EOL$="+BytesToHex(@EOL$, eol_blen)
; Debug "EOLLEN="+eol_len+" / "+eol_blen
; Debug "Type="+Str(type )
; Debug "Ascii: "+#PB_Ascii
; Debug "UTF8:" +#PB_UTF8
; Debug "UTF16: "+#PB_UTF16
; Debug "charsize="+charsize
*start = *Buffer + *BOMLEN
*Lim = *Buffer + BufferSize
*QEOL = @QEOL
PokeS(*QEOL, Eol$, eol_len, Type | #PB_String_NoZero)
*limeol = *QEOL + eol_blen
While *start < *Lim
*z1 = *start
*Z2 = *qeol
While *Z1\a = *z2\a And *Z1 < *LIM And *Z2 < *limeol
*Z1 + 1:*Z2 + 1
Wend
If *z2 = *limeol
*start = *z1
c = c + 1
If *start = *Lim
ProcedureReturn c
EndIf
Continue
EndIf
*Start + charsize
Wend
ProcedureReturn c + 1 ; unterminated last/lone line /empty file
EndProcedure
Procedure.i BufferLines(*Buffer, BufferSize, List Result.s(), Type = -1, Eol$ = #Null$) ; *Buffer: memory pointer of text file, BufferSize: BYTE size of the file in memory, Result.s(): result list where to return the file's lines, Type: character type (force ascii, utf8, unicode, #PB_Any), EOL$: End Of Line sequence. Leave empty to guess or set to force CRLF$ etc.
; --------------------------------------------------------------------
; count = BufferLines(*Buffer, BufferSize, List Result.s(), Type, Eol$)
;
; The procedure adds text lines from a buffer to a PB String List.
;
; *Buffer = *memory pointer of the text file (ascii, utf-8 or utf-16)
; BufferSize = Byte size of the buffer
; Result.s() = The results list where to add the text file's lines
; Type = #PB_Any (default):guess/read BOM, #PB_Ascii, #PB_Unicode, #PB_UTF8
; Eol$ = #Empty$ or #Null$: Guess End Of Line sequence, other$: force use of EOL sequence e.g. Eol$=#CRLF$
; --------------------------------------------------------------------
;
Protected eol.q, eolbytes, charsize, i, *EOL, chars
Protected *C.Character, *A.ascii , *Z1.ascii, *Z2.ascii , *eolim
Protected *START, *LIM
Protected BOM.i, BOMLEN
*START = *Buffer
*LIM = *Buffer + BufferSize
If Type = -1 And BufferSize >= 3
CopyMemory(*Buffer, @BOM, 3)
If $BFBBEF = BOM
BOMLEN = 3
Type = #PB_UTF8 | #PB_ByteLength
EndIf
EndIf
If BOM & $FFFF = $FEFF
BOMLEN + 2
type = #PB_Unicode
EndIf
If type = -1
type = #PB_Ascii
ElseIf type = #PB_UTF8
type = type | #PB_ByteLength
EndIf
If #Empty$ = Eol$ Or #Null$ = Eol$
Eol$ = GuessEOL(*Buffer, *LIM, Type)
If #Empty$ = Eol$
Eol$ = #CRLF$
; Debug "EMPTY EOL?!"
EndIf
EndIf
If type = 2 : charsize = 2 : Else : charsize = 1 : EndIf
PokeS(@eol, Eol$, Len(eol$), type | #PB_String_NoZero)
eolbytes = Len(eol$) * charsize
*EOL = @eol
*eolim = *EOL + eolbytes
*A = *Start + BOMLEN
If type = #PB_UTF8
type = type | #PB_ByteLength
EndIf
While *A < *LIM
*z1 = *A
*z2 = *EOL
While *z1\a = *z2\a And *z1 < *lim And *z2 < *eolim
*Z1 + 1
*Z2 + 1
Wend
If *z2 = *eolim
AddElement(Result())
Result() = PeekS(*A, chars, Type)
*Start = *Start + (chars * charsize) + eolbytes
i + 1
chars = 0
If *START >= *LIM
ProcedureReturn i
EndIf
Continue
EndIf
chars + 1
*A + charsize
Wend
If *START < *LIM
AddElement(Result())
Result() = PeekS(*START, chars, Type)
EndIf
Debug i
ProcedureReturn i
EndProcedure
Procedure.i EnumBufferLines(*Buffer, BufferSize, EnumBufferLinesCallback.EnumBufferLinesCallback, *CallbackCookie = 0, Type = -1, Eol$ = #Null$) ; Enumerate text file lines in *buffer to a Callback procedure
;
; EnumBufferLines(*Buffer, BufferSize, EnumBufferLinesCallback, *CallbackCookie, Type, Eol$)
;
; Enumerate lines form a text file to a callback.
; *Buffer = *memory pointer of the text file (ascii, utf-8 or utf-16)
; BufferSize = Byte size of the buffer
; EnumBufferLinesCallback: Pointer to @YourCallbackProcedure() where to enumerate the text file line string$'s. The Callback has the format: Callback(*Cookie, String$), where *cookie is an arbitrary (optional) value set by the user
; *CallbackCookie: *Userdata
; Type = #PB_Any (default):guess text format (read bom/guess); other: force #PB_Ascii, #PB_Unicode, #PB_UTF8 "text file" format in *Buffer
; EOL$ = #Empty$ or force EOL sequence
;
Protected eol.i, eolbytes, charsize, *EOL, chars
Protected *C.Character, *A.ascii , *Z1.ascii, *Z2.ascii , *eolim
Protected *START, *LIM
Protected BOM.i, BOMLEN
Protected result = #Null
*START = *Buffer
*LIM = *Buffer + BufferSize
If Type = -1 And BufferSize >= 3
CopyMemory(*Buffer, @BOM, 3)
If $BFBBEF = BOM
; Debug ">>> found UTF8 BOM"
Type = #PB_UTF8
BOMLEN = 3
EndIf
EndIf
If BOM & $FFFF = $FEFF
; Debug ">>> Found Unicode UTF16 BOM"
type = #PB_UTF16
BOMLEN = 2
EndIf
If type = -1
; Debug ">>> switch to default/ascii"
type = #PB_Ascii
EndIf
If EOL$ = #Empty$
; Debug ">>>Guess EOL..."
Eol$ = GuessEOL(*Buffer, *LIM, Type )
If #Empty$ = Eol$
; Debug ">>> Guess EOL: NO EOL found, set default CRLF"
Eol$ = #CRLF$
Else
; Debug ">>> Guessed EOL:"+BytesToHex(@eol$,Len(eol$)*2)
EndIf
EndIf
If type = #PB_UTF16 : charsize = 2 : Else : charsize = 1 : EndIf
*EOL = @eol
PokeS(*eol, Eol$, Len(Eol$), type | #PB_String_NoZero)
eolbytes = Len(eol$) * charsize
If type = #PB_UTF8
type = type | #PB_ByteLength
EndIf
*eolim = *EOL + eolbytes
*A = *Buffer + BOMLEN
*START = *A
While *A < *LIM ; Go through buffer character by character
*z1 = *A
*z2 = *EOL
While *z1\a = *z2\a And *z1 < *lim And *z2 < *eolim ; Check for EOL byte by byte
*Z1 + 1
*Z2 + 1
Wend
If *z2 = *eolim ; Found EOL, calling callback procedure
result = EnumBufferLinesCallback(*CallbackCookie, PeekS(*START, chars, Type))
If result
ProcedureReturn result
EndIf
*Start = *START + (chars * charsize) + eolbytes
*A = *START
i + 1
chars = 0
If *A => *LIM
ProcedureReturn result
EndIf
Continue
EndIf
chars + 1
*A + charsize
Wend
If *Start < *LIM ; check for unterminated last line, call the callback procedure if it's the case
result = EnumBufferLinesCallback(*CallbackCookie, PeekS(*Start, chars, Type))
EndIf
ProcedureReturn result
EndProcedure
Procedure.i BLOAD(File$, *FileSize.INTEGER) ; Loads a file to new buffer. Returns a *Memory pointer, and file size (write in *FileSize pointer parameter, optional).
Protected fh, *Memory, LOF
fh = ReadFile( - 1, file$) ; Open the file to load in read-mode
If fh
Lof = Lof(fh) ; Get file size for buffer allocation
If *FileSize ; Return file size
*FileSize\i = LOF
EndIf
If LOF > 1
; Check for minimum memory allocation size (2 bytes on Windows, IDK for the other OS'es)
; Allocate memory without zero-ing it (will be completely overwritten after ReadData()).
*Memory = AllocateMemory(Lof, #PB_Memory_NoClear)
Else ; file size <= 1 bytes
*Memory = AllocateMemory(2) ; Minimum malloc size (Windows or all OS?)
EndIf
If *Memory
ReadData(fh, *Memory, LOF) ; Read complete file in one step
EndIf
CloseFile(fh) ; Close the file
ProcedureReturn *Memory
EndIf
ProcedureReturn #False
EndProcedure
Structure _load_text_file_info
*ArrayBase
index.i
EndStructure
Procedure _load_text_file_callback(*Info._load_text_file_info, String$)
Protected *S.STRING = *Info\ArrayBase + (SizeOf(STRING) * *Info\index)
*info\index = *info\index + 1
*S\s = String$
;Debug "String = "+String$
ProcedureReturn #Null
EndProcedure
Procedure.i Load_Text_File(File$, Array Result.s(1))
Protected c, i
Protected *Memory, fsize
Protected info._load_text_file_info
*Memory = BLOAD(file$, @fsize)
If *Memory
c = CountBufferLines(*Memory, fsize)
ReDim Result(c + 1)
info\ArrayBase = @result() ; + (2 * SizeOf(Integer))
info\index = 0
EnumBufferLines(*Memory, fsize, @_load_text_file_callback(), @info)
FreeMemory(*Memory)
EndIf
ProcedureReturn c
EndProcedure
; ---------- Lang files
Procedure Lang_From_Array(Array TXT.s(1), Map L.s())
Protected line$
Protected c, i , *C.character, klen, poffset, keys
c = ArraySize(TXT())
For i = 0 To c - 1 Step 1
line$ = Txt(i)
If line$ = #Empty$
; empty line - ignore
ElseIf Left(line$,1)="#"
; commentary line
Else
*C = @line$
klen = 0
poffset = 0
While *C\c And *C\c <> 32 And *C\c <> 9
klen + 1
*C + SizeOf(Character)
Wend
If klen > 0
; key length must be above 0.
; Lines starting with a tab Or space will be ignored.
poffset = SizeOf(Character) * klen
While *C\c And (*C\c = #TAB Or *C\c = 32)
*C + SizeOf(Character)
poffset + SizeOf(Character)
Wend
keys + 1
AddMapElement(L(), Left(line$, klen), #PB_Map_NoElementCheck)
L() = UnescapeString(PeekS(@line$ + poffset))
; Debug "key:"+Chr(34)+Left(line$,klen)+Chr(34)
; Debug Chr(34)+l()+Chr(34)
Else
EndIf
EndIf
Next
ProcedureReturn keys
EndProcedure
Procedure Catch_Lang(*Buffer, size, Map L.s(), type=-1) ; type = character format (Ascii, utf8, utf16)
Protected c = CountBufferLines(*Buffer, size)
Protected Dim TXT$(c)
Protected _info._load_text_file_info
_info\ArrayBase = TXT$()
_info\index = 0
If EnumBufferLines(*Buffer, size, @_load_text_file_callback(), @_info, type) = 0
ProcedureReturn Lang_From_Array(Txt$(), L())
EndIf
EndProcedure
Procedure Load_Lang(File$, Map L.s())
Protected Dim TXT$(1)
If Load_Text_File(File$, TXT$())
ProcedureReturn Lang_From_Array(TXT$(), L())
EndIf
EndProcedure
; ---------- lang files end
CompilerIf #PB_Compiler_IsMainFile
DataSection
mylangfile:
Data.s Chr($FEFF)+"# (c) 2044 Snake Plissken Industries"+#CRLF$+
"# ##################################"+#CRLF$+
"LANGUAGE_NAME Deutsch"+#CRLF$+
"LANGUAGE_NAME_ENGLISH German"+#CRLF$+
"# ##################################"+#CRLF$+
"APPLICATION_NAME_ENGLISH The ultimate guide for bug burger gourmets"+#CRLF$+
"APPLICATION_NAME Der ultimative Wegweiser für Bugburger Gourmets"+#CRLF$+
"APPLICATION_NAME_SHORT Bugburger Gourmet App"+#CRLF$+
"APPLICATION_COPYRIGHT (c) 2044 Snake Plissken Industries"+#CRLF$+
"APPLICATION_ABOUT_TEXT Linie 1\nLinie 2\nLinie 3\nUsw..\n"+#CRLF$+
"APPLICATION_ABOUT_TITLE Über Bugburger Gourmet..."+#CRLF$+
"# ##################################"+#CRLF$+
"MENUTITLE_FILE Datei"+#CRLF$+
"MENUITEM_OPEN Öffnen"+#CRLF$+
"MENUITEM_CLOSE Schließen"+#CRLF$+
"MENUITEM_QUIT Verlassen"+#CRLF$+
"MENUTITLE_EDIT Bearbeiten"+#CRLF$+
"# ##################################"+#CRLF$+
"# Date format"+#CRLF$+
"DATE_FORMAT_SHORT %dd.%mm.%yyyy"+#CRLF$+
"TIME_FORMAT_SHORT %hh:%ii:%ss"+#CRLF$+
""+#CRLF$+
"#### EOF"+#CRLF$
end_mylangfile:
EndDataSection
NewMap Lang.s()
Catch_Lang(?mylangfile, ?end_mylangfile - ?mylangfile, Lang())
;file$ = OpenFileRequester("Select language file","de-de.lang","lang files|*.lang|All files|*.*",0)
;Load_Lang(file$, Lang())
Debug FormatDate(LANG("DATE_FORMAT_SHORT"),Date())
MessageRequester(Lang("APPLICATION_ABOUT_TITLE"), Lang("APPLICATION_NAME")+#CRLF$+Lang("APPLICATION_COPYRIGHT")+#CRLF$+#CRLF$+Lang("APPLICATION_ABOUT_TEXT"), #PB_MessageRequester_Info)
CompilerEndIf