Tools__InternationalLanguageTranslationEditor.st
changeset 2015 8f226ff9b5e6
child 2031 85947c4c6502
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Tools__InternationalLanguageTranslationEditor.st	Mon Mar 06 09:26:48 2006 +0100
@@ -0,0 +1,1330 @@
+"{ Package: 'stx:libtool2' }"
+
+"{ NameSpace: Tools }"
+
+ApplicationModel subclass:#InternationalLanguageTranslationEditor
+	instanceVariableNames:'languagesList keyStringsToLanguageMappings languageTextHolder
+		originalTextHolder modified shownLanguages selectedKeyRow
+		showMissingTranslationsOnly
+		keyStringAndLanguageSelectionTableColumnDescriptionHolder
+		languageShownHolders dataSetView lastExtractedClass
+		lastExtractedApplicationClass stopApplicationIconVisibleHolder
+		monitoredApplication originalTextModifiedHolder
+		languageTextModifiedHolder'
+	classVariableNames:'LastExtractedClass LastExtractedApplicationClass'
+	poolDictionaries:''
+	category:'Interface-UIPainter'
+!
+
+Object subclass:#AccessCollectingPseudoResourcePack
+	instanceVariableNames:'collectedKeys realResourcePack'
+	classVariableNames:''
+	poolDictionaries:''
+	privateIn:InternationalLanguageTranslationEditor
+!
+
+Collection subclass:#KeyStringsToLanguageMappings
+	instanceVariableNames:'keys languageMappings languages'
+	classVariableNames:''
+	poolDictionaries:''
+	privateIn:InternationalLanguageTranslationEditor
+!
+
+Object subclass:#LanguageMappingRow
+	instanceVariableNames:'key mappings'
+	classVariableNames:''
+	poolDictionaries:''
+	privateIn:InternationalLanguageTranslationEditor
+!
+
+Visitor subclass:#UISpecVisitor
+	instanceVariableNames:'translatedLabels'
+	classVariableNames:''
+	poolDictionaries:''
+	privateIn:InternationalLanguageTranslationEditor
+!
+
+!InternationalLanguageTranslationEditor class methodsFor:'documentation'!
+
+documentation
+"
+    documentation to be added.
+
+    [author:]
+        cg (cg@FUSI)
+
+    [instance variables:]
+
+    [class variables:]
+
+    [see also:]
+
+"
+!
+
+examples
+"
+  Starting the application:
+                                                                [exBegin]
+    InternationalLanguageTranslationEditor open
+
+                                                                [exEnd]
+
+  more examples to be added:
+                                                                [exBegin]
+    ... add code fragment for 
+    ... executable example here ...
+                                                                [exEnd]
+"
+!
+
+history
+    "Created: / 04-03-2006 / 09:07:19 / cg"
+! !
+
+!InternationalLanguageTranslationEditor class methodsFor:'interface specs'!
+
+windowSpec
+    "This resource specification was automatically generated
+     by the UIPainter of ST/X."
+
+    "Do not manually edit this!! If it is corrupted,
+     the UIPainter may not be able to read the specification."
+
+    "
+     UIPainter new openOnClass:InternationalLanguageTranslationEditor andSelector:#windowSpec
+     InternationalLanguageTranslationEditor new openInterface:#windowSpec
+     InternationalLanguageTranslationEditor open
+    "
+
+    <resource: #canvas>
+
+    ^ 
+     #(FullSpec
+        name: windowSpec
+        window: 
+       (WindowSpec
+          label: 'InternationalLanguageTranslationEditor'
+          name: 'InternationalLanguageTranslationEditor'
+          min: (Point 10 10)
+          max: (Point 1024 768)
+          bounds: (Rectangle 0 0 640 400)
+          menu: mainMenu
+        )
+        component: 
+       (SpecCollection
+          collection: (
+           (MenuPanelSpec
+              name: 'ToolBar'
+              layout: (LayoutFrame 0 0 0 0 0 1 30 0)
+              menu: menuToolBar
+              textDefault: true
+            )
+           (VariableVerticalPanelSpec
+              name: 'VariableVerticalPanel1'
+              layout: (LayoutFrame 0 0 30 0 0 1 0 1)
+              component: 
+             (SpecCollection
+                collection: (
+                 (DataSetSpec
+                    name: 'KeyStringAndLanguageSelectionTable'
+                    model: selectedKeyRow
+                    hasHorizontalScrollBar: true
+                    hasVerticalScrollBar: true
+                    dataList: keyStringAndLanguageSelectionTable
+                    columnHolder: keyStringAndLanguageSelectionTableColumnDescriptionHolder
+                    columnAdaptor: yourself
+                    postBuildCallback: postBuildDataSet:
+                  )
+                 (ViewSpec
+                    name: 'Box1'
+                    component: 
+                   (SpecCollection
+                      collection: (
+                       (LabelSpec
+                          label: 'Original String (Key):'
+                          name: 'Label2'
+                          layout: (LayoutFrame 0 0 0 0 0 1 30 0)
+                          translateLabel: true
+                          adjust: left
+                        )
+                       (TextEditorSpec
+                          name: 'OriginalText'
+                          layout: (LayoutFrame 0 0 30 0 0 1 0 1)
+                          model: originalTextHolder
+                          hasHorizontalScrollBar: true
+                          hasVerticalScrollBar: true
+                          modifiedChannel: originalTextModifiedHolder
+                        )
+                       )
+                     
+                    )
+                  )
+                 (ViewSpec
+                    name: 'Box2'
+                    component: 
+                   (SpecCollection
+                      collection: (
+                       (LabelSpec
+                          label: 'Translated String:'
+                          name: 'Label1'
+                          layout: (LayoutFrame 0 0 0 0 0 1 30 0)
+                          translateLabel: true
+                          adjust: left
+                        )
+                       (TextEditorSpec
+                          name: 'LanguageText'
+                          layout: (LayoutFrame 0 0 30 0 0 1 0 1)
+                          model: languageTextHolder
+                          hasHorizontalScrollBar: true
+                          hasVerticalScrollBar: true
+                          modifiedChannel: languageTextModifiedHolder
+                        )
+                       )
+                     
+                    )
+                  )
+                 )
+               
+              )
+              handles: (Any 0.33333333333333 0.66666666666667 1.0)
+            )
+           )
+         
+        )
+      )
+! !
+
+!InternationalLanguageTranslationEditor class methodsFor:'menu specs'!
+
+mainMenu
+    "This resource specification was automatically generated
+     by the MenuEditor of ST/X."
+
+    "Do not manually edit this!! If it is corrupted,
+     the MenuEditor may not be able to read the specification."
+
+    "
+     MenuEditor new openOnClass:InternationalLanguageTranslationEditor andSelector:#mainMenu
+     (Menu new fromLiteralArrayEncoding:(InternationalLanguageTranslationEditor mainMenu)) startUp
+    "
+
+    <resource: #menu>
+
+    ^ 
+     #(Menu
+        (
+         (MenuItem
+            label: 'File'
+            translateLabel: true
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  label: 'New'
+                  itemValue: menuNew
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  label: 'Open Resource File...'
+                  itemValue: menuOpen
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  label: 'Save Resource File'
+                  itemValue: menuSave
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: 'Save Resource File As...'
+                  itemValue: menuSaveAs
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  label: 'Exit'
+                  itemValue: closeRequest
+                  translateLabel: true
+                )
+               )
+              nil
+              nil
+            )
+          )
+         (MenuItem
+            label: 'View'
+            translateLabel: true
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  label: 'Show Missing Translations Only'
+                  translateLabel: true
+                  indication: showMissingTranslationsOnly
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  label: 'Shown Languages'
+                  translateLabel: true
+                  submenuChannel: shownLanguagesMenu
+                )
+               )
+              nil
+              nil
+            )
+          )
+         (MenuItem
+            label: 'Languages'
+            translateLabel: true
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  label: 'Add Language...'
+                  itemValue: addLanguage
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: 'Remove Language...'
+                  itemValue: removeLanguage
+                  translateLabel: true
+                )
+               )
+              nil
+              nil
+            )
+          )
+         (MenuItem
+            label: 'Translations'
+            translateLabel: true
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  label: 'Add Translation...'
+                  itemValue: addTranslation
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: 'Remove Translation...'
+                  itemValue: removeTranslation
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  label: 'Extract from Class...'
+                  itemValue: extractTranslationsFromClass
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: 'Run Application and Collect Translations...'
+                  itemValue: runApplicationAndCollectTranslations
+                  translateLabel: true
+                )
+               )
+              nil
+              nil
+            )
+          )
+         (MenuItem
+            label: 'Help'
+            translateLabel: true
+            startGroup: right
+            submenu: 
+           (Menu
+              (
+               (MenuItem
+                  label: 'Documentation'
+                  itemValue: openDocumentation
+                  translateLabel: true
+                )
+               (MenuItem
+                  label: '-'
+                )
+               (MenuItem
+                  label: 'About this Application...'
+                  itemValue: openAboutThisApplication
+                  translateLabel: true
+                )
+               )
+              nil
+              nil
+            )
+          )
+         )
+        nil
+        nil
+      )
+!
+
+menuToolBar
+    "This resource specification was automatically generated
+     by the MenuEditor of ST/X."
+
+    "Do not manually edit this!! If it is corrupted,
+     the MenuEditor may not be able to read the specification."
+
+    "
+     MenuEditor new openOnClass:InternationalLanguageTranslationEditor andSelector:#menuToolBar
+     (Menu new fromLiteralArrayEncoding:(InternationalLanguageTranslationEditor menuToolBar)) startUp
+    "
+
+    <resource: #menu>
+
+    ^ 
+     #(Menu
+        (
+         (MenuItem
+            label: 'Save'
+            translateLabel: true
+            labelImage: (ResourceRetriever XPToolbarIconLibrary saveImageIcon)
+          )
+         (MenuItem
+            label: '-'
+          )
+         (MenuItem
+            label: 'Add Translation'
+            itemValue: addTranslation
+            translateLabel: true
+            labelImage: (ResourceRetriever XPToolbarIconLibrary newRowIcon)
+          )
+         (MenuItem
+            label: 'Remove Translation'
+            itemValue: removeTranslation
+            translateLabel: true
+            labelImage: (ResourceRetriever XPToolbarIconLibrary removeRowIcon)
+          )
+         (MenuItem
+            label: ''
+          )
+         (MenuItem
+            label: 'Stop Application'
+            itemValue: stopApplication
+            translateLabel: true
+            isVisible: stopApplicationIconVisibleHolder
+            labelImage: (ResourceRetriever XPToolbarIconLibrary stop16x16Icon)
+          )
+         )
+        nil
+        nil
+      )
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'aspects'!
+
+keyStringAndLanguageSelectionTable
+    keyStringsToLanguageMappings isNil ifTrue:[
+        keyStringsToLanguageMappings := KeyStringsToLanguageMappings new.
+    ].
+    ^ keyStringsToLanguageMappings.
+!
+
+keyStringAndLanguageSelectionTableColumnDescriptionHolder
+    keyStringAndLanguageSelectionTableColumnDescriptionHolder isNil ifTrue:[
+        keyStringAndLanguageSelectionTableColumnDescriptionHolder := ValueHolder new.
+        keyStringAndLanguageSelectionTableColumnDescriptionHolder value:(self keyStringAndLanguageSelectionTableColumnDescription).
+    ].
+    ^ keyStringAndLanguageSelectionTableColumnDescriptionHolder.
+!
+
+languageAtCol:colNr
+    ^ shownLanguages at:colNr-1.
+!
+
+languageShownHolderFor:lang
+    |holder|
+
+    languageShownHolders isNil ifTrue:[
+        languageShownHolders := Dictionary new
+    ].
+    holder := languageShownHolders 
+                at:lang 
+                ifAbsentPut:[ 
+                    |h|
+
+                    h := true asValue.
+                    h onChangeEvaluate:[ 
+                        h value ifTrue:[
+                           self enableLanguageDisplayFor:lang     
+                        ] ifFalse:[
+                           self disableLanguageDisplayFor:lang     
+                        ].
+                    ].
+                    h
+                ].
+    ^ holder.
+!
+
+languageTextHolder
+    languageTextHolder isNil ifTrue:[
+        languageTextHolder := '' asValue.
+    ].
+    ^ languageTextHolder.
+!
+
+languageTextModifiedHolder
+    languageTextModifiedHolder isNil ifTrue:[
+        languageTextModifiedHolder := false asValue.
+    ].
+    ^ languageTextModifiedHolder.
+!
+
+languagesList
+    languagesList isNil ifTrue:[
+        languagesList := List withAll:(self keyStringAndLanguageSelectionTable languages).
+    ].
+    ^ languagesList.
+!
+
+originalTextHolder
+    originalTextHolder isNil ifTrue:[
+        originalTextHolder := '' asValue.
+    ].
+    ^ originalTextHolder.
+!
+
+originalTextModifiedHolder
+    originalTextModifiedHolder isNil ifTrue:[
+        originalTextModifiedHolder := false asValue.
+    ].
+    ^ originalTextModifiedHolder.
+!
+
+selectedKeyRow
+    selectedKeyRow isNil ifTrue:[
+        selectedKeyRow := ValueHolder new.
+        selectedKeyRow onChangeSend:#selectionChanged to:self.
+    ].
+    ^ selectedKeyRow.
+!
+
+showMissingTranslationsOnly
+    showMissingTranslationsOnly isNil ifTrue:[
+        showMissingTranslationsOnly := false asValue.
+    ].
+    ^ showMissingTranslationsOnly.
+!
+
+shownLanguages
+    shownLanguages isNil ifTrue:[
+        shownLanguages := List new.
+        shownLanguages addAll:(self languagesList).
+        shownLanguages onChangeSend:#shownLanguagesChanged to:self.
+    ].
+    ^ shownLanguages.
+!
+
+stopApplicationIconVisibleHolder
+    stopApplicationIconVisibleHolder isNil ifTrue:[
+        stopApplicationIconVisibleHolder := false asValue.
+    ].
+    ^ stopApplicationIconVisibleHolder.
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'initialization & release'!
+
+closeRequest
+    "asks for permission before closing"
+
+    self hasUnsavedChanges ifTrue:[
+        (Dialog 
+            confirm:(resources string:'Close without saving ?')
+            default:false) ifFalse:[
+            ^ self
+        ]
+    ].
+
+    super closeRequest
+!
+
+initialize
+    modified := false.
+    super initialize
+!
+
+postBuildDataSet:aView
+    dataSetView := aView
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'menu actions'!
+
+addLanguage
+    |lang|
+
+    lang := Dialog request:'New language (ISO symbol):'.
+    lang isEmptyOrNil ifTrue:[^ self].
+
+    (keyStringsToLanguageMappings includesLanguage:lang) ifTrue:[^ self ].
+
+    keyStringsToLanguageMappings addLanguage:lang.
+    self languagesList add:lang.
+    self shownLanguages add:lang.
+    modified := true.
+!
+
+addTranslation
+    |key index|
+
+    key := Dialog request:'New Key:'.
+    (keyStringsToLanguageMappings includesKey:key) ifFalse:[
+        keyStringsToLanguageMappings addKey:key.
+        modified := true.
+    ].
+    index := keyStringsToLanguageMappings indexOfKey:key.
+
+    self selectedKeyRow value:index
+!
+
+extractTranslationsFromClass
+    |aClass newTranslations|
+
+    aClass := Dialog 
+                requestClass:'Class to extract translations from:'
+                okLabel:'OK' 
+                initialAnswer:(lastExtractedClass ? LastExtractedClass).
+    aClass isNil ifTrue:[^ self ].
+
+    lastExtractedClass := LastExtractedClass := aClass.
+
+    newTranslations := Set new.
+
+    self withWaitCursorDo:[
+        "/ now, the hard part:
+        "/ possible translations are in the specs,
+        "/ and all arguments to (resources string:) messages.
+        aClass instAndClassMethodsDo:[:eachMethod |
+            newTranslations addAll:( self extractTranslationsFromMethod:eachMethod ).
+        ].
+    ].
+
+    newTranslations := newTranslations select:[:k | k notEmpty and:[k isBlank not]].
+    newTranslations := newTranslations select:[:k | (keyStringsToLanguageMappings includesKey:k) not].
+    newTranslations notEmpty ifTrue:[
+        newTranslations := newTranslations asSortedCollection.
+        keyStringsToLanguageMappings addKeys:newTranslations.
+        modified := true.
+    ].
+!
+
+menuNew
+    modified ifTrue:[
+        (Dialog 
+            confirm:(resources stringWithCRs:'Modified translations have not been changed.\\Create new translations set anyway ?')
+            default:false) ifFalse:[^ self].
+    ].
+
+    keyStringsToLanguageMappings initialize.
+    keyStringsToLanguageMappings changed.
+
+    self languagesList contents:self keyStringAndLanguageSelectionTable languages.
+    self shownLanguages contents:self keyStringAndLanguageSelectionTable languages.
+    modified := false.
+!
+
+menuOpen
+    |fn|
+
+    fn := Dialog 
+        requestFileName:'Name of resource file:'
+        default:nil 
+        pattern:'*.rs'.
+
+    fn isEmptyOrNil ifTrue:[^ self ].
+    fn := fn asFilename.
+    fn exists ifFalse:[^ self ].
+
+    self readResourceFile:fn
+!
+
+menuSave
+    "This method was generated by the Browser.
+     It will be invoked when the menu-item 'save' is selected."
+
+    "/ change below and add any actions as required here ...
+    self warn:'no action for ''save'' available.'.
+!
+
+menuSaveAs
+    "This method was generated by the Browser.
+     It will be invoked when the menu-item 'saveAs' is selected."
+
+    "/ change below and add any actions as required here ...
+    self warn:'no action for ''saveAs'' available.'.
+!
+
+openAboutThisApplication
+    "This method was generated by the Browser.
+     It will be invoked when the menu-item 'help-about' is selected."
+
+    "/ could open a customized aboutBox here ...
+    super openAboutThisApplication
+!
+
+openDocumentation
+    "This method was generated by the Browser.
+     It will be invoked when the menu-item 'help-documentation' is selected."
+
+    "/ change below as required ...
+
+    "/ to open an HTML viewer on some document (under 'doc/online/<language>/' ):
+    HTMLDocumentView openFullOnDocumentationFile:'TOP.html'.
+
+    "/ add application-specific help files under the 'doc/online/<language>/help/appName'
+    "/ directory, and open a viewer with:
+    "/ HTMLDocumentView openFullOnDocumentationFile:'help/<MyApplication>/TOP.html'.
+!
+
+readResourceFile:aFilename
+    |inStream lineString|
+
+    aFilename exists ifFalse:[^ self ].
+
+    inStream := aFilename readStream.
+    [inStream atEnd] whileFalse:[
+        lineString := inStream nextLine.
+        (lineString notEmpty 
+        and:[ (lineString startsWith:';') not ]) ifTrue:[
+            (lineString startsWith:'#encoding ') ifTrue:[
+self halt.
+            ] ifFalse:[
+self halt.
+            ].
+        ].
+    ].
+    inStream close.
+!
+
+removeLanguage
+    "automatically generated by UIEditor ..."
+
+    "*** the code below performs no action"
+    "*** (except for some feedback on the Transcript)"
+    "*** Please change as required and accept in the browser."
+    "*** (and replace this comment by something more useful ;-)"
+
+    "action to be added ..."
+
+    Transcript showCR:self class name, ': action for #removeLanguage ...'.
+!
+
+removeTranslation
+    |rowSelectionIndex selectedKey|
+
+    rowSelectionIndex := self selectedKeyRow value.
+    rowSelectionIndex ~~ 0 ifTrue:[
+        selectedKey := keyStringsToLanguageMappings keyAt:rowSelectionIndex ifAbsent:nil.
+        (Dialog confirm:(resources string:'Really remove key %1' with:selectedKey)) ifTrue:[
+            keyStringsToLanguageMappings removeKey:selectedKey.
+        ].
+    ].
+!
+
+runApplicationAndCollectTranslations
+    |applicationClass newTranslations pseudoPack app|
+
+    monitoredApplication notNil ifTrue:[
+        monitoredApplication terminate.
+        [monitoredApplication notNil] whileTrue:[
+            Delay waitForSeconds:0.1
+        ].
+    ].
+
+    applicationClass := Dialog 
+                requestClass:'Application class to start and collect translations from:'
+                okLabel:'OK' 
+                initialAnswer:(lastExtractedApplicationClass ? LastExtractedApplicationClass ).
+    applicationClass isNil ifTrue:[^ self ].
+
+    lastExtractedApplicationClass := LastExtractedApplicationClass := applicationClass.
+
+    newTranslations := Set new.
+
+    pseudoPack := AccessCollectingPseudoResourcePack new.
+    pseudoPack realResourcePack:(applicationClass classResources).
+
+    self stopApplicationIconVisibleHolder value:true.
+
+    monitoredApplication := [
+        [
+            app := applicationClass new.
+            app open.
+            app window waitUntilVisible.
+            app window waitUntilClosed.
+        ] ensure:[
+            app closeRequest.
+            self stopApplicationIconVisibleHolder value:false.
+            monitoredApplication := nil.
+        ].
+    ] fork.
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'menus dynamic'!
+
+disableLanguageDisplayFor:lang
+    self shownLanguages remove:lang ifAbsent:[]
+!
+
+enableLanguageDisplayFor:newLang
+    |shownLanguagesInOrder|
+
+    (self shownLanguages includes:newLang) ifFalse:[
+        shownLanguagesInOrder := self languagesList
+                                    select:[:lang | (self shownLanguages includes:lang)
+                                                    or:[ lang = newLang ]].
+        self shownLanguages contents:shownLanguagesInOrder.
+    ]
+!
+
+isLanguageShown:lang
+    ^ self shownLanguages includes:lang
+!
+
+shownLanguagesMenu
+    <resource: #programMenu >
+
+    ^ [
+        |m selected|
+
+        m := Menu new.
+
+        self languagesList do:[:lang |
+            |item|
+
+            item := MenuItem label:lang.
+            item indication:(self languageShownHolderFor:lang).
+            item hideMenuOnActivated:false.    
+            m addItem:item.
+        ].
+        m
+    ].
+!
+
+toggleLanguageDisplayFor:lang
+    (self shownLanguages includes:lang) ifTrue:[
+        self disableLanguageDisplayFor:lang
+    ] ifFalse:[
+        self enableLanguageDisplayFor:lang
+    ].
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'private-key extraction'!
+
+extractTranslationsFromHelpSpecMethod:aMethod
+    |codeStrings matcher parseTree resourceKeys|
+
+    parseTree := RBParser 
+            parseMethod:aMethod source 
+            onError: [:str :pos | Transcript showCR:str. Transcript showCR:pos.
+                                  nil].
+    parseTree isNil ifTrue:[^ #() ].
+
+    codeStrings  := 
+        #(
+                '`@dict addPairsFrom: `#helpKeysAndStrings'
+        ).
+
+    resourceKeys := Set new.
+
+    matcher := ParseTreeSearcher new.
+    matcher 
+        matchesAnyOf: codeStrings 
+        do: [:aNode :answer |
+                |sel argNode arg|
+
+                sel := aNode selector.
+                (sel startsWith:'addPairsFrom:') ifTrue:[
+                    argNode := aNode arguments at:1.
+                    argNode isLiteral ifTrue:[
+                        arg := argNode value.
+                        arg isArray ifTrue:[
+                            arg doWithIndex:[:el :index |
+                                index even ifTrue:[
+                                    el isString ifTrue:[    
+                                        resourceKeys add:el.
+                                    ]
+                                ].
+                            ].
+                        ] ifFalse:[
+                            Transcript 
+                                showCR:(resources 
+                                        string:'Cannot derive resourceKey from non-array in %1 in %2'
+                                        with:argNode formattedCode
+                                        with:aMethod selector).
+                        ].
+                    ] ifFalse:[
+                        Transcript 
+                            showCR:(resources 
+                                        string:'Cannot derive resourceKey from non-literal: %1 in %2'
+                                        with:argNode formattedCode
+                                        with:aMethod selector).
+                    ].
+                ].
+                aNode
+            ].
+
+    matcher executeTree: parseTree initialAnswer: nil.
+    ^ resourceKeys
+!
+
+extractTranslationsFromMenuSpecMethod:aMethod
+    |menu resourceKeys|
+
+    menu := aMethod mclass theNonMetaclass perform:aMethod selector.
+    menu isNil ifTrue:[ ^ #() ].
+
+    (menu isKindOf:Menu) ifFalse:[
+        menu := Menu new fromLiteralArrayEncoding:menu
+    ].
+
+    resourceKeys := Set new.
+    menu allItemsDo:[:aMenuItem |
+        aMenuItem translateLabel ifTrue:[
+            aMenuItem isSeparatorItem ifFalse:[
+                resourceKeys add:aMenuItem label.
+            ]
+        ]
+    ].
+    ^ resourceKeys
+!
+
+extractTranslationsFromMethod:aMethod
+    |mResources|
+
+    mResources := aMethod resources.
+    mResources notNil ifTrue:[
+        (mResources includesKey:#menu) ifTrue:[
+             ^ self extractTranslationsFromMenuSpecMethod:aMethod.
+        ].
+        (mResources includesKey:#canvas) ifTrue:[
+             ^ self extractTranslationsFromUISpecMethod:aMethod.
+        ].
+        (mResources includesKey:#help) ifTrue:[
+             ^ self extractTranslationsFromHelpSpecMethod:aMethod.
+        ].
+        (mResources includesKey:#tableColumns) ifTrue:[
+             ^ self extractTranslationsFromTableColumnsSpecMethod:aMethod.
+        ].
+
+        ^ self extractTranslationsFromSpecMethod:aMethod.
+    ].
+    ^ self extractTranslationsFromMethodsCode:aMethod
+!
+
+extractTranslationsFromMethodsCode:aMethod
+"/method:mthd selector:sel inClass:cls matchesParseTreeMatcher:aMatcher
+    |codeStrings matcher parseTree resourceKeys|
+
+    parseTree := RBParser 
+            parseMethod:aMethod source 
+            onError: [:str :pos | Transcript showCR:str. Transcript showCR:pos.
+                                  nil].
+    parseTree isNil ifTrue:[^ #() ].
+
+    codeStrings  := 
+        #(
+                'resources `@msg: `@args'
+                'self resources `@msg: `@args'
+                'self class resources `@msg: `@args'
+                'self classResources `@msg: `@args'
+        ).
+
+    resourceKeys := Set new.
+
+    matcher := ParseTreeSearcher new.
+    matcher 
+        matchesAnyOf: codeStrings 
+        do: [:aNode :answer |
+                |sel keyStringArgNode keyStringArg|
+
+                sel := aNode selector.
+                ((sel startsWith:'string:') or:[(sel startsWith:'at:')]) ifTrue:[
+                    keyStringArgNode := aNode arguments at:1.
+                    keyStringArgNode isLiteral ifTrue:[
+                        keyStringArg := keyStringArgNode value.
+                        keyStringArg isString ifTrue:[
+                            resourceKeys add:keyStringArg.
+                        ] ifFalse:[
+                            Transcript 
+                                showCR:(resources 
+                                        string:'Cannot derive resourceKey from non-string: %1 in %2'
+                                        with:keyStringArgNode formattedCode
+                                        with:aMethod selector).
+                        ].
+                    ] ifFalse:[
+                        Transcript 
+                            showCR:(resources 
+                                        string:'Cannot derive resourceKey from non-literal: %1 in %2'
+                                        with:keyStringArgNode formattedCode
+                                        with:aMethod selector).
+                    ].
+                ].
+                aNode
+            ].
+
+    matcher executeTree: parseTree initialAnswer: nil.
+    ^ resourceKeys
+!
+
+extractTranslationsFromSpecMethod:aMethod
+    ^ #()
+!
+
+extractTranslationsFromTableColumnsSpecMethod:aMethod
+    |columnDescription resourceKeys|
+
+    columnDescription := aMethod mclass theNonMetaclass perform:aMethod selector.
+    columnDescription isNil ifTrue:[ ^ #() ].
+
+    (columnDescription first isKindOf:DataSetColumnSpec) ifFalse:[
+        columnDescription := columnDescription collect:[:el | DataSetColumnSpec new fromLiteralArrayEncoding:el].
+    ].
+
+    resourceKeys := Set new.
+    columnDescription do:[:aColumnSpec |
+        aColumnSpec translateLabel ifTrue:[
+            resourceKeys add:aColumnSpec label.
+        ]
+    ].
+    ^ resourceKeys
+!
+
+extractTranslationsFromUISpecMethod:aMethod
+    |spec resourceKeys visitor|
+
+    spec := aMethod mclass theNonMetaclass perform:aMethod selector.
+    spec isNil ifTrue:[ ^ #() ].
+
+    (spec isKindOf:UISpecification) ifFalse:[
+        spec := UISpecification from:spec
+    ].
+
+    resourceKeys := Set new.
+
+    visitor := UISpecVisitor new.
+    spec acceptVisitor:visitor.
+
+    ^ visitor translatedLabels
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'queries'!
+
+hasUnsavedChanges
+    ^ modified
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'specs-dynamic'!
+
+columnInRow:row at:colIndex
+    |lang|
+
+    lang := self languageAtCol:colIndex.
+    ^ row atLanguage:lang
+!
+
+getBackgroundForRow:row rowNr:rowNr col:colIndex
+    |lang|
+
+    colIndex == 1 ifTrue:[^ nil].
+    lang := self languageAtCol:colIndex.
+    ^ (row atLanguage:lang) isNil ifTrue:[Color red lightened] ifFalse:nil
+!
+
+keyStringAndLanguageSelectionTableColumnDescription
+    |spec|
+
+    spec := OrderedCollection new.
+
+    spec add:
+      #(DataSetColumnSpec
+         label: 'Key'
+         labelAlignment: center
+         labelButtonType: Button
+         minWidth: 50
+         model: keyStringInRow:
+         isResizeable: true
+         canSelect: true
+         showRowSeparator: true
+         showColSeparator: true
+       ).
+
+    self shownLanguages do:[:lang |
+        |entry|
+
+        entry :=
+              #(DataSetColumnSpec
+                 label: lang
+                 labelAlignment: center
+                 labelButtonType: Button
+                 minWidth: 50
+                 model: #columnInRow:at:
+                 canSelect: true
+                 isResizeable: true
+                 showRowSeparator: true
+                 showColSeparator: true
+                 backgroundSelector: #getBackgroundForRow:rowNr:col:
+               ).
+        entry := entry copy.
+        entry replaceAll:#lang with:lang.
+        spec add: entry 
+    ].
+    ^ spec
+!
+
+keyStringInRow:row
+    ^ row keyString
+! !
+
+!InternationalLanguageTranslationEditor methodsFor:'user actions'!
+
+selectionChanged
+    |rowSelectionIndex colSelectionIndex language selectedKey originalText languageText
+     answer|
+
+    colSelectionIndex := dataSetView selectedColIndex.
+    language := colSelectionIndex > 1 ifTrue:[ shownLanguages at:colSelectionIndex-1 ] ifFalse:nil.
+
+    rowSelectionIndex := self selectedKeyRow value.
+    rowSelectionIndex ~~ 0 ifTrue:[
+        selectedKey := keyStringsToLanguageMappings keyAt:rowSelectionIndex.
+        originalText := selectedKey storeString.
+        language notNil ifTrue:[
+            languageText := keyStringsToLanguageMappings at:selectedKey language:language.
+            languageText notNil ifTrue:[
+                languageText := languageText storeString.
+            ]
+        ]
+    ].
+
+    self originalTextModifiedHolder value ifTrue:[ 
+        answer := OptionBox 
+                      request:'Accept changed original text (key) ?' 
+                      label:'Original text (key) changed'
+                      image:(WarningBox iconBitmap)
+                      buttonLabels:#('Cancel' 'Accept' 'Accept As New')
+                      values:#(nil #accept #acceptAsNew)
+                      default:#acceptAsNew.
+
+        answer isNil ifTrue:[^ self ].
+        answer == #accept ifTrue:[
+self halt.        ].
+        answer == #acceptAsNew ifTrue:[
+self halt.        ].
+    ].
+    self languageTextModifiedHolder value ifTrue:[ self halt.
+        (Dialog confirm:'Accept changed translation ?') ifTrue:[
+            self halt.
+        ]
+    ].
+
+    self originalTextHolder value:originalText.
+    self languageTextHolder value:languageText.
+
+    self originalTextModifiedHolder value:false.
+    self languageTextModifiedHolder value:false.
+!
+
+shownLanguagesChanged
+    self keyStringAndLanguageSelectionTableColumnDescriptionHolder 
+        value:(self keyStringAndLanguageSelectionTableColumnDescription).
+!
+
+stopApplication
+    |p|
+
+    (p := monitoredApplication) notNil ifTrue:[
+        p terminate.
+    ].
+! !
+
+!InternationalLanguageTranslationEditor::AccessCollectingPseudoResourcePack methodsFor:'accessing'!
+
+realResourcePack:something
+    realResourcePack := something.
+! !
+
+!InternationalLanguageTranslationEditor::KeyStringsToLanguageMappings class methodsFor:'instance creation'!
+
+new
+    ^ self basicNew initialize
+! !
+
+!InternationalLanguageTranslationEditor::KeyStringsToLanguageMappings methodsFor:'accessing'!
+
+addKey:aKey
+    keys add:aKey.
+    self changed.
+!
+
+addKeys:aCollectionOfKey
+    keys addAll:aCollectionOfKey.
+    self changed.
+!
+
+addLanguage:lang
+    languages add:lang.
+    languageMappings at:lang put:(Dictionary new).
+!
+
+at:aKey language:language
+    ^ (languageMappings at:language) at:aKey ifAbsent:nil
+!
+
+at:aKey language:language put:value
+    self addKey:aKey.
+    ^ (languageMappings at:language) at:aKey put:value
+!
+
+keyAt:index
+    ^ keys at:index.
+!
+
+keyAt:index ifAbsent:exceptionalValue
+    ^ keys at:index ifAbsent:exceptionalValue.
+!
+
+keys
+    ^ keys
+!
+
+languages
+    ^ languages
+!
+
+removeKey:aKey
+    keys remove:aKey ifAbsent:[].
+    languageMappings do:[:eachMapping |
+        eachMapping removeKey:aKey ifAbsent:[].
+    ].
+    self changed.
+!
+
+size
+    ^ keys size
+! !
+
+!InternationalLanguageTranslationEditor::KeyStringsToLanguageMappings methodsFor:'enumerating'!
+
+do:aBlock
+    keys do:[:eachKey |
+        aBlock value:(InternationalLanguageTranslationEditor::LanguageMappingRow new 
+                                key:eachKey; mappings:self).
+    ].
+! !
+
+!InternationalLanguageTranslationEditor::KeyStringsToLanguageMappings methodsFor:'initialization'!
+
+initialize
+    super initialize.
+
+    keys := OrderedSet new.
+    languages := OrderedCollection new.
+    languageMappings := Dictionary new.
+
+    keys add:'open'.
+    keys add:'close'.
+    keys add:'yes'.
+    keys add:'no'.
+
+    self addLanguage:#'de'.
+    self addLanguage:#'fr'.
+
+    self at:'open' language:#'de' put:'öffnen'.    
+    self at:'close' language:#'de' put:'schliessen'.    
+    self at:'yes' language:#'de' put:'ja'.    
+    self at:'no' language:#'de' put:'nein'.    
+    self at:'cancel' language:#'de' put:'abbrechen'.    
+
+    self at:'open' language:#'fr' put:'ouvrir'.    
+    self at:'close' language:#'fr' put:'fermer'.    
+    self at:'yes' language:#'fr' put:'oui'.    
+    self at:'no' language:#'fr' put:'non'.    
+! !
+
+!InternationalLanguageTranslationEditor::KeyStringsToLanguageMappings methodsFor:'private'!
+
+atLanguage:lang
+    ^ languageMappings at:lang
+!
+
+atLanguageIndex:idx
+    |lang|
+
+    lang := languages at:idx.
+    ^ self atLanguage:lang.
+! !
+
+!InternationalLanguageTranslationEditor::KeyStringsToLanguageMappings methodsFor:'queries'!
+
+includesKey:aKey
+    ^ keys includes:aKey
+!
+
+includesLanguage:lang
+    ^ languages includes:lang
+!
+
+indexOfKey:aKey
+    ^ keys indexOf:aKey
+! !
+
+!InternationalLanguageTranslationEditor::LanguageMappingRow methodsFor:'accessing'!
+
+atLanguage:language
+    ^ (mappings atLanguage:language) at: key ifAbsent:nil
+!
+
+columnAt:columnNr
+    ^ (mappings atLanguageIndex:columnNr-1) at: key ifAbsent:nil
+!
+
+key:something
+    key := something.
+!
+
+keyString
+    ^ key
+!
+
+mappings:something
+    mappings := something.
+! !
+
+!InternationalLanguageTranslationEditor::UISpecVisitor methodsFor:'accessing'!
+
+translatedLabels
+    ^ translatedLabels ? #()
+! !
+
+!InternationalLanguageTranslationEditor::UISpecVisitor methodsFor:'visiting'!
+
+visitObject:anObject with:aParameter
+    (anObject isKindOf:UISpecification) ifTrue:[
+        (anObject respondsTo:#translateLabel) ifTrue:[
+            anObject translateLabel == true ifTrue:[
+                translatedLabels isNil ifTrue:[
+                    translatedLabels := Set new.
+                ].
+                translatedLabels add:anObject label.
+            ].
+        ].
+    ].
+    self visitChildrenOf:anObject.
+! !
+
+!InternationalLanguageTranslationEditor class methodsFor:'documentation'!
+
+version
+    ^ '$Header$'
+! !