using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using UnityEditor.Localization; using UnityEngine; using UnityEngine.Localization.Tables; namespace Crowdin.Plugin.Operations { using Helpers; using Helpers.FileHelpers; using Helpers.PluginLocalization; using Helpers.UnityLocalization; using Models; using Models.Settings; using Server_API; using Server_API.Helpers; using Server_API.Serializable; using Server_API.Serializable.Translations; using Server_API.Services; /// Pushes translations for existing strings to Crowdin. String-based projects re-upload the /// whole CSV; file-based projects either upload per-language translations against an existing /// source file, or - if the source file does not exist on Crowdin yet - create it with /// translations included in one request. public static class PushStringTranslations { private const int MaxConcurrentUploads = 10; public static Task RunAsync( PushTranslationsOptions options, IProgress progress, CancellationToken cancellationToken) { progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByKey(LocalKeys.start_parsing_str_transl))); return SettingsHelper.GetProjectType() is ProjectType.StringBased ? PushStringBasedAsync(options, progress, cancellationToken) : PushFileBasedAsync(options, progress, cancellationToken); } private static async Task PushStringBasedAsync( PushTranslationsOptions options, IProgress progress, CancellationToken cancellationToken) { var perLocaleTotal = new Dictionary(); int totalSent = 0; foreach (string tableName in options.Tables) { TranslationsHelper.RefreshAllStringsTranslation(); StringTableCollection tableCollection = LocalizationEditorSettings.GetStringTableCollection(tableName); progress.Report(new OperationProgress(string.Empty)); progress.Report(new OperationProgress( LocalizationHelper.GetLocalStringByKey(LocalKeys.send_translations_str, tableName))); var perLocaleTable = new Dictionary(); foreach (LocalizationTableCollection.Row row in tableCollection.GetRowEnumerator()) { string context = tableCollection.GetSharedEntryComments(row.KeyEntry.Id); if (!ExportDataHelper.UpdateTranslationFromStringsTableRow( row, tableName, context, out IReadOnlyList localesWithTranslation)) { Debug.LogErrorFormat( "Failed to update translation from Row ID = {0} & Table Name = {1}", row.KeyEntry.Id, tableName); continue; } foreach (string locale in localesWithTranslation) { perLocaleTable[locale] = perLocaleTable.GetValueOrDefault(locale) + 1; } } bool uploaded = await StringBasedProjectsHelper.UploadStringBasedProject( new StringBasedProjectsHelper.UploadProjectCommand { TableName = tableName, SelectedBranchId = options.BranchId, UpdateStrings = options.UpdateStrings, CleanupMode = options.CleanupMode, UpdateOption = null, IncludeTranslations = true }, progress, cancellationToken); if (!uploaded) { continue; } foreach ((string locale, int count) in perLocaleTable) { progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByKey( LocalKeys.finish_upload_transl_table, locale, count.ToString()))); perLocaleTotal[locale] = perLocaleTotal.GetValueOrDefault(locale) + count; totalSent += count; } } return new PushTranslationsResult(totalSent, perLocaleTotal); } private static async Task PushFileBasedAsync( PushTranslationsOptions options, IProgress progress, CancellationToken cancellationToken) { int rootDirId = await DirectoriesHelper.EnsureRootDirectoryAsync(cancellationToken); var sourceFilesService = new SourceFilesApiService(); var storageService = new StorageApiService(); var translationsService = new TranslationsApiService(); var perLocaleTotal = new Dictionary(); int totalSent = 0; foreach (string tableName in options.Tables) { TranslationsHelper.RefreshAllStringsTranslation(); StringTableCollection tableCollection = LocalizationEditorSettings.GetStringTableCollection(tableName); progress.Report(new OperationProgress(string.Empty)); progress.Report(new OperationProgress( LocalizationHelper.GetLocalStringByKey(LocalKeys.send_translations_str, tableName))); var perLocaleTable = new Dictionary(); foreach (LocalizationTableCollection.Row row in tableCollection.GetRowEnumerator()) { string context = tableCollection.GetSharedEntryComments(row.KeyEntry.Id); ExportDataHelper.UpdateTranslationFromStringsTableRow( row, tableName, context, out IReadOnlyList localesWithTranslation); foreach (string locale in localesWithTranslation) { perLocaleTable[locale] = perLocaleTable.GetValueOrDefault(locale) + 1; } } string fileName = $"{tableName}.csv"; string filepath = $"{FolderHelper.resourcesDirectory}/{fileName}"; CreateFileHelper.SaveTranslationsToFile(filepath, tableName, withTranslations: true); var failedLocales = new HashSet(); try { List existingFiles = await sourceFilesService.ListSourceFiles(rootDirId, cancellationToken); FileData existing = existingFiles.FirstOrDefault(data => data.name == fileName); if (existing is null) { // The source file was never pushed - create it with translations included. await StorageHelper.UploadSourceFileAsync( fileName, fileName, filepath, rootDirId, includeTranslationsInFile: true, cancellationToken); } else { StorageData storage = await storageService.CreateStorage(fileName, filepath, cancellationToken); List languages = SettingsHelper.GetLanguageScheme(); for (var offset = 0; offset < languages.Count; offset += MaxConcurrentUploads) { IEnumerable batch = languages.Skip(offset).Take(MaxConcurrentUploads); await Task.WhenAll(batch.Select(async language => { try { await translationsService.UploadTranslation( new UpdateTranslationQueryData(existing.id, storage.id, options.AutoApprove), language.langType, cancellationToken); } catch (CrowdinApiException ex) { failedLocales.Add(language.langType); Debug.LogException(ex); progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByException(ex))); } })); } } } catch (CrowdinApiException ex) { // A table-level API error (e.g. a name conflict when creating the source file) // must not abort the remaining tables, same as PushStrings does. Debug.LogException(ex); progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByException(ex))); continue; } finally { RemoveFileHelper.RemoveFile(filepath); } foreach ((string locale, int count) in perLocaleTable) { if (failedLocales.Contains(locale)) { continue; } progress.Report(new OperationProgress(LocalizationHelper.GetLocalStringByKey( LocalKeys.finish_upload_transl_table, locale, count.ToString()))); perLocaleTotal[locale] = perLocaleTotal.GetValueOrDefault(locale) + count; totalSent += count; } } return new PushTranslationsResult(totalSent, perLocaleTotal); } } }