Skip to main content

Language Localisation

Localisation with i18n

Ketty uses i18n and its react adaptation react-i18n to localise all the strings of the application. You can find the configuration in ketty/app/translations/i18n.js.

To use the library throughout the app, first import the useTranslation hook in your file:

// useTranslation will satisfy most of your use cases, Trans component may be needed for more complex interpolations
import { useTranslation, Trans } from 'react-i18next'


// initialize inside the functional component
const { t } = useTranslation()


// use it to translate strings
<p>{t('translation.key')}</p>

The key that you pass to the t function must match the key in the language file. Because of the structure of our language file, it can be handy to initialize the translation function with a keyPrefix, to avoid repetion inside a component.

// target all strings inside pages.admin in the translation file
const { t } = useTranslation(null, { keyPrefix: 'pages.admin' })


// this will be equivalent to t('pages.admin.title')
<h1>{t('title')}</h1>

// you can still overwrite keyPrefix if you want to use a string that is localised outside the current page, e.g:
<Button>{t('save', { keyPrefix: 'pages.common.actions' })}</Button>

For strings that contain other components, the <Trans> component is handy:

// string in the translation file
"key": "This string contains a <link>link</link>"


// translation in the component
<Trans i18nKey="key" components={{
link: (<a href="https://example.com" />)
}}
/>

// this will output:
This string contains a <a href="https://example.com">link</a>

Language file structure

The language files are saved in ketty/server/cofig/languages. The file uses a nested JSON structure where each UI element has a type and value at minimum. Some elements have additional attributes or nested structures depending on their complexity and purpose.

Pages and layout hierarchy

The file is organised to mirror Ketty's structure, making it easier to find and maintain translations. The top level of the file contains a "pages" object, with each major section of the application represented as a nested object.

{
"pages": {
"admin": { ... },
"aiBookDesigner": { ... },
"common": { ... },
"dash": { ... },
"knowledgeBase": { ... },
"login": { ... },
"newBook": { ... },
"passwordReset": { ... },
"previewAndPublish": { ... },
"producer": { ... },
"signup": { ... }
}
}

The common section

The "common" section serves a special purpose in our translation system. It contains translations that are used across multiple pages of the application. This approach:

  • Reduces duplication
  • Ensures consistency
  • Simplifies maintenance
  • Centralises shared UI elements.

For example, form elements like email and password inputs appear on multiple pages (login, signup, password reset). Instead of duplicating these translations, they are stored once in the common section:

{
"pages": {
"common": {
"form": {
"email": {
"type": "input(label)",
"value": "Email",
"placeholder": {
"type": "input(placeholder)",
"value": "Enter your email address"
},
"errors": {
"noValue": {
"type": "error",
"value": "Email is required"
}
}
},
"password": {
"type": "input(label)",
"value": "Password",
"placeholder": {
"type": "input(placeholder)",
"value": "Enter your password"
},
"errors": {
"noValue": {
"type": "error",
"value": "Password is required"
}
}
}
}
}
}
}

Form elements

Input fields

Here's how to handle an input field's translation structure:

{
"email": {
"type": "input(label)",
"value": "Email address"
},
"placeholder": {
"type": "input(placeholder)",
"value": "Enter your email address"
},
"explanation": {
"type": "input(detail)",
"value": "We'll use this email for account recovery"
},
"errors": {
"noValue": {
"type": "error",
"value": "Email is required"
},
"invalidEmail": {
"type": "error",
"value": "This is not a valid email address"
}
}
}

Selection controls

Selection controls (dropdowns, radio buttons, and checkboxes) are used when users need to choose from predefined options. They have a more complex structure that includes the label, options, and often additional explanatory text.

{
"format": {
"type": "select(label)",
"value": "Format:"
},
"options": {
"pdf": {
"value": "PDF"
},
"epub": {
"value": "EPUB"
},
"web": {
"value": "Web"
}
},
"explanation": {
"type": "select(detail)",
"value": "Choose the format for your book export"
}
}

For complex options that need additional explanation:

{
"license": {
"type": "select(label)",
"value": "Copyright license"
},
"options": {
"allRightsReserved": {
"value": "All Rights Reserved",
"detail": {
"type": "option(detail)",
"value": "Your work cannot be distributed without your express consent"
}
},
"creativeCommons": {
"value": "Creative Commons",
"detail": {
"type": "option(detail)",
"value": "Some rights are reserved based on the specific license you select"
}
}
}
}

Toggle switches

Switches are used for binary settings (on/off, enable/disable). They often need both a label and an explanation of their effects:

        "aiDesigner": {
"type": "switch(label)",
"value": "AI Book Designer (Beta)",
"detail": {
"type": "switch(detail)",
"value": "Users with edit access to this book can use AI writing prompts."
}
}

Menus organise navigation and actions. They can include different types of items:

  • Use link(text) for navigation to other pages
  • Use action for operations like logout or delete
{
"menu": {
"type": "menu",
"options": {
"admin": {
"type": "link(text)",
"value": "Admin"
},
"logout": {
"type": "action",
"value": "Log Out"
},
"dashboard": {
"type": "link(text)",
"value": "Dashboard"
}
}
}
}

System messages and notifications

Simple messages

Use the message type for simple status updates or state information:

{
"status": {
"type": "message",
"value": "Last synced %{timestamp}"
}
}

Notifications

Use the notifications structure for more complex messages that appear on pop ups and might need titles or different severity levels:

{
"notifications": {
"previewFailed": {
"title": {
"type": "title",
"value": "Preview failed"
},
"messages": {
"401": {
"type": "error",
"value": "Error 401: There was a problem authenticating with the Flax microservice. Please contact your administrator."
}
}
}
}
}

Page structure elements

Titles and headings

{
"title": {
"type": "title",
"value": "Create New Book"
},
"sections": {
"upload": {
"heading": {
"type": "heading",
"value": "Upload your files"
},
"description": {
"type": "detail",
"value": "Start your book with .docx files"
}
}
}
}

Placeholders and empty states

Use placeholders to guide users when content is missing or not yet created:

{
"new": {
"type": "placeholder",
"value": "Untitled chapter"
}
}