Merge branch 'release/v2.6.0' into feature/regional-logo

This commit is contained in:
Alexey Safronov 2024-07-19 16:08:52 +04:00
commit 478366acfb
42 changed files with 578 additions and 387 deletions

View File

@ -29,14 +29,14 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Frontend.Tests;
using Frontend.Tests.Models;
using System.Text.RegularExpressions;
using Frontend.Tests;
using Frontend.Tests.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -47,7 +47,7 @@ using WeCantSpell.Hunspell;
namespace Frontend.Tests;
public class LocalesTest
{
{
public static string BasePath
{
get
@ -56,18 +56,18 @@ public class LocalesTest
}
}
public static bool Save
{
get
{
bool save;
if (bool.TryParse(Environment.GetEnvironmentVariable("SAVE"), out save))
{
return save;
}
return false;
}
public static bool Save
{
get
{
bool save;
if (bool.TryParse(Environment.GetEnvironmentVariable("SAVE"), out save))
{
return save;
}
return false;
}
}
public List<string> Workspaces { get; set; }
@ -77,13 +77,14 @@ public class LocalesTest
public List<KeyValuePair<string, string>> NotTranslatedToasts { get; set; }
public List<KeyValuePair<string, string>> NotTranslatedProps { get; set; }
public List<LanguageItem> CommonTranslations { get; set; }
public List<ParseJsonError> ParseJsonErrors { get; set; }
public static string ConvertPathToOS { get; private set; }
public List<string> ForbiddenElements { get { return new List<string>() { "ONLYOFFICE", "DOCSPACE" }; } }
//public List<JsonEncodingError> WrongEncodingJsonErrors { get; set; }
public List<ParseJsonError> ParseJsonErrors { get; set; }
public static string ConvertPathToOS { get; private set; }
public List<string> ForbiddenElements { get { return new List<string>() { "ONLYOFFICE", "DOCSPACE" }; } }
public List<string> SkipForbiddenKeys = new List<string> { "OrganizationName", "ProductName", "ProductEditorsName" };
//public List<JsonEncodingError> WrongEncodingJsonErrors { get; set; }
private static readonly string _md5ExcludesPath = Path.GetFullPath(Utils.ConvertPathToOS("../../../md5-excludes.json"));
private static readonly string _spellCheckCommonExcludesPath = Path.GetFullPath(Utils.ConvertPathToOS("../../../spellcheck-excludes-common.json"));
private static readonly string _spellCheckExcludesPath = Path.GetFullPath(Utils.ConvertPathToOS("../../../spellcheck-excludes.json"));
@ -104,7 +105,7 @@ public class LocalesTest
[OneTimeSetUp]
public void Setup()
{
{
ParseJsonErrors = new List<ParseJsonError>();
//WrongEncodingJsonErrors = new List<JsonEncodingError>();
@ -357,14 +358,14 @@ public class LocalesTest
TestContext.Progress.WriteLine($"Found CommonTranslations = {CommonTranslations.Count()}. First path is '{CommonTranslations.FirstOrDefault()?.Path}'");
TestContext.Progress.WriteLine($"Found Md5Excludes = {Md5Excludes.Count} Path to file '{_md5ExcludesPath}'");
TestContext.Progress.WriteLine($"Found SpellCheckCommonExcludes = {SpellCheckCommonExcludes.Count} Path to file '{_spellCheckCommonExcludesPath}'");
TestContext.Progress.WriteLine($"Save spell check excludes = {Save} Path to file '{_spellCheckExcludesPath}'");
}
TestContext.Progress.WriteLine($"Found Md5Excludes = {Md5Excludes.Count} Path to file '{_md5ExcludesPath}'");
TestContext.Progress.WriteLine($"Found SpellCheckCommonExcludes = {SpellCheckCommonExcludes.Count} Path to file '{_spellCheckCommonExcludesPath}'");
TestContext.Progress.WriteLine($"Save spell check excludes = {Save} Path to file '{_spellCheckExcludesPath}'");
}
[Test, Order(1)]
[Category("Locales")]
public void LanguageTranslatedPercentTest()
@ -499,7 +500,7 @@ public class LocalesTest
.ToList();
Assert.AreEqual(0, duplicates.Count, string.Join(", ", duplicates.Select(d => JObject.FromObject(d).ToString())));
}
}
[Test, Order(6)]
[Category("Locales")]
@ -514,10 +515,10 @@ public class LocalesTest
.Where(f => !f.Path.Contains("Banner.js")) // skip Banner.js (translations from firebase)
.SelectMany(j => j.TranslationKeys)
.Select(k => k.Substring(k.IndexOf(":") + 1))
.Distinct();
//var foo = JavaScriptFiles
// .Where(f => !f.Path.Contains("Banner.js"))
.Distinct();
//var foo = JavaScriptFiles
// .Where(f => !f.Path.Contains("Banner.js"))
// .Where(t => t.TranslationKeys.Any(k => k == "foo")).FirstOrDefault();
var notFoundJsKeys = allJsTranslationKeys.Except(allEnKeys);
@ -525,8 +526,8 @@ public class LocalesTest
Assert.AreEqual(0, notFoundJsKeys.Count(),
"Some i18n-keys are not exist in translations in 'en' language: Keys:\r\n{0}",
string.Join("\r\n", notFoundJsKeys));
}
}
[Test, Order(7)]
[Category("Locales")]
public void DublicatesFilesByMD5HashTest()
@ -541,8 +542,8 @@ public class LocalesTest
.ToList();
Assert.AreEqual(0, duplicatesByMD5.Count, "Dublicates by MD5 hash:\r\n" + string.Join("\r\n", duplicatesByMD5.Select(d => $"\r\nMD5='{d.Key}':\r\n{string.Join("\r\n", d.Paths.Select(p => p))}'")));
}
}
[Test, Order(8)]
[Category("Locales")]
public void UselessTranslationKeysTest()
@ -627,8 +628,8 @@ public class LocalesTest
{
Language = g.Key,
TranslationsWithVariables = g.ToList()
.SelectMany(t => t.Translations.Select(k => new TranslationItem($"{t.FileName}:{k.Key}", k.Value)))
//.Where(k => k.Value.IndexOf("{{") != -1)
.SelectMany(t => t.Translations.Select(k => new TranslationItem($"{t.FileName}:{k.Key}", k.Value)))
//.Where(k => k.Value.IndexOf("{{") != -1)
.Select(t => new
{
t.Key,
@ -654,12 +655,12 @@ public class LocalesTest
var i = 0;
var errorsCount = 0;
foreach (var enKeyWithVariables in enWithVariables)
{
foreach (var lng in otherLanguagesWithVariables)
{
var lngKey = lng.TranslationsWithVariables
.Where(t => t.Key == enKeyWithVariables.Key)
foreach (var enKeyWithVariables in enWithVariables)
{
foreach (var lng in otherLanguagesWithVariables)
{
var lngKey = lng.TranslationsWithVariables
.Where(t => t.Key == enKeyWithVariables.Key)
.FirstOrDefault();
if (lngKey == null)
@ -671,8 +672,8 @@ public class LocalesTest
}
if (enKeyWithVariables.Variables.Count != lngKey.Variables.Count)
{
// wrong
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{lngKey.Key}' has less variables then 'en' language have " +
$"(en={enKeyWithVariables.Variables.Count}|{lng.Language}={lngKey.Variables.Count})\r\n" +
$"'en': '{enKeyWithVariables.Value}'\r\n'{lng.Language}': '{lngKey.Value}'\r\n\r\n";
@ -680,10 +681,10 @@ public class LocalesTest
}
if (!lngKey.Variables.All(v => enKeyWithVariables.Variables.Contains(v)))
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{lngKey.Key}' has not equals variables of 'en' language have \r\n" +
$"'{enKeyWithVariables.Value}' Variables=[{string.Join(",", enKeyWithVariables.Variables)}]\r\n" +
{
// wrong
message += $"{++i}. lng='{lng.Language}' key='{lngKey.Key}' has not equals variables of 'en' language have \r\n" +
$"'{enKeyWithVariables.Value}' Variables=[{string.Join(",", enKeyWithVariables.Variables)}]\r\n" +
$"'{lngKey.Value}' Variables=[{string.Join(",", lngKey.Variables)}]\r\n\r\n";
errorsCount++;
}
@ -708,8 +709,8 @@ public class LocalesTest
{
Language = g.Key,
TranslationsWithTags = g.ToList()
.SelectMany(t => t.Translations)
//.Where(k => k.Value.IndexOf("<") != -1)
.SelectMany(t => t.Translations)
//.Where(k => k.Value.IndexOf("<") != -1)
.Select(t => new
{
t.Key,
@ -735,22 +736,22 @@ public class LocalesTest
var i = 0;
var errorsCount = 0;
foreach (var enKeyWithTags in enWithTags)
{
foreach (var lng in otherLanguagesWithTags)
{
var lngKey = lng.TranslationsWithTags
.Where(t => t.Key == enKeyWithTags.Key)
.FirstOrDefault();
if (lngKey == null)
{
foreach (var enKeyWithTags in enWithTags)
{
foreach (var lng in otherLanguagesWithTags)
{
var lngKey = lng.TranslationsWithTags
.Where(t => t.Key == enKeyWithTags.Key)
.FirstOrDefault();
if (lngKey == null)
{
// wrong
//message += $"{++i}. lng='{lng.Language}' key='{enKeyWithTags.Key}' not found\r\n\r\n";
//errorsCount++;
continue;
}
continue;
}
if (enKeyWithTags.Tags.Count != lngKey.Tags.Count)
{
// wrong
@ -758,8 +759,8 @@ public class LocalesTest
$"(en={enKeyWithTags.Tags.Count}|{lng.Language}={lngKey.Tags.Count})\r\n" +
$"'en': '{enKeyWithTags.Value}'\r\n'{lng.Language}': '{lngKey.Value}'\r\n\r\n";
errorsCount++;
}
}
if (!lngKey.Tags.All(v => enKeyWithTags.Tags.Contains(v)))
{
// wrong
@ -767,9 +768,9 @@ public class LocalesTest
$"'{enKeyWithTags.Value}' Tags=[{string.Join(",", enKeyWithTags.Tags)}]\r\n" +
$"'{lngKey.Value}' Tags=[{string.Join(",", lngKey.Tags)}]\r\n\r\n";
errorsCount++;
}
}
}
}
}
/*foreach (var lng in otherLanguagesWithTags)
@ -809,19 +810,18 @@ public class LocalesTest
}*/
Assert.AreEqual(0, errorsCount, message);
}
}
[Test, Order(13)]
[Category("Locales")]
public void ForbiddenValueElementsTest()
{
var message = $"Next keys have forbidden values `{string.Join(",", ForbiddenElements)}`:\r\n\r\n";
var message = $"Next keys have forbidden values `{string.Join(",", ForbiddenElements)}`:\r\n\r\n";
var exists = false;
var i = 0;
var i = 0;
foreach (var module in ModuleFolders)
{
if (module.AvailableLanguages == null)
@ -844,38 +844,41 @@ public class LocalesTest
message += string.Join("\r\n", keys) + "\r\n\r\n";
}
}
}
foreach (var lng in CommonTranslations)
{
var translationItems = lng.Translations.Where(f => ForbiddenElements.Any(elem => f.Value.ToUpper().Contains(elem))).ToList();
if (!translationItems.Any())
continue;
exists = true;
message += $"{++i}. Language '{lng.Language}' (Count: {translationItems.Count}). Path '{lng.Path}' " +
$"Keys:\r\n\r\n";
var keys = translationItems.Select(t => t.Key).ToList();
var translationItems = lng.Translations
.Where(elem => !SkipForbiddenKeys.Exists(k => k == elem.Key))
.Where(f => ForbiddenElements.Any(elem => f.Value.ToUpper().Contains(elem)))
.ToList();
if (!translationItems.Any())
continue;
exists = true;
message += $"{++i}. Language '{lng.Language}' (Count: {translationItems.Count}). Path '{lng.Path}' " +
$"Keys:\r\n\r\n";
var keys = translationItems.Select(t => t.Key).ToList();
message += string.Join("\r\n", keys) + "\r\n\r\n";
}
Assert.AreEqual(false, exists, message);
}
}
[Test, Order(14)]
[Category("Locales")]
[Category("Locales")]
public void ForbiddenKeysElementsTest()
{
var message = $"Next keys have forbidden elements in names `{string.Join(",", ForbiddenElements)}`:\r\n\r\n";
var message = $"Next keys have forbidden elements in names `{string.Join(",", ForbiddenElements)}`:\r\n\r\n";
var exists = false;
var i = 0;
var i = 0;
foreach (var module in ModuleFolders)
{
if (module.AvailableLanguages == null)
@ -883,9 +886,8 @@ public class LocalesTest
foreach (var lng in module.AvailableLanguages)
{
var translationItems = lng.Translations.Where(f => ForbiddenElements.Any(elem => f.Key.ToUpper().Contains(elem))).ToList();
var translationItems = lng.Translations.Where(f => ForbiddenElements.Any(elem => f.Key.ToUpper().Contains(elem))).ToList();
if (!translationItems.Any())
continue;
@ -899,28 +901,31 @@ public class LocalesTest
message += string.Join("\r\n", keys) + "\r\n\r\n";
}
}
}
foreach (var lng in CommonTranslations)
{
var translationItems = lng.Translations.Where(f => ForbiddenElements.Any(elem => f.Key.ToUpper().Contains(elem))).ToList();
if (!translationItems.Any())
continue;
exists = true;
message += $"{++i}. Language '{lng.Language}' (Count: {translationItems.Count}). Path '{lng.Path}' " +
$"Keys:\r\n\r\n";
var keys = translationItems.Select(t => t.Key).ToList();
{
var translationItems = lng.Translations
.Where(elem => !SkipForbiddenKeys.Exists(k => k == elem.Key))
.Where(f => ForbiddenElements.Any(elem => f.Key.ToUpper().Contains(elem)))
.ToList();
if (!translationItems.Any())
continue;
exists = true;
message += $"{++i}. Language '{lng.Language}' (Count: {translationItems.Count}). Path '{lng.Path}' " +
$"Keys:\r\n\r\n";
var keys = translationItems.Select(t => t.Key).ToList();
message += string.Join("\r\n", keys) + "\r\n\r\n";
}
Assert.AreEqual(false, exists, message);
}
}
[Test, Order(15)]
[Category("Locales")]
public void EmptyValueKeysTest()
@ -1023,8 +1028,8 @@ public class LocalesTest
}
Assert.AreEqual(false, exists, message);
}
}
[Test, Order(16)]
[Category("Locales")]
public void NotTranslatedKeysTest()
@ -1070,8 +1075,8 @@ public class LocalesTest
}
Assert.AreEqual(false, exists, message);
}
}
[Test, Order(17)]
[Category("Locales")]
public void NotTranslatedCommonKeysTest()
@ -1113,7 +1118,7 @@ public class LocalesTest
[Test, Order(18)]
[Category("Locales")]
public void NotAllLanguageTranslatedTest()
{
{
var groupedByLng = TranslationFiles
.GroupBy(t => t.Language)
.Select(grp => new { Lng = grp.Key, Count = grp.Count(), Files = grp.ToList() })
@ -1165,8 +1170,8 @@ public class LocalesTest
}
Assert.AreEqual(0, incompleteList.Count, message);
}
}
[Test, Order(19)]
[Category("SpellCheck")]
public void SpellCheckTest()
@ -1207,43 +1212,43 @@ public class LocalesTest
if (result.HasProblems)
{
var incorrectWords = result.SpellIssues
.Where(t => !SpellCheckCommonExcludes
.Exists(e => e.Equals(t.Word, StringComparison.InvariantCultureIgnoreCase)))
.Select(issue => $"'{issue.Word}' " +
var incorrectWords = result.SpellIssues
.Where(t => !SpellCheckCommonExcludes
.Exists(e => e.Equals(t.Word, StringComparison.InvariantCultureIgnoreCase)))
.Select(issue => $"'{issue.Word}' " +
$"Suggestion: '{issue.Suggestions.FirstOrDefault()}'")
.ToList();
if (!incorrectWords.Any())
continue;
message += $"{++i}. lng='{group.Language}' file='{g.FilePath}'\r\nkey='{item.Key}' " +
$"value='{item.Value}'\r\nIncorrect words:\r\n" +
message += $"{++i}. lng='{group.Language}' file='{g.FilePath}'\r\nkey='{item.Key}' " +
$"value='{item.Value}'\r\nIncorrect words:\r\n" +
$"{string.Join("\r\n", incorrectWords)}\r\n\r\n";
errorsCount++;
if (Save)
{
if (Save)
{
foreach (var word in result.SpellIssues
.Where(issue => issue.Suggestions.Any())
.Select(issue => issue.Word))
{
if (!spellCheckExclude.Excludes.Contains(word))
{
spellCheckExclude.Excludes.Add(word);
}
}
.Select(issue => issue.Word))
{
if (!spellCheckExclude.Excludes.Contains(word))
{
spellCheckExclude.Excludes.Add(word);
}
}
}
}
}
}
}
if (Save)
{
spellCheckExclude.Excludes.Sort();
list.Add(spellCheckExclude);
if (Save)
{
spellCheckExclude.Excludes.Sort();
list.Add(spellCheckExclude);
}
}
catch (NotSupportedException)
@ -1253,135 +1258,135 @@ public class LocalesTest
}
}
if (Save)
{
string json = JsonConvert.SerializeObject(list, Formatting.Indented);
File.WriteAllText(_spellCheckExcludesPath, json, Encoding.UTF8);
TestContext.Progress.WriteLine($"File spellcheck-excludes.json has been saved to '{_spellCheckExcludesPath}'");
if (Save)
{
string json = JsonConvert.SerializeObject(list, Formatting.Indented);
File.WriteAllText(_spellCheckExcludesPath, json, Encoding.UTF8);
TestContext.Progress.WriteLine($"File spellcheck-excludes.json has been saved to '{_spellCheckExcludesPath}'");
}
Assert.AreEqual(0, errorsCount, message);
}
/* [Test, Order(17)]
[Category("Locales")]
public void UselessModuleTranslationKeysTest()
{
var notFoundi18nKeys = new List<KeyValuePair<string, List<string>>>();
var message = $"Some i18n-keys are not found in Module or Common translations: \r\nKeys: \r\n\r\n";
var index = 0;
for (int i = 0; i < ModuleFolders.Count; i++)
{
var module = ModuleFolders[i];
if (module.AppliedJsTranslationKeys == null && module.AvailableLanguages != null)
{
message += $"{++index}. 'ANY LANGUAGES' '{module.Path}' NOT USED\r\n";
var list = module.AvailableLanguages
.SelectMany(l => l.Translations.Select(t => t.Key).ToList())
.ToList();
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>("ANY LANGUAGES", list));
continue;
}
var exepts = new List<string> { "Error", "Done", "Warning", "Alert", "Info" };
var notCommonKeys = module.AppliedJsTranslationKeys
.Except(exepts)
.Where(k => !k.StartsWith("Common:"))
.OrderBy(t => t)
.ToList();
var onlyCommonKeys = module.AppliedJsTranslationKeys
.Except(notCommonKeys)
.Select(k => k.Replace("Common:", ""))
.OrderBy(t => t)
.ToList();
notCommonKeys = notCommonKeys.Select(k => k.Substring(k.IndexOf(":") + 1)).ToList();
if (onlyCommonKeys.Any())
{
foreach (var lng in CommonTranslations)
{
var list = onlyCommonKeys
.Except(lng.Translations.Select(t => t.Key))
.ToList();
if (!list.Any())
continue;
message += $"{++index}. '{lng.Language}' '{module.Path}' \r\n {string.Join("\r\n", list)} \r\n";
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>(lng.Language, list));
}
}
if (module.AvailableLanguages == null)
{
if (notCommonKeys.Any())
{
var commonEnKeys = CommonTranslations.First(c => c.Language == "en").Translations.Select(t => t.Key).ToList();
var list = notCommonKeys
.Except(commonEnKeys.Select(k => k))
.ToList();
if (list.Any())
{
message += $"{++index}. 'ANY LANGUAGES' '{module.Path}' \r\n {string.Join("\r\n", list)} \r\n";
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>("ANY LANGUAGES", list));
}
}
continue;
}
foreach (var lng in module.AvailableLanguages)
{
var list = lng.Translations
.Select(t => t.Key)
.Except(notCommonKeys)
.ToList();
if (!list.Any())
continue;
message += $"{++index}. '{lng.Language}' '{module.Path}' \r\n {string.Join("\r\n", list)} \r\n";
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>(lng.Language, list));
}
}
Assert.AreEqual(0, notFoundi18nKeys.Count, message);
}*/
//[Test]
//[Category("Locales")]
//public void TranslationsEncodingTest()
//{
// /*//Convert to UTF-8
// foreach (var issue in WrongEncodingJsonErrors)
// {
// if (issue.DetectionDetail.Encoding == null)
// continue;
// ConvertFileEncoding(issue.Path, issue.Path, issue.DetectionDetail.Encoding, Encoding.UTF8);
// }*/
// var message = $"Next files have encoding issues:\r\n\r\n";
// Assert.AreEqual(0, WrongEncodingJsonErrors.Count,
// message + string.Join("\r\n", WrongEncodingJsonErrors
// .Select(e => $"File path = '{e.Path}' potentially wrong file encoding: {e.DetectionDetail.EncodingName}")));
//}
}
/* [Test, Order(17)]
[Category("Locales")]
public void UselessModuleTranslationKeysTest()
{
var notFoundi18nKeys = new List<KeyValuePair<string, List<string>>>();
var message = $"Some i18n-keys are not found in Module or Common translations: \r\nKeys: \r\n\r\n";
var index = 0;
for (int i = 0; i < ModuleFolders.Count; i++)
{
var module = ModuleFolders[i];
if (module.AppliedJsTranslationKeys == null && module.AvailableLanguages != null)
{
message += $"{++index}. 'ANY LANGUAGES' '{module.Path}' NOT USED\r\n";
var list = module.AvailableLanguages
.SelectMany(l => l.Translations.Select(t => t.Key).ToList())
.ToList();
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>("ANY LANGUAGES", list));
continue;
}
var exepts = new List<string> { "Error", "Done", "Warning", "Alert", "Info" };
var notCommonKeys = module.AppliedJsTranslationKeys
.Except(exepts)
.Where(k => !k.StartsWith("Common:"))
.OrderBy(t => t)
.ToList();
var onlyCommonKeys = module.AppliedJsTranslationKeys
.Except(notCommonKeys)
.Select(k => k.Replace("Common:", ""))
.OrderBy(t => t)
.ToList();
notCommonKeys = notCommonKeys.Select(k => k.Substring(k.IndexOf(":") + 1)).ToList();
if (onlyCommonKeys.Any())
{
foreach (var lng in CommonTranslations)
{
var list = onlyCommonKeys
.Except(lng.Translations.Select(t => t.Key))
.ToList();
if (!list.Any())
continue;
message += $"{++index}. '{lng.Language}' '{module.Path}' \r\n {string.Join("\r\n", list)} \r\n";
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>(lng.Language, list));
}
}
if (module.AvailableLanguages == null)
{
if (notCommonKeys.Any())
{
var commonEnKeys = CommonTranslations.First(c => c.Language == "en").Translations.Select(t => t.Key).ToList();
var list = notCommonKeys
.Except(commonEnKeys.Select(k => k))
.ToList();
if (list.Any())
{
message += $"{++index}. 'ANY LANGUAGES' '{module.Path}' \r\n {string.Join("\r\n", list)} \r\n";
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>("ANY LANGUAGES", list));
}
}
continue;
}
foreach (var lng in module.AvailableLanguages)
{
var list = lng.Translations
.Select(t => t.Key)
.Except(notCommonKeys)
.ToList();
if (!list.Any())
continue;
message += $"{++index}. '{lng.Language}' '{module.Path}' \r\n {string.Join("\r\n", list)} \r\n";
notFoundi18nKeys.Add(new KeyValuePair<string, List<string>>(lng.Language, list));
}
}
Assert.AreEqual(0, notFoundi18nKeys.Count, message);
}*/
//[Test]
//[Category("Locales")]
//public void TranslationsEncodingTest()
//{
// /*//Convert to UTF-8
// foreach (var issue in WrongEncodingJsonErrors)
// {
// if (issue.DetectionDetail.Encoding == null)
// continue;
// ConvertFileEncoding(issue.Path, issue.Path, issue.DetectionDetail.Encoding, Encoding.UTF8);
// }*/
// var message = $"Next files have encoding issues:\r\n\r\n";
// Assert.AreEqual(0, WrongEncodingJsonErrors.Count,
// message + string.Join("\r\n", WrongEncodingJsonErrors
// .Select(e => $"File path = '{e.Path}' potentially wrong file encoding: {e.DetectionDetail.EncodingName}")));
//}
/*[Test]
public void TempTest()
{
@ -1468,8 +1473,8 @@ public class LocalesTest
UpdateKeys(lng.Path, newKeys);
}
}*/
}*/
public static void SaveNotFoundKeys(string pathToJson, List<string> newKeys)
{
if (!File.Exists(pathToJson))
@ -1488,8 +1493,8 @@ public class LocalesTest
var sortedJsonString = JsonConvert.SerializeObject(result, Formatting.Indented);
File.WriteAllText(pathToJson, sortedJsonString, Encoding.UTF8);
}
}
public static void SaveNotFoundLanguage(string existJsonPath, string notExistJsonPath)
{
if (!File.Exists(existJsonPath) || File.Exists(notExistJsonPath))
@ -1513,8 +1518,8 @@ public class LocalesTest
Directory.CreateDirectory(fullPathOnly);
File.WriteAllText(notExistJsonPath, sortedJsonString, Encoding.UTF8);
}
}
public static void UpdateKeys(string pathToJson, List<TranslationItem> newKeys)
{
if (!File.Exists(pathToJson) || !newKeys.Any())
@ -1608,8 +1613,8 @@ public class LocalesTest
var sortedJsonString = JsonConvert.SerializeObject(result, Formatting.Indented);
File.WriteAllText(pathToJson, sortedJsonString, Encoding.UTF8);
}
}
public static Tuple<string, string> getPaths(string language)
{
const string dictionariesPath = @"../../../dictionaries";
@ -1630,84 +1635,84 @@ public class LocalesTest
var affPath = Utils.ConvertPathToOS(Path.Combine(path, language, $"{language}.aff"));
return new Tuple<string, string>(dicPath, affPath);
}
}
public static void ConvertFileEncoding(string sourcePath, string destPath, Encoding sourceEncoding, Encoding destEncoding)
{
// If the destination's parent doesn't exist, create it.
var parent = Path.GetDirectoryName(Path.GetFullPath(destPath));
if (!Directory.Exists(parent))
{
Directory.CreateDirectory(parent);
}
// If the source and destination encodings are the same, just copy the file.
if (sourceEncoding == destEncoding)
{
File.Copy(sourcePath, destPath, true);
return;
}
// Convert the file.
string tempName = null;
try
{
tempName = Path.GetTempFileName();
using (StreamReader sr = new StreamReader(sourcePath, sourceEncoding, false))
{
using (StreamWriter sw = new StreamWriter(tempName, false, destEncoding))
{
int charsRead;
char[] buffer = new char[128 * 1024];
while ((charsRead = sr.ReadBlock(buffer, 0, buffer.Length)) > 0)
{
sw.Write(buffer, 0, charsRead);
}
}
}
File.Delete(destPath);
File.Move(tempName, destPath);
}
finally
{
File.Delete(tempName);
}
public static void ConvertFileEncoding(string sourcePath, string destPath, Encoding sourceEncoding, Encoding destEncoding)
{
// If the destination's parent doesn't exist, create it.
var parent = Path.GetDirectoryName(Path.GetFullPath(destPath));
if (!Directory.Exists(parent))
{
Directory.CreateDirectory(parent);
}
// If the source and destination encodings are the same, just copy the file.
if (sourceEncoding == destEncoding)
{
File.Copy(sourcePath, destPath, true);
return;
}
// Convert the file.
string tempName = null;
try
{
tempName = Path.GetTempFileName();
using (StreamReader sr = new StreamReader(sourcePath, sourceEncoding, false))
{
using (StreamWriter sw = new StreamWriter(tempName, false, destEncoding))
{
int charsRead;
char[] buffer = new char[128 * 1024];
while ((charsRead = sr.ReadBlock(buffer, 0, buffer.Length)) > 0)
{
sw.Write(buffer, 0, charsRead);
}
}
}
File.Delete(destPath);
File.Move(tempName, destPath);
}
finally
{
File.Delete(tempName);
}
}
/* [Test]
public void MoveKeysToCommon()
{
var findKeys = new List<string> {
public void MoveKeysToCommon()
{
var findKeys = new List<string> {
"SharingPanel:CustomFilter",
"SharingPanel:ReadOnly",
"SharingPanel:DenyAccess",
"SharingPanel:Comment",
"SharingPanel:ShareVia"
};
//var findKeys = new List<string> {
// "Translations:DownloadApps",
//};
foreach (var findKey in findKeys)
{
var splitted = findKey.Split(":");
var file = splitted[0];
var key = splitted[1];
var tFiles = TranslationFiles.Where(t => t.FileName.Equals($"{file}.json", StringComparison.InvariantCultureIgnoreCase));
foreach (var tFile in tFiles)
{
var tKeys = tFile.Translations.Where(t => t.Key == key);
foreach (var tKey in tKeys)
{
var commonPath = Utils.ConvertPathToOS(Path.Combine(BasePath, "public/locales", tFile.Language, "Common.json"));
AddKeyValue(commonPath, tKey.Key, tKey.Value);
RemoveKey(tFile.FilePath, key);
}
}
}
"SharingPanel:ShareVia"
};
//var findKeys = new List<string> {
// "Translations:DownloadApps",
//};
foreach (var findKey in findKeys)
{
var splitted = findKey.Split(":");
var file = splitted[0];
var key = splitted[1];
var tFiles = TranslationFiles.Where(t => t.FileName.Equals($"{file}.json", StringComparison.InvariantCultureIgnoreCase));
foreach (var tFile in tFiles)
{
var tKeys = tFile.Translations.Where(t => t.Key == key);
foreach (var tKey in tKeys)
{
var commonPath = Utils.ConvertPathToOS(Path.Combine(BasePath, "public/locales", tFile.Language, "Common.json"));
AddKeyValue(commonPath, tKey.Key, tKey.Value);
RemoveKey(tFile.FilePath, key);
}
}
}
} */
}

