microsoft / AL

Home of the Dynamics 365 Business Central AL Language extension for Visual Studio Code. Used to track issues regarding the latest version of the AL compiler and developer tools available in the Visual Studio Code Marketplace or as part of the AL Developer Preview builds for Dynamics 365 Business Central.
MIT License
748 stars 245 forks source link

[Question / Bug] Translated Enums in OData #5460

Closed NAVRockClimber closed 4 years ago

NAVRockClimber commented 5 years ago

Hi, I try to access the Bunsiness Central OData Web Service.

But independent on which Service Language is set up in the service tier it seems to translate options and enums into the browser language.

Is this a bug?

I tested this on the following docker images:

Service Tier Configuration:

ServicesDefaultTimeZone                         UTC
ServicesLanguage                                en-US
ServicesOptionFormat                            OptionCaption

Data from Web Service:

{
  "@odata.context": "http://<Server>:7308/Service/ODataV4/$metadata#Company('CRONUS%20AG')/Setup",
  "value": [
    {
      "@odata.etag": "<removed>",
      "Primary_Key": "",
      "Heartbeat_Interval": 5000,
      "Default_Time_Period": "Tag",
      "WS_Url_Rewrite": "<removed>",
      "Update_Interval": 60,
      "Base_URL_StyleSheet": "<removed>",
      "Style_Grid": "Weiß",
      "Style_Gantt": "Weiß",
      "Planning_Horizon_Start": "2019-01-01",
      "Planning_Horizon_End": "2021-12-31",
      "Authentication_Method": "Keine",
      "Callback_URL": "",
      "Auth_URL": "",
      "Access_Token_URL": "",
      "Show_Simulated_Prod_Orders": false,
      "Show_Planned_Prod_Orders": true,
      "Show_Firm_Planned_Prod_Orders": true,
      "Show_Released_Prod_Orders": true,
      "Show_Finished_Prod_Orders": false,
      "Prod_Order_Progr_Calc_Type": "Mengenbasiert",
      "No_Planning_Before_Work_Date": false,
      "Simulated_Prod_Order": "Blau",
      "Planned_Prod_Order": "Gelb",
      "Firm_Planned_Prod_Order": "Fuchsie",
      "Released_Prod_Order": "Aquamarin",
      "Finished_Prod_Order": "Grün",
      "Available_Capacity_Color": "Limone",
      "Default_Capacity_Color": "Grün",
      "Percent_Load_Warning_Level_1": 80,
      "Color_Load_Warning_Level_1": "Gelb",
      "Percent_Load_Warning_Level_2": 110,
      "Color_Load_Warning_Level_2": "Rot",
      "Percent_Load_Warning_Level_3": 150,
      "Color_Load_Warning_Level_3": "Schwarz",
      "Highlight_Color": "Orange"
    }
  ]
}

You can see all bool fields and date fields are as you would expect it. But, all enums like Style_Grid, Style_Gantt are translated into German.

It seems like ODATA evaluates the 'Accept-Language' Header from the browser (Change UI language in Chrome) and ignores the service language.

Sample Enum (German translation in XLIFF file):

enum 60013 "Style"
{
    Extensible = true;

    value(0; Standard)
    {
        Caption = 'Standard';
    }

    value(1; Whte)
    {
        Caption = 'White';
    }

    value(100; Custom)
    {
        Caption = 'Custom';
    }
}

I would expect the service to behave consistent and either translate everything to my browser language or use the service language. Only in the cloud environment (businesscentral.dynamics.com/sandbox) I get the english output (caption).

Why is a web service language dependend anyway? I would expect a web serice due to it is intended for machine to machine communication to not having any language at all.

Best regards, Bert

nikolakukrika commented 5 years ago

Hi, This is by design.

It works like this: Some primitives are always going to be in English, e.g. for Boolean it is true, false. This is due to the OData specification - in metadata it is declared as a Boolean thus we cannot translate the value (it would break the OData spec, clients...). If you would like to get a translated value, you need to translate it client side. The same works in any other language, C# does not translate primitive values e.g. Booleans to the local language.

Option strings are treated as regular strings, thus they follow the UI Thread language. We do not know the usage - if you are building UI, you may want the local language so you don't translate item type yourself, plus you need the local language so you can work in that language. In some cases you would like it to be in English always, in this case then it has to be @Locked for translation and only English string should be added as an option (no translations). If it has translations then we will use that language's translation.

Accept-Language is used to override the default setting that comes from the server. You may want to have default as German but support English, Italian and French as a fall back language - App should be able to choose a different language.

One of the reasons we keep it language dependent is if you would like to show the errors directly to the user. In this case all errors will be selected language so you can simply display it to the user.

Here is the list of error codes, you can show directly to user some of them: https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/dynamics_error_codes

For example Application_DialogException is always safe to show to the user since it is an error dialog that we regularly show.

salgiza commented 5 years ago

Yeah, this is quite a problem. Years ago we actually created a partner ticket for this, because it basically means that you can not create a PowerBI that depends on particular option/enum values, as it will break if a user uses a language different from the one you used when you created the PowerBI.

