mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-12-15 23:03:15 +00:00
Translate rst files to md format and add new index entries for translation directives and provider
This commit is contained in:
@@ -12,6 +12,8 @@ As Chill relies on the [symfony ](http://symfony.com) framework, reading the fra
|
|||||||
- [Messages to users](messages-to-users.md)
|
- [Messages to users](messages-to-users.md)
|
||||||
- [Pagination](pagination.md)
|
- [Pagination](pagination.md)
|
||||||
- [Localisation](localisation.md)
|
- [Localisation](localisation.md)
|
||||||
|
- [Translation directives](translation_directives.md)
|
||||||
|
- [Translation provider](translation_provider.md)
|
||||||
- [Logging](logging.md)
|
- [Logging](logging.md)
|
||||||
- [Database migrations](migrations.md)
|
- [Database migrations](migrations.md)
|
||||||
- [Searching](searching.md)
|
- [Searching](searching.md)
|
||||||
|
|||||||
376
docs/source/development/translation_directives.md
Normal file
376
docs/source/development/translation_directives.md
Normal file
@@ -0,0 +1,376 @@
|
|||||||
|
# Translation Key Directives
|
||||||
|
|
||||||
|
These directives are meant to ensure better consistency across bundles, avoid duplication, and make keys more predictable.
|
||||||
|
|
||||||
|
## General Principles
|
||||||
|
|
||||||
|
1. **Use lowercase snake_case for all keys**
|
||||||
|
|
||||||
|
2. **Use dot-separated namespaces**
|
||||||
|
The dot is used to reflect:
|
||||||
|
- bundle
|
||||||
|
- feature
|
||||||
|
- sub-feature
|
||||||
|
- key type
|
||||||
|
|
||||||
|
3. **Do not use spaces in keys**
|
||||||
|
|
||||||
|
4. **Avoid duplicating the same text in multiple places**
|
||||||
|
When a translation is needed, try a search for the translation value first and see if it exists elsewhere
|
||||||
|
|
||||||
|
5. **If a key is used across multiple bundles, it must live in ChillMainBundle.**
|
||||||
|
|
||||||
|
6. **If a key is used across multiple bundles and is a generic term, it must be placed in the `common` namespace.**
|
||||||
|
|
||||||
|
## Key Structure
|
||||||
|
|
||||||
|
We use the following structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
<scope>.<feature>.<sub-feature>.<key-type>
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
- `<scope>` identifies the bundle or shared context
|
||||||
|
- `<feature>` identifies the part of the module using the translation
|
||||||
|
- `<element>` describes the text purpose
|
||||||
|
- `<subelement>` for a multi-level element (e.g., activity.export.person.count.description)
|
||||||
|
|
||||||
|
### Examples of scopes
|
||||||
|
|
||||||
|
- `activity` — ChillActivityBundle
|
||||||
|
- `person` — ChillPersonBundle
|
||||||
|
- `common` — neutral shared translation values
|
||||||
|
|
||||||
|
## Naming Scopes
|
||||||
|
|
||||||
|
### 1. Bundle-specific keys
|
||||||
|
|
||||||
|
For most things inside a bundle:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.<feature>.<element>
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.form.save
|
||||||
|
activity.list.title
|
||||||
|
activity.entity.type
|
||||||
|
activity.menu.activities
|
||||||
|
activity.controller.success_created
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Shared UI elements (buttons, labels, generic text)
|
||||||
|
|
||||||
|
These belong in the `common` namespace in ChillMainBundle:
|
||||||
|
|
||||||
|
```
|
||||||
|
common.save
|
||||||
|
common.delete
|
||||||
|
common.edit
|
||||||
|
common.filter
|
||||||
|
common.duration_time
|
||||||
|
```
|
||||||
|
|
||||||
|
## Translation Workflow
|
||||||
|
|
||||||
|
Use the following workflow when deciding where a key belongs:
|
||||||
|
|
||||||
|
1. **Is this text used in more than one bundle?**
|
||||||
|
→ Place in `main` or `common`
|
||||||
|
|
||||||
|
2. **Is this text generic UI (button, label, pagination, yes/no)?**
|
||||||
|
→ Place in `common`
|
||||||
|
|
||||||
|
3. **Is this text specific to one bundle and one feature?**
|
||||||
|
→ Place in `<bundle>.feature.<element>`
|
||||||
|
|
||||||
|
4. **Is this text related to an entity or value object?**
|
||||||
|
→ Place in `<bundle>.entity.<entityname>.<field>`
|
||||||
|
|
||||||
|
5. **Is this text used in forms?**
|
||||||
|
→ `<bundle>.form.<field>` or `<bundle>.form.<action>`
|
||||||
|
|
||||||
|
6. **Is this text related to exports?**
|
||||||
|
→ `<bundle>.export.<feature>.<column>`
|
||||||
|
|
||||||
|
7. **Is it related to filtering, searching or parameters?**
|
||||||
|
→ `<bundle>.filter.<name>` or
|
||||||
|
→ `<bundle>.filter.<feature>.<field>` for nested filters
|
||||||
|
|
||||||
|
## Examples Based on Translations Within ChillActivityBundle
|
||||||
|
|
||||||
|
Below are concrete examples from `ChillActivityBundle`, refactored according to the guidelines.
|
||||||
|
|
||||||
|
### General activity keys
|
||||||
|
|
||||||
|
Instead of scattered keys like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Show the activity
|
||||||
|
Edit the activity
|
||||||
|
Activity
|
||||||
|
Duration time
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
We use:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.general.show
|
||||||
|
activity.general.edit
|
||||||
|
activity.general.title
|
||||||
|
activity.general.duration
|
||||||
|
activity.general.travel_time
|
||||||
|
activity.general.attendee
|
||||||
|
activity.general.remark
|
||||||
|
activity.general.no_comments
|
||||||
|
```
|
||||||
|
|
||||||
|
### Forms
|
||||||
|
|
||||||
|
Instead of keys like:
|
||||||
|
|
||||||
|
```
|
||||||
|
Activity creation
|
||||||
|
Save activity
|
||||||
|
Reset form
|
||||||
|
Choose a type
|
||||||
|
```
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.form.title_create
|
||||||
|
activity.form.save
|
||||||
|
activity.form.reset
|
||||||
|
activity.form.choose_type
|
||||||
|
activity.form.choose_duration
|
||||||
|
```
|
||||||
|
|
||||||
|
Long lists (like durations) should be grouped:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.form.duration.5min
|
||||||
|
activity.form.duration.10min
|
||||||
|
activity.form.duration.15min
|
||||||
|
activity.form.duration.1h
|
||||||
|
activity.form.duration.1h30
|
||||||
|
activity.form.duration.2h
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Entities
|
||||||
|
|
||||||
|
Entity fields should follow:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.entity.activity.date
|
||||||
|
activity.entity.activity.comment
|
||||||
|
activity.entity.activity.deleted
|
||||||
|
activity.entity.location.name
|
||||||
|
activity.entity.location.type
|
||||||
|
```
|
||||||
|
|
||||||
|
### Controller messages
|
||||||
|
|
||||||
|
Instead of strings as keys:
|
||||||
|
|
||||||
|
```
|
||||||
|
'Success : activity created!'
|
||||||
|
'The form is not valid. The activity has not been created !'
|
||||||
|
```
|
||||||
|
|
||||||
|
Use:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.controller.success_created
|
||||||
|
activity.controller.error_invalid_create
|
||||||
|
activity.controller.success_updated
|
||||||
|
activity.controller.error_invalid_update
|
||||||
|
```
|
||||||
|
|
||||||
|
### Roles
|
||||||
|
|
||||||
|
Access control keys should be:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.role.create
|
||||||
|
activity.role.update
|
||||||
|
activity.role.see
|
||||||
|
activity.role.see_details
|
||||||
|
activity.role.delete
|
||||||
|
activity.role.stats
|
||||||
|
activity.role.list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.admin.configuration
|
||||||
|
activity.admin.types
|
||||||
|
activity.admin.reasons
|
||||||
|
activity.admin.reason_category
|
||||||
|
activity.admin.presence
|
||||||
|
```
|
||||||
|
|
||||||
|
### CRUD
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.crud.type.title_new
|
||||||
|
activity.crud.type.title_edit
|
||||||
|
activity.crud.presence.title_new
|
||||||
|
```
|
||||||
|
|
||||||
|
### Activity Reason
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.reason.list
|
||||||
|
activity.reason.create
|
||||||
|
activity.reason.active
|
||||||
|
activity.reason.category
|
||||||
|
activity.reason.entity_title
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exports
|
||||||
|
|
||||||
|
Group them logically:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.export.person.count.title
|
||||||
|
activity.export.person.count.description
|
||||||
|
activity.export.person.count.header
|
||||||
|
|
||||||
|
activity.export.period.sum_duration.title
|
||||||
|
activity.export.period.sum_duration.description
|
||||||
|
activity.export.period.sum_duration.header
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filters
|
||||||
|
|
||||||
|
Use hierarchical filters:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.filter.by_reason
|
||||||
|
activity.filter.by_type
|
||||||
|
activity.filter.by_date
|
||||||
|
activity.filter.by_location
|
||||||
|
activity.filter.by_sent_received
|
||||||
|
activity.filter.by_user
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aggregators
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.aggregator.reason.by_category
|
||||||
|
activity.aggregator.reason.level
|
||||||
|
activity.aggregator.user.by_scope
|
||||||
|
activity.aggregator.user.by_job
|
||||||
|
```
|
||||||
|
|
||||||
|
## Global/Shared Keys
|
||||||
|
|
||||||
|
Keys like the following **must not be redeclared** in each bundle:
|
||||||
|
|
||||||
|
- First name
|
||||||
|
- Last name
|
||||||
|
- Username
|
||||||
|
- ID
|
||||||
|
- Type
|
||||||
|
- Duration
|
||||||
|
- Comment
|
||||||
|
- Date
|
||||||
|
- Location
|
||||||
|
- Present / Not present
|
||||||
|
- Add / Edit / Delete / Save / Update
|
||||||
|
|
||||||
|
These belong in `common` or `main`:
|
||||||
|
|
||||||
|
```
|
||||||
|
common.firstname
|
||||||
|
common.lastname
|
||||||
|
common.username
|
||||||
|
common.id
|
||||||
|
common.type
|
||||||
|
common.comment
|
||||||
|
common.date
|
||||||
|
common.location
|
||||||
|
common.present
|
||||||
|
common.absent
|
||||||
|
common.add
|
||||||
|
common.edit
|
||||||
|
common.delete
|
||||||
|
common.save
|
||||||
|
common.update
|
||||||
|
```
|
||||||
|
|
||||||
|
## Naming Directives Summary
|
||||||
|
|
||||||
|
- **snake_case**
|
||||||
|
- **namespaced with dots**
|
||||||
|
- **bundle prefix for bundle-specific concepts**
|
||||||
|
- **common or main for shared concepts**
|
||||||
|
- **avoid free-floating keys (without namespace)**
|
||||||
|
- **reuse common keys wherever possible**
|
||||||
|
|
||||||
|
## Migration Strategy (Optional)
|
||||||
|
|
||||||
|
To apply this structure progressively:
|
||||||
|
|
||||||
|
1. New keys must follow these guidelines.
|
||||||
|
2. Existing keys may remain as-is until refactored.
|
||||||
|
3. When refactoring:
|
||||||
|
- Move cross-bundle keys to ChillMainBundle and possible `common` namespace.
|
||||||
|
- Replace duplicated keys with shared ones.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Avoiding Duplicate Translations
|
||||||
|
|
||||||
|
## 1. Use Shared Namespaces
|
||||||
|
|
||||||
|
Two namespaces must be used for shared translations:
|
||||||
|
|
||||||
|
- `common.*` — generic UI concepts (save, delete, date, name, etc.)
|
||||||
|
|
||||||
|
If a translation may be reused in multiple bundles, it must be placed in the `common` namespace or in ChillMainBundle.
|
||||||
|
|
||||||
|
## 2. Bundle-Specific Keys
|
||||||
|
|
||||||
|
Keys belonging only to one bundle or one feature are namespaced inside that bundle:
|
||||||
|
|
||||||
|
```
|
||||||
|
activity.<feature>.<element>
|
||||||
|
person.<feature>.<element>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Search Before Creating
|
||||||
|
|
||||||
|
Before adding a new translation key, developers must:
|
||||||
|
|
||||||
|
1. For common translations like "enregistrer/opslaan" look in the `common` namespace.
|
||||||
|
2. Search in Loco or translations for existing values.
|
||||||
|
|
||||||
|
If a suitable key exists, reuse it.
|
||||||
|
|
||||||
|
## 4. Only Create a New Key When Necessary
|
||||||
|
|
||||||
|
Create a new key only when the text is:
|
||||||
|
|
||||||
|
- specific to the bundle
|
||||||
|
- specific to the feature
|
||||||
|
- not reusable elsewhere
|
||||||
|
|
||||||
|
## 5. Progressive Cleanup
|
||||||
|
|
||||||
|
Old duplicates may remain temporarily. When updating code in an area, clean duplicate values by moving them into `common` or `main`.
|
||||||
|
|
||||||
|
## General Workflow
|
||||||
|
|
||||||
|
- **Reuse shared keys** within `common` namespace.
|
||||||
|
- **Search before creating** new keys.
|
||||||
|
- **Namespace bundle-specific keys** under their bundle.
|
||||||
|
- **Refactor progressively** when touching old code.
|
||||||
@@ -1,419 +0,0 @@
|
|||||||
============================================
|
|
||||||
Directives for creating new translation keys
|
|
||||||
============================================
|
|
||||||
|
|
||||||
These directives are meant to ensure better consistency across bundles, avoid duplication, and make keys more predictable.
|
|
||||||
|
|
||||||
|
|
||||||
General Principles
|
|
||||||
==================
|
|
||||||
|
|
||||||
1. **Use lowercase snake_case for all keys**
|
|
||||||
|
|
||||||
2. **Use dot-separated namespaces**
|
|
||||||
The dot is used to reflect:
|
|
||||||
- bundle
|
|
||||||
- feature
|
|
||||||
- sub-feature
|
|
||||||
- key type
|
|
||||||
|
|
||||||
3. **Do not use spaces in keys**
|
|
||||||
|
|
||||||
4. **Avoid duplicating the same text in multiple places**
|
|
||||||
When a translation is needed, try a search for the translation value first and see if it exists elsewhere
|
|
||||||
|
|
||||||
5. **If a key is used across multiple bundles, it must live in ChillMainBundle.**
|
|
||||||
|
|
||||||
6. **If a key is used across multiple bundles and is a generic term, it must be placed in the ``common`` namespace.**
|
|
||||||
|
|
||||||
|
|
||||||
Key Structure
|
|
||||||
=============
|
|
||||||
|
|
||||||
We use the following structure:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
<scope>.<feature>.<sub-feature>.<key-type>
|
|
||||||
|
|
||||||
Where:
|
|
||||||
|
|
||||||
* ``<>`` identifies the bundle or shared context
|
|
||||||
* ``<feature>`` identifies the part of the module using the translation
|
|
||||||
* ``<element>`` describes the text purpose
|
|
||||||
* ``<subelement>`` for a multi-level element ( eg. activity.export.person.count.description)
|
|
||||||
|
|
||||||
Examples of scopes
|
|
||||||
------------------
|
|
||||||
|
|
||||||
* ``activity`` — ChillActivityBundle
|
|
||||||
* ``person`` — ChillPersonBundle
|
|
||||||
* ``common`` — neutral shared translation values
|
|
||||||
|
|
||||||
|
|
||||||
Naming Scopes
|
|
||||||
=============
|
|
||||||
|
|
||||||
1. **Bundle-specific keys**
|
|
||||||
|
|
||||||
For most things inside a bundle:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.<feature>.<element>
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.form.save
|
|
||||||
activity.list.title
|
|
||||||
activity.entity.type
|
|
||||||
activity.menu.activities
|
|
||||||
activity.controller.success_created
|
|
||||||
|
|
||||||
2. **Shared UI elements (buttons, labels, generic text)**
|
|
||||||
|
|
||||||
These belong in the ``common`` namespace in ChillMainBundle:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
common.save
|
|
||||||
common.delete
|
|
||||||
common.edit
|
|
||||||
common.filter
|
|
||||||
common.duration_time
|
|
||||||
|
|
||||||
Translation workflow
|
|
||||||
====================
|
|
||||||
|
|
||||||
Use the following workflow when deciding where a key belongs:
|
|
||||||
|
|
||||||
1. **Is this text used in more than one bundle?**
|
|
||||||
→ Place in ``main`` or ``common``
|
|
||||||
|
|
||||||
2. **Is this text generic UI (button, label, pagination, yes/no)?**
|
|
||||||
→ Place in ``common``
|
|
||||||
|
|
||||||
3. **Is this text specific to one bundle and one feature?**
|
|
||||||
→ Place in ``<bundle>.feature.<element>``
|
|
||||||
|
|
||||||
4. **Is this text related to an entity or value object?**
|
|
||||||
→ Place in ``<bundle>.entity.<entityname>.<field>``
|
|
||||||
|
|
||||||
5. **Is this text used in forms?**
|
|
||||||
→ ``<bundle>.form.<field>`` or ``<bundle>.form.<action>``
|
|
||||||
|
|
||||||
6. **Is this text related to exports?**
|
|
||||||
→ ``<bundle>.export.<feature>.<column>``
|
|
||||||
|
|
||||||
7. **Is it related to filtering, searching or parameters?**
|
|
||||||
→ ``<bundle>.filter.<name>`` or
|
|
||||||
→ ``<bundle>.filter.<feature>.<field>`` for nested filters
|
|
||||||
|
|
||||||
|
|
||||||
Examples based on translations within ChillActivityBundle
|
|
||||||
=========================================================
|
|
||||||
|
|
||||||
Below are concrete examples from ``ChillActivityBundle``,
|
|
||||||
refactored according to the guidelines.
|
|
||||||
|
|
||||||
|
|
||||||
General activity keys
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Instead of scattered keys like::
|
|
||||||
|
|
||||||
Show the activity
|
|
||||||
Edit the activity
|
|
||||||
Activity
|
|
||||||
Duration time
|
|
||||||
...
|
|
||||||
|
|
||||||
We use:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.general.show
|
|
||||||
activity.general.edit
|
|
||||||
activity.general.title
|
|
||||||
activity.general.duration
|
|
||||||
activity.general.travel_time
|
|
||||||
activity.general.attendee
|
|
||||||
activity.general.remark
|
|
||||||
activity.general.no_comments
|
|
||||||
|
|
||||||
|
|
||||||
Forms
|
|
||||||
-----
|
|
||||||
|
|
||||||
Instead of keys like::
|
|
||||||
|
|
||||||
Activity creation
|
|
||||||
Save activity
|
|
||||||
Reset form
|
|
||||||
Choose a type
|
|
||||||
|
|
||||||
Use:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.form.title_create
|
|
||||||
activity.form.save
|
|
||||||
activity.form.reset
|
|
||||||
activity.form.choose_type
|
|
||||||
activity.form.choose_duration
|
|
||||||
|
|
||||||
Long lists (like durations) should be grouped:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.form.duration.5min
|
|
||||||
activity.form.duration.10min
|
|
||||||
activity.form.duration.15min
|
|
||||||
activity.form.duration.1h
|
|
||||||
activity.form.duration.1h30
|
|
||||||
activity.form.duration.2h
|
|
||||||
...
|
|
||||||
|
|
||||||
Entities
|
|
||||||
--------
|
|
||||||
|
|
||||||
Entity fields should follow:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.entity.activity.date
|
|
||||||
activity.entity.activity.comment
|
|
||||||
activity.entity.activity.deleted
|
|
||||||
activity.entity.location.name
|
|
||||||
activity.entity.location.type
|
|
||||||
|
|
||||||
|
|
||||||
Controller messages
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
Instead of strings as keys::
|
|
||||||
|
|
||||||
'Success : activity created!'
|
|
||||||
'The form is not valid. The activity has not been created !'
|
|
||||||
|
|
||||||
Use:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.controller.success_created
|
|
||||||
activity.controller.error_invalid_create
|
|
||||||
activity.controller.success_updated
|
|
||||||
activity.controller.error_invalid_update
|
|
||||||
|
|
||||||
|
|
||||||
Roles
|
|
||||||
-----
|
|
||||||
|
|
||||||
Access control keys should be:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.role.create
|
|
||||||
activity.role.update
|
|
||||||
activity.role.see
|
|
||||||
activity.role.see_details
|
|
||||||
activity.role.delete
|
|
||||||
activity.role.stats
|
|
||||||
activity.role.list
|
|
||||||
|
|
||||||
|
|
||||||
Admin
|
|
||||||
-----
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.admin.configuration
|
|
||||||
activity.admin.types
|
|
||||||
activity.admin.reasons
|
|
||||||
activity.admin.reason_category
|
|
||||||
activity.admin.presence
|
|
||||||
|
|
||||||
|
|
||||||
CRUD
|
|
||||||
----
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.crud.type.title_new
|
|
||||||
activity.crud.type.title_edit
|
|
||||||
activity.crud.presence.title_new
|
|
||||||
|
|
||||||
|
|
||||||
Activity Reason
|
|
||||||
---------------
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.reason.list
|
|
||||||
activity.reason.create
|
|
||||||
activity.reason.active
|
|
||||||
activity.reason.category
|
|
||||||
activity.reason.entity_title
|
|
||||||
|
|
||||||
|
|
||||||
Exports
|
|
||||||
-------
|
|
||||||
|
|
||||||
Group them logically:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.export.person.count.title
|
|
||||||
activity.export.person.count.description
|
|
||||||
activity.export.person.count.header
|
|
||||||
|
|
||||||
activity.export.period.sum_duration.title
|
|
||||||
activity.export.period.sum_duration.description
|
|
||||||
activity.export.period.sum_duration.header
|
|
||||||
|
|
||||||
|
|
||||||
Filters
|
|
||||||
-------
|
|
||||||
|
|
||||||
Use hierarchical filters:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.filter.by_reason
|
|
||||||
activity.filter.by_type
|
|
||||||
activity.filter.by_date
|
|
||||||
activity.filter.by_location
|
|
||||||
activity.filter.by_sent_received
|
|
||||||
activity.filter.by_user
|
|
||||||
|
|
||||||
|
|
||||||
Aggregators
|
|
||||||
-----------
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.aggregator.reason.by_category
|
|
||||||
activity.aggregator.reason.level
|
|
||||||
activity.aggregator.user.by_scope
|
|
||||||
activity.aggregator.user.by_job
|
|
||||||
|
|
||||||
|
|
||||||
Global/Shared Keys
|
|
||||||
==================
|
|
||||||
|
|
||||||
Keys like the following **must not be redeclared** in each bundle:
|
|
||||||
|
|
||||||
- First name
|
|
||||||
- Last name
|
|
||||||
- Username
|
|
||||||
- ID
|
|
||||||
- Type
|
|
||||||
- Duration
|
|
||||||
- Comment
|
|
||||||
- Date
|
|
||||||
- Location
|
|
||||||
- Present / Not present
|
|
||||||
- Add / Edit / Delete / Save / Update
|
|
||||||
|
|
||||||
These belong in ``common`` or ``main``:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
common.firstname
|
|
||||||
common.lastname
|
|
||||||
common.username
|
|
||||||
common.id
|
|
||||||
common.type
|
|
||||||
common.comment
|
|
||||||
common.date
|
|
||||||
common.location
|
|
||||||
common.present
|
|
||||||
common.absent
|
|
||||||
common.add
|
|
||||||
common.edit
|
|
||||||
common.delete
|
|
||||||
common.save
|
|
||||||
common.update
|
|
||||||
|
|
||||||
|
|
||||||
Naming directives summary
|
|
||||||
==========================
|
|
||||||
|
|
||||||
* **snake_case**
|
|
||||||
* **namespaced with dots**
|
|
||||||
* **bundle prefix for bundle-specific concepts**
|
|
||||||
* **common or main for shared concepts**
|
|
||||||
* **avoid free-floating keys (without namespace)**
|
|
||||||
* **reuse common keys wherever possible**
|
|
||||||
|
|
||||||
|
|
||||||
Migration Strategy (Optional)
|
|
||||||
=============================
|
|
||||||
|
|
||||||
To apply this structure progressively:
|
|
||||||
|
|
||||||
1. New keys must follow these guidelines.
|
|
||||||
2. Existing keys may remain as-is until refactored.
|
|
||||||
3. When refactoring:
|
|
||||||
- Move cross-bundle keys to ChillMainBundle and possible `common` namespace.
|
|
||||||
- Replace duplicated keys with shared ones.
|
|
||||||
|
|
||||||
===========================================
|
|
||||||
Avoiding duplicate translations
|
|
||||||
===========================================
|
|
||||||
|
|
||||||
1. Use Shared Namespaces
|
|
||||||
========================
|
|
||||||
|
|
||||||
Two namespaces must be used for shared translations:
|
|
||||||
|
|
||||||
* ``common.*`` — generic UI concepts (save, delete, date, name, etc.)
|
|
||||||
|
|
||||||
If a translation may be reused in multiple bundles, it must be placed
|
|
||||||
in the ``common`` namespace or in ChillMainBundle.
|
|
||||||
|
|
||||||
2. Bundle-Specific Keys
|
|
||||||
=======================
|
|
||||||
|
|
||||||
Keys belonging only to one bundle or one feature are namespaced inside that
|
|
||||||
bundle:
|
|
||||||
|
|
||||||
.. code-block:: text
|
|
||||||
|
|
||||||
activity.<feature>.<element>
|
|
||||||
person.<feature>.<element>
|
|
||||||
|
|
||||||
3. Search Before Creating
|
|
||||||
=========================
|
|
||||||
|
|
||||||
Before adding a new translation key, developers must:
|
|
||||||
|
|
||||||
1. For common translations like: "enregistrer/opslaan" look in the `common` namespace.
|
|
||||||
3. Search in Loco or translations for existing values.
|
|
||||||
|
|
||||||
If a suitable key exists, reuse it.
|
|
||||||
|
|
||||||
4. Only Create a New Key When Necessary
|
|
||||||
=======================================
|
|
||||||
|
|
||||||
Create a new key only when the text is:
|
|
||||||
|
|
||||||
* specific to the bundle
|
|
||||||
* specific to the feature
|
|
||||||
* not reusable elsewhere
|
|
||||||
|
|
||||||
6. Progressive Cleanup
|
|
||||||
======================
|
|
||||||
|
|
||||||
Old duplicates may remain temporarily. When updating code in an area, clean
|
|
||||||
duplicate values by moving them into ``common`` or ``main``.
|
|
||||||
|
|
||||||
General workflow
|
|
||||||
================
|
|
||||||
|
|
||||||
* **Reuse shared keys** within ``common`` namespace.
|
|
||||||
* **Search before creating** new keys.
|
|
||||||
* **Namespace bundle-specific keys** under their bundle.
|
|
||||||
* **Refactor progressively** when touching old code.
|
|
||||||
139
docs/source/development/translation_provider.md
Normal file
139
docs/source/development/translation_provider.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# Managing Translations Within CHILL Using Loco as a Translation Provider
|
||||||
|
|
||||||
|
Within CHILL we make use of Symfony's translation component together with *Loco* as an external translation provider. Using this setup centralises translations in a single online location (Loco), while still allowing developers to create and update translation keys locally in the project (YAML files).
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
We use the following workflow:
|
||||||
|
|
||||||
|
- Developers create translation keys in YAML files inside each bundle.
|
||||||
|
- Keys are written in **English**.
|
||||||
|
- Application UI defaults to **French**, with **Dutch** as an additional locale (other languages can be added in the future).
|
||||||
|
- Loco acts as the central translation memory and synchronisation source.
|
||||||
|
- Loco Symfony package was installed so that built-in translation commands can be used to push/pull content between Loco and the local project.
|
||||||
|
|
||||||
|
## Translation Directory Structure
|
||||||
|
|
||||||
|
Each bundle contains its own `translations` directory, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
chill-bundles/
|
||||||
|
ChillCoreBundle/
|
||||||
|
translations/
|
||||||
|
messages.fr.yml
|
||||||
|
messages.nl.yml
|
||||||
|
ChillPersonBundle/
|
||||||
|
translations/
|
||||||
|
messages.fr.yml
|
||||||
|
messages.nl.yml
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The translation configuration is defined in `config/packages/translation.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
framework:
|
||||||
|
default_locale: '%env(resolve:LOCALE)%'
|
||||||
|
translator:
|
||||||
|
default_path: '%kernel.project_dir%/translations'
|
||||||
|
fallbacks:
|
||||||
|
- '%env(resolve:LOCALE)%'
|
||||||
|
- 'en'
|
||||||
|
providers:
|
||||||
|
loco:
|
||||||
|
dsn: '%env(LOCO_DSN)%'
|
||||||
|
domains: [ 'messages' ]
|
||||||
|
locales: [ 'fr', 'nl' ]
|
||||||
|
```
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
- `en` is the **source locale** in Loco.
|
||||||
|
- `fr` and `nl` are the **application locales**.
|
||||||
|
- `domains: [messages]` means only `messages.*.yml` files are pushed.
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
In `.env`:
|
||||||
|
|
||||||
|
```
|
||||||
|
LOCALE=fr
|
||||||
|
```
|
||||||
|
|
||||||
|
In `.env.local`:
|
||||||
|
|
||||||
|
```
|
||||||
|
LOCO_DSN="loco://API_KEY@default"
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace `API_KEY` with the key provided by Loco.
|
||||||
|
|
||||||
|
## Working with Loco
|
||||||
|
|
||||||
|
Loco shows all translation keys under three languages:
|
||||||
|
|
||||||
|
- **English (source)** — keys are listed but remain "untranslated"
|
||||||
|
- **French** — translated strings for French users
|
||||||
|
- **Dutch** — translated strings for Dutch users
|
||||||
|
|
||||||
|
Note: Don't add translations directly in the English column. This column simply represents the *key*.
|
||||||
|
|
||||||
|
## Pushing Translations to Loco
|
||||||
|
|
||||||
|
You can push local translations to Loco using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
symfony console translation:push loco --locales=fr --locales=nl --force
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
- Upload all French and Dutch translation values from `*.fr.yml` and `*.nl.yml` files
|
||||||
|
- Ensure Loco stays in sync with local YAML files
|
||||||
|
- Create any missing keys in Loco
|
||||||
|
|
||||||
|
## Pulling Translations from Loco
|
||||||
|
|
||||||
|
When translators update strings in Loco, developers can fetch updates with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
symfony console translation:pull loco --locales=fr --locales=nl --force
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
|
||||||
|
- Download the latest French and Dutch translations
|
||||||
|
- Overwrite the local YAML files with Loco's content
|
||||||
|
- Keep everything consistent across the team
|
||||||
|
|
||||||
|
## Adding New Translation Keys (Developer Workflow)
|
||||||
|
|
||||||
|
1. Add a new key directly in the appropriate YAML file, for example:
|
||||||
|
|
||||||
|
```
|
||||||
|
chill-bundles/ChillPersonBundle/translations/messages.fr.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
Example key:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
person.form.submit: "Envoyer"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add Dutch translation as well if you can (otherwise leave empty to be translated within Loco later):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
person.form.submit: "Verzenden"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Run a push to send the new key to Loco:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
symfony console translation:push loco --locales=fr --locales=nl --force
|
||||||
|
```
|
||||||
|
|
||||||
|
4. The key will now appear in Loco for translation management.
|
||||||
|
|
||||||
|
Note: English appears as "untranslated", because it is merely the source language.
|
||||||
@@ -1,148 +0,0 @@
|
|||||||
=======================================================================
|
|
||||||
Managing translations within CHILL using Loco as a translation provider
|
|
||||||
=======================================================================
|
|
||||||
|
|
||||||
Within CHILL we make use of Symfony's translation component together with *Loco* as an external
|
|
||||||
translation provider. Using this setup centralise translations in a single online
|
|
||||||
location (Loco), while still allowing developers to create and update
|
|
||||||
translation keys locally in the project (YAML files).
|
|
||||||
|
|
||||||
Workflow
|
|
||||||
========
|
|
||||||
|
|
||||||
We use the following workflow:
|
|
||||||
|
|
||||||
* Developers create translation keys in YAML files inside each bundle.
|
|
||||||
* Keys are written in **English**.
|
|
||||||
* Application UI defaults to **French**, with **Dutch** as an additional locale (other languages can be added in the future).
|
|
||||||
* Loco acts as the central translation memory and synchronisation source.
|
|
||||||
* Loco Symfony package was installed so that built-in translation commands can be used to push/pull content
|
|
||||||
between Loco and the local project.
|
|
||||||
|
|
||||||
|
|
||||||
Translation directory structure
|
|
||||||
===============================
|
|
||||||
|
|
||||||
Each bundle contains its own ``translations`` directory, for example::
|
|
||||||
|
|
||||||
chill-bundles/
|
|
||||||
ChillCoreBundle/
|
|
||||||
translations/
|
|
||||||
messages.fr.yml
|
|
||||||
messages.nl.yml
|
|
||||||
ChillPersonBundle/
|
|
||||||
translations/
|
|
||||||
messages.fr.yml
|
|
||||||
messages.nl.yml
|
|
||||||
...
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
=============
|
|
||||||
|
|
||||||
The translation configuration is defined in
|
|
||||||
``config/packages/translation.yaml``::
|
|
||||||
|
|
||||||
framework:
|
|
||||||
default_locale: '%env(resolve:LOCALE)%'
|
|
||||||
translator:
|
|
||||||
default_path: '%kernel.project_dir%/translations'
|
|
||||||
fallbacks:
|
|
||||||
- '%env(resolve:LOCALE)%'
|
|
||||||
- 'en'
|
|
||||||
providers:
|
|
||||||
loco:
|
|
||||||
dsn: '%env(LOCO_DSN)%'
|
|
||||||
domains: [ 'messages' ]
|
|
||||||
locales: [ 'fr', 'nl' ]
|
|
||||||
|
|
||||||
Note:
|
|
||||||
|
|
||||||
* ``en`` is the **source locale** in Loco.
|
|
||||||
* ``fr`` and ``nl`` are the **application locales**.
|
|
||||||
* ``domains: [messages]`` means only ``messages.*.yml`` files are pushed.
|
|
||||||
|
|
||||||
|
|
||||||
Environment variables
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
In ``.env``::
|
|
||||||
|
|
||||||
LOCALE=fr
|
|
||||||
|
|
||||||
In ``.env.local``::
|
|
||||||
|
|
||||||
LOCO_DSN="loco://API_KEY@default"
|
|
||||||
|
|
||||||
Replace ``API_KEY`` with the key provided by Loco.
|
|
||||||
|
|
||||||
|
|
||||||
Working with Loco
|
|
||||||
=================
|
|
||||||
|
|
||||||
Loco shows all translation keys under three languages:
|
|
||||||
|
|
||||||
* **English (source)** — keys are listed but remain “untranslated”
|
|
||||||
* **French** — translated strings for French users
|
|
||||||
* **Dutch** — translated strings for Dutch users
|
|
||||||
|
|
||||||
Note: Don't add translations directly in the English column.
|
|
||||||
This column simply represents the *key*.
|
|
||||||
|
|
||||||
|
|
||||||
Pushing translations to Loco
|
|
||||||
============================
|
|
||||||
|
|
||||||
You can push local translations to Loco using:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
symfony console translation:push loco --locales=fr --locales=nl --force
|
|
||||||
|
|
||||||
This will:
|
|
||||||
|
|
||||||
* Upload all French and Dutch translation values from ``*.fr.yml`` and
|
|
||||||
``*.nl.yml`` files
|
|
||||||
* Ensures Loco stays in sync with local YAML files
|
|
||||||
* Creates any missing keys in Loco
|
|
||||||
|
|
||||||
|
|
||||||
Pulling translations from Loco
|
|
||||||
==============================
|
|
||||||
|
|
||||||
When translators update strings in Loco, developers can fetch updates with:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
symfony console translation:pull loco --locales=fr --locales=nl --force
|
|
||||||
|
|
||||||
This will:
|
|
||||||
|
|
||||||
* Download the latest French and Dutch translations
|
|
||||||
* Overwrite the local YAML files with Loco’s content
|
|
||||||
* Keep everything consistent across the team
|
|
||||||
|
|
||||||
|
|
||||||
Adding new translation keys (Developer workflow)
|
|
||||||
================================================
|
|
||||||
|
|
||||||
1. Add a new key directly in the appropriate YAML file, for example::
|
|
||||||
|
|
||||||
chill-bundles/ChillPersonBundle/translations/messages.fr.yml
|
|
||||||
|
|
||||||
Example key::
|
|
||||||
|
|
||||||
person.form.submit: "Envoyer"
|
|
||||||
|
|
||||||
2. Add Dutch translation as well if you can (otherwise leave empty to be translated within Loco later)::
|
|
||||||
|
|
||||||
person.form.submit: "Verzenden"
|
|
||||||
|
|
||||||
3. Run a push to send the new key to Loco:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
symfony console translation:push loco --locales=fr --locales=nl --force
|
|
||||||
|
|
||||||
4. The key will now appear in Loco for translation management.
|
|
||||||
|
|
||||||
Note: English appears as “untranslated”, because it is merely the source language
|
|
||||||
Reference in New Issue
Block a user