1"use strict";(self.webpackChunkopenrefine_documentation=self.webpackChunkopenrefine_documentation||[]).push([[6561],{16785(e,n,i){i.r(n),i.d(n,{assets:()=>l,contentTitle:()=>o,default:()=>h,frontMatter:()=>r,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"technical-reference/translating-ui","title":"Translate OpenRefine\'s interface","description":"You can help translate OpenRefine into your language by visiting Weblate which provides a web based UI to edit and add translations and sends automatic pull requests back to our project.","source":"@site/docs/technical-reference/translating-ui.md","sourceDirName":"technical-reference","slug":"/technical-reference/translating-ui","permalink":"/docs/technical-reference/translating-ui","draft":false,"unlisted":false,"editUrl":"https://github.com/OpenRefine/openrefine.github.com/edit/master/docs/technical-reference/translating-ui.md","tags":[],"version":"current","lastUpdatedBy":"Rory Sawyer","lastUpdatedAt":1772037469000,"frontMatter":{"id":"translating-ui","title":"Translate OpenRefine\'s interface","sidebar_label":"Contributing as a translator"},"sidebar":"docs","previous":{"title":"Communicating via GitHub","permalink":"/docs/technical-reference/github"},"next":{"title":"Contributing as a technical writer","permalink":"/docs/technical-reference/documentation-contributions"}}');var a=i(74848),s=i(28453);const r={id:"translating-ui",title:"Translate OpenRefine's interface",sidebar_label:"Contributing as a translator"},o=void 0,l={},d=[{value:"Manual translation process",id:"manual-translation-process",level:2},{value:"Simple case of localized string",id:"simple-case-of-localized-string",level:3},{value:"Localization with a parameterized value",id:"localization-with-a-parameterized-value",level:3},{value:"Localization with a singular/plural value",id:"localization-with-a-singularplural-value",level:3},{value:"Front-end development",id:"front-end-development",level:2},{value:"Adding a new string",id:"adding-a-new-string",level:3},{value:"Adding a new language",id:"adding-a-new-language",level:3},{value:"Main interface",id:"main-interface",level:4},{value:"Extensions",id:"extensions",level:4},{value:"Server-side localisation",id:"server--backend-coding",level:2}];function c(e){const n={a:"a",code:"code",h2:"h2",h3:"h3",h4:"h4",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)(n.p,{children:["You can help translate OpenRefine into your language by visiting ",(0,a.jsx)(n.a,{href:"https://hosted.weblate.org/engage/openrefine/?utm_source=widget",children:"Weblate"})," which provides a web based UI to edit and add translations and sends automatic pull requests back to our project."]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.img,{src:"https://hosted.weblate.org/widgets/openrefine/-/287x66-grey.png",alt:"Translation status"})}),"\n",(0,a.jsxs)(n.p,{children:["Click to help translate --\x3e ",(0,a.jsx)(n.a,{href:"https://hosted.weblate.org/engage/openrefine/?utm_source=widget",children:"Weblate"})]}),"\n",(0,a.jsx)(n.h2,{id:"manual-translation-process",children:"Manual translation process"}),"\n",(0,a.jsxs)(n.p,{children:["Localized strings are entered in a ",(0,a.jsx)(n.code,{children:".json"})," file, one per language. They are located in the folder ",(0,a.jsx)(n.code,{children:"main/webapp/modules/core/langs/"})," in a file named ",(0,a.jsx)(n.code,{children:"translation-xx.json"}),", where ",(0,a.jsx)(n.code,{children:"xx"})," is the language code (i.e. ",(0,a.jsx)(n.code,{children:"fr"})," for French)."]}),"\n",(0,a.jsx)(n.h3,{id:"simple-case-of-localized-string",children:"Simple case of localized string"}),"\n",(0,a.jsx)(n.p,{children:"This is an example of a simple string, with the start of the JSON file. This example is for French."}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{children:'{\n "name": "Fran\xe7ais",\n "core-index/help": "Aide",\n (\u2026 more lines)\n}\n'})}),"\n",(0,a.jsxs)(n.p,{children:["So the key ",(0,a.jsx)(n.code,{children:"core-index/help"})," will render as ",(0,a.jsx)(n.code,{children:'"Aide"'})," in French."]}),"\n",(0,a.jsx)(n.h3,{id:"localization-with-a-parameterized-value",children:"Localization with a parameterized value"}),"\n",(0,a.jsxs)(n.p,{children:["In this example, the name of the column (represented by ",(0,a.jsx)(n.code,{children:"$1"})," in this example), will be substituted with the string of the name of the column."]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.code,{children:'"core-facets/edit-facet-title": "Cliquez ici pour \xe9diter le nom de la facette\\nColonne\xa0: $1",'})}),"\n",(0,a.jsx)(n.h3,{id:"localization-with-a-singularplural-value",children:"Localization with a singular/plural value"}),"\n",(0,a.jsxs)(n.p,{children:["In this example, one of the parameter will have a different string depending if the value is 1 or another value.\nIn this example, the string for page, the second parameter, ",(0,a.jsx)(n.code,{children:"$2"}),", will have an \xab s \xbb or not depending on the value of ",(0,a.jsx)(n.code,{children:"$2"}),"."]}),"\n",(0,a.jsx)(n.p,{children:(0,a.jsx)(n.code,{children:'"core-views/goto-page": "$1 de $2 {{plural:$2|page|pages}}"'})}),"\n",(0,a.jsx)(n.h2,{id:"front-end-development",children:"Front-end development"}),"\n",(0,a.jsxs)(n.p,{children:["The OpenRefine front end has been localized using the ",(0,a.jsx)(n.a,{href:"https://github.com/wikimedia/jquery.i18n",children:"Wikimedia jquery.i18n library"}),". The localized text is stored in a JSON dictionary on the server and retrieved with a new OpenRefine command."]}),"\n",(0,a.jsx)(n.h3,{id:"adding-a-new-string",children:"Adding a new string"}),"\n",(0,a.jsxs)(n.p,{children:["There should be no hard-coded language strings in the HTML or JSON used for the front end. If you need a new string, first check the existing strings to make sure there isn't an equivalent string, ",(0,a.jsx)(n.strong,{children:"in an equivalent context"}),", that you can reuse. Context is important because it can affect how the same literal English text is translated. This cuts down on the amount of text which needs to be translated."]}),"\n",(0,a.jsxs)(n.p,{children:["Strings should be entire sentences or phrases and should include substitution variables for any parameters. Do not concatenate strings in either Java or Javascript (or implicitly by laying them out in a specific order). So, instead of ",(0,a.jsx)(n.code,{children:'"You have " + count + " row(s)"'})," (or worse ",(0,a.jsx)(n.code,{children:'count != 1 ? " rows" : " row"'}),"), internationalize everything together so that it can be translated taking into account word ordering and plurals for different languages, ie ",(0,a.jsx)(n.code,{children:'"You have $1 {{plural $1: row|rows}}"'}),", passing the parameter(s) into the ",(0,a.jsx)(n.code,{children:"$.i18n"})," call."]}),"\n",(0,a.jsx)(n.p,{children:"If there's no string you can reuse, allocate an available key in the appropriate translation dictionary and add the default string, e.g."}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-json",children:'...,\n"section/ne
1wkey": "new default string for this key",\n...\n'})}),"\n",(0,a.jsx)(n.p,{children:"and then set the text (or HTML) of your HTML element using i18n helper method. So given an HTML fragment like:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-html",children:'<label id="new-element-id">[untranslated text would have appeared here before]</label>\n'})}),"\n",(0,a.jsx)(n.p,{children:"we could set its text using:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{children:"$('#new-html-element-id').text($.i18n('section/newkey'));\n"})}),"\n",(0,a.jsx)(n.p,{children:"or, if you need to embed HTML tags:"}),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{children:"$('#new-html-element-id').html($.i18n('section/newkey'));\n"})}),"\n",(0,a.jsxs)(n.p,{children:["HTML should be avoided as it can enable ",(0,a.jsx)(n.a,{href:"https://owasp.org/www-community/attacks/xss/",children:"cross-scripting attacks"}),"."]}),"\n",(0,a.jsx)(n.h3,{id:"adding-a-new-language",children:"Adding a new language"}),"\n",(0,a.jsxs)(n.p,{children:["The language dictionaries are stored in the ",(0,a.jsx)(n.code,{children:"langs"})," subdirectory for the module e.g."]}),"\n",(0,a.jsxs)(n.ul,{children:["\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.a,{href:"https://github.com/OpenRefine/OpenRefine/tree/master/main/webapp/modules/core/langs",children:"https://github.com/OpenRefine/OpenRefine/tree/master/main/webapp/modules/core/langs"})," for the main interface"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.a,{href:"https://github.com/OpenRefine/OpenRefine/tree/master/extensions/database/module/langs",children:"https://github.com/OpenRefine/OpenRefine/tree/master/extensions/database/module/langs"})," for database via JDBC"]}),"\n",(0,a.jsxs)(n.li,{children:[(0,a.jsx)(n.a,{href:"https://github.com/OpenRefine/OpenRefine/tree/master/extensions/wikibase/module/langs",children:"https://github.com/OpenRefine/OpenRefine/tree/master/extensions/wikibase/module/langs"})," for the Wikibase extension"]}),"\n"]}),"\n",(0,a.jsxs)(n.p,{children:["To add support for a new language, the easiest way is to do it directly in Weblate. To do it manually, copy ",(0,a.jsx)(n.code,{children:"translation-en.json"})," to ",(0,a.jsx)(n.code,{children:"translation-<locale>.json"})," and have your translator translate all the value strings (ie right hand side)."]}),"\n",(0,a.jsx)(n.h4,{id:"main-interface",children:"Main interface"}),"\n",(0,a.jsxs)(n.p,{children:["The translation is best done ",(0,a.jsx)(n.a,{href:"https://hosted.weblate.org/engage/openrefine/?utm_source=widget",children:"with Weblate"}),". Changes are pushed to OpenRefine's git repository and are included in the next release."]}),"\n",(0,a.jsxs)(n.p,{children:[(0,a.jsx)(n.a,{href:"/docs/technical-reference/build-test-run",children:"Run OpenRefine from the master branch"})," and check whether translated words fit to the layout. Not all items can be translated word by word, especially into non-I\u0300ndo-European languages."]}),"\n",(0,a.jsx)(n.p,{children:"If you see any text which remains in English even when you have checked all items, please create bug report in the issue tracker so that the developers can fix it."}),"\n",(0,a.jsx)(n.h4,{id:"extensions",children:"Extensions"}),"\n",(0,a.jsx)(n.p,{children:"Extensions can be translated via Weblate just like the core software."}),"\n",(0,a.jsxs)(n.p,{children:["The Wikibase extension contains lots of domain-specific concepts, with which you may not be familiar. The Wikidata may not have reconciliation service for your language. We recommend checking the ",(0,a.jsx)(n.a,{href:"https://www.wikidata.org/wiki/Wikidata:Glossary",children:"Wikidata glossary"})," to be consistent."]}),"\n",(0,a.jsxs)(n.p,{children:['By default, the system tries to load the language file corresponding to the currently in-use browser language. To override this setting a new menu item ("Language Settings") has been added at the index page.\nTo support a new language file, the developer should add a corresponding entry to the dropdown menu in this file: ',(0,a.jsx)(n.code,{children:"/OpenRefine/main/webapp/modules/core/scripts/index/lang-settings-ui.html"}),". The entry should look like:"]}
1),"\n",(0,a.jsx)(n.pre,{children:(0,a.jsx)(n.code,{className:"language-javascript",children:'<option value="<locale>">[Language Label]</option>\n'})}),"\n",(0,a.jsx)(n.h2,{id:"server--backend-coding",children:"Server-side localisation"}),"\n",(0,a.jsxs)(n.p,{children:["Certain areas of the back-end can also be localized, also via Weblate.\nThe translated strings are stored in ",(0,a.jsx)(n.code,{children:".properties"})," file, from which Java classes are generated at compilation time.\nFor instance, operation descriptions are stored under ",(0,a.jsx)(n.a,{href:"https://github.com/OpenRefine/OpenRefine/tree/master/main/resources/com/google/refine/operations/",children:(0,a.jsx)(n.code,{children:"main/resources/com/google/refine/operations"})}),"."]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,a.jsx)(n,{...e,children:(0,a.jsx)(c,{...e})}):c(e)}},28453(e,n,i){i.d(n,{R:()=>r,x:()=>o});var t=i(96540);const a={},s=t.createContext(a);function r(e){const n=t.useContext(s);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:r(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.