gettext.pp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. {
  171. Define USEITERATOR if you want to translate the strings using
  172. the SetResourceStrings call. This is not recommended for this
  173. particular iplementation, since we must pass through a global
  174. variable TheFile : TMOFile. However that works too.
  175. }
  176. {$ifdef USEITERATOR}
  177. var
  178. Thefile : TMOFile;
  179. function Translate (Name,Value : AnsiString; Hash : Longint) : AnsiString;
  180. begin
  181. Result:=TheFile.Translate(Value,Hash);
  182. end;
  183. procedure TranslateResourceStrings(AFile: TMOFile);
  184. var
  185. i,j : Integer;
  186. s : String;
  187. begin
  188. TheFile:=AFile;
  189. SetResourceStrings(@Translate);
  190. end;
  191. {$else}
  192. procedure TranslateResourceStrings(AFile: TMOFile);
  193. var
  194. i, j, count: Integer;
  195. s: String;
  196. begin
  197. for i:=0 to ResourceStringTableCount - 1 do
  198. begin
  199. count := ResourceStringCount(I);
  200. for j := 0 to count - 1 do
  201. begin
  202. s := AFile.Translate(GetResourceStringDefaultValue(i, j),
  203. GetResourceStringHash(i, j));
  204. if Length(s) > 0 then
  205. begin
  206. SetResourceStringValue(i, j, s);
  207. GettextUsed := True;
  208. end;
  209. end;
  210. end;
  211. end;
  212. {$endif}
  213. {$ifdef win32}
  214. procedure GetLanguageIDs(var Lang, FallbackLang: string);
  215. var
  216. Buffer: array[1..4] of char;
  217. Country: string;
  218. UserLCID: LCID;
  219. begin
  220. //defaults
  221. Lang := '';
  222. FallbackLang:='';
  223. UserLCID := GetUserDefaultLCID;
  224. if GetLocaleInfo(UserLCID, LOCALE_SABBREVLANGNAME, @Buffer, 4)<>0 then
  225. FallbackLang := lowercase(copy(Buffer,1,2));
  226. if GetLocaleInfo(UserLCID, LOCALE_SABBREVCTRYNAME, @Buffer, 4)<>0 then begin
  227. Country := copy(Buffer,1,2);
  228. // some 2 letter codes are not the first two letters of the 3 letter code
  229. // there are probably more, but first let us see if there are translations
  230. if (Buffer='PRT') then Country:='PT';
  231. Lang := FallbackLang+'_'+Country;
  232. end;
  233. end;
  234. {$else}
  235. procedure GetLanguageIDs(var Lang, FallbackLang: string);
  236. begin
  237. lang := GetEnv('LC_ALL');
  238. if Length(lang) = 0 then
  239. begin
  240. lang := GetEnv('LC_MESSAGES');
  241. if Length(lang) = 0 then
  242. begin
  243. lang := GetEnv('LANG');
  244. if Length(lang) = 0 then
  245. exit; // no language defined via environment variables
  246. end;
  247. end;
  248. FallbackLang := Copy(lang, 1, 2);
  249. end;
  250. {$endif}
  251. procedure TranslateResourceStrings(const AFilename: String);
  252. var
  253. mo: TMOFile;
  254. lang, FallbackLang: String;
  255. begin
  256. GetLanguageIDs(Lang, FallbackLang);
  257. try
  258. mo := TMOFile.Create(Format(AFilename, [FallbackLang]));
  259. try
  260. TranslateResourceStrings(mo);
  261. finally
  262. mo.Free;
  263. end;
  264. except
  265. on e: Exception do;
  266. end;
  267. lang := Copy(lang, 1, 5);
  268. try
  269. mo := TMOFile.Create(Format(AFilename, [lang]));
  270. try
  271. TranslateResourceStrings(mo);
  272. finally
  273. mo.Free;
  274. end;
  275. except
  276. on e: Exception do;
  277. end;
  278. end;
  279. finalization
  280. if GettextUsed then
  281. ResetResourceTables;
  282. end.