Edgar J. De Cleene uploaded a new version of GetText to project The Trunk:
http://source.squeak.org/trunk/GetText-Richo.14.mcz ==================== Summary ==================== Name: GetText-Richo.14 Author: Richo Time: 18 April 2011, 1:35:48 pm UUID: fe80068b-c439-cb4e-b5bb-a659673f623b Ancestors: GetText-Richo.13 * Exporting a GetTextTranslator was giving a DNU because GetTextExporter>>translationFor:in: was asking "translator translations". * TextDomainManager class>>allKnownDomains was ignoring the special domain "Etoys-Tiles" ==================== Snapshot ==================== SystemOrganization addCategory: #'GetText-Editor'! SystemOrganization addCategory: #'GetText-Localization'! Object subclass: #GetTextExporter instanceVariableNames: 'stream' classVariableNames: '' poolDictionaries: '' category: 'GetText-Editor'! !GetTextExporter commentStamp: '<historical>' prior: 0! Export translations to gettext format divided into categories. "Export gettext template files" GetTextExporter new exportTemplate. "Export translation files for current locale" GetTextExporter new exportTranslator: (InternalTranslator newLocaleID: LocaleID current). "Export all gettext template and po files." GetTextExporter exportAll. ! ----- Method: GetTextExporter class>>coverageStatus (in category 'utilities') ----- coverageStatus "self coverageStatus" | keys diff | keys := self keys. diff := InternalTranslator allKnownPhrases keys difference: keys. Transcript cr; show: 'Detected keywords by GetTextExporter2: ' , keys size printString. Transcript cr; show: 'All known phrases in InternalTranslator: ' , InternalTranslator allKnownPhrases size printString. Transcript cr; show: 'Coverage: ' , (keys size / InternalTranslator allKnownPhrases size * 100.0) printString , '%'. diff inspect! ----- Method: GetTextExporter class>>exportAll (in category 'utilities') ----- exportAll "GetTextExporter2 exportAll" self new exportTemplate. InternalTranslator availableLanguageLocaleIDs do: [:each | self new exportTranslator: each translator]! ----- Method: GetTextExporter class>>exportTemplate (in category 'utilities') ----- exportTemplate "GetTextExporter2 exportTemplate" self new exportTemplate.! ----- Method: GetTextExporter class>>keys (in category 'utilities') ----- keys | categories | categories := Dictionary new. self new appendTranslations: categories. ^ categories values inject: Set new into: [:set :next | set addAll: next keys; yourself]! ----- Method: GetTextExporter class>>listAllHelp (in category 'utilities') ----- listAllHelp "self listAllHelp" | spec specs oCatalog flap flapSelectors allKeys oCatalogHelp flapHelp | oCatalog := Dictionary new. Morph withAllSubclasses do: [:aClass | (aClass class includesSelector: #descriptionForPartsBin) ifTrue: [spec := aClass descriptionForPartsBin. oCatalog at: spec formalName put: spec documentation]]. Morph withAllSubclasses do: [:aClass | (aClass class includesSelector: #supplementaryPartsDescriptions) ifTrue: [specs := aClass supplementaryPartsDescriptions. specs do: [:each | oCatalog at: each formalName put: each documentation]]]. flap := Dictionary new. flapSelectors := #(#defaultsQuadsDefiningPlugInSuppliesFlap #defaultsQuadsDefiningStackToolsFlap #defaultsQuadsDefiningSuppliesFlap #defaultsQuadsDefiningToolsFlap #defaultsQuadsDefiningWidgetsFlap #defaultsQuadsDefiningScriptingFlap ). flapSelectors do: [:selector | specs := Flaps perform: selector. specs do: [:each | flap at: each third put: each fourth]]. allKeys := oCatalog keys intersection: flap keys. allKeys asArray sort do: [:each | oCatalogHelp := oCatalog at: each ifAbsent: ['']. flapHelp := flap at: each ifAbsent: ['']. oCatalogHelp = flapHelp ifFalse: [Transcript cr; show: 'Name: ' , each. Transcript cr; show: 'O: ' , oCatalogHelp. Transcript cr; show: 'F: ' , flapHelp. Transcript cr. ]]! ----- Method: GetTextExporter class>>verifyExport (in category 'utilities') ----- verifyExport "Same as #verifyMsgID: but it writes / reads .po files actually" "GetTextExporter2 verifyExport" "InternalTranslator removeLocaleID: (LocaleID isoString: 'test-US')" | src dst localeID | localeID := LocaleID isoString: 'test-US'. self verifyMsgID: localeID. src := localeID translator. self new exportTranslator: src. InternalTranslator removeLocaleID: localeID. dst := localeID translator. GetTextImporter import: dst allDirectory: FileDirectory default! ----- Method: GetTextExporter class>>verifyMsgID: (in category 'utilities') ----- verifyMsgID: localeID "GetTextExporter2 verifyMsgID: (LocaleID isoString: 'test-US')" "InternalTranslator removeLocaleID: (LocaleID isoString: 'test-US')" "Test gettext keyword extract function without file I/O. A language named <langName> will be made. And all possible translated words are shown with extra X charactor like 'XwordX' in the language." | src | InternalTranslator removeLocaleID: localeID. src := localeID translator. self keys do: [:key | src generics at: key put: 'X' , key , 'X']! ----- Method: GetTextExporter>>appendStringReceivers:into: (in category 'private') ----- appendStringReceivers: aSymbol into: domains | literals references domainName methodReference keywords found | found := TranslatedReceiverFinder new stringReceiversWithContext: aSymbol. found do: [ :assoc | methodReference := assoc key. keywords := assoc value. domainName := (PackageOrganizer default packageOfMethod: methodReference ifNone: [nil]). domainName := domainName isNil ifTrue: [TextDomainManager defaultDomain] ifFalse: [domainName name]. literals _ domains at: domainName ifAbsentPut: [Dictionary new]. keywords do: [ :literal | references _ literals at: literal ifAbsentPut: [OrderedCollection new]. references add: methodReference. ]. ]. ! ----- Method: GetTextExporter>>appendTranslations: (in category 'exporting') ----- appendTranslations: domains self appendStringReceivers: #translated into: domains. self appendStringReceivers: #translatedNoop into: domains. self appendVocabularies: domains. ! ----- Method: GetTextExporter>>appendVocabularies: (in category 'private') ----- appendVocabularies: domains | literalsForDomain references domainName methodReference | EToyVocabulary allPhrasesWithContextToTranslate do: [ :r | methodReference := (MethodReference new setStandardClass: (r second) methodSymbol: (r third)). "domainName _ self getTextDomainForPackage: (PackageOrganizer default packageOfMethod: methodReference)". domainName := 'Etoys-Tiles'. literalsForDomain _ domains at: domainName ifAbsentPut: [Dictionary new]. r fourth do: [ :literal | references _ literalsForDomain at: literal ifAbsentPut: [OrderedCollection new]. references add: methodReference. ]. ]. ! ----- Method: GetTextExporter>>createExtraInformation (in category 'private') ----- createExtraInformation | extras | extras := OrderedCollection new. #( 'Language name as you''d like it to appear in the Languages menu' 'Language-Name' 'Directionality of language' 'Language-Direction' ) pairsDo: [:first :second | extras add: (Array with: '' with: first with: second). ]. ^ extras! ----- Method: GetTextExporter>>createHeaders (in category 'private') ----- createHeaders | headers | headers := OrderedCollection new. headers add: 'Project-Id-Version' -> 'eToys'. headers add: 'POT-Creation-Date' -> self currentDateAndTime. headers add: 'PO-Revision-Date' -> self currentDateAndTime. headers add: 'Last-Translator' -> ''. headers add: 'Language-Team' -> ''. headers add: 'MIME-Version' -> '1.0'. headers add: 'Content-Type' -> ('text/plain; charset=', stream converter class encodingNames first). headers add: 'Content-Transfer-Encoding' -> '8bit'. headers add: 'X-Etoys-SystemVersion' -> (SystemVersion current asString). ^ headers! ----- Method: GetTextExporter>>currentDateAndTime (in category 'private') ----- currentDateAndTime ^ String streamContents: [:aStream | aStream nextPutAll: Date today yyyymmdd; space. Time now print24: true showSeconds: false on: aStream. aStream nextPutAll: '-0000']! ----- Method: GetTextExporter>>dirNameCategory:translator: (in category 'exporting') ----- dirNameCategory: category translator: translator "Answer a file name for the category. Make one if it is not exist yet. Make template file name if translator is nil" "self new dirNameCategory: 'Morphic-Scripting Support' translator: NaturalLanguageTranslator current" "self new dirNameCategory: 'Morphic-Scripting Support' translator: nil" | safeCategory fileName dirName pathName | safeCategory := category copyReplaceAll: ' ' with: '_'. fileName := translator ifNil: [safeCategory , '.pot'] ifNotNil: [translator localeID posixName , '.po']. dirName := (safeCategory findTokens: '-') inject: 'po' into: [:aString :next | aString , FileDirectory slash , next]. pathName := dirName , FileDirectory slash , fileName. (FileDirectory default directoryNamed: dirName) assureExistence. ^ pathName! ----- Method: GetTextExporter>>dirNameDomain:translator: (in category 'exporting') ----- dirNameDomain: domain translator: translator "Answer a file name for the domain. Make one if it is not exist yet. Make template file name if translator is nil" "self new dirNameDomain: 'etoys' translator: NaturalLanguageTranslator current" "self new dirNameDomain: 'etoys' translator: nil" | fileName dirName pathName | "safeCategory := category copyReplaceAll: ' ' with: '_'." fileName := domain, (translator ifNil: ['.pot'] ifNotNil: ['.po']). dirName := 'po', FileDirectory slash, (translator ifNil: ['templates'] ifNotNil: [translator localeID posixName]). pathName := dirName , FileDirectory slash , fileName. (FileDirectory default directoryNamed: dirName) assureExistence. ^ pathName! ----- Method: GetTextExporter>>export:translator:domain: (in category 'private') ----- export: literals translator: translator domain: domainName | fileName | "Export a gettext file in a category. literals is a dictionary of keyword -> #(MethodReference...) in the textDomain." fileName := self dirNameDomain: domainName translator: translator. [stream := FileStream forceNewFileNamed: fileName. stream lineEndConvention: #lf. stream converter: UTF8TextConverter new. self exportHeader: domainName. domainName = TextDomainManager defaultDomain ifTrue: [self exportInformation: self createExtraInformation]. self exportBody: literals translator: translator] ensure: [stream close]! ----- Method: GetTextExporter>>exportBody:translator: (in category 'file out') ----- exportBody: literals translator: translator "Export a gettext file body. literals is a dictionary of keyword -> #(MethodReference...) in the textDomain." "Build {sortKey. comment. msgid } to optimize sorting (getting category is too slow). If there are two or more methods for a mgsid, only first method (alphabetical) is used for sorting." | sorted msgid sortedMethods category sortKey comment triplets commentUnderLined | triplets := literals associations collect: [:assoc | msgid := assoc key. sortedMethods := assoc value asArray sort. category := (Smalltalk at: sortedMethods first classSymbol) category asString. sortKey := category , ',' , sortedMethods first printString , ',' , msgid. comment := (sortedMethods collect: [:each | each actualClass asString , '>>' , each methodSymbol asString]) inject: category into: [:result :methodName | result , ',' , methodName]. "Replace white spaces to _ because gettext tool might replace a space to a new line some times, and it makes difficult to take a diff." commentUnderLined := comment copyReplaceAll: ' ' with: '_'. Array with: sortKey with: commentUnderLined with: msgid]. "Sort and output the words" sorted := triplets sort: [:a :b | a first <= b first]. sorted do: [:triplet | comment := triplet second. msgid := triplet third. self exportRecordHeader: comment. self exportPhrase: msgid translation: (self translationFor: msgid in: translator)]! ----- Method: GetTextExporter>>exportHeader: (in category 'private') ----- exportHeader: domainName | headers | self exportTag: 'msgid' msg: ''. self exportTag: 'msgstr' msg: ''. headers := self createHeaders. headers add: 'X-Etoys-Domain' -> domainName. headers do: [:each | self exportHeaderLineKey: each key value: each value]. stream cr; cr! ----- Method: GetTextExporter>>exportHeaderLineKey:value: (in category 'private') ----- exportHeaderLineKey: keyString value: valueString stream nextPut: $"; nextPutAll: keyString; nextPut: $:; space; nextPutAll: valueString; nextPutAll: '\n'; nextPut: $"; cr.! ----- Method: GetTextExporter>>exportInformation: (in category 'private') ----- exportInformation: anOrderedCollection anOrderedCollection do: [:each | self exportRecordHeader: each second. self exportPhrase: each third translation: '']. stream cr.! ----- Method: GetTextExporter>>exportPhrase:translation: (in category 'private') ----- exportPhrase: phraseString translation: translationString | normalizedTrans tmp transStartsWithCR transEndsWithCR| phraseString isEmpty ifTrue: [^ self]. self exportTag: 'msgid' msg: phraseString. translationString size = 0 ifTrue: [ normalizedTrans _ '' ] ifFalse: [ transEndsWithCR _ translationString last = (Character cr). phraseString last = (Character cr) ifTrue: [ transEndsWithCR ifTrue: [ normalizedTrans _ translationString ] ifFalse: [ normalizedTrans _ translationString , String cr ] ] ifFalse: [ transEndsWithCR ifTrue: [ normalizedTrans _ translationString allButLast ] ifFalse: [ normalizedTrans _ translationString ] ]. transStartsWithCR _ normalizedTrans first = (Character cr). phraseString first = (Character cr) ifTrue: [ transStartsWithCR ifFalse: [ tmp _ (Character cr asString) , normalizedTrans. normalizedTrans _ tmp. ] ] ifFalse: [ transStartsWithCR ifTrue: [ normalizedTrans _ normalizedTrans allButFirst ] ] ]. self exportTag: 'msgstr' msg: normalizedTrans. stream cr! ----- Method: GetTextExporter>>exportRecordHeader: (in category 'private') ----- exportRecordHeader: context stream nextPutAll: '#: '; nextPutAll: context; cr.! ----- Method: GetTextExporter>>exportTag:msg: (in category 'private') ----- exportTag: tag msg: aString | pos end line | (aString indexOf: Character cr) = 0 ifTrue: [self exportTag: tag singleLine: aString] ifFalse: [self exportTag: tag singleLine: ''. pos := 1. end := 0. [end < aString size] whileTrue: [end := aString indexOf: Character cr startingAt: pos. end = 0 ifTrue: [end := aString size]. line := aString copyFrom: pos to: end. stream nextPut: $"; nextPutAll: (self formatString: line); nextPut: $"; cr. pos := end + 1]]! ----- Method: GetTextExporter>>exportTag:singleLine: (in category 'private') ----- exportTag: tag singleLine: aString stream nextPutAll: tag. stream space. stream nextPut: $". stream nextPutAll: (self formatString: aString). stream nextPut: $". stream cr! ----- Method: GetTextExporter>>exportTemplate (in category 'exporting') ----- exportTemplate "GetTextExporter2 new exportTemplate" self exportTranslator: nil! ----- Method: GetTextExporter>>exportTranslator: (in category 'exporting') ----- exportTranslator: translator "Export translation files. the file extention is 'po', or 'pot' if translator is nil " "GetTextExporter2 new exportTranslator: NaturalLanguageTranslator current " | domains | domains := Dictionary new. self appendTranslations: domains. domains keysAndValuesDo: [:domainName :value | self export: value translator: translator domain: domainName]! ----- Method: GetTextExporter>>formatReplacements (in category 'private') ----- formatReplacements | replacements | replacements := OrderedCollection new. replacements add: '\' -> '\\'. replacements add: String cr -> '\n'. replacements add: String tab -> '\t'. replacements add: '"' -> '\"'. ^ replacements! ----- Method: GetTextExporter>>formatString: (in category 'private') ----- formatString: aString | result | result := aString. self formatReplacements do: [:each | result := result copyReplaceAll: each key with: each value]. ^ result! ----- Method: GetTextExporter>>getTextDomainForPackage: (in category 'as yet unclassified') ----- getTextDomainForPackage: aPackageInfo ^TextDomainManager domainForPackage: aPackageInfo! ----- Method: GetTextExporter>>stream (in category 'accessing') ----- stream ^ stream! ----- Method: GetTextExporter>>stream: (in category 'accessing') ----- stream: aStream stream := aStream! ----- Method: GetTextExporter>>translationFor:in: (in category 'private') ----- translationFor: aKey in: translator | translation | translator ifNil: [^ '']. TextDomainManager allKnownDomains do: [:domain | translation := translator translate: aKey inDomain: domain. aKey = translation ifFalse: [^translation] ]. ^ aKey! Object subclass: #GetTextInterchange instanceVariableNames: 'language stream' classVariableNames: '' poolDictionaries: '' category: 'GetText-Editor'! GetTextInterchange subclass: #GetTextImporter instanceVariableNames: 'msgId msgStr state' classVariableNames: '' poolDictionaries: '' category: 'GetText-Editor'! !GetTextImporter commentStamp: 'tak 10/24/2007 11:23' prior: 0! GetTextImporter load gettext po file into a InternalTranslator. GetTextImporter new importID: (LocaleID isoString: 'lang-name') fileNamed: 'lang.po' ! ----- Method: GetTextImporter class>>cleanUpUnnecessaryPhrases (in category 'utilities') ----- cleanUpUnnecessaryPhrases | keys refuse replaceBlock reader writer char result | "GetTextImporter cleanUpUnnecessaryPhrases" "" "Collect wrong phrases" keys := InternalTranslator allKnownPhrases copy keys. refuse := Set new. "replaceBlock value: 'te\\nst'." replaceBlock := [:aString | reader := aString readStream. writer := '' writeStream. [reader atEnd] whileFalse: [char := reader next. (char = $\ and: [reader peek = $\]) ifFalse: [writer nextPut: char]]. writer contents]. keys do: [:each | result := replaceBlock value: each. (result ~= each and: [keys includes: result]) ifTrue: [refuse add: each]. result := GetTextImporter new formatString: each. (result ~= each and: [keys includes: result]) ifTrue: [refuse add: each]]. "" "Remove from translated" InternalTranslator cachedTranslations do: [:each | refuse do: [:key | each translations removeKey: key ifAbsent: []]]. "" "Remove from untranslated" refuse do: [:key | InternalTranslator allKnownPhrases removeKey: key ifAbsent: []]! ----- Method: GetTextImporter class>>import:allDirectory: (in category 'utilities') ----- import: translator allDirectory: aFileDirectory "self import: NaturalLanguageTranslator current allDirectory: FileDirectory default" | fileName targetFile | fileName := translator localeID posixName , '.po'. (FileDirectory default directoryNamed: 'po') withAllSubdirectoriesCollect: [:each | (each fileExists: fileName) ifTrue: [targetFile := each fullNameFor: fileName. self new import: translator fileNamed: targetFile]]! ----- Method: GetTextImporter class>>importAll (in category 'utilities') ----- importAll "GetTextImporter importAll" "Import all gettext files on po/. Only registered language is imported" InternalTranslator cachedTranslations do: [:translator | self import: translator allDirectory: FileDirectory default]! ----- Method: GetTextImporter>>appendId: (in category 'parsing') ----- appendId: aString msgId := msgId , aString! ----- Method: GetTextImporter>>appendStr: (in category 'parsing') ----- appendStr: aString msgStr := msgStr , aString! ----- Method: GetTextImporter>>formatString: (in category 'private') ----- formatString: aString " self assert: (GetTextImporter new formatString: 'test') = 'test'. self assert: (GetTextImporter new formatString: 'te\nst') = ('te', String cr, 'st'). self assert: (GetTextImporter new formatString: 'te\\nst') = ('te\nst'). self assert: (GetTextImporter new formatString: 'te\\st') = ('te\st'). self assert: (GetTextImporter new formatString: 'te\st') = ('te\st'). " | reader writer char | reader := aString readStream. writer := '' writeStream. [reader atEnd] whileFalse: [char := reader next. (char = $\ and: [reader atEnd not]) ifTrue: [char := reader next. char caseOf: { [$n] -> [writer nextPut: Character cr]. [$t] -> [writer nextPut: Character tab]. [$"] -> [writer nextPut: $"]. [$\] -> [writer nextPut: $\]} otherwise: [writer nextPutAll: {$\. char}]] ifFalse: [writer nextPut: char]]. ^ writer contents! ----- Method: GetTextImporter>>import: (in category 'importing') ----- import: aLanguage ^ self import: aLanguage fileNamed: aLanguage localeID posixName , '.po'! ----- Method: GetTextImporter>>import:fileNamed: (in category 'importing') ----- import: aLanguage fileNamed: fileName self importID: aLanguage localeID fileNamed: fileName! ----- Method: GetTextImporter>>importID:fileNamed: (in category 'importing') ----- importID: localeID fileNamed: fileName | currentPlatform | language := InternalTranslator newLocaleID: localeID. currentPlatform := Locale currentPlatform. [Locale currentPlatform: (Locale localeID: localeID). [stream := FileStream readOnlyFileNamed: fileName. stream text. self parse] ensure: [stream notNil ifTrue: [stream close]]] ensure: [Locale currentPlatform: currentPlatform]. NaturalLanguageTranslator privateStartUp "Actually it is not private no more...".! ----- Method: GetTextImporter>>initialize (in category 'initialize-release') ----- initialize msgId := ''. msgStr := ''. state := nil! ----- Method: GetTextImporter>>parse (in category 'parsing') ----- parse | size | size := (stream isKindOf: FileStream) ifTrue: [stream size] ifFalse: [1]. ProgressInitiationException display: 'Importing phrases from a gettext file.' during: [:bar | [stream atEnd] whileFalse: [| line | line := stream upTo: Character linefeed. self parseLine: ((line endsWith: String cr) ifTrue: [line allButLast] ifFalse: [line]). bar value: stream position / size]]. self storeTranslation! ----- Method: GetTextImporter>>parseLine: (in category 'parsing') ----- parseLine: lineString (lineString beginsWith: '"Content-Type:') ifTrue: [self setContentType: lineString. ^ self]. (lineString beginsWith: '#') ifTrue: ["do nothing" ^ self]. lineString = '' ifTrue: [^ self storeTranslation]. (lineString beginsWith: 'msgid') ifTrue: [state := #appendId:. self parseMsg: lineString. ^ self]. (lineString beginsWith: 'msgstr') ifTrue: [state := #appendStr:. self parseMsg: lineString. ^ self]. self parseMsg: lineString! ----- Method: GetTextImporter>>parseMsg: (in category 'parsing') ----- parseMsg: lineString | begin end msg | begin := lineString indexOf: $". end := lineString lastIndexOf: $". msg := begin + 1 <= (end - 1) ifTrue: [lineString copyFrom: begin + 1 to: end - 1] ifFalse: ['']. state ifNotNil: [self perform: state with: msg]. ^ msg! ----- Method: GetTextImporter>>setContentType: (in category 'parsing') ----- setContentType: lineString "self new setContentType: 'Content-Type: text/plain; charset=utf-8'" | reader charSet | reader := lineString readStream. reader upTo: $=. charSet := reader upTo: $\. stream converter: (TextConverter newForEncoding: charSet)! ----- Method: GetTextImporter>>storeTranslation (in category 'parsing') ----- storeTranslation | key | key := self formatString: msgId. msgId isEmpty ifFalse: [InternalTranslator registerPhrase: key. msgStr isEmpty ifFalse: [language rawPhrase: key translation: (self formatString: msgStr)]]. self initialize! ----- Method: GetTextInterchange>>defaultFileName (in category 'private') ----- defaultFileName ^ language localeID posixName , '.po'! ----- Method: GetTextInterchange>>language: (in category 'accessing') ----- language: translator language _ translator! ----- Method: GetTextInterchange>>stream (in category 'accessing') ----- stream ^ stream! ----- Method: GetTextInterchange>>stream: (in category 'accessing') ----- stream: aStream stream _ aStream! Object subclass: #MOFile instanceVariableNames: 'localeID fileName isLittleEndian magic revision nStrings originalTableOffset translatedTableOffset hashTableSize hashTableOffset hashTable originalStrings translatedStrings translations' classVariableNames: 'Cr Lf' poolDictionaries: '' category: 'GetText-Localization'! !MOFile commentStamp: '<historical>' prior: 0! Wrapper for MO file of gettext. Known limitation: currently don't support prural form. translation strings have to be encoded in utf-8. Implementation notes: Testing on XO showed emulation of hash search without plugin + on demand loading is slow. The test also showed conversion of utf8 string to Squeak's String is really slow (especially for non-latin language). so in this version, all of original/translated strings are loaded on initiaization, but "translated strings" is left as ByteString on loading time, to reduce loading time. After that the translated string is converted on demand. ! ----- Method: MOFile class>>fileName:localeID: (in category 'instance creation') ----- fileName: path localeID: id ^self new load:path localeID: id! ----- Method: MOFile class>>initialize (in category 'class initialization') ----- initialize Cr := Character cr. Lf := Character lf. ! ----- Method: MOFile>>atRandom (in category 'public') ----- atRandom ^ self translatedString:nStrings atRandom. ! ----- Method: MOFile>>fileName (in category 'public') ----- fileName ^fileName! ----- Method: MOFile>>fileName: (in category 'public') ----- fileName: path fileName _ path! ----- Method: MOFile>>hashPjw: (in category 'experimental') ----- hashPjw: aString "So called `hashpjw' function by P.J. Weinberger [see Aho/Sethi/Ullman, COMPILERS: Principles, Techniques and Tools, 1986, 1987 Bell Telephone Laboratories, Inc.] " | stringSize hash g | stringSize _ aString size. hash _ 0. 1 to: stringSize do: [:pos | hash _ hash bitShift: 4. hash _ hash + ((aString at: pos) asInteger). g _ hash bitAnd: 16rF0000000. g = 0 ifFalse: [ hash _ hash bitXor: (g bitShift: -24). hash _ hash bitXor: g. ] ]. ^hash. ! ----- Method: MOFile>>load1:localeID: (in category 'experimental') ----- load1: aFileName localeID: id "CASE1: all of strings are loaded. translation strings are converted to Squeak format on load time. original-string/index pairs are registerd to Dictionary on load time. hash search can't be used" | strm originalTable translatedTable | localeID _ id. strm_ FileStream readOnlyFileNamed: aFileName. fileName _ aFileName. [ self loadHeader: strm. originalTable _ self loadStringPointers: strm offset: originalTableOffset. originalStrings _ self loadStrings: strm pointers: originalTable. translatedTable _ self loadStringPointers: strm offset: translatedTableOffset. translatedStrings _ self loadStrings: strm pointers: translatedTable encoding: 'utf8' languageEnvironment: (Locale localeID: localeID) languageEnvironment . translations _ Dictionary new. 1 to: nStrings do: [:index | | key | key _ originalStrings at: index. translations at: key put: index. ]. originalTable _ nil. ] ensure: [strm close].! ----- Method: MOFile>>load4:localeID: (in category 'experimental') ----- load4: aFileName localeID: id "CASE4: all of strings are loaded. loading and conversion of translation strings to Squeak format is executed on initialization time. only hash search can be used" | strm originalTable translatedTable | localeID _ id. strm_ FileStream readOnlyFileNamed: aFileName. fileName _ aFileName. [ self loadHeader: strm. self loadHashTable: strm. originalTable _ self loadStringPointers: strm offset: originalTableOffset. originalStrings _ self loadStrings: strm pointers: originalTable. translatedTable _ self loadStringPointers: strm offset: translatedTableOffset. translatedStrings _ self loadStrings: strm pointers: translatedTable encoding: 'utf-8' languageEnvironment: (Locale localeID: localeID) languageEnvironment . ] ensure: [strm close].! ----- Method: MOFile>>load:localeID: (in category 'public') ----- load: aFileName localeID: id "all of original/translated strings are loaded. but conversion of translation string (in utf-8 bytestring) to Squeak format will be defered. original-string/index pairs are registerd to Dictionary on load time. hash search can't be used" | strm originalTable translatedTable | localeID _ id. strm_ FileStream readOnlyFileNamed: aFileName. fileName _ aFileName. [ self loadHeader: strm. originalTable _ self loadStringPointers: strm offset: originalTableOffset. originalStrings _ self loadStrings: strm pointers: originalTable. translatedTable _ self loadStringPointers: strm offset: translatedTableOffset. translatedStrings _ self loadStrings: strm pointers: translatedTable. translations _ Dictionary new: nStrings * 2. "make too enough room to avoid #grow" 1 to: nStrings do: [:index | | key | key _ originalStrings at: index. translations at: key put: index. ]. originalStrings _ nil. ] ensure: [strm close].! ----- Method: MOFile>>loadHashTable: (in category 'experimental') ----- loadHashTable: strm | entry | hashTable _ IntegerArray ofSize: hashTableSize. strm binary. strm position: hashTableOffset. 1 to: hashTableSize do: [:index | entry _ self nextInt32From: strm. hashTable at: index put: entry. ]! ----- Method: MOFile>>loadHeader: (in category 'private') ----- loadHeader: strm strm binary. magic _ strm uint32. magic = 16rDE120495 ifTrue: [isLittleEndian _ true] ifFalse: [ magic = 16r950412DE ifTrue: [isLittleEndian _ false] ifFalse: [ self error: 'invalid MO'] ]. revision _ self nextInt32From: strm. nStrings _ self nextInt32From: strm. originalTableOffset _ self nextInt32From: strm. translatedTableOffset _ self nextInt32From: strm. hashTableSize _ self nextInt32From: strm. hashTableOffset _ self nextInt32From: strm. ! ----- Method: MOFile>>loadString:pointer:length: (in category 'private') ----- loadString: strm pointer: top length: len | str | str _ ByteString new: len. strm position: top. strm nextInto: str. ^str replaceAll: Lf with: Cr. ! ----- Method: MOFile>>loadStringPointers:offset: (in category 'private') ----- loadStringPointers: strm offset: tableOffset "returns tupple {arrayOfOffsetToString arrayOfLengthOfString}" | offsetTable lenTable len offset tupple | offsetTable _ IntegerArray new: nStrings. lenTable _ IntegerArray new: nStrings. strm binary. strm position: tableOffset. 1 to: nStrings do: [:index | len _ self nextInt32From: strm. offset _ self nextInt32From: strm. offsetTable at: index put: offset. lenTable at: index put: len. ]. tupple _ Array new: 2. tupple at: 1 put: offsetTable. tupple at: 2 put: lenTable. ^tupple ! ----- Method: MOFile>>loadStrings:pointers: (in category 'private') ----- loadStrings: strm pointers: table ^self loadStrings: strm pointers: table encoding: nil languageEnvironment: nil ! ----- Method: MOFile>>loadStrings:pointers:encoding:languageEnvironment: (in category 'private') ----- loadStrings: strm pointers: tupple encoding: encodingName languageEnvironment: env | strings rawStr str offsetTable lenTable | offsetTable _ tupple first. lenTable _ tupple second. strings _ Array new: nStrings. 1 to: nStrings do: [:index | rawStr _ self loadString: strm pointer: (offsetTable at: index) length: (lenTable at: index). str _ encodingName isNil ifTrue: [rawStr] ifFalse: [ encodingName = 'utf8' ifTrue: [rawStr utf8ToSqueak applyLanguageInfomation: env] ifFalse: [self error: 'this encoding isn''t supported'] ]. strings at: index put: str. ]. ^strings.! ----- Method: MOFile>>nextInt32From: (in category 'private') ----- nextInt32From: strm ^isLittleEndian ifTrue: [^strm nextLittleEndianNumber: 4] ifFalse: [^strm nextInt32]! ----- Method: MOFile>>originalString: (in category 'private') ----- originalString: index ^originalStrings at: index. ! ----- Method: MOFile>>searchByDictionary: (in category 'public') ----- searchByDictionary: aString | index | index _ translations at: aString ifAbsent: [^nil]. ^self translatedString: index ! ----- Method: MOFile>>searchByHash: (in category 'experimental') ----- searchByHash: aString | hashValue nstr index incr key | hashValue _ self hashPjw: aString. incr _ 1 + (hashValue \\ (hashTableSize -2)). index _ (hashValue \\ hashTableSize) . [ nstr _ (hashTable at: index +1 ). nstr = 0 ifTrue: [^nil]. key _ self originalString: nstr. key = aString ifTrue: [^self translatedString: nstr]. index >= (hashTableSize - incr) ifTrue: [index _ index - (hashTableSize - incr) ] ifFalse:[index _ index + incr]. ] doWhileTrue: true.! ----- Method: MOFile>>testSearchByDictionary (in category 'experimental') ----- testSearchByDictionary InternalTranslator allKnownPhrases do: [:each | self searchByDictionary: each ]. ! ----- Method: MOFile>>testSearchByHash (in category 'experimental') ----- testSearchByHash InternalTranslator allKnownPhrases do: [:each | self searchByHash: each ]. ! ----- Method: MOFile>>translateByHash: (in category 'experimental') ----- translateByHash: aString | trans | trans _ self searchByHash: aString. trans isNil ifTrue: [^aString] ifFalse: [^trans]. ! ----- Method: MOFile>>translatedString: (in category 'private') ----- translatedString: index "KNOWN PROBLEM: conversion is executed everytimes this method called" | str | str _ translatedStrings at: index. ^str utf8ToSqueak applyLanguageInfomation: (Locale localeID: localeID) languageEnvironment. ! ----- Method: MOFile>>translationFor: (in category 'public') ----- translationFor: aString | | aString size = 0 ifTrue: [^ '']. "Gettext header" ^ (self searchByDictionary: aString) ifNil: [aString] ! Object subclass: #TextDomainManager instanceVariableNames: '' classVariableNames: 'ClassCategories Classes DefaultDomain DomainInfos LoneClasses Packages' poolDictionaries: '' category: 'GetText-Localization'! TextDomainManager class instanceVariableNames: 'defaultDomain'! !TextDomainManager commentStamp: 'tk 1/4/2008 16:08' prior: 0! I manages mapping from class category to textdomain. Class variables: ClassCategories IdentityDictionary -- classCategory -> domainName Classes IdentityDictionary -- class name (a Symbol) -> domainName (a cache only!!) DefaultDomain String -- the default domain name DomainInfos Dictionary -- domainName -> a TextDomainInfo LoneClasses IdentityDictionary -- class name (a Symbol) -> domainName. For classes whose entire category are not all in the same domain (BookMorph and QuickGuideMorph) TextDomainManager registerCategoryPrefix: 'DrGeoII' domain: 'DrGeoII'. TextDomainManager unregisterDomain: 'DrGeoII'. TextDomainManager registerClass: #QuickGuideMorph domain: 'quickguides'. TextDomainManager registerClass: #QuickGuideHolderMorph domain: 'quickguides'. ! TextDomainManager class instanceVariableNames: 'defaultDomain'! ----- Method: TextDomainManager class>>allKnownDomains (in category 'accessing') ----- allKnownDomains "Every package has it's own text domain now so it's not necessary to keep a registry of all domains, we can simply return all the packages in the image. PROBLEM: If a package doesn't contain translations, it won't have a mo file but the GetTextTranslator will try to load it anyway. This happens when we switch languages. So far I tested it briefly and it seems to work..." ^PackageOrganizer default packageNames , {'Etoys-Tiles'}! ----- Method: TextDomainManager class>>allMethodsWithTranslations (in category 'accessing') ----- allMethodsWithTranslations "Look for #translated calls" | methodsWithTranslations | methodsWithTranslations := TranslatedReceiverFinder new stringReceiversWithContext: #translated. methodsWithTranslations := methodsWithTranslations, (TranslatedReceiverFinder new stringReceiversWithContext: #translatedNoop). methodsWithTranslations := methodsWithTranslations collect: [:each | each key compiledMethod]. "Look for Etoys tiles and vocabularies" methodsWithTranslations := methodsWithTranslations, (EToyVocabulary allPhrasesWithContextToTranslate collect: [:r | (MethodReference new setStandardClass: r second methodSymbol: r third) compiledMethod. ]). ^methodsWithTranslations! ----- Method: TextDomainManager class>>clearAllDomains (in category 'private') ----- clearAllDomains SystemNavigation default allCompiledMethodDo: [:each | each removeProperty: self textDomainProperty ifAbsent: []] ! ----- Method: TextDomainManager class>>defaultDomain (in category 'accessing') ----- defaultDomain "I'm not sure we still need a default domain, AFAIK the default domain will only be used when no domain is found. In that case, wouldn't it be better to just look for a translation in all domains?" ^defaultDomain! ----- Method: TextDomainManager class>>defaultDomain: (in category 'accessing') ----- defaultDomain: aDomainName defaultDomain := aDomainName! ----- Method: TextDomainManager class>>domainForClass: (in category 'accessing') ----- domainForClass: aClass ^'etoys'! ----- Method: TextDomainManager class>>domainForPackage: (in category 'accessing') ----- domainForPackage: aPackageInfo "Package names and text domains are synonyms now" ^aPackageInfo name! ----- Method: TextDomainManager class>>domainOfMethod: (in category 'accessing') ----- domainOfMethod: aCompiledMethod ^ aCompiledMethod propertyValueAt: self textDomainProperty ifAbsent: [self updateDomainOfMethod: aCompiledMethod] ! ----- Method: TextDomainManager class>>initialize (in category 'class initialization') ----- initialize " TextDomainManager initialize " self defaultDomain: 'Etoys'; clearAllDomains! ----- Method: TextDomainManager class>>textDomainProperty (in category 'private') ----- textDomainProperty ^#textDomain! ----- Method: TextDomainManager class>>updateDomainOfAllMethodsWithTranslations (in category 'private') ----- updateDomainOfAllMethodsWithTranslations self allMethodsWithTranslations do: [:each| self updateDomainOfMethod: each ]! ----- Method: TextDomainManager class>>updateDomainOfMethod: (in category 'private') ----- updateDomainOfMethod: aCompiledMethod "First it looks for the package of the method reference (using the PackageOrganizer: deadly slow). If the method doesn't belong to any package it uses the default domain. Finally it stores the text domain of the method using a method property, this way we gain performance the next time we translate the same method because we avoid the use of PackageOrganizer. Have I mentioned it is really slow? :)" | package | package := PackageOrganizer default packageOfMethod: aCompiledMethod methodReference ifNone: []. ^ aCompiledMethod propertyValueAt: self textDomainProperty put: (package isNil ifTrue: [TextDomainManager defaultDomain] ifFalse: [package name])! Object subclass: #TranslatedReceiverFinder instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'GetText-Editor'! ----- Method: TranslatedReceiverFinder class>>browseNonLiteralReceivers (in category 'utilities') ----- browseNonLiteralReceivers "TranslatedReceiverFinder browseNonLiteralReceivers" SystemNavigation default browseMessageList: self new nonLiteralReceivers asSortedCollection name: 'Non literal receivers of #translated' autoSelect: 'translated'! ----- Method: TranslatedReceiverFinder>>arraySearch:fromArray:addTo: (in category 'private') ----- arraySearch: aSymbol fromArray: anArray addTo: aCollection "Find literals ahead of aSymbol from arrays in the method." "BUG: it can handle just one occurrence" "self new arraySearch: #hello fromArray: #(ignore (ignore detected hello ignore)) addTo: Set new" | index | (index := anArray identityIndexOf: aSymbol) > 1 ifTrue: [aCollection add: (anArray at: index - 1) asString]. (anArray select: [:each | each isMemberOf: Array]) do: [:each | self arraySearch: aSymbol fromArray: each addTo: aCollection]. ^ aCollection! ----- Method: TranslatedReceiverFinder>>arraySearch:messageNode:addTo: (in category 'private') ----- arraySearch: aSymbol messageNode: aParseNode addTo: aCollection "Find literals ahead of aSymbol from arrays in the method." "self new arraySearch: #hello messageNode: (self decompile: #arraySearch:messageNode:addTo:) addTo: Set new" self flag: #(#ignore #detected #hello ). ((aParseNode isMemberOf: LiteralNode) and: [aParseNode key isMemberOf: Array]) ifTrue: [self arraySearch: aSymbol fromArray: aParseNode key addTo: aCollection]. (aParseNode notNil and: [aParseNode isLeaf not]) ifTrue: [aParseNode getAllChildren do: [:child | self arraySearch: aSymbol messageNode: child addTo: aCollection]]. ^ aCollection! ----- Method: TranslatedReceiverFinder>>findWordsWith:in: (in category 'accessing') ----- findWordsWith: aSymbol in: aMethodReference "Find words for translation with the symbol in a method. See LanguageEditorTest >>testFindTranslatedWords" "| message | message := MethodReference new setStandardClass: Morph class methodSymbol: #supplementaryPartsDescriptions. self new findWordsWIth: #translatedNoop in: message" | messages keywords aParseNode | aParseNode := aMethodReference decompile. "Find from string literal" messages := Set new. self search: aSymbol messageNode: aParseNode addTo: messages. keywords := OrderedCollection new. messages select: [:aMessageNode | aMessageNode receiver isMemberOf: LiteralNode] thenDo: [:aMessageNode | aMessageNode receiver key literalStringsDo: [:literal | keywords add: literal]]. "Find from array literal" self arraySearch: aSymbol messageNode: aParseNode addTo: keywords. ^ keywords! ----- Method: TranslatedReceiverFinder>>nonLiteralReceivers (in category 'accessing') ----- nonLiteralReceivers "self new nonLiteralReceivers" | receivers | "Answer method references of non literal senders of #translated" ^ (SystemNavigation default allCallsOn: #translated) select: [:message | receivers := OrderedCollection new. self search: #translated messageNode: message decompile addTo: receivers. receivers anySatisfy: [:each | (each receiver isMemberOf: LiteralNode) not]]! ----- Method: TranslatedReceiverFinder>>search:messageNode:addTo: (in category 'private') ----- search: aSymbol messageNode: aParseNode addTo: aCollection "self new search: #translated messageNode: (Project decompile: #updateLocaleDependentsWithPreviousSupplies:gently:) addTo: OrderedCollection new" ((aParseNode isMemberOf: MessageNode) and: [(aParseNode selector isMemberOf: SelectorNode) and: [aParseNode selector key = aSymbol]]) ifTrue: [aCollection add: aParseNode]. (aParseNode notNil and: [aParseNode isLeaf not]) ifTrue: [aParseNode getAllChildren do: [:child | self search: aSymbol messageNode: child addTo: aCollection]]. ^ aCollection! ----- Method: TranslatedReceiverFinder>>stringReceivers (in category 'accessing') ----- stringReceivers "TranslatedReceiverFinder new stringReceivers" | stringReceivers messages | messages := Set new. (SystemNavigation default allCallsOn: #translated) do: [:message | self search: #translated messageNode: message decompile addTo: messages]. stringReceivers := messages select: [:each | each receiver isMemberOf: LiteralNode] thenCollect: [:each | each receiver key]. ^ stringReceivers asArray sort! ----- Method: TranslatedReceiverFinder>>stringReceiversWithContext (in category 'accessing') ----- stringReceiversWithContext | mrs results rr cls mn t o | mrs _ SystemNavigation default allCallsOn: #translated. results _ OrderedCollection new. mrs do: [:mr | rr _ OrderedCollection new. cls _ Smalltalk at: mr classSymbol. rr add: cls category. rr add: mr classSymbol. rr add: mr methodSymbol. mr classIsMeta ifTrue: [ mn _ cls class decompile: mr methodSymbol. ] ifFalse: [ mn _ cls decompile: mr methodSymbol. ]. o _ OrderedCollection new. t _ Set new. self searchMessageNode: mn addTo: t. t do: [ :te | (te receiver isMemberOf: LiteralNode) ifTrue: [ o add: te receiver key. ]. ]. o ifNotEmpty: [ rr add: o. results add: rr. ]. ]. ^ results. ! ----- Method: TranslatedReceiverFinder>>stringReceiversWithContext: (in category 'accessing') ----- stringReceiversWithContext: aSymbol "Find string receivers for a symbol. Answer a collection of aMethodReference -> {keyword. keyword...}" "self new stringReceiversWithContext: #translated" | keywords methodReferences | methodReferences _ SystemNavigation default allCallsOn: aSymbol. ^ methodReferences inject: OrderedCollection new into: [:list :next | keywords := self findWordsWith: aSymbol in: next. keywords ifNotEmpty: [list add: next -> keywords]. list] ! NaturalLanguageTranslator subclass: #GetTextTranslator instanceVariableNames: 'moFiles' classVariableNames: 'LocaleDirsForDomain SystemDefaultLocaleDirs UserDefaultLocaleDirs' poolDictionaries: '' category: 'GetText-Localization'! !GetTextTranslator commentStamp: '<historical>' prior: 0! emulation of gettext runtime Known limitation: currently doesn't support plural forms. ! ----- Method: GetTextTranslator class>>addSystemDefaultLocaleDir: (in category 'translation data layout') ----- addSystemDefaultLocaleDir: dir "new dir will be put as first" self systemDefaultLocaleDirs addFirst: dir! ----- Method: GetTextTranslator class>>availableLanguageLocaleIDs (in category 'accessing') ----- availableLanguageLocaleIDs "GetTextTranslator availableLanguageLocaleIDs" | ids dirs localeDirForLang directoryNames | ids := Set new. dirs := Set new. dirs addAll: LocaleDirsForDomain values. dirs addAll: self defaultLocaleDirs. dirs do: [:dir | | localesDir | localesDir := FileDirectory on: dir. directoryNames := [localesDir directoryNames] on: InvalidDirectoryError do: [:e | #()]. directoryNames do: [:langDirName | | localeID | localeID := LocaleID posixName: langDirName. localeDirForLang := localesDir directoryNamed: (self langDirNameForLocaleID: localeID). localeDirForLang ifNotNil: [ (localeDirForLang fileNamesMatching: '*.mo') ifNotEmpty: [ids add: localeID] ] ]. ]. ^ids! ----- Method: GetTextTranslator class>>defaultLocaleDirs (in category 'translation data layout') ----- defaultLocaleDirs | dirs | dirs _ OrderedCollection new. UserDefaultLocaleDirs ifNotNil: [dirs addAll: UserDefaultLocaleDirs]. dirs addAll: self systemDefaultLocaleDirs. ^dirs ! ----- Method: GetTextTranslator class>>findMOForLocaleID:domain: (in category 'private') ----- findMOForLocaleID: id domain: aDomainName | sepa langSubDir path | sepa _ FileDirectory slash. langSubDir _ self langDirNameForLocaleID: id. (self localeDirsForDomain: aDomainName) do: [:each | path _ each , sepa , langSubDir, sepa , (self moNameForDomain: aDomainName). [(FileDirectory default fileExists: path) ifTrue: [^path]] on: InvalidDirectoryError do: [:e | ^nil]]. ^nil.! ----- Method: GetTextTranslator class>>initialize (in category 'class initialization') ----- initialize SystemDefaultLocaleDirs _ OrderedCollection new. UserDefaultLocaleDirs _ OrderedCollection new. LocaleDirsForDomain _ Dictionary new.! ----- Method: GetTextTranslator class>>langDirNameForLocaleID: (in category 'private') ----- langDirNameForLocaleID: id "returns relative path from locale directory to actual directory containing MOs" ^(id posixName) , (FileDirectory slash) , 'LC_MESSAGES'! ----- Method: GetTextTranslator class>>localeDirForDomain: (in category 'translation data layout') ----- localeDirForDomain: aDomainName "returns registered localeDirectory for the textdomain. returns nil if not registered" ^LocaleDirsForDomain at: aDomainName ifAbsent: [nil]! ----- Method: GetTextTranslator class>>localeDirsForDomain (in category 'private') ----- localeDirsForDomain ^LocaleDirsForDomain ifNil: [LocaleDirsForDomain _ Dictionary new]! ----- Method: GetTextTranslator class>>localeDirsForDomain: (in category 'translation data layout') ----- localeDirsForDomain: aDomainName "returns collection of locale directories for text domain. This includes user defined one for the domain, user defaults and system defaults" | dirs dir | dirs _ OrderedCollection new. dir _ self localeDirForDomain: aDomainName. dir ifNotNil: [dirs add: dir]. dirs addAll: self defaultLocaleDirs. ^dirs! ----- Method: GetTextTranslator class>>moNameForDomain: (in category 'private') ----- moNameForDomain: domainName ^domainName , '.mo'! ----- Method: GetTextTranslator class>>newForLocaleID: (in category 'instance creation') ----- newForLocaleID: id ^self new localeID: id! ----- Method: GetTextTranslator class>>privateStartUp (in category 'class initialization') ----- privateStartUp self setupLocaleDirs. self availableLanguageLocaleIDs do: [ :localeID | NaturalLanguageTranslator translators at: localeID put: (self newForLocaleID: localeID). ]! ----- Method: GetTextTranslator class>>setLocaleDir:forDoamin: (in category 'translation data layout') ----- setLocaleDir: path forDoamin: aDomainName self LocaleDirsForDomain at: aDomainName put: path.! ----- Method: GetTextTranslator class>>setupLocaleDirs (in category 'translation data layout') ----- setupLocaleDirs | dirs sepa localesDirName | sepa := FileDirectory slash. SystemDefaultLocaleDirs := nil. dirs := self systemDefaultLocaleDirs. localesDirName := 'locale'. dirs add: (SmalltalkImage current imagePath) , sepa , localesDirName. dirs add: (SmalltalkImage current vmPath) , sepa , localesDirName. ^dirs! ----- Method: GetTextTranslator class>>systemDefaultLocaleDirs (in category 'translation data layout') ----- systemDefaultLocaleDirs ^SystemDefaultLocaleDirs ifNil: [SystemDefaultLocaleDirs := OrderedCollection new] ! ----- Method: GetTextTranslator class>>userDefaultLocaleDirs (in category 'translation data layout') ----- userDefaultLocaleDirs ^UserDefaultLocaleDirs ifNil: [UserDefaultLocaleDirs := OrderedCollection new] ! ----- Method: GetTextTranslator>>atRandom (in category 'accessing') ----- atRandom | v | moFiles ifEmpty: [^ '']. (v := moFiles atRandom value) ifNil: [^ '']. ^ v atRandom. ! ----- Method: GetTextTranslator>>domainRegistered: (in category 'accessing') ----- domainRegistered: aDomainName "only current translator actually load the MO, to minimize loading time. other translator will load anyway when it goes current" (self class current == self) ifTrue: [self moFileForDomain: aDomainName]. ! ----- Method: GetTextTranslator>>domainUnregistered: (in category 'accessing') ----- domainUnregistered: aDomainName moFiles removeKey: aDomainName ifAbsent: [^self] ! ----- Method: GetTextTranslator>>initialize (in category 'initialize-release') ----- initialize moFiles _ Dictionary new.! ----- Method: GetTextTranslator>>isDomainLoaded: (in category 'accessing') ----- isDomainLoaded: aDomainName | mo | mo _ moFiles at: aDomainName ifAbsent: [nil]. ^mo isNil not. ! ----- Method: GetTextTranslator>>loadMOFileForDomain: (in category 'private') ----- loadMOFileForDomain: aDomainName | moName | moName _ self class findMOForLocaleID: self localeID domain: aDomainName. moName notNil ifTrue: [^MOFile new load: moName localeID: self localeID] ifFalse: [^nil] ! ----- Method: GetTextTranslator>>loadMOFiles (in category 'accessing') ----- loadMOFiles TextDomainManager allKnownDomains do: [:domainName | self moFileForDomain: domainName ].! ----- Method: GetTextTranslator>>moFileForDomain: (in category 'private') ----- moFileForDomain: domainName ^moFiles at: domainName ifAbsentPut: [self loadMOFileForDomain: domainName]! ----- Method: GetTextTranslator>>reloadMOFiles (in category 'accessing') ----- reloadMOFiles moFiles _ Dictionary new. self loadMOFiles.! ----- Method: GetTextTranslator>>setCurrent (in category 'language switching') ----- setCurrent "ensure actual contents of MOs is loaded on switching language" self loadMOFiles! ----- Method: GetTextTranslator>>translate:inDomain: (in category 'translation') ----- translate: aString inDomain: aDomainName | mo | mo _ self moFileForDomain: aDomainName. ^mo isNil ifTrue: [aString] ifFalse: [mo translationFor: aString] ! ----- Method: String>>literalStringsDo: (in category '*gettext') ----- literalStringsDo: aBlock "Assuming the receiver receiver is a literal, evaluate aBlock with all Strings (but not Symbols) within it." aBlock value: self! ----- Method: String>>translated (in category '*gettext') ----- translated "answer the receiver translated to the default language" | translation | translation := self translatedTo: LocaleID current inDomain: (TextDomainManager domainOfMethod: thisContext sender method). self == translation ifTrue: [^self translatedInAllDomains]. ^translation! ----- Method: String>>translatedIfCorresponds (in category '*gettext') ----- translatedIfCorresponds "answer the receiver translated to the default language only if the receiver begins and ends with an underscore (_)" ^ ('_*_' match: self) ifTrue: [(self copyFrom: 2 to: self size - 1) translated] ifFalse: [self]! ----- Method: String>>translatedInAllDomains (in category '*gettext') ----- translatedInAllDomains | translation | "Transcript show: self printString, ' translatedInAllDomains'; cr." TextDomainManager allKnownDomains do: [:domain | translation := self translatedTo: LocaleID current inDomain: domain. self = translation ifFalse: [^translation] ]. ^self! ----- Method: String>>translatedInDomain: (in category '*gettext') ----- translatedInDomain: aDomainName | translation | translation := self translatedTo: LocaleID current inDomain: aDomainName. self == translation ifTrue: [^self translatedInAllDomains]. ^translation ! ----- Method: String>>translatedInDomain:or: (in category '*gettext') ----- translatedInDomain: aDomainName or: anotherDomainName | translation | translation := self translatedTo: LocaleID current inDomain: aDomainName. self == translation ifTrue: [^self translatedInDomain: anotherDomainName]. ^translation ! ----- Method: String>>translatedNoop (in category '*gettext') ----- translatedNoop "This is correspondence gettext_noop() in gettext." ^ self! ----- Method: String>>translatedTo: (in category '*gettext') ----- translatedTo: localeID "answer the receiver translated to the given locale id" ^ self translatedTo: localeID inDomain: (TextDomainManager domainOfMethod: thisContext sender method).! ----- Method: String>>translatedTo:inDomain: (in category '*gettext') ----- translatedTo: localeID inDomain: aDomainName "answer the receiver translated to the given locale id in the textdomain" ^ NaturalLanguageTranslator translate: self toLocaleID: localeID inDomain: aDomainName! |
Free forum by Nabble | Edit this page |