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.

Why do we need Interface Codeunits

And what is an interface Codeunit?

A Codeunit that you can execute with CODEUNIT.RUN to perform a given task is, from my point of view, an interface Codeunit.

An interface Codeunit has a parameter that we put in the

This parameter is always a table object.

We have multiple examples of this already in the application.  Codeunits 12 and 80 are some.  There the parameter is a mixed set of data and settings.  Some of the table fields are business data being pushed into the business logic.  Other fields are settings used to control the business logic.

Table 36, Sales Header, is used as the parameter for Codeunit 80.  Fields like No., Bill-to Customer No., Posting Date and so on are business data.  Fields like Ship, Invoice, Print Posted Documents are settings used to control the business logic but have no meaning as business data.

Every table is then a potential parameter for an interface Codeunit.  Our extension can easily create a table that we use as a parameter table.  Record does not need to be inserted into the table to be passed to the Codeunit.

Let’s look at another scenario.  We know that there is an Interface Codeunit  with the name “My Interface Codeunit” but it is belongs to an Extensions that may and may not be installed in the database.

Here we use the virtual table “CodeUnit Metadata” to look for the Interface Codeunit before execution.

This is all simple and strait forward.  Things that we have been doing for a number of years.

Using TempBlob table as a parameter also gives us flexibility to define more complex interface for the Codeunit.  Tempblob table can store complex data in Json or Xml format and pass that to the Codeunit.

Let’s take an example.  We have an extension that extends the discount calculation for Customers and Items.  We would like to ask this extensions for the discount a given customer will have for a given Item.  Questions like that we can represent in a Json file.

{
    "CustomerNo": "C10000",
    "ItemNo": "1000"
}

And the question can be coded like this.

The Interface Codeunit could be something like

With a Page that contains a single Text variable (Json) we can turn this into a web service.

That we can use from C# with a code like

var navOdataUrl = new System.Uri("https://nav2018dev.westeurope.cloudapp.azure.com:7048/NAV/OData/Company('CRONUS%20International%20Ltd.')/AlexaRequest?$format=json");
var credentials = new NetworkCredential("navUser", "+lppLBb7OQJxlOfZ7CpboRCDcbmAEoCCJpg7cmAEReQ=");
var handler = new HttpClientHandler { Credentials = credentials };

using (var client = new HttpClient(handler))
{
    var Json = new { CustomerNo = "C10000", ItemNo = "1000" };
    JObject JsonRequest = JObject.Parse(Json.ToString());
    JObject requestJson = new JObject();                
    JProperty jProperty = new JProperty("Json", JsonRequest.ToString());
    requestJson.Add(jProperty);
    var requestData = new StringContent(requestJson.ToString(), Encoding.UTF8, "application/json");
    var response = await client.PostAsync(navOdataUrl,requestData);
    dynamic result = await response.Content.ReadAsStringAsync();

    JObject responseJson = JObject.Parse(Convert.ToString(result));
    if (responseJson.TryGetValue("Json", out JToken responseJToken))
    {
        jProperty = responseJson.Property("Json");
        JObject JsonResponse = JObject.Parse(Convert.ToString(jProperty.Value));
        Console.WriteLine(JsonResponse.ToString());
    }
}

This is just scratching the surface of what we can do.  To copy a record to and from Json is easy to do with these functions.

And even if I am showing all this in C/AL there should be no problem in using the new AL in Visual Studio Code to get the same results.

Don’t worry about DotNet version in C/AL

When using DotNet data type in NAV C/AL we normally lookup a sub type to use.  When we do the result can be something like

Newtonsoft.Json.Linq.JObject.'Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'

Then, what will happen when moving this code from NAV 2016 to NAV 2017 and NAV 2018.  The Newtonsoft.Json version is not the same and we will get a compile error!

Just remove the version information from the sub type information.

Newtonsoft.Json.Linq.JObject.'Newtonsoft.Json'

And NAV will find the matching Newtonsoft.Json library you have installed and use it.

This should work for all our DotNet variables.

Using AdvaniaGIT – Docker Containers

There is a new kid in town.  His name is Docker.

Microsoft is saying:

We are currently investigating how we can use Docker for deploying NAV. For test purposes we have created a Docker Container Image with the NAV Developer Preview, which you can try out.

Docker Containers is a technology where you, instead of virtualizing the entire machine, only virtualize the services and share resources from the host computer. Read more about it here: https://www.docker.com/what-docker

Read more about how to get started with Docker here: https://docs.docker.com/docker-for-windows/install/

