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

feat: Add Trans component for interpolating JSX in translations #6534

Merged
merged 14 commits into from
May 5, 2023

Conversation

bancek
Copy link
Contributor

@bancek bancek commented Apr 28, 2023

Fixes #6351
This is a proof of concept PR that shows a possible solution to #6351 (this already works, you can quickly test it by applying the diff at the bottom and removing if (isBrave() && !isMeasureTextSupported()) { in App.tsx).

Trans component make it possible to use JSX for translations.

Examples:

{
  "example1": "Hello {{audience}}",
  "example2": "Please <link>click the button</link> to continue.",
  "example3": "Please <link>click {{location}}</link> to continue.",
  "example4": "Please <link>click <bold>{{location}}</bold></link> to continue.",
}
<Trans i18nKey="example1" audience="world" />

<Trans
  i18nKey="example2"
  connectLink={(el) => <a href="https://example.com">{el}</a>}
/>

<Trans
  i18nKey="example3"
  connectLink={(el) => <a href="https://example.com">{el}</a>}
  location="the button"
/>

<Trans
  i18nKey="example4"
  connectLink={(el) => <a href="https://example.com">{el}</a>}
  location="the button"
  bold={(el) => <strong>{el}</strong>}
/>

Output:

Hello world
Please <a href="https://example.com">click the button</a> to continue.
Please <a href="https://example.com">click the button</a> to continue.
Please <a href="https://example.com">click <strong>the button</strong></a> to continue.

Trans component can also be used instead of t(). E.g. <span><Trans i18nKey="my.key" /></span> is the same as <span>{t("my.key")}</span> and <span><Trans i18nKey="my.key" name={currentName} /></span> is the same as <span>{t("my.key", { name: currentName })}</span>

@vercel
Copy link

vercel bot commented Apr 28, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
excalidraw ✅ Ready (Inspect) Visit Preview May 3, 2023 0:25am
excalidraw-package-example ✅ Ready (Inspect) Visit Preview May 3, 2023 0:25am
1 Ignored Deployment
Name Status Preview Updated (UTC)
docs ⬜️ Ignored (Inspect) May 3, 2023 0:25am

@ad1992
Copy link
Member

ad1992 commented Apr 28, 2023

Wohoo @bancek that was super quick 🚀
Just did a quick review and its looking good!
As you spoke about a custom component, so if its similar to that one then how about creating a library out of it so its easy to maintain as well ?
cc @dwelle

@bancek
Copy link
Contributor Author

bancek commented Apr 28, 2023

@ad1992 I took the code from Trans component from our codebase but we use i18next so I addapted it to your jotai based translations. I don't think it makes much sense to make a library for ~70 lines of code.

@ad1992
Copy link
Member

ad1992 commented Apr 28, 2023

@ad1992 I took the code from Trans component from our codebase but we use i18next so I addapted it to your jotai based translations. I don't think it makes much sense to make a library for ~70 lines of code.

Alright, lets keep as is in the codebase, we can figure out later if want to move all i18n related stuff to a lib.

