angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.74k stars 11.98k forks source link

ng xi18n fails with library which uses Ivy #16395

Closed romko391 closed 4 years ago

romko391 commented 4 years ago

🐞 Bug report

Command (mark with an x)

Is this a regression?

No

Description

When running xi18n command on my application, that imports my library i get the following error:

ERROR: Unexpected value 'SharedModule in /Users/****/Code/my-app/dist/@shared/lib/shared.module.d.ts' imported by the module 'CoreModule in /Users/****/Code/my-app/libs/my-core/src/lib/core.module.ts'. Please add a @NgModule annotation.

But this is only happening when my lib is pre-build with Ivy option set to true, otherwise everything is fine.

A clear and concise description of the problem... ## πŸ”¬ Minimal Reproduction

πŸ”₯ Exception or Error




`ERROR: Unexpected value 'SharedModule in /Users/****/Code/my-app/dist/@shared/lib/shared.module.d.ts' imported by the module 'CoreModule in /Users/****/Code/my-app/libs/my-core/src/lib/core.module.ts'. Please add a @NgModule annotation.`

🌍 Your Environment




`@angular/* version 9.0.0-rc-4 or 0.900.0-rc-4`

Anything else relevant?

Nope

alan-agius4 commented 4 years ago

Can you try to build the library in prod mode prior to running ng xi18n?

i18n extraction still uses parts of the legacy compiler which require a library to be compiled using View Engine.

romko391 commented 4 years ago

@alan-agius4 that's what i'm doing now. however, after reading docs about i18n working fine with Ivy i expected it to work fine in both, libs and apps. btw. can we expect the fix to ship in final 9.0.0 version?

alan-agius4 commented 4 years ago

We are targeting a fix for this in 9.1.0. There is already a draft PR in the framework side https://github.com/angular/angular/pull/32912

klemenoslaj commented 4 years ago

I just spend some time making a reproduction for this problem until I finally found this issue.

@alan-agius4 would it be possible for the known issues like this one to throw some meaningful errors?

This is just one example, but I had quite a few where something failed internally and I was presented with a useless message (likely even a stacktrace alone would be more helpful for faster reproduction and bug report).

In case of this issue I received one of the two:

alan-agius4 commented 4 years ago

Chiming in @alxhub as those errors are from the compiler.

mousindev commented 4 years ago

Hi all,

I just updated from 9.1.1 to 9.1.3 just in case, but still facing the same issue. I am not able to start localization as ng xi18n --output-path src/locale fails as shared by others above. I saw there is a Draft PR to fix this, is it known when it will be rolled out?

Thanks

petebacondarwin commented 4 years ago

The new extractor should be out in the next couple of weeks hopefully but it won't appear in a 9.1.x release, since it is a new feature. I'm aiming for v10.0.0.

mousindev commented 4 years ago

Thanks @petebacondarwin. The question then is, is there any workaround then to get ng xi18n working in a project that is using a library, being both 9.x?

Thanks

petebacondarwin commented 4 years ago

Can you confirm what the 9.x feature you are using that is not compilable by ViewEngine?

mousindev commented 4 years ago

Thanks @petebacondarwin. Tbh I don't know. Maybe I am lacking some knowledge here. This is what I have:

petebacondarwin commented 4 years ago

What happens if you try building with "angularCompilerOptions": { "ivyEnabled": false} in tsconfig.json?

mousindev commented 4 years ago

In the app? I did not have the ivyEnabled attribute, but stating it explicitly as you suggest in the tsconfig.json and executing the ng xi18n command gives the same result. I was thinking that maybe it is because of the library which is Ivy enabled, but I have a second one with custom dialogs (plain HTML with a bit of Angular Material on its latest version) which is not Ivy enabled, and still complains about both being imported in the AppModule.

petebacondarwin commented 4 years ago