So what does this mean?

We can install NAV environments as container both in Azure and on premise.  We can have multiple NAV versions to work with without having to install, so there is no conflict.  We can also get access to container images that are still in preview.

Note what Microsoft says, they are investigating.  The NAV Container Image service is not public yet.  You need authentication to get access.  This project has a place on GitHub.  To get access please contact Microsoft or send me a line and I will point you in the right direction.

The easiest way to get started is to try the NAV Developer Preview template on Azure,  http://aka.ms/navdeveloperpreview.  This will give you a full development environment for NAV Extension2.0 using VS Code.

It should be straight forward to install AdvaniaGIT on top of the NAV Developer Preview and start from there.  We can also start from Azure template called “Windows Server 2016 Datacenter – with Containers”.

The local option is to install Docker on our Windows laptop.  If you would like to use Docker on your laptop you need to change one setting after installation.  You need to switch to Windows containers.  Your laptop will most likely restart during installation and configuration of Docker so make sure to have your work saved.

If you are planning to run a Docker-Only environment you don’t need to install NAV.  Still there are prerequisite components that you must install.  These components can be found on the NAV DVD folder “Prerequisite Components”.  From the “Microsoft SQL Server” folder install sqlncli64.msi and ReportBuilder3.msi.  From the “Microsoft Visual C++ 2013” folder install vcredist_x64.exe.  From the “Microsoft Visual Studio 2010 Tools for Office Redist” install vstor_redist.exe.  From the “Microsoft Report Viewer” folder install both SQLSysClrTypes.msi and ReportViewer.msi.  You should now be ready for the next step.

So, let’s get started

In your C:\AdvaniaGIT\Data folder open DockerSettings.json

{
  "RepositoryPath": "navdocker.azurecr.io",
  "RepositoryUserName": "7cc3c660-fc3d-41c6-b7dd-dd260148fff7",
  "RepositoryPassword": "access_key_for_the_repository"
}

That’s all.  Your are now ready to use Docker with AdvaniaGIT.  Make sure to always have the latest version of AdvaniaGIT installed.

You can even use the “devpreview” build of NAV TENERIFE to do vNext development both in C/AL and AL.

Stay tuned, the AdvaniaGIT journey will continue…

Using AdvaniaGIT – How to configure licenses

When NAV environments are built a development license is uploaded to the development database.

Make sure to save your development licenses in your AdvaniaGIT license folder.

There are two ways to configure which license is used.  First with the “licenseFile” property of GITSettings.json in your AdvaniaGIT data folder.

{
  "ftpServer": "ftp://ftp02.hysing.is/",
  "ftpUser": "ftp_sourcetree",
  "ftpPass": "******",
  "licenseFile": "Advania.flf",
  "workFolder": "C:\\AdvaniaGIT\\Workspace",
  "patchNoFunction": "Display-PatchDayNo",
  "defaultDatabaseServer": "localhost",
  "defaultDatabaseInstance": "",
  "objectsNotToDelete": "(14125500..14125600)",
  "sigToolExecutable": "C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64\\signtool.exe",
  "codeSigningCertificate": "",
  "codeSigningCertificatePassword": "",
  "setupPath": "setup.json",
  "objectsPath": "Objects",
  "deltasPath": "Deltas",
  "reverseDeltasPath": "ReverseDeltas",
  "extensionPath": "Extension1",
  "imagesPath": "Images",
  "screenshotsPath": "ScreenShots",
  "permissionSetsPath": "PermissionSets",
  "addinsPath": "Addins",
  "languagePath": "Languages",
  "tableDataPath": "TableDatas",
  "customReportLayoutsPath": "CustomReportLayouts",
  "webServicesPath": "WebServices",
  "binaryPath": "Binaries",
  "targetPlatform": "DynamicsNAV",
  "VSCodePath": "AL",
  "NewSyntaxPrefix": "NewSyntax"
}

Just specify the file name in the license folder.

The license configured in GITSettings.json will be used as default for all builds.  However, as mentioned earlier, every settings in GITSettings.json can be overwritten by the settings in each branch.

The settings file in each branch is named according to the “setupPath” parameter in GITSettings.json.  If we take a look at the setup.json file for my G/L Source Names solution.

