2
0

GnuGettext.pas 106 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565
  1. //
  2. // This unit is part of the GLScene Engine, http://glscene.org
  3. //
  4. {*------------------------------------------------------------------------------
  5. GNU gettext translation system for Delphi, Kylix, C++ Builder and others.
  6. All parts of the translation system are kept in this unit.
  7. @author Lars B. Dybdahl and others
  8. @version $LastChangedRevision$
  9. @see http://dybdahl.dk/dxgettext/
  10. -------------------------------------------------------------------------------}
  11. unit GnuGettext;
  12. (**************************************************************)
  13. (* *)
  14. (* (C) Copyright by Lars B. Dybdahl and others *)
  15. (* E-mail: [email protected], phone +45 70201241 *)
  16. (* *)
  17. (* Contributors: Peter Thornqvist, Troy Wolbrink, *)
  18. (* Frank Andreas de Groot, Igor Siticov, *)
  19. (* Jacques Garcia Vazquez, Igor Gitman *)
  20. (* Arvid Winkelsdorf, Andreas Hausladen, *)
  21. (* Olivier Sannier *)
  22. (* *)
  23. (* See http://dybdahl.dk/dxgettext/ for more information *)
  24. (* *)
  25. (**************************************************************)
  26. // Information about this file:
  27. // $--LastChangedDate: 2010-08-25 15:40:17 +0200 (mer., 25 avg 2010) $
  28. // $--LastChangedRevision: 220 $
  29. // $--HeadURL: http://svn.berlios.de/svnroot/repos/dxgettext/trunk/dxgettext/sample/gnugettext.pas $
  30. // Redistribution and use in source and binary forms, with or without
  31. // modification, are permitted provided that the following conditions are met:
  32. // The names of any contributor may not be used to endorse or promote
  33. // products derived from this software without specific prior written permission.
  34. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  35. // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  36. // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  37. // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  38. // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  39. // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  40. // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  41. // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  42. // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  43. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  44. interface
  45. // If the conditional define DXGETTEXTDEBUG is defined, debugging log is activated.
  46. // Use DefaultInstance.DebugLogToFile() to write the log to a file.
  47. { $define DXGETTEXTDEBUG}
  48. {$ifdef VER140}
  49. // Delphi 6
  50. {$DEFINE DELPHI2007OROLDER}
  51. {$ifdef MSWINDOWS}
  52. {$DEFINE DELPHI6OROLDER}
  53. {$endif}
  54. {$endif}
  55. {$ifdef VER150}
  56. // Delphi 7
  57. {$DEFINE DELPHI2007OROLDER}
  58. {$endif}
  59. {$ifdef VER160}
  60. // Delphi 8
  61. {$DEFINE DELPHI2007OROLDER}
  62. {$endif}
  63. {$ifdef VER170}
  64. // Delphi 2005
  65. {$DEFINE DELPHI2007OROLDER}
  66. {$endif}
  67. {$ifdef VER180}
  68. // Delphi 2006
  69. {$DEFINE DELPHI2007OROLDER}
  70. {$endif}
  71. {$ifdef VER190}
  72. // Delphi 2007
  73. {$DEFINE DELPHI2007OROLDER}
  74. {$endif}
  75. {$ifdef VER200}
  76. // Delphi 2009 with Unicode
  77. {$endif}
  78. {$ifdef VER220}
  79. // Delphi XE with Unicode
  80. {$endif}
  81. {$ifdef VER230}
  82. // Delphi XE2 with Unicode
  83. {$endif}
  84. {$ifdef VER240}
  85. // Delphi XE3 with Unicode
  86. {$DEFINE DELPHIXE3OROLDER}
  87. {$endif}
  88. uses
  89. {$ifdef MSWINDOWS}
  90. Winapi.Windows,
  91. {$else}
  92. Libc,
  93. {$ifdef FPC}
  94. CWString,
  95. {$endif}
  96. {$endif}
  97. Classes,
  98. StrUtils,
  99. SysUtils,
  100. TypInfo;
  101. (*****************************************************************************)
  102. (* *)
  103. (* MAIN API *)
  104. (* *)
  105. (*****************************************************************************)
  106. type
  107. {$IFNDEF UNICODE}
  108. UnicodeString=WideString;
  109. RawUtf8String=AnsiString;
  110. RawByteString=AnsiString;
  111. {$ELSE}
  112. RawUtf8String=RawByteString;
  113. {$ENDIF}
  114. DomainString = string;
  115. LanguageString = string;
  116. ComponentNameString = string;
  117. FilenameString = string;
  118. MsgIdString = UnicodeString;
  119. TranslatedUnicodeString = UnicodeString;
  120. // Main GNU gettext functions. See documentation for instructions on how to use them.
  121. function _(const szMsgId: MsgIdString): TranslatedUnicodeString;
  122. function gettext(const szMsgId: MsgIdString): TranslatedUnicodeString;
  123. function dgettext(const szDomain: DomainString;
  124. const szMsgId: MsgIdString): TranslatedUnicodeString;
  125. function dngettext(const szDomain: DomainString; const singular, plural: MsgIdString;
  126. Number: longint): TranslatedUnicodeString;
  127. function ngettext(const singular, plural: MsgIdString;
  128. Number: longint): TranslatedUnicodeString;
  129. procedure textdomain(const szDomain: DomainString);
  130. function getcurrenttextdomain: DomainString;
  131. procedure bindtextdomain(const szDomain: DomainString;
  132. const szDirectory: FilenameString);
  133. // Set language to use
  134. procedure UseLanguage(LanguageCode: LanguageString);
  135. function GetCurrentLanguage: LanguageString;
  136. // Translates a component (form, frame etc.) to the currently selected language.
  137. // Put TranslateComponent(self) in the OnCreate event of all your forms.
  138. // See the manual for documentation on these functions
  139. type
  140. TTranslator = procedure(obj: TObject) of object;
  141. procedure TP_Ignore(AnObject: TObject; const Name: ComponentNameString);
  142. procedure TP_IgnoreClass(IgnClass: TClass);
  143. procedure TP_IgnoreClassProperty(IgnClass: TClass; const propertyname: ComponentNameString);
  144. procedure TP_GlobalIgnoreClass(IgnClass: TClass);
  145. procedure TP_GlobalIgnoreClassProperty(IgnClass: TClass; const propertyname: ComponentNameString);
  146. procedure TP_GlobalHandleClass(HClass: TClass; Handler: TTranslator);
  147. procedure TranslateComponent(AnObject: TComponent; const TextDomain: DomainString = '');
  148. procedure RetranslateComponent(AnObject: TComponent; const TextDomain: DomainString = '');
  149. // Add more domains that resourcestrings can be extracted from. If a translation
  150. // is not found in the default domain, this domain will be searched, too.
  151. // This is useful for adding mo files for certain runtime libraries and 3rd
  152. // party component libraries
  153. procedure AddDomainForResourceString(const domain: DomainString);
  154. procedure RemoveDomainForResourceString(const domain: DomainString);
  155. // Add more domains that component strings can be extracted from. If a translation
  156. // is not found in the default domain, this domain will be searched, too.
  157. // This is useful when an application inherits components from a 3rd
  158. // party component libraries
  159. procedure AddDomainForComponent (const domain:DomainString);
  160. procedure RemoveDomainForComponent (const domain:DomainString);
  161. // Unicode-enabled way to get resourcestrings, automatically translated
  162. // Use like this: ws:=LoadResStringW(@NameOfResourceString);
  163. function LoadResString(ResStringRec: PResStringRec): WideString;
  164. function LoadResStringW(ResStringRec: PResStringRec): UnicodeString;
  165. // This returns an empty string if not translated or translator name is not specified.
  166. function GetTranslatorNameAndEmail: TranslatedUnicodeString;
  167. (*****************************************************************************)
  168. (* *)
  169. (* ADVANCED FUNCTIONALITY *)
  170. (* *)
  171. (*****************************************************************************)
  172. const
  173. DefaultTextDomain = 'default';
  174. var
  175. ExecutableFilename: FilenameString;
  176. // This is set to paramstr(0) or the name of the DLL you are creating.
  177. const
  178. PreferExternal = False; // Set to true, to prefer external *.mo over embedded translation
  179. UseMemoryMappedFiles = True; // Set to False, to use the mo-file as independent copy in memory (you can update the file while it is in use)
  180. ReReadMoFileOnSameLanguage = True; // Set to True, to reread mo-file if the current language is selected again
  181. const
  182. // Subversion source code version control version information
  183. VCSVersion = '$LastChangedRevision$';
  184. type
  185. EGnuGettext = class(Exception);
  186. EGGProgrammingError = class(EGnuGettext);
  187. EGGComponentError = class(EGnuGettext);
  188. EGGIOError = class(EGnuGettext);
  189. EGGAnsi2WideConvError = class(EGnuGettext);
  190. // This function will turn resourcestring hooks on or off, eventually with BPL file support.
  191. // Please do not activate BPL file support when the package is in design mode.
  192. const
  193. AutoCreateHooks = True;
  194. procedure HookIntoResourceStrings(Enabled: boolean = True;
  195. SupportPackages: boolean = False);
  196. (*****************************************************************************)
  197. (* *)
  198. (* CLASS based implementation. *)
  199. (* Use TGnuGettextInstance to have more than one language *)
  200. (* in your application at the same time *)
  201. (* *)
  202. (*****************************************************************************)
  203. {$ifdef MSWINDOWS}
  204. {$ifndef DELPHIXE3OROLDER}
  205. {$WARN UNSAFE_TYPE OFF}
  206. {$WARN UNSAFE_CODE OFF}
  207. {$WARN UNSAFE_CAST OFF}
  208. {$endif}
  209. {$endif}
  210. type
  211. TOnDebugLine = procedure(Sender: TObject; const Line: string; var Discard: boolean) of
  212. object; // Set Discard to false if output should still go to ordinary debug log
  213. TGetPluralForm = function(Number: longint): integer;
  214. TDebugLogger = procedure(line: ansistring) of object;
  215. {*------------------------------------------------------------------------------
  216. Handles .mo files, in separate files or inside the exe file.
  217. Don't use this class. It's for internal use.
  218. -------------------------------------------------------------------------------}
  219. TMoFile=
  220. class /// Threadsafe. Only constructor and destructor are writing to memory
  221. private
  222. doswap: boolean;
  223. public
  224. Users:Integer; /// Reference count. If it reaches zero, this object should be destroyed.
  225. constructor Create (const filename: FilenameString;
  226. const Offset: int64; Size: int64;
  227. const xUseMemoryMappedFiles: Boolean);
  228. destructor Destroy; override;
  229. function gettext(const msgid: RawUtf8String;var found:boolean): RawUtf8String; // uses mo file and utf-8
  230. property isSwappedArchitecture:boolean read doswap;
  231. private
  232. N, O, T: Cardinal; /// Values defined at http://www.linuxselfhelp.com/gnu/gettext/html_chapter/gettext_6.html
  233. startindex,startstep:integer;
  234. FUseMemoryMappedFiles: Boolean;
  235. mo: THandle;
  236. momapping: THandle;
  237. momemoryHandle: PAnsiChar;
  238. momemory: PAnsiChar;
  239. function autoswap32(i: cardinal): cardinal;
  240. function CardinalInMem(baseptr: PAnsiChar; Offset: cardinal): cardinal;
  241. end;
  242. {*------------------------------------------------------------------------------
  243. Handles all issues regarding a specific domain.
  244. Don't use this class. It's for internal use.
  245. -------------------------------------------------------------------------------}
  246. TDomain = class
  247. private
  248. Enabled: boolean;
  249. vDirectory: FilenameString;
  250. procedure setDirectory(const dir: FilenameString);
  251. public
  252. DebugLogger: TDebugLogger;
  253. Domain: DomainString;
  254. property Directory: FilenameString Read vDirectory Write setDirectory;
  255. constructor Create;
  256. destructor Destroy; override;
  257. // Set parameters
  258. procedure SetLanguageCode(const langcode: LanguageString);
  259. procedure SetFilename(const filename: FilenameString);
  260. // Bind this domain to a specific file
  261. // Get information
  262. procedure GetListOfLanguages(list: TStrings);
  263. function GetTranslationProperty(Propertyname: ComponentNameString): TranslatedUnicodeString;
  264. function gettext(const msgid: RawUtf8String): RawUtf8String;
  265. // uses mo file and utf-8
  266. private
  267. mofile: TMoFile;
  268. SpecificFilename: FilenameString;
  269. curlang: LanguageString;
  270. OpenHasFailedBefore: boolean;
  271. procedure OpenMoFile;
  272. procedure CloseMoFile;
  273. end;
  274. {*------------------------------------------------------------------------------
  275. Helper class for invoking events.
  276. -------------------------------------------------------------------------------}
  277. TExecutable = class
  278. procedure Execute; virtual; abstract;
  279. end;
  280. {*------------------------------------------------------------------------------
  281. The main translation engine.
  282. -------------------------------------------------------------------------------}
  283. TGnuGettextInstance = class
  284. private
  285. fOnDebugLine: TOnDebugLine;
  286. CreatorThread: cardinal; /// Only this thread can use LoadResString
  287. public
  288. Enabled: boolean; /// Set this to false to disable translations
  289. DesignTimeCodePage: integer;
  290. /// See MultiByteToWideChar() in Win32 API for documentation
  291. constructor Create;
  292. destructor Destroy; override;
  293. procedure UseLanguage(LanguageCode: LanguageString);
  294. procedure GetListOfLanguages(const domain: DomainString; list: TStrings);
  295. // Puts list of language codes, for which there are translations in the specified domain, into list
  296. {$ifndef UNICODE}
  297. function gettext(const szMsgId: ansistring): TranslatedUnicodeString; overload; virtual;
  298. function ngettext(const singular,plural:ansistring;Number:longint):TranslatedUnicodeString; overload; virtual;
  299. {$endif}
  300. function gettext(const szMsgId: MsgIdString): TranslatedUnicodeString; overload; virtual;
  301. function gettext_NoExtract(const szMsgId: MsgIdString): TranslatedUnicodeString;
  302. function gettext_NoOp(const szMsgId: MsgIdString): TranslatedUnicodeString;
  303. function ngettext(const singular,plural:MsgIdString;Number:longint):TranslatedUnicodeString; overload; virtual;
  304. function ngettext_NoExtract(const singular,plural:MsgIdString;Number:longint):TranslatedUnicodeString;
  305. function GetCurrentLanguage:LanguageString;
  306. function GetTranslationProperty (const Propertyname:ComponentNameString):TranslatedUnicodeString;
  307. function GetTranslatorNameAndEmail:TranslatedUnicodeString;
  308. // Form translation tools, these are not threadsafe. All TP_ procs must be called just before TranslateProperites()
  309. procedure TP_Ignore(AnObject: TObject; const Name: ComponentNameString);
  310. procedure TP_IgnoreClass(IgnClass: TClass);
  311. procedure TP_IgnoreClassProperty(IgnClass: TClass; propertyname: ComponentNameString);
  312. procedure TP_GlobalIgnoreClass(IgnClass: TClass);
  313. procedure TP_GlobalIgnoreClassProperty(IgnClass: TClass; propertyname: ComponentNameString);
  314. procedure TP_GlobalHandleClass(HClass: TClass; Handler: TTranslator);
  315. procedure TranslateProperties(AnObject: TObject; textdomain: DomainString = '');
  316. procedure TranslateComponent(AnObject: TComponent; const TextDomain: DomainString = '');
  317. procedure RetranslateComponent(AnObject: TComponent; const TextDomain: DomainString = '');
  318. // Multi-domain functions
  319. {$ifndef UNICODE}
  320. function dgettext(const szDomain: DomainString; const szMsgId: ansistring): TranslatedUnicodeString; overload; virtual;
  321. function dngettext(const szDomain: DomainString; const singular,plural:ansistring;Number:longint):TranslatedUnicodeString; overload; virtual;
  322. {$endif}
  323. function dgettext(const szDomain: DomainString;
  324. const szMsgId: MsgIdString): TranslatedUnicodeString; overload; virtual;
  325. function dgettext_NoExtract(const szDomain: DomainString;
  326. const szMsgId: MsgIdString): TranslatedUnicodeString;
  327. function dngettext(const szDomain: DomainString;
  328. const singular, plural: MsgIdString; Number: longint): TranslatedUnicodeString;
  329. overload; virtual;
  330. function dngettext_NoExtract(const szDomain: DomainString;
  331. const singular, plural: MsgIdString; Number: longint): TranslatedUnicodeString;
  332. procedure textdomain(const szDomain: DomainString);
  333. function getcurrenttextdomain: DomainString;
  334. procedure bindtextdomain(const szDomain: DomainString;
  335. const szDirectory: FilenameString);
  336. procedure bindtextdomainToFile(const szDomain: DomainString;
  337. const filename: FilenameString); // Also works with files embedded in exe file
  338. // Windows API functions
  339. function LoadResString(ResStringRec: PResStringRec): UnicodeString;
  340. // Output all log info to this file. This may only be called once.
  341. procedure DebugLogToFile(const filename: FilenameString; append: boolean = False);
  342. procedure DebugLogPause(PauseEnabled: boolean);
  343. property OnDebugLine: TOnDebugLine Read fOnDebugLine Write fOnDebugLine;
  344. // If set, all debug output goes here
  345. {$ifndef UNICODE}
  346. // Conversion according to design-time character set
  347. function ansi2wideDTCP(const s: ansistring): MsgIdString;
  348. // Convert using Design Time Code Page
  349. {$endif}
  350. protected
  351. procedure TranslateStrings(sl: TStrings; const TextDomain: DomainString);
  352. // Override these three, if you want to inherited from this class
  353. // to create a new class that handles other domain and language dependent
  354. // issues
  355. procedure WhenNewLanguage(const LanguageID: LanguageString);
  356. virtual; // Override to know when language changes
  357. procedure WhenNewDomain(const TextDomain: DomainString); virtual;
  358. // Override to know when text domain changes. Directory is purely informational
  359. procedure WhenNewDomainDirectory(const TextDomain: DomainString;
  360. const Directory: FilenameString); virtual;
  361. // Override to know when any text domain's directory changes. It won't be called if a domain is fixed to a specific file.
  362. private
  363. curlang: LanguageString;
  364. curGetPluralForm: TGetPluralForm;
  365. curmsgdomain: DomainString;
  366. savefileCS: TMultiReadExclusiveWriteSynchronizer;
  367. savefile: TextFile;
  368. savememory: TStringList;
  369. DefaultDomainDirectory: FilenameString;
  370. domainlist: TStringList; /// List of domain names. Objects are TDomain.
  371. TP_IgnoreList: TStringList;
  372. /// Temporary list, reset each time TranslateProperties is called
  373. TP_ClassHandling: TList;
  374. /// Items are TClassMode. If a is derived from b, a comes first
  375. TP_GlobalClassHandling: TList;
  376. /// Items are TClassMode. If a is derived from b, a comes first
  377. TP_Retranslator: TExecutable; /// Cast this to TTP_Retranslator
  378. FWhenNewLanguageListeners: TInterfaceList; /// List of all registered WhenNewLanguage listeners
  379. {$ifdef DXGETTEXTDEBUG}
  380. DebugLogCS:TMultiReadExclusiveWriteSynchronizer;
  381. DebugLog:TStream;
  382. DebugLogOutputPaused:Boolean;
  383. {$endif}
  384. function TP_CreateRetranslator: TExecutable; // Must be freed by caller!
  385. procedure FreeTP_ClassHandlingItems;
  386. {$ifdef DXGETTEXTDEBUG}
  387. procedure DebugWriteln(line: ansistring);
  388. {$endif}
  389. procedure TranslateProperty(AnObject: TObject; PropInfo: PPropInfo;
  390. TodoList: TStrings; const TextDomain: DomainString);
  391. function Getdomain(const domain: DomainString;
  392. const DefaultDomainDirectory: FilenameString;
  393. const CurLang: LanguageString): TDomain;
  394. // Translates a single property of an object
  395. end;
  396. const
  397. LOCALE_SISO639LANGNAME = $59; // Used by Lazarus software development tool
  398. {$NODEFINE LOCALE_SISO639LANGNAME}
  399. LOCALE_SISO3166CTRYNAME = $5A; // Used by Lazarus software development tool
  400. {$NODEFINE LOCALE_SISO3166CTRYNAME }
  401. var
  402. DefaultInstance: TGnuGettextInstance;
  403. /// Default instance of the main API for singlethreaded applications.
  404. implementation
  405. {$ifndef MSWINDOWS}
  406. {$ifndef LINUX}
  407. 'This version of gnugettext.pas is only meant to be compiled with Kylix 3,'
  408. 'Delphi 6, Delphi 7 and later versions. If you use other versions, please'
  409. 'get the gnugettext.pas version from the Delphi 5 directory.'
  410. {$endif}
  411. {$endif}
  412. (**************************************************************************)
  413. // Some comments on the implementation:
  414. // This unit should be independent of other units where possible.
  415. // It should have a small footprint in any way.
  416. (**************************************************************************)
  417. // TMultiReadExclusiveWriteSynchronizer is used instead of TCriticalSection
  418. // because it makes this unit independent of the SyncObjs unit
  419. (**************************************************************************)
  420. {$B-,R+,I+,Q+}
  421. type
  422. TTP_RetranslatorItem = class
  423. obj: TObject;
  424. Propname: ComponentNameString;
  425. OldValue: TranslatedUnicodeString;
  426. end;
  427. TTP_Retranslator = class(TExecutable)
  428. TextDomain: DomainString;
  429. Instance: TGnuGettextInstance;
  430. constructor Create;
  431. destructor Destroy; override;
  432. procedure Remember(obj: TObject; PropName: ComponentNameString;
  433. OldValue: TranslatedUnicodeString);
  434. procedure Execute; override;
  435. private
  436. list: TList;
  437. end;
  438. TEmbeddedFileInfo = class
  439. offset, size: int64;
  440. end;
  441. TFileLocator = class // This class finds files even when embedded inside executable
  442. constructor Create;
  443. destructor Destroy; override;
  444. function FindSignaturePos(const signature: RawByteString; str: TFileStream): Int64;
  445. procedure Analyze; // List files embedded inside executable
  446. function FileExists(filename: FilenameString): boolean;
  447. function GetMoFile(filename: FilenameString; DebugLogger: TDebugLogger): TMoFile;
  448. procedure ReleaseMoFile(mofile: TMoFile);
  449. private
  450. basedirectory: FilenameString;
  451. filelist: TStringList;
  452. //Objects are TEmbeddedFileInfo. Filenames are relative to .exe file
  453. MoFilesCS: TMultiReadExclusiveWriteSynchronizer;
  454. MoFiles: TStringList; // Objects are filenames+offset, objects are TMoFile
  455. function ReadInt64(str: TStream): int64;
  456. end;
  457. TGnuGettextComponentMarker = class(TComponent)
  458. public
  459. LastLanguage: LanguageString;
  460. Retranslator: TExecutable;
  461. destructor Destroy; override;
  462. end;
  463. TClassMode = class
  464. HClass: TClass;
  465. SpecialHandler: TTranslator;
  466. PropertiesToIgnore: TStringList; // This is ignored if Handler is set
  467. constructor Create;
  468. destructor Destroy; override;
  469. end;
  470. TRStrinfo = record
  471. strlength, stroffset: cardinal;
  472. end;
  473. TStrInfoArr = array[0..10000000] of TRStrinfo;
  474. PStrInfoArr = ^TStrInfoArr;
  475. TCharArray5 = array[0..4] of ansichar;
  476. THook = // Replaces a runtime library procedure with a custom procedure
  477. class
  478. public
  479. constructor Create(OldProcedure, NewProcedure: pointer; FollowJump: boolean = False);
  480. destructor Destroy; override; // Restores unhooked state
  481. procedure Reset(FollowJump: boolean = False);
  482. // Disables and picks up patch points again
  483. procedure Disable;
  484. procedure Enable;
  485. private
  486. oldproc, newproc: Pointer;
  487. Patch: TCharArray5;
  488. Original: TCharArray5;
  489. PatchPosition: PAnsiChar;
  490. procedure Shutdown; // Same as destroy, except that object is not destroyed
  491. end;
  492. var
  493. // System information
  494. Win32PlatformIsUnicode: boolean = False;
  495. // Information about files embedded inside .exe file
  496. FileLocator: TFileLocator;
  497. // Hooks into runtime library functions
  498. ResourceStringDomainListCS: TMultiReadExclusiveWriteSynchronizer;
  499. ResourceStringDomainList: TStringList;
  500. ComponentDomainListCS:TMultiReadExclusiveWriteSynchronizer;
  501. ComponentDomainList:TStringList;
  502. HookLoadResString: THook;
  503. HookLoadStr: THook;
  504. HookFmtLoadStr: THook;
  505. function GGGetEnvironmentVariable(const Name: WideString): WideString;
  506. var
  507. Len: integer;
  508. W: WideString;
  509. begin
  510. Result := '';
  511. SetLength(W, 1);
  512. Len := GetEnvironmentVariableW(PWideChar(Name), PWideChar(W), 1);
  513. if Len > 0 then
  514. begin
  515. SetLength(Result, Len - 1);
  516. GetEnvironmentVariableW(PWideChar(Name), PWideChar(Result), Len);
  517. end;
  518. end;
  519. function StripCRRawMsgId(s: RawUtf8String): RawUtf8String;
  520. var
  521. i: integer;
  522. begin
  523. i := 1;
  524. while i <= length(s) do
  525. begin
  526. if s[i] = #13 then
  527. Delete(s, i, 1)
  528. else
  529. Inc(i);
  530. end;
  531. Result := s;
  532. end;
  533. function EnsureLineBreakInTranslatedString(s: RawUtf8String): RawUtf8String;
  534. {$ifdef MSWINDOWS}
  535. var
  536. i: integer;
  537. {$endif}
  538. begin
  539. {$ifdef MSWINDOWS}
  540. Assert(sLinebreak = AnsiString(#13#10));
  541. i := 1;
  542. while i <= length(s) do
  543. begin
  544. if (s[i] = #10) and (MidStr(s, i - 1, 1) <> #13) then
  545. begin
  546. insert(#13, s, i);
  547. Inc(i, 2);
  548. end
  549. else
  550. Inc(i);
  551. end;
  552. {$endif}
  553. Result := s;
  554. end;
  555. function IsWriteProp(Info: PPropInfo): boolean;
  556. begin
  557. Result := Assigned(Info) and (Info^.SetProc <> nil);
  558. end;
  559. function ResourceStringGettext(MsgId: MsgIdString): TranslatedUnicodeString;
  560. var
  561. i: integer;
  562. begin
  563. if (MsgID = '') or (ResourceStringDomainListCS = nil) then
  564. begin
  565. // This only happens during very complicated program startups that fail,
  566. // or when Msgid=''
  567. Result := MsgId;
  568. exit;
  569. end;
  570. ResourceStringDomainListCS.BeginRead;
  571. try
  572. for i := 0 to ResourceStringDomainList.Count - 1 do
  573. begin
  574. Result := dgettext(ResourceStringDomainList.Strings[i], MsgId);
  575. if Result <> MsgId then
  576. break;
  577. end;
  578. finally
  579. ResourceStringDomainListCS.EndRead;
  580. end;
  581. end;
  582. function ComponentGettext(MsgId: MsgIdString; Instance: TGnuGettextInstance = nil): TranslatedUnicodeString;
  583. var
  584. i:integer;
  585. begin
  586. if (MsgID='') or (ComponentDomainListCS=nil) then begin
  587. // This only happens during very complicated program startups that fail,
  588. // or when Msgid=''
  589. Result:=MsgId;
  590. exit;
  591. end;
  592. ComponentDomainListCS.BeginRead;
  593. try
  594. for i:=0 to ComponentDomainList.Count-1 do begin
  595. if Assigned(Instance) then
  596. Result:=Instance.dgettext(ComponentDomainList.Strings[i], MsgId)
  597. else
  598. Result:=dgettext(ComponentDomainList.Strings[i], MsgId);
  599. if Result<>MsgId then
  600. break;
  601. end;
  602. finally
  603. ComponentDomainListCS.EndRead;
  604. end;
  605. end;
  606. function gettext(const szMsgId: MsgIdString): TranslatedUnicodeString;
  607. begin
  608. Result := DefaultInstance.gettext(szMsgId);
  609. end;
  610. function gettext_NoExtract(const szMsgId: MsgIdString): TranslatedUnicodeString;
  611. begin
  612. // This one is very useful for translating text in variables.
  613. // This can sometimes be necessary, and by using this function,
  614. // the source code scanner will not trigger warnings.
  615. Result := gettext(szMsgId);
  616. end;
  617. function gettext_NoOp(const szMsgId: MsgIdString): TranslatedUnicodeString;
  618. begin
  619. //*** With this function Strings can be added to the po-file without beeing
  620. // ResourceStrings (dxgettext will add the string and this function will
  621. // return it without a change)
  622. // see gettext manual
  623. // 4.7 - Special Cases of Translatable Strings
  624. // http://www.gnu.org/software/hello/manual/gettext/Special-cases.html#Special-cases
  625. Result := DefaultInstance.gettext_NoOp(szMsgId);
  626. end;
  627. {*------------------------------------------------------------------------------
  628. This is the main translation procedure used in programs. It takes a parameter,
  629. looks it up in the translation dictionary, and returns the translation.
  630. If no translation is found, the parameter is returned.
  631. @param szMsgId The text, that should be displayed if no translation is found.
  632. -------------------------------------------------------------------------------}
  633. function _(const szMsgId: MsgIdString): TranslatedUnicodeString;
  634. begin
  635. Result := DefaultInstance.gettext(szMsgId);
  636. end;
  637. {*------------------------------------------------------------------------------
  638. Translates a text, using a specified translation domain.
  639. If no translation is found, the parameter is returned.
  640. @param szDomain Which translation domain that should be searched for a translation.
  641. @param szMsgId The text, that should be displayed if no translation is found.
  642. -------------------------------------------------------------------------------}
  643. function dgettext(const szDomain: DomainString;
  644. const szMsgId: MsgIdString): TranslatedUnicodeString;
  645. begin
  646. Result := DefaultInstance.dgettext(szDomain, szMsgId);
  647. end;
  648. function dngettext(const szDomain: DomainString; const singular, plural: MsgIdString;
  649. Number: longint): TranslatedUnicodeString;
  650. begin
  651. Result := DefaultInstance.dngettext(szDomain, singular, plural, Number);
  652. end;
  653. function ngettext(const singular, plural: MsgIdString;
  654. Number: longint): TranslatedUnicodeString;
  655. begin
  656. Result := DefaultInstance.ngettext(singular, plural, Number);
  657. end;
  658. procedure textdomain(const szDomain: Domainstring);
  659. begin
  660. DefaultInstance.textdomain(szDomain);
  661. end;
  662. procedure SetGettextEnabled(Enabled: boolean);
  663. begin
  664. DefaultInstance.Enabled := Enabled;
  665. end;
  666. function getcurrenttextdomain: DomainString;
  667. begin
  668. Result := DefaultInstance.getcurrenttextdomain;
  669. end;
  670. procedure bindtextdomain(const szDomain: DomainString;
  671. const szDirectory: FilenameString);
  672. begin
  673. DefaultInstance.bindtextdomain(szDomain, szDirectory);
  674. end;
  675. procedure TP_Ignore(AnObject: TObject; const Name: FilenameString);
  676. begin
  677. DefaultInstance.TP_Ignore(AnObject, Name);
  678. end;
  679. procedure TP_GlobalIgnoreClass(IgnClass: TClass);
  680. begin
  681. DefaultInstance.TP_GlobalIgnoreClass(IgnClass);
  682. end;
  683. procedure TP_IgnoreClass(IgnClass: TClass);
  684. begin
  685. DefaultInstance.TP_IgnoreClass(IgnClass);
  686. end;
  687. procedure TP_IgnoreClassProperty(IgnClass: TClass;
  688. const propertyname: ComponentNameString);
  689. begin
  690. DefaultInstance.TP_IgnoreClassProperty(IgnClass, propertyname);
  691. end;
  692. procedure TP_GlobalIgnoreClassProperty(IgnClass: TClass;
  693. const propertyname: ComponentNameString);
  694. begin
  695. DefaultInstance.TP_GlobalIgnoreClassProperty(IgnClass, propertyname);
  696. end;
  697. procedure TP_GlobalHandleClass(HClass: TClass; Handler: TTranslator);
  698. begin
  699. DefaultInstance.TP_GlobalHandleClass(HClass, Handler);
  700. end;
  701. procedure TranslateComponent(AnObject: TComponent; const TextDomain: DomainString = '');
  702. begin
  703. DefaultInstance.TranslateComponent(AnObject, TextDomain);
  704. end;
  705. procedure RetranslateComponent(AnObject: TComponent;
  706. const TextDomain: DomainString = '');
  707. begin
  708. DefaultInstance.RetranslateComponent(AnObject, TextDomain);
  709. end;
  710. {$ifdef MSWINDOWS}
  711. // These constants are only used in Windows 95
  712. // Thanks to Frank Andreas de Groot for this table
  713. const
  714. IDAfrikaans = $0436;
  715. IDAlbanian = $041C;
  716. IDArabicAlgeria = $1401;
  717. IDArabicBahrain = $3C01;
  718. IDArabicEgypt = $0C01;
  719. IDArabicIraq = $0801;
  720. IDArabicJordan = $2C01;
  721. IDArabicKuwait = $3401;
  722. IDArabicLebanon = $3001;
  723. IDArabicLibya = $1001;
  724. IDArabicMorocco = $1801;
  725. IDArabicOman = $2001;
  726. IDArabicQatar = $4001;
  727. IDArabic = $0401;
  728. IDArabicSyria = $2801;
  729. IDArabicTunisia = $1C01;
  730. IDArabicUAE = $3801;
  731. IDArabicYemen = $2401;
  732. IDArmenian = $042B;
  733. IDAssamese = $044D;
  734. IDAzeriCyrillic = $082C;
  735. IDAzeriLatin = $042C;
  736. IDBasque = $042D;
  737. IDByelorussian = $0423;
  738. IDBengali = $0445;
  739. IDBulgarian = $0402;
  740. IDBurmese = $0455;
  741. IDCatalan = $0403;
  742. IDChineseHongKong = $0C04;
  743. IDChineseMacao = $1404;
  744. IDSimplifiedChinese = $0804;
  745. IDChineseSingapore = $1004;
  746. IDTraditionalChinese = $0404;
  747. IDCroatian = $041A;
  748. IDCzech = $0405;
  749. IDDanish = $0406;
  750. IDBelgianDutch = $0813;
  751. IDDutch = $0413;
  752. IDEnglishAUS = $0C09;
  753. IDEnglishBelize = $2809;
  754. IDEnglishCanadian = $1009;
  755. IDEnglishCaribbean = $2409;
  756. IDEnglishIreland = $1809;
  757. IDEnglishJamaica = $2009;
  758. IDEnglishNewZealand = $1409;
  759. IDEnglishPhilippines = $3409;
  760. IDEnglishSouthAfrica = $1C09;
  761. IDEnglishTrinidad = $2C09;
  762. IDEnglishUK = $0809;
  763. IDEnglishUS = $0409;
  764. IDEnglishZimbabwe = $3009;
  765. IDEstonian = $0425;
  766. IDFaeroese = $0438;
  767. IDFarsi = $0429;
  768. IDFinnish = $040B;
  769. IDBelgianFrench = $080C;
  770. IDFrenchCameroon = $2C0C;
  771. IDFrenchCanadian = $0C0C;
  772. IDFrenchCotedIvoire = $300C;
  773. IDFrench = $040C;
  774. IDFrenchLuxembourg = $140C;
  775. IDFrenchMali = $340C;
  776. IDFrenchMonaco = $180C;
  777. IDFrenchReunion = $200C;
  778. IDFrenchSenegal = $280C;
  779. IDSwissFrench = $100C;
  780. IDFrenchWestIndies = $1C0C;
  781. IDFrenchZaire = $240C;
  782. IDFrisianNetherlands = $0462;
  783. IDGaelicIreland = $083C;
  784. IDGaelicScotland = $043C;
  785. IDGalician = $0456;
  786. IDGeorgian = $0437;
  787. IDGermanAustria = $0C07;
  788. IDGerman = $0407;
  789. IDGermanLiechtenstein = $1407;
  790. IDGermanLuxembourg = $1007;
  791. IDSwissGerman = $0807;
  792. IDGreek = $0408;
  793. IDGujarati = $0447;
  794. IDHebrew = $040D;
  795. IDHindi = $0439;
  796. IDHungarian = $040E;
  797. IDIcelandic = $040F;
  798. IDIndonesian = $0421;
  799. IDItalian = $0410;
  800. IDSwissItalian = $0810;
  801. IDJapanese = $0411;
  802. IDKannada = $044B;
  803. IDKashmiri = $0460;
  804. IDKazakh = $043F;
  805. IDKhmer = $0453;
  806. IDKirghiz = $0440;
  807. IDKonkani = $0457;
  808. IDKorean = $0412;
  809. IDLao = $0454;
  810. IDLatvian = $0426;
  811. IDLithuanian = $0427;
  812. IDMacedonian = $042F;
  813. IDMalaysian = $043E;
  814. IDMalayBruneiDarussalam = $083E;
  815. IDMalayalam = $044C;
  816. IDMaltese = $043A;
  817. IDManipuri = $0458;
  818. IDMarathi = $044E;
  819. IDMongolian = $0450;
  820. IDNepali = $0461;
  821. IDNorwegianBokmol = $0414;
  822. IDNorwegianNynorsk = $0814;
  823. IDOriya = $0448;
  824. IDPolish = $0415;
  825. IDBrazilianPortuguese = $0416;
  826. IDPortuguese = $0816;
  827. IDPunjabi = $0446;
  828. IDRhaetoRomanic = $0417;
  829. IDRomanianMoldova = $0818;
  830. IDRomanian = $0418;
  831. IDRussianMoldova = $0819;
  832. IDRussian = $0419;
  833. IDSamiLappish = $043B;
  834. IDSanskrit = $044F;
  835. IDSerbianCyrillic = $0C1A;
  836. IDSerbianLatin = $081A;
  837. IDSesotho = $0430;
  838. IDSindhi = $0459;
  839. IDSlovak = $041B;
  840. IDSlovenian = $0424;
  841. IDSorbian = $042E;
  842. IDSpanishArgentina = $2C0A;
  843. IDSpanishBolivia = $400A;
  844. IDSpanishChile = $340A;
  845. IDSpanishColombia = $240A;
  846. IDSpanishCostaRica = $140A;
  847. IDSpanishDominicanRepublic = $1C0A;
  848. IDSpanishEcuador = $300A;
  849. IDSpanishElSalvador = $440A;
  850. IDSpanishGuatemala = $100A;
  851. IDSpanishHonduras = $480A;
  852. IDMexicanSpanish = $080A;
  853. IDSpanishNicaragua = $4C0A;
  854. IDSpanishPanama = $180A;
  855. IDSpanishParaguay = $3C0A;
  856. IDSpanishPeru = $280A;
  857. IDSpanishPuertoRico = $500A;
  858. IDSpanishModernSort = $0C0A;
  859. IDSpanish = $040A;
  860. IDSpanishUruguay = $380A;
  861. IDSpanishVenezuela = $200A;
  862. IDSutu = $0430;
  863. IDSwahili = $0441;
  864. IDSwedishFinland = $081D;
  865. IDSwedish = $041D;
  866. IDTajik = $0428;
  867. IDTamil = $0449;
  868. IDTatar = $0444;
  869. IDTelugu = $044A;
  870. IDThai = $041E;
  871. IDTibetan = $0451;
  872. IDTsonga = $0431;
  873. IDTswana = $0432;
  874. IDTurkish = $041F;
  875. IDTurkmen = $0442;
  876. IDUkrainian = $0422;
  877. IDUrdu = $0420;
  878. IDUzbekCyrillic = $0843;
  879. IDUzbekLatin = $0443;
  880. IDVenda = $0433;
  881. IDVietnamese = $042A;
  882. IDWelsh = $0452;
  883. IDXhosa = $0434;
  884. IDZulu = $0435;
  885. function GetWindowsLanguage: WideString;
  886. var
  887. langid: cardinal;
  888. langcode: WideString;
  889. CountryName: array[0..4] of widechar;
  890. LanguageName: array[0..4] of widechar;
  891. works: boolean;
  892. begin
  893. // The return value of GetLocaleInfo is compared with 3 = 2 characters and a zero
  894. works := 3 = GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME,
  895. LanguageName, SizeOf(LanguageName));
  896. works := works and (3 = GetLocaleInfoW(LOCALE_USER_DEFAULT,
  897. LOCALE_SISO3166CTRYNAME, CountryName, SizeOf(CountryName)));
  898. if works then
  899. begin
  900. // Windows 98, Me, NT4, 2000, XP and newer
  901. LangCode := PWideChar(@(LanguageName[0]));
  902. if lowercase(LangCode) = 'no' then
  903. LangCode := 'nb';
  904. LangCode := LangCode + '_' + PWideChar(@CountryName[0]);
  905. end
  906. else
  907. begin
  908. // This part should only happen on Windows 95.
  909. langid := GetThreadLocale;
  910. case langid of
  911. IDBelgianDutch: langcode := 'nl_BE';
  912. IDBelgianFrench: langcode := 'fr_BE';
  913. IDBrazilianPortuguese: langcode := 'pt_BR';
  914. IDDanish: langcode := 'da_DK';
  915. IDDutch: langcode := 'nl_NL';
  916. IDEnglishUK: langcode := 'en_GB';
  917. IDEnglishUS: langcode := 'en_US';
  918. IDFinnish: langcode := 'fi_FI';
  919. IDFrench: langcode := 'fr_FR';
  920. IDFrenchCanadian: langcode := 'fr_CA';
  921. IDGerman: langcode := 'de_DE';
  922. IDGermanLuxembourg: langcode := 'de_LU';
  923. IDGreek: langcode := 'el_GR';
  924. IDIcelandic: langcode := 'is_IS';
  925. IDItalian: langcode := 'it_IT';
  926. IDKorean: langcode := 'ko_KO';
  927. IDNorwegianBokmol: langcode := 'nb_NO';
  928. IDNorwegianNynorsk: langcode := 'nn_NO';
  929. IDPolish: langcode := 'pl_PL';
  930. IDPortuguese: langcode := 'pt_PT';
  931. IDRussian: langcode := 'ru_RU';
  932. IDSpanish, IDSpanishModernSort: langcode := 'es_ES';
  933. IDSwedish: langcode := 'sv_SE';
  934. IDSwedishFinland: langcode := 'sv_FI';
  935. else
  936. langcode := 'C';
  937. end;
  938. end;
  939. Result := langcode;
  940. end;
  941. {$endif}
  942. {$ifndef UNICODE}
  943. function LoadResStringA(ResStringRec: PResStringRec): ansistring;
  944. begin
  945. Result := DefaultInstance.LoadResString(ResStringRec);
  946. end;
  947. {$endif}
  948. function GetTranslatorNameAndEmail: TranslatedUnicodeString;
  949. begin
  950. Result := DefaultInstance.GetTranslatorNameAndEmail;
  951. end;
  952. procedure UseLanguage(LanguageCode: LanguageString);
  953. begin
  954. DefaultInstance.UseLanguage(LanguageCode);
  955. end;
  956. type
  957. PStrData = ^TStrData;
  958. TStrData = record
  959. Ident: integer;
  960. Str: string;
  961. end;
  962. function SysUtilsEnumStringModules(Instance: NativeInt; Data: Pointer): boolean;
  963. {$IFDEF MSWINDOWS}
  964. var
  965. Buffer: array [0..1023] of char; // WideChar in Delphi 2008, AnsiChar before that
  966. begin
  967. with PStrData(Data)^ do
  968. begin
  969. SetString(Str, Buffer,
  970. LoadString(Instance, Ident, @Buffer[0], sizeof(Buffer)));
  971. Result := Str = '';
  972. end;
  973. end;
  974. {$ENDIF}
  975. {$IFDEF LINUX}
  976. var
  977. rs:TResStringRec;
  978. Module:HModule;
  979. begin
  980. Module:=Instance;
  981. rs.Module:=@Module;
  982. with PStrData(Data)^ do begin
  983. rs.Identifier:=Ident;
  984. Str:=System.LoadResString(@rs);
  985. Result:=Str='';
  986. end;
  987. end;
  988. {$ENDIF}
  989. function SysUtilsFindStringResource(Ident: integer): string;
  990. var
  991. StrData: TStrData;
  992. tmp :TEnumModuleFunc;
  993. begin
  994. StrData.Ident := Ident;
  995. StrData.Str := '';
  996. tmp := SysUtilsEnumStringModules;
  997. EnumResourceModules(tmp, Pointer(@StrData));
  998. Result := StrData.Str;
  999. end;
  1000. function SysUtilsLoadStr(Ident: integer): string;
  1001. begin
  1002. {$ifdef DXGETTEXTDEBUG}
  1003. DefaultInstance.DebugWriteln ('Sysutils.LoadRes('+IntToStr(ident)+') called');
  1004. {$endif}
  1005. Result := ResourceStringGettext(SysUtilsFindStringResource(Ident));
  1006. end;
  1007. function SysUtilsFmtLoadStr(Ident: integer; const Args: array of const): string;
  1008. begin
  1009. {$ifdef DXGETTEXTDEBUG}
  1010. DefaultInstance.DebugWriteln ('Sysutils.FmtLoadRes('+IntToStr(ident)+',Args) called');
  1011. {$endif}
  1012. FmtStr(Result, ResourceStringGettext(SysUtilsFindStringResource(Ident)), Args);
  1013. end;
  1014. function LoadResString(ResStringRec: PResStringRec): WideString;
  1015. begin
  1016. Result := DefaultInstance.LoadResString(ResStringRec);
  1017. end;
  1018. function LoadResStringW(ResStringRec: PResStringRec): UnicodeString;
  1019. begin
  1020. Result := DefaultInstance.LoadResString(ResStringRec);
  1021. end;
  1022. function GetCurrentLanguage: LanguageString;
  1023. begin
  1024. Result := DefaultInstance.GetCurrentLanguage;
  1025. end;
  1026. { TDomain }
  1027. procedure TDomain.CloseMoFile;
  1028. begin
  1029. if mofile <> nil then
  1030. begin
  1031. FileLocator.ReleaseMoFile(mofile);
  1032. mofile := nil;
  1033. end;
  1034. OpenHasFailedBefore := False;
  1035. end;
  1036. destructor TDomain.Destroy;
  1037. begin
  1038. CloseMoFile;
  1039. inherited;
  1040. end;
  1041. {$ifdef mswindows}
  1042. function GetLastWinError: WideString;
  1043. var
  1044. errcode: cardinal;
  1045. begin
  1046. SetLength(Result, 2000);
  1047. errcode := GetLastError();
  1048. FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, nil, errcode, 0,
  1049. PWideChar(Result), 2000, nil);
  1050. Result := PWideChar(Result);
  1051. end;
  1052. {$endif}
  1053. procedure TDomain.OpenMoFile;
  1054. var
  1055. filename: FilenameString;
  1056. begin
  1057. // Check if it is already open
  1058. if mofile <> nil then
  1059. exit;
  1060. // Check if it has been attempted to open the file before
  1061. if OpenHasFailedBefore then
  1062. exit;
  1063. if SpecificFilename <> '' then
  1064. begin
  1065. filename := SpecificFilename;
  1066. {$ifdef DXGETTEXTDEBUG}
  1067. DebugLogger ('Domain '+domain+' is bound to specific file '+filename);
  1068. {$endif}
  1069. end
  1070. else
  1071. begin
  1072. filename := Directory + curlang + PathDelim + 'LC_MESSAGES' +
  1073. PathDelim + domain + '.mo';
  1074. if (not FileLocator.FileExists(filename)) and (not fileexists(filename)) then
  1075. begin
  1076. {$ifdef DXGETTEXTDEBUG}
  1077. DebugLogger ('Domain '+domain+': File does not exist, neither embedded or in file system: '+filename);
  1078. {$endif}
  1079. filename := Directory + MidStr(curlang, 1, 2) + PathDelim +
  1080. 'LC_MESSAGES' + PathDelim + domain + '.mo';
  1081. {$ifdef DXGETTEXTDEBUG}
  1082. DebugLogger ('Domain '+domain+' will attempt to use this file: '+filename);
  1083. {$endif}
  1084. end
  1085. else
  1086. begin
  1087. {$ifdef DXGETTEXTDEBUG}
  1088. if FileLocator.FileExists(filename) then
  1089. DebugLogger ('Domain '+domain+' will attempt to use this embedded file: '+filename)
  1090. else
  1091. DebugLogger ('Domain '+domain+' will attempt to use this file that was found on the file system: '+filename);
  1092. {$endif}
  1093. end;
  1094. end;
  1095. if (not FileLocator.FileExists(filename)) and (not fileexists(filename)) then
  1096. begin
  1097. {$ifdef DXGETTEXTDEBUG}
  1098. DebugLogger ('Domain '+domain+' failed to locate the file: '+filename);
  1099. {$endif}
  1100. OpenHasFailedBefore := True;
  1101. exit;
  1102. end;
  1103. {$ifdef DXGETTEXTDEBUG}
  1104. DebugLogger ('Domain '+domain+' now accesses the file.');
  1105. {$endif}
  1106. mofile := FileLocator.GetMoFile(filename, DebugLogger);
  1107. {$ifdef DXGETTEXTDEBUG}
  1108. if mofile.isSwappedArchitecture then
  1109. DebugLogger ('.mo file is swapped (comes from another CPU architecture)');
  1110. {$endif}
  1111. // Check, that the contents of the file is utf-8
  1112. if pos('CHARSET=UTF-8', uppercase(GetTranslationProperty('Content-Type'))) = 0 then
  1113. begin
  1114. CloseMoFile;
  1115. {$ifdef DXGETTEXTDEBUG}
  1116. DebugLogger ('The translation for the language code '+curlang+' (in '+filename+') does not have charset=utf-8 in its Content-Type. Translations are turned off.');
  1117. {$endif}
  1118. {$ifdef MSWINDOWS}
  1119. MessageBoxW(0, PWideChar(WideString('The translation for the language code ' +
  1120. curlang + ' (in ' + filename +
  1121. ') does not have charset=utf-8 in its Content-Type. Translations are turned off.')),
  1122. 'Localization problem', MB_OK);
  1123. {$else}
  1124. writeln (stderr,'The translation for the language code '+curlang+' (in '+filename+') does not have charset=utf-8 in its Content-Type. Translations are turned off.');
  1125. {$endif}
  1126. Enabled := False;
  1127. end;
  1128. end;
  1129. {$IFDEF UNICODE}
  1130. function utf8decode (s:RawByteString):UnicodeString; inline;
  1131. begin
  1132. Result:=UTF8ToWideString(s);
  1133. end;
  1134. {$endif}
  1135. function TDomain.GetTranslationProperty(
  1136. Propertyname: ComponentNameString): TranslatedUnicodeString;
  1137. var
  1138. sl: TStringList;
  1139. i: integer;
  1140. s: string;
  1141. begin
  1142. Propertyname := uppercase(Propertyname) + ': ';
  1143. sl := TStringList.Create;
  1144. try
  1145. sl.Text := utf8decode(gettext(''));
  1146. for i := 0 to sl.Count - 1 do
  1147. begin
  1148. s := sl.Strings[i];
  1149. if uppercase(MidStr(s, 1, length(Propertyname))) = Propertyname then
  1150. begin
  1151. Result := trim(MidStr(s, length(PropertyName) + 1, maxint));
  1152. {$ifdef DXGETTEXTDEBUG}
  1153. DebugLogger ('GetTranslationProperty('+PropertyName+') returns '''+Result+'''.');
  1154. {$endif}
  1155. exit;
  1156. end;
  1157. end;
  1158. finally
  1159. FreeAndNil(sl);
  1160. end;
  1161. Result := '';
  1162. {$ifdef DXGETTEXTDEBUG}
  1163. DebugLogger ('GetTranslationProperty('+PropertyName+') did not find any value. An empty string is returned.');
  1164. {$endif}
  1165. end;
  1166. procedure TDomain.setDirectory(const dir: FilenameString);
  1167. begin
  1168. vDirectory := IncludeTrailingPathDelimiter(dir);
  1169. SpecificFilename := '';
  1170. CloseMoFile;
  1171. end;
  1172. procedure AddDomainForResourceString(const domain: DomainString);
  1173. begin
  1174. {$ifdef DXGETTEXTDEBUG}
  1175. DefaultInstance.DebugWriteln ('Extra domain for resourcestring: '+domain);
  1176. {$endif}
  1177. ResourceStringDomainListCS.BeginWrite;
  1178. try
  1179. if ResourceStringDomainList.IndexOf(domain) = -1 then
  1180. ResourceStringDomainList.Add(domain);
  1181. finally
  1182. ResourceStringDomainListCS.EndWrite;
  1183. end;
  1184. end;
  1185. procedure RemoveDomainForResourceString(const domain: DomainString);
  1186. var
  1187. i: integer;
  1188. begin
  1189. {$ifdef DXGETTEXTDEBUG}
  1190. DefaultInstance.DebugWriteln ('Remove domain for resourcestring: '+domain);
  1191. {$endif}
  1192. ResourceStringDomainListCS.BeginWrite;
  1193. try
  1194. i := ResourceStringDomainList.IndexOf(domain);
  1195. if i <> -1 then
  1196. ResourceStringDomainList.Delete(i);
  1197. finally
  1198. ResourceStringDomainListCS.EndWrite;
  1199. end;
  1200. end;
  1201. procedure AddDomainForComponent (const domain:DomainString);
  1202. begin
  1203. {$ifdef DXGETTEXTDEBUG}
  1204. DefaultInstance.DebugWriteln ('Extra domain for component: '+domain);
  1205. {$endif}
  1206. ComponentDomainListCS.BeginWrite;
  1207. try
  1208. if ComponentDomainList.IndexOf(domain)=-1 then
  1209. ComponentDomainList.Add (domain);
  1210. finally
  1211. ComponentDomainListCS.EndWrite;
  1212. end;
  1213. end;
  1214. procedure RemoveDomainForComponent (const domain:DomainString);
  1215. var
  1216. i:integer;
  1217. begin
  1218. {$ifdef DXGETTEXTDEBUG}
  1219. DefaultInstance.DebugWriteln ('Remove domain for component: '+domain);
  1220. {$endif}
  1221. ComponentDomainListCS.BeginWrite;
  1222. try
  1223. i:=ComponentDomainList.IndexOf(domain);
  1224. if i<>-1 then
  1225. ComponentDomainList.Delete (i);
  1226. finally
  1227. ComponentDomainListCS.EndWrite;
  1228. end;
  1229. end;
  1230. procedure TDomain.SetLanguageCode(const langcode: LanguageString);
  1231. begin
  1232. CloseMoFile;
  1233. curlang := langcode;
  1234. end;
  1235. function GetPluralForm2EN(Number: integer): integer;
  1236. begin
  1237. Number := abs(Number);
  1238. if Number = 1 then
  1239. Result := 0
  1240. else
  1241. Result := 1;
  1242. end;
  1243. function GetPluralForm1(Number: integer): integer;
  1244. begin
  1245. Result := 0;
  1246. end;
  1247. function GetPluralForm2FR(Number: integer): integer;
  1248. begin
  1249. Number := abs(Number);
  1250. if (Number = 1) or (Number = 0) then
  1251. Result := 0
  1252. else
  1253. Result := 1;
  1254. end;
  1255. function GetPluralForm3LV(Number: integer): integer;
  1256. begin
  1257. Number := abs(Number);
  1258. if (Number mod 10 = 1) and (Number mod 100 <> 11) then
  1259. Result := 0
  1260. else
  1261. if Number <> 0 then
  1262. Result := 1
  1263. else
  1264. Result := 2;
  1265. end;
  1266. function GetPluralForm3GA(Number: integer): integer;
  1267. begin
  1268. Number := abs(Number);
  1269. if Number = 1 then
  1270. Result := 0
  1271. else if Number = 2 then
  1272. Result := 1
  1273. else
  1274. Result := 2;
  1275. end;
  1276. function GetPluralForm3LT(Number: integer): integer;
  1277. var
  1278. n1, n2: byte;
  1279. begin
  1280. Number := abs(Number);
  1281. n1 := Number mod 10;
  1282. n2 := Number mod 100;
  1283. if (n1 = 1) and (n2 <> 11) then
  1284. Result := 0
  1285. else
  1286. if (n1 >= 2) and ((n2 < 10) or (n2 >= 20)) then
  1287. Result := 1
  1288. else
  1289. Result := 2;
  1290. end;
  1291. function GetPluralForm3PL(Number: integer): integer;
  1292. var
  1293. n1, n2: byte;
  1294. begin
  1295. Number := abs(Number);
  1296. n1 := Number mod 10;
  1297. n2 := Number mod 100;
  1298. if Number = 1 then
  1299. Result := 0
  1300. else if (n1 >= 2) and (n1 <= 4) and ((n2 < 10) or (n2 >= 20)) then
  1301. Result := 1
  1302. else
  1303. Result := 2;
  1304. end;
  1305. function GetPluralForm3RU(Number: integer): integer;
  1306. var
  1307. n1, n2: byte;
  1308. begin
  1309. Number := abs(Number);
  1310. n1 := Number mod 10;
  1311. n2 := Number mod 100;
  1312. if (n1 = 1) and (n2 <> 11) then
  1313. Result := 0
  1314. else
  1315. if (n1 >= 2) and (n1 <= 4) and ((n2 < 10) or (n2 >= 20)) then
  1316. Result := 1
  1317. else
  1318. Result := 2;
  1319. end;
  1320. function GetPluralForm3SK(Number: integer): integer;
  1321. begin
  1322. Number := abs(Number);
  1323. if number = 1 then
  1324. Result := 0
  1325. else if (number < 5) and (number <> 0) then
  1326. Result := 1
  1327. else
  1328. Result := 2;
  1329. end;
  1330. function GetPluralForm4SL(Number: integer): integer;
  1331. var
  1332. n2: byte;
  1333. begin
  1334. Number := abs(Number);
  1335. n2 := Number mod 100;
  1336. if n2 = 1 then
  1337. Result := 0
  1338. else
  1339. if n2 = 2 then
  1340. Result := 1
  1341. else
  1342. if (n2 = 3) or (n2 = 4) then
  1343. Result := 2
  1344. else
  1345. Result := 3;
  1346. end;
  1347. procedure TDomain.GetListOfLanguages(list: TStrings);
  1348. var
  1349. sr: TSearchRec;
  1350. more: boolean;
  1351. filename, path: FilenameString;
  1352. langcode: LanguageString;
  1353. i, j: integer;
  1354. begin
  1355. list.Clear;
  1356. // Iterate through filesystem
  1357. more := FindFirst(Directory + '*', faAnyFile, sr) = 0;
  1358. try
  1359. while more do
  1360. begin
  1361. if (sr.Attr and faDirectory <> 0) and (sr.Name <> '.') and (sr.Name <> '..') then
  1362. begin
  1363. filename := Directory + sr.Name + PathDelim + 'LC_MESSAGES' +
  1364. PathDelim + domain + '.mo';
  1365. if fileexists(filename) then
  1366. begin
  1367. langcode := lowercase(sr.Name);
  1368. if list.IndexOf(langcode) = -1 then
  1369. list.Add(langcode);
  1370. end;
  1371. end;
  1372. more := FindNext(sr) = 0;
  1373. end;
  1374. finally
  1375. FindClose(sr);
  1376. end;
  1377. // Iterate through embedded files
  1378. for i := 0 to FileLocator.filelist.Count - 1 do
  1379. begin
  1380. filename := FileLocator.basedirectory + FileLocator.filelist.Strings[i];
  1381. path := Directory;
  1382. {$ifdef MSWINDOWS}
  1383. path := uppercase(path);
  1384. filename := uppercase(filename);
  1385. {$endif}
  1386. j := length(path);
  1387. if MidStr(filename, 1, j) = path then
  1388. begin
  1389. path := PathDelim + 'LC_MESSAGES' + PathDelim + domain + '.mo';
  1390. {$ifdef MSWINDOWS}
  1391. path := uppercase(path);
  1392. {$endif}
  1393. if MidStr(filename, length(filename) - length(path) + 1, length(path)) = path then
  1394. begin
  1395. langcode := lowercase(MidStr(filename, j + 1, length(filename) -
  1396. length(path) - j));
  1397. langcode := LeftStr(langcode, 3) + uppercase(MidStr(langcode, 4, maxint));
  1398. if list.IndexOf(langcode) = -1 then
  1399. list.Add(langcode);
  1400. end;
  1401. end;
  1402. end;
  1403. end;
  1404. procedure TDomain.SetFilename(const filename: FilenameString);
  1405. begin
  1406. CloseMoFile;
  1407. vDirectory := '';
  1408. SpecificFilename := filename;
  1409. end;
  1410. function TDomain.gettext(const msgid: RawUtf8String): RawUtf8String;
  1411. var
  1412. found: boolean;
  1413. begin
  1414. if not Enabled then
  1415. begin
  1416. Result := msgid;
  1417. exit;
  1418. end;
  1419. if (mofile = nil) and (not OpenHasFailedBefore) then
  1420. OpenMoFile;
  1421. if mofile = nil then
  1422. begin
  1423. {$ifdef DXGETTEXTDEBUG}
  1424. DebugLogger('.mo file is not open. Not translating "'+msgid+'"');
  1425. {$endif}
  1426. Result := msgid;
  1427. end
  1428. else
  1429. begin
  1430. Result := mofile.gettext(msgid, found);
  1431. {$ifdef DXGETTEXTDEBUG}
  1432. if found then
  1433. DebugLogger ('Found in .mo ('+Domain+'): "'+utf8encode(msgid)+'"->"'+utf8encode(Result)+'"')
  1434. else
  1435. DebugLogger ('Translation not found in .mo file ('+Domain+') : "'+utf8encode(msgid)+'"');
  1436. {$endif}
  1437. end;
  1438. end;
  1439. constructor TDomain.Create;
  1440. begin
  1441. inherited Create;
  1442. Enabled := True;
  1443. end;
  1444. { TGnuGettextInstance }
  1445. procedure TGnuGettextInstance.bindtextdomain(const szDomain: DomainString;
  1446. const szDirectory: FilenameString);
  1447. var
  1448. dir: FilenameString;
  1449. begin
  1450. dir := IncludeTrailingPathDelimiter(szDirectory);
  1451. {$ifdef DXGETTEXTDEBUG}
  1452. DebugWriteln ('Text domain "'+szDomain+'" is now located at "'+dir+'"');
  1453. {$endif}
  1454. getdomain(szDomain, DefaultDomainDirectory, CurLang).Directory := dir;
  1455. WhenNewDomainDirectory(szDomain, szDirectory);
  1456. end;
  1457. constructor TGnuGettextInstance.Create;
  1458. begin
  1459. CreatorThread := GetCurrentThreadId;
  1460. {$ifdef MSWindows}
  1461. DesignTimeCodePage := CP_ACP;
  1462. {$endif}
  1463. {$ifdef DXGETTEXTDEBUG}
  1464. DebugLogCS:=TMultiReadExclusiveWriteSynchronizer.Create;
  1465. DebugLog:=TMemoryStream.Create;
  1466. DebugWriteln('Debug log started '+DateTimeToStr(Now));
  1467. DebugWriteln('GNU gettext module version: '+VCSVersion);
  1468. DebugWriteln('');
  1469. {$endif}
  1470. curGetPluralForm := GetPluralForm2EN;
  1471. Enabled := True;
  1472. curmsgdomain := DefaultTextDomain;
  1473. savefileCS := TMultiReadExclusiveWriteSynchronizer.Create;
  1474. domainlist := TStringList.Create;
  1475. TP_IgnoreList := TStringList.Create;
  1476. TP_IgnoreList.Sorted := True;
  1477. TP_GlobalClassHandling := TList.Create;
  1478. TP_ClassHandling := TList.Create;
  1479. // Set some settings
  1480. DefaultDomainDirectory := IncludeTrailingPathDelimiter(
  1481. extractfilepath(ExecutableFilename)) + 'locale';
  1482. UseLanguage('');
  1483. bindtextdomain(DefaultTextDomain, DefaultDomainDirectory);
  1484. textdomain(DefaultTextDomain);
  1485. // Add default properties to ignore
  1486. TP_GlobalIgnoreClassProperty(TComponent, 'Name');
  1487. TP_GlobalIgnoreClassProperty(TCollection, 'PropName');
  1488. end;
  1489. destructor TGnuGettextInstance.Destroy;
  1490. begin
  1491. if savememory <> nil then
  1492. begin
  1493. savefileCS.BeginWrite;
  1494. try
  1495. CloseFile(savefile);
  1496. finally
  1497. savefileCS.EndWrite;
  1498. end;
  1499. FreeAndNil(savememory);
  1500. end;
  1501. FreeAndNil(savefileCS);
  1502. FreeAndNil(TP_IgnoreList);
  1503. while TP_GlobalClassHandling.Count <> 0 do
  1504. begin
  1505. TObject(TP_GlobalClassHandling.Items[0]).Free;
  1506. TP_GlobalClassHandling.Delete(0);
  1507. end;
  1508. FreeAndNil(TP_GlobalClassHandling);
  1509. FreeTP_ClassHandlingItems;
  1510. FreeAndNil(TP_ClassHandling);
  1511. while domainlist.Count <> 0 do
  1512. begin
  1513. domainlist.Objects[0].Free;
  1514. domainlist.Delete(0);
  1515. end;
  1516. FreeAndNil(domainlist);
  1517. {$ifdef DXGETTEXTDEBUG}
  1518. FreeAndNil (DebugLog);
  1519. FreeAndNil (DebugLogCS);
  1520. {$endif}
  1521. inherited;
  1522. end;
  1523. {$ifndef UNICODE}
  1524. function TGnuGettextInstance.dgettext(const szDomain: DomainString;
  1525. const szMsgId: ansistring): TranslatedUnicodeString;
  1526. begin
  1527. Result := dgettext(szDomain, ansi2wideDTCP(szMsgId));
  1528. end;
  1529. {$endif}
  1530. function TGnuGettextInstance.dgettext(const szDomain: DomainString;
  1531. const szMsgId: MsgIdString): TranslatedUnicodeString;
  1532. begin
  1533. if not Enabled then
  1534. begin
  1535. {$ifdef DXGETTEXTDEBUG}
  1536. DebugWriteln ('Translation has been disabled. Text is not being translated: '+szMsgid);
  1537. {$endif}
  1538. Result := szMsgId;
  1539. end
  1540. else
  1541. begin
  1542. Result := UTF8Decode(EnsureLineBreakInTranslatedString(
  1543. getdomain(szDomain, DefaultDomainDirectory, CurLang).gettext(
  1544. StripCRRawMsgId(utf8encode(szMsgId)))));
  1545. {$ifdef DXGETTEXTDEBUG}
  1546. if (szMsgId<>'') and (Result='') then
  1547. DebugWriteln (Format('Error: Translation of %s was an empty string. This may never occur.',[szMsgId]));
  1548. {$endif}
  1549. end;
  1550. end;
  1551. function TGnuGettextInstance.dgettext_NoExtract(const szDomain: DomainString;
  1552. const szMsgId: MsgIdString): TranslatedUnicodeString;
  1553. begin
  1554. // This one is very useful for translating text in variables.
  1555. // This can sometimes be necessary, and by using this function,
  1556. // the source code scanner will not trigger warnings.
  1557. Result := dgettext(szDomain, szMsgId);
  1558. end;
  1559. function TGnuGettextInstance.GetCurrentLanguage: LanguageString;
  1560. begin
  1561. Result := curlang;
  1562. end;
  1563. function TGnuGettextInstance.getcurrenttextdomain: DomainString;
  1564. begin
  1565. Result := curmsgdomain;
  1566. end;
  1567. {$ifndef UNICODE}
  1568. function TGnuGettextInstance.gettext(
  1569. const szMsgId: ansistring): TranslatedUnicodeString;
  1570. begin
  1571. Result := dgettext(curmsgdomain, szMsgId);
  1572. end;
  1573. {$endif}
  1574. function TGnuGettextInstance.gettext(
  1575. const szMsgId: MsgIdString): TranslatedUnicodeString;
  1576. begin
  1577. Result := dgettext(curmsgdomain, szMsgId);
  1578. end;
  1579. function TGnuGettextInstance.gettext_NoExtract(
  1580. const szMsgId: MsgIdString): TranslatedUnicodeString;
  1581. begin
  1582. // This one is very useful for translating text in variables.
  1583. // This can sometimes be necessary, and by using this function,
  1584. // the source code scanner will not trigger warnings.
  1585. Result:=gettext (szMsgId);
  1586. end;
  1587. function TGnuGettextInstance.gettext_NoOp(const szMsgId: MsgIdString): TranslatedUnicodeString;
  1588. begin
  1589. //*** With this function Strings can be added to the po-file without beeing
  1590. // ResourceStrings (dxgettext will add the string and this function will
  1591. // return it without a change)
  1592. // see gettext manual
  1593. // 4.7 - Special Cases of Translatable Strings
  1594. // http://www.gnu.org/software/hello/manual/gettext/Special-cases.html#Special-cases
  1595. Result := TranslatedUnicodeString(szMsgId);
  1596. end;
  1597. procedure TGnuGettextInstance.textdomain(const szDomain: DomainString);
  1598. begin
  1599. {$ifdef DXGETTEXTDEBUG}
  1600. DebugWriteln ('Changed text domain to "'+szDomain+'"');
  1601. {$endif}
  1602. curmsgdomain := szDomain;
  1603. WhenNewDomain(szDomain);
  1604. end;
  1605. function TGnuGettextInstance.TP_CreateRetranslator: TExecutable;
  1606. var
  1607. ttpr: TTP_Retranslator;
  1608. begin
  1609. ttpr := TTP_Retranslator.Create;
  1610. ttpr.Instance := self;
  1611. TP_Retranslator := ttpr;
  1612. Result := ttpr;
  1613. {$ifdef DXGETTEXTDEBUG}
  1614. DebugWriteln ('A retranslator was created.');
  1615. {$endif}
  1616. end;
  1617. procedure TGnuGettextInstance.TP_GlobalHandleClass(HClass: TClass;
  1618. Handler: TTranslator);
  1619. var
  1620. cm: TClassMode;
  1621. i: integer;
  1622. begin
  1623. for i := 0 to TP_GlobalClassHandling.Count - 1 do
  1624. begin
  1625. cm := TObject(TP_GlobalClassHandling.Items[i]) as TClassMode;
  1626. if cm.HClass = HClass then
  1627. raise EGGProgrammingError.Create(
  1628. 'You cannot set a handler for a class that has already been assigned otherwise.');
  1629. if HClass.InheritsFrom(cm.HClass) then
  1630. begin
  1631. // This is the place to insert this class
  1632. cm := TClassMode.Create;
  1633. cm.HClass := HClass;
  1634. cm.SpecialHandler := Handler;
  1635. TP_GlobalClassHandling.Insert(i, cm);
  1636. {$ifdef DXGETTEXTDEBUG}
  1637. DebugWriteln ('A handler was set for class '+HClass.ClassName+'.');
  1638. {$endif}
  1639. exit;
  1640. end;
  1641. end;
  1642. cm := TClassMode.Create;
  1643. cm.HClass := HClass;
  1644. cm.SpecialHandler := Handler;
  1645. TP_GlobalClassHandling.Add(cm);
  1646. {$ifdef DXGETTEXTDEBUG}
  1647. DebugWriteln ('A handler was set for class '+HClass.ClassName+'.');
  1648. {$endif}
  1649. end;
  1650. procedure TGnuGettextInstance.TP_GlobalIgnoreClass(IgnClass: TClass);
  1651. var
  1652. cm: TClassMode;
  1653. i: integer;
  1654. begin
  1655. for i := 0 to TP_GlobalClassHandling.Count - 1 do
  1656. begin
  1657. cm := TObject(TP_GlobalClassHandling.Items[i]) as TClassMode;
  1658. if cm.HClass = IgnClass then
  1659. raise EGGProgrammingError.Create(
  1660. 'You cannot add a class to the ignore list that is already on that list: ' +
  1661. IgnClass.ClassName +
  1662. '. You should keep all TP_Global functions in one place in your source code.');
  1663. if IgnClass.InheritsFrom(cm.HClass) then
  1664. begin
  1665. // This is the place to insert this class
  1666. cm := TClassMode.Create;
  1667. cm.HClass := IgnClass;
  1668. TP_GlobalClassHandling.Insert(i, cm);
  1669. {$ifdef DXGETTEXTDEBUG}
  1670. DebugWriteln ('Globally, class '+IgnClass.ClassName+' is being ignored.');
  1671. {$endif}
  1672. exit;
  1673. end;
  1674. end;
  1675. cm := TClassMode.Create;
  1676. cm.HClass := IgnClass;
  1677. TP_GlobalClassHandling.Add(cm);
  1678. {$ifdef DXGETTEXTDEBUG}
  1679. DebugWriteln ('Globally, class '+IgnClass.ClassName+' is being ignored.');
  1680. {$endif}
  1681. end;
  1682. procedure TGnuGettextInstance.TP_GlobalIgnoreClassProperty(IgnClass: TClass;
  1683. propertyname: ComponentNameString);
  1684. var
  1685. cm: TClassMode;
  1686. i, idx: integer;
  1687. begin
  1688. propertyname := uppercase(propertyname);
  1689. for i := 0 to TP_GlobalClassHandling.Count - 1 do
  1690. begin
  1691. cm := TObject(TP_GlobalClassHandling.Items[i]) as TClassMode;
  1692. if cm.HClass = IgnClass then
  1693. begin
  1694. if Assigned(cm.SpecialHandler) then
  1695. raise EGGProgrammingError.Create(
  1696. 'You cannot ignore a class property for a class that has a handler set.');
  1697. if not cm.PropertiesToIgnore.Find(propertyname, idx) then
  1698. cm.PropertiesToIgnore.Add(propertyname);
  1699. {$ifdef DXGETTEXTDEBUG}
  1700. DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.');
  1701. {$endif}
  1702. exit;
  1703. end;
  1704. if IgnClass.InheritsFrom(cm.HClass) then
  1705. begin
  1706. // This is the place to insert this class
  1707. cm := TClassMode.Create;
  1708. cm.HClass := IgnClass;
  1709. cm.PropertiesToIgnore.Add(propertyname);
  1710. TP_GlobalClassHandling.Insert(i, cm);
  1711. {$ifdef DXGETTEXTDEBUG}
  1712. DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.');
  1713. {$endif}
  1714. exit;
  1715. end;
  1716. end;
  1717. cm := TClassMode.Create;
  1718. cm.HClass := IgnClass;
  1719. cm.PropertiesToIgnore.Add(propertyname);
  1720. TP_GlobalClassHandling.Add(cm);
  1721. {$ifdef DXGETTEXTDEBUG}
  1722. DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.');
  1723. {$endif}
  1724. end;
  1725. procedure TGnuGettextInstance.TP_Ignore(AnObject: TObject;
  1726. const Name: ComponentNameString);
  1727. begin
  1728. TP_IgnoreList.Add(uppercase(Name));
  1729. {$ifdef DXGETTEXTDEBUG}
  1730. DebugWriteln ('On object with class name '+AnObject.ClassName+', ignore is set on '+name);
  1731. {$endif}
  1732. end;
  1733. procedure TGnuGettextInstance.TranslateComponent(AnObject: TComponent;
  1734. const TextDomain: DomainString);
  1735. var
  1736. comp: TGnuGettextComponentMarker;
  1737. begin
  1738. {$ifdef DXGETTEXTDEBUG}
  1739. DebugWriteln ('======================================================================');
  1740. DebugWriteln ('TranslateComponent() was called for a component with name '+AnObject.Name+'.');
  1741. {$endif}
  1742. comp := AnObject.FindComponent('GNUgettextMarker') as TGnuGettextComponentMarker;
  1743. if comp = nil then
  1744. begin
  1745. comp := TGnuGettextComponentMarker.Create(nil);
  1746. comp.Name := 'GNUgettextMarker';
  1747. comp.Retranslator := TP_CreateRetranslator;
  1748. TranslateProperties(AnObject, TextDomain);
  1749. AnObject.InsertComponent(comp);
  1750. {$ifdef DXGETTEXTDEBUG}
  1751. DebugWriteln ('This is the first time, that this component has been translated. A retranslator component has been created for this component.');
  1752. {$endif}
  1753. end
  1754. else
  1755. begin
  1756. {$ifdef DXGETTEXTDEBUG}
  1757. DebugWriteln ('This is not the first time, that this component has been translated.');
  1758. {$endif}
  1759. if comp.LastLanguage <> curlang then
  1760. begin
  1761. {$ifdef DXGETTEXTDEBUG}
  1762. DebugWriteln ('ERROR: TranslateComponent() was called twice with different languages. This indicates an attempt to switch language at runtime, but by using TranslateComponent every time. This API has changed - please use RetranslateComponent() instead.');
  1763. {$endif}
  1764. {$ifdef mswindows}
  1765. MessageBox(0,
  1766. 'This application tried to switch the language, but in an incorrect way. The programmer needs to replace a call to TranslateComponent with a call to RetranslateComponent(). The programmer should see the changelog of gnugettext.pas for more information.', 'Error', MB_OK);
  1767. {$else}
  1768. writeln (stderr,'This application tried to switch the language, but in an incorrect way. The programmer needs to replace a call to TranslateComponent with a call to RetranslateComponent(). The programmer should see the changelog of gnugettext.pas for more information.');
  1769. {$endif}
  1770. end
  1771. else
  1772. begin
  1773. {$ifdef DXGETTEXTDEBUG}
  1774. DebugWriteln ('ERROR: TranslateComponent has been called twice, but with the same language chosen. This is a mistake, but in order to prevent that the application breaks, no exception is raised.');
  1775. {$endif}
  1776. end;
  1777. end;
  1778. comp.LastLanguage := curlang;
  1779. {$ifdef DXGETTEXTDEBUG}
  1780. DebugWriteln ('======================================================================');
  1781. {$endif}
  1782. end;
  1783. procedure TGnuGettextInstance.TranslateProperty(AnObject: TObject;
  1784. PropInfo: PPropInfo; TodoList: TStrings; const TextDomain: DomainString);
  1785. var
  1786. ppi: PPropInfo;
  1787. ws: TranslatedUnicodeString;
  1788. old: TranslatedUnicodeString;
  1789. compmarker: TComponent;
  1790. obj: TObject;
  1791. Propname: ComponentNameString;
  1792. begin
  1793. PropName := string(PropInfo^.Name);
  1794. try
  1795. // Translate certain types of properties
  1796. case PropInfo^.PropType^.Kind of
  1797. {$IFDEF UNICODE}
  1798. // All dfm files returning tkUString
  1799. tkString, tkLString, tkWString, tkUString:
  1800. {$ELSE}
  1801. tkString, tkLString, tkWString:
  1802. {$ENDIF}
  1803. begin
  1804. {$ifdef DXGETTEXTDEBUG}
  1805. DebugWriteln ('Translating '+AnObject.ClassName+'.'+PropName);
  1806. {$endif}
  1807. case PropInfo^.PropType^.Kind of
  1808. tkString, tkLString:
  1809. old := GetStrProp(AnObject, PropName);
  1810. tkWString:
  1811. old := GetStrProp(AnObject, PropName);
  1812. tkUString :
  1813. old := GetStrProp(AnObject, PropName);
  1814. else
  1815. raise Exception.Create(
  1816. 'Internal error: Illegal property type. This problem needs to be solved by a programmer, try to find a workaround.');
  1817. end;
  1818. {$ifdef DXGETTEXTDEBUG}
  1819. if old='' then
  1820. DebugWriteln ('(Empty, not translated)')
  1821. else
  1822. DebugWriteln ('Old value: "'+old+'"');
  1823. {$endif}
  1824. if (old <> '') and (IsWriteProp(PropInfo)) then
  1825. begin
  1826. if TP_Retranslator <> nil then
  1827. (TP_Retranslator as TTP_Retranslator).Remember(AnObject, PropName, old);
  1828. if textdomain = '' then
  1829. ws := ComponentGettext(old)
  1830. else
  1831. ws := dgettext(textdomain, old);
  1832. if ws <> old then
  1833. begin
  1834. ppi := GetPropInfo(AnObject, Propname);
  1835. if ppi <> nil then
  1836. begin
  1837. SetWideStrProp(AnObject, ppi, ws);
  1838. end
  1839. else
  1840. begin
  1841. {$ifdef DXGETTEXTDEBUG}
  1842. DebugWriteln ('ERROR: Property disappeared: '+Propname+' for object of type '+AnObject.ClassName);
  1843. {$endif}
  1844. end;
  1845. end;
  1846. end;
  1847. end { case item };
  1848. tkClass:
  1849. begin
  1850. obj := GetObjectProp(AnObject, PropName);
  1851. if obj <> nil then
  1852. begin
  1853. if obj is TComponent then
  1854. begin
  1855. compmarker := TComponent(obj).FindComponent('GNUgettextMarker');
  1856. if Assigned(compmarker) then
  1857. exit;
  1858. end;
  1859. TodoList.AddObject('', obj);
  1860. end;
  1861. end { case item };
  1862. end { case };
  1863. except
  1864. on E: Exception do
  1865. raise EGGComponentError.Create('Property cannot be translated.' +
  1866. sLineBreak + 'Add TP_GlobalIgnoreClassProperty(' + AnObject.ClassName +
  1867. ',''' + PropName + ''') to your source code or use' +
  1868. sLineBreak + 'TP_Ignore (self,''.' + PropName +
  1869. ''') to prevent this message.' + sLineBreak + 'Reason: ' + e.Message);
  1870. end;
  1871. end;
  1872. procedure TGnuGettextInstance.TranslateProperties(AnObject: TObject;
  1873. textdomain: DomainString = '');
  1874. var
  1875. TodoList: TStringList; // List of Name/TObject's that is to be processed
  1876. DoneList: TStringList;
  1877. // List of hex codes representing pointers to objects that have been done
  1878. i, j, Count: integer;
  1879. PropList: PPropList;
  1880. UPropName: ComponentNameString;
  1881. PropInfo: PPropInfo;
  1882. compmarker, comp: TComponent;
  1883. cm, currentcm: TClassMode;
  1884. // currentcm is nil or contains special information about how to handle the current object
  1885. ObjectPropertyIgnoreList: TStringList;
  1886. objid: string;
  1887. Name: ComponentNameString;
  1888. begin
  1889. {$ifdef DXGETTEXTDEBUG}
  1890. DebugWriteln ('----------------------------------------------------------------------');
  1891. DebugWriteln ('TranslateProperties() was called for an object of class '+AnObject.ClassName+' with domain "'+textdomain+'".');
  1892. {$endif}
  1893. if textdomain = '' then
  1894. textdomain := curmsgdomain;
  1895. if TP_Retranslator <> nil then
  1896. (TP_Retranslator as TTP_Retranslator).TextDomain := textdomain;
  1897. {$ifdef FPC}
  1898. DoneList:=TCSStringList.Create;
  1899. TodoList:=TCSStringList.Create;
  1900. ObjectPropertyIgnoreList:=TCSStringList.Create;
  1901. {$else}
  1902. DoneList := TStringList.Create;
  1903. TodoList := TStringList.Create;
  1904. ObjectPropertyIgnoreList := TStringList.Create;
  1905. {$endif}
  1906. try
  1907. TodoList.AddObject('', AnObject);
  1908. DoneList.Sorted := True;
  1909. ObjectPropertyIgnoreList.Sorted := True;
  1910. ObjectPropertyIgnoreList.Duplicates := dupIgnore;
  1911. ObjectPropertyIgnoreList.CaseSensitive := False;
  1912. DoneList.Duplicates := dupError;
  1913. DoneList.CaseSensitive := True;
  1914. while TodoList.Count <> 0 do
  1915. begin
  1916. AnObject := TodoList.Objects[0];
  1917. Name := TodoList.Strings[0];
  1918. TodoList.Delete(0);
  1919. if (AnObject <> nil) and (AnObject is TPersistent) then
  1920. begin
  1921. // Make sure each object is only translated once
  1922. Assert(sizeof(integer) = sizeof(TObject));
  1923. objid := IntToHex(integer(AnObject), 8);
  1924. if DoneList.Find(objid, i) then
  1925. begin
  1926. continue;
  1927. end
  1928. else
  1929. begin
  1930. DoneList.Add(objid);
  1931. end;
  1932. ObjectPropertyIgnoreList.Clear;
  1933. // Find out if there is special handling of this object
  1934. currentcm := nil;
  1935. // First check the local handling instructions
  1936. for j := 0 to TP_ClassHandling.Count - 1 do
  1937. begin
  1938. cm := TObject(TP_ClassHandling.Items[j]) as TClassMode;
  1939. if AnObject.InheritsFrom(cm.HClass) then
  1940. begin
  1941. if cm.PropertiesToIgnore.Count <> 0 then
  1942. begin
  1943. ObjectPropertyIgnoreList.AddStrings(cm.PropertiesToIgnore);
  1944. end
  1945. else
  1946. begin
  1947. // Ignore the entire class
  1948. currentcm := cm;
  1949. break;
  1950. end;
  1951. end;
  1952. end;
  1953. // Then check the global handling instructions
  1954. if currentcm = nil then
  1955. for j := 0 to TP_GlobalClassHandling.Count - 1 do
  1956. begin
  1957. cm := TObject(TP_GlobalClassHandling.Items[j]) as TClassMode;
  1958. if AnObject.InheritsFrom(cm.HClass) then
  1959. begin
  1960. if cm.PropertiesToIgnore.Count <> 0 then
  1961. begin
  1962. ObjectPropertyIgnoreList.AddStrings(cm.PropertiesToIgnore);
  1963. end
  1964. else
  1965. begin
  1966. // Ignore the entire class
  1967. currentcm := cm;
  1968. break;
  1969. end;
  1970. end;
  1971. end;
  1972. if currentcm <> nil then
  1973. begin
  1974. ObjectPropertyIgnoreList.Clear;
  1975. // Ignore or use special handler
  1976. if Assigned(currentcm.SpecialHandler) then
  1977. begin
  1978. currentcm.SpecialHandler(AnObject);
  1979. {$ifdef DXGETTEXTDEBUG}
  1980. DebugWriteln ('Special handler activated for '+AnObject.ClassName);
  1981. {$endif}
  1982. end
  1983. else
  1984. begin
  1985. {$ifdef DXGETTEXTDEBUG}
  1986. DebugWriteln ('Ignoring object '+AnObject.ClassName);
  1987. {$endif}
  1988. end;
  1989. continue;
  1990. end;
  1991. Count := GetPropList(AnObject, PropList);
  1992. try
  1993. for j := 0 to Count - 1 do
  1994. begin
  1995. PropInfo := PropList[j];
  1996. {$IFDEF UNICODE}
  1997. if not (PropInfo^.PropType^.Kind in [tkString, tkLString, tkWString, tkClass, tkUString]) then
  1998. {$ELSE}
  1999. if not (PropInfo^.PropType^.Kind in [tkString, tkLString,
  2000. tkWString, tkClass]) then
  2001. {$ENDIF}
  2002. continue;
  2003. UPropName := uppercase(string(PropInfo^.Name));
  2004. // Ignore properties that are meant to be ignored
  2005. if ((currentcm = nil) or (not
  2006. currentcm.PropertiesToIgnore.Find(UPropName, i))) and
  2007. (not TP_IgnoreList.Find(Name + '.' + UPropName, i)) and
  2008. (not ObjectPropertyIgnoreList.Find(UPropName, i)) then
  2009. begin
  2010. TranslateProperty(AnObject, PropInfo, TodoList, TextDomain);
  2011. end; // if
  2012. end; // for
  2013. finally
  2014. if Count <> 0 then
  2015. FreeMem(PropList);
  2016. end;
  2017. if AnObject is TStrings then
  2018. begin
  2019. if ((AnObject as TStrings).Text <> '') and (TP_Retranslator <> nil) then
  2020. (TP_Retranslator as TTP_Retranslator).Remember(AnObject,
  2021. 'Text', (AnObject as TStrings).Text);
  2022. TranslateStrings(AnObject as TStrings, TextDomain);
  2023. end;
  2024. // Check for TCollection
  2025. if AnObject is TCollection then
  2026. begin
  2027. for i := 0 to (AnObject as TCollection).Count - 1 do
  2028. begin
  2029. // Only add the object if it's not totally ignored already
  2030. if not Assigned(currentcm) or not AnObject.InheritsFrom(
  2031. currentcm.HClass) then
  2032. TodoList.AddObject('', (AnObject as TCollection).Items[i]);
  2033. end;
  2034. end;
  2035. if AnObject is TComponent then
  2036. begin
  2037. for i := 0 to TComponent(AnObject).ComponentCount - 1 do
  2038. begin
  2039. comp := TComponent(AnObject).Components[i];
  2040. if (not TP_IgnoreList.Find(uppercase(comp.Name), j)) then
  2041. begin
  2042. // Only add the object if it's not totally ignored or translated already
  2043. if not Assigned(currentcm) or not
  2044. AnObject.InheritsFrom(currentcm.HClass) then
  2045. begin
  2046. compmarker := comp.FindComponent('GNUgettextMarker');
  2047. if not Assigned(compmarker) then
  2048. TodoList.AddObject(uppercase(comp.Name), comp);
  2049. end;
  2050. end;
  2051. end;
  2052. end;
  2053. end { if AnObject<>nil };
  2054. end { while todolist.count<>0 };
  2055. finally
  2056. FreeAndNil(todolist);
  2057. FreeAndNil(ObjectPropertyIgnoreList);
  2058. FreeAndNil(DoneList);
  2059. end;
  2060. FreeTP_ClassHandlingItems;
  2061. TP_IgnoreList.Clear;
  2062. TP_Retranslator := nil;
  2063. {$ifdef DXGETTEXTDEBUG}
  2064. DebugWriteln ('----------------------------------------------------------------------');
  2065. {$endif}
  2066. end;
  2067. procedure TGnuGettextInstance.UseLanguage(LanguageCode: LanguageString);
  2068. var
  2069. i, p: integer;
  2070. dom: TDomain;
  2071. l2: string;
  2072. begin
  2073. {$ifdef DXGETTEXTDEBUG}
  2074. DebugWriteln('UseLanguage('''+LanguageCode+'''); called');
  2075. {$endif}
  2076. if LanguageCode = '' then
  2077. begin
  2078. LanguageCode := GGGetEnvironmentVariable('LANG');
  2079. {$ifdef DXGETTEXTDEBUG}
  2080. DebugWriteln ('LANG env variable is '''+LanguageCode+'''.');
  2081. {$endif}
  2082. {$ifdef MSWINDOWS}
  2083. if LanguageCode = '' then
  2084. begin
  2085. LanguageCode := GetWindowsLanguage;
  2086. {$ifdef DXGETTEXTDEBUG}
  2087. DebugWriteln ('Found Windows language code to be '''+LanguageCode+'''.');
  2088. {$endif}
  2089. end;
  2090. {$endif}
  2091. p := pos('.', LanguageCode);
  2092. if p <> 0 then
  2093. LanguageCode := LeftStr(LanguageCode, p - 1);
  2094. {$ifdef DXGETTEXTDEBUG}
  2095. DebugWriteln ('Language code that will be set is '''+LanguageCode+'''.');
  2096. {$endif}
  2097. end;
  2098. curlang := LanguageCode;
  2099. for i := 0 to domainlist.Count - 1 do
  2100. begin
  2101. dom := domainlist.Objects[i] as TDomain;
  2102. dom.SetLanguageCode(curlang);
  2103. end;
  2104. l2 := lowercase(LeftStr(curlang, 2));
  2105. if (l2 = 'en') or (l2 = 'de') then
  2106. curGetPluralForm := GetPluralForm2EN
  2107. else
  2108. if (l2 = 'hu') or (l2 = 'ko') or (l2 = 'zh') or (l2 = 'ja') or (l2 = 'tr') then
  2109. curGetPluralForm := GetPluralForm1
  2110. else
  2111. if (l2 = 'fr') or (l2 = 'fa') or (lowercase(curlang) = 'pt_br') then
  2112. curGetPluralForm := GetPluralForm2FR
  2113. else
  2114. if (l2 = 'lv') then
  2115. curGetPluralForm := GetPluralForm3LV
  2116. else
  2117. if (l2 = 'ga') then
  2118. curGetPluralForm := GetPluralForm3GA
  2119. else
  2120. if (l2 = 'lt') then
  2121. curGetPluralForm := GetPluralForm3LT
  2122. else
  2123. if (l2 = 'ru') or (l2 = 'uk') or (l2 = 'hr') then
  2124. curGetPluralForm := GetPluralForm3RU
  2125. else
  2126. if (l2 = 'cs') or (l2 = 'sk') then
  2127. curGetPluralForm := GetPluralForm3SK
  2128. else
  2129. if (l2 = 'pl') then
  2130. curGetPluralForm := GetPluralForm3PL
  2131. else
  2132. if (l2 = 'sl') then
  2133. curGetPluralForm := GetPluralForm4SL
  2134. else
  2135. begin
  2136. curGetPluralForm := GetPluralForm2EN;
  2137. {$ifdef DXGETTEXTDEBUG}
  2138. DebugWriteln ('Plural form for the language was not found. English plurality system assumed.');
  2139. {$endif}
  2140. end;
  2141. WhenNewLanguage(curlang);
  2142. {$ifdef DXGETTEXTDEBUG}
  2143. DebugWriteln('');
  2144. {$endif}
  2145. end;
  2146. procedure TGnuGettextInstance.TranslateStrings(sl: TStrings;
  2147. const TextDomain: DomainString);
  2148. var
  2149. line: string;
  2150. i: integer;
  2151. s:TStringList;
  2152. slAsTStringList:TStringList;
  2153. begin
  2154. if sl.Count > 0 then
  2155. begin
  2156. sl.BeginUpdate;
  2157. try
  2158. s := TStringList.Create;
  2159. try
  2160. s.Assign(sl);
  2161. for i := 0 to s.Count - 1 do
  2162. begin
  2163. line := s.Strings[i];
  2164. if line <> '' then
  2165. s.Strings[i] := dgettext(TextDomain, line);
  2166. end;
  2167. sl.Assign(s);
  2168. finally
  2169. FreeAndNil(s);
  2170. end;
  2171. finally
  2172. sl.EndUpdate;
  2173. end;
  2174. end;
  2175. end;
  2176. function TGnuGettextInstance.GetTranslatorNameAndEmail: TranslatedUnicodeString;
  2177. begin
  2178. Result := GetTranslationProperty('LAST-TRANSLATOR');
  2179. end;
  2180. function TGnuGettextInstance.GetTranslationProperty(
  2181. const Propertyname: ComponentNameString): TranslatedUnicodeString;
  2182. begin
  2183. Result := getdomain(curmsgdomain, DefaultDomainDirectory,
  2184. CurLang).GetTranslationProperty(Propertyname);
  2185. end;
  2186. function TGnuGettextInstance.dngettext(const szDomain: DomainString;
  2187. const singular, plural: MsgIdString; Number: integer): TranslatedUnicodeString;
  2188. var
  2189. org: MsgIdString;
  2190. trans: TranslatedUnicodeString;
  2191. idx: integer;
  2192. p: integer;
  2193. begin
  2194. {$ifdef DXGETTEXTDEBUG}
  2195. DebugWriteln ('dngettext translation (domain '+szDomain+', number is '+IntTostr(Number)+') of '+singular+'/'+plural);
  2196. {$endif}
  2197. org := singular + #0 + plural;
  2198. trans := dgettext(szDomain, org);
  2199. if org = trans then
  2200. begin
  2201. {$ifdef DXGETTEXTDEBUG}
  2202. DebugWriteln ('Translation was equal to english version. English plural forms assumed.');
  2203. {$endif}
  2204. idx := GetPluralForm2EN(Number);
  2205. end
  2206. else
  2207. idx := curGetPluralForm(Number);
  2208. {$ifdef DXGETTEXTDEBUG}
  2209. DebugWriteln ('Index '+IntToStr(idx)+' will be used');
  2210. {$endif}
  2211. while True do
  2212. begin
  2213. p := pos(#0, trans);
  2214. if p = 0 then
  2215. begin
  2216. {$ifdef DXGETTEXTDEBUG}
  2217. DebugWriteln ('Last translation used: '+utf8encode(trans));
  2218. {$endif}
  2219. Result := trans;
  2220. exit;
  2221. end;
  2222. if idx = 0 then
  2223. begin
  2224. {$ifdef DXGETTEXTDEBUG}
  2225. DebugWriteln ('Translation found: '+utf8encode(trans));
  2226. {$endif}
  2227. Result := LeftStr(trans, p - 1);
  2228. exit;
  2229. end;
  2230. Delete(trans, 1, p);
  2231. Dec(idx);
  2232. end;
  2233. end;
  2234. function TGnuGettextInstance.dngettext_NoExtract(const szDomain: DomainString;
  2235. const singular, plural: MsgIdString; Number: integer): TranslatedUnicodeString;
  2236. begin
  2237. // This one is very useful for translating text in variables.
  2238. // This can sometimes be necessary, and by using this function,
  2239. // the source code scanner will not trigger warnings.
  2240. Result := dngettext(szDomain, singular, plural, Number);
  2241. end;
  2242. {$ifndef UNICODE}
  2243. function TGnuGettextInstance.ngettext(const singular, plural: ansistring;
  2244. Number: integer): TranslatedUnicodeString;
  2245. begin
  2246. Result := dngettext(curmsgdomain, singular, plural, Number);
  2247. end;
  2248. {$endif}
  2249. function TGnuGettextInstance.ngettext(const singular, plural: MsgIdString;
  2250. Number: integer): TranslatedUnicodeString;
  2251. begin
  2252. Result := dngettext(curmsgdomain, singular, plural, Number);
  2253. end;
  2254. function TGnuGettextInstance.ngettext_NoExtract(const singular, plural: MsgIdString;
  2255. Number: integer): TranslatedUnicodeString;
  2256. begin
  2257. // This one is very useful for translating text in variables.
  2258. // This can sometimes be necessary, and by using this function,
  2259. // the source code scanner will not trigger warnings.
  2260. Result := ngettext(singular, plural, Number);
  2261. end;
  2262. procedure TGnuGettextInstance.WhenNewDomain(const TextDomain: DomainString);
  2263. begin
  2264. // This is meant to be empty.
  2265. end;
  2266. procedure TGnuGettextInstance.WhenNewLanguage(const LanguageID: LanguageString);
  2267. begin
  2268. // This is meant to be empty.
  2269. end;
  2270. procedure TGnuGettextInstance.WhenNewDomainDirectory(const TextDomain: DomainString;
  2271. const Directory: FilenameString);
  2272. begin
  2273. // This is meant to be empty.
  2274. end;
  2275. procedure TGnuGettextInstance.GetListOfLanguages(const domain: DomainString;
  2276. list: TStrings);
  2277. begin
  2278. getdomain(Domain, DefaultDomainDirectory, CurLang).GetListOfLanguages(list);
  2279. end;
  2280. procedure TGnuGettextInstance.bindtextdomainToFile(const szDomain: DomainString;
  2281. const filename: FilenameString);
  2282. begin
  2283. {$ifdef DXGETTEXTDEBUG}
  2284. DebugWriteln ('Text domain "'+szDomain+'" is now bound to file named "'+filename+'"');
  2285. {$endif}
  2286. getdomain(szDomain, DefaultDomainDirectory, CurLang).SetFilename(filename);
  2287. end;
  2288. procedure TGnuGettextInstance.DebugLogPause(PauseEnabled: boolean);
  2289. begin
  2290. {$ifdef DXGETTEXTDEBUG}
  2291. DebugLogOutputPaused:=PauseEnabled;
  2292. {$endif}
  2293. end;
  2294. procedure TGnuGettextInstance.DebugLogToFile(const filename: FilenameString;
  2295. append: boolean = False);
  2296. {$ifdef DXGETTEXTDEBUG}
  2297. var
  2298. fs:TFileStream;
  2299. marker:ansistring;
  2300. {$endif}
  2301. begin
  2302. {$ifdef DXGETTEXTDEBUG}
  2303. // Creates the file if needed
  2304. if (not fileexists(filename)) or (not append) then
  2305. fileclose (filecreate (filename));
  2306. // Open file
  2307. fs:=TFileStream.Create (filename,fmOpenWrite or fmShareDenyWrite);
  2308. if append then
  2309. fs.Seek(0,soFromEnd);
  2310. // Write header if appending
  2311. if fs.Position<>0 then begin
  2312. marker:=sLineBreak+'==========================================================================='+sLineBreak;
  2313. fs.WriteBuffer(marker[1],length(marker));
  2314. end;
  2315. // Copy the memorystream contents to the file
  2316. DebugLog.Seek(0,soFromBeginning);
  2317. fs.CopyFrom(DebugLog,0);
  2318. // Make DebugLog point to the filestream
  2319. FreeAndNil (DebugLog);
  2320. DebugLog:=fs;
  2321. {$endif}
  2322. end;
  2323. {$ifdef DXGETTEXTDEBUG}
  2324. procedure TGnuGettextInstance.DebugWriteln(line: ansistring);
  2325. Var
  2326. Discard: Boolean;
  2327. begin
  2328. Assert (DebugLogCS<>nil);
  2329. Assert (DebugLog<>nil);
  2330. DebugLogCS.BeginWrite;
  2331. try
  2332. if DebugLogOutputPaused then
  2333. exit;
  2334. if Assigned (fOnDebugLine) then begin
  2335. Discard := True;
  2336. fOnDebugLine (Self, Line, Discard);
  2337. If Discard then Exit;
  2338. end;
  2339. line:=line+sLineBreak;
  2340. // Ensure that memory usage doesn't get too big.
  2341. if (DebugLog is TMemoryStream) and (DebugLog.Position>1000000) then begin
  2342. line:=sLineBreak+sLineBreak+sLineBreak+sLineBreak+sLineBreak+
  2343. 'Debug log halted because memory usage grew too much.'+sLineBreak+
  2344. 'Specify a filename to store the debug log in or disable debug loggin in gnugettext.pas.'+
  2345. sLineBreak+sLineBreak+sLineBreak+sLineBreak+sLineBreak;
  2346. DebugLogOutputPaused:=True;
  2347. end;
  2348. DebugLog.WriteBuffer(line[1],length(line));
  2349. finally
  2350. DebugLogCS.EndWrite;
  2351. end;
  2352. end;
  2353. {$endif}
  2354. function TGnuGettextInstance.Getdomain(const domain: DomainString;
  2355. const DefaultDomainDirectory: FilenameString; const CurLang: LanguageString): TDomain;
  2356. // Retrieves the TDomain object for the specified domain.
  2357. // Creates one, if none there, yet.
  2358. var
  2359. idx: integer;
  2360. begin
  2361. idx := domainlist.IndexOf(Domain);
  2362. if idx = -1 then
  2363. begin
  2364. Result := TDomain.Create;
  2365. {$ifdef DXGETTEXTDEBUG}
  2366. Result.DebugLogger:=DebugWriteln;
  2367. {$endif}
  2368. Result.Domain := Domain;
  2369. Result.Directory := DefaultDomainDirectory;
  2370. Result.SetLanguageCode(curlang);
  2371. domainlist.AddObject(Domain, Result);
  2372. end
  2373. else
  2374. begin
  2375. Result := domainlist.Objects[idx] as TDomain;
  2376. end;
  2377. end;
  2378. function TGnuGettextInstance.LoadResString(ResStringRec: PResStringRec): UnicodeString;
  2379. {$ifdef MSWINDOWS}
  2380. var
  2381. Len: integer;
  2382. {$IFDEF UNICODE}
  2383. Buffer: array [0..1023] of widechar;
  2384. {$else}
  2385. Buffer: array [0..1023] of ansichar;
  2386. {$endif}
  2387. {$endif}
  2388. {$ifdef LINUX }
  2389. const
  2390. ResStringTableLen = 16;
  2391. type
  2392. ResStringTable = array [0..ResStringTableLen-1] of LongWord;
  2393. var
  2394. Handle: TResourceHandle;
  2395. Tab: ^ResStringTable;
  2396. ResMod: HMODULE;
  2397. {$endif }
  2398. begin
  2399. if ResStringRec = nil then
  2400. exit;
  2401. if ResStringRec.Identifier >= 64 * 1024 then
  2402. begin
  2403. {$ifdef DXGETTEXTDEBUG}
  2404. DebugWriteln ('LoadResString was given an invalid ResStringRec.Identifier');
  2405. {$endif}
  2406. Result := 'ERROR';
  2407. exit;
  2408. end
  2409. else
  2410. begin
  2411. {$ifdef LINUX}
  2412. // This works with Unicode if the Linux has utf-8 character set
  2413. // Result:=System.LoadResString(ResStringRec);
  2414. ResMod:=FindResourceHInstance(ResStringRec^.Module^);
  2415. Handle:=FindResource(ResMod,
  2416. PAnsiChar(ResStringRec^.Identifier div ResStringTableLen), PAnsiChar(6)); // RT_STRING
  2417. Tab:=Pointer(LoadResource(ResMod, Handle));
  2418. if Tab=nil then
  2419. Result:=''
  2420. else
  2421. Result:=PWideChar(PAnsiChar(Tab)+Tab[ResStringRec^.Identifier mod ResStringTableLen]);
  2422. {$endif}
  2423. {$ifdef MSWINDOWS}
  2424. if not Win32PlatformIsUnicode then
  2425. begin
  2426. SetString(Result, Buffer,
  2427. LoadString(FindResourceHInstance(ResStringRec.Module^),
  2428. ResStringRec.Identifier, Buffer, SizeOf(Buffer)));
  2429. end
  2430. else
  2431. begin
  2432. Result := '';
  2433. Len := 0;
  2434. while Length(Result) <= Len + 1 do
  2435. begin
  2436. if Length(Result) = 0 then
  2437. SetLength(Result, 1024)
  2438. else
  2439. SetLength(Result, Length(Result) * 2);
  2440. Len := LoadStringW(FindResourceHInstance(ResStringRec.Module^),
  2441. ResStringRec.Identifier, PWideChar(Result), Length(Result));
  2442. end;
  2443. SetLength(Result, Len);
  2444. end;
  2445. {$endif}
  2446. end;
  2447. {$ifdef DXGETTEXTDEBUG}
  2448. DebugWriteln ('Loaded resourcestring: '+utf8encode(Result));
  2449. {$endif}
  2450. if CreatorThread <> GetCurrentThreadId then
  2451. begin
  2452. {$ifdef DXGETTEXTDEBUG}
  2453. DebugWriteln ('LoadResString was called from an invalid thread. Resourcestring was not translated.');
  2454. {$endif}
  2455. end
  2456. else
  2457. Result := ResourceStringGettext(Result);
  2458. end;
  2459. procedure TGnuGettextInstance.RetranslateComponent(AnObject: TComponent;
  2460. const TextDomain: DomainString);
  2461. var
  2462. comp: TGnuGettextComponentMarker;
  2463. begin
  2464. {$ifdef DXGETTEXTDEBUG}
  2465. DebugWriteln ('======================================================================');
  2466. DebugWriteln ('RetranslateComponent() was called for a component with name '+AnObject.Name+'.');
  2467. {$endif}
  2468. comp := AnObject.FindComponent('GNUgettextMarker') as TGnuGettextComponentMarker;
  2469. if comp = nil then
  2470. begin
  2471. {$ifdef DXGETTEXTDEBUG}
  2472. DebugWriteln ('Retranslate was called on an object that has not been translated before. An Exception is being raised.');
  2473. {$endif}
  2474. raise EGGProgrammingError.Create(
  2475. 'Retranslate was called on an object that has not been translated before. Please use TranslateComponent() before RetranslateComponent().');
  2476. end
  2477. else
  2478. begin
  2479. if comp.LastLanguage <> curlang then
  2480. begin
  2481. {$ifdef DXGETTEXTDEBUG}
  2482. DebugWriteln ('The retranslator is being executed.');
  2483. {$endif}
  2484. comp.Retranslator.Execute;
  2485. end
  2486. else
  2487. begin
  2488. {$ifdef DXGETTEXTDEBUG}
  2489. DebugWriteln ('The language has not changed. The retranslator is not executed.');
  2490. {$endif}
  2491. end;
  2492. end;
  2493. comp.LastLanguage := curlang;
  2494. {$ifdef DXGETTEXTDEBUG}
  2495. DebugWriteln ('======================================================================');
  2496. {$endif}
  2497. end;
  2498. procedure TGnuGettextInstance.TP_IgnoreClass(IgnClass: TClass);
  2499. var
  2500. cm: TClassMode;
  2501. i: integer;
  2502. begin
  2503. for i := 0 to TP_ClassHandling.Count - 1 do
  2504. begin
  2505. cm := TObject(TP_ClassHandling.Items[i]) as TClassMode;
  2506. if cm.HClass = IgnClass then
  2507. raise EGGProgrammingError.Create(
  2508. 'You cannot add a class to the ignore list that is already on that list: ' +
  2509. IgnClass.ClassName + '.');
  2510. if IgnClass.InheritsFrom(cm.HClass) then
  2511. begin
  2512. // This is the place to insert this class
  2513. cm := TClassMode.Create;
  2514. cm.HClass := IgnClass;
  2515. TP_ClassHandling.Insert(i, cm);
  2516. {$ifdef DXGETTEXTDEBUG}
  2517. DebugWriteln ('Locally, class '+IgnClass.ClassName+' is being ignored.');
  2518. {$endif}
  2519. exit;
  2520. end;
  2521. end;
  2522. cm := TClassMode.Create;
  2523. cm.HClass := IgnClass;
  2524. TP_ClassHandling.Add(cm);
  2525. {$ifdef DXGETTEXTDEBUG}
  2526. DebugWriteln ('Locally, class '+IgnClass.ClassName+' is being ignored.');
  2527. {$endif}
  2528. end;
  2529. procedure TGnuGettextInstance.TP_IgnoreClassProperty(IgnClass: TClass;
  2530. propertyname: ComponentNameString);
  2531. var
  2532. cm: TClassMode;
  2533. i: integer;
  2534. begin
  2535. propertyname := uppercase(propertyname);
  2536. for i := 0 to TP_ClassHandling.Count - 1 do
  2537. begin
  2538. cm := TObject(TP_ClassHandling.Items[i]) as TClassMode;
  2539. if cm.HClass = IgnClass then
  2540. begin
  2541. if Assigned(cm.SpecialHandler) then
  2542. raise EGGProgrammingError.Create(
  2543. 'You cannot ignore a class property for a class that has a handler set.');
  2544. cm.PropertiesToIgnore.Add(propertyname);
  2545. {$ifdef DXGETTEXTDEBUG}
  2546. DebugWriteln ('Globally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.');
  2547. {$endif}
  2548. exit;
  2549. end;
  2550. if IgnClass.InheritsFrom(cm.HClass) then
  2551. begin
  2552. // This is the place to insert this class
  2553. cm := TClassMode.Create;
  2554. cm.HClass := IgnClass;
  2555. cm.PropertiesToIgnore.Add(propertyname);
  2556. TP_ClassHandling.Insert(i, cm);
  2557. {$ifdef DXGETTEXTDEBUG}
  2558. DebugWriteln ('Locally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.');
  2559. {$endif}
  2560. exit;
  2561. end;
  2562. end;
  2563. cm := TClassMode.Create;
  2564. cm.HClass := IgnClass;
  2565. cm.PropertiesToIgnore.Add(propertyname);
  2566. TP_GlobalClassHandling.Add(cm);
  2567. {$ifdef DXGETTEXTDEBUG}
  2568. DebugWriteln ('Locally, the '+propertyname+' property of class '+IgnClass.ClassName+' is being ignored.');
  2569. {$endif}
  2570. end;
  2571. procedure TGnuGettextInstance.FreeTP_ClassHandlingItems;
  2572. begin
  2573. while TP_ClassHandling.Count <> 0 do
  2574. begin
  2575. TObject(TP_ClassHandling.Items[0]).Free;
  2576. TP_ClassHandling.Delete(0);
  2577. end;
  2578. end;
  2579. {$ifndef UNICODE}
  2580. function TGnuGettextInstance.ansi2wideDTCP(const s: ansistring): MsgIdString;
  2581. {$ifdef MSWindows}
  2582. var
  2583. len: integer;
  2584. {$endif}
  2585. begin
  2586. {$ifdef MSWindows}
  2587. if DesignTimeCodePage = CP_ACP then
  2588. begin
  2589. // No design-time codepage specified. Using runtime codepage instead.
  2590. {$endif}
  2591. Result := s;
  2592. {$ifdef MSWindows}
  2593. end
  2594. else
  2595. begin
  2596. len := length(s);
  2597. if len = 0 then
  2598. Result := ''
  2599. else
  2600. begin
  2601. SetLength(Result, len);
  2602. len := MultiByteToWideChar(DesignTimeCodePage, 0, pansichar(s),
  2603. len, pwidechar(Result), len);
  2604. if len = 0 then
  2605. raise EGGAnsi2WideConvError.Create(
  2606. 'Cannot convert string to widestring:' + sLineBreak + s);
  2607. SetLength(Result, len);
  2608. end;
  2609. end;
  2610. {$endif}
  2611. end;
  2612. {$endif}
  2613. {$ifndef UNICODE}
  2614. function TGnuGettextInstance.dngettext(const szDomain: DomainString;
  2615. const singular, plural: ansistring; Number: integer): TranslatedUnicodeString;
  2616. begin
  2617. Result := dngettext(szDomain, ansi2wideDTCP(singular), ansi2wideDTCP(plural), Number);
  2618. end;
  2619. {$endif}
  2620. { TClassMode }
  2621. constructor TClassMode.Create;
  2622. begin
  2623. PropertiesToIgnore := TStringList.Create;
  2624. PropertiesToIgnore.Sorted := True;
  2625. PropertiesToIgnore.Duplicates := dupError;
  2626. PropertiesToIgnore.CaseSensitive := False;
  2627. end;
  2628. destructor TClassMode.Destroy;
  2629. begin
  2630. FreeAndNil(PropertiesToIgnore);
  2631. inherited;
  2632. end;
  2633. { TFileLocator }
  2634. function TFileLocator.FindSignaturePos(const signature: RawByteString;
  2635. str: TFileStream): Int64;
  2636. // Finds the position of signature in the file.
  2637. const
  2638. bufsize=100000;
  2639. var
  2640. a:RawByteString;
  2641. b:RawByteString;
  2642. offset:integer;
  2643. rd,p:Integer;
  2644. begin
  2645. if signature='' then
  2646. begin
  2647. Result := 0;
  2648. Exit;
  2649. end;
  2650. offset:=0;
  2651. str.Seek(0, soFromBeginning);
  2652. SetLength (a, bufsize);
  2653. SetLength (b, bufsize);
  2654. str.Read(a[1],bufsize);
  2655. while true do begin
  2656. rd:=str.Read(b[1],bufsize);
  2657. p:=pos(signature,a+b);
  2658. if (p<>0) then begin // do not check p < bufsize+100 here!
  2659. Result:=offset+p-1;
  2660. exit;
  2661. end;
  2662. if rd<>bufsize then begin
  2663. // Prematurely ended without finding anything
  2664. Result:=0;
  2665. exit;
  2666. end;
  2667. a:=b;
  2668. offset:=offset+bufsize;
  2669. end;
  2670. Result:=0;
  2671. end;
  2672. procedure TFileLocator.Analyze;
  2673. var
  2674. s: ansistring;
  2675. i: integer;
  2676. offset: int64;
  2677. fs: TFileStream;
  2678. fi: TEmbeddedFileInfo;
  2679. filename: FilenameString;
  2680. filename8bit: ansistring;
  2681. begin
  2682. s := '6637DB2E-62E1-4A60-AC19-C23867046A89'#0#0#0#0#0#0#0#0;
  2683. s := MidStr(s, length(s) - 7, 8);
  2684. offset := 0;
  2685. for i := 8 downto 1 do
  2686. offset := offset shl 8 + Ord(s[i]);
  2687. if offset = 0 then
  2688. exit;
  2689. BaseDirectory := ExtractFilePath(ExecutableFilename);
  2690. try
  2691. fs := TFileStream.Create(ExecutableFilename, fmOpenRead or fmShareDenyNone);
  2692. try
  2693. while True do
  2694. begin
  2695. fs.Seek(offset, soFromBeginning);
  2696. offset := ReadInt64(fs);
  2697. if offset = 0 then
  2698. exit;
  2699. fi := TEmbeddedFileInfo.Create;
  2700. try
  2701. fi.Offset := ReadInt64(fs);
  2702. fi.Size := ReadInt64(fs);
  2703. SetLength(filename8bit, offset - fs.position);
  2704. fs.ReadBuffer(filename8bit[1], offset - fs.position);
  2705. filename := trim(string(filename8bit));
  2706. if PreferExternal and SysUtils.fileexists(basedirectory + filename) then
  2707. begin
  2708. // Disregard the internal version and use the external version instead
  2709. FreeAndNil(fi);
  2710. end
  2711. else
  2712. filelist.AddObject(filename, fi);
  2713. except
  2714. FreeAndNil(fi);
  2715. raise;
  2716. end;
  2717. end;
  2718. finally
  2719. FreeAndNil(fs);
  2720. end;
  2721. except
  2722. {$ifdef DXGETTEXTDEBUG}
  2723. raise;
  2724. {$endif}
  2725. end;
  2726. end;
  2727. constructor TFileLocator.Create;
  2728. begin
  2729. MoFilesCS := TMultiReadExclusiveWriteSynchronizer.Create;
  2730. MoFiles := TStringList.Create;
  2731. filelist := TStringList.Create;
  2732. {$ifdef LINUX}
  2733. filelist.Duplicates:=dupError;
  2734. filelist.CaseSensitive:=True;
  2735. {$endif}
  2736. MoFiles.Sorted := True;
  2737. MoFiles.Duplicates := dupError;
  2738. MoFiles.CaseSensitive := False;
  2739. {$ifdef MSWINDOWS}
  2740. filelist.Duplicates := dupError;
  2741. filelist.CaseSensitive := False;
  2742. {$endif}
  2743. filelist.Sorted := True;
  2744. end;
  2745. destructor TFileLocator.Destroy;
  2746. begin
  2747. while filelist.Count <> 0 do
  2748. begin
  2749. filelist.Objects[0].Free;
  2750. filelist.Delete(0);
  2751. end;
  2752. FreeAndNil(filelist);
  2753. FreeAndNil(MoFiles);
  2754. FreeAndNil(MoFilesCS);
  2755. inherited;
  2756. end;
  2757. function TFileLocator.FileExists(filename: FilenameString): boolean;
  2758. var
  2759. idx: integer;
  2760. begin
  2761. if LeftStr(filename, length(basedirectory)) = basedirectory then
  2762. begin
  2763. // Cut off basedirectory if the file is located beneath that base directory
  2764. filename := MidStr(filename, length(basedirectory) + 1, maxint);
  2765. end;
  2766. Result := filelist.Find(filename, idx);
  2767. end;
  2768. function TFileLocator.GetMoFile(filename: FilenameString; DebugLogger: TDebugLogger): TMoFile;
  2769. var
  2770. fi: TEmbeddedFileInfo;
  2771. idx: integer;
  2772. idxname: FilenameString;
  2773. Offset, Size: int64;
  2774. realfilename: FilenameString;
  2775. begin
  2776. // Find real filename
  2777. offset := 0;
  2778. size := 0;
  2779. realfilename := filename;
  2780. if LeftStr(filename, length(basedirectory)) = basedirectory then
  2781. begin
  2782. filename := MidStr(filename, length(basedirectory) + 1, maxint);
  2783. idx := filelist.IndexOf(filename);
  2784. if idx <> -1 then
  2785. begin
  2786. fi := filelist.Objects[idx] as TEmbeddedFileInfo;
  2787. realfilename := ExecutableFilename;
  2788. offset := fi.offset;
  2789. size := fi.size;
  2790. {$ifdef DXGETTEXTDEBUG}
  2791. DebugLogger ('Instead of '+filename+', using '+realfilename+' from offset '+IntTostr(offset)+', size '+IntToStr(size));
  2792. {$endif}
  2793. end;
  2794. end;
  2795. {$ifdef DXGETTEXTDEBUG}
  2796. DebugLogger ('Reading .mo data from file '''+filename+'''');
  2797. {$endif}
  2798. // Find TMoFile object
  2799. MoFilesCS.BeginWrite;
  2800. try
  2801. idxname := realfilename + ' //\\ ' + IntToStr(offset);
  2802. if MoFiles.Find(idxname, idx) then
  2803. begin
  2804. Result := MoFiles.Objects[idx] as TMoFile;
  2805. end
  2806. else
  2807. begin
  2808. Result:=TMoFile.Create (realfilename, Offset, Size, UseMemoryMappedFiles);
  2809. MoFiles.AddObject(idxname, Result);
  2810. end;
  2811. Inc(Result.Users);
  2812. finally
  2813. MoFilesCS.EndWrite;
  2814. end;
  2815. end;
  2816. function TFileLocator.ReadInt64(str: TStream): int64;
  2817. begin
  2818. Assert(sizeof(Result) = 8);
  2819. str.ReadBuffer(Result, 8);
  2820. end;
  2821. procedure TFileLocator.ReleaseMoFile(mofile: TMoFile);
  2822. var
  2823. i: integer;
  2824. begin
  2825. Assert(mofile <> nil);
  2826. MoFilesCS.BeginWrite;
  2827. try
  2828. Dec(mofile.Users);
  2829. if mofile.Users <= 0 then
  2830. begin
  2831. i := MoFiles.Count - 1;
  2832. while i >= 0 do
  2833. begin
  2834. if MoFiles.Objects[i] = mofile then
  2835. begin
  2836. MoFiles.Delete(i);
  2837. FreeAndNil(mofile);
  2838. break;
  2839. end;
  2840. Dec(i);
  2841. end;
  2842. end;
  2843. finally
  2844. MoFilesCS.EndWrite;
  2845. end;
  2846. end;
  2847. { TTP_Retranslator }
  2848. constructor TTP_Retranslator.Create;
  2849. begin
  2850. list := TList.Create;
  2851. end;
  2852. destructor TTP_Retranslator.Destroy;
  2853. var
  2854. i: integer;
  2855. begin
  2856. for i := 0 to list.Count - 1 do
  2857. TObject(list.Items[i]).Free;
  2858. FreeAndNil(list);
  2859. inherited;
  2860. end;
  2861. procedure TTP_Retranslator.Execute;
  2862. var
  2863. i: integer;
  2864. sl: TStrings;
  2865. item: TTP_RetranslatorItem;
  2866. newvalue: TranslatedUnicodeString;
  2867. comp: TGnuGettextComponentMarker;
  2868. ppi: PPropInfo;
  2869. begin
  2870. for i := 0 to list.Count - 1 do
  2871. begin
  2872. item := TObject(list.items[i]) as TTP_RetranslatorItem;
  2873. if item.obj is TComponent then
  2874. begin
  2875. comp := TComponent(item.obj).FindComponent('GNUgettextMarker') as
  2876. TGnuGettextComponentMarker;
  2877. if Assigned(comp) and (self <> comp.Retranslator) then
  2878. begin
  2879. comp.Retranslator.Execute;
  2880. Continue;
  2881. end;
  2882. end;
  2883. if item.obj is TStrings then
  2884. begin
  2885. // Since we don't know the order of items in sl, and don't have
  2886. // the original .Objects[] anywhere, we cannot anticipate anything
  2887. // about the current sl.Strings[] and sl.Objects[] values. We therefore
  2888. // have to discard both values. We can, however, set the original .Strings[]
  2889. // value into the list and retranslate that.
  2890. sl := TStringList.Create;
  2891. try
  2892. sl.Text := item.OldValue;
  2893. Instance.TranslateStrings(sl, textdomain);
  2894. (item.obj as TStrings).BeginUpdate;
  2895. try
  2896. (item.obj as TStrings).Text := sl.Text;
  2897. finally
  2898. (item.obj as TStrings).EndUpdate;
  2899. end;
  2900. finally
  2901. FreeAndNil(sl);
  2902. end;
  2903. end else begin
  2904. if (textdomain = '') or (textdomain = DefaultTextDomain) then
  2905. newValue := ComponentGettext(item.OldValue, instance)
  2906. else
  2907. newValue := instance.dgettext(textdomain,item.OldValue);
  2908. ppi:=GetPropInfo(item.obj, item.Propname);
  2909. if ppi<>nil then begin
  2910. SetWideStrProp(item.obj, ppi, newValue);
  2911. end else begin
  2912. {$ifdef DXGETTEXTDEBUG}
  2913. Instance.DebugWriteln ('ERROR: On retranslation, property disappeared: '+item.Propname+' for object of type '+item.obj.ClassName);
  2914. {$endif}
  2915. end;
  2916. end;
  2917. end;
  2918. end;
  2919. procedure TTP_Retranslator.Remember(obj: TObject; PropName: ComponentNameString;
  2920. OldValue: TranslatedUnicodeString);
  2921. var
  2922. item: TTP_RetranslatorItem;
  2923. begin
  2924. item := TTP_RetranslatorItem.Create;
  2925. item.obj := obj;
  2926. item.Propname := Propname;
  2927. item.OldValue := OldValue;
  2928. list.Add(item);
  2929. end;
  2930. { TGnuGettextComponentMarker }
  2931. destructor TGnuGettextComponentMarker.Destroy;
  2932. begin
  2933. FreeAndNil(Retranslator);
  2934. inherited;
  2935. end;
  2936. { THook }
  2937. constructor THook.Create(OldProcedure, NewProcedure: pointer;
  2938. FollowJump: boolean = False);
  2939. { Idea and original code from Igor Siticov }
  2940. { Modified by Jacques Garcia Vazquez and Lars Dybdahl }
  2941. begin
  2942. {$ifndef CPU386}
  2943. raise Exception.Create(
  2944. 'This procedure only works on Intel i386 compatible processors.');
  2945. {$endif}
  2946. oldproc := OldProcedure;
  2947. newproc := NewProcedure;
  2948. Reset(FollowJump);
  2949. end;
  2950. destructor THook.Destroy;
  2951. begin
  2952. Shutdown;
  2953. inherited;
  2954. end;
  2955. procedure THook.Disable;
  2956. begin
  2957. Assert(PatchPosition <> nil,
  2958. 'Patch position in THook was nil when Disable was called');
  2959. PatchPosition[0] := Original[0];
  2960. PatchPosition[1] := Original[1];
  2961. PatchPosition[2] := Original[2];
  2962. PatchPosition[3] := Original[3];
  2963. PatchPosition[4] := Original[4];
  2964. end;
  2965. procedure THook.Enable;
  2966. begin
  2967. Assert(PatchPosition <> nil, 'Patch position in THook was nil when Enable was called');
  2968. PatchPosition[0] := Patch[0];
  2969. PatchPosition[1] := Patch[1];
  2970. PatchPosition[2] := Patch[2];
  2971. PatchPosition[3] := Patch[3];
  2972. PatchPosition[4] := Patch[4];
  2973. end;
  2974. procedure THook.Reset(FollowJump: boolean);
  2975. var
  2976. offset: integer;
  2977. {$ifdef LINUX}
  2978. p:pointer;
  2979. pagesize:integer;
  2980. {$endif}
  2981. {$ifdef MSWindows}
  2982. ov: cardinal;
  2983. {$endif}
  2984. begin
  2985. if PatchPosition <> nil then
  2986. Shutdown;
  2987. patchPosition := OldProc;
  2988. if FollowJump and (word(OldProc^) = $25FF) then
  2989. begin
  2990. // This finds the correct procedure if a virtual jump has been inserted
  2991. // at the procedure address
  2992. /// Inc(integer(patchPosition), 2); // skip the jump
  2993. patchPosition := patchPosition + 2;
  2994. patchPosition := pansiChar(Pointer(pointer(patchPosition)^)^);
  2995. end;
  2996. offset := integer(NewProc) - integer(pointer(patchPosition)) - 5;
  2997. Patch[0] := ansichar($E9);
  2998. Patch[1] := ansichar(offset and 255);
  2999. Patch[2] := ansichar((offset shr 8) and 255);
  3000. Patch[3] := ansichar((offset shr 16) and 255);
  3001. Patch[4] := ansichar((offset shr 24) and 255);
  3002. Original[0] := PatchPosition[0];
  3003. Original[1] := PatchPosition[1];
  3004. Original[2] := PatchPosition[2];
  3005. Original[3] := PatchPosition[3];
  3006. Original[4] := PatchPosition[4];
  3007. {$ifdef MSWINDOWS}
  3008. if not VirtualProtect(Pointer(PatchPosition), 5, PAGE_EXECUTE_READWRITE, @ov) then
  3009. RaiseLastOSError;
  3010. {$endif}
  3011. {$ifdef LINUX}
  3012. pageSize:=sysconf (_SC_PAGE_SIZE);
  3013. p:=pointer(PatchPosition);
  3014. p:=pointer((integer(p) + PAGESIZE-1) and not (PAGESIZE-1) - pageSize);
  3015. if mprotect (p, pageSize, PROT_READ + PROT_WRITE + PROT_EXEC) <> 0 then
  3016. RaiseLastOSError;
  3017. {$endif}
  3018. end;
  3019. procedure THook.Shutdown;
  3020. begin
  3021. Disable;
  3022. PatchPosition := nil;
  3023. end;
  3024. procedure HookIntoResourceStrings(Enabled: boolean = True;
  3025. SupportPackages: boolean = False);
  3026. begin
  3027. HookLoadResString.Reset(SupportPackages);
  3028. HookLoadStr.Reset(SupportPackages);
  3029. HookFmtLoadStr.Reset(SupportPackages);
  3030. if Enabled then
  3031. begin
  3032. HookLoadResString.Enable;
  3033. HookLoadStr.Enable;
  3034. HookFmtLoadStr.Enable;
  3035. end;
  3036. end;
  3037. { TMoFile }
  3038. function TMoFile.autoswap32(i: cardinal): cardinal;
  3039. var
  3040. cnv1, cnv2: record
  3041. case integer of
  3042. 0: (arr: array[0..3] of byte);
  3043. 1: (int: cardinal);
  3044. end;
  3045. begin
  3046. if doswap then
  3047. begin
  3048. cnv1.int := i;
  3049. cnv2.arr[0] := cnv1.arr[3];
  3050. cnv2.arr[1] := cnv1.arr[2];
  3051. cnv2.arr[2] := cnv1.arr[1];
  3052. cnv2.arr[3] := cnv1.arr[0];
  3053. Result := cnv2.int;
  3054. end
  3055. else
  3056. Result := i;
  3057. end;
  3058. function TMoFile.CardinalInMem(baseptr: PansiChar; Offset: cardinal): cardinal;
  3059. var
  3060. pc: ^cardinal;
  3061. begin
  3062. Inc(baseptr, offset);
  3063. pc := Pointer(baseptr);
  3064. Result := pc^;
  3065. if doswap then
  3066. autoswap32(Result);
  3067. end;
  3068. constructor TMoFile.Create(const filename: FilenameString;
  3069. const Offset: int64; Size: int64;
  3070. const xUseMemoryMappedFiles: Boolean);
  3071. var
  3072. i:cardinal;
  3073. nn:integer;
  3074. mofile:TFileStream;
  3075. begin
  3076. if sizeof(i) <> 4 then
  3077. raise EGGProgrammingError.Create('TDomain in gnugettext is written for an architecture that has 32 bit integers.');
  3078. {$ifdef mswindows}
  3079. FUseMemoryMappedFiles := xUseMemoryMappedFiles;
  3080. {$endif}
  3081. {$ifdef linux}
  3082. FUseMemoryMappedFiles := False;
  3083. {$endif}
  3084. if FUseMemoryMappedFiles then
  3085. begin
  3086. // Map the mo file into memory and let the operating system decide how to cache
  3087. mo:=createfile (PChar(filename),GENERIC_READ,FILE_SHARE_READ,nil,OPEN_EXISTING,0,0);
  3088. if mo=INVALID_HANDLE_VALUE then
  3089. raise EGGIOError.Create ('Cannot open file '+filename);
  3090. momapping:=CreateFileMapping (mo, nil, PAGE_READONLY, 0, 0, nil);
  3091. if momapping=0 then
  3092. raise EGGIOError.Create ('Cannot create memory map on file '+filename);
  3093. momemoryHandle:=MapViewOfFile (momapping,FILE_MAP_READ,0,0,0);
  3094. if momemoryHandle=nil then begin
  3095. raise EGGIOError.Create ('Cannot map file '+filename+' into memory. Reason: '+GetLastWinError);
  3096. end;
  3097. momemory:=momemoryHandle+offset;
  3098. end
  3099. else
  3100. begin
  3101. // Read the whole file into memory
  3102. mofile:=TFileStream.Create (filename, fmOpenRead or fmShareDenyNone);
  3103. try
  3104. if (size = 0) then
  3105. size := mofile.Size;
  3106. Getmem (momemoryHandle, size);
  3107. momemory := momemoryHandle;
  3108. mofile.Seek(offset, soBeginning);
  3109. mofile.ReadBuffer(momemory^, size);
  3110. finally
  3111. FreeAndNil(mofile);
  3112. end;
  3113. end;
  3114. // Check the magic number
  3115. doswap := False;
  3116. i := CardinalInMem(momemory, 0);
  3117. if (i <> $950412DE) and (i <> $DE120495) then
  3118. raise EGGIOError.Create('This file is not a valid GNU gettext mo file: ' + filename);
  3119. doswap := (i = $DE120495);
  3120. // Find the positions in the file according to the file format spec
  3121. CardinalInMem(momemory, 4);
  3122. // Read the version number, but don't use it for anything.
  3123. N := CardinalInMem(momemory, 8); // Get string count
  3124. O := CardinalInMem(momemory, 12); // Get offset of original strings
  3125. T := CardinalInMem(momemory, 16); // Get offset of translated strings
  3126. // Calculate start conditions for a binary search
  3127. nn := N;
  3128. startindex := 1;
  3129. while nn <> 0 do
  3130. begin
  3131. nn := nn shr 1;
  3132. startindex := startindex shl 1;
  3133. end;
  3134. startindex := startindex shr 1;
  3135. startstep := startindex shr 1;
  3136. end;
  3137. destructor TMoFile.Destroy;
  3138. begin
  3139. if FUseMemoryMappedFiles then
  3140. begin
  3141. UnMapViewOfFile (momemoryHandle);
  3142. CloseHandle (momapping);
  3143. CloseHandle (mo);
  3144. end
  3145. else
  3146. begin
  3147. FreeMem (momemoryHandle);
  3148. end;
  3149. inherited;
  3150. end;
  3151. function TMoFile.gettext(const msgid: RawUtf8String; var found: boolean): RawUtf8String;
  3152. var
  3153. i, step: cardinal;
  3154. offset, pos: cardinal;
  3155. CompareResult: integer;
  3156. msgidptr, a, b: PAnsiChar;
  3157. abidx: integer;
  3158. size, msgidsize: integer;
  3159. begin
  3160. found := False;
  3161. msgidptr := PAnsiChar(msgid);
  3162. msgidsize := length(msgid);
  3163. // Do binary search
  3164. i := startindex;
  3165. step := startstep;
  3166. while True do
  3167. begin
  3168. // Get string for index i
  3169. pos := O + 8 * (i - 1);
  3170. offset := CardinalInMem(momemory, pos + 4);
  3171. size := CardinalInMem(momemory, pos);
  3172. a := msgidptr;
  3173. b := momemory + offset;
  3174. abidx := size;
  3175. if msgidsize < abidx then
  3176. abidx := msgidsize;
  3177. CompareResult := 0;
  3178. while abidx <> 0 do
  3179. begin
  3180. CompareResult := integer(byte(a^)) - integer(byte(b^));
  3181. if CompareResult <> 0 then
  3182. break;
  3183. Dec(abidx);
  3184. Inc(a);
  3185. Inc(b);
  3186. end;
  3187. if CompareResult = 0 then
  3188. CompareResult := msgidsize - size;
  3189. if CompareResult = 0 then
  3190. begin // msgid=s
  3191. // Found the msgid
  3192. pos := T + 8 * (i - 1);
  3193. offset := CardinalInMem(momemory, pos + 4);
  3194. size := CardinalInMem(momemory, pos);
  3195. SetString(Result, momemory + offset, size);
  3196. found := True;
  3197. break;
  3198. end;
  3199. if step = 0 then
  3200. begin
  3201. // Not found
  3202. Result := msgid;
  3203. break;
  3204. end;
  3205. if CompareResult < 0 then
  3206. begin // msgid<s
  3207. if i < 1 + step then
  3208. i := 1
  3209. else
  3210. i := i - step;
  3211. step := step shr 1;
  3212. end
  3213. else
  3214. begin // msgid>s
  3215. i := i + step;
  3216. if i > N then
  3217. i := N;
  3218. step := step shr 1;
  3219. end;
  3220. end;
  3221. end;
  3222. var
  3223. param0: string;
  3224. initialization
  3225. {$ifdef DXGETTEXTDEBUG}
  3226. {$ifdef MSWINDOWS}
  3227. MessageBox (0,'gnugettext.pas debugging is enabled. Turn it off before releasing this piece of software.','Information',MB_OK);
  3228. {$endif}
  3229. {$ifdef LINUX}
  3230. writeln (stderr,'gnugettext.pas debugging is enabled. Turn it off before releasing this piece of software.');
  3231. {$endif}
  3232. {$endif}
  3233. {$ifdef FPC}
  3234. {$ifdef LINUX}
  3235. SetLocale(LC_ALL, '');
  3236. SetCWidestringManager;
  3237. {$endif LINUX}
  3238. {$endif FPC}
  3239. // Get DLL/shared object filename
  3240. SetLength(ExecutableFilename, 300); // MAX_PATH ?
  3241. {$ifdef MSWINDOWS}
  3242. SetLength(ExecutableFilename, GetModuleFileName(HInstance,
  3243. PChar(ExecutableFilename), Length(ExecutableFilename)));
  3244. {$endif}
  3245. {$ifdef LINUX}
  3246. if ModuleIsLib or ModuleIsPackage then
  3247. begin
  3248. // This line has not been tested on Linux, yet, but should work.
  3249. SetLength(ExecutableFilename, GetModuleFileName(0, PChar(ExecutableFilename),
  3250. Length(ExecutableFilename)));
  3251. end else
  3252. ExecutableFilename:=Paramstr(0);
  3253. {$endif}
  3254. FileLocator:=TFileLocator.Create;
  3255. FileLocator.Analyze;
  3256. ResourceStringDomainList := TStringList.Create;
  3257. ResourceStringDomainList.Add(DefaultTextDomain);
  3258. ResourceStringDomainListCS:=TMultiReadExclusiveWriteSynchronizer.Create;
  3259. ComponentDomainList:=TStringList.Create;
  3260. ComponentDomainList.Add(DefaultTextDomain);
  3261. ComponentDomainListCS:=TMultiReadExclusiveWriteSynchronizer.Create;
  3262. DefaultInstance:=TGnuGettextInstance.Create;
  3263. {$ifdef MSWINDOWS}
  3264. Win32PlatformIsUnicode := (Win32Platform = VER_PLATFORM_WIN32_NT);
  3265. {$endif}
  3266. // replace Borlands LoadResString with gettext enabled version:
  3267. {$ifdef UNICODE}
  3268. HookLoadResString:=THook.Create (@system.LoadResString, @LoadResStringW);
  3269. {$else}
  3270. HookLoadResString := THook.Create(@system.LoadResString, @LoadResStringA);
  3271. {$endif}
  3272. HookLoadStr := THook.Create(@SysUtils.LoadStr, @SysUtilsLoadStr);
  3273. HookFmtLoadStr := THook.Create(@SysUtils.FmtLoadStr, @SysUtilsFmtLoadStr);
  3274. param0 := lowercase(extractfilename(ParamStr(0)));
  3275. if (param0 <> 'delphi32.exe') and (param0 <> 'kylix') and (param0 <> 'bds.exe') then
  3276. HookIntoResourceStrings(AutoCreateHooks, False);
  3277. param0 := '';
  3278. finalization
  3279. FreeAndNil (DefaultInstance);
  3280. FreeAndNil (ResourceStringDomainListCS);
  3281. FreeAndNil (ResourceStringDomainList);
  3282. FreeAndNil (ComponentDomainListCS);
  3283. FreeAndNil (ComponentDomainList);
  3284. FreeAndNil (HookFmtLoadStr);
  3285. FreeAndNil (HookLoadStr);
  3286. FreeAndNil (HookLoadResString);
  3287. FreeAndNil (FileLocator);
  3288. end.