Add translations to your NAV/BC Server

Yesterday I got a question via LinkedIn. I need to add Spanish translation to my W1 instance. How do I do that?

So, let me walk you through that process.

Here is my Business Central setup. It is the Icelandic Docker Container, so I have Icelandic and English. Switching between Icelandic and English works just fine.

Switching to Spanish gives me a mix of Spanish and English.

The Spanish translation for the platform is shipped with the DVD image and automatically installed. So are a lot of other languages.

Icelandic and English are built in captions in the C/AL code. And even if all these languages are shipped with the platform, these languages are not shipped with the application.

There is a way to get these application translations from the appropriate release and add them to your application.

Let’s start in VS Code where I have cloned my Business Central repository from GitHub. I opened the workspace file and also opened “setup.json” from the root folder of my repository.

This configuration points to the W1 Business Central OnPrem Docker Image. Now, let’s point to the Spanish one.

And let’s build a container.


Switching the Terminal part to AdvaniaGIT, I see that I am now pulling the Spanish Docker image down to my laptop.

This may take a few minutes…

After the container is ready I start FinSql.exe

Just opening the first table and properties for the first field I can verify than I have the Spanish captions installed.

So, let’s export these Spanish captions by selecting all objects except the new trigger codeunits (Business Central only) and selecting to export translation…

Save the export to a TXT file.

Opening this file in Visual Studio Code, we can see that the code page does not match the required UTF-8 format. Here we can also see that we have English in lines with A1033 and Spanish in lines with A1034.

We need to process this file with PowerShell. Executing that script can also take some time…

This script reads the file using the “Oem” code page. This code page is the one FinSql uses for import and export. We read through the file and every line that is identified as Spanish is the added to the output variable. We end by writing that output variable to the same file using the “utf8” code page.

Visual Studio Code should refresh the file automatically.

We need to create a “Translations” folder in the server folder. The default server uses the root Translations folder.

If you have instances then the “Translations” folder needs to be in the Instance.

Since I am running this in a container I may need to create this folder in the container.

Then, copy the updated file to the “Translations” folder.

And make sure it has been put into the correct path.

We need to restart the service instance.

Then in my Web Client I can verify that the Spanish application language is now available.

That is it!

Here is the PowerShell script

$LanguageFile = Get-Item -Path C:\AdvaniaGIT\Workspace\es.txt

Write-Host "Loading $($LanguageFile.Name)..."
$TranslateFile = Get-Content -Path $LanguageFile.FullName -Encoding Oem
$i = 0
$count = $TranslateFile.Length
$StartTime = Get-Date
foreach ($Line in $TranslateFile) {
    $i++
    $NowTime = Get-Date
    $TimeSpan = New-TimeSpan $StartTime $NowTime
    $percent = $i / $count
    if ($percent -gt 1) 
    {
        $percent = 1
    }
    $remtime = $TimeSpan.TotalSeconds / $percent * (1-$percent)
    if (($i % 100) -eq 0) 
    {
        Write-Progress -Status "Processing $i of $count" -Activity 'Updating Translation...' -PercentComplete ($percent*100) -SecondsRemaining $remtime
    }

    if ($Line -match "A1034") {
        if ($TranslatedFile) {
            $TranslatedFile += $Line + "`r`n"
        } else {
            $TranslatedFile = $Line + "`r`n"
        }
    }
}

Write-Host "Saving $($LanguageFile.Name)..."
Remove-Item -Path $LanguageFile.FullName -Force -ErrorAction SilentlyContinue
Out-File -FilePath $LanguageFile.FullName -Encoding utf8 -InputObject $TranslatedFile -Force

In this post I used both AdvaniaGIT and NAVContainerHelper tools. Good luck.

Business Central Docker on Windows 10

In Advania we are switching more and more to using the Docker images for Dynamics NAV and Business Central development.

Since version 1809 of Windows 10 and the latest blog post from Arend-Jan Kauffmann we are moving to using the Docker EE engine instead of the Docker Desktop setup.

Using the latest Windows 10 version and the latest version of Docker means that we can now use “Process Isolation” images when running NAV and Business Central.

Not using process isolation images on Windows 10 requires Hyper-V support. Inside Hyper-V a server core is running as the platform for the processes executed by the container created from the image. If using process isolation images then the Windows 10 operating system is used as foundation and Hyper-V server core is not needed. Just this little fact can save up to 4GB of memory usage by the container.

Freddy Kristiansen announced in this blog that his PowerShell Module, NAVContainerHelper, had support for selecting the proper Docker Image based on the host capabilities.

We have had some issues with our Windows installations and I wanted to give you the heads up and how these issues where resolved.

First thing first, make sure that you are running Windows 10 version 1809 or newer. Execute

