{"id":21067020,"url":"https://github.com/dkochmanski/translate","last_synced_at":"2026-01-31T12:02:11.338Z","repository":{"id":83294714,"uuid":"42645818","full_name":"dkochmanski/translate","owner":"dkochmanski","description":"Mirror of CL library for seamless language localization","archived":false,"fork":false,"pushed_at":"2015-09-17T08:55:59.000Z","size":124,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-13T20:02:49.679Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://gitlab.common-lisp.net/dkochmanski/translate","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dkochmanski.png","metadata":{"files":{"readme":"README.org","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-09-17T08:55:34.000Z","updated_at":"2025-03-30T08:21:42.000Z","dependencies_parsed_at":"2023-03-03T22:27:12.707Z","dependency_job_id":null,"html_url":"https://github.com/dkochmanski/translate","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dkochmanski/translate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkochmanski%2Ftranslate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkochmanski%2Ftranslate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkochmanski%2Ftranslate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkochmanski%2Ftranslate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dkochmanski","download_url":"https://codeload.github.com/dkochmanski/translate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkochmanski%2Ftranslate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28941921,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-31T11:39:38.044Z","status":"ssl_error","status_checked_at":"2026-01-31T11:39:27.765Z","response_time":128,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2024-11-19T18:02:08.117Z","updated_at":"2026-01-31T12:02:11.320Z","avatar_url":"https://github.com/dkochmanski.png","language":"Common Lisp","readme":"\n* Introduction\n\n  =TRANSLATE= is a thin abstraction layer over the ordinary strings\n  which allows the creation of a seamless translation for your\n  project. The code is written in a plain Common Lisp without any\n  dependencies. A system definition is provided in =ASD= format,\n  although =ASDF= isn't required to run this software.\n\n  This library is licensed under =LLGPLv2=, which means that it may be\n  used in any project for any purpose although any significant\n  modifications to the library should be published. It doesn't impose\n  it's license on software depending on it.\n\n  To illustrate library usage some imaginary functions are used:\n  =MAKE-BUTTON= and =MAKE-LABEL=. The following sample code convention\n  is used:\n\n  - lines preceded with ==\u003e indicate *reader* input\n  - all other lines represent *printer* output\n\n* Description\n\n** Basic usage\n\n   The library pollutes =*READTABLE*= with dispatch macro character\n   =#t=, where \"t\" is abbreviation of \"translate\". Instead of writing\n   ordinary constant strings in his/her application, the user instead\n   precedes them with =#t=:\n\n   #+BEGIN_SRC lisp\n     ==\u003e #t\"dum dum dum, piję rum\"\n     \"la la la, I drink in the spa\"\n   #+END_SRC\n\n   If =TRANSLATE:*LANGUAGE*= is bound to =NIL= and\n   =TRANSLATE:*RESOLUTION-TIME*= to =:LOAD-TIME=, then =#t=\"hello\"\n   will resolve to the string \"hello\" and the translation will have no\n   effect. It is convenient, because programmer can put =#t=\"strings\"\n   for the future translation with no functional consequences at\n   run-time while keeping application innards visually separated from\n   the messages meant to be presented to user.\n\n   #+BEGIN_SRC lisp\n     ==\u003e (setf translate:*language* nil)\n     NIL\n\n     ==\u003e (make-button :label #t\"hello\")\n     #\u003cbutton \"hello\"\u003e\n   #+END_SRC\n\n   Binding =TRANSLATE:*LANGUAGE*= to a non-NIL value enables string\n   translation. If no translation exists it will be temporarily\n   translated to the same value enclosed in a curly brackets:\n\n   #+BEGIN_SRC lisp\n     ==\u003e (translate:define-language :pl)\n     (#\u003chash-table 00000000054323c0\u003e NIL #\u003ccompiled-function 0000000005fe08c0\u003e)\n\n     ==\u003e (setf translate:*language* :pl)\n     :PL\n\n     ==\u003e (make-label :text #t\"Greetings, programs\")\n     ;;; Warning: phrase \"Greetings, programs\" isn't defined for language :PL.\n     #\u003clabel \"{Greetings, programs}\"\u003e\n\n     ==\u003e (make-label :text #t\"Leave the grid\")\n     ;;; Warning: phrase \"Leave the grid\" isn't defined for language :PL.\n     #\u003clabel \"{Leave the grid}\"\u003e\n   #+END_SRC\n\n   Using a string that has not been translated will cause a warning at\n   resolution time and string will be added to the special list of\n   phrases not yet translated (which may be evaluated for future\n   processing, like adding the missing translations). If a language\n   put in the =TRANSLATE:*LANGUAGE*= isn't defined yet, =CERROR= will\n   be signaled with a restart allowing language creation.\n\n   To distinguish dictionaries, the predicate =EQL= is used and\n   phrases are distinguished using the predicate =EQUAL=, so the most\n   convenient is using symbols as the language designators. It also\n   implies, that phrases meant for translation are case sensitive.\n\n** Adding translation\n\n   To add translations, two interfaces are provided. The\n   =ADD-SINGLE-TRANSLATION= function takes three arguments, where the\n   first is the language for which we provide translation, the second\n   is a translated phrase and the third is an actual\n   translation. Phrase type must be a string, but the translation\n   might be any kind of object (although it is advised to use strings,\n   the user is free to shoot himself in the foot by abusing the\n   translation mechanism).\n\n   #+BEGIN_SRC lisp\n     ==\u003e (translate:add-single-translation 'pl \"hello\" \"Witaj!\")\n     ;;; Warning: Implicitly creating language PL.\n     [PL] hello -\u003e Witaj!\n     \"Witaj!\"\n\n     ==\u003e (translate:add-single-translation 'en \"hello\" \"Welcome!\")\n     ;;; Warning: Implicitly creating language EN.\n     [EN] hello -\u003e Welcome!\n     \"Welcome!\"\n\n     ==\u003e (translate:add-single-translation\n          'pl \"bang\"\n          (let ((i 1))\n            (lambda ()\n              (format nil \"bah ~A\" (incf i)))))\n     bang -\u003e #\u003cbytecompiled-closure #\u003cbytecompiled-function 00000000067d9f50\u003e\u003e (PL)\n     #\u003cbytecompiled-closure #\u003cbytecompiled-function 00000000067d9f50\u003e\u003e\n   #+END_SRC\n\n   =ADD-TRANSLATIONS= is a simple wrapper around\n   =ADD-SINGLE-TRANSLATION= that allows the translation of multiple\n   phrases in one form. The first argument is once again the\n   translation language, while all further arguments are alternately\n   phrases and translations.\n\n   #+BEGIN_SRC lisp\n     ==\u003e (translate:add-translations 'pl\n            \"header-about\"   \"O firmie\"\n            \"header-offer\"   \"Oferta\"\n            \"header-blog\"    \"Blog\"\n            \"header-pricing\" \"Cennik\"\n            \"header-contact\" \"Kontakt\")\n     [PL] header-about -\u003e O firmie\n     [PL] header-offer -\u003e Oferta\n     [PL] header-blog -\u003e Blog\n     [PL] header-pricing -\u003e Cennik\n     [PL] header-contact -\u003e Kontakt\n     T\n\n     ==\u003e (translate:add-translations 'en\n            \"header-about\"   \"About\"\n            \"header-offer\"   \"Offer\"\n            \"header-blog\"    \"Blog\"\n            \"header-pricing\" \"Prices\"\n            \"header-contact\" \"Contact\")\n     [EN] header-about -\u003e About\n     [EN] header-offer -\u003e Offer\n     [EN] header-blog -\u003e Blog\n     [EN] header-pricing -\u003e Prices\n     [EN] header-contact -\u003e Contact\n     T\n   #+END_SRC\n\n   Generally it is advised to use symbolic and meaningful names for\n   phrases to be translated, not the final phrases written in English.\n   Providing \"translation-tags\" of concise form is easier to\n   comprehend for people who will translate the application.\n\n** Interactive fixing of the missing phrases\n\n   Loading the code is enough to catch all not yet translated phrases\n   for the active language (bound to =TRANSLATE:*LANGUAGE*=) if\n   resolution is performed at load time. Otherwise, an untranslated\n   phrase is saved after it's first evaluation. To list saved phrases\n   without translations, the function =MISSING-TRANSLATIONS= is\n   available. It returns a list of the form ={((LANG (PHRASES*))*)}=.\n\n   #+BEGIN_SRC lisp\n     ==\u003e (translate:missing-translations)\n     ((PL (\"phrase-1\" \"phrase-2\" \"phrase-3\"))\n      (BG (\"phrase-1\" \"phrase-3\")))\n   #+END_SRC\n\n   Such output means, that language =PL= doesn't have translations for\n   \"phrase-1\", \"phrase-2\" and \"phrase-3\", while =BG= doesn't have\n   translations for \"phrase-1\" and \"phrase-3\". Languages which have\n   all translations are filtered and they don't appear in the result.\n\n** Different times of the resolution\n\n   The library may work in two different modes which dictate the time\n   when the actual translation is performed. Strings may be translated\n   at load-time, or at run-time.\n\n   The first approach is faster, because it doesn't require any\n   processing at run-time, while the second is much more flexible\n   allowing the change of dictionaries and translations when the\n   program is running or depending on lexically scoped value of the\n   parameter =TRANSLATE:*LANGUAGE*=.\n\n   It is important to remember that, when translations are done at\n   run-time, strings preceded by =#t= are transformed to the function\n   calls and they may work not as expected in the context where\n   enclosing macro prevents their evaluation.\n\n   #+BEGIN_SRC lisp\n     ==\u003e (setf translate:*resolution-time* :run-time)\n     :RUN-TIME\n\n     ==\u003e (setf translate:*language* :en)\n     :EN\n\n     ==\u003e (translate:add-single-translation :en \"hello\" \"Hello\")\n     [EN] hello -\u003e Hello\n\n     ==\u003e (translate:add-single-translation :pl \"hello\" \"Cześć\")\n     [PL] hello -\u003e Cześć\n\n     ==\u003e (let ((translate:*language* :en))\n           #t\"hello\")\n     \"Hello\"\n\n     ==\u003e (let ((translate:*language* :pl))\n           #t\"hello\")\n     \"Cześć\"\n\n     ==\u003e (quote #t\"hello\")\n     (TRANSLATE:TRANSLATE \"hello\")\n   #+END_SRC\n\n   When translation is performed at load-time, the translation has to\n   be present before the actual phrase is used (e.g. in a lambda\n   expression), because phrases are resolved to their translations\n   immediately. That also means that changing =TRANSLATE:*LANGUAGE*=\n   in the future won't affect translations resolved earlier.\n\n   #+BEGIN_SRC lisp\n     ==\u003e (setf translate:*resolution-time* :load-time)\n     :LOAD-TIME\n\n     ==\u003e (setf translate:*language* :en)\n     :EN\n\n     ==\u003e (defparameter *my-function-1*\n           (lambda () #t\"hello\"))\n     ;;; Warning: phrase \"hello\" isn't defined for language EN.\n     *MY-FUNCTION-1*\n\n     ==\u003e (translate:add-single-translation :en \"hello\" \"Hello\")\n     hello -\u003e Hello (EN)\n\n     ==\u003e (translate:add-single-translation :pl \"hello\" \"Cześć\")\n     hello -\u003e Cześć (PL)\n\n     ==\u003e (let ((*language* :en))\n           #t\"hello\")\n     \"Hello\"\n\n     ==\u003e (let ((*language* :pl))              ; lexical scope is ignored\n           #t\"hello\")\n     \"Hello\"\n\n     ==\u003e (defparameter *my-function-2*\n           (lambda () #t\"hello\"))\n     *MY-FUNCTION-2*\n\n     ==\u003e (funcall *my-function-1*) ; phrase wasn't translated when function was created\n     \"{hello}\"\n\n     ==\u003e (funcall *my-function-2*)\n     \"Hello\"\n\n     ==\u003e (quote #t\"hello\")\n     \"Hello\"\n   #+END_SRC\n\n   Translation at run-time is better when the programmer wants to add\n   translations ad-hoc or wants to switch languages when the\n   application is running. Load-time translation is more suitable for\n   static translations for deployed applications or where macros\n   prevent necessary evaluation of the expressions. Also when the\n   programmer wants to add translations in future (if language is\n   bound to nil and resolution is performed at load-time the\n   expression =#t=\"hello world\" means the same as the \"hello world\").\n\n* Reference\n** Parameters\n*** =*LANGUAGE*=\n    #+BEGIN_SRC text\n      This variable holds the current language designator (the predicate\n      used for comparison is EQL). If bound to NIL, translation works the\n      same way as the IDENTITY function.\n    #+END_SRC\n\n*** =*RESOLUTION-TIME*=\n    #+BEGIN_SRC text\n      Applicable values are :LOAD-TIME and :RUN-TIME (the latter is the\n      default). The variable controls time of actual resolution.\n\n      If it's the :LOAD-TIME, then resolution is performed when the reader\n      encounters the #t dispatch macro character, while setting the variable\n      to :RUN-TIME translates #t\"string\" to the form (TRANSLATE \"string\")\n      and resolution takes place at the time of the form evaluation.\n    #+END_SRC\n\n** Functions\n*** DEFINE-LANGUAGE\n    #+BEGIN_SRC text\n      DEFINE-LANGUAGE - external symbol in TRANSLATE package\n      -----------------------------------------------------------------------------\n      DEFINE-LANGUAGE (NAME \u0026REST TRANSLATIONS)                          [Function]\n      Define language NAME with provided TRANSLATIONS\n\n      If LANGUAGE exists, a continuable error is signalled, which allows either\n      dropping the operation or superseding the language which is already defined.\n      TRANSLATIONS are alternating phrases and their corresponding objects.\n      -----------------------------------------------------------------------------\n    #+END_SRC\n\n*** ADD-SINGLE-TRANSLATION\n    #+BEGIN_SRC text\n      ADD-SINGLE-TRANSLATION - external symbol in TRANSLATE package\n      -----------------------------------------------------------------------------\n      ADD-SINGLE-TRANSLATION (LANGUAGE PHRASE TRANSLATION)               [Function]\n      Add TRANSLATION of PHRASE for given LANGUAGE\n\n      If LANGUAGE doesn't exist, it is implicitly created and a warning is\n      emmited.\n      -----------------------------------------------------------------------------\n    #+END_SRC\n\n*** ADD-TRANSLATIONS\n    #+BEGIN_SRC text\n      ADD-TRANSLATIONS - external symbol in TRANSLATE package\n      -----------------------------------------------------------------------------\n      ADD-TRANSLATIONS (LANGUAGE \u0026REST TRANSLATIONS)                     [Function]\n      Add any number of TRANSLATIONS for the given LANGUAGE\n      -----------------------------------------------------------------------------\n    #+END_SRC\n\n*** TRANSLATE\n    #+BEGIN_SRC text\n      TRANSLATE - external symbol in TRANSLATE package\n      -----------------------------------------------------------------------------\n      TRANSLATE (PHRASE \u0026OPTIONAL (LANGUAGE *LANGUAGE*))                 [Function]\n      Find the translation of PHRASE in the store associated with LANGUAGE\n\n      If LANGUAGE is NIL, then this is the same as the IDENTITY function. If\n      the provided LANGUAGE isn't defined, the store is explicitly\n      created. If no PHRASE is defined for a given language, it is stored\n      for later translation and replaced by PHRASE surrunded by curly\n      brackets.\n      -----------------------------------------------------------------------------\n    #+END_SRC\n\n*** MISSING-TRANSLATIONS\n    #+BEGIN_SRC text\n      MISSING-TRANSLATIONS - external symbol in TRANSLATE package\n      -----------------------------------------------------------------------------\n      MISSING-TRANSLATIONS                                               [Function]\n      Creates a list of phrases which aren't translated for the defined\n      languages. Returns a list of form: ({(LANG ({PHRASE}*))}*)\n      -----------------------------------------------------------------------------\n    #+END_SRC\n","funding_links":[],"categories":["Online editors ##"],"sub_categories":["Third-party APIs"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkochmanski%2Ftranslate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdkochmanski%2Ftranslate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkochmanski%2Ftranslate/lists"}