# How to Handle Localization
## Translations
### Zammad GUI Translations - `i18n/*.po`
Zammad translations are managed exclusively via [translations.zammad.org](https://translations.zammad.org/).
You are welcome to contribute. Please get a free account there if you want to do so.
Any pull requests modifying translation files directly will be rejected.
### Zammad Text Modules - `i18n/text_modules/*.yml`
Zammad text modules are automatically imported when the first admin user is created, according to this user's language.
This is just meant to provide for a smooth start with some existing helpful text modules. They can be modified later on;
no subsequent import from the example files will be performed.
Feel free to send pull requests to add more helpful examples to existing files or even files for new languages.
## Developer Workflows
### How to Write Translatable Strings Well
Writing strings that can be translated well and are a pleasure to read for the end user is not always easy.
#### Bad Examples
- `lock` - this English word is ambiguous, it can be a noun (`a lock`), an infinitive (`to lock`) and an imperative (`lock this!`). That makes it impossible to translate it properly.
- `No email!` - this error message is not understandable for an end user.
#### Good Practices
- *Talk to the end user.* Imagine you are a non-technical end user of Zammad. Would you understand the error messages? Use natural, respectful language.
- *Write at least two words.* Whenever possible, avoid creating single-word strings. This will reduce ambiguity drastically.
- *Include punctuation.* Strings should include punctuation like final stops (`This is a sentence.`) or colons (`My label:`) as part of the translatable string. This punctuation might look different in some languages and should therefore not be hardcoded.
- *Use placeholders, don't concatenate strings.* Bad: `"Open" + ticket_number`, good: `"Open ticket %s"` (and pass `ticket_number` as parameter). It's almost always a good idea to produce such slightly longer strings with placeholders. That helps translators to understand them and allows them to change the position of the placeholders in translations.
### Weblate Process Overview
- The codebase has a translation catalog file [i18n/zammad.pot](zammad.pot), which must be kept up-to-date.
- Weblate automatically picks this file up from git and updates its database. Now translators see the new/changed strings and can work on them.
- From time to time, Weblate pushes the new/updated translations via merge request to git.
- After the merge, the translation files in `i18n/zammad.*.po` are updated.
- Zammad will pick them up as soon as `Translation.sync` gets called, which happens for example during a package update.
### Updating the Source String Catalog
If changes to translatable strings are made during the development process, developers can just run the following to
re-extract the strings to the catalog file:
`rails generate zammad:translation_catalog`
This will update the `i18n/zammad.pot` file in Zammad. To do this for an existing addon module, call
`rails generate zammad:translation_catalog --addon-path /path/to/addon`
To perform additional tasks such as updating template files from translations, use:
`rails generate zammad:translation_catalog --full`
### Testing Translation Changes from Weblate
To test any changes made to translations in Weblate that are not yet published to Zammad,
you can use this workflow:
- Download the current state of the language from Weblate as po file.
- Save it locally in the Zammad folder as `i18n/zammad.pt-br.po` (for `pt-br` in this case, use corresponding file names for other languages).
- Run `rails r Translation.sync` to import the latest state to the database.
- Now the changes should appear in the GUI.
### Known Issues With Localization
- There is currently no support for plural forms.
## API & Code Examples
### CoffeeScript
See [i18n.coffee](app/assets/javascripts/app/lib/app_post/i18n.coffee)
#### Translate With Markup Support
```coffeescript
App.i18n.translateContent('translate and html escape and replace _markup_')
```
#### Translate Without Markup Support
```coffeescript
App.i18n.translateInline('translate and html escape and no _markup_')
```
#### Translate Without Escaping
```coffeescript
App.i18n.translatePlain('plain text')
```
#### Translate Datetime
```coffeescript
App.Ticket.find(1).created_at
"2021-06-10T09:45:30.394Z"
App.i18n.translateTimestamp(App.Ticket.find(1).created_at)
"10.06.2021 11:45"
```
#### Translate Date
```coffeescript
App.Ticket.find(1).created_at
"2021-06-10T09:45:30.394Z"
App.i18n.translateDate(App.Ticket.find(1).created_at)
"10.06.2021"
```
### Ruby
#### Translate a String
```ruby
Translation.translate('de-de', '3 high')
"3 hoch"
```
#### Enable Translation For an ObjectAttribute
```ruby
> obj = ObjectManager::Attribute.find_by(name: 'my_attribute')
=> # obj.data_option['translate'] = true
=> true
[35] pry(main)> obj.save
=> true
```