Translate rst files to md format and add new index entries for translation directives and provider

This commit is contained in:
2025-12-10 04:32:19 +01:00
parent c19b2e18ad
commit 09ef95d13e
5 changed files with 517 additions and 567 deletions

View File

@@ -12,6 +12,8 @@ As Chill relies on the [symfony ](http://symfony.com) framework, reading the fra
- [Messages to users](messages-to-users.md)
- [Pagination](pagination.md)
- [Localisation](localisation.md)
- [Translation directives](translation_directives.md)
- [Translation provider](translation_provider.md)
- [Logging](logging.md)
- [Database migrations](migrations.md)
- [Searching](searching.md)

View 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.

View File

@@ -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.

View 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.

View File

@@ -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 Locos 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