So….I’m working through a new build process for our ISV solution. Why? Because we’re finally breaking it down into many extensions!! More on that another day…..
We’ve created a ‘library’ app which will house all of our common functionality. Each of our functional apps will have a dependency on the library app. Further to this, each of our functional apps will have its own test app. The test app of course has a dependency on the app that it is testing.
So……you create your test app, add a dependency to the app you are testing and compile…..NOPE…..failure. 😦
In the above example, what needs to be done is the dependency to Library also needs to be added to Test App 1.
Does this make sense? Maybe. This forces an assumption that because App 1 has direct access to the functions and entities within Library that Test App 1 also needs that access. In my example above, this direct access to Library was not something that we needed.
From the symbol perspective it perhaps makes a bit more sense. Adding the dependency forces the system to download the symbols for the dependent apps. If you just add the dependency for App 1‘ above, those symbols could be considered “incomplete” without also having the symbols for its dependencies, in this case Library.
I really (!!!) wish that we didn’t have to specify the extra dependencies. It would be nice if the compiler was able to figure out all of the downstream dependencies. An indirect dependency so to speak!?
The above scenario is fairly simple, but imagine this one:
ISV ‘AA’ creates a ‘app b’ that has a dependency on ‘app a’.
ISV ‘BB’ creates ‘app c’ that extends ‘app b’. 2 dependencies needed here (a, b).
ISV ‘BB’ sells ‘app c’ to a customer who then extends it even more with ‘app d’. This new app requires 3 dependencies (a, b, c).
…..look at all the apps now that have to be updated when ISV ‘AA’ releases a new version of ‘app b’? In an agile saas world, rapid small incremental releases are a reality. Is this also going to be magnified once Microsoft breaks down the main application into smaller (and perhaps dependent) apps?
Oh…in case you don’t know or don’t remember how to add dependencies to your app? It’s done in the app.json file in each app. See below for an example of what the app.json would look like in the ‘test app 1’ from the above example….
So how did I find this? As I mentioned, I’m working on a new build pipeline for our apps. My goal is that the pipeline will dynamically handle the dependencies so that it will update all of the versions in the app.json without the developer having to worry about that. After all, the build numbers are being generated from Azure DevOps so the developer is not going to know what the build numbers of the dependent apps will be. This little dependency hiccup caused a little bit of a wrench in my original plans.
More to come on the build pipeline later……..but spoiler alert…..it is working!!
Microsoft announced today that the April 2019 release notes for Dynamics 365 and the Power Platform are now available for download. These notes cover all products within these platforms, but for readers of this blog, you’re likely most interested in what’s coming for Business Central.
A few highlights of new end-user functionality for Business Central are:
Base application as an app (!!)
Yes, one of the things I’m looking forward to most is that we’re getting the base application moved from CAL objects to 2 AL extensions – system and application. Yes, the end of C/Side is coming.
Add multiple items to a sales or purchase order at once.
Name and description fields on master/document/journal records increased from 50 to 100 characters.
Watch out for this one in your ISV solutions as you may need to increase your field sizes to match!
New Physical Inventory Order and Physical Inventory Recording interfaces to enhance physical inventory functionality.
Set an expiration date for your sales quotes.
Merge duplicate customer and vendor records (!!).
Configurable reports for warehouse documents.
Save your filtered list views (!!).
Document focus mode – expend the item section on documents to see more data for faster entry.
More keyboard shortcuts – show/hide fact box, add item, previous/next navigation, etc.
Adjust field importance via personalization.
Page inspection – See all data elements of the current page record…….think the old ‘page zoom’ but even better!
The April 2019 release also includes improvements for developers working with AL extensions, such as:
Optimizing the experience of using VS Code with large projects.
new Outline View to show the symbol tree in teh current editor.
The in-client designer no longer makes dependencies on all apps, only the ones that have been used in the designer.
Attach to an existing client session for debugging.
Code Actions – have VS Code suggest ways to improve your code.
More protection for ISV’s over their IP.
Standard web API moving out of beta. Will support webhooks, OAS 3.0, OData v4, and versioning.
As noted in the release notes, the above features are things that are slated to be released anywhere between April 2019 and September 2019. Some of the features may be available for preview as early as February 2019.
The above is just a highlight of what’s coming down the road for Business Central and as you can see we are in for quite a lot of new features and quite a few old features that are being resurrected in a new a better way.
UPDATE (Jan 15, 2019): Since writing this post, the process has become much easier. Instead of downloading the nightly binaries, set your Docker Desktop installation to use the Edge channel and then get the latest update. That update now includes the executable files to run containers in process isolation mode! I’ll leave the original post here for posterity.
Well, it looks like all of the pieces are finally coming together where we can run our Docker containers on a Windows 10 machine without the overhead of Hyper-V. Don’t get me wrong, I’ve been running my containers always on Windows 10 without much issue, but being able to not have to allocate RAM and letting the o/s handle it like it does o Windows Server is a very welcome change!
The process you are about to see involves manually replacing your Docker executables with ones from the nightly Docker build repository. If this does not sound appealing to you, I’d recommend to wait for the official Docker release that has this feature.
First off, I’m not going to pretend I’m any sort of Docker expert. I know it well enough to be ‘comfortable’, but I’m still very much a rookie in many ways (Docker networking!? Yikes…..).
One thing I do know though, is that running Docker on Windows 10 has always leveraged the built-in Hyper-V layer to manage the containers. This adds some overhead when it comes to CPU and RAM usage, and also meant that you had to specify a “maximum RAM” amount for each container you created. Running too many containers meant likely a visit from the big bad blue screen. On an 8gb machine, that often means being able to run only 1 container at a time.
On Windows Server the Hyper-V is not required (although it can be if you desire) as the containers run in what is called Process Isolation. I’m not going to get into the differences of Process vs. Hyper-V isolation (you can read about that in Freddy’s Blog here) but the thing to know is that process isolation does NOT use Hyper-V, so no extra overhead, and no need to specify any amount of allocated RAM. You can run multiple containers at a time and the operating system manages everything.
What you need to do
Here are the steps to getting your system configured for running Docker containers in process isolation on Windows 10. This process assumes that you have already installed and configured Docker on your Windows 10 machine and that it functions correctly.
If you’ve not installed Docker yet, you can grab it from here. Make sure you set Docker to use Windows Containers.
1. Apply the Windows 10 – October 2018 update
The first thing that you need to do is apply the Windows 10 update from October 2018. This will update your system to version 1809 which is required in order to run Windows containers in process isolation mode.
By the time this post is published, you should be able to get the update via Windows Update, but if not, you can manually the update using the Update Assistant here.
After the update you can use the winver command from your Start Menu to verify you are running version 1809.
2. Download Docker nightly build binaries
The ability to run Windows 10 containers without Hyper-V is not a fully baked feature in Docker yet (again, if this freaks you out, you may want to wait), so what you need to do is get the nightly build binaries that contain this feature.
These files can be downloaded from the Docker Master Binaries site. You have a load of options here as to what binaries you can get so make sure you get the correct ones.
I find the easiest file to grab is the docker.zip file. You can bookmark the link to this file and it will always get you the latest build. If you are browsing through the site though, the file I’m talking about is shown below. You can see that as of this post the file is dated December 20, 2018 but that will likely change by the time you download the file.
Once you have the file downloaded, extract it and you will see 2 files inside:
3. Replace Docker binaries
We need to replace the Docker binaries that are currently installed with the new ones that we downloaded in step 2. The first thing we need to do is stop Docker. Do that by right-clicking on the Docker whale icon in your system tray and choose Quit Docker Desktop.
Once Docker has stopped we can replace the existing files. You will find them at the following locations:
Before replacing any files make sure to backup the original ones!!!
One thing to note is that after doing this a few times, once in a while stopping Docker was not enough. I also had to also go into Computer Management and stop the Docker services. Just right-click on each service and select Stop.
4. Reboot your system
Yes you can probably just restart everything that you stopped in step 3, but I like to do a full reboot so that I can make sure everything starts up as it normally would.
5. Turn off Docker update checks [OPTIONAL]
This step is not required, but you may or may not want to do it depending on how tempted you are to click on the install button the next time Docker prompts you that an update is available. Remember we’re using an unreleased set of binaries now so before taking any update, make sure that it’s not going to revert you to a version that still requires Hyper-V. If you want to turn off those prompts you can do so in the Docker Desktop Settings.
If you are using NavContainerHelper to do all of your Dynamics NAV and Business Central container management then make sure to update to the latest version so that when you pull images and create containers, you will be pulling the ltsc2019 images, which Freddy explains all about here.
At this point your system is ready to run Windows containers without Hyper-V. If you have any containers that you had created before doing the above updates, they will continue to use Hyper-V. You’ll probably want to recreate those containers using the new ltsc2019 images and if you do, you will then want to clean up your old containers and images. Once again, I can defer to Freddy on how to do this.
That’s it! Until next time, happy coding…and Dockering!?
Since moving to the Web Client, have you seen this at all?
You probably have, and has it popped up while you were running a long processing routine? Yup, me too.
When you kick off those long running routines, your Web Client basically sits idle while the Service Tier is doing the processing. Once that idle time reaches a particular limit, the Service Tier shuts down that connection.
Changing the Timeout
When using the Windows Client it’s easy to set a few settings (see here) in the Service Tier configuration in order to adjust the session timeout for a user. Moving forward it remains just as easy, but if you’ve tried using the same settings as in the past, you’ve certainly noticed that they don’t work for the Web Client.
The session timeout for the Web Client is now controlled by settings in the Service Tier and in the Web Client configuration. I wish there was just a single setting, but maybe once the Windows Client is gone we’ll see some of the old settings go away as well.
In order to change the timeout for the Web Client, you need to change two settings. Thankfully though, we can easily change these settings using a couple of PowerShell commands.
This setting is found in the Microsoft Business Central Server configuration.
Set-NAVServerConfiguration DynamicsNAV130 -KeyName ClientServicesIdleClientTimeout -KeyValue "00:20:00"
The timeout uses this format: [dd.]hh:mm:ss[.ff]
dd – number of days
hh – number of hours
mm – number of minutes
ss – number of seconds
ff – hundredths of a second
You can also set the setting to “MaxValue” in order to indicate no timeout. This is also the default value for a new installation.
You must restart the service tier for the new setting to take affect.
This settings is found in the navsettings.json file on the Microsoft Business Central Web Server.
Set-NAVWebServerInstanceConfiguration -WebServerInstance DynamicsNAV -KeyName SessionTimeout -KeyValue "00:20:00"
The timeout uses this format: [dd.]hh:mm:ss[.ff]
dd – number of days
hh – number of hours
mm – number of minutes
ss – number of seconds
ff – hundredths of a second
The default value is “00:20:00” (20 minutes) for a new installation.
How the Settings Work Together
The above settings are used in conjunction with each other. A session will be closed based on the setting that has the shortest time period.
By default, the ClientServicesIdleClientTimeout setting is set to “MaxValue”, which means that in a default installation, the SessionTimeout settings is what will be used.
This is also why configuring the Microsoft Business Central Server to a higher timeout does not work, since it is the setting that has the shortest time period that is used.
The ClientServicesIdleClientTimeout and SessionTimeout settings work together to determine the timeout of the Web Client. The setting that has the shortest time period is the one that is used to determine when a session is closed.
This means that in a default installation you can leave the ClientServicesIdleClientTimeout at its default value, and just change the SessionTimeout setting on the Microsoft Business Central Web Server to your desired timeout.
You can read more on this and other configurations you can set here.
I realize it’s been a bit since my last update. I decided to take a week of vacation (no, not brought on by this conversion process!), and then decided to build a separate app to compliment our ISV solution.
Back to the conversion project….I’ve been able to spend a little over a week worth of time since my last update , so I’ve got a few things I want to cover.
I was asked a little while ago what the environment is that I’m using, so I’ll quickly explain. I’m using a 100% Docker-based system running locally on my Windows 10 Surface Book 2. With the luxury of 16GB RAM available on my machine, I’m able to dedicate 8Gb to the container which allows for a nice smooth experience without having to switch to Windows Server. If you want to get into building your own Docker system, check out Freddy’s post to get started.
As for the image I’m using, I’m using the Business Central insider builds. You may want to choose another image to use, or if you do not have access to the insider builds, refer to this post to determine what’s right for you.
Where I’m at in the Process
At the end of the last post, I had tackled some of the easy things. Now I’ve been able to get through a large chunk of the more tedious things. Remember, I’m not necessarily fixing issues right now, I’m still working my way towards an extension that builds and publishes.
Issue 1 – Report DataItem Naming
One of the issues that came up was with the converted reports. It appears as though that in our original reports, we had named various dataitems with the name ‘Labels‘. Well, guess what is a reserved word in AL? This was an easy fix as it just involved renaming the dataitem. Since then I’ve tried to reproduce the error but can’t so I’m wondering if this has already been fixed in the more recent AL Language extension that I’m now using.
Issue 2 – TextConst Dropped in Conversion
This is a weird one. I’ll mention it here in case anyone else comes across this, but somewhere in the conversion of my CAL objects to AL files, a bunch of TextConst variables were dropped. I don’t know why, and there’s a possibility that it was something I did, but nonetheless, I had a bunch of files I had to go through in order to fix the missing variables.
Issue 3 – Trigger Modifications Unsupported…or are they?
This one was a bit annoying, but I understand why it gets flagged by the Txt2AL conversion tool. If you’re converting an ISV, you’ll be almost certain to run into this.
In our table and page extensions, there a load of places where we had put code in various triggers, such as onValidate, onOpenPage, onAfterGetCurrentRecord, etc. All of these changes were marked as “Unsupported” during the conversion process.
In your extension object, you do get a nice indicator of what the change was. The example below shows that I had made a modification to the onOpenPage trigger and added some code at the end to populate a ‘User ID Filter’. I often found myself going back to C/Side just to confirm the exact modification and what code was around it.
This is a bit misleading though as you can create the necessary trigger in the extension file and then add your existing code to it. Using the example above, my extension file would now look like this:
The caveat to this, and the reason why the conversion tool flags these changes, is that you have to be conscious of when the triggers are fired and where your original code was within the trigger.
If your original code was at the start of the onOpenPage trigger, your new code that you add to the onOpenPage trigger in the extension object will not fire until AFTER the “original” trigger fires. Given this, while you may be able to recreate the triggers needed and then put your code in them, the code may not actually be firing when you intended.
If your existing trigger code was somewhere in the middle of the old trigger code, you will need to rework your solution to work within the onBefore/onAfter structure.
Some triggers (e.g. onInitPage) are not available in extension objects, so you need to also be aware that. Available triggers also change depending on whether or not you are adding your own field/control to the page, or modifying an existing one.
You can always see what triggers are available to you by pressing Ctrl+Space after the ‘trigger‘ keyword.
Knowing all of this information, you can see why it doesn’t make sense for the conversion to automatically assume all of your trigger code can simply be moved over ‘as-is’. This one’s going to require some developer know-how!
Alright, I think this is it for now. I have more updates to share, but at the risk of turning this post into a novel, I’ll save those for the next post.
Well, what can I say……….this is going to take a while. 🙂
If you have no idea what I am talking about, start here.
After getting the objects converted to AL (using the standard process documented here), I was immediately greeted in VS Code by just over 8000 errors/warnings. Great start.
Luckily half of those were generated by the code analyzers. If you don’t know what they are, see here. Yes, at this point in the project I am considering that half of my issues are “only non-standard and poorly formatted code”. Gotta try and stay positive!
This brings me to my first “recommendation” in this process:
Turn off ALL the code analyzers.
The code analyzers are enabled by default, but can be turned off in your VS Code settings (see here). Once I get to a point where the “non-syntax” errors have been addressed, I will re-enable the analyzers and then deal with that barrel of fun. I’ve done this before in much smaller apps and very quickly got tired of typing ‘()’ 🙂
After disabling the analyzers my list of errors/warnings was brought “all the way down to” just over 4000. Oh wow I’m halfway done!! Ha ha.
The next thing I decided to tackle was another fairly easy one. Dealing with Product Group Code. If you’re not already aware, the Product Group Code feature was deprecated in favour of an improved parent/child Item Category structure. The Product Group Code fields however, were left in the product (not sure I like that, but we can deal with it) and marked for removal (see ObsoleteState property here). This causes any references to those fields to be flagged as warnings. Your code will still build, but this is a heads up that at some point in the future the fields will (probably!?) be removed. As I’m already head deep in things to fix and rework, I decided I’d rather not wait and I will remove references to those fields now.
For me, removing the references to the Product Group Code was pretty easy. We didn’t have major functionality tied to that field, but other ISV solutions may require more effort to remove those references, or, you may just choose to not deal with that now. Up to you.
That’s my update for now, back to fixing more issues. More updates to come!
I’ve been a bit neglectful of this blog for the past couple of months, but with good reason. I’ve been jumping around multiple projects, and have now landed on a project that will involve taking a roughly 3000 object ISV solution and converting it to an extension.
Easy task? Far from it.
Challenging? Most definitely……but that’s why we do what we do right!?
Will I be able to convert it all to an extension? Not likely. (Yes, extensions still have limitations)
What I want to do is post my journey through this project. It’s not going to be a step by step of what to do as this is the first time I’m doing this. Up to this point, my extension experience was building them from scratch, or taking small pieces of existing features and recreating them as extensions. Nothing on this level yet. In all likelihood, I will make some mistakes, or perhaps do things in a less than ideal way, but that’s what I hope to capture in these posts, so that hopefully others can learn form them……and so I don’t make the same mistakes the next time I might have to do this. 🙂
Now first off, I want to be clear that having a 3000+ object extension is not ideal. It’s not the actual end-goal of this project. The overall solution will eventually be broken down into a set of smaller extensions, but as the current design and code is so tightly integrated, we’re moving it all to AL now and will break it apart later.
My “plan” (I use that word loosely) for this project when I began was to do the following:
Convert all net new ISV objects to AL.
Convert modified base tables and pages to AL (e.g. table/page extension files).
Rework existing C/Side objects to move to an AL-based solution.
The first 2 steps leave me with a large portion of the solution in AL, but also a portion back in C/Side. The objects in C/Side would be the base codeunits, xmlports, reports, etc. that were modified by the ISV solution but can’t be directly converted to AL. That’s where step 3 comes into play. Reworking the objects to use events and/or redesigning the ISV solution is going to be required to clean up those objects. All in all, a lot of work, and I know for a fact that I will come across some design that cannot be replicated in an extension…..so stay tuned for however I’m going to deal with that!
Look for more posts on this as I move through this process. I hope to show both the good and the bad so that people can get a sense of what they’re in for if they come across a similar project.
I recently came across this article, which talks about using the Azure Cloud Shell to create an Azure Container Instance that runs Dynamics 365 Business Central. I was intrigued because Azure Container Instances has just recently been released to the public and I just gotta try the new stuff! Thanks Andrey for that article!
What is an Azure Container Instance you might be asking? If you’ve been keeping up with Dynamics 365 Business Central development, you have been using containers to create your environments. This requires Docker to run either on a Windows 10 or Windows 2016 Server machine that’s either hosted or on-prem. Either way, you’re carrying the overhead of the machine, physical or virtual. With Azure Container Instances, you can create the containers directly in Azure without that machine overhead. This ‘should’ translate to some sort of cost savings, but as my container has only been up for about 2 hours as of the time of this article, I don’t yet know if or how much savings there will be.
In Andrey’s post, he walks you through using the Azure Portal and Azure Cloud Shell to create the container. Being the ‘lazy developer’ that I am though, I prefer to do as little manual work as possible so I thought I’d take a stab at building PowerShell script that I can run locally and potentially automate the entire process. Yup, even opening my browser to paste the code into Azure Cloud Shell is apparently too much work for me. 🙂
Turns, out this is pretty easy to do. Using the Azure Resource Manager PowerShell module, we can easily connect to our Azure account, and create the necessary container pieces.
The first thing we need to do is connect to our subscription and tenant. The user will be prompted for credentials when this command is executed. If you don’t know what your subscription and tenant IDs are, you can find instructions here for the subscription ID, and here for the tenant ID.
Once we’re connected we need to create the Azure Resource Group that will be used for our container instance.
Once the resource group is created now we can create the container. This is where we get to set the parameters for the container. One change I made from Andrey’s initial post is that I assigned the container the DnsNameLabel, which will mean we can use the Fqdn to access the container instead of the IP address. If you’ve used FreddyK‘s NavContainerHelper module, you’ll also notice that the parameters here are similar to some of the ones used by the New-NavContainer commandlet. Hey maybe we can get some new additions to the module for this stuff!
Ok…..here’s the actual code. It’s pretty basic at this point in time. Just getting my feet wet to see how it goes.
When you run the above code you’ll see various properties of the container. What you want to pay attention to are ProvisioningState and State, which will appear as ‘Creating‘ and ‘Pending‘ as shown below.
Once the container has been created, you should see the following statuses:
Take note of the Fqdn property and save the address value. This is the address that you will need to use to connect to your Business Central environment later on.
Once your container has a State of ‘Running‘, you can check the container logs by using the following code:
Running the above code will show you the container logs, and again, if you’ve been using the NavContainerHelper, these logs will look very familiar to you:
When you connect to your container via Visual Studio Code or the Web Client, or to download the VSIX, you need to use the address from the FQDN property of the container instance, and not the address values that you see in the container logs. See some examples below:
If you have access to the private insider builds for Business Central, you need to provide credentials in order to access the Docker image registry. You can do that by adding the ‘-RegistryCredential‘ parameter and supplying a PSCredential object to the New-AzureRmContainerGroup command.
Oh, if you’re into this kind of thing, you can check out the Azure Container InstanceSLA here. It’s a super fun read! 🙂
Thanks again to Andrey Baludin for his original post on Azure Container Instances!
In this post, I showed you how you can use Microsoft Dynamics Lifecycle Services to translate your AL project into a variety of languages.
As with most things, there are multiple ways to go about doing things. This post will look at the Microsoft Multilingual App Toolkit. This toolkit is integrated into Visual Studio but there is also a standalone version, called the Multilingual App Toolkit Editor.
With this tool you can manually do your translation and/or you can connect it to the Microsoft Translator service via Azure, which is what I will describe in this post.
If all you want to do is work offline and manually do your translations, you can stop here. Continue on if you want to connect to the translation service in Azure, but note that you do need an active Azure subscription for this.
Enable the Translator Text API in Azure.
Using the Azure portal, do the following to add the Translator Text API to your Azure subscription:
Choose “Create a Resource“.
Search for “Translator Text API” and select it.
On the Translator Text API blade, press the Create button to begin configuring the subscription.
Fill in the fields accordingly for the API by giving it a name, pricing tier, etc. Note that there is a free tier option that lets you translate up to 2 million characters per month. Additional pricing info can be found here.
Press the Create button to deploy the API to your Azure subscription. This might take a few minutes.
Get your Translator Text API authentication key.
Once the API has been deployed, you need to get your subscription key so that the Multilingual Tool can authenticate and connect to it.
In the Azure portal, select “All Resources” and select the Azure subscription that you deployed the API to.
In the list of resources, click on the Translator Text API service that you deployed.
In the Translator Text API blade, select Keys.
Copy one of the 2 keys that are associated with the service. You will need this key value in the next step.
Add Multilingual App Toolkit Editor credentials.
Now that we have the Translator Text API up and running, and the Multilingual App Toolkit Editor installed, we need to configure the authentication. We do that using the Windows Credential Manager.
On your Windows machine, launch Credential Manager.
Select Windows Credentials.
Select Add a generic credential.
Enter the following values:
Internet or network address: Multilingual/MicrosoftTranslator
User name: Multilingual App Toolkit
Password:<the Translator Text API key that you retrieved earlier>
Close Credential Manager.
Ok, now that we have everything installed, deployed, and configured, we can open up the Multilingual App Toolkit Editor (search Multilingual Editor in your Start menu) and translate the XLF file from our AL project. You can learn about generating this file here.
Copy the auto-generated ‘.g.xlf‘ file to create a new file in the same folder. Rename the file based on the AL standards here.
Edit the new file and update the ‘target-language‘ property to be the language that you are translating the file to (e.g. fr-CA).
Close and save the file.
Using the Multilingual App Toolkit Editor, select Open and select your new file.
From the ribbon, select Translate > Translate All. The toolkit will now use the Translator Text API in Azure to translate the file based on the source and target languages. This might take a few minutes based on the numbers of items that need to be translated in your solution.
Once the translation is done you can manually review and edit any if you wish.
Close and save the file.
Now you have your new translation file. Simply repeat the steps to generate each translation that your AL solution requires!
Submitting to AppSource
If you are submitting your solution to AppSource, even if you do not need multi-language support in your solution, you still must provide (at a minimum) a translation file for the base language (e.g. en-US) in which your solution is published.
Note that the auto-generated ‘.g.xlf’ file is NOT a properly formatted translation file and your solution will not pass validation if you do not create at least the base language file.
In the pic below you have the raw ‘.g.xlf’ file as it gets created during the AL project build process. As you can see, there is only a ‘source‘ property for the message control even though the ‘target-language‘ of the file is set to ‘en-US’:
In a properly formatted translation file, you will have both the ‘source‘ and the ‘target‘ properties:
In addition to the formatting, you can’t rely on editing the ‘.g.xlf’ file because it gets overwritten each time you build your AL project.
In short, use the ‘.g.xlf’ file ONLY as the source for generating other translation files.
EDIT: I was just informed that fellow MVP Tobias Fenster‘s ALRunner VS Code extension can (among many things) convert the ‘.g.xlf’ translation file into a properly formatted file. Quick and easy! Check it out here!