コード例 #1
0
        public void TestWithPreArchiveData()
        {
            var memoryStream = new MemoryStream();
            var randomData   = new byte[999];

            randomData[100] = 99;
            memoryStream.Write(randomData, 0, randomData.Length);

            var randomFiles = new List <MpqFile>();

            for (var i = 0; i < 35; i++)
            {
                var fileStream = new MemoryStream();
                fileStream.Write(randomData, 0, randomData.Length);
                fileStream.Position = 0;
                randomFiles.Add(MpqFile.New(fileStream, $"file{i}"));
            }

            using var a = MpqArchive.Create(memoryStream, randomFiles, new MpqArchiveCreateOptions()
            {
                ListFileCreateMode = MpqFileCreateMode.None, AttributesCreateMode = MpqFileCreateMode.None
            });

            memoryStream.Position = 0;
            var archive = MpqArchive.Open(memoryStream);

            foreach (var file in archive.GetMpqFiles())
            {
                file.MpqStream.Seek(100, SeekOrigin.Begin);
                Assert.AreEqual(99, file.MpqStream.ReadByte());
            }

            archive.Dispose();
        }
コード例 #2
0
ファイル: MapExtensions.cs プロジェクト: Drake53/War3Net
        public static MpqFile GetEnvironmentFile(this Map map, Encoding?encoding = null)
        {
            using var memoryStream = new MemoryStream();
            using var writer       = new BinaryWriter(memoryStream, encoding ?? _defaultEncoding, true);

            writer.Write(map.Environment);
            writer.Flush();

            return(MpqFile.New(memoryStream, MapEnvironment.FileName));
        }
コード例 #3
0
        public static MpqFile GetInfoFile(this Campaign campaign, Encoding?encoding = null)
        {
            using var memoryStream = new MemoryStream();
            using var writer       = new BinaryWriter(memoryStream, encoding ?? _defaultEncoding, true);

            writer.Write(campaign.Info);
            writer.Flush();

            return(MpqFile.New(memoryStream, CampaignInfo.FileName));
        }
コード例 #4
0
ファイル: MapExtensions.cs プロジェクト: Drake53/War3Net
        public static MpqFile GetScriptFile(this Map map, Encoding?encoding = null)
        {
            using var memoryStream = new MemoryStream();
            using var writer       = new StreamWriter(memoryStream, encoding ?? _defaultEncoding, leaveOpen: true);

            writer.Write(map.Script);
            writer.Flush();

            return(MpqFile.New(memoryStream, map.Info.ScriptLanguage == ScriptLanguage.Lua ? "war3map.lua" : "war3map.j"));
        }
コード例 #5
0
ファイル: MPQEditor.cs プロジェクト: Stannnnn/War3NetMPQApi
 public void AddAll(string folder)
 {
     if (mpqArchiveBuilder != null)
     {
         foreach ((var fileName, var _, var stream) in EnumerateFiles(folder))
         {
             mpqArchiveBuilder.RemoveFile(fileName);
             mpqArchiveBuilder.AddFile(MpqFile.New(stream, fileName));
         }
     }
 }
コード例 #6
0
        public void TestStoreThenRetrieveFile(string filename)
        {
            var fileStream = File.OpenRead(filename);
            var mpqFile    = MpqFile.New(fileStream, filename, true);
            var archive    = MpqArchive.Create(new MemoryStream(), new List <MpqFile>()
            {
                mpqFile
            });

            var openedArchive = MpqArchive.Open(archive.BaseStream);
            var openedStream  = openedArchive.OpenFile(filename);

            StreamAssert.AreEqual(fileStream, openedStream, true);
        }
コード例 #7
0
ファイル: MapExtensions.cs プロジェクト: Drake53/War3Net
        public static MpqFile?GetCustomTextTriggersFile(this Map map, Encoding?encoding = null)
        {
            if (map.CustomTextTriggers is null)
            {
                return(null);
            }

            using var memoryStream = new MemoryStream();
            using var writer       = new BinaryWriter(memoryStream, encoding ?? _defaultEncoding, true);

            writer.Write(map.CustomTextTriggers, encoding ?? _defaultEncoding);
            writer.Flush();

            return(MpqFile.New(memoryStream, MapCustomTextTriggers.FileName));
        }
