Sign in Join
Wiki copytemporary

Translations / i18n / localizations for programs/ko: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Translations / i18n / localizations for programs/ko

OldNewLine
1+## 개요
2+
3+This is about how a program can use different strings for various languages like english, chinese, german, finnish, italian, ... . Basically it works like this: Add a *resourcestring* for every caption, compile to get the .rst and/or .po files (the IDE can do this automatically), create one translated .po file for each language (there are free graphical tools) and use the functions of the LCL *translations* unit to load the right one at start of the program.
4+
5+## 날자, 시간 그리고 숫자형태
6+
7+Under Linux, BSD, Mac OS X there are several locales defining things like time and date format or the thousand separator. In order to initialize the RTL you need to iclude the clocale unit in the uses section of your program (lpr file).
8+
9+## Resourcestrings
10+
11+For example
12+
13+``` pascal
14+resourcestring
15+ Caption1 = 'Some text';
16+ HelloWorld1 = 'Hello World';
17+```
18+
19+These are like normal string constants, that means you can assign them to any string. For example
20+
21+``` pascal
22+Label1.Caption := HelloWorld1;
23+```
24+
25+When fpc compiles them, it creates for each unit a file **unitname.rst**, containing the resourcestring data (name + content).
26+
27+## .po Files
28+
29+There are many free graphical tools to edit .po files, which are simple text like the .rst files, but with some more options, like a header providing fields for author, encoding, language and date. Every FPC installation provides the tool **rstconv** (windows: rstconv.exe). This tool can be used to convert a .rst file into a .po file. The IDE can do this automatically. Some free tools: kbabel, poedit, virtaal.
30+
31+Virtaal has a translation memory containing source-target language pairs for items that you already translated once, and a translation suggestion function that shows already translated terms in various open source software packages. These function may save you a lot of work and improve consistency.
32+
33+Example of using rstconv directly:
34+
35+`rstconv -i unit1.rst -o unit1.po`
36+
37+## 번역
38+
39+For every language the .po file must be copied and translated. The LCL translation unit uses the common language codes (en=english, de=german, it=italian, ...) to search. For example the German translation of unit1.po would be unit1.de.po. To achieve this, copy the unit1.po file to unit1.de.po, unit1.it.po, and whatever language you want to support and then the translators can edit their specific .po file.
40+
41+> **Note:** For Brazilians/Portuguese: Lazarus IDE and LCL only have a Brazilian Portuguese translation and these files have 'pt_BR.po' extensions
42+
43+## IDE options for automatic updates of .po files
44+
45+- The unit containing the resource strings must be added to the package or project.
46+- You must provide a .po path, this means a separate directory. For example: create a sub directory *language* in the package / project directory. For projects go to the Project \> Project Options. For packages go to Options \> IDE integration.
47+
48+When this options are enabled, the IDE generates or updates the base .po file using the information contained in .rst and .lrt files (rstconv tool is then not necesary). The update process begins by collecting all existing entries found in base .po file and in .rst and .lrt files and then applying the following features it finds and brings up to date any translated .xx.po file.
49+
50+### Removal of Obsolete entries
51+
52+Entries in the base .po file that are not found in .rst and .lrt files are removed. Subsequently, all entries found in translated .xx.po files not found in the base .po file are also removed. This way, .po files are not cluttered with obsolete entries and translators don't have to translate entries that are not used.
53+
54+### Duplicate entries
55+
56+Duplicate entries occur when for some reason the same text is used for diferent resource strings, a random example of this is the file lazarus/ide/lazarusidestrconst.pas for the 'Gutter' string:
57+
58+``` pascal
59+ dlfMouseSimpleGutterSect = 'Gutter';
60+ dlgMouseOptNodeGutter = 'Gutter';
61+ dlgGutter = 'Gutter';
62+ dlgAddHiAttrGroupGutter = 'Gutter';
63+```
64+
65+A converted .rst file for this resource strings would look similar to this in a .po file:
66+
67+`#: lazarusidestrconsts.dlfmousesimpleguttersect`\
68+`msgid "Gutter"`\
69+`msgstr ""`\
70+`#: lazarusidestrconsts.dlgaddhiattrgroupgutter`\
71+`msgid "Gutter"`\
72+`msgstr ""`\
73+`etc.`\
74+
75+Where the lines starting with "#: " are considered comments and the tools used to translate this entries see the repeated msgid "Gutter" lines like duplicated entries and produce errors or warnings on loading or saving. Duplicate entries are considered a normal eventuality on .po files and they need to have some context attached to them. The msgctxt keyword is used to add context to duplicated entries and the automatic update tool use the entry ID (the text next to "#: " prefix) as the context, for the previous example it would produce something like this:
76+
77+`#: lazarusidestrconsts.dlfmousesimpleguttersect`\
78+`msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"`\
79+`msgid "Gutter"`\
80+`msgstr ""`\
81+`#: lazarusidestrconsts.dlgaddhiattrgroupgutter`\
82+`msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"`\
83+`msgid "Gutter"`\
84+`msgstr ""`\
85+`etc.`\
86+
87+On translated .xx.po files the automatic tool does one additional check: if the duplicated entry was already translated, the new entry gets the old translation, so it appears like being translated automatically.
88+
89+The automatic detection of duplicates is not yet perfect, duplicate detection is made as items are added to the list and it may happen that some untranslated entries are read first. So it may take several passes to get all duplicates automatically translated by the tool.
90+
91+### Fuzzy entries
92+
93+Changes in resource strings affect translations, for example if initially a resource string was defined like:
94+
95+``` pascal
96+dlgEdColor = 'Syntax highlight';
97+```
98+
99+this would produce a .po entry similar to this
100+
101+`#: lazarusidestrconsts.dlgedcolor`\
102+`msgid "Syntax highlight"`\
103+`msgstr ""`
104+
105+which if translated to Spanish (this sample was taken from lazarus history), may result in
106+
107+`#: lazarusidestrconsts.dlgedcolor`\
108+`msgid "Syntax highlight"`\
109+`msgstr "Color"`
110+
111+Suppose then that at a later time, the resource string has been changed to
112+
113+``` pascal
114+ dlgEdColor = 'Colors';
115+```
116+
117+the resulting .po entry may become
118+
119+`#: lazarusidestrconsts.dlgedcolor`\
120+`msgid "Colors"`\
121+`msgstr ""`
122+
123+Note that while the ID remained the same lazarusidestrconsts.dlgedcolor the string has changed from 'Syntax highlight' to 'Colors'. As the string was already translated the old translation may not match the new meaning. Indeed, for the new string probably 'Colores' may be a better translation. The automatic update tool notices this situation and produces an entry like this:
124+
125+`#: lazarusidestrconsts.dlgedcolor`\
126+`#, fuzzy`\
127+`#| msgid "Syntax highlight"`\
128+`msgctxt "lazarusidestrconsts.dlgedcolor"`\
129+`msgid "Colors"`\
130+`msgstr "Color"`
131+
132+In terms of .po file format, the "#," prefix means the entry has a flag (fuzzy) and translator programs may present a special GUI to the translator user for this item. In this case, the flag would mean that the translation in its current state is doubtful and needs to be reviewed more carefully by translator. The "#\|" prefix indicates what was the previous untranslated string of this entry and gives the translator a hint why the entry was marked as fuzzy.
133+
134+## Translating Forms, Datamodules and Frames
135+
136+When the i18n option is enabled for the project / package then the IDE automatically creates .lrt files for every form. It creates the .lrt file on saving a unit. So, if you enable the option for the first time, you must open every form once, move it a little bit, so that it is modified, and save the form. For example if you save a form *unit1.pas* the IDE creates a *unit1.lrt*. And on compile the IDE gathers all strings of all .lrt files and all .rst file into a single .po file (projectname.po or packagename.po) in the i18n directory.
137+
138+For the forms to be actually translated at runtime, you have to assign a translator to LRSTranslator (defined in LResources) in the initialization section to one of your units
139+
140+``` pascal
141+...
142+uses
143+ ...
144+ LResources;
145+...
146+...
147+initialization
148+ LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');
149+```
150+
151+However there's no TPoTranslator class (i.e a class that translates using .po files) available in the LCL. This is a possible implementation (partly lifted from DefaultTranslator.pas in the LCL): The following code isn't needed anymore if you use recent Lazarus 0.9.29 snapshots. Simply include DefaultTranslator in Uses clause.
152+
153+``` pascal
154+unit PoTranslator;
155+
156+{$mode objfpc}{$H+}
157+
158+interface
159+
160+uses
161+ Classes, SysUtils, LResources, typinfo, Translations;
162+
163+type
164+
165+ { TPoTranslator }
166+
167+ TPoTranslator=class(TAbstractTranslator)
168+ private
169+ FPOFile:TPOFile;
170+ public
171+ constructor Create(POFileName:string);
172+ destructor Destroy;override;
173+ procedure TranslateStringProperty(Sender:TObject;
174+ const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;
175+ end;
176+
177+implementation
178+
179+{ TPoTranslator }
180+
181+constructor TPoTranslator.Create(POFileName: string);
182+begin
183+ inherited Create;
184+ FPOFile:=TPOFile.Create(POFileName);
185+end;
186+
187+destructor TPoTranslator.Destroy;
188+begin
189+ FPOFile.Free;
190+ inherited Destroy;
191+end;
192+
193+procedure TPoTranslator.TranslateStringProperty(Sender: TObject;
194+ const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);
195+var
196+ s: String;
197+begin
198+ if not Assigned(FPOFile) then exit;
199+ if not Assigned(PropInfo) then exit;
200+{DO we really need this?}
201+ if Instance is TComponent then
202+ if csDesigning in (Instance as TComponent).ComponentState then exit;
203+{End DO :)}
204+ if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;
205+ s:=FPOFile.Translate(Content, Content);
206+ if s<>'' then Content:=s;
207+end;
208+
209+end.
210+```
211+
212+Alternatively you can transform the .po file into .mo using msgfmt (isn't needed anymore if you use recent 0.9.29 snapshot) and simply use the DefaultTranslator unit
213+
214+``` pascal
215+...
216+uses
217+ ...
218+ DefaultTranslator;
219+```
220+
221+which will automatically look in several standard places for a .po file (higher precedence) or .mo file (the disadvantage is that you'll have to keep around both the .mo files for the DefaultTranslator unit and the .po files for TranslateUnitResourceStrings). If you use DefaultTranslator, it will try to automatically detect the language based on the LANG environment variable (overridable using the --lang command line switch), then look in these places for the translation (LANG stands for the desired language, ext can be either po or mo):
222+
223+- \<Application Directory\>/\<LANG\>/\<Application Filename\>.\<ext\>
224+- \<Application Directory\>/languages/\<LANG\>/\<Application Filename\>.\<ext\>
225+- \<Application Directory\>/locale/\<LANG\>/\<Application Filename\>.\<ext\>
226+- \<Application Directory\>/locale/LC_MESSAGES/\<LANG/\>\<Application Filename\>.\<ext\>
227+
228+under unix-like systems it will also look in
229+
230+- /usr/share/locale/\<LANG\>/LC_MESSAGES/\<Application Filename\>.\<ext\>
231+
232+as well as using the short part of the language (e.g. if it is "es_ES" or "es_ES.UTF-8" and it doesn't exist it will also try "es")
233+
234+## Translating at start of program
235+
236+For every .po file, you must call TranslateUnitResourceStrings. The LCL po file is lclstrconsts. For example you do this in FormCreate of your MainForm:
237+
238+``` pascal
239+uses
240+ ..., gettext, translations;
241+
242+procedure TForm1.FormCreate(Sender: TObject);
243+var
244+ PODirectory, Lang, FallbackLang: String;
245+begin
246+ PODirectory := '/path/to/lazarus/lcl/languages/';
247+ GetLanguageIDs(Lang, FallbackLang);
248+ Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);
249+
250+ // the following dialog now shows translated buttons:
251+ MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);
252+end;
253+```
254+
255+## Compiling po files into the executable
256+
257+If you don't want to install the .po files, but put all files of the application into the executable, use the following:
258+
259+- Create a new unit (not a form!).
260+- Convert the .po file(s) to .lrs using tools/lazres:
261+
262+ ./lazres unit1.lrs unit1.de.po
263+
264+This will create an include file unit1.lrs beginning with
265+
266+``` pascal
267+LazarusResources.Add('unit1.de','PO',[
268+ ...
269+```
270+
271+- Add the code:
272+
273+``` pascal
274+uses LResources, Translations;
275+
276+resourcestring
277+ MyCaption = 'Caption';
278+
279+function TranslateUnitResourceStrings: boolean;
280+var
281+ r: TLResource;
282+ POFile: TPOFile;
283+begin
284+ r:=LazarusResources.Find('unit1.de','PO');
285+ POFile:=TPOFile.Create;
286+ try
287+ POFile.ReadPOText(r.Value);
288+ Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
289+ finally
290+ POFile.Free;
291+ end;
292+end;
293+
294+initialization
295+ {$I unit1.lrs}
296+```
297+
298+- Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.
299+
300+## Cross-platform method to determine system language
301+
302+The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.
303+
304+``` pascal
305+uses
306+ Classes, SysUtils {add additional units that may be needed by your code here}
307+ {$IFDEF win32}
308+ , Windows
309+ {$ELSE}
310+ , Unix
311+ {$IFDEF LCLCarbon}
312+ , MacOSAll
313+ {$ENDIF}
314+ {$ENDIF}
315+ ;
316+```
317+
318+``` pascal
319+function GetOSLanguage: string;
320+{platform-independent method to read the language of the user interface}
321+var
322+ l, fbl: string;
323+ {$IFDEF LCLCarbon}
324+ theLocaleRef: CFLocaleRef;
325+ locale: CFStringRef;
326+ buffer: StringPtr;
327+ bufferSize: CFIndex;
328+ encoding: CFStringEncoding;
329+ success: boolean;
330+ {$ENDIF}
331+begin
332+ {$IFDEF LCLCarbon}
333+ theLocaleRef := CFLocaleCopyCurrent;
334+ locale := CFLocaleGetIdentifier(theLocaleRef);
335+ encoding := 0;
336+ bufferSize := 256;
337+ buffer := new(StringPtr);
338+ success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);
339+ if success then
340+ l := string(buffer^)
341+ else
342+ l := '';
343+ fbl := Copy(l, 1, 2);
344+ dispose(buffer);
345+ {$ELSE}
346+ {$IFDEF LINUX}
347+ fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);
348+ {$ELSE}
349+ GetLanguageIDs(l, fbl);
350+ {$ENDIF}
351+ {$ENDIF}
352+ Result := fbl;
353+end;
354+```
355+
356+## Translating the IDE
357+
358+### Files
359+
360+The .po files of the IDE are in the lazarus source directory:
361+
362+- lazarus/languages strings for the IDE
363+- lcl/languages/ strings for the LCL
364+- ideintf/languages/ strings for the IDE interface
365+
366+### Translators
367+
368+- german translation is maintained by Joerg Braun.
369+- finnish translation is maintained by Seppo Suurtarla
370+- russian translation is maintained by Maxim Ganetsky
371+
372+When you want to start a new translation, ask on the mailing if someone is already working on that.
373+
374+Please read carefully: [[Lazarus Documentation|Translations]]
375+
376+## See also
377+
378+- [[IDE Development|IDE Development: Translations, i18n, lrt, po files]]
379+- [[Getting translation strings right]]
380+- [[Lazarus Documentation|Translations]]
381+
382+---
383+*From the Free Pascal wiki, page "Translations / i18n / localizations for programs/ko", as it stood on 2020-03-01 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history