{"id":16086672,"url":"https://github.com/astares/pharo-i18n","last_synced_at":"2025-03-18T06:30:42.471Z","repository":{"id":78113782,"uuid":"257860676","full_name":"astares/Pharo-I18N","owner":"astares","description":"Simple I18N ","archived":false,"fork":false,"pushed_at":"2025-01-20T12:16:34.000Z","size":80,"stargazers_count":6,"open_issues_count":0,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-02-28T07:49:29.434Z","etag":null,"topics":["pharo"],"latest_commit_sha":null,"homepage":null,"language":"Smalltalk","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/astares.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-22T09:57:34.000Z","updated_at":"2025-01-20T12:16:36.000Z","dependencies_parsed_at":"2024-10-27T17:23:15.489Z","dependency_job_id":"f18fe886-58b6-475a-9a60-792876aca57d","html_url":"https://github.com/astares/Pharo-I18N","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astares%2FPharo-I18N","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astares%2FPharo-I18N/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astares%2FPharo-I18N/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/astares%2FPharo-I18N/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/astares","download_url":"https://codeload.github.com/astares/Pharo-I18N/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243906382,"owners_count":20367018,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pharo"],"created_at":"2024-10-09T13:24:51.440Z","updated_at":"2025-03-18T06:30:42.465Z","avatar_url":"https://github.com/astares.png","language":"Smalltalk","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pharo-I18N\n\n**Internationalization support for applications written in [Pharo][1]** (for instance [Seaside][2] applications). It allows for simple handling of multiple translations of an app without a dependency to other external frameworks (like GetText, ...).\n\n[![Pharo](https://img.shields.io/static/v1?style=for-the-badge\u0026message=Pharo\u0026color=3297d4\u0026logo=Harbor\u0026logoColor=FFFFFF\u0026label=)](https://www.pharo.org) \n\n[![Unit Tests](https://github.com/astares/Pharo-I18N/actions/workflows/build.yml/badge.svg)](https://github.com/astares/Pharo-I18N/actions/workflows/build.yml)\n[![Coverage Status](https://codecov.io/github/astares/Pharo-Pharo-I18N/coverage.svg?branch=master)](https://codecov.io/gh/astares/Pharo-Pharo-I18N/branch/master)\n\n[![Pharo 11](https://img.shields.io/badge/Pharo-11-%23aac9ff.svg)](https://pharo.org/download)\n[![Pharo 12](https://img.shields.io/badge/Pharo-12-%23aac9ff.svg)](https://pharo.org/download)\n\n### Project location\nThe project is located on GitHub at [https://github.com/astares/Pharo-I18N][3]\n\n### License\nThe code of I18N is under MIT License.\n\n## Installation\nYou can load the code either in Pharo easily using the given script:\n\n```Smalltalk\nMetacello new\n\tbaseline: 'I18N';\n\trepository: 'github://astares/Pharo-I18N/src';\n\tload\n```    \n    \n\n## How to use\n### The basic idea\n\nThe basic idea of the framework is instead of hardcoding texts and strings for labels, menu items or other in an application just provide a unique message key. Depending on a translator object this message key resolves to a translation in the specific natural language. So for instance the key #welcome should resolve to 'Welcome' in english, 'Willkommen' in german, 'Bienvenue' in french, ...\n\nAlso the framework should be very lean and easy to use in any application built on top of Pharo or maybe in Pharo as well.\n\n### Translations\n\nTo perform it's tasks the framework includes a simple class called **\"I18NTranslator\"** that allows you to manage translators with translations in different languages. \n\nAs the english language is often used to develop by default an english translator is available:\n\n```Smalltalk\nI18NTranslator defaultTranslator inspect\n```\n\nIn the inspector you will see that a translator is responsible for a language (using the ISO code) and manages a translation map.\n\nTo access all the translators use\n\n```Smalltalk\nI18NTranslator translators inspect\n```\n\nSo far we have only one translator who can also be accessed using:\n\n```Smalltalk\nI18NTranslator forLanguage: 'EN'\n```    \n## Adding translations\n\n### Manually adding translations\n\nAny translator has a translation map that has to be filled with translations in advance. We do this by using a unique message key (a symbol) and provide the translation:\n\n```Smalltalk\n|translator|\ntranslator := I18NTranslator defaultTranslator.\ntranslator translationMap at: #welcome put: 'Welcome'\n```\nSo \n\n```Smalltalk\n(I18NTranslator forLanguage: 'EN') translationMap isEmpty\n```\n\nshould return *false* as we have added our first translation for the english language.\n\n### Retrieving the translation again\n\nIf you have access to the translator the you can retrieve the given translation by just sending a message:\n\n```Smalltalk\nI18NTranslator defaultTranslator welcome\n```\n\nThis should return the 'Welcome' string as the translator we use currently cares about the english language.\n\n*If you look at the implementation of class I18NTranslator you will see that this is handles by overriding #doesNotUnderstand: message. If there is a translation that matches the given selector then the translated string is returned, otherwise the message is handled as a usual Does Not Understand (DNU) message.*  \n\nSo far that was not very interesting, but let's move on:\n\n### Translation viewer\nAfter loading the framework you will find a tool called \"Translation viewer\" in the world menu. Just go the \"Browse\" menu item and check the \"I18N Translations\" menu item. When you open the tool right after loading you will notice that the translations are empty.\n\nIf we add a translation and open the tool we will see our entry for the english language:\n\n```Smalltalk\n|englishTranslator|\nenglishTranslator := I18NTranslator defaultTranslator.\nenglishTranslator translationMap at: #welcome put: 'Welcome'.\nI18NTranslationViewerApplication open\n```\n\n### Adding more languages/translators\n\nNow lets add another translator for a different language, we do this by instantiating a new translator object and registering it as a translator:\n\n```Smalltalk\n|germanTranslator|\ngermanTranslator := (I18NTranslator newForLanguage: 'DE').\nI18NTranslator addTranslator: germanTranslator.\n```\n    \nTo check if our translator is now a registered translator we can use the following expression:\n\n```Smalltalk\nI18NTranslator translators inspect\n```\n\nNow two translators are registered with the framework - one for the english and one for german language. Lets open the translation viewer again:\n\n```Smalltalk    \nI18NTranslationViewerApplication open\n```\n\nYou will now notice that the viewer has a new column for the german language. But the entry for our welcome text is still set to nil.\n\nSo we have to make it complete and provide also a german translation for the same message key #welcome:\n\n```Smalltalk\n|germanTranslator|\ngermanTranslator := I18NTranslator forLanguage: 'DE'.\ngermanTranslator translationMap at: #welcome put: 'Willkommen'.\n```\n\nIf you now open the translation viewer you will see both translations: 'Welcome' for the english language and 'Willkommen' for german language registered with the given message key. So depending on the translator that one asks a different String is returned:\n\n```Smalltalk\n(I18NTranslator forLanguage: 'DE') welcome.\n```\n\nreturns 'Willkommen' and \n\n```Smalltalk\n(I18NTranslator forLanguage: 'EN') welcome.\n```\n\nreturns 'Welcome'.\n\nIf we like we can easily add other languages like french:\n\n```Smalltalk\n(I18NTranslator forLanguage: 'FR') translationMap at: #welcome put: 'Bienvenue'.\n```\n\n### Using the framework in an application\n\nSo the typical use case in an application is to:\n\n1. Load necessary translation from an external source (CSV, Database, ...) into the image\n2. Do not harcode strings but access them via a translator object (for instance via a global singleton that can be switched for your application).\n4. Allow you user to switch between translators in your application\n\n### Using the framework in a Seaside application\n\nFor instance in Seaside web application you could easily provide a custom session class holding the current translator:\n\n```Smalltalk\nWAExpirySession subclass: #MyAppSessionWithTranslation\n  instanceVariableNames: 'translator'\n\tclassVariableNames: ''\n\tcategory: 'MyApp-Web-Lifetime'\n```\n\nAlso provide a getter:\n\n```Smalltalk\ntranslator \n\t\"Return the translator of the session - by default the english one\"\n\t\n\t    translator ifNil: [ translator := I18NTranslator defaultTranslator ].\n\t    ^translator\n```\n\nIn your web component class (typically a subclass of WAComponent) you can then access the translator easily like this:\n\n```Smalltalk\n    translator\n    \t^self session translator\n```\n    \t\nTo provide translated texts (instead of hardcoded strings) easily rewrite your render methods like this:\n\n```Smalltalk\n    renderContentOn: html\n    \n        html paragraph: self translator welcome\n```\n\nCheck out the Lighthouse sample application for more details.\n\n### Tips and Tricks\n\nAn easy way of storing and managing translations is by combining the \"I18N\" project with the \"[INIFile][6]\" project. Although INI files are usually known from Windows operating system they can be used on other operating systems as well.\n\nThe interesting thing is that we can use the structure of an INI file very easily to manage our I18N requirements as languages can be mapped to INI file sections and key value pairs to the translations. We can also either have the translations internal to the Pharo image (so they can not be changed) or externally in a file and can also be corrected by the user of an application. \n\nLet's try with an example:\n\nFirst load the project \"INIFile\" from the Pharo config browser. Now add a simple translation class:\n\n```Smalltalk\nObject subclass: #MyAppTranslations\n    instanceVariableNames: ''\n    classVariableNames: ''\n    category: 'MyApp-Core-I18N'\n```\n\nOn the class side of the new class implement the following class method:\n\n```Smalltalk\n    initFromStream: aStream\n\n\t    |iniFile|\n\t    iniFile := INIFile readFrom: aStream.\n\t    iniFile sectionsDo: [:section |\n    \t\t|translator|\n    \t\ttranslator := I18NTranslator newForLanguage: section label.\n    \t\tsection associationsDo: [:each |\n    \t\t\ttranslator translationMap at: each key asSymbol put: each value ].\t \n\t\t    I18NTranslator addTranslator: translator.\t\t\n\t    ]\n```\n\nand the class side #initialize method:\n\n```Smalltalk\n    initialize\n\t    self initFromStream: self comment readStream\n```\n\t\nThe first method will read the translations from a given stream that is structured like an INI file (with sections and entries). And the clas initialize method will use the class comment of our **MyAppTranslations** class as the source for the translations. \n\nThis way the translations will be initialized as soon as the class loads into the Pharo image and we can easily add the folling translations as a class comment in our MyAppTranslations class.  \n\n```\n    [EN]\n    learnMore=Learn more\n    login=Login\n    loginTo=Sign in to {1}\n    welcome=Welcome\n    welcomeTo=Welcome to {1}\n    \n    [DE]\n    learnMore=Erfahren Sie mehr\n    login=Anmeldung\n    loginTo={1} Anmeldung\n    welcome=Willkommen\n    welcomeTo=Willkommen bei {1}\n\n    [FR]\n    learnMore=En savoir plus\n    login=Entrer\n    loginTo=Connecter à {1}\n    welcome=Accueil\n    welcomeTo=Bienvenue sur le {1}\n\n    [ES]\n    learnMore=Obtenga más información\n    login=Aplicación\n    loginTo=Iniciar sesión en {1}\n    welcome=Bienvenido\n    welcomeTo=Bienvenido al {1}\n```\n\nDo not forget to call the #initialize method to (re)initialize the translations:\n\n```Smalltalk\n    MyAppTranslations initialize\n```\n\nAlso check the translations in the translation viewer.\n\n```Smalltalk\n    I18NTranslationViewerApplication open\n\n```\n\nAs before we can access the translations:\n\n```Smalltalk\n    I18NTranslator defaultTranslator welcomeTo format: #('Pharo')\n```\n\nshould return 'Welcome to Pharo' while \n\n```Smalltalk\n    (I18NTranslator forLanguage: 'FR') welcomeTo format: #('Pharo')  \n```\n\nshould return the french translation: 'Bienvenue sur le Pharo'\n\nSo depending on own needs one can manage the translations externally in an INI file or directly within the image as a simple class comment. Using the later has the advantage that one can use the usual Pharo tools to checks modifications and store/version the translations together with the other application code.\n\n## Summary\n \n[I18N][7] is a framework to address the problem of translating applications and making them available to a wider audience. It is a simple \"Smalltalk only\" solution without any dependency to other external translation frameworks. One can use any source to manage the translations - an example was given for managing translations as an INIFile like structure. \n\n\n  [1]: http://www.pharo.org\n  [2]: http://www.seaside.st\n  [3]: https://github.com/astares/Pharo-I18N\n  [4]: http://smalltalkhub.com/#!/~TorstenBergmann/Lighthouse\n  [5]: https://ci.inria.fr/pharo-contribution/job/Lighthouse/\n  [6]: https://github.com/astares/Pharo-INIFile\n  [7]: https://github.com/astares/Pharo-I18N\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fastares%2Fpharo-i18n","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fastares%2Fpharo-i18n","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fastares%2Fpharo-i18n/lists"}