Sign in Join
Wiki copytemporary

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

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

OldNewLine
11## 개요
22
33This 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.
44
55## 날자, 시간 그리고 숫자형태
66
77Under 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).
88
99## Resourcestrings
1010
1111For example
1212
1313``` pascal
1414resourcestring
1515 Caption1 = 'Some text';
1616 HelloWorld1 = 'Hello World';
1717```
1818
1919These are like normal string constants, that means you can assign them to any string. For example
2020
2121``` pascal
2222Label1.Caption := HelloWorld1;
2323```
2424
2525When fpc compiles them, it creates for each unit a file **unitname.rst**, containing the resourcestring data (name + content).
2626
2727## .po Files
2828
2929There 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.
3030
3131Virtaal 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.
3232
3333Example of using rstconv directly:
3434
35-`rstconv -i unit1.rst -o unit1.po`
35+```text
36+rstconv -i unit1.rst -o unit1.po
37+```
3638
3739## 번역
3840
3941For 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.
4042
4143> **Note:** For Brazilians/Portuguese: Lazarus IDE and LCL only have a Brazilian Portuguese translation and these files have 'pt_BR.po' extensions
4244
4345## IDE options for automatic updates of .po files
4446
4547- The unit containing the resource strings must be added to the package or project.
4648- 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.
4749
4850When 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.
4951
5052### Removal of Obsolete entries
5153
5254Entries 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.
5355
5456### Duplicate entries
5557
5658Duplicate 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:
5759
5860``` pascal
5961 dlfMouseSimpleGutterSect = 'Gutter';
6062 dlgMouseOptNodeGutter = 'Gutter';
6163 dlgGutter = 'Gutter';
6264 dlgAddHiAttrGroupGutter = 'Gutter';
6365```
6466
6567A converted .rst file for this resource strings would look similar to this in a .po file:
6668
67-`#: lazarusidestrconsts.dlfmousesimpleguttersect`\
68-`msgid "Gutter"`\
69-`msgstr ""`\
70-`#: lazarusidestrconsts.dlgaddhiattrgroupgutter`\
71-`msgid "Gutter"`\
72-`msgstr ""`\
73-`etc.`\
69+```text
70+#: lazarusidestrconsts.dlfmousesimpleguttersect
71+msgid "Gutter"
72+msgstr ""
73+#: lazarusidestrconsts.dlgaddhiattrgroupgutter
74+msgid "Gutter"
75+msgstr ""
76+etc.
77+```
7478
7579Where 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:
7680
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.`\
81+```text
82+#: lazarusidestrconsts.dlfmousesimpleguttersect
83+msgctxt "lazarusidestrconsts.dlfmousesimpleguttersect"
84+msgid "Gutter"
85+msgstr ""
86+#: lazarusidestrconsts.dlgaddhiattrgroupgutter
87+msgctxt "lazarusidestrconsts.dlgaddhiattrgroupgutter"
88+msgid "Gutter"
89+msgstr ""
90+etc.
91+```
8692
8793On 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.
8894
8995The 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.
9096
9197### Fuzzy entries
9298
9399Changes in resource strings affect translations, for example if initially a resource string was defined like:
94100
95101``` pascal
96102dlgEdColor = 'Syntax highlight';
97103```
98104
99105this would produce a .po entry similar to this
100106
101-`#: lazarusidestrconsts.dlgedcolor`\
102-`msgid "Syntax highlight"`\
103-`msgstr ""`
107+```text
108+#: lazarusidestrconsts.dlgedcolor
109+msgid "Syntax highlight"
110+msgstr ""
111+```
104112
105113which if translated to Spanish (this sample was taken from lazarus history), may result in
106114
107-`#: lazarusidestrconsts.dlgedcolor`\
108-`msgid "Syntax highlight"`\
109-`msgstr "Color"`
115+```text
116+#: lazarusidestrconsts.dlgedcolor
117+msgid "Syntax highlight"
118+msgstr "Color"
119+```
110120
111121Suppose then that at a later time, the resource string has been changed to
112122
113123``` pascal
114124 dlgEdColor = 'Colors';
115125```
116126
117127the resulting .po entry may become
118128
119-`#: lazarusidestrconsts.dlgedcolor`\
120-`msgid "Colors"`\
121-`msgstr ""`
129+```text
130+#: lazarusidestrconsts.dlgedcolor
131+msgid "Colors"
132+msgstr ""
133+```
122134
123135Note 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:
124136
125-`#: lazarusidestrconsts.dlgedcolor`\
126-`#, fuzzy`\
127-`#| msgid "Syntax highlight"`\
128-`msgctxt "lazarusidestrconsts.dlgedcolor"`\
129-`msgid "Colors"`\
130-`msgstr "Color"`
137+```text
138+#: lazarusidestrconsts.dlgedcolor
139+#, fuzzy
140+#| msgid "Syntax highlight"
141+msgctxt "lazarusidestrconsts.dlgedcolor"
142+msgid "Colors"
143+msgstr "Color"
144+```
131145
132146In 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.
133147
134148## Translating Forms, Datamodules and Frames
135149
136150When 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.
137151
138152For 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
139153
140154``` pascal
141155...
142156uses
143157 ...
144158 LResources;
145159...
146160...
147161initialization
148162 LRSTranslator := TPoTranslator.Create('/path/to/the/po/file');
149163```
150164
151165However 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.
152166
153167``` pascal
154168unit PoTranslator;
155169
156170{$mode objfpc}{$H+}
157171
158172interface
159173
160174uses
161175 Classes, SysUtils, LResources, typinfo, Translations;
162176
163177type
164178
165179 { TPoTranslator }
166180
167181 TPoTranslator=class(TAbstractTranslator)
168182 private
169183 FPOFile:TPOFile;
170184 public
171185 constructor Create(POFileName:string);
172186 destructor Destroy;override;
173187 procedure TranslateStringProperty(Sender:TObject;
174188 const Instance: TPersistent; PropInfo: PPropInfo; var Content:string);override;
175189 end;
176190
177191implementation
178192
179193{ TPoTranslator }
180194
181195constructor TPoTranslator.Create(POFileName: string);
182196begin
183197 inherited Create;
184198 FPOFile:=TPOFile.Create(POFileName);
185199end;
186200
187201destructor TPoTranslator.Destroy;
188202begin
189203 FPOFile.Free;
190204 inherited Destroy;
191205end;
192206
193207procedure TPoTranslator.TranslateStringProperty(Sender: TObject;
194208 const Instance: TPersistent; PropInfo: PPropInfo; var Content: string);
195209var
196210 s: String;
197211begin
198212 if not Assigned(FPOFile) then exit;
199213 if not Assigned(PropInfo) then exit;
200214{DO we really need this?}
201215 if Instance is TComponent then
202216 if csDesigning in (Instance as TComponent).ComponentState then exit;
203217{End DO :)}
204218 if (AnsiUpperCase(PropInfo^.PropType^.Name)<>'TTRANSLATESTRING') then exit;
205219 s:=FPOFile.Translate(Content, Content);
206220 if s<>'' then Content:=s;
207221end;
208222
209223end.
210224```
211225
212226Alternatively 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
213227
214228``` pascal
215229...
216230uses
217231 ...
218232 DefaultTranslator;
219233```
220234
221235which 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):
222236
223237- \<Application Directory\>/\<LANG\>/\<Application Filename\>.\<ext\>
224238- \<Application Directory\>/languages/\<LANG\>/\<Application Filename\>.\<ext\>
225239- \<Application Directory\>/locale/\<LANG\>/\<Application Filename\>.\<ext\>
226240- \<Application Directory\>/locale/LC_MESSAGES/\<LANG/\>\<Application Filename\>.\<ext\>
227241
228242under unix-like systems it will also look in
229243
230244- /usr/share/locale/\<LANG\>/LC_MESSAGES/\<Application Filename\>.\<ext\>
231245
232246as 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")
233247
234248## Translating at start of program
235249
236250For every .po file, you must call TranslateUnitResourceStrings. The LCL po file is lclstrconsts. For example you do this in FormCreate of your MainForm:
237251
238252``` pascal
239253uses
240254 ..., gettext, translations;
241255
242256procedure TForm1.FormCreate(Sender: TObject);
243257var
244258 PODirectory, Lang, FallbackLang: String;
245259begin
246260 PODirectory := '/path/to/lazarus/lcl/languages/';
247261 GetLanguageIDs(Lang, FallbackLang);
248262 Translations.TranslateUnitResourceStrings('LCLStrConsts', PODirectory + 'lclstrconsts.%s.po', Lang, FallbackLang);
249263
250264 // the following dialog now shows translated buttons:
251265 MessageDlg('Title', 'Text', mtInformation, [mbOk, mbCancel, mbYes], 0);
252266end;
253267```
254268
255269## Compiling po files into the executable
256270
257271If you don't want to install the .po files, but put all files of the application into the executable, use the following:
258272
259273- Create a new unit (not a form!).
260274- Convert the .po file(s) to .lrs using tools/lazres:
261275
262276 ./lazres unit1.lrs unit1.de.po
263277
264278This will create an include file unit1.lrs beginning with
265279
266280``` pascal
267281LazarusResources.Add('unit1.de','PO',[
268282 ...
269283```
270284
271285- Add the code:
272286
273287``` pascal
274288uses LResources, Translations;
275289
276290resourcestring
277291 MyCaption = 'Caption';
278292
279293function TranslateUnitResourceStrings: boolean;
280294var
281295 r: TLResource;
282296 POFile: TPOFile;
283297begin
284298 r:=LazarusResources.Find('unit1.de','PO');
285299 POFile:=TPOFile.Create;
286300 try
287301 POFile.ReadPOText(r.Value);
288302 Result:=Translations.TranslateUnitResourceStrings('unit1',POFile);
289303 finally
290304 POFile.Free;
291305 end;
292306end;
293307
294308initialization
295309 {$I unit1.lrs}
296310```
297311
298312- Call TranslateUnitResourceStrings at the beginning of the program. You can do that in the initialization section if you like.
299313
300314## Cross-platform method to determine system language
301315
302316The following function delivers a string that represents the language of the user interface. It supports Linux, Mac OS X and Windows.
303317
304318``` pascal
305319uses
306320 Classes, SysUtils {add additional units that may be needed by your code here}
307321 {$IFDEF win32}
308322 , Windows
309323 {$ELSE}
310324 , Unix
311325 {$IFDEF LCLCarbon}
312326 , MacOSAll
313327 {$ENDIF}
314328 {$ENDIF}
315329 ;
316330```
317331
318332``` pascal
319333function GetOSLanguage: string;
320334{platform-independent method to read the language of the user interface}
321335var
322336 l, fbl: string;
323337 {$IFDEF LCLCarbon}
324338 theLocaleRef: CFLocaleRef;
325339 locale: CFStringRef;
326340 buffer: StringPtr;
327341 bufferSize: CFIndex;
328342 encoding: CFStringEncoding;
329343 success: boolean;
330344 {$ENDIF}
331345begin
332346 {$IFDEF LCLCarbon}
333347 theLocaleRef := CFLocaleCopyCurrent;
334348 locale := CFLocaleGetIdentifier(theLocaleRef);
335349 encoding := 0;
336350 bufferSize := 256;
337351 buffer := new(StringPtr);
338352 success := CFStringGetPascalString(locale, buffer, bufferSize, encoding);
339353 if success then
340354 l := string(buffer^)
341355 else
342356 l := '';
343357 fbl := Copy(l, 1, 2);
344358 dispose(buffer);
345359 {$ELSE}
346360 {$IFDEF LINUX}
347361 fbl := Copy(GetEnvironmentVariable('LC_CTYPE'), 1, 2);
348362 {$ELSE}
349363 GetLanguageIDs(l, fbl);
350364 {$ENDIF}
351365 {$ENDIF}
352366 Result := fbl;
353367end;
354368```
355369
356370## Translating the IDE
357371
358372### Files
359373
360374The .po files of the IDE are in the lazarus source directory:
361375
362376- lazarus/languages strings for the IDE
363377- lcl/languages/ strings for the LCL
364378- ideintf/languages/ strings for the IDE interface
365379
366380### Translators
367381
368382- german translation is maintained by Joerg Braun.
369383- finnish translation is maintained by Seppo Suurtarla
370384- russian translation is maintained by Maxim Ganetsky
371385
372386When you want to start a new translation, ask on the mailing if someone is already working on that.
373387
374388Please read carefully: [[Lazarus Documentation|Translations]]
375389
376390## See also
377391
378392- [[IDE Development|IDE Development: Translations, i18n, lrt, po files]]
379393- [[Getting translation strings right]]
380394- [[Lazarus Documentation|Translations]]
381395
382396---
383397*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