Sorry I mean to say enableIvy: false (https://angular.io/guide/angular-compiler-options#enableivy). The point is that currently the ng xi18n process has to build the application using ViewEngine (not Ivy). Both compilers need to do some static analysis of the code. And ViewEngine compiler is not as clever at parsing certain code constructs. By trying to build (e.g. ng build --prod) with ivy disabled, I thought you might get a better error message.

petebacondarwin commented 4 years ago

But @alan-agius4 suggests that the problem is actually that your local libraries have been built using Ivy, and so they are not usable when trying to compile the main app via ViewEngine. The two built formats are incompatible. (This is the reason that we have ngcc, which converts ViewEngine compiled code to Ivy compiled code. But we do not have a reverse, which converts Ivy compiled code to ViewEngine compiled code).

So the way to work around this, until the new Ivy i18n message extractor is released, is to recompile your local libraries with ViewEngine before running the extraction.

mousindev commented 4 years ago

Thanks @petebacondarwin. You are correct. Now I understand. One of the Libraries had the enableIvy set to false in the tsconfig.lib.json but not in the prod one, where both were set to true. Now with libraries set to false, and app set to false, all are compiling with ViewEngine if I understood, and the ng xi18n works and creates the locale folder.

Thanks a lot for your help!

petebacondarwin commented 4 years ago

Glad you got it working. Hopefully the new extraction process will resolve this more cleanly.

blemoine167 commented 4 years ago

Hello, we fall in the same case with both app and lib compiled with Ivy. So we finished by disabling Ivy in all our apps and the shared lib. The problem is that when we do that we cannot use Localization with multiple locales in one build anymore. Is it a workaround here please? Maybe is it a way to blacklist module from extraction ?

BenLune commented 4 years ago

Hello, It would be appreciated if we had a banner, at the top of this page, saying it doesn't work yet with Ivy... with a link to the issue to check the state of it.

BenLune commented 4 years ago

Hello, Me again ;-) I tried the the latest 10-RC3 version and it still doesn't work... I was enthusiastic to use the official $localize service rather than ngx-translate (which is really great BTW), but I will go back to it. Also, discovering the way to use $localize, it seems very strange to me to compile an app version by language. To me it breaks the principle of an app... switching language shouldn't mean reloading an app. I read also that we could load the xlf files in runtime, but with Ivy only, and it doesn't seem to be really ready... But as we can't compile a project with a lib with Ivy... So, two possibilities : I've missed something, possible, this framework is deeply impressive and so powerful at many points, Or there is something the angular team should communicate in a more precise way in order to help everyone. Just saying it's not ready yet, we plan to solve in the 10 version, not the 9 etc. Thank you to the Angular Team, driving such a project is very hard, but I feel that sometimes, it lacks a transparent communication. Keep up the great work !

petebacondarwin commented 4 years ago

Hi @BenLune - I understand your frustration with this and I am sorry that we have not communicated clearly enough what is going on. I have tried to explain the situation in the various issues that have come up, but I can see that this is hard for developers to find when their project is not working as expected.

The documentation for i18n is currently being updated and I will see if we can get an info box that explains that, right now, the extraction mechanism is based on ViewEngine compiler, which is not able to cope with some code syntax shapes that the Ivy compiler can handle.

Regarding the use of $localize. I am sorry that you are frustrated by this. We have purposefully not documented this since for v9 and v10 it is an internal implementation device which was not ready to be used directly in Angular applications. I am afraid that there have been a number of blog postings and other comments where this has been talked abut as a new Ivy feature, which has confused the message.

Indeed using $localize in your own application code effectively works, but as is mentioned in numerous issues, which I have tried to respond to, there is currently no way to automatically extract these messages into translation files. This means that those people who have been trying to use this "internal" feature have felt disappointed. This was never our intention and why we did not publicly document this feature.

The fact that the current message extraction fails in certain cases is indeed a bug, which will be fixed by the new Ivy based message extractor, which I am actively working on. It has been my 2nd highest priority after ensuring that our compatibility compiler (ngcc) is working correctly, which has a large impact on all Ivy projects.

Finally, I would like to clear up the translation strategy that the Angular framework is offering here. Terms like "runtime translation" are ambiguous and mean different things to different people:

There are multiple pieces in the Angular translation offering:

$localize