View File

@ -43054,6 +43054,138 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>ProductEditorsName</name>
<description/>
<comment/>
<default_text/>
<translations>
<translation>
<language>ar-SA</language>
<approved>false</approved>
</translation>
<translation>
<language>az-Latn-AZ</language>
<approved>false</approved>
</translation>
<translation>
<language>bg-BG</language>
<approved>false</approved>
</translation>
<translation>
<language>cs-CZ</language>
<approved>false</approved>
</translation>
<translation>
<language>de-DE</language>
<approved>false</approved>
</translation>
<translation>
<language>el-GR</language>
<approved>false</approved>
</translation>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-ES</language>
<approved>false</approved>
</translation>
<translation>
<language>fi-FI</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-FR</language>
<approved>false</approved>
</translation>
<translation>
<language>hy-AM</language>
<approved>false</approved>
</translation>
<translation>
<language>it-IT</language>
<approved>false</approved>
</translation>
<translation>
<language>ja-JP</language>
<approved>false</approved>
</translation>
<translation>
<language>ko-KR</language>
<approved>false</approved>
</translation>
<translation>
<language>lo-LA</language>
<approved>false</approved>
</translation>
<translation>
<language>lv-LV</language>
<approved>false</approved>
</translation>
<translation>
<language>nl-NL</language>
<approved>false</approved>
</translation>
<translation>
<language>pl-PL</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-BR</language>
<approved>false</approved>
</translation>
<translation>
<language>pt-PT</language>
<approved>false</approved>
</translation>
<translation>
<language>ro-RO</language>
<approved>false</approved>
</translation>
<translation>
<language>ru-RU</language>
<approved>false</approved>
</translation>
<translation>
<language>si-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>sk-SK</language>
<approved>false</approved>
</translation>
<translation>
<language>sl-SI</language>
<approved>false</approved>
</translation>
<translation>
<language>sr-Cyrl-RS</language>
<approved>false</approved>
</translation>
<translation>
<language>sr-Latn-RS</language>
<approved>false</approved>
</translation>
<translation>
<language>tr-TR</language>
<approved>false</approved>
</translation>
<translation>
<language>uk-UA</language>
<approved>false</approved>
</translation>
<translation>
<language>vi-VN</language>
<approved>false</approved>
</translation>
<translation>
<language>zh-CN</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>ProductName</name>
<description/>

