Internationalisation (I18N) in FB code

Post your FreeBASIC tips and tricks here. Please don’t post your code without including an explanation.
TJF
Posts: 3601
Joined: Dec 06, 2009 22:27
Location: N47°, E15°
Contact:

Internationalisation (I18N) in FB code

Postby TJF » Aug 22, 2011 17:00

I18N means that a program uses different languages. The program is shiped with several translation files and it'll auto-choose the users language regarding the OS setting.

To realize this in FB you can find proposals here and here. Unfortunately they are uncomplete and a bit complicated (use of a C wrapper).


Here's an alternative (tested on win32 and LINUX)

To use the GNU tools for I18N save and include this header (libintl.bi)

Code: Select all

' This is file libintl.bi
' (FreeBasic binding for libintl version 0.18)
'
' translated with help of h_2_bi.bas by
' Thomas[ dot ]Freiherr[ at ]gmx[ dot ]net.
'
' Licence:
' (C) 2011 Thomas[ dot ]Freiherr[ at ]gmx[ dot ]net
'
' This library binding is free software; you can redistribute it
' and/or modify it under the terms of the GNU Lesser General Public
' License as published by the Free Software Foundation; either
' version 2 of the License, or (at your option) ANY later version.
'
' This binding is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
' Lesser General Public License for more details, refer to:
' http://www.gnu.org/licenses/lgpl.html
'
'
' Original license text:
'
'/* Message catalogs for internationalization.
   'Copyright (C) 1995-2002, 2004, 2005 Free Software Foundation, Inc.
   'This file is part of the GNU C Library.
   'This file is derived from the file libgettext.h in the GNU gettext package.

   'The GNU C Library is free software; you can redistribute it and/or
   'modify it under the terms of the GNU Lesser General Public
   'License as published by the Free Software Foundation; either
   'version 2.1 of the License, or (at your option) any later version.

   'The GNU C Library is distributed in the hope that it will be useful,
   'but WITHOUT ANY WARRANTY; without even the implied warranty of
   'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   'Lesser General Public License for more details.

   'You should have received a copy of the GNU Lesser General Public
   'License along with the GNU C Library; if not, write to the Free
   'Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
   '02111-1307 USA.  */

#IFNDEF __LIBINTL_TJF__
#DEFINE __LIBINTL_TJF__

#IFDEF __FB_WIN32__
 #PRAGMA push(msbitfields)
 #INCLIB "iconv"
 #INCLIB "intl"
#ENDIF

#LANG "fb"
EXTERN "C" ' (h_2_bi -P_oCD option)

' 001 start from: libintl.h2bi ==> libintl.h

#IFNDEF _LIBINTL_H
#DEFINE _LIBINTL_H 1

' file not found: features.h

#DEFINE __USE_GNU_GETTEXT 1
#DEFINE __GNU_GETTEXT_SUPPORTED_REVISION(major) IIF((major)= 0 , 1 , -1)

DECLARE FUNCTION gettext(BYVAL AS CONST ZSTRING PTR) AS ZSTRING PTR
DECLARE FUNCTION dgettext(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR) AS ZSTRING PTR
DECLARE FUNCTION __dgettext(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR) AS ZSTRING PTR
DECLARE FUNCTION dcgettext(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR, BYVAL AS INTEGER) AS ZSTRING PTR
DECLARE FUNCTION __dcgettext(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR, BYVAL AS INTEGER) AS ZSTRING PTR
DECLARE FUNCTION ngettext(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR, BYVAL AS ULONGINT) AS ZSTRING PTR
DECLARE FUNCTION dngettext(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR, BYVAL AS ULONGINT) AS ZSTRING PTR
DECLARE FUNCTION dcngettext(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR, BYVAL AS ULONGINT, BYVAL AS INTEGER) AS ZSTRING PTR
DECLARE FUNCTION textdomain(BYVAL AS CONST ZSTRING PTR) AS ZSTRING PTR
DECLARE FUNCTION bindtextdomain(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR) AS ZSTRING PTR
DECLARE FUNCTION bind_textdomain_codeset(BYVAL AS CONST ZSTRING PTR, BYVAL AS CONST ZSTRING PTR) AS ZSTRING PTR

#IF DEFINED (__OPTIMIZE__) AND NOT DEFINED (__cplusplus)
#DEFINE __need_NULL
#INCLUDE ONCE "crt/stddef.bi" '__HEADERS__: stddef.h

' file not found: locale.h

#DEFINE gettext(msgid) dgettext (NULL, msgid)
#DEFINE dgettext(domainname, msgid) dcgettext (domainname, msgid, LC_MESSAGES)
#DEFINE ngettext(msgid1, msgid2, n) dngettext (NULL, msgid1, msgid2, n)
#DEFINE dngettext(domainname, msgid1, msgid2, n) dcngettext (domainname, msgid1, msgid2, n, LC_MESSAGES)
#ENDIF ' DEFINED __OPTIM...
#ENDIF ' _LIBINTL_H

' 001 back from libintl.h2bi ==> libintl.h

DECLARE FUNCTION setlocale(BYVAL AS INTEGER, BYVAL AS CONST ZSTRING PTR) AS ZSTRING PTR

END EXTERN ' (h_2_bi -P_oCD option)
' 000 start from: libintl.h2bi, section __END_BI__

#IFDEF __FB_WIN32__
#PRAGMA pop(msbitfields)
#ENDIF