This (currently internal) tool is a "tag handler" that can be applied to back-ticked strings to "mark" them as translatable. How that happens is dependent upon the project setup:

runtime translation

If we do no "translation inlining" during compilation then we must provide an implementation of this tag handler (by adding import '@angular/localize/init'; to your application). This handler will be executed every time the $localize tagged back-ticked string is evaluated.

By default this implementation is just a pass-through function that does not modify the text of the message. But if you were to load up some translations, by calling the (still internal) loadTranslations() function the implementation of $localize will actually replace the original text with the translated text.

What this means in practice is that in your own application code you could use $localize and as long as your code re-evaluates the back-ticked strings at the appropriate time you could indeed change the translations (i.e. the language) at runtime. But see below about why this doesn't work for translations in templates...

compile-time inlining

In many cases we do not want to be doing the work of translating the messages at runtime so we have in place (via the CLI tool, and also a standalone binary localize-translate) tooling that will actually parse through the built JS source code and replace any instance of a $localize back-ticked string with a new back-ticked string, which has the $localize tag handler removed and all the text replaced with the translated text.

The result of this is code that no longer has any reference to $localize which keeps the code small and fast, and means we no longer need to have the $localize implementation mentioned above in the distributable code downloaded to the browser. All of this results in performance benefits across the board for the application.

Obviously, in this case, we must generate a separate copy of the source code for each language that your project supports, and there is no option to change language at runtime. The CLI is optimised to make this happen as fast as possible, by only running the translation as the last part of the build pipe-line avoiding having to run the earlier parts of the build multiple times for each language.

i18n in Angular templates

Everything about $localize above is internal. Currently the only publcly supported way to internationalise your application is via i18n tags. Here I want to clear up the Angular approach to i18n in the templates and why we believe it is not effective to enable the language in templates to be changed at runtime.

Previously, in the View Engine compiler, we would always inline the chosen language at the point the component template is compiled. This is extremely early in the build pipeline and resulted in long builds where we had to run the complete pipeline for each language. Now that we have the option of using $localize for this purpose, which can be inlined much later in the pipeline, the Ivy compiler takes a different approach.

In the Ivy compiler, we translate the i18n tags in the templates into back-ticked string that are tagged with $localize. This means that we can delay the inlining of the translations until later in the build. The result of Ivy template compilation is a "template function" that contains Ivy rendering instructions and also references variables that hold the result of evaluating tagged back-ticked message strings. These message strings contain additional markers (οΏ½) that are used by the Ivy runtime to interleave interpolations {{...}} and actual HTML elements within the translated message. This is necessary because we support ICU messages that can contain both.

At runtime the Ivy rendering instructions will process the translated message string and process the special markers breaking up the string and inserting pieces of it into the correct place in the DOM.

In order to support changing the language of these translated messages at runtime we would need two support two things:

While it is theoretically feasible (I think) to achieve this, we feel that it has negative impact:

Because of this the design of i18n tags in templates precludes changing the language during normal application execution. I have spoken to a number of developers about runtime language replacement. The initial reaction is often one of "this is an essential feature" but when we dig into the practical benefits there have been very few (if any) scenarios where a reload of the site is not acceptable. For the kind of message translation that most applications require users tend to choose one language to work in for the life of the application.

There are some scenarios where language is used in a more dynamic way. Perhaps a call centre application where the user needs to be given responses in the language of the caller. But this is not really about localisation of the application itself, it is part of the business rules - for example in when changing language for the caller, you would not expect the language of the menus or action buttons to change.


Finally, it might be worth considering that $localize, when it finally becomes public, could provide an application developer with a way to change some aspects of language at runtime without reloading. It would involve re-evaluating the back-ticked strings as needed - and the application developer can have control over that - but the application would then be responsible for getting these updated strings into the rendered DOM (perhaps via Pipes or Directives).

But for the foreseeable future there are no plans to support such dynamic language updates via the i18n tag mechanism in Angular templates.


I hope that this helps to clear up some of the confusion around $localize and I hope that I can get the new Ivy extraction tooling merged and released before the end of June so that you can get past the issues of the current ViewEngine extraction tooling. Please keep an eye on my PR for this to see the progress: #32912 : i18n - extraction tooling.

