diff --git a/source/BaselineOfBuoy/BaselineOfBuoy.class.st b/source/BaselineOfBuoy/BaselineOfBuoy.class.st index 16830f8..e276369 100644 --- a/source/BaselineOfBuoy/BaselineOfBuoy.class.st +++ b/source/BaselineOfBuoy/BaselineOfBuoy.class.st @@ -187,6 +187,8 @@ BaselineOfBuoy >> baselineLocalization: spec [ group: 'Deployment' with: 'Buoy-Localization'; package: 'Buoy-Localization-Extensions' with: [ spec requires: 'Buoy-Localization' ]; group: 'Deployment' with: 'Buoy-Localization-Extensions'; + package: 'Buoy-Localization-Pharo-Extensions' with: [ spec requires: 'Buoy-Localization' ]; + group: 'Deployment' with: 'Buoy-Localization-Pharo-Extensions'; package: 'Buoy-Localization-Tests' with: [ spec requires: #( 'Buoy-Localization-Extensions' 'Dependent-SUnit-Extensions' ) ]; group: 'Tests' with: 'Buoy-Localization-Tests' @@ -236,13 +238,13 @@ BaselineOfBuoy >> baselineMetaprogramming: spec [ { #category : 'baselines' } BaselineOfBuoy >> baselineSUnit: spec [ - spec - package: 'Buoy-SUnit-Model'; - group: 'Dependent-SUnit-Extensions' with: 'Buoy-SUnit-Model'; - package: 'Buoy-SUnit-Pharo-Extensions'; - group: 'Dependent-SUnit-Extensions' with: 'Buoy-SUnit-Pharo-Extensions'; - package: 'Buoy-SUnit-Tests' with: [ spec requires: 'Dependent-SUnit-Extensions' ]; - group: 'Tests' with: 'Buoy-SUnit-Tests' + spec + package: 'Buoy-SUnit-Model' with: [ spec requires: 'Buoy-Localization' ]; + group: 'Dependent-SUnit-Extensions' with: 'Buoy-SUnit-Model'; + package: 'Buoy-SUnit-Pharo-Extensions'; + group: 'Dependent-SUnit-Extensions' with: 'Buoy-SUnit-Pharo-Extensions'; + package: 'Buoy-SUnit-Tests' with: [ spec requires: 'Dependent-SUnit-Extensions' ]; + group: 'Tests' with: 'Buoy-SUnit-Tests' ] { #category : 'baselines' } diff --git a/source/Buoy-Localization-Extensions/String.extension.st b/source/Buoy-Localization-Extensions/String.extension.st index 59daa50..0b95cea 100644 --- a/source/Buoy-Localization-Extensions/String.extension.st +++ b/source/Buoy-Localization-Extensions/String.extension.st @@ -22,6 +22,18 @@ String >> escaped [ ] ] +{ #category : '*Buoy-Localization-Extensions' } +String >> localized [ + + ^ self localizedWithAll: #( ) +] + +{ #category : '*Buoy-Localization-Extensions' } +String >> localizedWithAll: collection [ + + ^ NaturalLanguageTranslator current localize: self withAll: collection to: CurrentLocale value +] + { #category : '*Buoy-Localization-Extensions' } String >> unescaped [ diff --git a/source/Buoy-Localization-GS64-Extensions/NaturalLanguageTranslator.class.st b/source/Buoy-Localization-GS64-Extensions/NaturalLanguageTranslator.class.st new file mode 100644 index 0000000..d43be68 --- /dev/null +++ b/source/Buoy-Localization-GS64-Extensions/NaturalLanguageTranslator.class.st @@ -0,0 +1,35 @@ +" +A NaturalLanguageTranslator is a dummy translator. + +The localization framework is found in the gettext package usually +overriding this class completely. + +As an alternative you can register a translator using + + NaturalLanguageTranslator current: myTranslator + +If this is done the messages will be dispatched to it +" +Class { + #name : 'NaturalLanguageTranslator', + #superclass : 'Object', + #classVars : [ + 'Current' + ], + #category : 'Buoy-Localization-GS64-Extensions', + #package : 'Buoy-Localization-GS64-Extensions' +} + +{ #category : 'accessing' } +NaturalLanguageTranslator class >> current [ + "Return the currently registered translator" + + ^Current +] + +{ #category : 'accessing' } +NaturalLanguageTranslator class >> current: aTranslator [ + "Register a translator that should receiver the translation messages" + + Current := aTranslator +] diff --git a/source/Buoy-Localization-GS64-Extensions/package.st b/source/Buoy-Localization-GS64-Extensions/package.st new file mode 100644 index 0000000..84837fe --- /dev/null +++ b/source/Buoy-Localization-GS64-Extensions/package.st @@ -0,0 +1 @@ +Package { #name : 'Buoy-Localization-GS64-Extensions' } diff --git a/source/Buoy-Localization-Pharo-Extensions/PolyglotNaturalLanguageTranslator.extension.st b/source/Buoy-Localization-Pharo-Extensions/PolyglotNaturalLanguageTranslator.extension.st new file mode 100644 index 0000000..f896910 --- /dev/null +++ b/source/Buoy-Localization-Pharo-Extensions/PolyglotNaturalLanguageTranslator.extension.st @@ -0,0 +1,15 @@ +Extension { #name : 'PolyglotNaturalLanguageTranslator' } + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +PolyglotNaturalLanguageTranslator >> translate: string [ + "Just for Pharo compatibility" + + ^ string +] + +{ #category : '*Buoy-Localization-Pharo-Extensions' } +PolyglotNaturalLanguageTranslator >> translate: string toLocale: localeID [ + "Just for Pharo compatibility" + + ^ self translate: string +] diff --git a/source/Buoy-Localization-Pharo-Extensions/package.st b/source/Buoy-Localization-Pharo-Extensions/package.st new file mode 100644 index 0000000..2642ed8 --- /dev/null +++ b/source/Buoy-Localization-Pharo-Extensions/package.st @@ -0,0 +1 @@ +Package { #name : 'Buoy-Localization-Pharo-Extensions' } diff --git a/source/Buoy-Localization-Tests/PolyglotNaturalLanguageTranslatorTest.class.st b/source/Buoy-Localization-Tests/PolyglotNaturalLanguageTranslatorTest.class.st new file mode 100644 index 0000000..7713e97 --- /dev/null +++ b/source/Buoy-Localization-Tests/PolyglotNaturalLanguageTranslatorTest.class.st @@ -0,0 +1,61 @@ +" +A PolyglotNaturalLanguageTranslatorTest is a test class for testing the behavior of PolyglotNaturalLanguageTranslator +" +Class { + #name : 'PolyglotNaturalLanguageTranslatorTest', + #superclass : 'TestCase', + #instVars : [ + 'translator' + ], + #category : 'Buoy-Localization-Tests', + #package : 'Buoy-Localization-Tests' +} + +{ #category : 'running' } +PolyglotNaturalLanguageTranslatorTest >> setUp [ + + super setUp. + translator := PolyglotNaturalLanguageTranslator new +] + +{ #category : 'tests' } +PolyglotNaturalLanguageTranslatorTest >> testLocalizedToLanguageWithTranslation [ + + translator + translationFor: 'Happy new year' in: 'es' is: 'Feliz año nuevo'; + translationFor: 'Happy new year! \u{1F389}' in: 'es' is: 'Feliz año nuevo! \u{1F389}'; + translationFor: 'Happy {1} year' in: 'es' is: 'Feliz año {1}'; + translationFor: 'Happy new year! \u{1F389}' in: 'es-AR' is: '¡Feliz año nuevo! \u{1F389}'. + + self + assert: ( translator localize: 'Happy new year' to: 'es-AR' ) equals: 'Feliz año nuevo'; + assert: ( translator localize: 'Happy new year! \u{1F389}' to: 'es-AR' ) + equals: '¡Feliz año nuevo! 🎉'; + assert: ( translator localize: 'Happy {1} year' withAll: { 2024 } to: 'es-AR' ) + equals: 'Feliz año 2024'. + + self + assert: ( translator localize: 'Happy new year' to: 'es' ) equals: 'Feliz año nuevo'; + assert: ( translator localize: 'Happy new year! \u{1F389}' to: 'es' ) + equals: 'Feliz año nuevo! 🎉'; + assert: ( translator localize: 'Happy {1} year' withAll: { 2024 } to: 'es' ) + equals: 'Feliz año 2024'. + + self + assert: ( translator localize: 'Happy new year' to: 'es-ES' ) equals: 'Feliz año nuevo'; + assert: ( translator localize: 'Happy new year! \u{1F389}' to: 'es-ES' ) + equals: 'Feliz año nuevo! 🎉'; + assert: ( translator localize: 'Happy {1} year' withAll: { 2024 } to: 'es-ES' ) + equals: 'Feliz año 2024' +] + +{ #category : 'tests' } +PolyglotNaturalLanguageTranslatorTest >> testLocalizedToLanguageWithoutTranslations [ + + self + assert: ( translator localize: 'Happy new year' to: 'es-AR' ) equals: 'Happy new year'; + assert: ( translator localize: 'Happy new year! \u{1F389}' to: 'es-AR' ) + equals: 'Happy new year! 🎉'; + assert: ( translator localize: 'Happy {1} year' withAll: { 2024 } to: 'es-AR' ) + equals: 'Happy 2024 year' +] diff --git a/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st b/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st index f769827..7e854cd 100644 --- a/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st +++ b/source/Buoy-Localization-Tests/StringLocalizationExtensionsTest.class.st @@ -98,6 +98,74 @@ StringLocalizationExtensionsTest >> testEscapedWithoutEscapingSequence [ assert: '¡vamos! ¿adonde?' escaped equals: '¡vamos! ¿adonde?' ] +{ #category : 'tests - localization' } +StringLocalizationExtensionsTest >> testLocalized [ + + self assert: 'Welcome' localized equals: 'Welcome'. + + self + use: [ :translator | + translator + translationFor: 'Welcome' in: 'es' is: 'Bienvenido'; + translationFor: 'Welcome' + in: 'ar' + is: '\u{623}\u{647}\u{644}\u{627}\u{64B} \u{648} \u{633}\u{647}\u{644}\u{627}\u{64B}'; + translationFor: 'Welcome' in: 'de' is: 'Willkommen'; + translationFor: 'Welcome' in: 'fr' is: 'Bienvenue'; + translationFor: 'Welcome' in: 'pt' is: 'Bem-vindo'; + translationFor: 'Welcome' in: 'ja' is: '\u{3088}\u{3046}\u{3053}\u{305D}' + ] + asNaturalLanguageTranslatorDuring: [ + self + use: 'es-AR' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'Bienvenido' ]; + use: 'es-MX' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'Bienvenido' ]; + use: 'ar' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'أهلاً و سهلاً' ]; + use: 'de' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'Willkommen' ]; + use: 'fr' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'Bienvenue' ]; + use: 'pt' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'Bem-vindo' ]; + use: 'pt-BR' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'Bem-vindo' ]; + use: 'ja' asLocaleDuring: [ self assert: 'Welcome' localized equals: 'ようこそ' ] + ] +] + +{ #category : 'tests - localization' } +StringLocalizationExtensionsTest >> testLocalizedWithAll [ + + | stringToTranslate parameters | + stringToTranslate := '{1} seems to be out of sync. Please fetch from "{2}" and try again.'. + parameters := #( 'Buoy' 'origin' ). + + self + assert: ( stringToTranslate localizedWithAll: parameters ) + equals: 'Buoy seems to be out of sync. Please fetch from "origin" and try again.'. + + self + use: [ :translator | + translator + translationFor: stringToTranslate + in: 'es' + is: '{1} parece no estar sincronizado. Por favor, actualiza desde "{2}" y reintenta.'; + translationFor: stringToTranslate + in: 'es-AR' + is: '{1} parece no estar sincronizado. Por favor, actualizá desde "{2}" y reintentá.' + ] + asNaturalLanguageTranslatorDuring: [ + self + use: 'es-AR' asLocaleDuring: [ + self + assert: ( stringToTranslate localizedWithAll: parameters ) + equals: + 'Buoy parece no estar sincronizado. Por favor, actualizá desde "origin" y reintentá.' + ]; + use: 'es-MX' asLocaleDuring: [ + self + assert: ( stringToTranslate localizedWithAll: parameters ) + equals: + 'Buoy parece no estar sincronizado. Por favor, actualiza desde "origin" y reintenta.' + ] + ] +] + { #category : 'tests - escaping' } StringLocalizationExtensionsTest >> testUnescaped [ diff --git a/source/Buoy-Localization/CurrentLocale.class.st b/source/Buoy-Localization/CurrentLocale.class.st new file mode 100644 index 0000000..7a2a672 --- /dev/null +++ b/source/Buoy-Localization/CurrentLocale.class.st @@ -0,0 +1,18 @@ +Class { + #name : 'CurrentLocale', + #superclass : 'DynamicVariable', + #category : 'Buoy-Localization', + #package : 'Buoy-Localization' +} + +{ #category : 'configuring' } +CurrentLocale class >> use: languageTagOrString during: aBlock [ + + ^ self value: languageTagOrString asLanguageTag during: aBlock +] + +{ #category : 'accessing' } +CurrentLocale >> default [ + + ^ 'en' asLanguageTag +] diff --git a/source/Buoy-Localization/MonoglotNaturalLanguageTranslator.class.st b/source/Buoy-Localization/MonoglotNaturalLanguageTranslator.class.st new file mode 100644 index 0000000..d5072f2 --- /dev/null +++ b/source/Buoy-Localization/MonoglotNaturalLanguageTranslator.class.st @@ -0,0 +1,66 @@ +Class { + #name : 'MonoglotNaturalLanguageTranslator', + #superclass : 'Object', + #instVars : [ + 'targetLanguageRange', + 'translations' + ], + #category : 'Buoy-Localization', + #package : 'Buoy-Localization' +} + +{ #category : 'instance creation' } +MonoglotNaturalLanguageTranslator class >> for: aLanguageRange [ + + ^ self new initializeFor: aLanguageRange +] + +{ #category : 'private' } +MonoglotNaturalLanguageTranslator >> hashCodeFor: string [ + + ^ LanguagePlatform current messageDigest: string +] + +{ #category : 'initialization' } +MonoglotNaturalLanguageTranslator >> initializeFor: aLanguageRange [ + + targetLanguageRange := aLanguageRange. + translations := Dictionary new +] + +{ #category : 'testing' } +MonoglotNaturalLanguageTranslator >> isFor: aLanguageRange [ + + ^ targetLanguageRange = aLanguageRange +] + +{ #category : 'testing' } +MonoglotNaturalLanguageTranslator >> matches: aLanguageTag [ + + ^ targetLanguageRange matches: aLanguageTag +] + +{ #category : 'accessing' } +MonoglotNaturalLanguageTranslator >> specificity [ + + ^targetLanguageRange specificity + +] + +{ #category : 'private' } +MonoglotNaturalLanguageTranslator >> translationAt: hash put: translatedString [ + + translations at: hash put: translatedString +] + +{ #category : 'configuring' } +MonoglotNaturalLanguageTranslator >> translationFor: string is: translatedString [ + + ^ self translationAt: ( self hashCodeFor: string unescaped ) put: translatedString unescaped +] + +{ #category : 'localization' } +MonoglotNaturalLanguageTranslator >> withTranslationOf: string do: block [ + + translations at: ( self hashCodeFor: string ) ifPresent: block +] diff --git a/source/Buoy-Localization/PolyglotNaturalLanguageTranslator.class.st b/source/Buoy-Localization/PolyglotNaturalLanguageTranslator.class.st new file mode 100644 index 0000000..5c75227 --- /dev/null +++ b/source/Buoy-Localization/PolyglotNaturalLanguageTranslator.class.st @@ -0,0 +1,62 @@ +Class { + #name : 'PolyglotNaturalLanguageTranslator', + #superclass : 'Object', + #instVars : [ + 'translators' + ], + #category : 'Buoy-Localization', + #package : 'Buoy-Localization' +} + +{ #category : 'class initialization' } +PolyglotNaturalLanguageTranslator class >> initialize [ + + + NaturalLanguageTranslator current: self new +] + +{ #category : 'initialization' } +PolyglotNaturalLanguageTranslator >> initialize [ + + super initialize. + translators := SortedCollection sortBlock: #specificity descending +] + +{ #category : 'localization' } +PolyglotNaturalLanguageTranslator >> localize: string to: stringOrLanguageTag [ + + ^ self localize: string withAll: #( ) to: stringOrLanguageTag +] + +{ #category : 'localization' } +PolyglotNaturalLanguageTranslator >> localize: string withAll: collection to: stringOrLanguageTag [ + + | languageTag stringToTranslate | + languageTag := stringOrLanguageTag asLanguageTag. + stringToTranslate := string unescaped. + translators do: [ :translator | + ( translator matches: languageTag ) then: [ + translator + withTranslationOf: stringToTranslate + do: [ :translatedString | ^ translatedString format: collection ] + ] + ]. + ^ stringToTranslate format: collection +] + +{ #category : 'configuring' } +PolyglotNaturalLanguageTranslator >> translationFor: string in: targetLanguageRange is: translatedString [ + + ^ ( self translatorFor: targetLanguageRange ) translationFor: string is: translatedString +] + +{ #category : 'private' } +PolyglotNaturalLanguageTranslator >> translatorFor: stringOrLanguageRange [ + + | range | + range := stringOrLanguageRange asLanguageRange. + + ^ translators + detect: [ :translator | translator isFor: range ] + ifNone: [ translators add: ( MonoglotNaturalLanguageTranslator for: range ) ] +] diff --git a/source/Buoy-SUnit-Model/NaturalLanguageTranslator.extension.st b/source/Buoy-SUnit-Model/NaturalLanguageTranslator.extension.st new file mode 100644 index 0000000..006a0d5 --- /dev/null +++ b/source/Buoy-SUnit-Model/NaturalLanguageTranslator.extension.st @@ -0,0 +1,12 @@ +Extension { #name : 'NaturalLanguageTranslator' } + +{ #category : '*Buoy-SUnit-Model' } +NaturalLanguageTranslator class >> use: translator during: block [ + + | currentTranslator | + currentTranslator := self current. + [ + self current: translator. + block value + ] ensure: [ self current: currentTranslator ] +] diff --git a/source/Buoy-SUnit-Model/TestAsserter.extension.st b/source/Buoy-SUnit-Model/TestAsserter.extension.st index 74d6d46..282f096 100644 --- a/source/Buoy-SUnit-Model/TestAsserter.extension.st +++ b/source/Buoy-SUnit-Model/TestAsserter.extension.st @@ -33,6 +33,21 @@ TestAsserter >> should: aBlock raise: anException withMessageText: aString [ withExceptionDo: [ :error | self assert: error messageText equals: aString ] ] +{ #category : '*Buoy-SUnit-Model' } +TestAsserter >> use: languageTag asLocaleDuring: aBlock [ + + CurrentLocale use: languageTag during: aBlock +] + +{ #category : '*Buoy-SUnit-Model' } +TestAsserter >> use: translatorConfigurationBlock asNaturalLanguageTranslatorDuring: aBlock [ + + | translator | + translator := PolyglotNaturalLanguageTranslator new. + translatorConfigurationBlock value: translator. + NaturalLanguageTranslator use: translator during: aBlock +] + { #category : '*Buoy-SUnit-Model' } TestAsserter >> withTheOnlyOneIn: aCollection do: aBlock [