#DEFINE LC_CTYPE 0
#DEFINE LC_NUMERIC 1
#DEFINE LC_TIME 2
#DEFINE LC_COLLATE 3
#DEFINE LC_MONETARY 4
#DEFINE LC_MESSAGES 5
#DEFINE LC_ALL 6
#DEFINE LC_PAPER 7
#DEFINE LC_NAME 8
#DEFINE LC_ADDRESS 9
#DEFINE LC_TELEPHONE 10
#DEFINE LC_MEASUREMENT 11
#DEFINE LC_IDENTIFICATION 12

#DEFINE __(_T_) gettext(_T_)

#ENDIF ' __LIBINTL_TJF__

' Translated at 11-01-28 11:16:38, by h_2_bi (version 0.2.0.1,
' released under GPLv3 by Thomas[ dot ]Freiherr[ at ]gmx[ dot ]net)

'   Protocol: libintl.bi
' Parameters:
'                                  Process time [s]: 0.006257920642383397
'                                  Bytes translated: 2184
'                                      Maximum deep: 1
'                                SUB/FUNCTION names: 11
'                                mangled TYPE names: 0
'                                        files done: 0
'                                      files missed: 2
' features.h
' locale.h
'                                       #DEFINE FOLDERS#DEFINE : 0
'                                        #DEFINE MACROS#DEFINE : 5
' 1: #define #DEFINE BEGIN_DECLS
' 1: #define #DEFINE END_DECLS
' 11: #define #DEFINE THROW
' 33: #define #DEFINE const const
' 8: #define #DEFINE attribute_format_arg#DEFINE (x)
'                                       #DEFINE HEADERS#DEFINE : 0
'                                         #DEFINE TYPES#DEFINE : 0
'                                     #DEFINE POST_REPS#DEFINE : 0

On win32 it depends on libintl.dll and libiconv.dll. (On LINUX libc has inbuild gettext features -- no need for external libraries.)

Here's an example how to use it (I18N.bas)

Code: Select all

#INCLUDE "libintl.bi"

' set then system locale
VAR t = setlocale(LC_ALL, "")
?*t ' show locale setting

' your project file name
CONST PROJ_NAME = "I18N"

' set the path where the translations are located
bindtextdomain(PROJ_NAME, EXEPATH & "/locale")
' set the character encoding
bind_textdomain_codeset(PROJ_NAME, "UTF-8")
' set the filename for the translations
textdomain(PROJ_NAME)


' use a translatable text
?*__("Hello world!")

This code will search for translations in I18N.mo files. Ie a German translation will be searched in
    locale/de/LC_MESSAGES/I18N.mo
To generate the .mo file you need a I18N.pot file first. This file contains all texts marked as translatable in your source. Create it by using xgettext. This is a bit tricky since xgettext doesn't support FB syntax yet (feel free to file a feature request to xgettext developers). As a workaround I use awk syntax and declare all keywords like (execute in your project folder)
    xgettext --output=I18N.pot --no-wrap --from-code=utf-8 --package-name=I18N --package-version=0.0 --msgid-bugs-address=Email@provider.country --copyright-holder=me --keyword= --keyword=__ --keyword=gettext --keyword=dgettext:2 --keyword=dcgettext:2 --keyword=ngettext:1,2 --keyword=dngettext:2,3 --keyword=dcngettext:2,3 --keyword=gettext_noop --keyword=pgettext:1c,2 --keyword=dpgettext:2c,3 --keyword=dcpgettext:2c,3 --keyword=npgettext:1c,2,3 --keyword=dnpgettext:2c,3,4--keyword=dcnpgettext:2c,3,4 --language=awk --force-po I18N.bas
(Propably you don't need all the keyword declarations. You can use C syntax as well but it drops more warnings regarding unterminated strings.)

After the xgettext run a new I18N.pot file will be generated in your project folder like

Code: Select all

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR me
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: I18N 0.0\n"
"Report-Msgid-Bugs-To: Email@provider.country\n"
"POT-Creation-Date: 2011-08-22 18:16+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: I18N.bas:21
msgid "Hello world!"
msgstr ""

Load and translate this file with poedit. Create a subfolder
    locale/__YOUR_LANG__/LC_MESSAGES
(where __YOUR_LANG__ is replaced by your ISO 639 language code) and save the translation into this folder (I18N.po and I18N.mo file). The .mo file is binary and gets red on runtime. The .po file is similar to your .pot file, but includes the translated texts

Code: Select all

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: I18N 0.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-08-22 16:33+0200\n"
"PO-Revision-Date: 2011-08-22 16:37+0100\n"
"Last-Translator: Thomas Freiherr <Thomas[ dot ]Freiherr[ at ]gmx[ dot ]net>\n"
"Language-Team: awd <SAF>\n"
"Language: German\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Poedit-Language: German\n"
"X-Poedit-Country: Germany\n"
"X-Poedit-SourceCharset: utf-8\n"

#: I18N.bas:21
msgid "Hello world!"
msgstr "Hallo Welt!"


That's all. Compile and run your FB code to see "Hello world!" in your native language.

To run I18N in the original language use
    win: SET LANG=C & I18N.exe
    LINUX: LANG=C ./I18N
To run I18N in the a specific language (ie German) use
    win: SET LANG=de_DE.UTF-8 & I18N.exe
    LINUX: LANG=de_DE.UTF-8 ./I18N


To get further translations just pass the I18N.pot file to the translators. They will generate new __LANG__/LC_MESSAGES folders for you.

For GTK-GUI code try GladeToBac. It'll autocreate the .pot by scanning over all source files (*.bas, *.bi and *.ui or *.glade).
rgwan
Posts: 41
Joined: Aug 19, 2011 17:41
Contact:

Postby rgwan » Aug 23, 2011 6:26

Good!

Return to “Tips and Tricks”

Who is online

Users browsing this forum: No registered users and 2 guests