BenLune commented 4 years ago

Thank you very much Pete for these rich informations for all of us !

BenLune commented 4 years ago

Pete, In your example you talk about the "reload of the site". Working with Angular (before Flex, Flash, Director), I don't feel like creating sites but applications ;-) We are using Angular to build multi-languages applications with Drupal as backend. Languages management is one of the challenges we have (the other one to use the router with urls coming from Drupal, managed by contributors, and of course, translatable). As we create PWAs, it's quite strange for me to reload the app just to switch language.

But sure, I understand the huge challenges doing it in runtime or build time.

To me, the great advantage of the $localize workflow is to tag the translatable tags, and get magically, the xlf file ready for translation. With $localize it seems possible to get translated string from Typescript or from templates (a Pipe can be done for that). But still no runtime language switch without having to restart the application... We use ngx-translate in our applications just for interface terms, not the contents, which are coming from Drupal. So when we switch language, we just ask the content in the right language to Drupal.

We use angular for the frontend experience, but also for the frontend of the backend, sometimes mixing both. Then the ability, especially for the contributors to quickly change languages, and check if everything is ok, is really comfortable. And I think, the more it is easy to check the contents of an application, cross language, the more we have the feeling to master the contents and to get a strong application. Finally, we may put our application interface terms in Drupal and load them runtime, maybe with a static export embedded in the application, then updated in IndexedDB when there is network.

Let's share our experiences ! Thanks again !

PS : I think that if angular.io was a multi-language application with a CMS behind it, it would be a great lab ;-)

petebacondarwin commented 4 years ago

Thanks for the additional feedback @BenLune - it is really helpful in guiding the design of the i18n features.

As we create PWAs, it's quite strange for me to reload the app just to switch language.

I would be really interested in some metrics of how often users switch language in your PWAs.

Then the ability, especially for the contributors to quickly change languages, and check if everything is ok, is really comfortable.

If I understand this correctly your are talking about some kind of "preview" in the backend of what the front end content will look like, yes? If so then I would not expect the backend user to want to change the actual language of the backend app. Instead they would just change the language of the preview, yes? In this case, I would imagine having the previous in some kind of iframe, which can be reloaded when you switch preview language, while the actual backend app itself remains in a single language. Am I misunderstanding the workflow here?

Finally, we may put our application interface terms in Drupal and load them runtime, maybe with a static export embedded in the application, then updated in IndexedDB when there is network.

This can actually be achieved with the $localize design right now. In fact I believe that https://www.locl.app/ has support, wrapped around $localize to do just this. The basic idea is that you do "runtime" merging of translations at the point the application loads - where the translated messages can be downloaded on-the-fly (or accessed from a local DB) before the application boots. (This is rather than inlining the translations at compile-time.)

CN91 commented 4 years ago

So if I understand correctly I can't upgrade to 9.x.x ? Because I import a local library, and even with enableIvy set to false it won't work. And it won't work until it's fixed in v10?

I got a little sidetracked by the discussion about runtime-rendering.

For those searching ; i found out that tsconfig.lib.json didn't set enablyIvy to false for my lib! Which when setting all to false did the trick.

PowerKiKi commented 4 years ago

If your library is built with enableIvy set to false, then you can upgrade to Angular 10.0 (independently of the project enableIvy flag).

If your library is built with enableIvy set to true, then you have to wait for Angular 10.1

CN91 commented 4 years ago

Okay so we've upgraded to 9.1 by now, with the library and everything set to enableIvy false and it works :).

Sidenote though; because of the old ViewEngine I had to revert deleting all entryComponent-settings. It's is deprecated in 9, however the old ViewEngine still requires them and gives errors if dynamic components don't exist in entryComponents.

jonyadamit commented 4 years ago

After upgrading to 10.1, and using --ivy switch, everything works as expected.

alan-agius4 commented 4 years ago

Closing as per above comment.

If the problem persists after upgrading kindly open a new issue.

Thanks.

angular-automatic-lock-bot[bot] commented 3 years ago

This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.