Skip to content
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

[Proposal] Simplify the Translation Process #2

Closed
NetanelBasal opened this issue Aug 11, 2019 · 11 comments
Closed

[Proposal] Simplify the Translation Process #2

NetanelBasal opened this issue Aug 11, 2019 · 11 comments
Labels
enhancement New feature or request

Comments

@NetanelBasal
Copy link
Contributor

NetanelBasal commented Aug 11, 2019

This proposal tries to simplify the translation process. The issues we face today are:

  • The developer has to come up with the properties names.
  • Redundant nested object, which blows the bundle, thus takes more memory.
  • Duplicate keys and texts.
  • Merge conflicts.

The idea is to treat each string as a ״raw string״. Word concatenation isn't valid anyway in most cases because the translators need to know the context.

We expose a hash function through the context object that by default, returns the given string.

<ng-template transloco let-t>
  {{ t.hash('Hello world') }}

  <span>{{ t.hash('Login') }}</span>
</ng-template>

Now, let's run it through hashing function:

hash('Hello world') // The value will always be: 261689226
hash('Login') // 180154982

Based on these values, we can automatically generate a clean translation file in build time:

// en.json
{
  "261689226": "Hello world",
  "180154982": "Login"
}

And change each usage to the corresponding hash:

<ng-template transloco let-t>
  {{ t['261689226'] }}

  <span>{{ t['180154982'] }}</span>
</ng-template>

Disadvantages

  • ?
  • ?
@NetanelBasal NetanelBasal added the enhancement New feature or request label Aug 11, 2019
@NetanelBasal NetanelBasal pinned this issue Aug 11, 2019
@jsverse jsverse deleted a comment from shaharkazaz Aug 11, 2019
@jsverse jsverse deleted a comment from itayod Aug 11, 2019
@jsverse jsverse deleted a comment from itayod Aug 11, 2019
@jsverse jsverse deleted a comment from shaharkazaz Aug 11, 2019
@shaharkazaz
Copy link
Collaborator

shaharkazaz commented Aug 12, 2019

We can also support dynamic params:

{{ t.hash('some {{dynamic}} thing', { dynamic: propFromComponent }) }}

Will transpile to:

{{ t['1234567'] | translocoParams: { dynamic: propFromComponent} }}

@shaharkazaz
Copy link
Collaborator

shaharkazaz commented Aug 13, 2019

We can also support dynamic strings. For example:
In the template:

{{ t[dynamicProperty] }} 

And in the component:

import { hash } from '@ngneat/transloco';
...
this.dynamicProperty = condition ? hash('hello world') : hash('bye');

Will transpile to:

import { hash } from '@ngneat/transloco';
...
this.dynamicProperty = condition ? '261689226' :  '781689346';

@ronnetzer
Copy link

Love the idea but unfortunately, it fails in our use cases.

  1. we receive our translations file from our client's backend, which stores them as CSV (so that the content guys can change and edit each key's value to whatever they want), and converts them to json when we request the translation. so your approach won't be readable for the guys responsible for the translations.
  2. in another project (this client also does the CSV thing), there's a "debug" mode which enables to view full translation path in a tooltip when you hover on a translated value. (this is used by QA and content guys)

and besides that, you kinda bind your code to the content instead of binding it to the translation key that will probably won't change as frequently as the content of that key

@shaharkazaz
Copy link
Collaborator

@ronnetzer

  1. I'm not sure how this affects what you describe since what matters is the value and not the keys (they are not supposed to change otherwise it breaks the translations), so if they can see the translations I'm not sure where this fails.
  2. What is the usage of this debug mode? what's the value of seeing the key? we want to detach this entire process from the developer.

From my experience (working with a pretty large translated app) the content rarely changes, and even if you do need to change the content once in a while I'd rather use find replace and gain all the benefits of working this way.

@ronnetzer
Copy link

@shaharkazaz

  1. The point is that the developers shouldn't be aware of content changes and the client should be free to edit the content whenever he wants without any development cost. And with your approach (if I understand correctly) when marketing decides to change "Hello World" to "Good Morning Planet" nothing breaks but now the template (t.hash('Hello world')) will return "Good Morning Planet".
    So now you have 2 options, keep it that way or align with the content and update t.hash value.
    most of us would probably go with option 2, which in the end creates a new translation file with this new hash (what happens to the previous hash?) that will need to be translated manually (again) to the rest of the languages (?).
  2. Mainly by marketing and QA team. The client demands that if a translation to a key is missing, we won't show any other fallback text (including the key) and only in debug mode you can see the missing translation keys (i know its weird but its not our decision).

Another use case that I think will break:
We have a toaster for displaying error messages returned from the backend based on endpoint and error code, let's say the endpoints are in the format of '.../page_name/action_name'.
for each action_name we have a default error message in case we don't have a translation for an error code and the same goes if we don't have translations for an action_name at all and the same for page_name. So how something like that can be done using your approach? (getting human error messages from the backend is not possible)

From my experience (working with a pretty large translated app) the content rarely changes, and even if you do need to change the content once in a while I'd rather use find replace and gain all the benefits of working this way.

The size of the app is not the issue, the issue is the companies and clients. Each client is different and as a 3rd party library, you cannot assume that the content won't change and sacrifice maintenance for performance.

@shaharkazaz
Copy link
Collaborator

@ronnetzer

(if I understand correctly) when marketing decides to change "Hello World" to "Good Morning Planet" nothing breaks but now the template (t.hash('Hello world')) will return "Good Morning Planet".

