8000 [Translator] Message key syntax and object const for javascript UX Translator · Issue #2619 · symfony/ux · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[Translator] Message key syntax and object const for javascript UX Translator #2619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dukeofgaming opened this issue Mar 3, 2025 · 4 comments

Comments

@dukeofgaming
Copy link
dukeofgaming commented Mar 3, 2025

I am delegating translations to the front-end by sending the message through REST API requests to a React component.

The problem I'm facing is that I am forced to create a decoder ring that seems like boilerplate code: and pretty tedious:

import {
    trans,
...
    OTP_FIELD_LABEL,
    OTP_EXCEPTIONS_USERNAME_NOT_FOUND
} from '../../../translator';

const messages = {
    'OTP_EXCEPTIONS_USERNAME_NOT_FOUND' : OTP_EXCEPTIONS_USERNAME_NOT_FOUND
};

That decoder ring is const messages.

While reading the documentation, I noticed two things:

https://symfony.com/doc/current/translation.html#using-real-or-keyword-messages

The first is that I could refer to this translation in the backend as "otp.exceptions.username_not_found".

The second is that, I wrote the translations in YAML... had I written it in PHP I could send the whole translation file as a JSON object (not efficent obviously.

While importing consts from the translator is usable, it is also an error prone process, and more than once I've found myself using a const that I didn't import.

The feature request is to either or both be able to:

  1. Use trans() with the key notation and not having to import consts:
trans("otp.exceptions.username_not_found")
  1. Have a single objects to import, instead of all the consts:
import{
   trans,
   otp, //or OTP, but lowercase would make it consistent
} from '../../../translator';

...

trans(otp.exceptions.username_not_found);

...

trans(otp.field.label);

Why I think this is important:

  1. As it turned out, the decoder ring is sort of worth the effort because I have REST API unit tests (well, integration tests)... and sending the key notation as the message to delegate translation makes my tests very anti-fragile / not brittle / resilient.

I could be switching around the text and even the variables inside the text (I'm using the ICU format) and as long as the message id / structure remains the same, the tests don't break

  1. Either the key notation or the const object adds consistency, and also reduce redundant work. As pretty as the caps are (e.g. "const OTP_EXCEPTIONS_USERNAME_NOT_FOUND"... this is totally different than what one would use in PHP.

One of the goals of Symfony UX Translator (I assume) is to reuse translations in the front-end to reduce redundant work. The object const notation not only would make it look consistent with the key notation in PHP, but also reduce cognitive load and boilerplate code (multiple imports).

Boilerplate code not only makes the task longer, boring, but is also error prone.

  1. Having the key notation available in Javascript would mean that one wouldn't even have to import the translation consts (could be imported in the background). This would make it super simple to understand the process, help improve API consistency when the translations are being delegated by the backend and make the whole process less error proof.

  2. There is a term in the manufacturing world in quality management systems for "error-proofing" called poka-yoke. Error-proofing would essentially translate to Developer Experience :)

Image

In summary

This feature would lead to more consistent, less error prone, less boilerplate and less cognitive load while working with front-end translation, and also make it very easy to delegate translations from the backend while making enabling more anti-fragile API testing.

I think this feature can be added without compatibility issues by enabling something like this for the key notation:

trans.key("otp.exceptions.username_not_found", ...)

And for the const objects, just generating that code in addition to the uppercase consts:

const otp = {
  field : {
    label: OTP_FIELD_LABEL    
  },
  exceptions: {
      username_not_found: OTP_EXCEPTIONS_USERNAME_NOT_FOUND
  }
};
import {trans, otp } from '../../../translator';

...

trans(otp.exceptions.username_not_found);

...

trans(otp.field.label);
@Kocal
Copy link
Member
Kocal commented Mar 3, 2025

Thanks for your suggestion, I understand having different cases for the same translation key is a problem, it makes code updates or refactoring harder than it should be.

However, translations keys were generated like that (export const KEY_1 = ...; export const KEY_2 = ...; etc) in order to reduce the bundle size thanks to Webpack (or any other bundler) tree-shaking.

Before implementing something like const otp = { field: { label: OTP_FIELD_LABEL }}, we must ensure that it can be tree-shakable.

About trans.key("otp.exceptions.username_not_found", ...), I don't think it would work as it mean that trans() must access the translations keys, which is not the way it currently works. However, maybe trans(keys['otp.exceptions.username_not_found']) is doable, and it does not require a new API.

@dukeofgaming
Copy link
Author
dukeofgaming commented Mar 4, 2025

Thanks for your suggestion, I understand having different cases for the same translation key is a problem, it makes code updates or refactoring harder than it should be.

However, translations keys were generated like that (export const KEY_1 = ...; export const KEY_2 = ...; etc) in order to reduce the bundle size thank's to Webpack (or any other bundler) tree-shaking.

Before implementing something like const otp = { field: { label: OTP_FIELD_LABEL }}, we must ensure that it can be tree-shakable.

About trans.key("otp.exceptions.username_not_found", ...), I don't think it would work as it mean that trans() must access the translations keys, which is not the way it currently works. However, maybe trans(keys['otp.exceptions.username_not_found']) is doable, and it does not require a new API.

Interesting, I'm doing some refactoring in the direction I proposed and see what I end up with, so the context of tree-shaking and bundling is very useful to inform what I'm doing.

@dukeofgaming
Copy link
Author

I just noticed the generated translation objects actually have an "id" attribute with the key:

export const OTP_EXCEPTIONS_USERNAME_NOT_FOUND = {"id":"otp.exceptions.username_not_found","translations":{"messages+intl-icu":{"en":"...","fr":"..."}}};

@Kocal
Copy link
Member
Kocal commented Mar 4, 2025

Yes, it is used when the message were not able to be translated https://github.com/symfony/ux/blob/2.x/src/Translator/assets/src/translator.ts#L136-L138 & https://github.com/symfony/ux/blob/2.x/src/Translator/assets/src/translator.ts#L174

But we can't use it to find the message, since the Translator is not aware about all messages.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants
0