{
  "branchId": "479e77f3-031a-49fe-bb6a-314464c6a9a8",
  "navVersion": "10.0.15052.0",
  "navSolution": "W1",
  "projectName": "GLSOURCENAMES",
  "baseBranch": "master",
  "storeAllObjects": "false",
  "uidOffset": "70009200",  
  "licenseFile": "Kappi.flf",
  "versionList": "GLSN10.0",
  "objectProperties": "true",
  "datetimeCulture": "is-IS",
  "targetPlatform": "Dynamics365",
  "appId": "479e77f3-031a-49fe-bb6a-314464c6a9a8",
  "appName": "G/L Source Names",
  "appPublisher": "Objects4NAV",
  "appVersion": "1.0.0.1",
  "appCompatibilityId": "",
  "appManifestName": "G/L Source Names",
  "appManifestDescription": "G/L Source Names adds the source name to the G/L Entries page.  Source Name is the customer in sales transaction and the vendor in purchase transactions", 
  "appBriefDescription": "Source Names in G/L Entries",
  "appPrivacyStatement": "http://objects4nav.com/privacy",
  "appEula": "http://objects4nav.com/terms",
  "appHelp": "http://objects4nav.com/glsourcenames",
  "appUrl": "http://objects4nav.com",
  "appIcon": "Logo250x250",  
  "appDependencies":
    [
  
    ],
  "appPrerequisites":
    [
  
    ],
  "permissionSets":
    [
  		{"id": "G/L-SOURCE NAMES",    "description": "Read G/L Source Names"},
  		{"id": "G/L-SOURCE NAMES, E", "description": "Update G/L Source Names"},
  		{"id": "G/L-SOURCE NAMES, S", "description": "Setup G/L Source Names"}
  	],
  "webServices":
    [
    
    ],
  "dotnetAddins":
    [
  
    ],
  "tableDatas":
    [
   
    ]
}

Here I need to use another license file.  The one used when I applied for the object range for my extension.

Licenses are not to be stored in SQL backups used by AdvaniaGIT.  When using AdvaniaGIT to create SQL backups the license is removed before creating the backup and reinstalled afterwards.

The first function that is executed after SQL database restore is a database upgrade with the development environment.  This must be done to make sure that the database fits the service version being used.  For this database upgrade function to be successful, first either make sure that the database does not contain expired license, and make sure that you have a valid license in the master database.

There are a few ways of doing this.  First, there is an option when installing NAV to upload the license.

Secondly, in the development environment you can upload a license, going through Tools and License Information.

But make sure that the database your development environment is connected to does not have the “Save license in database” set like here, going through File, Database and Alter.

The third option is to use the server administrative shell.

Hope this helps.  More to come soon.  Stay tuned…

Using AdvaniaGIT – Create a localization branch

In our previous post we completed the installation of GIT, SourceTree, NAV and the AdvaniaGIT modules.  We also created a GIT repository in Bitbucket.  We selected to use BitBucket just because I already had a user configured there.  I also have a user configured in Visual Studio Online where I store many of my NAV solutions in a private GIT repository.  Public projects I put on GitHub.  As I stated before we must make sure not to push any of the NAV standard code to public repositories.

Advania has internal GIT servers where we host our solutions.

The choice is yours.  I don’t have any favorite GIT server solution.

In Advania we have created a branch structure.  We create repositories for each NAV version.  The master branch is always the W1 release.  Each commit to the W1 branch contains a CU update.  We always store all objects in every branch.  In solution branches we also store deltas and reverse deltas.

We can access any of the CU’s code from the GIT repository and we can see every code change made from one CU to the next.

We branch out master to each localization.  Each localization has the same rule as the master branch.  Every CU is available and every code change is traceable.

All the GIT servers have a nice web interface.  There we can easily see all commits, all code and all changes.  This is a list of commits for the master branch.

This is a list of code changes in NAV 2017 CU8.

Let’s go ahead and create our first localization branch.  In the following video I use the AdvaniaGIT functions to download and extract information from the latest CU.  I don’t have a function that will download all updates for a given version.

Our next step will be creating a solution branch based of our localization.  Stay tuned…

Using AdvaniaGIT – Create your first private GIT repository

We have a predefined folder structure in our GIT repository.  Each repository can have multiple branches.  The first branch and the parent to all the rest is “master”.  In our structure we always store released W1 objects in the master branch.

The GIT sub folder structure is defined in GITSettings.json.

