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

[Translator] Add Symfony UX Translator package #616

Merged
merged 1 commit into from
Apr 24, 2023

Conversation

Kocal
Copy link
Member

@Kocal Kocal commented Dec 18, 2022

Q A
Bug fix? no
New feature? yes
Tickets Fix #...
License MIT

Hi everyone, this PR add a new UX Translator package, which allow to re-use the same logic than the Symfony Translator, but in JavaScript.
The Intl ICU syntax is also supported.

Why?

This package is not a revolution, there is already https://github.com/willdurand/BazingaJsTranslationBundle which allows to re-use translations (defined in your Symfony app) in JavaScript.

Loading issues

However, there is one big difference between https://github.com/willdurand/BazingaJsTranslationBundle and this new UX package: the way translations are passed to JavaScript.

To me, Bazinga contains performance issues. If you choose to dump your translations into a separated .js or .json, or if you choose to load translations through a separate HTTP request, in each case you will need to dump and load the whole catalogue (for a given domain), which contains messages that you will not use into the webpage.

With the Symfony UX Translator package's logic, you explicitly define which translations you wants to load, making HTTP responses less heavier and/or JavaScript execution faster.

Types definition

Another issue is that Bazinga does not provide types definitions in the same repository.
Instead, you must install @types/bazinga-translator package separately, which can lead to some "desynchronization" between the upstream code and types.

How