winver.exe

in Windows-R to get this displayed.

Optional, make sure to remove the Hyper-V support if you are not using any virtual machines on your host. If you have version 1903 or later I suggest enabling the Hyper-V feature.

Restart your computer as needed.

Start PowerShell ISE as Administrator.

Copy from Arend-Jan‘s blog the Option 1: Manual installation script into the script editor in Powershell ISE and execute by pressing F5.

If you have older Docker Images download you should remove them. Executing

docker rmi -f (docker images -q)

in your PowerShell ISE prompt.

Now to the problems we have encountered.

The NAVContainerHelper added a support for the process isolation images just a few releases ago. Some of our machines had older versions installed and that gave us problems. Execute

Get-Module NAVContainerHelper -ListAvailable

in PowerShell ISE prompt to make sure you have version 0.5.0.5 or newer.

If you have any other versions installed use the File Explorer to delete the “navcontainerhelper” folder from

 C:\Program Files (x86)\WindowsPowerShell\Modules

and

C:\Program Files\WindowsPowerShell\Modules

Then execute

Install-Module NAVContainerHelper

in PowerShell ISE prompt to install the latest versions. Verify the installation.

We also had problems downloading the images. Getting the error “read tcp 172.16.4.17:56878->204.79.197.219:443: wsarecv: An existing connection was forcibly closed by the remote host.“.

My college in Advania, Sigurður Gunnlaugsson, figured out that multiple download threads caused network errors.

In PowerShell ISE prompth execute

Stop-Service docker
dockerd --unregister-service

to remove the docker service. Then re-register docker service using

dockerd --exec-opt isolation=process --max-concurrent-downloads 1 --register-service
Start-Service docker

in the PowerShell ISE prompt.

This should result in only one download thread and this way our download was able to complete.

More details on Docker images for Dynamics NAV and Business Central can be found in here.

Waldo’s Blog on Docker Image Tags

AdvaniaGIT and Docker

Tobias Fenster on Docker

Freddy´s Blog

Use references to break compile dependencies

I was looking into a customer App yesterday. That app had a dependency defined in app.json.

I wanted to look at the real requirements for this dependency. I found 1 (one) place in my code where this dependent App was used.

dataitem(PageLoop; "Integer")
{
    DataItemTableView = SORTING (Number) WHERE (Number = CONST (1));
    column(Phone_No_Cust; Cust."Phone No.")
    {
    }
    column(Registration_No_Cust; Cust."ADV Registration No.")
    {
    }
    column(CompanyInfo_Picture; CompanyInfo.Picture)
    {
    }

In Iceland we add a field to the Customer table (Cust.”ADV Registration No.”). Every business entity in Iceland has a registration number. A company only has one registration number but can have multiple VAT numbers. We already have that registration number field in the Company Information record, but we also add it to Customer, Vendor and Contact records. The Employee social security number equals to the registration number for an individual.

To be able to remove the compile dependency, and therefore the installation dependency I did the following:

Removed the dependency App from app.json

Added a variable to the report

    var
        ADVRegistrationNo: Text;

Changed the data set configuration to use this variable

dataitem(PageLoop; "Integer")
	{
	    DataItemTableView = SORTING (Number) WHERE (Number = CONST (1));
	    column(Phone_No_Cust; Cust."Phone No.")
	    {
	    }
	    column(Registration_No_Cust; ADVRegistrationNo)
	    {
	    }
	    column(CompanyInfo_Picture; CompanyInfo.Picture)
	    {
	    }

Located the code that fetches the Customer record and added the reference way to get the required data

trigger OnAfterGetRecord()
var
    DataTypeMgt: Codeunit "Data Type Management";
    RecRef: RecordRef;
    FldRef: FieldRef;
begin
    Cust.Get("Ship-to Customer No.");
    RecRef.GetTable(Cust);
    if DataTypeMgt.FindFieldByName(RecRef, FldRef, 'ADV Registration No.') then
        ADVRegistrationNo := FldRef.Value();
end;

There are both positive and negative repercussion of these changes.

The positive is that we can now install, uninstall both apps without worrying about the compile dependency.

The negative is that breaking changes to the dependent App does not break the installation of this customer App.

So, what happens if the dependent App is not installed? The FindFieldByName will return false and the variable will be blank text.

Since we have adapted the policy that Microsoft uses; no breaking table changes, this field should just be there.

If the data is required and will break the functionality if not present we can change the code to something like this.


Cust.Get("Ship-to Customer No.");
RecRef.GetTable(Cust);
if DataTypeMgt.FindFieldByName(RecRef, FldRef, 'ADV Registration No.') then
    ADVRegistrationNo := FldRef.Value()
else
    Error('Please install the Advania IS Localization App into this Tenant!')