Comment on lines 58 to 62
const Trans = memo<{ i18nKey: string; [key: string]: any }>(
({ i18nKey, children, ...props }) => {
const { t, langCode } = useI18n();

const transChildren = React.useMemo(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useMemo will never memoize due to props, and the component memo won't work either unless we memoized each render function pass to it which I doubt we'll do / would yield much benefit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on the type of props. Props values can also be just strings (see example 1) but I agree that in the Excalidraw codebase, Trans props will probably be more complex so memoization won't make much sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed memo and useMemo and added tests.

Copy link
Member

@dwelle dwelle Apr 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Props values can also be just strings (see example 1)

what would be the use case for using strings? EDIT: nvm, the <Trans/> is meant to also be usable as t().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, <span><Trans i18nKey="my.key" /></span> is the same as <span>{t("my.key")}</span>.

@bancek
Copy link
Contributor Author

bancek commented Apr 28, 2023

@ad1992 I've removed the commit for BraveMeasureTextError and added the diff to the PR description.

@dwelle
Copy link
Member

dwelle commented Apr 28, 2023

Thanks @bancek!

One note, what's the [bold]xxx[/bold] syntax ([]) for. My current understanding it's a DSL you guys use in your own codebase that's further transformed, and as such nothing we would be using ourselves, correct?

@bancek
Copy link
Contributor Author

bancek commented Apr 28, 2023

I would have to update the examples in the PR description. I just copied those from our internal tests to showcase what is possible. The tests in this PR render proper HTML.

@bancek
Copy link
Contributor Author

bancek commented Apr 28, 2023

I've updated the examples. Now it should be more obvious what <Trans /> does.

src/components/Trans.tsx Outdated Show resolved Hide resolved
src/components/Trans.tsx Outdated Show resolved Hide resolved
src/components/Trans.tsx Outdated Show resolved Hide resolved
@bancek
Copy link
Contributor Author

bancek commented May 1, 2023

@ad1992 thanks for adding the comments ❤️

@ad1992
Copy link
Member

ad1992 commented May 1, 2023

Hey so as we we were discussing in chat with @dwelle
Should we be changing the translation string api a bit, instead of appending start and end to the strings, can we use tags instead ?
Eg

"example2": "Please <link>click <bold>{{location}}</bold></link>} to continue.",
<Trans
  i18nKey="example2"
  link={(el: any) => <a href="https://example.com">{el}</a>}
  location="the button"
  bold={(el: any) => <strong>{el}</strong>}
/>

So Trans API remains the same, the translation string uses tags instead of start/end strings for better readability in case of non string values.

@dwelle
Copy link
Member

dwelle commented May 1, 2023

If we don't want to use <tag></tag> syntax, e.g. because it evokes HTML/JSX, we can use Mustache-inspired syntax {{tag}}{{/tag}}, or Mustache syntax directly {{#tag}}{{/tag}}. In fact the latter may be preferable due to point (1) below.

The reason for switch the syntax:

  1. right now the open/close tags look like regular variables: hard to read, and potentially conflicting with variable names
  2. harder to edit/select in editor (you can't select the opening tag to highlight/find the closing tag)

@bancek
Copy link
Contributor Author

bancek commented May 1, 2023

I've implemented support for Please <link>click <bold>{{location}}</bold></link>} to continue.. It's a bit more code (it can probably be cleaned up a bit) but the translations strings are now much cleaner indeed.

@ad1992
Copy link
Member

ad1992 commented May 1, 2023

If we don't want to use <tag></tag> syntax, e.g. because it evokes HTML/JSX, we can use Mustache-inspired syntax {{tag}}{{/tag}}, or Mustache syntax directly {{#tag}}{{/tag}}. In fact the latter may be preferable due to point (1) below.

The reason for switch the syntax:

  1. right now the open/close tags look like regular variables: hard to read, and potentially conflicting with variable names
  2. harder to edit/select in editor (you can't select the opening tag to highlight/find the closing tag)

I think <tag></tag> would be better coz ultimately on DOM they are replaced with some html tags so by looking at translate string it would be conveyed that these tags are replaced with some valid html tag on actual string. As per the naming thats up to us so can make sure the naming is done correctly and closer to html tags so it conveys the same meaning too.

},
];

format.split(REGEXP).forEach((match) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the split here could be confusing as its introducing unncessary undefined tags so I think its better if we improve the split here so tokens are correct
eg for "Hello {{audience}}" its splitting to

 [ 'Hello ', '{{audience}}', undefined, undefined, '' ]

so we should remove those undefined tokens

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if it's possible to write a regex with delimiters in JavaScript so that it won't produce undefined results. For cleaner code we could use format.split(REGEXP).filter(Boolean) which will filter our empty strings and undefined.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes I meant filtering only after tokens are generated

);
}

return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this shouldn't be needed if we use else If instead of multiple ifs and readability wise will be better as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a compromise between readability and performance. If you want to use else if you need to generate all regex matches before.

const tagStartMatch = match.match(TAG_START_REGEXP);
const tagEndMatch = match.match(TAG_END_REGEXP);
const keyMatch = match.match(KEY_REGEXP);

if (tagStartMatch !== null) {

} else if (tagEndMatch != null) {

} else if (keyMatch !== null) {

} else {

}

src/components/Trans.tsx Outdated Show resolved Hide resolved

import { useI18n } from "../i18n";

const REGEXP = /({{\w+?}})|(<\w+?>)|(<\/\w+?>)/g;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets add a comment for this regex to be more clear

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
@bancek
Copy link
Contributor Author

bancek commented May 2, 2023

@ad1992 I'll be offline for the rest of the day so you can fix things yourself or I can do it tomorrow.

@ad1992
Copy link
Member

ad1992 commented May 5, 2023

Merging 🚀 Thanks @bancek ❤️

@ad1992 ad1992 merged commit 1184a8c into excalidraw:master May 5, 2023
4 checks passed
"line1": "Looks like you are using Brave browser with the <bold>Aggressively Block Fingerprinting</bold> setting enabled.",
"line2": "This could result in breaking the <bold>Text Elements</bold> in your drawings.",
"line3": "We strongly recommend disabling this setting. You can follow <link>these steps</link> on how to do so.",
"line4": " If disabling this setting doesn't fix the display of text elements, please open an <issueLink>issue</issueLink> on our GitHub, or write us on <discordLink>Discord</discordLink>",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a leading space in line4 and discord is not needed anymore.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the catch. Fixed in #6561

@bancek
Copy link
Contributor Author

bancek commented May 5, 2023

This is so much better now. Thanks for your help @ad1992

Screenshot 2023-05-05 at 18 43 23

alswl pushed a commit to alswl/excalidraw that referenced this pull request Nov 15, 2023
…lidraw#6534)

* feat: add Trans component

* Add comments

* tweak

* Move brave to trans component

* fix test and tweaks

* remove any

* fix

* fix

* comment

* replace render function type

* Use tags for Trans

* Fix a typo

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>

* Cleanup, add comments, add support for kebab case

* tweaks

---------

Co-authored-by: Aakansha Doshi <aakansha1216@gmail.com>
Co-authored-by: dwelle <luzar.david@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

strings: formatting makes strings barely translatable
3 participants