gettext.pp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 1999-2001 by the Free Pascal development team
  4. Gettext interface to resourcestrings.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$MODE objfpc}
  12. {$H+}
  13. unit gettext;
  14. interface
  15. uses SysUtils, Classes;
  16. const
  17. MOFileHeaderMagic = $950412DE;
  18. type
  19. TMOFileHeader = packed record
  20. magic: LongWord; // MOFileHeaderMagic
  21. revision: LongWord; // 0
  22. nstrings: LongWord; // Number of string pairs
  23. OrigTabOffset: LongWord; // Offset of original string offset table
  24. TransTabOffset: LongWord; // Offset of translated string offset table
  25. HashTabSize: LongWord; // Size of hashing table
  26. HashTabOffset: LongWord; // Offset of first hashing table entry
  27. end;
  28. TMOStringInfo = packed record
  29. length: LongWord;
  30. offset: LongWord;
  31. end;
  32. TMOStringTable = array[0..(1 shl 30) div SizeOf(TMOStringInfo)] of TMOStringInfo;
  33. PMOStringTable = ^TMOStringTable;
  34. TLongWordArray = array[0..(1 shl 30) div SizeOf(LongWord)] of LongWord;
  35. PLongWordArray = ^TLongWordArray;
  36. TPCharArray = array[0..(1 shl 30) div SizeOf(PChar)] of PChar;
  37. PPCharArray = ^TPCharArray;
  38. TMOFile = class
  39. protected
  40. StringCount, HashTableSize: LongWord;
  41. HashTable: PLongWordArray;
  42. OrigTable, TranslTable: PMOStringTable;
  43. OrigStrings, TranslStrings: PPCharArray;
  44. public
  45. constructor Create(const AFilename: String);
  46. constructor Create(AStream: TStream);
  47. destructor Destroy; override;
  48. function Translate(AOrig: PChar; ALen: Integer; AHash: LongWord): String;
  49. function Translate(AOrig: String; AHash: LongWord): String;
  50. function Translate(AOrig: String): String;
  51. end;
  52. EMOFileError = class(Exception);
  53. procedure GetLanguageIDs(var Lang, FallbackLang: string);
  54. procedure TranslateResourceStrings(AFile: TMOFile);
  55. procedure TranslateUnitResourceStrings(const AUnitName:string; AFile: TMOFile);
  56. procedure TranslateResourceStrings(const AFilename: String);
  57. procedure TranslateUnitResourceStrings(const AUnitName:string; const AFilename: String);
  58. implementation
  59. {$ifdef Windows}
  60. uses
  61. windows;
  62. {$endif}
  63. procedure Endianfixmotable(p:PMOStringTable;n:integer);
  64. var I:integer;
  65. begin
  66. if n>0 then
  67. for i:=0 to n-1 do
  68. begin
  69. p^[i].length:=swapendian(p^[i].length);
  70. p^[i].offset:=swapendian(p^[i].offset);
  71. end;
  72. end;
  73. procedure Endianfixhashtable(p:PLongwordArray;n:integer);
  74. var I:integer;
  75. begin
  76. if n>0 then
  77. for i:=0 to n-1 do
  78. begin
  79. p^[i]:=swapendian(p^[i]);
  80. end;
  81. end;
  82. constructor TMOFile.Create(AStream: TStream);
  83. var
  84. header: TMOFileHeader;
  85. i: Integer;
  86. endianswap : boolean;
  87. begin
  88. inherited Create;
  89. AStream.Read(header, Sizeof(header));
  90. if (header.magic <> MOFileHeaderMagic) and (swapendian(header.magic)<>MOFileHeaderMagic) then
  91. raise EMOFileError.Create('Invalid magic - not a MO file?');
  92. endianswap:=header.magic<>MOFileHeaderMagic;
  93. If EndianSwap then
  94. begin
  95. with header do
  96. begin
  97. revision :=SwapEndian(revision);
  98. nstrings :=SwapEndian(nstrings);
  99. OrigTabOffset :=SwapEndian(OrigTabOffset);
  100. TransTabOffset:=SwapEndian(TransTabOffset);
  101. HashTabSize :=SwapEndian(HashTabSize);
  102. HashTabOffset :=SwapEndian(HashTabOffset);
  103. end;
  104. end;
  105. GetMem(OrigTable, header.nstrings * SizeOf(TMOStringInfo));
  106. GetMem(TranslTable, header.nstrings * SizeOf(TMOStringInfo));
  107. GetMem(OrigStrings, header.nstrings * SizeOf(PChar));
  108. GetMem(TranslStrings, header.nstrings * SizeOf(PChar));
  109. AStream.Position := header.OrigTabOffset;
  110. AStream.Read(OrigTable^, header.nstrings * SizeOf(TMOStringInfo));
  111. if EndianSwap then
  112. EndianFixmotable(OrigTable,Header.NStrings);
  113. AStream.Position := header.TransTabOffset;
  114. AStream.Read(TranslTable^, header.nstrings * SizeOf(TMOStringInfo));
  115. if EndianSwap then
  116. EndianFixmotable(TranslTable,Header.NStrings);
  117. StringCount := header.nstrings;
  118. // Read strings
  119. for i := 0 to StringCount - 1 do
  120. begin
  121. AStream.Position := OrigTable^[i].offset;
  122. { SetLength(s, OrigTable^[i].length);
  123. AStream.Read(s[1], OrigTable^[i].length);
  124. OrigStrings^[i] := StrNew(PChar(s));}
  125. GetMem(OrigStrings^[i], OrigTable^[i].length + 1);
  126. AStream.Read(OrigStrings^[i]^, OrigTable^[i].length);
  127. OrigStrings^[i][OrigTable^[i].length] := #0;
  128. end;
  129. for i := 0 to StringCount - 1 do
  130. begin
  131. AStream.Position := TranslTable^[i].offset;
  132. { SetLength(s, TranslTable^[i].length);
  133. AStream.Read(s[1], TranslTable^[i].length);
  134. TranslStrings^[i] := StrNew(PChar(s));}
  135. GetMem(TranslStrings^[i], TranslTable^[i].length+1);
  136. AStream.Read(TranslStrings^[i]^, TranslTable^[i].length);
  137. TranslStrings^[i][TranslTable^[i].length] := #0;
  138. end;
  139. // Read hashing table
  140. HashTableSize := header.HashTabSize;
  141. GetMem(HashTable, 4 * HashTableSize);
  142. AStream.Position := header.HashTabOffset;
  143. AStream.Read(HashTable^, 4 * HashTableSize);
  144. if EndianSwap then
  145. EndianFixHashTable(hashtable,hashtablesize);
  146. end;
  147. constructor TMOFile.Create(const AFilename: String);
  148. var
  149. f: TStream;
  150. begin
  151. f := TFileStream.Create(AFilename, fmOpenRead);
  152. try
  153. Self.Create(f);
  154. finally
  155. f.Free;
  156. end;
  157. end;
  158. destructor TMOFile.Destroy;
  159. var
  160. i: Integer;
  161. begin
  162. for i := 0 to StringCount - 1 do
  163. begin
  164. FreeMem(OrigStrings^[i]);
  165. FreeMem(TranslStrings^[i]);
  166. end;
  167. FreeMem(OrigTable);
  168. FreeMem(TranslTable);
  169. FreeMem(OrigStrings);
  170. FreeMem(TranslStrings);
  171. FreeMem(HashTable);
  172. inherited Destroy;
  173. end;
  174. function TMOFile.Translate(AOrig: PChar; ALen: Integer; AHash: LongWord): String;
  175. var
  176. idx, incr, nstr: LongWord;
  177. begin
  178. if AHash = $FFFFFFFF then
  179. begin
  180. Result := '';
  181. exit;
  182. end;
  183. idx := AHash mod HashTableSize;
  184. incr := 1 + (AHash mod (HashTableSize - 2));
  185. while True do
  186. begin
  187. nstr := HashTable^[idx];
  188. if (nstr = 0) or (nstr > StringCount) then
  189. begin
  190. Result := '';
  191. exit;
  192. end;
  193. if (OrigTable^[nstr - 1].length = LongWord(ALen)) and
  194. (StrComp(OrigStrings^[nstr - 1], AOrig) = 0) then
  195. begin
  196. Result := TranslStrings^[nstr - 1];
  197. exit;
  198. end;
  199. if idx >= HashTableSize - incr then
  200. Dec(idx, HashTableSize - incr)
  201. else
  202. Inc(idx, incr);
  203. end;
  204. end;
  205. function TMOFile.Translate(AOrig: String; AHash: LongWord): String;
  206. begin
  207. Result := Translate(PChar(AOrig), Length(AOrig), AHash);
  208. end;
  209. function TMOFile.Translate(AOrig: String): String;
  210. begin
  211. Result := Translate(AOrig, Hash(AOrig));
  212. end;
  213. // -------------------------------------------------------
  214. // Resourcestring translation procedures
  215. // -------------------------------------------------------
  216. function Translate (Name,Value : AnsiString; Hash : Longint; arg:pointer) : AnsiString;
  217. var contextempty : boolean;
  218. begin
  219. contextempty:=name='';
  220. Result:='';
  221. if not contextempty then
  222. Result:=TMOFile(arg).Translate(Name+#4+Value);
  223. if contextempty or (Result='') then
  224. Result:=TMOFile(arg).Translate(Value,Hash);
  225. end;
  226. procedure TranslateResourceStrings(AFile: TMOFile);
  227. begin
  228. SetResourceStrings(@Translate,AFile);
  229. end;
  230. procedure TranslateUnitResourceStrings(const AUnitName:string; AFile: TMOFile);
  231. begin
  232. SetUnitResourceStrings(AUnitName,@Translate,AFile);
  233. end;
  234. {$ifdef windows}
  235. procedure GetLanguageIDs(var Lang, FallbackLang: string);
  236. var
  237. Buffer: array[1..4] of {$ifdef Wince}WideChar{$else}char{$endif};
  238. Country: string;
  239. UserLCID: LCID;
  240. begin
  241. //defaults
  242. Lang := '';
  243. FallbackLang:='';
  244. UserLCID := GetUserDefaultLCID;
  245. if GetLocaleInfo(UserLCID, LOCALE_SABBREVLANGNAME, @Buffer[1], 4)<>0 then
  246. FallbackLang := lowercase(copy(Buffer,1,2));
  247. if GetLocaleInfo(UserLCID, LOCALE_SABBREVCTRYNAME, @Buffer[1], 4)<>0 then begin
  248. Country := copy(Buffer,1,2);
  249. // some 2 letter codes are not the first two letters of the 3 letter code
  250. // there are probably more, but first let us see if there are translations
  251. if (Buffer='PRT') then Country:='PT';
  252. Lang := FallbackLang+'_'+Country;
  253. end;
  254. end;
  255. {$else}
  256. procedure GetLanguageIDs(var Lang, FallbackLang: string);
  257. begin
  258. FallbackLang:='';
  259. lang := GetEnvironmentVariable('LC_ALL');
  260. if Length(lang) = 0 then
  261. begin
  262. lang := GetEnvironmentVariable('LC_MESSAGES');
  263. if Length(lang) = 0 then
  264. begin
  265. lang := GetEnvironmentVariable('LANG');
  266. if Length(lang) = 0 then
  267. exit; // no language defined via environment variables
  268. end;
  269. end;
  270. FallbackLang := Copy(lang, 1, 2);
  271. end;
  272. {$endif}
  273. procedure TranslateResourceStrings(const AFilename: String);
  274. var
  275. mo: TMOFile;
  276. lang, FallbackLang: String;
  277. fn: String;
  278. begin
  279. GetLanguageIDs(Lang, FallbackLang);
  280. fn:=Format(AFilename, [FallbackLang]);
  281. if fileexists(fn) then
  282. begin
  283. try
  284. mo := TMOFile.Create(fn);
  285. try
  286. TranslateResourceStrings(mo);
  287. finally
  288. mo.Free;
  289. end;
  290. except
  291. on e: Exception do;
  292. end;
  293. end;
  294. lang := Copy(lang, 1, 5);
  295. fn:=Format(AFilename, [lang]);
  296. if fileexists(fn) then
  297. begin
  298. try
  299. mo := TMOFile.Create(Format(AFilename, [lang]));
  300. try
  301. TranslateResourceStrings(mo);
  302. finally
  303. mo.Free;
  304. end;
  305. except
  306. on e: Exception do;
  307. end;
  308. end;
  309. end;
  310. procedure TranslateUnitResourceStrings(const AUnitName:string; const AFilename: String);
  311. var
  312. mo: TMOFile;
  313. lang, FallbackLang: String;
  314. begin
  315. GetLanguageIDs(Lang, FallbackLang);
  316. try
  317. mo := TMOFile.Create(Format(AFilename, [FallbackLang]));
  318. try
  319. TranslateUnitResourceStrings(AUnitName,mo);
  320. finally
  321. mo.Free;
  322. end;
  323. except
  324. on e: Exception do;
  325. end;
  326. lang := Copy(lang, 1, 5);
  327. try
  328. mo := TMOFile.Create(Format(AFilename, [lang]));
  329. try
  330. TranslateUnitResourceStrings(AUnitName,mo);
  331. finally
  332. mo.Free;
  333. end;
  334. except
  335. on e: Exception do;
  336. end;
  337. end;
  338. finalization
  339. finalizeresourcetables;
  340. end.