コード例 #8
0
ファイル: MapExtensions.cs プロジェクト: Drake53/War3Net
        public static MpqFile?GetUpgradeObjectDataFile(this Map map, Encoding?encoding = null)
        {
            if (map.UpgradeObjectData is null)
            {
                return(null);
            }

            using var memoryStream = new MemoryStream();
            using var writer       = new BinaryWriter(memoryStream, encoding ?? _defaultEncoding, true);

            writer.Write(map.UpgradeObjectData);
            writer.Flush();

            return(MpqFile.New(memoryStream, MapUpgradeObjectData.FileName));
        }
コード例 #9
0
ファイル: MapExtensions.cs プロジェクト: Drake53/War3Net
        public static MpqFile?GetPreviewIconsFile(this Map map, Encoding?encoding = null)
        {
            if (map.PreviewIcons is null)
            {
                return(null);
            }

            using var memoryStream = new MemoryStream();
            using var writer       = new BinaryWriter(memoryStream, encoding ?? _defaultEncoding, true);

            writer.Write(map.PreviewIcons);
            writer.Flush();

            return(MpqFile.New(memoryStream, MapPreviewIcons.FileName));
        }
コード例 #10
0
        public static MpqFile?GetItemObjectDataFile(this Campaign campaign, Encoding?encoding = null)
        {
            if (campaign.ItemObjectData is null)
            {
                return(null);
            }

            using var memoryStream = new MemoryStream();
            using var writer       = new BinaryWriter(memoryStream, encoding ?? _defaultEncoding, true);

            writer.Write(campaign.ItemObjectData);
            writer.Flush();

            return(MpqFile.New(memoryStream, CampaignItemObjectData.FileName));
        }
コード例 #11
0
ファイル: MapExtensions.cs プロジェクト: Drake53/War3Net
        public static MpqFile?GetTriggerStringsFile(this Map map, Encoding?encoding = null)
        {
            if (map.TriggerStrings is null)
            {
                return(null);
            }

            using var memoryStream = new MemoryStream();
            using var writer       = new StreamWriter(memoryStream, encoding ?? _defaultEncoding, leaveOpen: true);

            writer.WriteTriggerStrings(map.TriggerStrings);
            writer.Flush();

            return(MpqFile.New(memoryStream, MapTriggerStrings.FileName));
        }
コード例 #12
0
        public void TestStoreThenRetrieveEmptyFileWithFlags(MpqFileFlags flags)
        {
            const string FileName = "someRandomFile.empty";

            using var mpqFile   = MpqFile.New(null, FileName);
            mpqFile.TargetFlags = flags;
            using var archive   = MpqArchive.Create(new MemoryStream(), new List <MpqFile>()
            {
                mpqFile
            }, new MpqArchiveCreateOptions { BlockSize = BlockSize });

            using var openedArchive = MpqArchive.Open(archive.BaseStream);
            var openedStream = openedArchive.OpenFile(FileName);

            Assert.IsTrue(openedStream.Length == 0);
        }
コード例 #13
0
        public void TestStoreThenRetrieveFileWithFlags(string fileName, MpqFileFlags flags)
        {
            using var fileStream = File.OpenRead(fileName);
            var mpqFile = MpqFile.New(fileStream, fileName, true);

            mpqFile.TargetFlags = flags;
            using var archive   = MpqArchive.Create(new MemoryStream(), new List <MpqFile>()
            {
                mpqFile
            }, new MpqArchiveCreateOptions { BlockSize = BlockSize });

            using var openedArchive = MpqArchive.Open(archive.BaseStream);
            var openedStream = openedArchive.OpenFile(fileName);

            StreamAssert.AreEqual(fileStream, openedStream, true);
        }
コード例 #14
0
ファイル: MpqArchiveTest.cs プロジェクト: pilgarlicx/War3Net
        public void TestStoreThenRetrieveFile(string filename)
        {
            var fileStream = File.OpenRead(filename);
            // var mpqFile = new MpqKnownFile(filename, fileStream, MpqFileFlags.Exists, MpqLocale.Neutral, true);
            var mpqFile = MpqFile.New(fileStream, filename);
            var archive = MpqArchive.Create(new MemoryStream(), new List <MpqFile>()
            {
                mpqFile
            });

            var openedArchive = MpqArchive.Open(archive.BaseStream);
            var openedStream  = openedArchive.OpenFile(filename);

            fileStream.Position = 0;
            StreamAssert.AreEqual(fileStream, openedStream);
        }
コード例 #15
0
ファイル: MpqArchiveTests.cs プロジェクト: ihaiucom/War3Net
        public void TestDeleteFile()
        {
            var          inputArchivePath = TestDataProvider.GetFile(@"Maps\NewLuaMap.w3m");
            const string fileName         = "war3map.lua";

            using var inputArchive = MpqArchive.Open(inputArchivePath);
            var newFile = MpqFile.New(null, fileName);

            var mpqFiles = inputArchive.GetMpqFiles();
            var oldFile  = mpqFiles.FirstOrDefault(file => file.Equals(newFile)) ?? throw new FileNotFoundException($"File not found: {fileName}");
            var newFiles = mpqFiles.Select(file => ReferenceEquals(file, oldFile) ? newFile : file).ToArray();

            using var outputArchive = MpqArchive.Create((Stream?)null, newFiles, new MpqArchiveCreateOptions { BlockSize = inputArchive.Header.BlockSize, HashTableSize = (ushort)inputArchive.Header.HashTableSize, AttributesFlags = AttributesFlags.DateTime | AttributesFlags.Crc32 });

            Assert.IsTrue(outputArchive.FileExists(fileName, out var entryIndex));
            Assert.AreEqual(0U, outputArchive[entryIndex].FileSize);
        }
コード例 #16
0
ファイル: MpqArchiveTest.cs プロジェクト: pilgarlicx/War3Net
        public void TestDeleteFile()
        {
            const string inputArchivePath = @".\TestArchives\NewLuaMap.w3m";
            const string fileName         = "war3map.lua";

            using var inputArchive = MpqArchive.Open(inputArchivePath);
            var newFile = MpqFile.New(null, fileName);

            var mpqFiles = inputArchive.GetMpqFiles();
            var oldFile  = mpqFiles.FirstOrDefault(file => file.IsSameAs(newFile)) ?? throw new FileNotFoundException($"File not found: {fileName}");
            var newFiles = mpqFiles.Select(file => ReferenceEquals(file, oldFile) ? newFile : file).ToArray();

            using var outputArchive = MpqArchive.Create((Stream?)null, newFiles, (ushort)inputArchive.Header.HashTableSize, inputArchive.Header.BlockSize);

            Assert.IsTrue(outputArchive.FileExists(fileName, out var entryIndex));
            Assert.AreEqual(0U, outputArchive[entryIndex].FileSize);
        }
コード例 #17
0
ファイル: MpqArchiveTest.cs プロジェクト: pilgarlicx/War3Net
        public void TestStoreThenRetrieveEmptyFileWithFlags(MpqFileFlags flags)
        {
            const string FileName = "someRandomFile.empty";

            // var mpqFile = new MpqKnownFile(FileName, null, flags, MpqLocale.Neutral);
            var mpqFile = MpqFile.New(null, FileName);

            mpqFile.TargetFlags = flags;
            var archive = MpqArchive.Create(new MemoryStream(), new List <MpqFile>()
            {
                mpqFile
            }, blockSize: BlockSize);

            var openedArchive = MpqArchive.Open(archive.BaseStream);
            var openedStream  = openedArchive.OpenFile(FileName);

            Assert.IsTrue(openedStream.Length == 0);
        }
コード例 #18
0
        public static void TranspileAndSave(string inputFile, string outputFile, string?commonJPath, string?blizzardJPath, ScriptLanguage targetLanguage, BackgroundWorker?worker)
        {
            var targetFileName = targetLanguage switch
            {
                ScriptLanguage.Jass => "war3map.j",
                ScriptLanguage.Lua => "war3map.lua",
                _ => throw new InvalidEnumArgumentException(nameof(targetLanguage), (int)targetLanguage, typeof(ScriptLanguage)),
            };

            using var mapArchive = MpqArchive.Open(inputFile, true);

            var map               = Map.Open(mapArchive);
            var sourceLanguage    = map.Info.ScriptLanguage;
            var mpqArchiveBuilder = new MpqArchiveBuilder(mapArchive);

            using var mapInfoStream = new MemoryStream();
            using var mapInfoWriter = new BinaryWriter(mapInfoStream);

            var mapInfo = map.Info;

            mapInfo.ScriptLanguage = targetLanguage;
            if (mapInfo.FormatVersion < MapInfoFormatVersion.Lua)
            {
                mapInfo.FormatVersion = MapInfoFormatVersion.Lua;
                if (mapInfo.GameVersion is null)
                {
                    mapInfo.GameVersion = GamePatchVersionProvider.GetGameVersion(GamePatch.v1_31_1);
                }
            }

            mapInfoWriter.Write(mapInfo);
            mapInfoStream.Position = 0;
            mpqArchiveBuilder.AddFile(MpqFile.New(mapInfoStream, MapInfo.FileName));

            if (sourceLanguage == ScriptLanguage.Jass)
            {
                if (targetLanguage != ScriptLanguage.Lua)
                {
                    throw new NotSupportedException($"Unable to transpile from {sourceLanguage} to {targetLanguage}.");
                }

                mpqArchiveBuilder.RemoveFile("war3map.j");
                mpqArchiveBuilder.RemoveFile(@"Scripts\war3map.j");

                var jassHelperFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Warcraft III", "JassHelper");
                if (string.IsNullOrEmpty(commonJPath))
                {
                    if (Directory.Exists(jassHelperFolder))
                    {
                        commonJPath = Path.Combine(jassHelperFolder, "common.j");
                        if (string.IsNullOrEmpty(blizzardJPath))
                        {
                            blizzardJPath = Path.Combine(jassHelperFolder, "Blizzard.j");
                        }
                    }
                    else
                    {
                        throw new DirectoryNotFoundException("Unable to automatically find common.j and Blizzard.j folder.");
                    }
                }

                var mapHasCustomBlizzardJ = mapArchive.FileExists(@"Scripts\Blizzard.j");
                if (string.IsNullOrEmpty(blizzardJPath) && !mapHasCustomBlizzardJ)
                {
                    if (Directory.Exists(jassHelperFolder))
                    {
                        blizzardJPath = Path.Combine(jassHelperFolder, "Blizzard.j");
                    }
                    else
                    {
                        throw new DirectoryNotFoundException("Unable to automatically find common.j and Blizzard.j folder.");
                    }
                }

                var estimatedWorkToTranspile = map.Script.Length + 700000;
                var estimatedWorkToRegisterCommonAndBlizzard = 35000000 / estimatedWorkToTranspile;
                if (estimatedWorkToRegisterCommonAndBlizzard == 0)
                {
                    estimatedWorkToRegisterCommonAndBlizzard = 1;
                }

                var transpiler = new JassToLuaTranspiler();
                transpiler.RegisterJassFile(JassSyntaxFactory.ParseCompilationUnit(File.ReadAllText(commonJPath)));

                worker?.ReportProgress(estimatedWorkToRegisterCommonAndBlizzard);

                if (mapHasCustomBlizzardJ)
                {
                    throw new NotImplementedException("Custom Blizzard.j files are currently not supported.");
                }
                else
                {
                    transpiler.RegisterJassFile(JassSyntaxFactory.ParseCompilationUnit(File.ReadAllText(blizzardJPath)));

                    worker?.ReportProgress(estimatedWorkToRegisterCommonAndBlizzard * 2);
                }

                var luaCompilationUnit = transpiler.Transpile(JassSyntaxFactory.ParseCompilationUnit(map.Script));

                using var stream = new MemoryStream();
                using (var writer = new StreamWriter(stream, leaveOpen: true))
                {
                    var luaRenderOptions = new LuaSyntaxGenerator.SettingInfo
                    {
                        // Indent = 4,
                        // IsCommentsDisabled = true,
                    };

                    var luaRenderer = new LuaRenderer(luaRenderOptions, writer);
                    luaRenderer.RenderCompilationUnit(luaCompilationUnit);
                    writer.Flush();
                }

                stream.Position = 0;
                mpqArchiveBuilder.AddFile(MpqFile.New(stream, targetFileName));

                worker?.ReportProgress(100);

                var mpqArchiveCreateOptions = new MpqArchiveCreateOptions
                {
                };

                mpqArchiveBuilder.SaveTo(outputFile, mpqArchiveCreateOptions);
            }
            else if (sourceLanguage == ScriptLanguage.Lua)
            {
                mpqArchiveBuilder.RemoveFile("war3map.lua");
                mpqArchiveBuilder.RemoveFile(@"Scripts\war3map.lua");

                throw new NotSupportedException($"Unable to transpile from {sourceLanguage} to {targetLanguage}.");
            }
            else
            {
                throw new NotSupportedException($"Unable to transpile from {sourceLanguage} to {targetLanguage}.");
            }
        }
コード例 #19
0
ファイル: MainForm.cs プロジェクト: Drake53/War3App
        private static void SaveArchiveBackgroundWork(object?sender, DoWorkEventArgs e)
        {
            var archiveBuilder = new MpqArchiveBuilder(_archive);

            var progress = new SaveArchiveProgress();

            progress.Saving = false;

            for (var i = 0; i < _fileList.Items.Count; i++)
            {
                var tag = _fileList.Items[i].GetTag();
                if (tag.Parent is not null)
                {
                    continue;
                }

                if (tag.Status == MapFileStatus.Removed)
                {
                    if (tag.TryGetHashedFileName(out var hashedFileName))
                    {
                        archiveBuilder.RemoveFile(hashedFileName);
                    }
                    else
                    {
                        archiveBuilder.RemoveFile(_archive, tag.MpqEntry);
                    }
                }
                else if (tag.Children is not null)
                {
                    if (tag.Children.All(child => child.Status == MapFileStatus.Removed))
                    {
                        throw new InvalidOperationException("Parent tag should have been removed since all child tags are removed, but was " + tag.Status);
                    }
                    else if (tag.Children.Any(child => child.IsModified || child.Status == MapFileStatus.Removed))
                    {
                        // Assume at most one nested archive (for campaign archives), so no recursion.
                        using var subArchive = MpqArchive.Open(_archive.OpenFile(tag.FileName));
                        foreach (var child in tag.Children)
                        {
                            if (child.FileName != null)
                            {
                                subArchive.AddFileName(child.FileName);
                            }
                        }

                        var subArchiveBuilder = new MpqArchiveBuilder(subArchive);
                        foreach (var child in tag.Children)
                        {
                            if (child.Status == MapFileStatus.Removed)
                            {
                                if (child.TryGetHashedFileName(out var hashedFileName))
                                {
                                    subArchiveBuilder.RemoveFile(hashedFileName);
                                }
                                else
                                {
                                    subArchiveBuilder.RemoveFile(subArchive, child.MpqEntry);
                                }
                            }
                            else if (child.TryGetModifiedMpqFile(out var subArchiveAdaptedFile))
                            {
                                subArchiveBuilder.AddFile(subArchiveAdaptedFile);

                                _saveArchiveWorker.ReportProgress(0, progress);
                            }
                            else
                            {
                                _saveArchiveWorker.ReportProgress(0, progress);
                            }
                        }

                        var adaptedSubArchiveStream = new MemoryStream();
                        subArchiveBuilder.SaveWithPreArchiveData(adaptedSubArchiveStream, true);

                        adaptedSubArchiveStream.Position = 0;
                        var adaptedFile = MpqFile.New(adaptedSubArchiveStream, tag.FileName, false);
                        adaptedFile.TargetFlags = tag.MpqEntry.Flags;
                        archiveBuilder.AddFile(adaptedFile);

                        _saveArchiveWorker.ReportProgress(0, progress);
                    }
                    else
                    {
                        _saveArchiveWorker.ReportProgress(tag.Children.Length, progress);
                    }
                }
                else if (tag.TryGetModifiedMpqFile(out var adaptedFile))
                {
                    archiveBuilder.AddFile(adaptedFile);

                    _saveArchiveWorker.ReportProgress(0, progress);
                }
                else
                {
                    _saveArchiveWorker.ReportProgress(0, progress);
                }
            }

            progress.Saving = true;
            _saveArchiveWorker.ReportProgress(0, progress);

            using (var fileStream = File.Create((string)e.Argument))
            {
                archiveBuilder.SaveWithPreArchiveData(fileStream);
            }
        }
コード例 #20
0
ファイル: MPQEditor.cs プロジェクト: Stannnnn/War3NetMPQApi
 public void Add(string inputFile, string fileName)
 {
     mpqArchiveBuilder?.RemoveFile(fileName);
     mpqArchiveBuilder?.AddFile(MpqFile.New(File.OpenRead(inputFile), fileName));
 }
コード例 #21
0
ファイル: MPQEditor.cs プロジェクト: Stannnnn/War3NetMPQApi
        public void TranspileAndSaveTest(string inputFile, string outputFile, ScriptLanguage targetLanguage)
        {
            var targetFileName = targetLanguage switch
            {
                ScriptLanguage.Jass => "war3map.j",
                ScriptLanguage.Lua => "war3map.lua",
                _ => throw new InvalidEnumArgumentException(nameof(targetLanguage), (int)targetLanguage, typeof(ScriptLanguage)),
            };

            using var mapArchive = MpqArchive.Open(inputFile, true);

            var map               = Map.Open(mapArchive);
            var sourceLanguage    = map.Info.ScriptLanguage;
            var mpqArchiveBuilder = new MpqArchiveBuilder(mapArchive);

            using var mapInfoStream = new MemoryStream();
            using var mapInfoWriter = new BinaryWriter(mapInfoStream);

            var mapInfo = map.Info;

            mapInfo.ScriptLanguage = targetLanguage;
            if (mapInfo.FormatVersion < MapInfoFormatVersion.Lua)
            {
                mapInfo.FormatVersion = MapInfoFormatVersion.Lua;
                if (mapInfo.GameVersion is null)
                {
                    mapInfo.GameVersion = GamePatchVersionProvider.GetGameVersion(War3Net.Build.Common.GamePatch.v1_31_1);
                }
            }

            mapInfoWriter.Write(mapInfo);
            mapInfoStream.Position = 0;
            mpqArchiveBuilder.AddFile(MpqFile.New(mapInfoStream, MapInfo.FileName));

            if (sourceLanguage == ScriptLanguage.Jass)
            {
                if (targetLanguage != ScriptLanguage.Lua)
                {
                    throw new NotSupportedException($"Unable to transpile from {sourceLanguage} to {targetLanguage}.");
                }

                mpqArchiveBuilder.RemoveFile("war3map.j");
                mpqArchiveBuilder.RemoveFile(@"Scripts\war3map.j");

                using var reader = new StreamReader(@"C:\Users\Stan\Google Drive\PHP Projects\Files\common.j");
                var commonJ = JassSyntaxFactory.ParseCompilationUnit(reader.ReadToEnd());

                using var reader2 = new StreamReader(@"C:\Users\Stan\Google Drive\PHP Projects\Files\blizzard.j");
                var blizzardJ = JassSyntaxFactory.ParseCompilationUnit(reader2.ReadToEnd());

                var transpiler = new JassToLuaTranspiler();
                transpiler.RegisterJassFile(commonJ);
                transpiler.RegisterJassFile(blizzardJ);

                var script = mapArchive.OpenFile("war3map.j");

                using var reader3 = new StreamReader(script);
                var luaCompilationUnit = transpiler.Transpile(JassSyntaxFactory.ParseCompilationUnit(reader3.ReadToEnd()));
                script.Close();

                var tempFileName = Path.GetTempFileName();
                try
                {
                    using (var writer = new StreamWriter(tempFileName))
                    {
                        var luaRenderOptions = new LuaSyntaxGenerator.SettingInfo
                        {
                            // Indent = 4,
                            // IsCommentsDisabled = true,
                        };

                        var luaRenderer = new LuaRenderer(luaRenderOptions, writer);
                        luaRenderer.RenderCompilationUnit(luaCompilationUnit);
                    }

                    using var fileStream = File.OpenRead(tempFileName);
                    mpqArchiveBuilder.AddFile(MpqFile.New(fileStream, targetFileName));

                    var mpqArchiveCreateOptions = new MpqArchiveCreateOptions
                    {
                        AttributesCreateMode = MpqFileCreateMode.Prune,
                    };

                    mpqArchiveBuilder.SaveTo(outputFile, mpqArchiveCreateOptions);
                }
                finally
                {
                    File.Delete(tempFileName);
                }
            }
            else if (sourceLanguage == ScriptLanguage.Lua)
            {
                throw new NotSupportedException($"Unable to transpile from {sourceLanguage} to {targetLanguage}.");
            }
            else
            {
                throw new NotSupportedException($"Unable to transpile from {sourceLanguage} to {targetLanguage}.");
            }
        }