gettext.pp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  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 TranslateResourceStrings(const AFilename: String);
  56. implementation
  57. uses {$ifdef win32} windows, {$endif}dos;
  58. var
  59. GettextUsed: Boolean;
  60. constructor TMOFile.Create(AStream: TStream);
  61. var
  62. header: TMOFileHeader;
  63. i: Integer;
  64. s: String;
  65. begin
  66. inherited Create;
  67. AStream.Read(header, Sizeof(header));
  68. if header.magic <> MOFileHeaderMagic then
  69. raise EMOFileError.Create('Invalid magic - not a MO file?');
  70. GetMem(OrigTable, header.nstrings * SizeOf(TMOStringInfo));
  71. GetMem(TranslTable, header.nstrings * SizeOf(TMOStringInfo));
  72. GetMem(OrigStrings, header.nstrings * SizeOf(PChar));
  73. GetMem(TranslStrings, header.nstrings * SizeOf(PChar));
  74. AStream.Position := header.OrigTabOffset;
  75. AStream.Read(OrigTable^, header.nstrings * SizeOf(TMOStringInfo));
  76. AStream.Position := header.TransTabOffset;
  77. AStream.Read(TranslTable^, header.nstrings * SizeOf(TMOStringInfo));
  78. StringCount := header.nstrings;
  79. // Read strings
  80. for i := 0 to StringCount - 1 do
  81. begin
  82. AStream.Position := OrigTable^[i].offset;
  83. { SetLength(s, OrigTable^[i].length);
  84. AStream.Read(s[1], OrigTable^[i].length);
  85. OrigStrings^[i] := StrNew(PChar(s));}
  86. GetMem(OrigStrings^[i], OrigTable^[i].length + 1);
  87. AStream.Read(OrigStrings^[i]^, OrigTable^[i].length);
  88. OrigStrings^[i][OrigTable^[i].length] := #0;
  89. end;
  90. for i := 0 to StringCount - 1 do
  91. begin
  92. AStream.Position := TranslTable^[i].offset;
  93. { SetLength(s, TranslTable^[i].length);
  94. AStream.Read(s[1], TranslTable^[i].length);
  95. TranslStrings^[i] := StrNew(PChar(s));}
  96. GetMem(TranslStrings^[i], TranslTable^[i].length+1);
  97. AStream.Read(TranslStrings^[i]^, TranslTable^[i].length);
  98. TranslStrings^[i][TranslTable^[i].length] := #0;
  99. end;
  100. // Read hashing table
  101. HashTableSize := header.HashTabSize;
  102. GetMem(HashTable, 4 * HashTableSize);
  103. AStream.Position := header.HashTabOffset;
  104. AStream.Read(HashTable^, 4 * HashTableSize);
  105. end;
  106. constructor TMOFile.Create(const AFilename: String);
  107. var
  108. f: TStream;
  109. begin
  110. f := TFileStream.Create(AFilename, fmOpenRead);
  111. try
  112. Self.Create(f);
  113. finally
  114. f.Free;
  115. end;
  116. end;
  117. destructor TMOFile.Destroy;
  118. var
  119. i: Integer;
  120. begin
  121. for i := 0 to StringCount - 1 do
  122. begin
  123. Dispose(OrigStrings^[i]);
  124. Dispose(TranslStrings^[i]);
  125. end;
  126. Dispose(OrigTable);
  127. Dispose(TranslTable);
  128. Dispose(OrigStrings);
  129. Dispose(TranslStrings);
  130. Dispose(HashTable);
  131. inherited Destroy;
  132. end;
  133. function TMOFile.Translate(AOrig: PChar; ALen: Integer; AHash: LongWord): String;
  134. var
  135. idx, incr, nstr: LongWord;
  136. begin
  137. idx := AHash mod HashTableSize;
  138. incr := 1 + (AHash mod (HashTableSize - 2));
  139. while True do
  140. begin
  141. nstr := HashTable^[idx];
  142. if nstr = 0 then
  143. begin
  144. Result := '';
  145. exit;
  146. end;
  147. if (OrigTable^[nstr - 1].length = ALen) and
  148. (StrComp(OrigStrings^[nstr - 1], AOrig) = 0) then
  149. begin
  150. Result := TranslStrings^[nstr - 1];
  151. exit;
  152. end;
  153. if idx >= HashTableSize - incr then
  154. Dec(idx, HashTableSize - incr)
  155. else
  156. Inc(idx, incr);
  157. end;
  158. end;
  159. function TMOFile.Translate(AOrig: String; AHash: LongWord): String;
  160. begin
  161. Result := Translate(PChar(AOrig), Length(AOrig), AHash);
  162. end;
  163. function TMOFile.Translate(AOrig: String): String;
  164. begin
  165. Result := Translate(AOrig, Hash(AOrig));
  166. end;
  167. // -------------------------------------------------------
  168. // Resourcestring translation procedures
  169. // -------------------------------------------------------
  170. function Translate (Name,Value : AnsiString; Hash : Longint; arg:pointer) : AnsiString;
  171. begin
  172. Result:=TMOFile(arg).Translate(Value,Hash);
  173. end;
  174. procedure TranslateResourceStrings(AFile: TMOFile);
  175. begin
  176. SetResourceStrings(@Translate,AFile);
  177. end;
  178. {$ifdef win32}
  179. procedure GetLanguageIDs(var Lang, FallbackLang: string);
  180. var
  181. Buffer: array[1..4] of char;
  182. Country: string;
  183. UserLCID: LCID;
  184. begin
  185. //defaults
  186. Lang := '';
  187. FallbackLang:='';
  188. UserLCID := GetUserDefaultLCID;
  189. if GetLocaleInfo(UserLCID, LOCALE_SABBREVLANGNAME, @Buffer, 4)<>0 then
  190. FallbackLang := lowercase(copy(Buffer,1,2));
  191. if GetLocaleInfo(UserLCID, LOCALE_SABBREVCTRYNAME, @Buffer, 4)<>0 then begin
  192. Country := copy(Buffer,1,2);
  193. // some 2 letter codes are not the first two letters of the 3 letter code
  194. // there are probably more, but first let us see if there are translations
  195. if (Buffer='PRT') then Country:='PT';
  196. Lang := FallbackLang+'_'+Country;
  197. end;
  198. end;
  199. {$else}
  200. procedure GetLanguageIDs(var Lang, FallbackLang: string);
  201. begin
  202. lang := GetEnv('LC_ALL');
  203. if Length(lang) = 0 then
  204. begin
  205. lang := GetEnv('LC_MESSAGES');
  206. if Length(lang) = 0 then
  207. begin
  208. lang := GetEnv('LANG');
  209. if Length(lang) = 0 then
  210. exit; // no language defined via environment variables
  211. end;
  212. end;
  213. FallbackLang := Copy(lang, 1, 2);
  214. end;
  215. {$endif}
  216. procedure TranslateResourceStrings(const AFilename: String);
  217. var
  218. mo: TMOFile;
  219. lang, FallbackLang: String;
  220. begin
  221. GetLanguageIDs(Lang, FallbackLang);
  222. try
  223. mo := TMOFile.Create(Format(AFilename, [FallbackLang]));
  224. try
  225. TranslateResourceStrings(mo);
  226. finally
  227. mo.Free;
  228. end;
  229. except
  230. on e: Exception do;
  231. end;
  232. lang := Copy(lang, 1, 5);
  233. try
  234. mo := TMOFile.Create(Format(AFilename, [lang]));
  235. try
  236. TranslateResourceStrings(mo);
  237. finally
  238. mo.Free;
  239. end;
  240. except
  241. on e: Exception do;
  242. end;
  243. end;
  244. finalization
  245. if GettextUsed then
  246. ResetResourceTables;
  247. end.