Everything is documented in https://github.com/Kocal/symfony-ux/blob/feat/translator/src/Translator/doc/index.rst, but to be quick:

  • a CacheWarmer dumps your app's translations as constants into a JavaScript file (types definitions are generated too), that you can imports later in your JavaScript
  • a function trans() can be imported from @symfony/ux-translator, which:
    • works the same as TranslatorInterface#trans() from Symfony (PHP)
    • fully supports ICU, thanks to the code ported from https://formatjs.io/docs/icu-messageformat-parser
    • support default locale (configured, or from the <html>'s lang attribute, and locales fallbacks
    • support TypeScript types definitions, telling you and validate which parameters are usable or not, their types, which domain or locale is usable, ...
  • the bundle's size will stay as lightweight as possible, thanks to the Tree Shaking

@@ -0,0 +1,149 @@
/*
Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -0,0 +1,60 @@
/*
Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines 51 to 58
test('format with HTML inside', function() {
expect(formatIntl('Hello <b>{name}</b>', { name: 'Fab'}, 'messages+intl-icu', 'en')).toEqual('Hello <b>Fab</b>');
expect(formatIntl('Hello {name}', { name: '<b>Fab</b>'}, 'messages+intl-icu', 'en')).toEqual('Hello <b>Fab</b>');
})
Copy link
Member Author

Choose a reason for hiding this comment

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

We want to ensure that the issue fixed in willdurand/BazingaJsTranslationBundle#325 is not present here.

Copy link

@Mikescops Mikescops left a comment

Choose a reason for hiding this comment

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

Hello 👋 ,

I'm not used to Symfony at all but wanted to suggest some optimizations on the JS part.

}
});
return intlMessage.format(parameters);
}

Choose a reason for hiding this comment

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

This function has two drawbacks that you may want to solve:

  1. it is updating the parent object "parameters" without making a proper copy
  2. it's unclear from your regex what is the expected behavior for cleaning { and } because here you're only looking for open brackets to trigger the clean
  3. (also the if is not really needed if you use a regex)

I'm suggesting below a different approach (you may use the test scenarios for the unit tests too):

function sample(parameters = {}) {
  Object.entries(parameters).forEach(([key, value]) => {
    if (key.includes('%') || key.includes('{')) {
      delete parameters[key];
      parameters[key.replace(/[%{} ]/g, '').trim()] = value;
    }
  });
  return parameters
}

function rework(parameters = {}) {
  return Object.entries(parameters).reduce((acc, [key, value]) => {
    return {
      ...acc,
      [key.replace(/[%{} ]/g, '').trim()]: value
    }
  }, {});
}

const parameters = {
  'example1': 'content1',
  'example2%': 'content2',
  '{example3}': 'content3',
  '%{ example4 }%': 'content4',
  'example5 }': 'content5',
  'example6% }': 'content6'
}

console.log('params', parameters);

console.log('rework', rework(parameters));
console.log('params', parameters);

console.log('sample', sample(parameters));
console.log('params', parameters);

I made a quick JSFiddle to show the result: https://jsfiddle.net/p6qnzs7t/18/

Copy link
Member Author

@Kocal Kocal Dec 18, 2022

Choose a reason for hiding this comment

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

  1. Fixed, thanks
  2. As said on Discord (:eyes:), the logic is taken from the Symfony Translator in its PHP version (https://github.com/symfony/symfony/blob/6.3/src/Symfony/Component/Translation/Formatter/IntlFormatter.php#L45), so I prefer keep the same logic instead.
  3. Usually you want to quickly check if one of the concerned character can be found before using the regex, to prevent performances loss if you directly use (parse + run) the regex. However I don't know how its behave with JavaScript.

@Kocal Kocal force-pushed the feat/translator branch 2 times, most recently from fde423a to 380b106 Compare December 19, 2022 08:55
@kbond
Copy link
Member

kbond commented Dec 19, 2022

While I can see the need for this, my app doesn't have so many translations that I'd be concerned with the perf impact of dumping the entire catalog. Should we include this functionality here or are you thinking, in this case, use BazingaJsTranslationBundle?

@Kocal
Copy link
Member Author

Kocal commented Dec 19, 2022

Some applications can have more than 1000 keys (we got ~2500 in our main app 😅):
image

In those 2500 keys, maybe 200 are used both in PHP and injected in JavaScript (we translate the messages with |trans and expose them in JavaScript, but it does not supports parameters). So we can't dump whole translations files nor creating a dedicated domain for JavaScript because we don't want to duplicate translations.

I would like to see this functionality here because this package behave as a bridge between Symfony and JavaScript:

Symfony UX: A set of PHP & JavaScript packages to solve every day frontend problems

I know that the BazingaJsTranslationBundle suffers from a lack of maintainability, the last release is 4 months old and some PRs are still opened. IINW @ willdurand does not maintain (or even use?) its bundle anymore.

Finally, I think it can makes Symfony UX more "valuable", as it provides a more simple and light installation + usage than Bazinga.

@kbond
Copy link
Member

kbond commented Dec 19, 2022

In those 2500 keys, maybe 200 are used both in PHP and injected in JavaScript

Is there an alternate way we can flag these translations for js? Adding each and every key in your twig template feels a bit cumbersome, no?

Message catalog metadata? Just thinking out loud.

I know that the BazingaJsTranslationBundle suffers from a lack of maintainability

So should we consider moving it's primary functionality here (in addition to this new feature)?

Finally, I think it can makes Symfony UX more "valuable", as it provides a more simple and light installation + usage than Bazinga.

I agree but clearly many people use Bazinga in it's current form w/o the perf issues you describe. I think we should provide both options.

@Kocal
Copy link
Member Author

Kocal commented Dec 19, 2022

In those 2500 keys, maybe 200 are used both in PHP and injected in JavaScript

Is there an alternate way we can flag these translations for js? Adding each and every key in your twig template feels a bit cumbersome, no?

Message catalog metadata? Just thinking out loud.

I can looks cumbersome yeah, but I don't find it shocking since we are not building an SPA.

Where the end-user can access to Catalogue Metadata? To me those metadata are internals to Symfony right?

I know that the BazingaJsTranslationBundle suffers from a lack of maintainability

So should we consider moving it's primary functionality here (in addition to this new feature)?

To me, yes. Each PHP + JS existing bundles should be put under the Symfony UX brand/repository.

Finally, I think it can makes Symfony UX more "valuable", as it provides a more simple and light installation + usage than Bazinga.

I agree but clearly many people use Bazinga in it's current form w/o the perf issues you describe.

I think that many people use Bazinga because it's the only one bundle to make re-using Symfony translations messages in JavaScript possible.

For performance issues, I'm willing to bet my arm they either don't know or they don't care about these. Unfortunatelly, not every developer use profiling tools like Blackfire for PHP, and the tabs Analyzer/Performance Insights for JavaScript.

I think we should provide both options.

We can, this is something I planned for a 2nd PR if people had the issues. We can do it by making the 1st parameter of load_translations_for_js() optional, which will load all translations for a domain+locale. WDYT?

@kbond
Copy link
Member

kbond commented Dec 19, 2022

Where the end-user can access to Catalogue Metadata? To me those metadata are internals to Symfony right?

I think it can be used outside - the standard MessageCatalog implements MetadataAwareInterface. Don't know if all the loaders support metadata - xliff for sure. Would be really neat if you could tag translations for js. Outside of the scope of this current PR - just something to think about later.

We can, this is something I planned for a 2nd PR if people had the issues. We can do it by making the 1st parameter of load_translations_for_js() optional, which will load all translations for a domain+locale. WDYT?

This, or I was thinking dumping a js file to be included in a user's encore asset build? I use friendsofsymfony/jsrouting-bundle and this pattern works well. You flag routes to be included in js and these are dumped (this is where I had the idea of flagging translation messages). You don't need any twig helpers - just include the router.js script provided by the bundle and the dumped routes.js.

For translations, we'd need the figure out what the current locale is. Can we use the <html lang="x"> attribute to get this?

@Kocal
Copy link
Member Author

Kocal commented Dec 19, 2022

We can, this is something I planned for a 2nd PR if people had the issues. We can do it by making the 1st parameter of load_translations_for_js() optional, which will load all translations for a domain+locale. WDYT?

This, or I was thinking dumping a js file to be included in a user's encore asset build? I use friendsofsymfony/jsrouting-bundle and this pattern works well. You flag routes to be included in js and these are dumped (this is where I had the idea of flagging translation messages).

I've also used this pattern (with Bazinga and friendsofsymfony/jsrouting-bundle), it worked pretty fine but this was a bit tricky to setup:

  • "should we commit those files?"
  • you can't run Encore (or equivalent) before installing PHP deps and making your PHP app usable, so you must modify your installation steps to make things working, and that's not a easy thing to do with PaaS. Note: this argument is not so much valid anymore with Symfony UX, which seems have "popularized" this behavior.

The load_translations_for_js() is a solution that does not suffer about those issues.

@kbond
Copy link
Member

kbond commented Dec 19, 2022

Certainly load_translations_for_js() could be an option but if we add the ability to dump all messages, we shouldn't suggest adding to every page's content.

Maybe we shouldn't even provide this. If you want to pick and choose which messages to include, use load_translations_for_js(). If you want to dump you're entire catalog, use the js-dump command and include in your assets.

@Kocal
Copy link
Member Author

Kocal commented Dec 19, 2022

For translations, we'd need the figure out what the current locale is. Can we use the <html lang="x"> attribute to get this?

Mmmh, maybe it can works if you use <html lang="{{ app.request.locale }}">, but this is not always true (e.g.: IIRC for SEO practices).

@kbond
Copy link
Member

kbond commented Dec 19, 2022

Yeah, my thinking was to default the locale to the html.lang attribute and if initialize_js_translator() is used, override.

@tgalopin
Copy link
Contributor

tgalopin commented Dec 22, 2022

Just my adding my two cents: I did something similar in the past (declaring translations in JS globally) but in the end, I reverted back to translations local to controllers. It's cumbersome to declare all translations to expose so my thinking was that I might as well declare them locally in a "labels" key in the controller to pass the translations directly from Twig:

{{ stimulus_controller('my_controller', {
    'labels': {
        'location': 'crm.list.location'|trans,
        'createdAt': 'crm.list.createdAt'|trans,
        ...
    }
) }}

This provides the following benefits:

  • It naturally exposes only the translations actually used by the project ;
  • It allows to have different translations for a same controller/component used in different places (think something like a confirm button)
  • It allows to profit from Twig autocompletion and other similar tools to detect translation usages

So I'd say that if we don't have a big DX improvement using this component over this "local key" approach, we shouldn't release it.

There is a different way I can think of that could provide such great DX improvement: leveraging Webpack tree shaking to not have to care about marking translations to expose and easily importing them.

In such case, a translations module would be declared dynamically as a virtual module and would create one exported JS constant per translation key. We could then import these keys in our JS files (we could even have autocompletion if we create a stub file) and they would be linked to either EN or FR at runtime. Because of tree shaking, Webpack would remove the unused keys from the built JS automatically.

WDYT? I know it's very different from the current proposal but I really think we should be careful about the DX of this component, it's tricky to do right :) .

@Kocal
Copy link
Member Author

Kocal commented Dec 22, 2022

Hi @tgalopin, and thanks for your comment.

Your approach is interesting, however I can see three major issues in your code example:

  1. You are "forced" to use Stimulus controller. It's not that I don't like them, quite the opposite, but it complicates the migrations from BazingaJsTranslationBundle where developpers could use Translator.trans('my_message')
  2. You need to inject those translations everywhere you need them (i.e.: in a Vue.js component or a utils.js), where we can easily rely on a global state (like BazingaJsTranslationBundle and this new package do)
  3. You can't use translation parameters with values coming from JS since the message is already translated before, which can makes the UI less interactive

There is a different way I can think of that could provide such great DX improvement: leveraging Webpack tree shaking to not have to care about marking translations to expose and easily importing them.

In such case, a translations module would be declared dynamically as a virtual module and would create one exported JS constant per translation key. We could then import these keys in our JS files (we could even have autocompletion if we create a stub file) and they would be linked to either EN or FR at runtime. Because of tree shaking, Webpack would remove the unused keys from the built JS automatically.

I like this suggestion, however I have many questions:

  1. Where those JS constants would be defined? In a file previously dumped by a Symfony command?
  2. What if we have two same keys from different domains?
  3. What if the locale is updated (through setLocale()), how the constants import + three-shaking will works? Should we import all translations from a given id ?
  4. Are we ok to keep trans(key, parameters, domain, locale) function?

Thanks!

@tgalopin
Copy link
Contributor

tgalopin commented Dec 22, 2022

Where those JS constants would be defined? In a file previously dumped by a Symfony command?

I was thinking about a virtual module or a Webpack loader so that it's dynamic and it doesn't need to be dumped in between.

It could generate multiple files (one per domain) like:

export const CRM_LIST_LOCATION = {
    'en': '...',
    'fr': '...',
};

This would solve your 3 first questions (2: one file per domain, 3: we need to have locales in the key so that tree-shaking works as expected).

For 4, the usage could then be:

CRM_LIST_LOCATION['en']
// or perhaps
trans(CRM_LIST_LOCATION, 'en')

There may be a better DX to be found though, expecially given how big the DX improvements can be.

@Kocal
Copy link
Member Author

Kocal commented Dec 22, 2022

About the virtual module, would it add too much complexity?

I'm not familiar with them, but I wonder how we will deal with auto-completion in IDE? With TypeScript support? How do we get translations, from a Symfony command? If yes how do we run it (which php executable should be use?)

If we go to this direction, I prefer having a dedicated Symfony command that will dump translations (for all domains + enabled locales) somewhere in multiple .ts files (one file per domain).

After running the command manually (ie. during a composer install), you will be able to import translations like this:

import { trans } from '@symfony/ux-translator';
import { CRM_LIST_LOCATION, FOO_BAR } from '@symfony/ux-translator/translations/messages';

trans(CRM_LIST_LOCATION, { param1: 'value1' }, 'en'); 

We can also imagine, maybe for later, thanks to TypeScript types, to have auto-completion on translations parameters given the translation id, WDYT?

/cc @weaverryan @kbond

@kbond
Copy link
Member

kbond commented Dec 22, 2022

That'd be really cool! Could the imported messages be js objects? (I have minimal experience with js so not sure if this is even possible)

import { CRM_LIST_LOCATION, FOO_BAR } from '@symfony/ux-translator/translations/messages';

CRM_LIST_LOCATION; // no parameters just is the value

FOO_BAR.with({ param1: 'value1' });
FOO_BAR.with({ param1: 'value1' }).locale('fr');

// maybe this would be required? (i'm thinking about __toString() in PHP above)
CRM_LIST_LOCATION.render();
FOO_BAR.with({ param1: 'value1' }).render();
FOO_BAR.with({ param1: 'value1' }).locale('fr').render();

Also, how are you thinking the locale-detection would work? I'm thinking the following cascade:

  1. defaults to en
  2. html.lang attr
  3. some kind of twig function ( ie {{ js_trans_locale('fr') }})
  4. trans() js function

@Kocal
Copy link
Member Author

Kocal commented Dec 22, 2022

The following syntax would be possible, however I'm not a big of creating multiple objects like this because it can lead to performances issues.

@kbond
Copy link
Member

kbond commented Dec 22, 2022

creating multiple objects like this because it can lead to performances issues.

Ok, fair enough.


I'm guessing each domain would/could be dumped by the command?

import { trans } from '@symfony/ux-translator';
import { FOO } from '@symfony/ux-translator/translations/messages';
import { BAR } from '@symfony/ux-translator/translations/domain1';

Or how would that work?

Nevermind, I see you specified this above :).


For small apps that don't have a perf problem loading all their translations, what about something like:

import { trans } from '@symfony/ux-translator/translations/_all';

trans('key1');
trans('key2', { param1: 'value1' }, domain: 'security');
trans('key3', domain: 'security', locale: 'fr');

@Kocal
Copy link
Member Author

Kocal commented Dec 22, 2022

For small apps that don't have a perf problem loading all their translations, what about something like:

import { trans } from '@symfony/ux-translator/translations/_all';

trans('key1');
trans('key2', { param1: 'value1' }, domain: 'security');
trans('key3', domain: 'security', locale: 'fr');

The trans() function from @symfony/ux-translator/translations/_all would be a different function than trans() from @symfony/ux-translator?

If no, we can't make tree-shaking + translation id auto-completion + translation parameters working all together
If yes, I'm a bit afraid to confuse users with two different functions having the same name but different parameters type.

Constants imported from @symfony/ux-translator/translations/messages won't be a string but an object contains the following data:

  • the translated message per local
  • the parameters (maybe in a future iteration)

Thanks to TypeScript types, we could get parameters autocompletion given the constant name. This is also something that can work with your proposition, but you won't benefit from tree-shaking.

If you really need to import all translations, per domain, you can still use import * as messages from '@symfony/ux-translator/translations/messages';, but I honestly don't think there is an interest in doing that.

WDYT?

@kbond
Copy link
Member

kbond commented Dec 22, 2022

The trans() function from @symfony/ux-translator/translations/_all would be a different function than trans() from @symfony/ux-translator?

I was thinking yes.

If you really need to import all translations, per domain, you can still use import * as messages from '@symfony/ux-translator/translations/messages';, but I honestly don't think there is an interest in doing that.

Your probably right but I'm trying to find an easy migration path for bazinga users.

@Kocal
Copy link
Member Author

Kocal commented Dec 22, 2022

The trans() function from @symfony/ux-translator/translations/_all would be a different function than trans() from @symfony/ux-translator?

I was thinking yes.

Alright, I will see what I can do.

If you really need to import all translations, per domain, you can still use import * as messages from '@symfony/ux-translator/translations/messages';, but I honestly don't think there is an interest in doing that.

Your probably right but I'm trying to find an easy migration path for bazinga users.

Oh, yeah that's true! I don't have a better suggestion for the moment, let's go for _all + special trans() function so.

@Kocal
Copy link
Member Author

Kocal commented Dec 23, 2022

This is what I've imagined for parameters/locales autocompletion, by using constants:

interface Message<Translations = { [locale: string]: string }, Parameters = Record<string, unknown>> {
    domain: string;
    translations: Translations,
    parameters?: Parameters,
}

export type TranslationsOf<T> = T extends Message<infer TranslationsType, infer ParametersType> ? TranslationsType : never;
export type ParametersOf<T> = T extends Message<infer TranslationsType, infer ParametersType> ? ParametersType : never;

function trans<
    M extends Message,
    Parameters extends ParametersOf<M>,
    Translations extends TranslationsOf<M>,
>(message: M, parameters?: Parameters, locale?: keyof Translations): string {
    // ...
}

With the following constants file, which will be auto-generated by a Symfony command:

const CRM_LIST_LOCATION: Message<Record<'en'|'fr', string>, { foo: string }> = {
    domain: 'messages',
    translations: {
        en: 'CRM List {foo}',
        fr: 'Liste CRM {foo}'
    },
}

const FOO_BAR: Message<Record<'en'|'fr'|'it', string>, { foo: string }> = {
    domain: 'messages',
    translations: {
        en: 'Foo bar (en)',
        fr: 'Foo bar (fr)',
        it: 'qsd',
    }
}

This way we can provide a very good DX:
Capture d’écran 2022-12-23 à 08 54 13
image
image

@Kocal Kocal force-pushed the feat/translator branch 3 times, most recently from 9405f94 to 3c95625 Compare March 21, 2023 23:38
@Kocal
Copy link
Member Author

Kocal commented Mar 21, 2023

Hi everyone, and thanks for the reviews!

Your comments have been addressed, here are the updates:

  • The locale fallbacks is now computed at cache warmup, it's located in var/translations/configuration.js file
  • The Twig Extension (and the Twig dependency aswell) have been removed

And... so we also need to at least have some "plan" for import maps, which is coming very soon to Symfony. The big tricky part is the lack of tree shaking. You can import specific modules (e.g. import { TRANSLATION_SIMPLE } from './translator'), but the entire target ./translator would be downloaded to make that happen. If you're using importmaps, you ARE opting out of tree shaking... but generally-speaking, it works well and there should be some reasonable path for UX translator to work with then. Any ideas about this?

Are you speaking about symfony/symfony#48371?

Unfortunately, this Translator package now highly relies on bundler's (Webpack or anything else) tree-shaking.
It was not the case with the 1st implementation (loading translations you nedd through a Twig function), but Titouan proposed a solution with a better DX in #616 (comment) but it needs tree-shaking.

I don't know how works ImportMaps works, but if it does not support tree-shaking, then the Translator won't be usable. :/

@tgalopin
Copy link
Contributor

IMO we shouldn't care too much about importmaps for this PR.

My reasonning is that I think there are mostly two use cases for import maps (ie. two types of people using them):

1/ in dev environment it allows not to have to wait for webpack to build, meaning a faster iteration loop => we don't care that the whole translator is imported here, as long as it will work the same way when using Webpack.

2/ In prod environment, almost everyone with a slightly big app uses Webpack or another bundler. Webpack isn't only about building, it's also about productivity: many modules allow to inspect the code, to compile it, to provide DX tools, ... I generally compare it to the Symfony container being built: of course we could do without, but CompilerPass are hugely useful.

The only two cases where I can see someone using import maps in prod would be for small projects (=> fine to import the whole translator) or for projects having a large app but a tiny bit of front-end (in which case they most likely should just pass the translations from PHP as Stimulus values or similar).

I don't see a case where import maps are an issue, especially given how the DX is so good using the tree-shaking approach (you simply don't need to think about translations).

BTW I think a good tool to create in the future will be a Router ;)

@weaverryan
Copy link
Member

The only two cases where I can see someone using import maps in prod would be for small projects (=> fine to import the whole translator) or for projects having a large app but a tiny bit of front-end (in which case they most likely should just pass the translations from PHP as Stimulus values or similar).

Yes, I think this is the key. I would be careful not to make it look like importmaps are not ok for production, because they are - even in large apps. So the point is that, if you're using importmaps, you are likely using Turbo + Stimulus to get your rich frontend, and so can pass translations as values to Stimulus.

@kbond
Copy link
Member

kbond commented Mar 22, 2023

BTW I think a good tool to create in the future will be a Router ;)

Ooo, like a replacement for https://github.com/FriendsOfSymfony/FOSJsRoutingBundle?

Copy link
Member

@kbond kbond left a comment

Choose a reason for hiding this comment

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

👍🏻 from me from a high-level perspective! I love the simplicity!

@Kocal Kocal force-pushed the feat/translator branch from 3c95625 to facb08f Compare March 22, 2023 17:37
@Kocal
Copy link
Member Author

Kocal commented Mar 22, 2023

BTW I think a good tool to create in the future will be a Router ;)

Ooo, like a replacement for FriendsOfSymfony/FOSJsRoutingBundle?

Well, I had the same idea as Titouan, but I didn't want to spoil until this PR was merged 😛

Now that the logic "warm => dump => import + tree-shaking" is mastered, that would be easy to create a UX Router package.

@Kocal Kocal mentioned this pull request Mar 22, 2023
@tgalopin
Copy link
Contributor

Now that the logic "warm => dump => import + tree-shaking" is mastered, that would be easy to create a UX Router package.

Exactly my thinking as well :)

Copy link
Contributor

@tgalopin tgalopin left a comment

Choose a reason for hiding this comment

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

This is a great PR, thanks a lot for your work @Kocal

@Kocal Kocal force-pushed the feat/translator branch from facb08f to 4f0b91a Compare March 24, 2023 09:52
weaverryan added a commit that referenced this pull request Apr 11, 2023
This PR was merged into the 2.x branch.

Discussion
----------

chore: commit yarn.lock

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Tickets       | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

As discussed with `@weaverryan` on Slack, we will now commit the `yarn.lock` to prevent the same issue than #616 (comment) (2nd part).

Commits
-------

c18e9f2 chore: commit yarn.lock
@Kocal Kocal force-pushed the feat/translator branch from 4f0b91a to 973dc28 Compare April 15, 2023 07:00
@Kocal
Copy link
Member Author

Kocal commented Apr 15, 2023

(the PR has been rebased)

@Kocal Kocal force-pushed the feat/translator branch 2 times, most recently from 752d11b to 7d7023c Compare April 20, 2023 09:11
@Kocal Kocal force-pushed the feat/translator branch 3 times, most recently from e86b960 to 8376744 Compare April 24, 2023 19:10
@weaverryan
Copy link
Member

Thank you @Kocal!

@weaverryan weaverryan merged commit 4b3aa8d into symfony:2.x Apr 24, 2023
@Kocal Kocal deleted the feat/translator branch April 24, 2023 20:23
jameswebapp added a commit to jameswebapp/ux that referenced this pull request Aug 1, 2023
This PR was merged into the 2.x branch.

Discussion
----------

chore: commit yarn.lock

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Tickets       | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exist, explain below instead -->
| License       | MIT

As discussed with `@weaverryan` on Slack, we will now commit the `yarn.lock` to prevent the same issue than symfony/ux#616 (comment) (2nd part).

Commits
-------

c18e9f20 chore: commit yarn.lock
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.

6 participants