[squeak-dev] The Trunk: GetText-Richo.14.mcz

commits at source.squeak.org commits at source.squeak.org
Wed Mar 14 22:27:42 UTC 2012


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!



More information about the Squeak-dev mailing list