“setupPath”: “setup.json”,
“objectsPath”: “Objects”,
“deltasPath”: “Deltas”,
“reverseDeltasPath”: “ReverseDeltas”,
“extensionPath”: “Extension1”,
“imagesPath”: “Images”,
“screenshotsPath”: “ScreenShots”,
“permissionSetsPath”: “PermissionSets”,
“addinsPath”: “Addins”,
“languagePath”: “Languages”,
“tableDataPath”: “TableDatas”,
“customReportLayoutsPath”: “CustomReportLayouts”,
“webServicesPath”: “WebServices”,
“binaryPath”: “Binaries”,

We store all the NAV objects in the “Objects” folder.  All NAV objects is everything needed to build a solution from our GIT branch.

The basic rules are

  • Each branch needs a unique id, a GUID.  You can find a new guid with any of the online guid generators.  The branchId parameter is stored in each branch setup file.
  • Public repositories must not contain exported Microsoft objects.  These repositories must have the storeAllObjects parameter set to false.  These branches are based on standard objects in the Source folder and the Deltas in the repository.
  • Keep your common parameters in Data\GITSettings.json and your branch specific parameters in Setup.json

List of available setup parameters can be found on the AdvaniaGIT wiki.  The Wiki is a work in progress.

As we go through each of the Custom Actions we will talk about these parameters and how they are used.

Here is a demo video on me creating a private repository for NAV 2017 solutions.

 

The AdvaniaGIT module links each branch to an installed NAV environment.  This link is stored in Data\BranchSettings.json and is automatically maintained when environments are built and removed.

NAV Environment and NAV Databases have name rules.  Prefix is NAVDEV then the main release then an automatically increment number.  Example could be “NAV2017DEV0000008”.  The last increment number is stored in Data\BuildSettings.json and is updated on every environment build.

All installed NAV versions must be defined in Data\NAVVersions.json.  Make sure to have the correct web client port to have your web client working correctly.  This is an example of my NAV versions.

{
  "Releases": 
	[
		{"mainVersion": "90", "navRelease": "2016", "webClientPort": "8090", "helpServer": "nav90help.advania.is", "helpServerPort": "80"},
		{"mainVersion": "100","navRelease": "2017", "webClientPort": "8080", "helpServer": "nav100help.advania.is", "helpServerPort": "80"}
	]
}

This summer I got the change to work with Microsoft on vNext.  Microsoft gave me access to a GIT repository with their base application.  The GIT repository contained a ReadMe file and a folder called BaseApp with all the objects.  I just added a setup.json file to repository and was able to use SourceTree and the custom actions for all the development.  Here is the setup.json file I added.

{
  "branchId": "1901c80b-711a-4564-aec6-ac7684e578cb",
  "navVersion": "10.0.17001.0",
  "navSolution": "W1",
  "projectName": "W1",
  "storeAllObjects": "true",
  "objectProperties": "false",
  "objectsPath": "BaseApp"
}

Our next task will be on creating a branch for our localization and our solution.  Stay tuned…

 

Installing AdvaniaGIT – Video Help

We need to add DotNet 3.5 to the standard Windows installation to be able to do NAV report design in our development environment.  The following video will show you how.  We also need to make changes to PowerShell execution policy.  The scripts and functions in AdvaniaGIT have not yet been signed.  Therefore we need to allow for execution of unsigned scripts.

From Administrative PowerShell run

Set-ExecutionPolicy -ExecutionPolicy Unrestricted

This you can also see in this video.

Install SQL Server 2016 Developer Edition.  AdvaniaGIT will work with SQL Express as well.

Install GIT, SourceTree and AdvaniaGIT module.  Make sure to have your NAV development license at hand.

There is an alternate ending to the above video.  If you have already installed NAV on your Windows machine you will need to perform the environment preparation as well.  This is demoed in the update video below.  Under normal circumstances doing Fetch and Pull will be enough to update the AdvaniaGIT module installation.  If you have manually updated or installed NAV make sure to execute the update steps shown here.

Now you machine should be ready for NAV installation.  By cloning my 2016 demo repository and using the custom actions in SourceTree you will be able to download and install NAV easily.  Our method have been to always use the latest CU from Microsoft.  The update and installation functions in AdvaniaGIT will read CU information from Microsoft and update/install the latest version.  Installation requires both the application setup and also it needs to extract the database backup and database object export from the version.  The backup and the object export are saved in your AdvaniaGIT folder.  If you have enabled the connection to a FTP server these files will also be uploaded to that server.

Here is how to install NAV 2016.

And the video from NAV 2017 is almost identical.  Using my 2017 demo repository.

Now your Windows machine should no be ready to start NAV development with Source Control Management.

Soon to come are more videos and demos on how to use SCM for your organization.  Stay tuned…