diff --git a/docs/source/development/translation_directives.rst b/docs/source/development/translation_directives.rst new file mode 100644 index 000000000..106ffb3ab --- /dev/null +++ b/docs/source/development/translation_directives.rst @@ -0,0 +1,419 @@ +============================================ +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 + + ... + +Where: + +* ``<>`` identifies the bundle or shared context +* ```` identifies the part of the module using the translation +* ```` describes the text purpose +* ```` 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.. + + 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 ``.feature.`` + +4. **Is this text related to an entity or value object?** + → Place in ``.entity..`` + +5. **Is this text used in forms?** + → ``.form.`` or ``.form.`` + +6. **Is this text related to exports?** + → ``.export..`` + +7. **Is it related to filtering, searching or parameters?** + → ``.filter.`` or + → ``.filter..`` 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.. + person.. + +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.