Locales
Internationalization (i18n) appears to be simple but can be complex. Some considerations are:
- Front-end internationalization
- Internationalization of server
- Internationalized resource file management
- How to collaborate between projects, developers and translators
- Internationalization of dates and times
Internationalization implementations are often tied to specific technology stacks. This internationalization setup is only for the React technology stack and does not involve server-side internationalization.
Front-end internationalization
The core steps of internationalization are as follows:
Add the desired
locale
to the transact-config configuration. If omitted, the defaultlocale
isen
.Create and import a messages file, stored as a key-value object in the
src/locales
folder.Import and re-export those locale message files in
src/locales/index.ts
. For example:src/locales/index.tsimport en from './en';
import es from './es';
import pt from './pt';
import zhCN from './zh-cn';
import zhTW from './zh-tw';
export default {
en,
es,
pt,
'zh-CN': zhCN,
'zh-TW': zhTW,
};note
Some locales include a region code as well as the language code, such as Chinese locales like
zh-CN
andzh-TW
. The samplesrc/locales/index.ts
file above shows how to add locales with a region code. For example, for the Chinese localezh-CN
, add'zh-CN': zhCN
to the exported locales.Pass the configs and messages to the
App
init.
The Workspaces app allows the user to select a language for static text in the UI. The list of languages offered is generated by matching the exported locales against the following list.
{value: 'en', id: 'eng', key: 'English',label: 'English'},
{value: 'ar', id: 'ara', key: 'Arabic',label: 'Arabic'},
{value: 'af', id: 'afr', key: 'Afrikaans locale',label: 'Afrikaans'},
{value: 'ar-DZ', id: 'ara', key: 'Arabic locale (Algerian Arabic)',label: 'Algerian Arabic'},
{value: 'ar-MA', id: 'ara', key: 'Arabic locale (Moroccan Arabic)',label: 'Moroccan Arabic'},
{value: 'ar-SA', id: 'ara', key: 'Arabic locale (Sauid Arabic)',label: 'Arabic'},
{value: 'ar-TN', id: 'ara', key: 'Arabic locale (Tunisian Arabic)',label: 'Arabic'},
{value: 'az', id: 'aze', key: 'Azerbaijani locale',label: 'Azerbaijani'},
{value: 'be', id: 'bel', key: 'Belarusian locale',label: 'Belarusian'},
{value: 'bg', id: 'bul', key: 'Bulgarian locale',label: 'Bulgarian'},
{value: 'bn', id: 'ben', key: 'Bengali locale',label: 'Bengali'},
{value: 'bs', id: 'bos', key: 'Bosnian locale',label: 'Bosnian'},
{value: 'ca', id: 'cat', key: 'Catalan locale',label: 'Catalan'},
{value: 'cs', id: 'ces', key: 'Czech locale',label: 'Czech'},
{value: 'cy', id: 'cym', key: 'Welsh locale',label: 'Welsh'},
{value: 'da', id: 'dan', key: 'Danish locale',label: 'Danish'},
{value: 'de', id: 'deu', key: 'German locale',label: 'German'},
{value: 'de-AT', id: 'deu', key: 'German locale (Austria)',label: 'German'},
{value: 'el', id: 'ell', key: 'Greek locale',label: 'Greek'},
{value: 'en-AU', id: 'eng', key: 'English locale (Australia)',label: 'English'},
{value: 'en-CA', id: 'eng', key: 'English locale (Canada)',label: 'English'},
{value: 'en-GB', id: 'eng', key: 'English locale (United Kingdom)',label: 'English'},
{value: 'en-IN', id: 'eng', key: 'English locale (India)',label: 'English'},
{value: 'en-NZ', id: 'eng', key: 'English locale (New Zealand)',label: 'English'},
{value: 'en-US', id: 'eng', key: 'English locale (United States)',label: 'English'},
{value: 'en-ZA', id: 'eng', key: 'English locale (South Africa)',label: 'English'},
{value: 'eo', id: 'epo', key: 'Esperanto locale',label: 'Esperanto'},
{value: 'es', id: 'spa', key: 'Spanish locale',label: 'Spanish'},
{value: 'et', id: 'est', key: 'Estonian locale',label: 'Estonian'},
{value: 'eu', id: 'eus', key: 'Basque locale',label: 'Basque'},
{value: 'fa-IR', id: 'ira', key: 'Persian/Farsi locale (Iran)',label: 'Persian'},
{value: 'fi', id: 'fin', key: 'Finnish locale',label: 'Finnish'},
{value: 'fr', id: 'fra', key: 'French locale',label: 'French'},
{value: 'fr-CA', id: 'fra', key: 'French locale (Canada)',label: 'French'},
{value: 'fr-CH', id: 'fra', key: 'French locale',label: 'French'},
{value: 'gd', id: 'gla', key: 'Scottish Gaelic',label: 'Scottish Gaelic'},
{value: 'gl', id: 'glg', key: 'Galician locale',label: 'Galician'},
{value: 'gu', id: 'guj', key: 'Gujarati locale (India)',label: 'Gujarati'},
{value: 'he', id: 'heb', key: 'Hebrew locale',label: 'Hebrew'},
{value: 'hi', id: 'hin', key: 'Hindi locale (India)',label: 'Hindi'},
{value: 'hr', id: 'hrv', key: 'Croatian locale',label: 'Croatian'},
{value: 'ht', id: 'hat', key: 'Haitian Creole locale',label: 'Haitian Creole'},
{value: 'hu', id: 'hun', key: 'Hungarian locale',label: 'Hungarian'},
{value: 'hy', id: 'arm', key: 'Armenian locale',label: 'Armenian'},
{value: 'id', id: 'ind', key: 'Indonesian locale',label: 'Indonesian'},
{value: 'is', id: 'isl', key: 'Icelandic locale',label: 'Icelandic'},
{value: 'it', id: 'ita', key: 'Italian locale',label: 'Italian'},
{value: 'ja', id: 'jpn', key: 'Japanese locale',label: 'Japanese'},
{value: 'ja-Hira', id: 'jpn', key: 'Japanese (Hiragana) locale',label: 'Japanese (Hiragana)'},
{value: 'ka', id: 'geo', key: 'Georgian locale',label: 'Georgian'},
{value: 'kk', id: 'kaz', key: 'Kazakh locale',label: 'Kazakh'},
{value: 'kn', id: 'kan', key: 'Kannada locale (India)',label: 'Kannada'},
{value: 'ko', id: 'kor', key: 'Korean locale',label: 'Korean'},
{value: 'lb', id: 'ltz', key: 'Luxembourgish locale',label: 'Luxembourgish'},
{value: 'lt', id: 'lit', key: 'Lithuanian locale',label: 'Lithuanian'},
{value: 'lv', id: 'lav', key: 'Latvian locale (Latvia)',label: 'Latvian'},
{value: 'mk', id: 'mkd', key: 'Macedonian locale',label: 'Macedonian'},
{value: 'mn', id: 'mon', key: 'Mongolian locale',label: 'Mongolian'},
{value: 'ms', id: 'msa', key: 'Malay locale',label: 'Malay'},
{value: 'mt', id: 'mlt', key: 'Maltese locale',label: 'Maltese'},
{value: 'nb', id: 'nob', key: 'Norwegian Bokmål locale',label: 'Norwegian Bokmål'},
{value: 'nl', id: 'nld', key: 'Dutch locale',label: 'Dutch'},
{value: 'nl-BE', id: 'nld', key: 'Dutch locale',label: 'Dutch'},
{value: 'nn', id: 'nno', key: 'Norwegian Nynorsk locale',label: 'Norwegian Nynorsk'},
{value: 'pl', id: 'pol', key: 'Polish locale',label: 'Polish'},
{value: 'pt', id: 'por', key: 'Portuguese locale',label: 'Portuguese'},
{value: 'pt-BR', id: 'por', key: 'Portuguese locale (Brazil)',label: 'Portuguese'},
{value: 'ro', id: 'ron', key: 'Romanian locale',label: 'Romanian'},
{value: 'ru', id: 'rus', key: 'Russian locale',label: 'Russian'},
{value: 'sk', id: 'slk', key: 'Slovak locale',label: 'Slovak'},
{value: 'sl', id: 'slv', key: 'Slovenian locale',label: 'Slovenian'},
{value: 'sq', id: 'sqi', key: 'Albanian locale',label: 'Shqip'},
{value: 'sr', id: 'srp', key: 'Serbian cyrillic locale',label: 'Serbian'},
{value: 'sr-Latn', id: 'srp', key: 'Serbian latin locale',label: 'Serbian'},
{value: 'sv', id: 'swe', key: 'Swedish locale',label: 'Swedish'},
{value: 'ta', id: 'tam', key: 'Tamil locale (India)',label: 'Tamil'},
{value: 'te', id: 'tel', key: 'Telugu locale',label: 'Telugu'},
{value: 'th', id: 'tha', key: 'Thai locale',label: 'Thai'},
{value: 'tr', id: 'tur', key: 'Turkish locale',label: 'Turkish'},
{value: 'ug', id: 'uig', key: 'Uighur locale',label: 'Uighur'},
{value: 'uk', id: 'ukr', key: 'Ukrainian locale',label: 'Ukrainian'},
{value: 'uz', id: 'uzb', key: 'Uzbek locale',label: 'Uzbek'},
{value: 'vi', id: 'vie', key: 'Vietnamese locale (Vietnam)',label: 'Vietnamese'},
{value: 'zh-CN', id: 'zho', key: 'Chinese Simplified locale',label: 'Chinese Simplified'},
{value: 'zh-TW', id: 'zho', key: 'Chinese Traditional locale',label: 'Chinese Traditional'},
Date translations use the date-fns
library which requires some unique locale data when doing internationalization, mainly for relative time translation such as yesterday
, today
, tomorrow
, N minutes ago
, or N months ago
. Consequently, it's important to import the correct locale at the beginning of the file. To learn more, see Internationalization in the date-fns
documentation.
Internationalized resource file management
The above document discusses the steps of how to internationalize after the introduction of messages files. Next, let's talk about the management of international resource files.
Currently we manage the resource file in the src/locales
folder:
.
├── en.ts
├── es.ts
└── pt.ts
\*.ts
is a resource file that returns an object; the key for our translation id, and the value for the specific language translation. For example:
export default {
Home: 'Home',
All: 'All',
Apply: 'Apply',
Process: 'Process',
Helpdesk: 'Helpdesk',
...
}
export default {
Home: 'Hogar',
All: 'Todos',
Applications: 'Aplicaciones',
Review: 'Revisar',
Funding: 'Fondos',
...
}
Right-to-left languages
note
Workspaces supports the following right-to-left languages based on the list included in the article Right-To-Left vs Left-To-Right. If any issues are discovered with this behaviour or another ISO language code needs to be added, you can contact us to discuss your requirements.
If the locale specified in your transact-config matches one of the ISO language codes listed below, the application will display in right-to-left mode. For more information about locales, see Locales.
ISO Language Code | Language Name |
---|---|
ar | Arabic |
arc | Aramaic |
dv | Divehi |
fa | Persian |
ha | Hausa |
he | Hebrew |
khw | Khowar |
ks | Kashmiri |
ku | Kurdish |
ps | Pashto |
ur | Urdu |
yi | Yiddish |
Translation Namespacing
Workspaces enables translation of hardcoded values and values from API responses in TJM. You can use the same values as keys for translations. Starting with version 25.04, namespace keys were introduced to provide more granular control over translations.
In your language translation files, you can define a global translation by using values as keys. These translations will apply wherever the word or phrase appears in the app. Additionally, you can override global translations for specific sections or components using namespace keys.
The table below maps the parent namespace keys available in the app to their corresponding sections or components.
Key | Section/Component |
---|---|
app.actions | Action Buttons |
app.attachments | Attachments/Documents Panel |
app.autocomplete | Autocomplete Dropdown |
app.bulkaction | Bulk Actions |
app.configeditor | Config Editor |
app.customcards | Custom Cards |
app.dashboardlayout | App Header Bar |
app.datepicker | Date Picker |
app.daterangepicker | Date Range Picker |
app.errorpage | Error Page |
app.jobswitcher | Application Switcher (Mobile) |
app.sidesheet | SideSheet |
app.notes | Notes Panel |
app.notifications | Notifications Bar |
app.pdfRenderer | PDF Renderer |
app.refreshcontrol | Refresh Control |
app.listing | Listing Table |
app.search | Listing Table Search |
app.searchFilter | Listing Table Filter |
app.timeline | Timeline |
app.treecard | Tree |
app.txnswitcher | Transaction Switcher |
Example
In this example, the phrase Show All
is used globally in the app and will render as Show All
unless overridden. For the app.txnswitcher
namespace, the override app.txnswitcher.showAll
changes the rendering to Show All Tasks
specifically in the Transaction Switcher component. When the Spanish language is selected, the override renders as Mostrar todo
.
// ./locales/en.ts
export default {
'Mark all as Read': 'Mark all as Read',
'No messages to display': 'No messages to display',
'New comment at': 'New comment at',
'New assignment at': 'New assignment at',
'Assigned by': 'Assigned by',
'Show All': 'Show All' // global translation
'app.txnswitcher.showAll': 'Show All Tasks' // namespace override
};
// ./locales/es.ts - Spanish
export default {
'New comment at': 'Nuevo comentario en',
'New assignment at': 'Nueva asignación en',
'Assigned by': 'Asignado por',
'Failed to load PDF file': 'No se pudo cargar el archivo PDF',
'app.txnswitcher.showAll': 'Mostrar todo', // namespace override
};
Reference
An auto-generated reference to default translation keys is provided below. This reference serves as a guide for all available namespace keys that can be overridden.
{
"app.actions.label": "Actions",
"app.actions.noaction": "No actions available",
"app.attachments.nodownload": "No download available",
"app.attachments.upload": "Upload",
"app.autocomplete.choose": "Choose an option",
"app.bulkaction.apply": "Apply",
"app.bulkaction.bulkactions": "Bulk Actions",
"app.bulkaction.selected": "selected",
"app.chat.noagent": "Sorry, no agent is currently available",
"app.configeditor.configure": "Configure",
"app.configeditor.download": "Download",
"app.configeditor.preview": "Preview",
"app.configeditor.save": "Save",
"app.configeditor.ui": "UI",
"app.configeditor.upload": "Upload",
"app.customcards.action": "Action",
"app.customcards.copy": "Copy Text",
"app.customcards.legend": "Legend",
"app.customcards.negative": "Negative",
"app.customcards.neutral": "Neutral",
"app.customcards.nocards": "There are no Custom Cards to view",
"app.customcards.norecords": "No records found",
"app.customcards.positive": "Positive",
"app.customcards.reset": "Reset",
"app.customcards.select": "Select",
"app.customcards.submit": "Submit",
"app.customcards.warning": "Warning",
"app.dashboardlayout.applicationid": "Application ID",
"app.dashboardlayout.documents": "Documents",
"app.dashboardlayout.logout": "Logout",
"app.dashboardlayout.notes": "Notes",
"app.dashboardlayout.outofoffice": "Out of Office",
"app.datepicker.cancel": "Cancel",
"app.datepicker.clear": "Clear",
"app.datepicker.ok": "OK",
"app.datepicker.pickadate": "Pick a Date",
"app.daterangepicker.cancel": "Cancel",
"app.daterangepicker.clear": "Clear",
"app.daterangepicker.ok": "OK",
"app.daterangepicker.pickadate": "Pick a Date",
"app.dropdown.cancel": "Cancel",
"app.dropdown.confirm": "Confirm",
"app.errorpage.logout": "Logout",
"app.errorpage.sorry": "Sorry",
"app.errorpage.support": "Please contact support.",
"app.errorpage.wrong": "Something went wrong.",
"app.jobswitcher.applications": "Applications",
"app.jobswitcher.updated": "Updated:",
"app.listing.cardsperpage": "Cards per page",
"app.listing.details": "Go to Detail Screen",
"app.listing.gotodetails": "Go to Detail Screen",
"app.listing.morelist": "More Information",
"app.listing.moretable": "Transactions",
"app.listing.nodata": "No Data",
"app.listing.rowsperpage": "Rows per page",
"app.listing.sort": "Sort",
"app.notes.addnote": "Add note",
"app.notes.addnotes": "Add note",
"app.notes.comment": "Comment",
"app.notes.comments": "Comments",
"app.notes.showall": "Show All",
"app.notes.showless": "Show Less",
"app.notes.viewthread": "View Thread",
"app.notifcations.all": "All",
"app.notifcations.markallasread": "Mark all as Read",
"app.notifcations.markasread": "Mark as Read",
"app.notifcations.newassigmentat": "New assignment at",
"app.notifcations.newcommentat": "New comment at",
"app.notifcations.nomessagetodisplay": "No messages to display",
"app.notifcations.open": "Open",
"app.notifcations.readopened": "Read/Opened",
"app.notifcations.thread": "Unread",
"app.notifications.assignedby": "Assigned by",
"app.notifications.newassigmentat": "New assignment",
"app.pagination.next": "Next",
"app.pagination.pageInfo": "Page",
"app.pagination.previous": "Previous",
"app.pdfRenderer.failedToLoadPdfFile": "Failed to load PDF file",
"app.pdfrenderer.notauthorized": "Not authorised",
"app.refreshcontrol.updated": "Updated",
"app.search.clear": "Clear",
"app.search.type": "Type exact text to match; e.g. \"Apple\" instead of \"Appl\"",
"app.searchFilter.apply": "Apply",
"app.searchFilter.globalfilters": "Global Filters",
"app.searchFilter.title": "Search Filters",
"app.searchFilter.viewfilter": "View Filters",
"app.searchfilter.all": "All",
"app.searchfilter.apply": "Apply",
"app.searchfilter.filter": "Filter",
"app.searchfilter.filters": "Filters",
"app.searchfilter.reset": "Reset",
"app.select.all": "All",
"app.select.none": "None",
"app.sidesheet.close": "Close",
"app.sidesheet.noreceipt": "No receipt URL found",
"app.table.loading": "Loading",
"app.table.sort": "Sort",
"app.timeline.noresult": "No result",
"app.timeline.step": "Step",
"app.timeline.tasks": "Tasks",
"app.treecard.loading": "Loading...",
"app.treecard.reset": "Reset",
"app.treecard.zoomin": "Zoom in",
"app.treecard.zoomout": "Zoom out",
"app.txnswitcher.showAll": "Show All",
"app.txnswitcher.showLess": "Show Less",
"app.txnswticher.status": "Status",
"app.txnswticher.taskId": "Task ID",
"app.txnswticher.tasks": "Tasks"
}