We have a mobile app that connects to NAV/BC and we actually had to create a WS that returns the captions of the options we use, in whatever language is active in that moment, so that we know what values we actually have to use when we filter the data published by the server.

I can understand the usefulness of getting the caption in OData/API, for people that just want to read (as in literally read out loud) the value. But this should not kill the option to query the data returned by the URLs in a non-language dependant way.

Why don't you publish two properties (when an Enum/Option field is published through OData/API)? One with the caption and one with the value. That way we can use one to show to the user, but use the other to be able query the URL without depending on the active user language.

nikolakukrika commented 5 years ago

There are 2 ways to solve this issue:

  1. Add accept-language: en-us to every request. This will force English texts to be returned always
  2. Create a dedicated page for PowerBI, that only has English Options as texts

We cannot update the page, since the page is used for UI. Exposing an UI page as a SOAP or ODataV4 page will not work gracefully.

Adding 2 properties would be confusing for API consumers, we would also break internal API guidelines.

NAVRockClimber commented 5 years ago

I have to agree to @salgiza. It is not the first time we stumble over this for us as well.

I must agree, this is good if you just want to pass the information directly to the user. But if you rely on data in your further program logic it is a desaster to having this data translated. You cannot rely on it that is just working and the value black will be "black". We definitely need a solution here where the developer does not need to massage the data for being able to build logic on it. This would mean in the worst case when it is a setup page (like the example above) for the user that is also passed via web service to an application we need to copy the page an fix all fields that might get translated by the web service.

Maybe some kind of checkmark in the web service setup page would be helpful where you are able to tell BC if it is actually "user" data or control data.

Nevertheless, the Business Central Cloud environment returns always english. So we even need to distinguish somehow between an OnPrem Service and the Cloud service.

nikolakukrika commented 5 years ago

@NAVRockClimber - are you sure that the Business Central Cloud environment returns always English? You can do a quick test - login as an user and change a language. Then call the webservice with that language.

Is there any reason why adding Accept-Language: en-us to every request to force English would not work?

salgiza commented 5 years ago

@nikolakukrika

Option 1 has several problems: How do you tell PowerBI or Excel to pass the "accept-language: en-us" param to the server, when the customer has changed the default web services language? (this is a real example, and I can't use a page because we are publishing queries). How do I force the language only for option/enums? (I want error messages in the language used by the user).

Option 2 is easier said than done: For query objects there is no way to publish the internal name of the option/enum. For pages, you can duplicate the field, sure, but how do you handle writes? How do you filter by it?

Aaand... what happens if someone updates the English caption of an enum/option? I know its unlikely, but caption is not the real value, so why should updating it should break my code or PowerBI?

Don't get me wrong, there are usually ways (not necessarily easy) to overcome this problem, but, really, publishing a query of Sales Headers and being able to filter them by the "Document Type" field should not be so hard...

nikolakukrika commented 5 years ago

I agree. The proper solution would be to change the options to be enum in web services instead of using strings. We have this item on the backlog, however no one is looking at it right now. Then you could define the values by numbers. The problem is that it available options are not visible at all...

Right now - the usage should either be the local language or it should always be English. We cannot mix them.

For now you would have to expose the enum in your own service as an integer - so instead of showing the string, you can show the integer value on the page. This value would be used for filtering only - it should be the read-only field.

Second option is to make the URL configurable if possible - end users can enter the values themselves if they need to change the value.

NAVRockClimber commented 5 years ago

@nikolakukrika I just retested. It is actually even stranger than expected. But at least consistent in the on premise and cloud area. If I set my browser UI language to "de" (no country specified) I get the response in english. If I set my UI language to de-DE I get a German response. I guess in my test I had DE set up as language. Maybe you can add the 'Accept-Language' header to the BC documentation?

Again, I have to agree to @salgiza this causes more troubles and issues.

Maybe you can extend the URL with a parameter for getting a proper response? Like you already do with the company. If it is ommited everything behaves like before and you won't cause any breaking changes. If we need the data in a special language or just the enum values we can add the parameter.

Example:

For specific language: https://api.businesscentral.dynamics.com/v2.0/tenant-id/Sandbox/ODataV4/Company('CRONUS%20DE')/LANGUAGE('de-DE')/Setup

For specific values: https://api.businesscentral.dynamics.com/v2.0/tenant-id/Sandbox/ODataV4/Company('CRONUS%20DE')/LANGUAGE('none')/Setup

Having this in the backlog is nice to hear. Altough I would appreciate to hear that this will come in near future.

nikolakukrika commented 5 years ago

@NAVRockClimber Thanks for investigating, I would that the system behaves the same OnPrem as OnSaaS.

I have opened the documentation issue to write an article about Accept-Language, I was only able to find this article (how to get reports in a desired language): https://docs.microsoft.com/en-us/dynamics-nav/api-reference/v1.0/api/dynamics_purchaseinvoice_pdfdocument

For the URL examples, we cannot do this since it would not adhere to the OData standard. You could write this in the custom API page - the key would be passed as the filter and you can use it to set the GLOBALLANGUAGE which will override the Accept-Language...

PooyaKharamesh commented 4 years ago

I am closing this issue because it appears that it has been resolved. Please open a new issue if you believe this has not been resolved and reference the current issue.