├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── TestRunner
├── TestRunner.al
├── Webservice.xml
└── app.json
└── src
├── ALBuild.sln
├── ALBuild
├── ALBuild.csproj
├── App.config
├── Examples
│ ├── example.json
│ ├── examplesimple.json
│ └── pos.json
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Tasks
│ ├── Compile.cs
│ ├── Copy.cs
│ ├── Deploy Basic Docker.cs
│ ├── Deploy SaaS.cs
│ ├── Download Symbols Docker.cs
│ ├── Download Symbols SaaS.cs
│ ├── Git.cs
│ ├── PowerShell.cs
│ ├── Remember.cs
│ ├── Result.cs
│ ├── Sign.cs
│ ├── Test Basic Docker.cs
│ ├── Test SaaS.cs
│ ├── Translate.cs
│ └── UpdateVersion.cs
└── signtool.exe
├── BCTranslateApp
├── App.config
├── BCTranslateApp.csproj
├── BCTranslateApp.sln
├── Program.cs
└── Properties
│ └── launchSettings.json
├── TranslateAdmin
├── App.config
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── Global Vars.cs
├── MainWindow.xaml
├── MainWindow.xaml.cs
├── Ripper.xaml
├── Ripper.xaml.cs
└── TranslateAdmin.csproj
└── TranslationTools
├── Translation.cs
├── TranslationProcess.cs
├── TranslationTableEntry.cs
├── TranslationTools.csproj
└── XlfParser
├── Converter.cs
└── Model.cs
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
298 | *.vbp
299 |
300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
301 | *.dsw
302 | *.dsp
303 |
304 | # Visual Studio 6 technical files
305 | *.ncb
306 | *.aps
307 |
308 | # Visual Studio LightSwitch build output
309 | **/*.HTMLClient/GeneratedArtifacts
310 | **/*.DesktopClient/GeneratedArtifacts
311 | **/*.DesktopClient/ModelManifest.xml
312 | **/*.Server/GeneratedArtifacts
313 | **/*.Server/ModelManifest.xml
314 | _Pvt_Extensions
315 |
316 | # Paket dependency manager
317 | .paket/paket.exe
318 | paket-files/
319 |
320 | # FAKE - F# Make
321 | .fake/
322 |
323 | # CodeRush personal settings
324 | .cr/personal
325 |
326 | # Python Tools for Visual Studio (PTVS)
327 | __pycache__/
328 | *.pyc
329 |
330 | # Cake - Uncomment if you are using it
331 | # tools/**
332 | # !tools/packages.config
333 |
334 | # Tabs Studio
335 | *.tss
336 |
337 | # Telerik's JustMock configuration file
338 | *.jmconfig
339 |
340 | # BizTalk build output
341 | *.btp.cs
342 | *.btm.cs
343 | *.odx.cs
344 | *.xsd.cs
345 |
346 | # OpenCover UI analysis results
347 | OpenCover/
348 |
349 | # Azure Stream Analytics local run output
350 | ASALocalRun/
351 |
352 | # MSBuild Binary and Structured Log
353 | *.binlog
354 |
355 | # NVidia Nsight GPU debugger configuration file
356 | *.nvuser
357 |
358 | # MFractors (Xamarin productivity tool) working folder
359 | .mfractor/
360 |
361 | # Local History for Visual Studio
362 | .localhistory/
363 |
364 | # Visual Studio History (VSHistory) files
365 | .vshistory/
366 |
367 | # BeatPulse healthcheck temp database
368 | healthchecksdb
369 |
370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
371 | MigrationBackup/
372 |
373 | # Ionide (cross platform F# VS Code tools) working folder
374 | .ionide/
375 |
376 | # Fody - auto-generated XML schema
377 | FodyWeavers.xsd
378 |
379 | # VS Code files for those working on multiple tools
380 | .vscode/*
381 | !.vscode/settings.json
382 | !.vscode/tasks.json
383 | !.vscode/launch.json
384 | !.vscode/extensions.json
385 | *.code-workspace
386 |
387 | # Local History for Visual Studio Code
388 | .history/
389 |
390 | # Windows Installer files from build outputs
391 | *.cab
392 | *.msi
393 | *.msix
394 | *.msm
395 | *.msp
396 |
397 | # JetBrains Rider
398 | *.sln.iml
399 |
400 | # AL
401 | LaunchSettings.json
402 | *.app
403 | launch.json
404 | .vscode/
405 | .snapshots/
406 | .alpackages/
407 | rad.json
408 |
409 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Erik Hougaard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ALBuild
2 |
3 | ALBuild is an open-source tool for building AL applications in both pipeline and non-pipeline environments. It maintains a database of translations.
4 |
5 | ALBuild supports the following types of operations:
6 |
7 | * GIT operations
8 | * File Copy operations
9 | * Deploy app to Docker container with Basic authentication
10 | * Deploy app to Business Central SaaS with service 2 service OAuth authentication
11 | * Download Symbols from Docker container
12 | * Download Symbols from SaaS sandbox
13 | * PowerShell operations
14 | * App Signing (using signtool.exe)
15 | * Run test codeunits on Docker container with Basic authentication
16 | * Run test codeunits on Business Central SaaS with OAuth authentication
17 | * Translate XLF using Azure Cognitive Services
18 | * Update version in app.json
19 |
20 | The list of operations is defined in a .json file that describes the series of operations.
21 |
22 | # ALBuild Translation Administration
23 |
24 | The admin tool enables you to perform maintenance operations on the translation database, such as:
25 |
26 | * Edit specific entries
27 | * Bulk import from XLF files
28 | * Ripper for BC artifacts - To reuse translations from Microsoft
29 |
30 | # Standalone Translation Tool
31 | The standalone translation tool enables you to add translations to an AL app (working in the /Translation folder)
32 |
33 | # Configuration
34 |
35 | Each app has a .config file where you configure:
36 |
37 | * AzureKey for Azure Cognitive Services (translation)
38 | * "App Name" for specifying in XLF where translation comes from
39 | * Location of local translation database
40 | * List of languages supported
41 | * Storage Account and key for Azure Table Storage
42 |
43 | ```
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | ```
56 |
57 | # Build File
58 |
59 | The following is an example of a build json file:
60 |
61 | ```{
62 | "Project": "Demo",
63 | "Report" : "Email",
64 | "ReportDestination" : "appowner@company.com,
65 | "Tasks": [
66 | {
67 | "Type": "DeployBasicDocker",
68 | "Settings": {
69 | "AppFile": "c:\\projects\\albuild\\testrunner\\Hougaard_ALBuild TestRunner_1.0.0.0.app",
70 | "BaseURL": "http://bc20:7049/BC/",
71 | "User": "demo",
72 | "Password": "demo",
73 | "SchemaUpdateMode": "forcesync"
74 | }
75 | },
76 | {
77 | "Type": "Git",
78 | "Settings": {
79 | "Path": "c:\\projects\\youtube\\point of sale",
80 | "Command": "pull"
81 | }
82 | },
83 | {
84 | "Type": "UpdateVersion",
85 | "Settings": {
86 | "AppPath": "c:\\projects\\youtube\\point of sale",
87 | "VersionPartToIncrement": 4,
88 | "Increment": 1,
89 | "DateInVersionPartNo":3
90 | }
91 | },
92 | {
93 | "Type": "Remember",
94 | "Settings": {
95 | "AppPath": "c:\\projects\\youtube\\point of sale"
96 | }
97 | },
98 | {
99 | "Type": "DownloadSymbolsDocker",
100 | "Settings": {
101 | "AppPath": "%APPPATH%",
102 | "BaseURL": "http://bc20:7049/BC/",
103 | "User": "demo",
104 | "Password": "demo"
105 | }
106 | },
107 | {
108 | "Type": "Compile",
109 | "Settings": {
110 | "AppPath": "%APPPATH%"
111 | }
112 | },
113 | {
114 | "Type": "Translate",
115 | "Settings": {
116 | "XLFPath": "%APPPATH%\\Translations\\%NAME%.g.xlf",
117 | "ProductName": "%NAME%"
118 | }
119 | },
120 | {
121 | "Type": "Compile",
122 | "Settings": {
123 | "AppPath": "%APPPATH%"
124 | }
125 | },
126 | {
127 | "Type": "Copy",
128 | "Settings": {
129 | "From": "%APPPATH%\\%PUBLISHER%_%NAME%_%VERSION%.app",
130 | "To": "C:\\Projects\\youtube\\ALbuild\\Release\\%PUBLISHER%_%NAME%_%VERSION%.app"
131 | }
132 | },
133 | {
134 | "Type": "Git",
135 | "Settings": {
136 | "Path": "%APPPATH%",
137 | "Command": "add *"
138 | }
139 | },
140 | {
141 | "Type": "Git",
142 | "Settings": {
143 | "Path": "%APPPATH%",
144 | "Command": "commit -a -m \"ALBuild Version %VERSION%\""
145 | }
146 | },
147 | {
148 | "Type": "Git",
149 | "Settings": {
150 | "Path": "%APPPATH%",
151 | "Command": "push"
152 | }
153 | }
154 | ]
155 | }
156 | ```
157 |
--------------------------------------------------------------------------------
/TestRunner/TestRunner.al:
--------------------------------------------------------------------------------
1 | codeunit 99999 "ALBuild TestRunner"
2 | {
3 | TestIsolation = Codeunit;
4 | Subtype = TestRunner;
5 | procedure runcodeunit(no: Integer): Boolean
6 | begin
7 | Codeunit.run(no);
8 | if GetLastErrorText() <> '' then
9 | error(GetLastErrorText() + ' ' + GetLastErrorCallStack());
10 | exit(true);
11 | end;
12 | }
--------------------------------------------------------------------------------
/TestRunner/Webservice.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CodeUnit
6 | ALBuild
7 | 99999
8 | true
9 |
10 |
11 |
--------------------------------------------------------------------------------
/TestRunner/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "83a287ac-b280-453f-9059-314674fb999e",
3 | "name": "ALBuild TestRunner",
4 | "publisher": "Hougaard",
5 | "version": "1.0.0.0",
6 | "brief": "",
7 | "description": "",
8 | "privacyStatement": "",
9 | "EULA": "",
10 | "help": "",
11 | "url": "",
12 | "logo": "",
13 | "dependencies": [],
14 | "screenshots": [],
15 | "platform": "1.0.0.0",
16 | "idRanges": [
17 | {
18 | "from": 99999,
19 | "to": 99999
20 | }
21 | ],
22 | "resourceExposurePolicy": {
23 | "allowDebugging": true,
24 | "allowDownloadingSource": true,
25 | "includeSourceInSymbolFile": true
26 | },
27 | "runtime": "8.0"
28 | }
29 |
--------------------------------------------------------------------------------
/src/ALBuild.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.32112.339
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ALBuild", "ALBUILD\ALBuild.csproj", "{AA01062D-A8E9-44F1-97C4-65689B11946C}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranslationTools", "TranslationTools\TranslationTools.csproj", "{B9D17DBE-35D1-4A23-BCDD-B6C53867FC0C}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCTranslateApp", "BCTranslateApp\BCTranslateApp.csproj", "{F1E21E1C-BDF8-43C7-AD03-C3F8E732BD2B}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranslateAdmin", "TranslateAdmin\TranslateAdmin.csproj", "{5D737A48-E222-4B96-8EC0-87A93BC00301}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {AA01062D-A8E9-44F1-97C4-65689B11946C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {AA01062D-A8E9-44F1-97C4-65689B11946C}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {AA01062D-A8E9-44F1-97C4-65689B11946C}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {AA01062D-A8E9-44F1-97C4-65689B11946C}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {B9D17DBE-35D1-4A23-BCDD-B6C53867FC0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {B9D17DBE-35D1-4A23-BCDD-B6C53867FC0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {B9D17DBE-35D1-4A23-BCDD-B6C53867FC0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {B9D17DBE-35D1-4A23-BCDD-B6C53867FC0C}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {F1E21E1C-BDF8-43C7-AD03-C3F8E732BD2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {F1E21E1C-BDF8-43C7-AD03-C3F8E732BD2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {F1E21E1C-BDF8-43C7-AD03-C3F8E732BD2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {F1E21E1C-BDF8-43C7-AD03-C3F8E732BD2B}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {5D737A48-E222-4B96-8EC0-87A93BC00301}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {5D737A48-E222-4B96-8EC0-87A93BC00301}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {5D737A48-E222-4B96-8EC0-87A93BC00301}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {5D737A48-E222-4B96-8EC0-87A93BC00301}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {5A4008D6-98BA-4B5A-B4C3-E524D6F986E9}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/src/ALBuild/ALBuild.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | Always
26 |
27 |
28 | PreserveNewest
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/ALBuild/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/ALBuild/Examples/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "Project": "Erik's ToolBox",
3 | "Tasks": [
4 | {
5 | "Type": "DeployBasicDocker",
6 | "Settings": {
7 | "AppFile": "c:\\projects\\albuild\\testrunner\\Hougaard_ALBuild TestRunner_1.0.0.0.app",
8 | "BaseURL": "http://bc19:7049/BC/",
9 | "User": "demo",
10 | "Password": "demo",
11 | "SchemaUpdateMode": "forcesync"
12 | }
13 | },
14 | {
15 | "Type": "Git",
16 | "Settings": {
17 | "Path": "c:\\projects\\toolbox\\multiroot",
18 | "Command": "pull"
19 | }
20 | },
21 | {
22 | "Type": "UpdateVersion",
23 | "Settings": {
24 | "AppPath": "c:\\projects\\toolbox\\multiroot\\alcompilerandinterpreter",
25 | "VersionPartToIncrement": 4,
26 | "Increment": 1
27 | }
28 | },
29 | {
30 | "Type": "Remember",
31 | "Settings": {
32 | "AppPath": "c:\\projects\\toolbox\\multiroot\\alcompilerandinterpreter"
33 | }
34 | },
35 | {
36 | "Type": "Compile",
37 | "Settings": {
38 | "AppPath": "%APPPATH%"
39 | }
40 | },
41 | {
42 | "Type": "Translate",
43 | "Settings": {
44 | "XLFPath": "%APPPATH%\\Translations\\%NAME%.g.xlf",
45 | "ProductName": "%NAME%"
46 | }
47 | },
48 | {
49 | "Type": "Compile",
50 | "Settings": {
51 | "AppPath": "%APPPATH%"
52 | }
53 | },
54 | {
55 | "Type": "Sign",
56 | "Settings": {
57 | "AppPath": "%APPPATH%",
58 | "KeyFile": "c:\\projects\\Keystore\\codesign.pfx",
59 | "Password": "demo"
60 | }
61 | },
62 | {
63 | "Type": "Copy",
64 | "Settings": {
65 | "From": "%APPPATH%\\%PUBLISHER%_%NAME%_%VERSION%.app",
66 | "To": "C:\\Projects\\ToolBox\\build\\Release\\%PUBLISHER%_%NAME%_%VERSION%.app"
67 | }
68 | },
69 | {
70 | "Type": "DeploySaaS",
71 | "Settings": {
72 | "AppFile": "%APPPATH%\\%PUBLISHER%_%NAME%_%VERSION%.app",
73 | "ClientId": "fffd35f7-aff0-4fef-baf7-44d83972576b",
74 | "ClientSecret": "LdI7Q~lO_u2x-0MUyBAqGXAUUTlKhuCJwNUvG",
75 | "TenantId": "c2f19d2f-252e-4d89-a7c7-5bb9042d59bb",
76 | "Environment": "acs",
77 | "SchemaUpdateMode": "forcesync"
78 | }
79 | },
80 | {
81 | "Type": "TestBasicDocker",
82 | "Settings": {
83 | "BaseURL": "http://bc19:7048/BC/",
84 | "User": "demo",
85 | "Password": "demo",
86 | "Company": "CRONUS Canada, Inc.",
87 | "TestCodeunit": 70310410
88 | }
89 | },
90 | {
91 | "Type": "UpdateVersion",
92 | "Settings": {
93 | "AppPath": "c:\\projects\\toolbox\\multiroot\\ToolBox",
94 | "VersionPartToIncrement": 4,
95 | "Increment": 1
96 | }
97 | },
98 | {
99 | "Type": "Remember",
100 | "Settings": {
101 | "AppPath": "c:\\projects\\toolbox\\multiroot\\ToolBox"
102 | }
103 | },
104 | {
105 | "Type": "Compile",
106 | "Settings": {
107 | "AppPath": "%APPPATH%"
108 | }
109 | },
110 | {
111 | "Type": "Translate",
112 | "Settings": {
113 | "XLFPath": "%APPPATH%\\Translations\\%NAME%.g.xlf",
114 | "ProductName": "%NAME%"
115 | }
116 | },
117 | {
118 | "Type": "Compile",
119 | "Settings": {
120 | "AppPath": "%APPPATH%"
121 | }
122 | },
123 | {
124 | "Type": "Sign",
125 | "Settings": {
126 | "AppPath": "%APPPATH%",
127 | "KeyFile": "c:\\projects\\Keystore\\codesign.pfx",
128 | "Password": "demo"
129 | }
130 | },
131 | {
132 | "Type": "DeploySaaS",
133 | "Settings": {
134 | "AppFile": "%APPPATH%\\%PUBLISHER%_%NAME%_%VERSION%.app",
135 | "ClientId": "fffd35f7-aff0-4fef-baf7-44d83972576b",
136 | "ClientSecret": "LdI7Q~lO_u2x-0MUyBAqGXAUUTlKhuCJwNUvG",
137 | "TenantId": "c2f19d2f-252e-4d89-a7c7-5bb9042d59bb",
138 | "Environment": "acs",
139 | "SchemaUpdateMode": "forcesync"
140 | }
141 | },
142 | {
143 | "Type": "Copy",
144 | "Settings": {
145 | "From": "%APPPATH%\\%PUBLISHER%_%NAME%_%VERSION%.app",
146 | "To": "C:\\Projects\\ToolBox\\build\\Release\\%PUBLISHER%_%NAME%_%VERSION%.app"
147 | }
148 | },
149 | {
150 | "Type": "Git",
151 | "Settings": {
152 | "Path": "c:\\projects\\toolbox\\multiroot",
153 | "Command": "add *"
154 | }
155 | },
156 | {
157 | "Type": "Git",
158 | "Settings": {
159 | "Path": "c:\\projects\\toolbox\\multiroot",
160 | "Command": "commit -a -m \"ALBuild Version %VERSION%\""
161 | }
162 | },
163 | {
164 | "Type": "Git",
165 | "Settings": {
166 | "Path": "c:\\projects\\toolbox\\multiroot",
167 | "Command": "push"
168 | }
169 | }
170 | ]
171 | }
--------------------------------------------------------------------------------
/src/ALBuild/Examples/examplesimple.json:
--------------------------------------------------------------------------------
1 | {
2 | "Project": "Erik's ToolBox Simple Test",
3 | "Tasks": [
4 | {
5 | "Type": "Remember",
6 | "Settings": {
7 | "AppPath": "c:\\projects\\toolbox\\multiroot\\alcompilerandinterpreter"
8 | }
9 | },
10 | {
11 | "Type": "DeployBasicDocker",
12 | "Settings": {
13 | "AppFile": "c:\\projects\\albuild\\testrunner\\Hougaard_ALBuild TestRunner_1.0.0.0.app",
14 | "BaseURL": "http://bc19:7049/BC/",
15 | "User": "demo",
16 | "Password": "demo",
17 | "SchemaUpdateMode": "forcesync"
18 | }
19 | },
20 | {
21 | "Type": "TestBasicDocker",
22 | "Settings": {
23 | "BaseURL": "http://bc19:7048/BC/",
24 | "User": "demo",
25 | "Password": "demo",
26 | "Company": "CRONUS Canada, Inc.",
27 | "TestCodeunit": 70310410
28 | }
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/src/ALBuild/Examples/pos.json:
--------------------------------------------------------------------------------
1 | {
2 | "Project": "Point of Sale",
3 | "Report" : "Email",
4 | "ReportDestination" : "erik@hougaard.com",
5 | "Tasks": [
6 | {
7 | "Type": "DeployBasicDocker",
8 | "Settings": {
9 | "AppFile": "c:\\projects\\albuild\\testrunner\\Hougaard_ALBuild TestRunner_1.0.0.0.app",
10 | "BaseURL": "http://bc20:7049/BC/",
11 | "User": "demo",
12 | "Password": "demo",
13 | "SchemaUpdateMode": "forcesync"
14 | }
15 | },
16 | {
17 | "Type": "Git",
18 | "Settings": {
19 | "Path": "c:\\projects\\youtube\\point of sale",
20 | "Command": "pull"
21 | }
22 | },
23 | {
24 | "Type": "UpdateVersion",
25 | "Settings": {
26 | "AppPath": "c:\\projects\\youtube\\point of sale",
27 | "VersionPartToIncrement": 4,
28 | "Increment": 1,
29 | "DateInVersionPartNo":3
30 | }
31 | },
32 | {
33 | "Type": "Remember",
34 | "Settings": {
35 | "AppPath": "c:\\projects\\youtube\\point of sale"
36 | }
37 | },
38 | {
39 | "Type": "DownloadSymbolsDocker",
40 | "Settings": {
41 | "AppPath": "%APPPATH%",
42 | "BaseURL": "http://bc20:7049/BC/",
43 | "User": "demo",
44 | "Password": "demo"
45 | }
46 | },
47 | {
48 | "Type": "Compile",
49 | "Settings": {
50 | "AppPath": "%APPPATH%"
51 | }
52 | },
53 | {
54 | "Type": "Translate",
55 | "Settings": {
56 | "XLFPath": "%APPPATH%\\Translations\\%NAME%.g.xlf",
57 | "ProductName": "%NAME%"
58 | }
59 | },
60 | {
61 | "Type": "Compile",
62 | "Settings": {
63 | "AppPath": "%APPPATH%"
64 | }
65 | },
66 | {
67 | "Type": "Copy",
68 | "Settings": {
69 | "From": "%APPPATH%\\%PUBLISHER%_%NAME%_%VERSION%.app",
70 | "To": "C:\\Projects\\youtube\\ALbuild\\Release\\%PUBLISHER%_%NAME%_%VERSION%.app"
71 | }
72 | },
73 | {
74 | "Type": "Git",
75 | "Settings": {
76 | "Path": "%APPPATH%",
77 | "Command": "add *"
78 | }
79 | },
80 | {
81 | "Type": "Git",
82 | "Settings": {
83 | "Path": "%APPPATH%",
84 | "Command": "commit -a -m \"ALBuild Version %VERSION%\""
85 | }
86 | },
87 | {
88 | "Type": "Git",
89 | "Settings": {
90 | "Path": "%APPPATH%",
91 | "Command": "push"
92 | }
93 | }
94 | ]
95 | }
--------------------------------------------------------------------------------
/src/ALBuild/Program.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using ALBuild.Tasks;
4 | using System.Diagnostics;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace ALBuild
8 | {
9 | internal class Program
10 | {
11 | static Boolean OffLineMode = false;
12 | static async Task Main(string[] args)
13 | {
14 | Console.ForegroundColor = ConsoleColor.White;
15 | Console.WriteLine("ALBuild 22.05.01");
16 | Console.WriteLine("(c) 2022 Erik Hougaard - hougaard.com");
17 | if (args.Length == 0)
18 | {
19 | Console.WriteLine("Syntax: ALBuild [-offline]");
20 | return;
21 | }
22 |
23 | var hostFile = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + "\\ALBuild.exe";
24 |
25 |
26 | if (!File.Exists(args[0]))
27 | {
28 | Console.WriteLine("Cannot find {0}", args[0]);
29 | return;
30 | }
31 | Console.WriteLine("- Reading build script {0}", args[0]);
32 | var BuildScript = JObject.Parse(File.ReadAllText(args[0]));
33 | Console.WriteLine("- Building {0}", BuildScript["Project"]);
34 |
35 | if (args.Length > 1)
36 | {
37 | for (int i = 1; i < args.Length; i++)
38 | {
39 | if (args[i].ToLower() == "-offline")
40 | OffLineMode = true;
41 | }
42 | }
43 | var StartTime = DateTime.Now;
44 | JArray Tasks = (JArray)BuildScript["Tasks"];
45 | JObject CurrentApp = null;
46 | foreach (var Task in Tasks)
47 | {
48 | Console.ForegroundColor = ConsoleColor.White;
49 | Console.WriteLine("- {0}", Task["Type"].ToString());
50 | Console.ForegroundColor = ConsoleColor.Yellow;
51 | PrepareSettings(Task, CurrentApp);
52 | Result Res = null;
53 | switch (Task["Type"].ToString())
54 | {
55 | case "Git":
56 | Res = new Git().Run((JObject)Task["Settings"], OffLineMode);
57 | break;
58 | case "UpdateVersion":
59 | Res = new UpdateVersion().Run((JObject)Task["Settings"]);
60 | break;
61 | case "Remember":
62 | Res = new Remember().Run((JObject)Task["Settings"]);
63 | CurrentApp = Res.Data;
64 | break;
65 | case "Compile":
66 | Res = new Compile().Run((JObject)Task["Settings"]);
67 | break;
68 | case "Translate":
69 | Res = new Translate().Run((JObject)Task["Settings"], hostFile, OffLineMode);
70 | break;
71 | case "Sign":
72 | Res = new Sign().Run((JObject)Task["Settings"], CurrentApp, hostFile);
73 | break;
74 | case "Copy":
75 | Res = new Copy().Run((JObject)Task["Settings"]);
76 | break;
77 | case "PowerShell":
78 | Res = new Powershell().Run((JObject)Task["Settings"]);
79 | break;
80 | case "DeploySaaS":
81 | Res = await new DeploySaaS().RunAsync((JObject)Task["Settings"]);
82 | break;
83 | case "DeployBasicDocker":
84 | Res = await new DeployBasicDocker().RunAsync((JObject)Task["Settings"]);
85 | break;
86 | case "TestSaaS":
87 | Res = await new TestSaaS().RunAsync((JObject)Task["Settings"]);
88 | break;
89 | case "TestBasicDocker":
90 | Res = await new TestBasicDocker().RunAsync((JObject)Task["Settings"]);
91 | break;
92 | case "DownloadSymbolsSaaS":
93 | Res = await new DownloadSymbolsSaaS().RunAsync((JObject)Task["Settings"]);
94 | break;
95 | case "DownloadSymbolsDocker":
96 | Res = await new DownloadSymbolsDocker().RunAsync((JObject)Task["Settings"]);
97 | break;
98 | default:
99 | Console.WriteLine("Unkown task \"{0}\", aborting", Task["Type"].ToString());
100 | return;
101 | }
102 | if (Res != null)
103 | {
104 | if (Res.Success)
105 | {
106 | Console.ForegroundColor = ConsoleColor.Green;
107 | Console.WriteLine("Completed Successful");
108 | Console.ForegroundColor = ConsoleColor.White;
109 | }
110 | else
111 | {
112 | Console.ForegroundColor = ConsoleColor.Red;
113 | Console.WriteLine("Error: {0}", Res.Message);
114 | Console.ForegroundColor = ConsoleColor.White;
115 | return;
116 | }
117 | }
118 | }
119 |
120 | Console.WriteLine("- Build completed, time elapsed {0}, existing", DateTime.Now - StartTime);
121 | //Console.ReadLine();
122 | }
123 |
124 | private static void PrepareSettings(JToken task, JObject? currentApp)
125 | {
126 | Regex reg = new Regex(@"\%([A-Z0-9]*)\%");
127 | if (currentApp != null)
128 | {
129 | var Settings = (JObject)task["Settings"];
130 | foreach (var setting in Settings)
131 | {
132 | while (reg.IsMatch(Settings[setting.Key].ToString()))
133 | {
134 | var m = reg.Match(Settings[setting.Key].ToString());
135 | if (m.Success)
136 | {
137 | Capture cap = m.Captures[0];
138 | var SettingsKey = Settings[setting.Key].ToString().Substring(cap.Index, cap.Length);
139 | if (!currentApp.ContainsKey(SettingsKey))
140 | {
141 | Console.WriteLine("Unknown {0} in {1}", SettingsKey, setting);
142 | }
143 | else
144 | {
145 | var newvalue = currentApp[SettingsKey.Trim('%').ToLower()].ToString();
146 | Settings[setting.Key] = Settings[setting.Key].ToString().Replace(SettingsKey, newvalue);
147 | }
148 | }
149 | }
150 | }
151 | }
152 | }
153 | }
154 | }
--------------------------------------------------------------------------------
/src/ALBuild/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "ALBuild": {
4 | "commandName": "Project",
5 | "commandLineArgs": "pos.json"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Compile.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class Compile
12 | {
13 | public Compile()
14 | {
15 |
16 | }
17 | public Result Run(JObject Settings)
18 | {
19 | //Console.WriteLine()
20 | var CompilerPath = LocateCompilerFolder();
21 |
22 | var proc = new Process
23 | {
24 | StartInfo = new ProcessStartInfo
25 | {
26 | FileName = CompilerPath + "\\bin\\win32\\alc.exe",
27 | Arguments = "/project:\"" + Settings["AppPath"].ToString() +
28 | "\" /packagecachepath:\"" + Settings["AppPath"].ToString() + "\\.alpackages",
29 | UseShellExecute = false,
30 | RedirectStandardOutput = true,
31 | CreateNoWindow = true
32 | }
33 | };
34 | proc.Start();
35 | while (!proc.StandardOutput.EndOfStream)
36 | {
37 | Console.WriteLine(proc.StandardOutput.ReadLine());
38 | }
39 |
40 | return new Result(proc.ExitCode == 0);
41 | }
42 | public string LocateCompilerFolder()
43 | {
44 | foreach (var folder in Directory.GetDirectories(Environment.ExpandEnvironmentVariables("%USERPROFILE%\\.vscode\\extensions\\")))
45 | {
46 | if (folder.Contains("ms-dynamics-smb.al"))
47 | {
48 | return folder;
49 | }
50 | }
51 | throw new Exception("Cannot locate Business Central ALC Compiler, cannot continue");
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Copy.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ALBuild.Tasks
9 | {
10 | internal class Copy
11 | {
12 | public Copy()
13 | {
14 |
15 | }
16 | public Result Run(JObject Settings)
17 | {
18 | try
19 | {
20 | Console.WriteLine("Copy {0} to {1}", Settings["From"].ToString(), Settings["To"].ToString());
21 | File.Copy(Settings["From"].ToString(), Settings["To"].ToString());
22 | }
23 | catch (Exception ex)
24 | {
25 | return new Result(false, ex.Message);
26 | }
27 | return new Result(true);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Deploy Basic Docker.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Web;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class DeployBasicDocker
12 | {
13 | public DeployBasicDocker()
14 | {
15 |
16 | }
17 | public async Task RunAsync(JObject Settings)
18 | {
19 | string BaseURL = Settings["BaseURL"].ToString();
20 | string User = Settings["User"].ToString();
21 | string Password = Settings["Password"].ToString();
22 | string Authentication = Convert.ToBase64String(Encoding.UTF8.GetBytes(User + ":" + Password));
23 |
24 |
25 | // http://bc19:7049/BC/dev/apps?tenant=default&SchemaUpdateMode=forcesync&DependencyPublishingOption=default
26 |
27 | string URL2 = BaseURL.TrimEnd('/') + "/dev/apps?SchemaUpdateMode=" + Settings["SchemaUpdateMode"] + "&DependencyPublishingOption=default";
28 | HttpClient httpClient = new HttpClient();
29 | httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Authentication);
30 | MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
31 | using (Stream stream = File.OpenRead(Settings["AppFile"].ToString()))
32 | {
33 | string fileName = Path.GetFileName(Settings["AppFile"].ToString());
34 | Console.Write("- {0} ", fileName);
35 | multipartFormDataContent.Add((HttpContent)new StreamContent(stream), fileName, fileName);
36 | var result = await httpClient.PostAsync(URL2, multipartFormDataContent);
37 | Console.WriteLine(result.StatusCode);
38 | if (result.Content.ReadAsStringAsync().Result.Contains("A duplicate package ID is detected."))
39 | return new Result(true, "Same exact app is already installed");
40 | else
41 | return new Result(result.StatusCode == System.Net.HttpStatusCode.OK, result.Content.ReadAsStringAsync().Result);
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Deploy SaaS.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Web;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class DeploySaaS
12 | {
13 | public DeploySaaS()
14 | {
15 |
16 | }
17 | public async Task RunAsync(JObject Settings)
18 | {
19 | string ClientId = Settings["ClientId"].ToString();
20 | string ClientSecret = Settings["ClientSecret"].ToString();
21 | string TenantId = Settings["TenantId"].ToString();
22 |
23 | string URL = "https://login.microsoftonline.com/" + TenantId + "/oauth2/v2.0/token";
24 |
25 | HttpClient client = new HttpClient();
26 | var content = new StringContent("grant_type=client_credentials" +
27 | "&scope=https://api.businesscentral.dynamics.com/.default" +
28 | "&client_id=" + HttpUtility.UrlEncode(ClientId) +
29 | "&client_secret=" + HttpUtility.UrlEncode(ClientSecret));
30 | content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
31 | var response = await client.PostAsync(URL, content);
32 | if (response.IsSuccessStatusCode)
33 | {
34 | JObject Result = JObject.Parse(await response.Content.ReadAsStringAsync());
35 | string BearerToken = Result["access_token"].ToString();
36 |
37 | string URL2 = "https://api.businesscentral.dynamics.com/v2.0" +
38 | Settings["TenantId"].ToString() + "/" +
39 | Settings["Environment"].ToString() +
40 | "/dev/apps?SchemaUpdateMode=" + Settings["SchemaUpdateMode"] + "&DependencyPublishingOption=default";
41 | HttpClient httpClient = new HttpClient();
42 | httpClient.DefaultRequestHeaders.Add("Authorization", "bearer " + BearerToken);
43 | MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
44 | using (Stream stream = File.OpenRead(Settings["AppFile"].ToString()))
45 | {
46 | string fileName = Path.GetFileName(Settings["AppFile"].ToString());
47 | Console.Write("- {0} ", fileName);
48 | multipartFormDataContent.Add((HttpContent)new StreamContent(stream), fileName, fileName);
49 | var result = await httpClient.PostAsync(URL2, multipartFormDataContent);
50 | Console.WriteLine(result.StatusCode);
51 | return new Result(result.StatusCode == System.Net.HttpStatusCode.OK, result.Content.ReadAsStringAsync().Result);
52 |
53 | }
54 | }
55 | return new Result(false,response.Content.ReadAsStringAsync().Result);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Download Symbols Docker.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Web;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class DownloadSymbolsDocker
12 | {
13 | public DownloadSymbolsDocker()
14 | {
15 |
16 | }
17 | public async Task RunAsync(JObject Settings)
18 | {
19 | try
20 | {
21 | string BaseURL = Settings["BaseURL"].ToString().TrimEnd('/');
22 | string User = Settings["User"].ToString();
23 | string Password = Settings["Password"].ToString();
24 | string Authentication = Convert.ToBase64String(Encoding.UTF8.GetBytes(User + ":" + Password));
25 |
26 | JObject appjson = JObject.Parse(File.ReadAllText(Settings["AppPath"].ToString() + "\\app.json"));
27 |
28 | List Apps = new List();
29 | List Publishers = new List();
30 | List Versions = new List();
31 |
32 | Apps.Add("Base Application");
33 | Publishers.Add("Microsoft");
34 | Versions.Add(appjson["application"].ToString());
35 |
36 | Apps.Add("System Application");
37 | Publishers.Add("Microsoft");
38 | Versions.Add(appjson["application"].ToString());
39 |
40 | Apps.Add("Application");
41 | Publishers.Add("Microsoft");
42 | Versions.Add(appjson["platform"].ToString());
43 |
44 | Apps.Add("System");
45 | Publishers.Add("Microsoft");
46 | Versions.Add(appjson["platform"].ToString());
47 |
48 | JArray deps = (JArray)appjson["dependencies"];
49 |
50 | foreach (JObject dep in deps)
51 | {
52 | Apps.Add(dep["name"].ToString());
53 | Publishers.Add(dep["publisher"].ToString());
54 | Versions.Add(dep["version"].ToString());
55 | }
56 |
57 | for (int i = 0; i < Apps.Count; i++)
58 | {
59 | Console.WriteLine("- Downloading {0}",Apps[i].ToString());
60 | string URL2 = BaseURL +
61 | "/dev/packages?publisher=" + Publishers[i] +
62 | "&appName=" + Apps[i] +
63 | "&versionText=" + Versions[i];
64 |
65 | HttpClient httpClient = new HttpClient();
66 | httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Authentication);
67 | var result = await httpClient.GetByteArrayAsync(URL2);
68 | File.WriteAllBytes(Settings["AppPath"].ToString() + "\\.alpackages\\" + Publishers[i] + "_" + Apps[i] + "_" + Versions[i] + "_temp.app", result);
69 | }
70 | return new Result(true);
71 | }
72 | catch(Exception ex)
73 | {
74 | Console.WriteLine("Download Failed: {0}", ex.Message);
75 | return new Result(false, ex.Message);
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Download Symbols SaaS.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Web;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class DownloadSymbolsSaaS
12 | {
13 | public DownloadSymbolsSaaS()
14 | {
15 |
16 | }
17 | public async Task RunAsync(JObject Settings)
18 | {
19 | try
20 | {
21 | string ClientId = Settings["ClientId"].ToString();
22 | string ClientSecret = Settings["ClientSecret"].ToString();
23 | string TenantId = Settings["TenantId"].ToString();
24 |
25 | string URL = "https://login.microsoftonline.com/" + TenantId + "/oauth2/v2.0/token";
26 |
27 | HttpClient client = new HttpClient();
28 | var content = new StringContent("grant_type=client_credentials" +
29 | "&scope=https://api.businesscentral.dynamics.com/.default" +
30 | "&client_id=" + HttpUtility.UrlEncode(ClientId) +
31 | "&client_secret=" + HttpUtility.UrlEncode(ClientSecret));
32 | content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
33 | var response = await client.PostAsync(URL, content);
34 | if (response.IsSuccessStatusCode)
35 | {
36 | JObject Result = JObject.Parse(await response.Content.ReadAsStringAsync());
37 | string BearerToken = Result["access_token"].ToString();
38 |
39 | JObject appjson = JObject.Parse(File.ReadAllText(Settings["AppPath"].ToString() + "\\app.json"));
40 |
41 | List Apps = new List();
42 | List Publishers = new List();
43 | List Versions = new List();
44 |
45 | Apps.Add("Base Application");
46 | Publishers.Add("Microsoft");
47 | Versions.Add(appjson["application"].ToString());
48 |
49 | Apps.Add("System Application");
50 | Publishers.Add("Microsoft");
51 | Versions.Add(appjson["application"].ToString());
52 |
53 | Apps.Add("Application");
54 | Publishers.Add("Microsoft");
55 | Versions.Add(appjson["platform"].ToString());
56 |
57 | Apps.Add("System");
58 | Publishers.Add("Microsoft");
59 | Versions.Add(appjson["platform"].ToString());
60 |
61 | JArray deps = (JArray)appjson["dependencies"];
62 |
63 | foreach (JObject dep in deps)
64 | {
65 | Apps.Add(dep["name"].ToString());
66 | Publishers.Add(dep["publisher"].ToString());
67 | Versions.Add(dep["version"].ToString());
68 | }
69 |
70 | for (int i = 0; i < Apps.Count; i++)
71 | {
72 | Console.WriteLine("- Downloading {0}", Apps[i].ToString());
73 |
74 | string URL2 = "https://api.businesscentral.dynamics.com/v2.0/" +
75 | Settings["Environment"] +
76 | "/dev/packages?publisher=" + Publishers[i] +
77 | "&appName=" + Apps[i] +
78 | "&versionText=" + Versions[i];
79 |
80 | HttpClient httpClient = new HttpClient();
81 | httpClient.DefaultRequestHeaders.Add("Authorization", "bearer " + BearerToken);
82 | MultipartFormDataContent multipartFormDataContent = new MultipartFormDataContent();
83 |
84 | var result = await httpClient.GetByteArrayAsync(URL2);
85 | File.WriteAllBytes(Settings["AppPath"].ToString() + "\\.alpackages\\" + Publishers[i] + "_" + Apps[i] + "_" + Versions[i] + "_temp.app", result);
86 | }
87 | }
88 | return new Result(false, response.Content.ReadAsStringAsync().Result);
89 | }
90 | catch (Exception ex)
91 | {
92 | Console.WriteLine("Download Failed: {0}", ex.Message);
93 | return new Result(false, ex.Message);
94 | }
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Git.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class Git
12 | {
13 | public Git()
14 | {
15 |
16 | }
17 | public Result Run(JObject Settings, Boolean OffLineMode)
18 | {
19 | if (!ExistsOnPath("git.exe"))
20 | throw new Exception("Cannot find git.exe on this machine");
21 | var proc = new Process
22 | {
23 | StartInfo = new ProcessStartInfo
24 | {
25 | FileName = GetFullPath("git.exe"),
26 | WorkingDirectory = Settings["Path"].ToString(),
27 | Arguments = Settings["Command"].ToString(),
28 | UseShellExecute = false,
29 | RedirectStandardOutput = true,
30 | RedirectStandardError = true,
31 | CreateNoWindow = false
32 | }
33 | };
34 | Console.WriteLine("{0} in {1}", Settings["Command"].ToString(), Settings["Path"].ToString());
35 | proc.Start();
36 | while (!proc.StandardOutput.EndOfStream)
37 | {
38 | Console.WriteLine(proc.StandardOutput.ReadLine());
39 | }
40 | while (!proc.StandardError.EndOfStream)
41 | {
42 | Console.WriteLine(proc.StandardError.ReadLine());
43 | }
44 | if (OffLineMode)
45 | {
46 | if(proc.ExitCode != 0)
47 | {
48 | Console.WriteLine("Error occured, ignored due to ");
49 | }
50 | return new Result(true);
51 | }
52 | else
53 | return new Result(proc.ExitCode == 0);
54 |
55 | }
56 | public bool ExistsOnPath(string fileName)
57 | {
58 | return GetFullPath(fileName) != null;
59 | }
60 |
61 | public string GetFullPath(string fileName)
62 | {
63 | if (File.Exists(fileName))
64 | return Path.GetFullPath(fileName);
65 |
66 | var values = Environment.GetEnvironmentVariable("PATH");
67 | foreach (var path in values.Split(Path.PathSeparator))
68 | {
69 | var fullPath = Path.Combine(path, fileName);
70 | if (File.Exists(fullPath))
71 | return fullPath;
72 | }
73 | return null;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/PowerShell.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Management.Automation;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class Powershell
12 | {
13 | public Powershell()
14 | {
15 |
16 | }
17 | public Result Run(JObject Settings)
18 | {
19 | Console.WriteLine("- Command {0}", Settings["Command"].ToString());
20 | PowerShell ps = PowerShell.Create();
21 | ps.AddCommand(Settings["Command"].ToString());
22 | foreach(var res in ps.Invoke())
23 | {
24 | Console.WriteLine(res.ToString());
25 | }
26 |
27 | return new Result(ps.HadErrors == false);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Remember.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ALBuild.Tasks
9 | {
10 | internal class Remember
11 | {
12 | public Remember()
13 | {
14 | }
15 | public Result Run(JObject Settings)
16 | {
17 | var appjson = Path.Combine(Settings["AppPath"].ToString(), "app.json");
18 | Console.WriteLine("- Using {0}",appjson);
19 |
20 | var appTxt = File.ReadAllText(appjson);
21 | var js = JObject.Parse(appTxt);
22 | js["apppath"] = Settings["AppPath"];
23 |
24 | return new Result(true, js);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Result.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ALBuild.Tasks
9 | {
10 | internal class Result
11 | {
12 | public Result(bool _success)
13 | {
14 | Success = _success;
15 | }
16 | public Result(bool _success, string _msg)
17 | {
18 | Success = _success;
19 | Message = _msg;
20 | }
21 | public Result(bool _success, JObject data)
22 | {
23 | Success= _success;
24 | Data = data;
25 | }
26 | public bool Success { get; set; }
27 | public string Message { get; set; }
28 | public JObject Data { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Sign.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class Sign
12 | {
13 | public Sign()
14 | {
15 |
16 | }
17 | public Result Run(JObject Settings,JObject CurrentApp,string hostFile)
18 | {
19 | string SignToolExePath = hostFile.Replace("ALBuild.exe", "signtool.exe");
20 |
21 | string AppName = CurrentApp["publisher"] + "_" + CurrentApp["name"] + "_" + CurrentApp["version"] + ".app";
22 |
23 | var proc = new Process
24 | {
25 | StartInfo = new ProcessStartInfo
26 | {
27 | FileName = SignToolExePath,
28 | Arguments = "sign " +
29 | "/f \"" + Settings["KeyFile"] + "\" " +
30 | "/p \"" + Settings["Password"] + "\" " +
31 | "\"" + Settings["AppPath"] + "\\" + AppName + "\"",
32 | UseShellExecute = false,
33 | RedirectStandardOutput = true,
34 | CreateNoWindow = true
35 | }
36 | };
37 | proc.Start();
38 | while (!proc.StandardOutput.EndOfStream)
39 | {
40 | Console.WriteLine(proc.StandardOutput.ReadLine());
41 | }
42 |
43 | return new Result(proc.ExitCode == 0);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Test Basic Docker.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Net.Mime;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Web;
9 |
10 | namespace ALBuild.Tasks
11 | {
12 | internal class TestBasicDocker
13 | {
14 | public TestBasicDocker()
15 | {
16 |
17 | }
18 | public async Task RunAsync(JObject Settings)
19 | {
20 | string BaseURL = Settings["BaseURL"].ToString();
21 | string User = Settings["User"].ToString();
22 | string Password = Settings["Password"].ToString();
23 | string Authentication = Convert.ToBase64String(Encoding.UTF8.GetBytes(User + ":" + Password));
24 |
25 | Console.WriteLine("Calling Test codeunit {0} in company {1}",Settings["TestCodeunit"].ToString(),Settings["Company"].ToString());
26 |
27 | string URL2 = BaseURL.TrimEnd('/') + "/ODataV4/ALBuild_runcodeunit?tenant=default&company=" + Settings["Company"];
28 | HttpClient httpClient = new HttpClient();
29 | httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Authentication);
30 | string input = "{\"no\":" + Settings["TestCodeunit"].ToString() + "}";
31 | var content = new StringContent(input, Encoding.UTF8, "application/json");
32 | var result = await httpClient.PostAsync(URL2, content);
33 | //Console.WriteLine(result.StatusCode);
34 | return new Result(result.StatusCode == System.Net.HttpStatusCode.OK, result.Content.ReadAsStringAsync().Result);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Test SaaS.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using System.Web;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class TestSaaS
12 | {
13 | public TestSaaS()
14 | {
15 |
16 | }
17 | public async Task RunAsync(JObject Settings)
18 | {
19 | string ClientId = Settings["ClientId"].ToString();
20 | string ClientSecret = Settings["ClientSecret"].ToString();
21 | string TenantId = Settings["TenantId"].ToString();
22 |
23 | string URL = "https://login.microsoftonline.com/" + TenantId + "/oauth2/v2.0/token";
24 |
25 | HttpClient client = new HttpClient();
26 | var content = new StringContent("grant_type=client_credentials" +
27 | "&scope=https://api.businesscentral.dynamics.com/.default" +
28 | "&client_id=" + HttpUtility.UrlEncode(ClientId) +
29 | "&client_secret=" + HttpUtility.UrlEncode(ClientSecret));
30 | content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");
31 | var response = await client.PostAsync(URL, content);
32 | if (response.IsSuccessStatusCode)
33 | {
34 | JObject Result = JObject.Parse(await response.Content.ReadAsStringAsync());
35 | string BearerToken = Result["access_token"].ToString();
36 |
37 | Console.WriteLine("Calling Test codeunit {0} in company {1}", Settings["TestCodeunit"].ToString(), Settings["Company"].ToString());
38 |
39 | string URL2 = "https://api.businesscentral.dynamics.com/v2.0/" + TenantId + "/" + Settings["Environment"] + "/ODataV4/ALBuild_runcodeunit?company=" + Settings["Company"];
40 | HttpClient httpClient = new HttpClient();
41 | httpClient.DefaultRequestHeaders.Add("Authorization", "bearer " + BearerToken);
42 | string input = "{\"no\":" + Settings["TestCodeunit"].ToString() + "}";
43 | var content2 = new StringContent(input, Encoding.UTF8, "application/json");
44 | var result = await httpClient.PostAsync(URL2, content2);
45 | //Console.WriteLine(result.StatusCode);
46 | return new Result(result.StatusCode == System.Net.HttpStatusCode.OK, result.Content.ReadAsStringAsync().Result);
47 | }
48 | return new Result(false,response.Content.ReadAsStringAsync().Result);
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/Translate.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace ALBuild.Tasks
10 | {
11 | internal class Translate
12 | {
13 | public Translate()
14 | {
15 |
16 | }
17 | public Result Run(JObject Settings, string hostFile, Boolean OffLineMode)
18 | {
19 | var worker = new TranslationTools.TranslateXlf();
20 | worker.DoTheWork(Settings["XLFPath"].ToString(), Settings["ProductName"].ToString(), hostFile,true,OffLineMode);
21 | Console.WriteLine();
22 | return new Result(true);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/ALBuild/Tasks/UpdateVersion.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace ALBuild.Tasks
9 | {
10 | internal class UpdateVersion
11 | {
12 | public UpdateVersion()
13 | {
14 |
15 | }
16 | public Result Run(JObject Settings)
17 | {
18 | int NewBuildNumber = 0;
19 | var appjson = Path.Combine(Settings["AppPath"].ToString(), "app.json");
20 | int PartNo = Settings["VersionPartToIncrement"].ToObject() - 1;
21 | int Increment = Settings["Increment"].ToObject();
22 |
23 | Console.WriteLine("- Updating {0}", appjson);
24 | var appTxt = File.ReadAllText(appjson);
25 | var js = JObject.Parse(appTxt);
26 | var version = js.GetValue("version").ToString();
27 |
28 | Console.WriteLine("- Old version {0}", version);
29 |
30 | var VersionParts = version.Split('.');
31 | int v = Convert.ToInt32(VersionParts[PartNo]);
32 | if (NewBuildNumber == 0)
33 | {
34 | NewBuildNumber = v + 1;
35 | }
36 | VersionParts[PartNo] = NewBuildNumber.ToString();
37 |
38 | if (Settings.ContainsKey("DateInVersionPartNo"))
39 | {
40 | var DatePartNo = Settings["DateInVersionPartNo"].ToObject() - 1;
41 | VersionParts[DatePartNo] = DateTime.Now.ToString("yyyymmdd");
42 | }
43 |
44 | js["version"] = VersionParts[0] + "." + VersionParts[1] + "." + VersionParts[2] + "." + VersionParts[3];
45 | Console.WriteLine("- New version {0}", js["version"].ToString());
46 |
47 | File.WriteAllText(appjson, js.ToString());
48 |
49 | return new Result(true);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/ALBuild/signtool.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hougaard/albuild-opensource/00bda8087ae3fe75d368871004de3e3266da2e4e/src/ALBuild/signtool.exe
--------------------------------------------------------------------------------
/src/BCTranslateApp/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/BCTranslateApp/BCTranslateApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net6.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | PreserveNewest
22 | true
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/BCTranslateApp/BCTranslateApp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31321.278
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BCTranslateApp", "BCTranslateApp.csproj", "{9A7CCDB5-C162-4EC9-B9DD-F93CC4889BFB}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArtifactRipper", "..\ArtifactRipper\ArtifactRipper.csproj", "{12EBF12D-DBB5-48B8-8619-C1C5E7FA6A89}"
9 | EndProject
10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TranslationTools", "..\TranslationTools\TranslationTools.csproj", "{80232125-D55E-4169-8AE7-10213E16D5EC}"
11 | EndProject
12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LIteDB2Azure", "..\LIteDB2Azure\LIteDB2Azure.csproj", "{4B69E4D1-3EC4-4F4D-BED1-943034A69CF6}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImportXlf", "..\ImportXlf\ImportXlf.csproj", "{B7BA3009-02DA-4FF0-A7D5-179D3219522F}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TranslateAdmin", "..\TranslateAdmin\TranslateAdmin.csproj", "{3DC57A14-708B-43AC-9AA9-26419ED0006F}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Release|Any CPU = Release|Any CPU
22 | EndGlobalSection
23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
24 | {9A7CCDB5-C162-4EC9-B9DD-F93CC4889BFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {9A7CCDB5-C162-4EC9-B9DD-F93CC4889BFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {9A7CCDB5-C162-4EC9-B9DD-F93CC4889BFB}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {9A7CCDB5-C162-4EC9-B9DD-F93CC4889BFB}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {12EBF12D-DBB5-48B8-8619-C1C5E7FA6A89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {12EBF12D-DBB5-48B8-8619-C1C5E7FA6A89}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {12EBF12D-DBB5-48B8-8619-C1C5E7FA6A89}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {12EBF12D-DBB5-48B8-8619-C1C5E7FA6A89}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {80232125-D55E-4169-8AE7-10213E16D5EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {80232125-D55E-4169-8AE7-10213E16D5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {80232125-D55E-4169-8AE7-10213E16D5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {80232125-D55E-4169-8AE7-10213E16D5EC}.Release|Any CPU.Build.0 = Release|Any CPU
36 | {4B69E4D1-3EC4-4F4D-BED1-943034A69CF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {4B69E4D1-3EC4-4F4D-BED1-943034A69CF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {4B69E4D1-3EC4-4F4D-BED1-943034A69CF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {4B69E4D1-3EC4-4F4D-BED1-943034A69CF6}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {B7BA3009-02DA-4FF0-A7D5-179D3219522F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {B7BA3009-02DA-4FF0-A7D5-179D3219522F}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {B7BA3009-02DA-4FF0-A7D5-179D3219522F}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {B7BA3009-02DA-4FF0-A7D5-179D3219522F}.Release|Any CPU.Build.0 = Release|Any CPU
44 | {3DC57A14-708B-43AC-9AA9-26419ED0006F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
45 | {3DC57A14-708B-43AC-9AA9-26419ED0006F}.Debug|Any CPU.Build.0 = Debug|Any CPU
46 | {3DC57A14-708B-43AC-9AA9-26419ED0006F}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {3DC57A14-708B-43AC-9AA9-26419ED0006F}.Release|Any CPU.Build.0 = Release|Any CPU
48 | EndGlobalSection
49 | GlobalSection(SolutionProperties) = preSolution
50 | HideSolutionNode = FALSE
51 | EndGlobalSection
52 | GlobalSection(ExtensibilityGlobals) = postSolution
53 | SolutionGuid = {0DD11BDD-8B70-4700-8363-267A596E369E}
54 | EndGlobalSection
55 | EndGlobal
56 |
--------------------------------------------------------------------------------
/src/BCTranslateApp/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Configuration;
5 | using CognitiveServices.Translator;
6 | using CognitiveServices.Translator.Translate;
7 | using XlfParser.Model;
8 | using System.Threading;
9 | using LiteDB;
10 | using TranslationTools;
11 | using Azure.Data.Tables;
12 | using System.IO;
13 | using System.Diagnostics;
14 |
15 | namespace BCTranslateApp
16 | {
17 | partial class Program
18 | {
19 | static void Main(string[] args)
20 | {
21 | Console.WriteLine("ALBuild - Translate App 22.05.01");
22 | Console.WriteLine("(c) 2022 Erik Hougaard - hougaard.com");
23 |
24 | if (args.Length != 2)
25 | {
26 | Console.WriteLine("Syntax: BCTranslateApp ");
27 | return;
28 | }
29 | var hostFile = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) + "\\BCTranslateApp.exe";
30 |
31 | var Worker = new TranslateXlf();
32 | Worker.DoTheWork(args[0], args[1], hostFile, true, false);
33 | }
34 |
35 | }
36 | }
--------------------------------------------------------------------------------
/src/BCTranslateApp/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "BCTranslateApp": {
4 | "commandName": "Project",
5 | "commandLineArgs": "\"SharePoint Connector.g.xlf\" \"SharePoint Connector\""
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/src/TranslateAdmin/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace TranslateAdmin
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/Global Vars.cs:
--------------------------------------------------------------------------------
1 | using Azure.Data.Tables;
2 | using LiteDB;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.ComponentModel;
7 | using System.Configuration;
8 | using System.IO;
9 | using System.IO.Compression;
10 | using System.Linq;
11 | using System.Net;
12 | using System.Net.Http;
13 | using System.Text;
14 | using System.Threading.Tasks;
15 | using System.Windows;
16 | using System.Windows.Controls;
17 | using System.Windows.Data;
18 | using System.Windows.Documents;
19 | using System.Windows.Input;
20 | using System.Windows.Media;
21 | using System.Windows.Media.Imaging;
22 | using System.Windows.Shapes;
23 | using System.Windows.Threading;
24 | using System.Xml;
25 | using TranslationTools;
26 |
27 | namespace TranslateAdmin
28 | {
29 | public static class GlobalVars
30 | {
31 | public static LiteDatabase db;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
19 |
20 |
21 | Search
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | Source
31 |
32 |
33 |
34 | Language
35 |
36 |
37 |
38 | Target
39 |
40 |
41 |
42 | Origin
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using Azure.Data.Tables;
2 | using LiteDB;
3 | using Microsoft.Win32;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Configuration;
7 | using System.IO;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 | using System.Windows.Controls;
13 | using System.Windows.Data;
14 | using System.Windows.Documents;
15 | using System.Windows.Input;
16 | using System.Windows.Media;
17 | using System.Windows.Media.Imaging;
18 | using System.Windows.Navigation;
19 | using System.Windows.Shapes;
20 | using TranslationTools;
21 |
22 | namespace TranslateAdmin
23 | {
24 | ///
25 | /// Interaction logic for MainWindow.xaml
26 | ///
27 | public partial class MainWindow : Window
28 | {
29 | public MainWindow()
30 | {
31 | InitializeComponent();
32 | Languages = ConfigurationManager.AppSettings["Languages"].Split(',').ToList();
33 | Languages.Insert(0, "Any");
34 | LanguageSelect.ItemsSource = Languages;
35 | LanguageSelect.SelectedIndex = 0;
36 | GlobalVars.db = new LiteDatabase(ConfigurationManager.AppSettings["Database"]);
37 | }
38 | ~MainWindow()
39 | {
40 | GlobalVars.db.Dispose();
41 | }
42 |
43 | private void SearchButton_Click(object sender, RoutedEventArgs e)
44 | {
45 | var col = GlobalVars.db.GetCollection("translation");
46 | if (SearchName.Text.StartsWith("*"))
47 | {
48 | if (LanguageSelect.SelectedIndex == 0)
49 | {
50 | var results = col.Query().Where(x => x.source.Contains(SearchName.Text.Substring(1))).Limit(200).ToList();
51 | Result.ItemsSource = results;
52 | }
53 | else
54 | {
55 | var results = col.Query().Where(x => x.source.Contains(SearchName.Text.Substring(1)) && x.Language == (string)LanguageSelect.SelectedItem).Limit(200).ToList();
56 | Result.ItemsSource = results;
57 | }
58 | }
59 | else
60 | {
61 | if (LanguageSelect.SelectedIndex == 0)
62 | {
63 | var results = col.Query().Where(x => x.source.Equals(SearchName.Text)).Limit(200).ToList();
64 | Result.ItemsSource = results;
65 | }
66 | else
67 | {
68 | var results = col.Query().Where(x => x.source.Equals(SearchName.Text) && x.Language == (string)LanguageSelect.SelectedItem).Limit(200).ToList();
69 | Result.ItemsSource = results;
70 | }
71 | }
72 | }
73 |
74 | private void Save_Click(object sender, RoutedEventArgs e)
75 | {
76 | LoadedTranslation.source = source.Text;
77 | LoadedTranslation.target = target.Text;
78 | LoadedTranslation.Origin = Origin.Text;
79 | LoadedTranslation.Language = language.Text;
80 |
81 | var col = GlobalVars.db.GetCollection("translation");
82 | col.Update(LoadedTranslation);
83 |
84 | var tableClient = new TableClient(new Uri("https://" + ConfigurationManager.AppSettings["storageaccount"] + ".table.core.windows.net"),
85 | "translation",
86 | new TableSharedKeyCredential(ConfigurationManager.AppSettings["storageaccount"], ConfigurationManager.AppSettings["storageaccountkey"]));
87 |
88 |
89 | var entity = new TableEntity(LoadedTranslation.Language, LoadedTranslation.Index)
90 | {
91 | { "Language", LoadedTranslation.Language},
92 | { "Origin", LoadedTranslation.Origin },
93 | { "Source", LoadedTranslation.source },
94 | { "Target", LoadedTranslation.target }
95 | };
96 | try
97 | {
98 | tableClient.AddEntity(entity);
99 | }
100 | catch (Exception ex)
101 | {
102 | tableClient.UpdateEntity(entity, Azure.ETag.All);
103 | }
104 |
105 | LoadedTranslation = null;
106 | source.Text = "";
107 | target.Text = "";
108 | Origin.Text = "";
109 | language.Text = "";
110 | Save.IsEnabled = false;
111 | Cancel.IsEnabled = false;
112 | SearchButton.IsEnabled = true;
113 | }
114 |
115 | private void Cancel_Click(object sender, RoutedEventArgs e)
116 | {
117 | LoadedTranslation = null;
118 | source.Text = "";
119 | target.Text = "";
120 | Origin.Text = "";
121 | language.Text = "";
122 | Save.IsEnabled = false;
123 | Cancel.IsEnabled = false;
124 | SearchButton.IsEnabled = true;
125 | }
126 |
127 | private void Result_MouseDoubleClick(object sender, MouseButtonEventArgs e)
128 | {
129 | ListBox lb = (ListBox)sender;
130 | LoadedTranslation = (Translation)lb.SelectedItem;
131 | source.Text = LoadedTranslation.source;
132 | target.Text = LoadedTranslation.target;
133 | Origin.Text = LoadedTranslation.Origin + "+ Edited";
134 | language.Text = LoadedTranslation.Language;
135 | Save.IsEnabled = true;
136 | Cancel.IsEnabled = true;
137 | SearchButton.IsEnabled = false;
138 | }
139 | Translation LoadedTranslation;
140 | List Languages;
141 |
142 | private void ImportXLFMenu_Click(object sender, RoutedEventArgs e)
143 | {
144 | OpenFileDialog openFileDialog = new OpenFileDialog();
145 | openFileDialog.Filter = "Translation files (*.xlf)|*.xlf";
146 | if (openFileDialog.ShowDialog() == true)
147 | ImportXLF(openFileDialog.FileName);
148 | }
149 |
150 | private void Ripper_Click(object sender, RoutedEventArgs e)
151 | {
152 | Ripper ripWindow = new Ripper();
153 | ripWindow.Show();
154 | }
155 |
156 | private void Exit_Click(object sender, RoutedEventArgs e)
157 | {
158 | this.Close();
159 | }
160 | static void ImportXLF(string FileName)
161 | {
162 | var tableClient = new TableClient(new Uri("https://" + ConfigurationManager.AppSettings["storageaccount"] + ".table.core.windows.net"),
163 | "translation",
164 | new TableSharedKeyCredential(ConfigurationManager.AppSettings["storageaccount"], ConfigurationManager.AppSettings["storageaccountkey"]));
165 |
166 |
167 | var col = GlobalVars.db.GetCollection("translation");
168 |
169 | var model = XlfParser.Converter.Deserialize(File.ReadAllText(FileName));
170 | foreach (var Entry in model.File.Body.Group.TransUnit)
171 | {
172 | Translation StoreTranslation = new Translation();
173 | StoreTranslation.source = Entry.Source;
174 | StoreTranslation.target = Entry.Target;
175 | StoreTranslation.Language = model.File.TargetLanguage;
176 | StoreTranslation.Origin = System.IO.Path.GetFileName(FileName);
177 | StoreTranslation.Index = Translation.Hash(StoreTranslation.Language, StoreTranslation.source);
178 | if (!col.Exists(x => x.Index == StoreTranslation.Index))
179 | {
180 | col.Insert(StoreTranslation);
181 | col.EnsureIndex(x => x.Index);
182 | }
183 | else
184 | {
185 | col.Update(StoreTranslation);
186 | col.EnsureIndex(x => x.Index);
187 | }
188 |
189 | var entity = new TableEntity(StoreTranslation.Language, StoreTranslation.Index)
190 | {
191 | { "Language", StoreTranslation.Language},
192 | { "Origin", StoreTranslation.Origin },
193 | { "Source", StoreTranslation.source },
194 | { "Target", StoreTranslation.target }
195 | };
196 | try
197 | {
198 | tableClient.AddEntity(entity);
199 | }
200 | catch
201 | {
202 | tableClient.UpdateEntity(entity, Azure.ETag.All);
203 | }
204 | }
205 | MessageBox.Show("Done");
206 | }
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/Ripper.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 | Artifact Prefix
13 |
14 |
15 |
16 | Country Filter
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/Ripper.xaml.cs:
--------------------------------------------------------------------------------
1 | using Azure.Data.Tables;
2 | using LiteDB;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Collections.ObjectModel;
6 | using System.ComponentModel;
7 | using System.Configuration;
8 | using System.IO;
9 | using System.IO.Compression;
10 | using System.Linq;
11 | using System.Net;
12 | using System.Net.Http;
13 | using System.Text;
14 | using System.Threading.Tasks;
15 | using System.Windows;
16 | using System.Windows.Controls;
17 | using System.Windows.Data;
18 | using System.Windows.Documents;
19 | using System.Windows.Input;
20 | using System.Windows.Media;
21 | using System.Windows.Media.Imaging;
22 | using System.Windows.Shapes;
23 | using System.Windows.Threading;
24 | using System.Xml;
25 | using TranslationTools;
26 |
27 | namespace TranslateAdmin
28 | {
29 | ///
30 | /// Interaction logic for Ripper.xaml
31 | ///
32 | public partial class Ripper : Window
33 | {
34 | ConsoleContent dc = new ConsoleContent();
35 | public Ripper()
36 | {
37 | InitializeComponent();
38 | DataContext = dc;
39 | }
40 |
41 | private void Ripper_start_Click(object sender, RoutedEventArgs e)
42 | {
43 | ImportButton.IsEnabled = false;
44 | //await RunRipper(PrefixText.Text, CountryFilter.Text);
45 | BackgroundWorker backgroundWorker = new BackgroundWorker
46 | {
47 | WorkerReportsProgress = true
48 | };
49 | backgroundWorker.DoWork += BackgroundWorker_DoWork;
50 | backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
51 | backgroundWorker.RunWorkerAsync(argument: PrefixText.Text + "|" + CountryFilter.Text);
52 | }
53 |
54 | private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
55 | {
56 | dc.Add((string)e.UserState);
57 | }
58 |
59 | private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
60 | {
61 | string Prefix = ((string)e.Argument).Split('|')[0];
62 | string Filter = ((string)e.Argument).Split('|')[1];
63 | RunRipper(sender, Prefix, Filter).GetAwaiter().GetResult();
64 | }
65 |
66 | private async Task RunRipper(object sender, string Prefix, string Filter)
67 | {
68 | BackgroundWorker worker = (BackgroundWorker)sender;
69 | String NextMarker = "";
70 |
71 | var client = new HttpClient();
72 | client.Timeout = new TimeSpan(0, 20, 0);
73 |
74 | do
75 | {
76 | worker.ReportProgress(0, string.Format("Download listings from bcartifacts ({0})", NextMarker));
77 | //dc.Add(string.Format("Download listings from bcartifacts ({0})", NextMarker));
78 | var Master = new XmlDocument();
79 | if (NextMarker == "")
80 | Master.LoadXml(await client.GetStringAsync("https://bcartifacts.azureedge.net/sandbox/?comp=list&restype=container&prefix=" + Prefix));
81 | else
82 | Master.LoadXml(await client.GetStringAsync("https://bcartifacts.azureedge.net/sandbox/?comp=list&restype=container&prefix=" + Prefix + "&marker=" + NextMarker));
83 | worker.ReportProgress(0, string.Format("- Download Done"));
84 | NextMarker = Master.GetElementsByTagName("NextMarker")[0].InnerText;
85 | foreach (XmlNode xn in Master.GetElementsByTagName("Blob"))
86 | {
87 | var Name = xn.SelectSingleNode("Name").InnerText;
88 | var country = Name.Substring(Name.IndexOf("/") + 1);
89 | var vtxt = Name.Substring(0, Name.IndexOf("/"));
90 | if (Filter.Length > 0)
91 | if (!Name.Contains(Filter))
92 | continue;
93 | var URL = xn.SelectSingleNode("Url").InnerText;
94 | worker.ReportProgress(0, string.Format("- Found {0} on {1}", Name, URL));
95 | var data = await client.GetByteArrayAsync(URL);
96 | MemoryStream ms = new MemoryStream(data.Length);
97 | ms.Write(data);
98 | ms.Position = 0;
99 | worker.ReportProgress(0, "Unzipping");
100 | ZipArchive zip = new ZipArchive(ms);
101 | foreach (var entry in zip.Entries)
102 | {
103 | worker.ReportProgress(0, entry.FullName);
104 | if (entry.FullName.EndsWith(".app"))
105 | {
106 | var col = GlobalVars.db.GetCollection("translation");
107 |
108 | var tableClient = new TableClient(new Uri("https://" + ConfigurationManager.AppSettings["storageaccount"] + ".table.core.windows.net"),
109 | "translation",
110 | new TableSharedKeyCredential(ConfigurationManager.AppSettings["storageaccount"], ConfigurationManager.AppSettings["storageaccountkey"]));
111 |
112 | worker.ReportProgress(0, string.Format("- Extracting {0}", entry.Name));
113 | var app = entry.Open();
114 | MemoryStream m2 = new MemoryStream();
115 | app.CopyTo(m2);
116 | byte[] buf = m2.GetBuffer();
117 | Buffer.BlockCopy(buf, 40, buf, 0, (int)m2.Length - 40);
118 | m2.SetLength(m2.Length - 40);
119 |
120 | ZipArchive appzip = new ZipArchive(m2);
121 | string AppId = "";
122 | string VersionTxt = "";
123 | foreach (var appentry in appzip.Entries)
124 | {
125 | if (appentry.Name.Contains(".xlf") && !appentry.Name.Contains(".g.xlf"))
126 | {
127 | worker.ReportProgress(0, string.Format("Processing translations {0}", System.IO.Path.GetFileName(appentry.Name)));
128 | MemoryStream TransStream = new MemoryStream();
129 | var translatetream = appentry.Open();
130 | translatetream.CopyTo(TransStream);
131 | TransStream.Seek(0, SeekOrigin.Begin);
132 | if (TransStream.Length > 20)
133 | {
134 | StreamReader sr = new StreamReader(TransStream);
135 |
136 | var model = XlfParser.Converter.Deserialize(sr.ReadToEnd());
137 | if (!ConfigurationManager.AppSettings["Languages"].Split(',').ToList().Contains(model.File.TargetLanguage))
138 | {
139 | worker.ReportProgress(0, string.Format(" - Skipping {0} translations in {1} file as the language is not included in .config key 'Languages'", model.File.Body.Group.TransUnit.Count, model.File.TargetLanguage));
140 | }
141 | else
142 | {
143 | worker.ReportProgress(0, string.Format(" - {0} translations in file", model.File.Body.Group.TransUnit.Count));
144 | int counter = 0;
145 | foreach (var Entry in model.File.Body.Group.TransUnit)
146 | {
147 | /*
148 |
149 | Account Entity Setup
150 | Account Entity Setup
151 |
152 | Table Account Entity Setup - Property Caption
153 |
154 | */
155 | Translation StoreTranslation = new Translation();
156 | StoreTranslation.source = Entry.Source;
157 | StoreTranslation.target = Entry.Target;
158 | StoreTranslation.Language = model.File.TargetLanguage;
159 | StoreTranslation.Origin = entry.Name + " " + Name;
160 | StoreTranslation.Index = Translation.Hash(StoreTranslation.Language, StoreTranslation.source);
161 | if (!col.Exists(x => x.Index == StoreTranslation.Index))
162 | {
163 | col.Insert(StoreTranslation);
164 | col.EnsureIndex(x => x.Index);
165 |
166 | var entity = new TableEntity(StoreTranslation.Language, StoreTranslation.Index)
167 | {
168 | { "Language", StoreTranslation.Language},
169 | { "Origin", StoreTranslation.Origin },
170 | { "Source", StoreTranslation.source },
171 | { "Target", StoreTranslation.target }
172 | };
173 | try
174 | {
175 | tableClient.AddEntity(entity);
176 | }
177 | catch (Exception ex)
178 | {
179 | tableClient.UpdateEntity(entity, Azure.ETag.All);
180 | }
181 | }
182 | counter++;
183 | if (counter % 1000 == 0)
184 | {
185 | worker.ReportProgress(0, string.Format(" - {0} processed", counter));
186 | }
187 | }
188 | }
189 | }
190 | }
191 | }
192 | }
193 | }
194 | }
195 | } while (NextMarker != "");
196 | worker.ReportProgress(100, "Completed");
197 | }
198 | }
199 | public class ConsoleContent : INotifyPropertyChanged
200 | {
201 | string consoleInput = string.Empty;
202 | ObservableCollection consoleOutput = new ObservableCollection() { "Awaiting work..." };
203 |
204 | public ObservableCollection ConsoleOutput
205 | {
206 | get
207 | {
208 | return consoleOutput;
209 | }
210 | set
211 | {
212 | consoleOutput = value;
213 | OnPropertyChanged("ConsoleOutput");
214 | }
215 | }
216 |
217 | public object Dispatcher { get; private set; }
218 |
219 | public void Add(string txt)
220 | {
221 | ConsoleOutput.Add(txt);
222 | System.Windows.Threading.Dispatcher.CurrentDispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
223 | }
224 |
225 |
226 | public event PropertyChangedEventHandler PropertyChanged;
227 | void OnPropertyChanged(string propertyName)
228 | {
229 | if (null != PropertyChanged)
230 | PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
231 | }
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/TranslateAdmin/TranslateAdmin.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | true
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | PreserveNewest
21 | true
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/TranslationTools/Translation.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Text;
3 | using System.Security.Cryptography;
4 |
5 | namespace TranslationTools
6 | {
7 | public class Translation
8 | {
9 | public int Id { get; set; }
10 | public string source { get; set; }
11 | public string target { get; set; }
12 | public string Index { get; set; }
13 | public string Language { get; set; }
14 | public string Origin { get; set; }
15 | public static string Hash(String _language, string _source)
16 | {
17 | using (SHA256 sha256Hash = SHA256.Create())
18 | {
19 | // ComputeHash - returns byte array
20 | byte[] bytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(_language + '.' + _source));
21 |
22 | // Convert byte array to a string
23 | StringBuilder builder = new StringBuilder();
24 | for (int i = 0; i < bytes.Length; i++)
25 | {
26 | builder.Append(bytes[i].ToString("x2"));
27 | }
28 | return builder.ToString();
29 | }
30 | }
31 | public override string ToString()
32 | {
33 | return Language + " : " + source + " -> " + target;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/TranslationTools/TranslationProcess.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Configuration;
5 | using CognitiveServices.Translator;
6 | using CognitiveServices.Translator.Translate;
7 | using XlfParser.Model;
8 | using System.Threading;
9 | using LiteDB;
10 | using TranslationTools;
11 | using Azure.Data.Tables;
12 | using System.IO;
13 | using System.Diagnostics;
14 |
15 | namespace TranslationTools
16 | {
17 | public class TranslateXlf
18 | {
19 | string[] languages;
20 | TranslateClient client;
21 |
22 | int TranslateCount = 0;
23 | int TranslateRemote = 0;
24 | int TranslateAzure = 0;
25 | int TranslateErrors = 0;
26 |
27 | void UpdateStatus()
28 | {
29 | try
30 | {
31 | Console.SetCursorPosition(0, Console.GetCursorPosition().Top);
32 | }
33 | catch
34 | {
35 | Console.Write("\r");
36 | }
37 | Console.Write("Processed:{0} Pulled from cloud:{1} Translated:{2} Errors:{3}", TranslateCount, TranslateRemote, TranslateAzure, TranslateErrors);
38 | }
39 |
40 | public TranslateXlf()
41 | {
42 |
43 | }
44 | public void DoTheWork(string InputFile, string ProductName, string ConfigFile, bool ShowOutput, bool OffLineMode)
45 | {
46 | var config = ConfigurationManager.OpenExeConfiguration(ConfigFile);
47 | languages = ConfigurationManager.AppSettings["Languages"].Split(',');
48 |
49 | Console.WriteLine("Opening local database {0}", ConfigurationManager.AppSettings["Database"]);
50 |
51 | using (var db = new LiteDatabase(ConfigurationManager.AppSettings["Database"]))
52 | {
53 | if ((ConfigurationManager.AppSettings["AzureKey"] == null) ||
54 | (ConfigurationManager.AppSettings["AzureKey"] == ""))
55 | {
56 | Console.WriteLine("No Azure Cognitive Service Key specified in .config file, running without translation service.");
57 | OffLineMode = true;
58 | }
59 | Console.WriteLine("Input: {0}", InputFile);
60 |
61 |
62 | if (!OffLineMode)
63 | {
64 | client = new TranslateClient(new CognitiveServices.Translator.Configuration.CognitiveServicesConfig
65 | {
66 | SubscriptionKey = ConfigurationManager.AppSettings["AzureKey"],
67 | SubscriptionKeyAlternate = ConfigurationManager.AppSettings["AzureKey"],
68 | Name = ConfigurationManager.AppSettings["Name"]
69 | });
70 | var col = db.GetCollection("translation");
71 | Console.WriteLine("Connecting to Azure storage table {0}", ConfigurationManager.AppSettings["storageaccount"] + ".table.core.windows.net");
72 |
73 | var tableClient = new TableClient(new Uri("https://" + ConfigurationManager.AppSettings["storageaccount"] + ".table.core.windows.net"),
74 | "translation",
75 | new TableSharedKeyCredential(ConfigurationManager.AppSettings["storageaccount"], ConfigurationManager.AppSettings["storageaccountkey"]));
76 | ProcessXLFFile(InputFile, ProductName, col, tableClient, ShowOutput);
77 | }
78 | else
79 | {
80 | var col = db.GetCollection("translation");
81 | ProcessXLFFile(InputFile, ProductName, col, null, ShowOutput);
82 | }
83 | }
84 | if (!ShowOutput)
85 | Console.WriteLine("Processed:{0} Pulled from cloud:{1} Translated:{2} Errors:{3}", TranslateCount, TranslateRemote, TranslateAzure, TranslateErrors);
86 | //Console.WriteLine("\nDone!", db);
87 | //Console.ReadLine();
88 | }
89 | void ProcessXLFFile(string InputFile, string AppName, ILiteCollection col, TableClient tc, bool ShowOutput)
90 | {
91 | string xml = System.IO.File.ReadAllText(InputFile);
92 | var model = XlfParser.Converter.Deserialize(xml);
93 |
94 | Dictionary> Translations = new Dictionary>();
95 | for (int i = 0; i < languages.Length; i++)
96 | {
97 | Translations[languages[i]] = new List();
98 | }
99 | foreach (var Entry in model.File.Body.Group.TransUnit)
100 | {
101 | string[] results;
102 |
103 | results = TranslateLocal(AppName, Entry.Source, languages, col, tc);
104 | if (ShowOutput)
105 | UpdateStatus();
106 | for (int i = 0; i < results.Length; i++)
107 | {
108 | TransUnit tu = new TransUnit
109 | {
110 | Id = Entry.Id,
111 | Note = Entry.Note,
112 | Source = Entry.Source,
113 | Target = results[i]
114 | };
115 | Translations[languages[i]].Add(tu);
116 | }
117 | }
118 |
119 | for (int i = 0; i < languages.Length; i++)
120 | {
121 | string outputLanguage = languages[i].Split('-')[0];
122 | string outputCountry = languages[i].Split('-')[1];
123 | Xliff xliffout = new XlfParser.Model.Xliff()
124 | {
125 | Version = 1.2m,
126 | File = new XlfParser.Model.File()
127 | {
128 | Datatype = "xml",
129 | SourceLanguage = "en-US",
130 | TargetLanguage = outputLanguage + "-" + outputCountry,
131 | ToolId = "hougaard.com",
132 | Header = new XlfParser.Model.Header()
133 | {
134 | Tool = new XlfParser.Model.Tool()
135 | {
136 | Id = "hougaard.com",
137 | Company = "hougaard.com",
138 | Name = "Erik Hougaard"
139 | }
140 | },
141 | Body = new XlfParser.Model.Body()
142 | {
143 | Group = new Group()
144 | {
145 | TransUnit = Translations[languages[i]]
146 | }
147 | }
148 | }
149 | };
150 |
151 | var xml2 = XlfParser.Converter.Serialize(xliffout);
152 | System.IO.File.WriteAllText(InputFile.Replace(".g.", ".g." + languages[i] + "."), xml2.Replace("utf-16", "utf-8"));
153 | }
154 | }
155 | string[] TranslateLocal(string AppName, string Txt, string[] outputLanguages, ILiteCollection col, TableClient tc)
156 | {
157 | TranslateCount++;
158 | string[] result = new string[outputLanguages.Length];
159 | Dictionary ResultList = new Dictionary();
160 | List Missing = new List();
161 | foreach (var lng in outputLanguages)
162 | {
163 | var res = col.Find(x => x.Index == Translation.Hash(lng, Txt));
164 | if (res.Count() == 0)
165 | {
166 |
167 | try
168 | {
169 | var res2 = tc.GetEntity(lng, Translation.Hash(lng, Txt), null, CancellationToken.None);
170 | ResultList.Add(lng, res2.Value.Target);
171 |
172 | var translation = new Translation();
173 | translation.source = Txt;
174 | translation.target = res2.Value.Target;
175 | translation.Language = lng;
176 | translation.Index = Translation.Hash(translation.Language, translation.source);
177 | translation.Origin = AppName;
178 | col.Insert(translation);
179 | col.EnsureIndex(x => x.Index);
180 | TranslateRemote++;
181 | }
182 | catch
183 | {
184 | Missing.Add(lng);
185 | }
186 | }
187 | else
188 | ResultList.Add(lng, res.First().target);
189 | }
190 | if (Missing.Count > 0 && tc != null)
191 | {
192 | string[] lang = new string[Missing.Count];
193 | for (int i = 0; i < Missing.Count; i++)
194 | lang[i] = Missing[i].Split('-')[0];
195 | TranslateAzure++;
196 | var webserviceresult = CallWebService(Txt, lang);
197 | for (int i = 0; i < outputLanguages.Length; i++)
198 | {
199 | if (webserviceresult.ContainsKey(outputLanguages[i].Split('-')[0]))
200 | {
201 | var translation = new Translation();
202 | translation.source = Txt;
203 | translation.target = webserviceresult[outputLanguages[i].Split('-')[0]];
204 | translation.Language = outputLanguages[i];
205 | translation.Index = Translation.Hash(translation.Language, translation.source);
206 | translation.Origin = AppName;
207 | col.Insert(translation);
208 | col.EnsureIndex(x => x.Index);
209 | result[i] = webserviceresult[outputLanguages[i].Split('-')[0]];
210 |
211 | var entity = new TableEntity(translation.Language, translation.Index)
212 | {
213 | { "Language", translation.Language},
214 | { "Origin", translation.Origin },
215 | { "Source", translation.source },
216 | { "Target", translation.target }
217 | };
218 | try
219 | {
220 | tc.AddEntity(entity);
221 | TranslateAzure++;
222 | }
223 | catch (Exception ex)
224 | {
225 | tc.UpdateEntity(entity, Azure.ETag.All);
226 | //TranslateErrors++;
227 | //Console.WriteLine("\nCould not update Azure Table, error: {0}\n", ex.Message);
228 | }
229 | }
230 | else
231 | result[i] = ResultList[outputLanguages[i]];
232 | }
233 | return result;
234 | }
235 | else
236 | {
237 | for (int i = 0; i < outputLanguages.Length; i++)
238 | result[i] = ResultList[outputLanguages[i]];
239 | return result;
240 | }
241 | }
242 | Dictionary CallWebService(string Txt, string[] lang)
243 | {
244 | string[] outputLanguages = new string[lang.Length];
245 | RequestContent rc = new RequestContent()
246 | {
247 | Text = Txt
248 | };
249 | RequestParameter rp = new RequestParameter
250 | {
251 | From = "en",
252 | To = lang
253 | };
254 | int Retries = 0;
255 | Boolean Translated = false;
256 | do
257 | {
258 | try
259 | {
260 | var rb = client.Translate(rc, rp);
261 |
262 | //Console.Write(".");
263 | //string[] result = new string[lang.Length];
264 | Dictionary result = new Dictionary();
265 | for (int i = 0; i < lang.Length; i++)
266 | {
267 | if (!result.ContainsKey(lang[i]))
268 | result.Add(lang[i], rb[0].Translations[i].Text);
269 | }
270 | return result;
271 | }
272 | catch (Exception ex)
273 | {
274 | // Translation failed
275 | Thread.Sleep(10000);
276 | Retries++;
277 | if (Retries > 5)
278 | {
279 | Console.WriteLine("\nTranslation failed for {0} with error {1}", Txt, ex.Message);
280 | TranslateErrors++;
281 | //string[] result = new string[outputLanguages.Length];
282 | Dictionary result = new Dictionary();
283 | for (int i = 0; i < lang.Length; i++)
284 | {
285 | if (!result.ContainsKey(lang[i]))
286 | result.Add(lang[i], Txt);
287 | }
288 | return result;
289 | }
290 | }
291 | } while (!Translated);
292 | return null;
293 | }
294 | }
295 |
296 | }
--------------------------------------------------------------------------------
/src/TranslationTools/TranslationTableEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Azure;
7 | using Azure.Data.Tables;
8 |
9 | namespace TranslationTools
10 | {
11 | class TranslationTableEntry : ITableEntity
12 | {
13 | public string PartitionKey { get; set; }
14 | public string RowKey { get; set; }
15 | public DateTimeOffset? Timestamp { get; set; }
16 | public ETag ETag { get; set; }
17 | public string Source { get; set; }
18 | public string Target { get; set; }
19 | public string Language { get; set; }
20 | public string Origin { get; set; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/TranslationTools/TranslationTools.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/TranslationTools/XlfParser/Converter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Xml.Serialization;
6 | using XlfParser.Model;
7 |
8 | namespace XlfParser
9 | {
10 | public static class Converter
11 | {
12 | public static Xliff Deserialize(string input)
13 | {
14 | XmlSerializer ser = new XmlSerializer(typeof(Xliff));
15 |
16 | using (StringReader sr = new StringReader(input))
17 | {
18 | return (Xliff)ser.Deserialize(sr);
19 | }
20 | }
21 |
22 | public static string Serialize(Xliff ObjectToSerialize)
23 | {
24 | XmlSerializer xmlSerializer = new XmlSerializer(ObjectToSerialize.GetType());
25 |
26 | using (StringWriter textWriter = new StringWriter())
27 | {
28 | xmlSerializer.Serialize(textWriter, ObjectToSerialize);
29 | return textWriter.ToString();
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/TranslationTools/XlfParser/Model.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace XlfParser.Model
6 | {
7 | [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
8 | [System.Xml.Serialization.XmlRootAttribute(ElementName = "xliff", Namespace = "urn:oasis:names:tc:xliff:document:1.2", IsNullable = false)]
9 | public class Xliff
10 | {
11 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "version")]
12 | public decimal Version { get; set; }
13 |
14 | [System.Xml.Serialization.XmlElementAttribute("file", Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
15 | public File File { get; set; }
16 | }
17 |
18 | [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
19 | [System.Xml.Serialization.XmlRootAttribute(ElementName = "file", Namespace = "urn:oasis:names:tc:xliff:document:1.2", IsNullable = false)]
20 | public class File
21 | {
22 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "datatype")]
23 | public string Datatype { get; set; }
24 |
25 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "source-language")]
26 | public string SourceLanguage { get; set; }
27 |
28 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "target-language")]
29 | public string TargetLanguage { get; set; }
30 |
31 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "tool-id")]
32 | public string ToolId { get; set; }
33 |
34 | [System.Xml.Serialization.XmlElementAttribute("header", Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
35 | public Header Header { get; set; }
36 |
37 | [System.Xml.Serialization.XmlElementAttribute("body", Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
38 | public Body Body { get; set; }
39 | }
40 |
41 | #region Header
42 |
43 | [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
44 | public class Header
45 | {
46 | [System.Xml.Serialization.XmlElementAttribute("tool", Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
47 | public Tool Tool { get; set; }
48 | }
49 |
50 | [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
51 | public class Tool
52 | {
53 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "tool-id")]
54 | public string Id { get; set; }
55 |
56 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "tool-name")]
57 | public string Name { get; set; }
58 |
59 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "tool-company")]
60 | public string Company { get; set; }
61 | }
62 |
63 | #endregion
64 |
65 | #region Body
66 |
67 | [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
68 | public class Body
69 | {
70 | [System.Xml.Serialization.XmlElementAttribute("group", Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
71 | public Group Group { get; set; }
72 |
73 | [System.Xml.Serialization.XmlElementAttribute("trans-unit", Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
74 | public List TransUnit { get; set; }
75 | }
76 |
77 | [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
78 | public class Group
79 | {
80 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "datatype")]
81 | public string Datatype { get; set; }
82 |
83 | [System.Xml.Serialization.XmlElementAttribute("trans-unit", Namespace = "urn:oasis:names:tc:xliff:document:1.2")]
84 | public List TransUnit { get; set; }
85 | }
86 |
87 | [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
88 | [System.Xml.Serialization.XmlRootAttribute(ElementName = "trans-unit", IsNullable = false)]
89 | public class TransUnit
90 | {
91 | [System.Xml.Serialization.XmlAttributeAttribute(AttributeName = "id")]
92 | public string Id { get; set; }
93 |
94 | [System.Xml.Serialization.XmlElementAttribute(ElementName = "source")]
95 | public string Source { get; set; }
96 |
97 | [System.Xml.Serialization.XmlElementAttribute(ElementName = "target")]
98 | public string Target { get; set; }
99 |
100 | [System.Xml.Serialization.XmlElementAttribute(ElementName = "note")]
101 | public string Note { get; set; }
102 | }
103 |
104 | #endregion
105 | }
106 |
--------------------------------------------------------------------------------