Ich habe mich hier auf Englisch als Quellsprache beschränkt, weil für andere Ausgangssprachen die Übersetzungsergebnisse deutlich schlechter sind. Das kann aber natürlich auch leicht im Skript geändert werden.
Die über den NAV-Client erzeugte OEM850 Ausgangsdatei wird erst UTF-8 gewandelt, dann pro Zeile die Übersetzung aus dem Hub geholt und mit der geänderten Code-ID und der übersetzten Caption in eine neue UTF-8-Datei geschrieben, aus der dann am Schluss eine OEM850-Datei erzeugt wird, die man dann theoretisch direkt per Cmdlet oder über den Client in NAV-Objekte importieren könnte. Alternativ kann ab Version NAV 2013 R2 die UTF-8 Datei auch direkt auf dem Server abgelegt werden (How to: Add Translated Strings for Conflicting Text Encoding Formats). Wenn die Zielsprache Zeichen außerhalb von Codepage 850 für Westeuropa enthält, muss dieses sogar so passieren, falls bei den Windows Regionen/Gebieten keine andere Region gewählt wurde, die diese Sonderzeichen enthält.
Da die Datei nach der maschinellen Übersetzung je nach Umfang und "Problemcaptions" meist noch an vielen Stellen nachbearbeitet werden muss, ist die UTF8-Version besser geeignet falls das außerhalb von NAV erfolgen soll, dann erst am Schluss die manuelle OEM850-Konvertierung vornehmen .
Formatierungsfehler in den Ãœbersetzungen
- Captions mit # (wie in Laufzeitfenstern häufiger anzutreffen) werden beim Übersetzen dermaßen ramponiert, dass ich diese ab diesem Zeichen abschneide, weil die Nachbearbeitung so etwas weniger aufwendig ist.
Im Bild ist der Effekt zu sehen, wenn ohne Abschneiden übersetzt wird.
Nach einem # in einer Caption wird also bis zum Zeilenende nichts übersetzt.
Die Wörter sollte man in solchen Captions deshalb möglichst nach vorne stellen, z.B. so
MeinText #1#######
und nicht
#1####### MeinText
- &-Shortcutzeichen in Pages und Forms, Optionswerte und Filterausdrücke müssen auch allesamt nach der Übersetzung von schädlichen Leerzeichen "befreit" werden, bei den Optionswerten kommen die meist erforderlichen Großbuchstaben am Anfang noch dazu. Die &-Shortcutzeichen stören außerdem teilweise die Übersetzungen erheblich, ggf. also vorab entfernen und manuell nachtragen.
- Alle DateFilter-Ausdrücke nachbearbeiten (bzw. generell alle Captions mit <>-Zeichen), diese erhalten sonst auch Leerstellen und zusätzliche =""- Einschübe.
Voraussetzungen zur Nutzung:
Azure Marketplace AccountAzure-Account (DataMarket-Account nur noch verwendbar bis 31.03.2017, dann wird der DataMarket eingestellt und es ist ein regulärer Azure-Account erforderlich (Link))- Translator Hub im Azure Marketplace-Account (bzw. Azure-Account) freigeschaltet (bis zu 2.000.000 Zeichen pro Monat sind kostenlos)
Das Skript mit selbstgewähltem Application-Name (das ist dann die ClientID im Skript, nicht die Azure-Kunden-ID, der "Primäre Kontoschlüssel" wird ebenfalls nicht benötigt) registiert und (mit zugehörigem ClientSecret, dies ist auch frei wählbar) versehen.ClientID und ClientSecret müssen im Skript danach individell an diese Werte angepasst werden.Neue Anmeldemethode siehe Folgebeitrag- Optional: Wenn man den Hub vorab trainiert hat und das System für die Zielsprache den Status "Deployed" hat, kann man zusätzlich die individuelle CategoryID bei der Abfrage mitgeben um die Übersetzungergebnisse zu verbessern (PDF: Translator Hub API Guide). Ansonsten muss man diese weglassen und es wird dann der Bing-Standardübersetzer verwendet.
Das Access-Token wird pro Zeile im Skript jeweils neu übertragen, da diese nur 10 Minuten gültig sind und die Abarbeitung einer kompletten Add-on-Datei nicht überleben . Bei kleinen Dateihäppchen könnte man den Codeblock auch außerhalb der ForEach-Schleife legen oder eleganter in der Schleife den Ablauf überwachen und rechtzeitig neu übertragen.
Alte Skriptversion, hier nur noch zu Vergleichszwecken. Azure DataMarket ist nicht mehr im Betrieb, die Anmelde- und Abrufverfahren haben sich geändert. Die neue Version ist hier im Folgebeitrag.
- Code: Alles auswählen
Function MTfromNAVtranslateFile
{
Function WriteToFile
{
out-file $TranslatedfileName -inputobject $args -force -append -Width 1024 -Encoding utf8
}
$NAVtransfile = Read-host "Existing NAV translation file with ENU -A1033- Captions"
$WorkingFolder = Split-Path -Parent $NAVtransfile
$lines = Get-Content $NAVtransfile | Measure-Object –Line
$Nooflines = $lines.Lines
write-host "File has $Nooflines lines"
$ENUlines = Get-Content $NAVtransfile | select-string -pattern "-A1033-" -Allmatches -CaseSensitive | Measure-Object –Line
$NoofENUlines = $ENUlines.Lines
write-host "File has $NoofENUlines ENU lines (Code A1033)"
$SourceLangCode = '-A1033-' # en = ENU
$sourceEncoding = [System.Text.Encoding]::GetEncoding(850)
$targetEncoding = [System.Text.Encoding]::GetEncoding(65001)
$convertedFileName = [System.IO.Path]::GetDirectoryName($NAVtransfile) + "\"+ [System.IO.Path]::GetFileNameWithoutExtension($NAVtransfile) +"_UTF8" + [System.IO.Path]::GetExtension($NAVtransfile)
if (Test-Path $convertedFileName) {Remove-Item $convertedFileName}
$convertedBaseName = Split-Path -Leaf $convertedFileName
$convertedfile = New-Item -path "$WorkingFolder\$convertedBaseName" -type file -Force
$textfile = [System.IO.File]::ReadAllText($NAVtransfile, $sourceencoding)
[System.IO.File]::WriteAllText($convertedfile, $textfile, $targetencoding)
Write-host $NAVtransfile 'OEM850 converted to UTF8' $convertedFileName
$TargetLanguage = Read-host "Target Language: de,es,fi,fr,nl,no,pt,sv,en-gb,..."
# Supported Translator Codes: https://msdn.microsoft.com/en-us/library/hh456380.aspx
Switch ($TargetLanguage)
{
"de" {$NAVTargetlangCode = '-A1031-'}
"es" {$NAVTargetlangCode = '-A1034-'}
"fi" {$NAVTargetlangCode = '-A1035-'}
"fr" {$NAVTargetlangCode = '-A1036-'}
"nl" {$NAVTargetlangCode = '-A1043-'}
"no" {$NAVTargetlangCode = '-A1044-'}
"pt" {$NAVTargetlangCode = '-A1046-'}
"sv" {$NAVTargetlangCode = '-A1053-'}
# "gsw" {$NAVTargetlangCode = '-A2055-'} Swiss German currently not supported
"en-gb" {$NAVTargetlangCode = '-A2057-'}
# "de" {$NAVlangCode = '-A3079-'} # activate in AT and deactivate de -> A1031
default {Throw "Please assign a NAV translation code for $NAVTargetlangCode in the script"}
}
$TranslatedFileName = [System.IO.Path]::GetDirectoryName($NAVtransfile) + "\"+ [System.IO.Path]::GetFileNameWithoutExtension($NAVtransfile) +"_$TargetLanguage" + [System.IO.Path]::GetExtension($NAVtransfile)
foreach ($line in [System.IO.File]::ReadLines($convertedFileName))
{
$SourceTextLine = $line.ToString()
if ($SourceTextLine.contains($SourceLangCode))
{
#region Construct Azure Datamarket access_token
#Get ClientId and Client_Secret from https://datamarket.azure.com/developer/applications/
#Refer obtaining AccessToken (http://msdn.microsoft.com/en-us/library/hh454950.aspx)
### Use your own account credentials here #########
# Note ClientID is the registered application id !
# Use the app's designated client_Secret
#################################################
$ClientID = '<---------------------->'
$client_Secret = '<---------------------->'
# If ClientId or Client_Secret has special characters, UrlEncode before sending request
$clientIDEncoded = [System.Web.HttpUtility]::UrlEncode($ClientID)
$client_SecretEncoded = [System.Web.HttpUtility]::UrlEncode($client_Secret)
#Define uri for Azure Data Market
$Uri = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
#Define the body of the request
$Body = "grant_type=client_credentials&client_id=$clientIDEncoded&client_secret=$client_SecretEncoded&scope=http://api.microsofttranslator.com"
#Define the content type for the request
$ContentType = "application/x-www-form-urlencoded"
#Invoke REST method. This handles the deserialization of the JSON result. Less effort than invoke-webrequest
$admAuth=Invoke-RestMethod -Uri $Uri -Body $Body -ContentType $ContentType -Method Post
#Construct the header value with the access_token just recieved
$HeaderValue = "Bearer " + $admauth.access_token
#endregion
$TargetLine = ''
Write-host "Source line $SourceTextLine"
$pos1 = $SourceTextLine.IndexOf(":")
# Start-Sleep -Seconds 1 -verbose
$SourceCodestringID = $SourceTextLine.Substring(0,$pos1)
$SourceCaption = $SourceTextLine.Substring($pos1 + 1)
Write-host "$SourceCaption" -ForegroundColor Green
# Cut off trailing #1###,#2### etc,as these are battered in the translation process, but not if at first pos)
$posHashKey = $SourceCaption.IndexOf("#")
$TrailingSourceCaption = '';
if ($posHashKey -gt 1)
{
$TrailingSourceCaption = $SourceCaption.Substring($posHashKey)
$SourceCaption = $SourceCaption.Substring(0,$posHashKey)
}
$TargetCodestringID = $SourceCodestringID -replace $SourceLangCode,$NAVTargetLangCode
$TargetTextLine = ''
Write-host "$TargetCodestringID" -ForegroundColor Yellow
#region Construct and invoke REST request to Microsoft Translator Service
[string] $text = $SourceCaption;
[string] $textEncoded = [System.Web.HttpUtility]::UrlEncode($text)
[string] $from = "en";
[string] $to = $TargetLanguage;
# Use category only if translation language pair has been deployed in the hub, otherwise without (Bing standard is used then)
[string] $uri = "http://api.microsofttranslator.com/v2/Http.svc/Translate?text=" + $textEncoded + "&from=" + $from + "&to=" + $to
#[string] $uri = "http://api.microsofttranslator.com/v2/Http.svc/Translate?text=" + $textEncoded + "&from=" + $from + "&to=" + $to + "&category=xxxxInsertYourCodeHerexxxx"
$result = Invoke-RestMethod -Uri $uri -Headers @{Authorization = $HeaderValue}
#endregion
$result.string.'#text'
$TargetTextLine = $result.string.'#text'
$TranslatedLine = $TargetCodestringID + ':' + $TargetTextLine + $TrailingSourceCaption
Write-host "$TargetTextLine$TrailingSourceCaption" -ForegroundColor Yellow
WriteToFile $TranslatedLine
#write-host "Waiting 2 seconds..."
# Start-Sleep -Seconds 2
}
}
if (Test-Path $translatedfilename)
{
$translatedfilename = resolve-path $translatedfilename
$WorkingFolder = Split-Path -Parent $translatedfilename
$sourceEncoding = [System.Text.Encoding]::GetEncoding(65001)
$targetEncoding = [System.Text.Encoding]::GetEncoding(850)
$OEM850FileName = [System.IO.Path]::GetFileNameWithoutExtension($translatedfilename) +"_OEM850" + [System.IO.Path]::GetExtension($translatedfilename)
$OEM850file = New-Item -path "$WorkingFolder\$OEM850FileName" -type file
$UTF8file = [System.IO.File]::ReadAllText($translatedfilename, $sourceencoding)
[System.IO.File]::WriteAllText($OEM850file, $UTF8file, $targetencoding)
Write-host $translatedfilename 'converted to' $OEM850FileName
}
}
Anzeige zur Laufzeit mit Holländisch
Beispiele für Übersetzungen in den erzeugten Dateien. Der Translator Hub wurde vorab für die Zielsprachen nl, es und fr mit den vorhandenen NAV-Standardcaptions mittels XLIFFs trainiert (.xlf) und die zugehörige CategoryID im $uri-Abfragestring genutzt.
Niederländisch NLD Code A1043
Spanisch ESP Code A1034
Französisch FRA Code A1036
Um eine monolinguale Datei für einen direkten Zeilenvergleich zweiter Dateien wie die obigen zu erstellen genügt diese Pipelinekette, hier z.B. für ENU (= -A1033- im Codestring):
Bei der Originaldatei aus dem Clientexport
- Code: Alles auswählen
get-content C:\temp\translations.txt -encoding oem | select-string -pattern "-A1033-" -allmatches | Out-File C:\temp\translations_ENU.txt -Width 1024 -Encoding oem
bzw. bei einer UTF8-datei
- Code: Alles auswählen
get-content C:\temp\translations.txt -encoding utf8 | select-string -pattern "-A1033-" -allmatches | Out-File C:\temp\translations_ENU.txt -Width 1024 -Encoding utf8
Hinweis: Bei der Originaldatei aus dem NAV-Client den Encodingparameter beachten:
-Encoding oem , nicht -Encoding ascii, das bedeutet in der Powershell Codepage 437 (und nicht die erforderliche Codepage 850) und dann werden aus den vielen dort fehlenden Sonderzeichen (z.B. Großbuchstaben mit Umlauten) Fragezeichen in der Ausgabedatei und es kann auch zu Fehlzuordnungen kommen. Im Englischen stört das natürlich eher selten, aber bei anderen Sprachen schon eher .
Links
Microsoft Dynamics ERP Translation Solution