View File

@ -61,6 +61,7 @@ newInstance
},
ns: [
"Common",
"ArchiveDialog",
"InfoPanel",
"InviteDialog",

View File

@ -183,7 +183,8 @@ const AboutContent = (props) => {
target="_blank"
enableUserSelect
>
&nbsp;ONLYOFFICE Docs&nbsp;
&nbsp;{t("Common:OrganizationName")}{" "}
{t("Common:ProductEditorsName")}&nbsp;
</ColorTheme>
<Text className="row-el select-el" fontSize="13px" fontWeight="600">
v.

View File

@ -375,31 +375,29 @@ const SectionHeaderContent = (props) => {
/>
)}
<Headline type="content" truncate={true}>
<div className="settings-section_header">
<div className="header">
{isMobile() && isServicePage && (
<IconButton
iconName={ArrowPathReactSvgUrl}
size="17"
isFill={true}
onClick={onBackToParent}
className="arrow-button"
/>
)}
{t(header, { organizationName: t("Common:OrganizationName") })}
</div>
{isNeedPaidIcon ? (
<Badge
backgroundColor={theme.isBase ? "#EDC409" : "#A38A1A"}
label={t("Common:Paid")}
fontWeight="700"
className="settings-section_badge"
isPaidBadge={true}
/>
) : (
""
)}
</div>
{isMobile() && isServicePage && (
<IconButton
iconName={ArrowPathReactSvgUrl}
size="17"
isFill={true}
onClick={onBackToParent}
className="arrow-button"
/>
)}
{t(header, {
organizationName: t("Common:OrganizationName"),
})}
{isNeedPaidIcon ? (
<Badge
backgroundColor={theme.isBase ? "#EDC409" : "#A38A1A"}
label={t("Common:Paid")}
fontWeight="700"
className="settings-section_badge"
isPaidBadge={true}
/>
) : (
""
)}
</Headline>
<div className="tariff-bar">
<TariffBar />

View File

@ -183,7 +183,7 @@ const Appearance = (props) => {
useEffect(() => {
getSettings();
setDocumentTitle(t("Appearance"));
setDocumentTitle(t("Common:Appearance"));
}, []);
useEffect(() => {

View File

@ -110,7 +110,7 @@ const Customization = (props) => {
const isLoadedSetting = isLoaded && tReady;
useEffect(() => {
setDocumentTitle(t("Customization"));
setDocumentTitle(t("Settings:Customization"));
return () => {
resetIsInit();

View File

@ -34,6 +34,7 @@ import { RectangleSkeleton } from "@docspace/shared/skeletons";
import GithubLight from "PUBLIC_DIR/images/github.light.react.svg";
import GithubDark from "PUBLIC_DIR/images/github.dark.react.svg";
import { setDocumentTitle } from "SRC_DIR/helpers/utils";
import { StyledContainer } from "./StyledPluginSDK";
@ -48,6 +49,10 @@ const PluginSDK = ({
}) => {
const { t } = useTranslation(["WebPlugins", "VersionHistory", "Common"]);
React.useEffect(() => {
setDocumentTitle(t("WebPlugins:PluginSDK"));
}, []);
const isMobile = currentDeviceType === "mobile";
const icon = !theme.isBase ? <GithubLight /> : <GithubDark />;

View File

@ -38,6 +38,7 @@ import { toastr } from "@docspace/shared/components/toast";
import { SettingsDSConnectSkeleton } from "@docspace/shared/skeletons/settings";
import { DeviceType } from "@docspace/shared/enums";
import { SaveCancelButtons } from "@docspace/shared/components/save-cancel-buttons";
import { setDocumentTitle } from "SRC_DIR/helpers/utils";
const URL_REGEX = /^https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{1,256}\/?$/;
const DNS_PLACEHOLDER = `${window.location.protocol}//<docspace-dns-name>/`;
@ -69,6 +70,7 @@ const DocumentService = ({
const [initInternalUrl, setInitInternalUrl] = useState("");
useEffect(() => {
setDocumentTitle(t("DocumentService"));
setIsLoading(true);
getDocumentServiceLocation()
.then((result) => {

View File

@ -479,6 +479,8 @@ const FilterBlock = ({
};
const showFooter = isLoading ? false : isEqualFilter();
const showClearFilterBtn =
!isLoading && (selectedFilterValue.length > 0 || filterValues.length > 0);
const filterBlockComponent = (
<>
@ -535,7 +537,7 @@ const FilterBlock = ({
<Heading size={HeadingSize.medium} level={HeadingLevel.h1}>
{filterHeader}
</Heading>
{showFooter && (
{showClearFilterBtn && (
<IconButton
id="filter_search-options-clear"
iconName={ClearReactSvgUrl}

View File

@ -104,6 +104,20 @@ const StyledTag = styled.div<{
padding: 2px 0px;
width: 16px;
height: 16px;
${(props) =>
!props.theme.isBase &&
css`
svg {
path[fill] {
fill: #fff;
}
path[stroke] {
stroke: #fff;
}
`}
}
}
${(props) =>

View File

@ -310,6 +310,7 @@
"PreparationPortalTitle": "جاري استعادة البوابة",
"Preview": "معاينة ",
"Previous": "السابق",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "الملف الشخصي",
"Projects": "المشاريع",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Portalın bərpası davam edir",
"Preview": "Öncədən bax",
"Previous": "Əvvəlki ",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Layihələr",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Възстановяването на портала е в ход.",
"Preview": "Преглед",
"Previous": "Предишен",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Профил",
"Projects": "Проекти",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Probíhá obnova portálu",
"Preview": "Náhled",
"Previous": "Předchozí",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projekty",

View File

@ -310,6 +310,7 @@
"PreparationPortalTitle": "Wiederherstellung des Portals ist im Gange",
"Preview": "Vorschau",
"Previous": "Zurück",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projekte",

View File

@ -310,6 +310,7 @@
"PreparationPortalTitle": "Η αποκατάσταση της πύλης βρίσκεται σε εξέλιξη",
"Preview": "Προεπισκόπηση",
"Previous": "Προηγούμενο",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Προφίλ",
"Projects": "Έργα",

View File

@ -325,6 +325,7 @@
"PreparationPortalTitle": "Portal restoring is underway",
"Preview": "Preview",
"Previous": "Previous",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profile",
"Projects": "Projects",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Restauración de portal está en progreso",
"Preview": "Vista previa",
"Previous": "Anterior",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Perfil",
"Projects": "Proyectos",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Portaalin palautus on käynnissä.",
"Preview": "Esikatselu",
"Previous": "Edellinen",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profiili",
"Projects": "Projektit",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Restauration du portail est en cours.",
"Preview": "Aperçu",
"Previous": "Précédent",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projets",

View File

@ -310,6 +310,7 @@
"PreparationPortalTitle": "Պորտալի վերականգնումն ընթացքի մեջ է",
"Preview": "Նախադիտել",
"Previous": "Նախորդ",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Անձնական էջ",
"Projects": "Նախագծեր",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Ripristino portale in corso.",
"Preview": "Anteprima",
"Previous": "Indietro",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profilo",
"Projects": "Projects",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "ポータルの復元が実施中です。",
"Preview": "プレビュー",
"Previous": "前",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "プロフィール",
"Projects": "プロジェクト",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "포털 복원이 진행 중입니다",
"Preview": "미리 보기",
"Previous": "이전",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "프로필",
"Projects": "프로젝트",

View File

@ -245,6 +245,7 @@
"PreparationPortalTitle": "ກໍາລັງດໍາເນີນການກູ້ຄືນພອດທັອນ",
"Preview": "ເບິ່ງຕົວຢ່າງ",
"Previous": "ທີ່ຜ່ານມາ",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "ຂໍ້ມູນສ່ວນໂຕ",
"ProviderLoginError": "ອານຸຍາດຜິດພາດ",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Notiek portāla atjaunošana.",
"Preview": "Priekšskatīt",
"Previous": "Iepriekšējais",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profils",
"Projects": "Projekti",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Portaal herstel is bezig.",
"Preview": "Preview",
"Previous": "Vorige",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profiel",
"Projects": "Projecten",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Odtwarzanie portalu jest w toku.",
"Preview": "Podgląd",
"Previous": "Poprzednia",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projekty",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Restauração do portal está em andamento.",
"Preview": "Pré-visualizar",
"Previous": "Anterior",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Perfil",
"Projects": "Projetos",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "A restauração do Portal está em curso",
"Preview": "Pré-visualizar",
"Previous": "Anterior",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Perfil",
"Projects": "Projetos",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Restaurarea portalului este în curs de desfășurare",
"Preview": "Previzualizare",
"Previous": "Anterior",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projecte",

View File

@ -312,6 +312,7 @@
"PreparationPortalTitle": "Выполняется восстановление портала",
"Preview": "Просмотр",
"Previous": "Предыдущая",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Профиль",
"Projects": "Проекты",

View File

@ -306,6 +306,7 @@
"PreparationPortalTitle": "ද්වාරය ප්‍රත්‍යර්පණය වෙමින්",
"Preview": "පෙරදසුන",
"Previous": "කලින්",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "පැතිකඩ",
"Projects": "ව්‍යාපෘති",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Prebieha obnova portálu.",
"Preview": "Náhľad",
"Previous": "Predchádzajúci",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projekty",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Obnavljanje portala je v teku",
"Preview": "Predogled",
"Previous": "Prejšnji",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projekti",

View File

@ -315,6 +315,7 @@
"PreparationPortalTitle": "Обнова портала је у току",
"Preview": "Преглед",
"Previous": "Претходно",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Профил",
"Projects": "Пројекти",

View File

@ -315,6 +315,7 @@
"PreparationPortalTitle": "Obnova portala je u toku",
"Preview": "Pregled",
"Previous": "Prethodno",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projekti",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Portal geri yükleme yapılıyor.",
"Preview": "Önizleme",
"Previous": "Önceki",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Profil",
"Projects": "Projeler",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Портал відновлюється.",
"Preview": "Попередній перегляд",
"Previous": "Назад",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Профіль",
"Projects": "Проєкти",

View File

@ -311,6 +311,7 @@
"PreparationPortalTitle": "Đang tiến hành khôi phục cổng.",
"Preview": "Xem trước",
"Previous": "Trước",
"ProductEditorsName": "Docs",
"ProductName": "DocSpace",
"Profile": "Hồ sơ",
"Projects": "Kế hoạch",

View File

@ -304,6 +304,7 @@
"PreparationPortalTitle": "门户恢复正在进行中。",
"Preview": "预览",
"Previous": "上一个",
"ProductEditorsName": "文档",
"ProductName": "协作空间",
"Profile": "个人资料",
"Projects": "项目",