It seems that you misunderstood, if you want to change "Hello world" to "Good Morning Planet" t.hash('Hello world') must change to t.hash('Good Morning Planet')

which in the end creates a new translation file with this new hash (what happens to the previous hash?) that will need to be translated manually (again) to the rest of the languages (?).

I'm not sure how is this different from changing the value of a key? it won't need to be translated to every language as well? The idea is that the new translation file created will be merged with the previous one so just this new key needs translation ( as you would need to translate it if you just change an existing key's value).

Mainly by marketing and QA team. The client demands that if a translation to a key is missing, we won't show any other fallback text (including the key) and only in debug mode you can see the
missing translation keys (i know its weird but its not our decision).

This can be achieved, allowing you to pass a custom missing key handler via an injection token is practically ready so you could do whatever you want in case there is no key.

For each action_name we have a default error message in case we don't have a translation for an error code and the same goes if we don't have translations for an action_name at all and the same for page_name. So how something like that can be done using your approach? (getting human error messages from the backend is not possible)

This proposal is not all or nothing, the idea is to implement it where you can.
You can still use the regular key values to support these cases, where you will have all you server keys and the hashing solution is for the static translations in your app.

The size of the app is not the issue, the issue is the companies and clients. Each client is different and as a 3rd party library, you cannot assume that the content won't change and sacrifice maintenance for performance.

You don't have to use this feature if it's not meeting your needs, we are not going to force this on everyone who uses this library, it's a proposal that might fit other use cases but not yours.

@ronnetzer
Copy link

@shaharkazaz

It seems that you misunderstood, if you want to change "Hello world" to "Good Morning Planet" t.hash('Hello world') must change to t.hash('Good Morning Planet')

So you're saying that for every small change required by the marketing the flow is:

  1. marketing needs to ask the dev team to change a value in the code.
  2. a new translation file created and sent back to the marketing (and a new version is released)
  3. marketing updates the rest of the translations with the new key along with the new translated value.

the current flow is:

  1. marketing updates the value of a key
    That's it.

Can you see the problem now? I don't think I have any other way to explain it.
From a client perspective, this is not practical, and from a dev perspective, this could become a maintenance nightmare. There is no way that I'm the only one who thinks that 🤔.

we are not going to force this on everyone who uses this library, it's a proposal that might fit other use cases but not yours.

That's good to hear and not what I've understood from your proposal.

@shaharkazaz
Copy link
Collaborator

@ronnetzer
Can you explain what is your company's process? I think this is the gap here.
Can you add an example of one of your templates and your translation file?

@CharlBest
Copy link

CharlBest commented Aug 18, 2019

@ronnetzer not sure if this is what you meant but let me try an example.
In the current implementation the key will be something like 'helloWorld' and the value 'Hello World'. But lets say the key doesn't describe the value but rather the use case like 'headerTitle' with value 'Hello World'. This means that translators can change the translation file (the 'Hello World' value) as many times as they want without any developer that has to go and update the template with some new key.

There is maybe an argument to be made there that isn't good practice because the key could be become unrelated to the value/text. An example could be key = 'headerTitle' and then its intial value is 'Welcome to our site' but the release v2 it becomes 'Company Brand Name'. Then the UI changes and from release v3 that text gets placed in the footer because it's value is the companies name but the key is still saying 'headerTitle'.

I hope I interpreted this conversation in the right way @ronnetzer and helped a bit.
@shaharkazaz you guys are doing such a cool job with this project. I'm really impressed and happy I found a good i18n library. Well done!

@NetanelBasal Good time so say I'm a really big fan of your work. Have read probably 90% of all you Medium blog posts and thanks for all you good tips, trick, practices and knowledge.

@ronnetzer
Copy link

@shaharkazaz
@CharlBest got it right (thanks btw), as he said the keys describe the use case, I also explained most of our client's workflow in more details to Netanel over the phone.

In general, First we provide the translation file to the client (according to their design) and then during development, they change the content and we add new keys.

for example, a simplified configuration for a dynamic form (with ngx-dynamic-forms library):

[
	{
		"id": "name",
		"type": "INPUT",
		"labelTooltip": "",
		"controlTooltip": "",
		"label": "form.field.name.label",
		"placeholder": "",
		"validators": {
			"required": null
		}
	},
	{
		"id": "description",
		"type": "TEXTAREA",
		"labelTooltip": "form.field.description.labelTooltip",
		"controlTooltip": "form.field.description.controlTooltip",
		"label": "form.field.description.label",
		"placeholder": "form.field.description.placeholder"
	}
]

and the corresponding translation file:

"form": {
	"field": {
		"name": {
			"label": "Name",
			"labelTooltip": "",
			"controlTooltip": "",
			"placeholder": "",
			"errorMessage": {}
		},
		"displayName": {
			"label": "Display name",
			"labelTooltip": "",
			"controlTooltip": "",
			"placeholder": "",
			"errorMessage": {}
		}
	}
}

*the form's JSON configuration will be modified in the future by product/marketing/someone who's not tech-oriented.

So this gives the client the ability to build/edit forms and their translations (even new keys), without any further development.

In any case, there are downsides for both ways, your proposal is optional therefore it doesn't worth spending time trying to solve my rare (?) use cases, I will just stick with the current way for them :)

@NetanelBasal
Copy link
Contributor Author

@CharlBest only 90% 😜? Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants