using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using UnityEditor; using UnityEditor.Localization; using UnityEngine; namespace Crowdin.Plugin.Operations { using Helpers; using Helpers.PluginLocalization; using Helpers.UnityLocalization; using Models; using Models.PushData; using Models.Settings; using Models.Translations; using Models.Translations.Items; using Server_API; using Server_API.Helpers; using Server_API.Models.Projects; using Server_API.Serializable; using Server_API.Serializable.Translations; using Server_API.Services; /// Pushes translations for existing assets to Crowdin: for every source asset already on /// Crowdin, uploads a storage per (asset, target language) pair and attaches it as that /// language's translation. public static class PushAssetTranslations { private const int MaxConcurrentUploads = 5; public static async Task RunAsync( PushTranslationsOptions options, IProgress progress, CancellationToken cancellationToken) { int rootDirId = await DirectoriesHelper.EnsureRootDirectoryAsync(cancellationToken); progress.Report(new OperationProgress( LocalizationHelper.GetLocalStringByKey(LocalKeys.start_export_transl_assets))); TranslationsHelper.RefreshAllAssetsTranslations(); foreach (string tableName in options.Tables) { var collection = LocalizationEditorSettings.GetAssetTableCollection(tableName); foreach (var row in collection.GetRowEnumerator()) { ExportDataHelper.ParseAssetRowToCrowdinTranslation(row, tableName); } } Dictionary tableDirIds = await DirectoriesHelper.EnsureDirectoriesAsync( options.Tables.Select(tableName => (name: tableName, title: tableName)).ToList(), rootDirId, cancellationToken); var projectsService = new ProjectsApiService(); ProjectInfo projectInfo = await projectsService.GetProjectById(SettingsHelper.GetProjectId(), cancellationToken); var remoteLanguageIds = new HashSet(projectInfo.TargetLanguageIds ?? Array.Empty()); var pushList = new List(); foreach (string tableName in options.Tables) { await FillUpdateAssetsAsync(tableDirIds[tableName], tableName, remoteLanguageIds, pushList, progress, cancellationToken); } var perLocale = new Dictionary(); int sentTranslations = 0; for (var offset = 0; offset < pushList.Count; offset += MaxConcurrentUploads) { PushAssetTranslationData[] items = pushList.Skip(offset).Take(MaxConcurrentUploads).ToArray(); bool[] results = await Task.WhenAll(items.Select(item => UploadAssetTranslationAsync(item, options.AutoApprove, progress, cancellationToken))); for (var i = 0; i < items.Length; i++) { if (!results[i]) { continue; } sentTranslations++; perLocale[items[i].lang] = perLocale.GetValueOrDefault(items[i].lang) + 1; progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByKey( LocalKeys.translation_upd, items[i].lang, items[i].key, items[i].tableName))); } } return new PushTranslationsResult(sentTranslations, perLocale); } private static async Task UploadAssetTranslationAsync( PushAssetTranslationData item, bool autoApprove, IProgress progress, CancellationToken cancellationToken) { long storageId = 0; try { var storageService = new StorageApiService(); var translationsService = new TranslationsApiService(); StorageData storage = await storageService.CreateStorage(item.fullname, item.filePath, cancellationToken); storageId = storage.id; await translationsService.UploadTranslation( new UpdateTranslationQueryData(item.sourceAssetId, storage.id, autoApproveImported: autoApprove), item.lang, cancellationToken); return true; } catch (CrowdinApiException ex) { Debug.LogException(ex); progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByException(ex))); return false; } finally { if (storageId != 0) { try { await new StorageApiService().DeleteStorage(storageId, cancellationToken); } catch (CrowdinApiException ex) { Debug.LogException(ex); } } } } private static async Task FillUpdateAssetsAsync( int directoryId, string tableName, HashSet remoteLanguageIds, List pushList, IProgress progress, CancellationToken cancellationToken) { try { List crowdinTranslationAssets = TranslationsHelper.GetTranslationAssets(tableName).ToList(); var sourceFilesService = new SourceFilesApiService(); List sourceFiles = await sourceFilesService.ListSourceFiles(directoryId, cancellationToken); if (sourceFiles.Count is 0) { return; } foreach (FileData fileData in sourceFiles) { string name = Path.GetFileNameWithoutExtension(fileData.name); TranslationAsset translationAsset = crowdinTranslationAssets.FirstOrDefault( asset => asset.key == name ); if (translationAsset is null) { continue; } foreach (TranslationAssetItem translation in translationAsset.translations) { if (translation.language == SettingsHelper.GetSourceLanguage()) { continue; } if (!remoteLanguageIds.Contains(translation.language)) { Debug.LogWarning( $"Skipping \"{translation.language}\" translation for asset \"{translationAsset.key}\" - this language is not available on the Crowdin project."); progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByKey( LocalKeys.skip_language_not_on_remote, translation.language, translationAsset.key))); continue; } string transFilepath = AssetDatabase.GetAssetPath(translation.asset); pushList.Add(new PushAssetTranslationData( fileData.id, translationAsset.key, fileData.name, $"{translationAsset.key}-{translation.language}{Path.GetExtension(transFilepath)}", transFilepath, translation.language, tableName )); } } } catch (CrowdinApiException ex) { Debug.LogException(ex); progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByException(ex))); } } } }