From bfead7dd8285ec8171d9f4ca47ccaa095a48d309 Mon Sep 17 00:00:00 2001 From: Matthias H Date: Fri, 4 Jun 2021 12:31:49 +0200 Subject: [PATCH 01/37] Fixed typo in components field type description (#719) --- backend/i18n/frontend_en.json | 2 +- backend/i18n/frontend_it.json | 2 +- backend/i18n/frontend_nl.json | 2 +- backend/i18n/source/frontend_en.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index a14b65bf0..319fe9bd3 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -761,7 +761,7 @@ "schemas.fieldTypes.assets.sizeMin": "Max Size", "schemas.fieldTypes.boolean.description": "Yes or no, true or false.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Default Mode", "schemas.fieldTypes.dateTime.description": "Events date, opening hours.", "schemas.fieldTypes.dateTime.rangeMax": "Max Value", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index c133c893d..ed5883010 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -761,7 +761,7 @@ "schemas.fieldTypes.assets.sizeMin": "Dimensione Max", "schemas.fieldTypes.boolean.description": "Si o no, vero o falso.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Modalità predefinita", "schemas.fieldTypes.dateTime.description": "Data degli eventi, orari di apertura.", "schemas.fieldTypes.dateTime.rangeMax": "Valore Max", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index b2a16b822..86a95187a 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -761,7 +761,7 @@ "schemas.fieldTypes.assets.sizeMin": "Max. grootte", "schemas.fieldTypes.boolean.description": "Ja of nee, waar of niet waar.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Standaardmodus", "schemas.fieldTypes.dateTime.description": "Datum van evenementen, openingstijden.", "schemas.fieldTypes.dateTime.rangeMax": "Max. waarde", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index a14b65bf0..319fe9bd3 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -761,7 +761,7 @@ "schemas.fieldTypes.assets.sizeMin": "Max Size", "schemas.fieldTypes.boolean.description": "Yes or no, true or false.", "schemas.fieldTypes.component.description": "Embed another schema to this content.", - "schemas.fieldTypes.components.description": "Embed another schemas to this content as array.", + "schemas.fieldTypes.components.description": "Embed other schemas to this content as array.", "schemas.fieldTypes.dateTime.defaultMode": "Default Mode", "schemas.fieldTypes.dateTime.description": "Events date, opening hours.", "schemas.fieldTypes.dateTime.rangeMax": "Max Value", From 79de8b69f79abcd2dc9243b768c2ee9ac1120d65 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 4 Jun 2021 15:33:25 +0200 Subject: [PATCH 02/37] Bugfixes (#720) * Just a few bugfixes. * Log errors. * Fix state when recreated. --- .github/workflows/dev.yml | 7 +++++++ .../Assets/DomainObject/AssetDomainObject.cs | 2 +- .../Commands/DomainObject.cs | 18 ++++++++++++++--- .../Commands/DomainObjectTests.cs | 6 ++++++ .../TestSuite.ApiTests/ContentUpdateTests.cs | 20 +++++++++++++++---- 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 2a55263bd..b2fafa835 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -95,6 +95,13 @@ jobs: options: --name test volumes: ${{ github.workspace }}:/src run: dotnet test /src/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj --filter Category!=NotAutomated + + - name: Dump docker logs on failure + if: failure() + uses: jwalton/gh-docker-logs@v1 + with: + images: 'squidex-tmp' + tail: '100' - name: Cleanup Test if: always() diff --git a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs index cacb45f38..1c6491944 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetDomainObject.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) diff --git a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs index 12b1e7b2e..13161c4ad 100644 --- a/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs +++ b/backend/src/Squidex.Infrastructure/Commands/DomainObject.cs @@ -217,6 +217,8 @@ namespace Squidex.Infrastructure.Commands { Guard.NotNull(handler, nameof(handler)); + var wasDeleted = IsDeleted(); + var previousSnapshot = Snapshot; var previousVersion = Version; try @@ -233,7 +235,7 @@ namespace Squidex.Infrastructure.Commands { await EnsureLoadedAsync(true); - if (IsDeleted()) + if (wasDeleted) { if (CanRecreate() && isCreation) { @@ -304,9 +306,19 @@ namespace Squidex.Infrastructure.Commands { var newVersion = Version + 1; - var snapshotNew = Apply(Snapshot, @event); + var snapshotOld = Snapshot; + + if (IsDeleted()) + { + snapshotOld = new T + { + Version = Version + }; + } + + var snapshotNew = Apply(snapshotOld, @event); - if (!ReferenceEquals(Snapshot, snapshotNew) || isLoading) + if (!ReferenceEquals(snapshotOld, snapshotNew) || isLoading) { snapshotNew.Version = newVersion; snapshots.Add(snapshotNew, newVersion, true); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs index b03fe0def..bc6d982db 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/Commands/DomainObjectTests.cs @@ -79,6 +79,7 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(CommandResult.Empty(id, 0, EtagVersion.Empty), result); Assert.Equal(0, sut.Version); + Assert.Equal(0, sut.Snapshot.Version); Assert.Empty(sut.GetUncomittedEvents()); AssertSnapshot(sut.Snapshot, 4, 0); @@ -116,6 +117,7 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(CommandResult.Empty(id, 2, 1), result); Assert.Equal(2, sut.Version); + Assert.Equal(2, sut.Snapshot.Version); Assert.Empty(sut.GetUncomittedEvents()); AssertSnapshot(sut.Snapshot, 4, 2); @@ -155,6 +157,7 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(CommandResult.Empty(id, 2, 1), result); Assert.Equal(2, sut.Version); + Assert.Equal(2, sut.Snapshot.Version); Assert.Empty(sut.GetUncomittedEvents()); AssertSnapshot(sut.Snapshot, 4, 2); @@ -192,6 +195,7 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(CommandResult.Empty(id, 1, 0), result); Assert.Equal(1, sut.Version); + Assert.Equal(1, sut.Snapshot.Version); Assert.Empty(sut.GetUncomittedEvents()); AssertSnapshot(sut.Snapshot, 8, 1); @@ -213,6 +217,7 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(CommandResult.Empty(id, 1, 0), result); Assert.Equal(1, sut.Version); + Assert.Equal(1, sut.Snapshot.Version); Assert.Empty(sut.GetUncomittedEvents()); AssertSnapshot(sut.Snapshot, 8, 1); @@ -345,6 +350,7 @@ namespace Squidex.Infrastructure.Commands Assert.Equal(CommandResult.Empty(id, 0, 0), result); Assert.Equal(0, sut.Version); + Assert.Equal(0, sut.Snapshot.Version); Assert.Empty(sut.GetUncomittedEvents()); AssertSnapshot(sut.Snapshot, 4, 0); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs index 9c2005dab..aefd2127b 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs @@ -575,17 +575,17 @@ namespace TestSuite.ApiTests public async Task Should_delete_content(bool permanent) { // STEP 1: Create a new item. - var content = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, true); + var content_1 = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, true); // STEP 2: Delete the item. - await _.Contents.DeleteAsync(content.Id, permanent); + await _.Contents.DeleteAsync(content_1.Id, permanent); // STEP 3: Retrieve all items and ensure that the deleted item does not exist. var updated = await _.Contents.GetAsync(); - Assert.DoesNotContain(updated.Items, x => x.Id == content.Id); + Assert.DoesNotContain(updated.Items, x => x.Id == content_1.Id); // STEP 4: Retrieve all deleted items and check if found. @@ -594,7 +594,7 @@ namespace TestSuite.ApiTests Filter = "isDeleted eq true" }, QueryContext.Default.Unpublished(true)); - Assert.Equal(!permanent, deleted.Items.Any(x => x.Id == content.Id)); + Assert.Equal(!permanent, deleted.Items.Any(x => x.Id == content_1.Id)); } [Theory] @@ -614,6 +614,12 @@ namespace TestSuite.ApiTests var content_2 = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, content_1.Id, true); Assert.Equal(Status.Published, content_2.Status); + + + // STEP 4: Check if we can find it again with a query. + var contents_4 = await _.Contents.GetAsync(new ContentQuery { Filter = $"id eq '{content_1.Id}'" }); + + Assert.NotNull(contents_4.Items.Find(x => x.Id == content_1.Id)); } [Theory] @@ -633,6 +639,12 @@ namespace TestSuite.ApiTests var content_2 = await _.Contents.UpsertAsync(content_1.Id, new TestEntityData { Number = 2 }, true); Assert.Equal(Status.Published, content_2.Status); + + + // STEP 4: Check if we can find it again with a query. + var contents_4 = await _.Contents.GetAsync(new ContentQuery { Filter = $"id eq '{content_1.Id}'" }); + + Assert.NotNull(contents_4.Items.Find(x => x.Id == content_1.Id)); } } } From 7436a26845ce00745d43cd79a2ad13c12c33c15c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 4 Jun 2021 16:42:46 +0200 Subject: [PATCH 03/37] Make tests work with cloud restriction. --- backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index ede84e790..2c03392f8 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -188,8 +188,7 @@ namespace TestSuite.ApiTests // Use role name with hash to test previous bug. var roleName = $"{Guid.NewGuid()}/1"; var roleClient = Guid.NewGuid().ToString(); - var roleContributor1 = "role1@squidex.io"; - var roleContributor2 = "role2@squidex.io"; + var roleContributor1 = "hello@squidex.io"; // STEP 1: Add role. var createRequest = new AddRoleDto { Name = roleName }; @@ -214,10 +213,11 @@ namespace TestSuite.ApiTests // STEP 3: Assign client and contributor. await _.Apps.PostClientAsync(_.AppName, new CreateClientDto { Id = roleClient }); + // Add client to role. await _.Apps.PutClientAsync(_.AppName, roleClient, new UpdateClientDto { Role = roleName }); + // Add contributor to role. await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor1, Role = roleName, Invite = true }); - await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor2, Role = roleName, Invite = true }); var roles_3 = await _.Apps.GetRolesAsync(_.AppName); var role_3 = roles_3.Items.Single(x => x.Name == roleName); @@ -239,10 +239,11 @@ namespace TestSuite.ApiTests // STEP 5: Remove after client and contributor removed. var fallbackRole = "Developer"; + // Remove client from role. await _.Apps.PutClientAsync(_.AppName, roleClient, new UpdateClientDto { Role = fallbackRole }); + // Remove contributor from role. await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor1, Role = fallbackRole }); - await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor2, Role = fallbackRole }); await _.Apps.DeleteRoleAsync(_.AppName, roleName); From 1ca54e0cec02f157226e0682cc79cfca9674cd0f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 4 Jun 2021 17:41:22 +0200 Subject: [PATCH 04/37] Fix tests. --- backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index 2c03392f8..afbdd91cf 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -224,7 +224,7 @@ namespace TestSuite.ApiTests // Should return role with correct number of users and clients. Assert.Equal(1, role_3.NumClients); - Assert.Equal(2, role_3.NumContributors); + Assert.Equal(1, role_3.NumContributors); // STEP 4: Try to delete role. From 428639e8d3e59e02a0ed3375b7062cb1936f60ed Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 4 Jun 2021 17:59:13 +0200 Subject: [PATCH 05/37] Add tests for workflows. --- .../TestSuite/TestSuite.ApiTests/AppTests.cs | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index afbdd91cf..edb481555 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -270,10 +270,10 @@ namespace TestSuite.ApiTests await _.Apps.PostLanguageAsync(appName, new AddLanguageDto { Language = "fr" }); var languages_1 = await _.Apps.GetLanguagesAsync(appName); - var languageEN_1 = languages_1.Items.First(x => x.Iso2Code == "en"); + var language1_EN = languages_1.Items.FirstOrDefault(x => x.Iso2Code == "en"); Assert.Equal(new string[] { "en", "de", "fr", "it" }, languages_1.Items.Select(x => x.Iso2Code).ToArray()); - Assert.True(languageEN_1.IsMaster); + Assert.True(language1_EN.IsMaster); // STEP 3: Update German language. @@ -288,10 +288,10 @@ namespace TestSuite.ApiTests }; var languages_2 = await _.Apps.PutLanguageAsync(appName, "de", updateRequest1); - var languageDE_2 = languages_2.Items.First(x => x.Iso2Code == "de"); + var language2_EN = languages_2.Items.FirstOrDefault(x => x.Iso2Code == "de"); - Assert.Equal(new string[] { "fr", "it" }, languageDE_2.Fallback.ToArray()); - Assert.True(languageDE_2.IsOptional); + Assert.Equal(new string[] { "fr", "it" }, language2_EN.Fallback.ToArray()); + Assert.True(language2_EN.IsOptional); // STEP 4: Update Italian language. @@ -305,7 +305,7 @@ namespace TestSuite.ApiTests }; var languages_3 = await _.Apps.PutLanguageAsync(appName, "it", updateRequest2); - var languageDE_3 = languages_3.Items.First(x => x.Iso2Code == "it"); + var languageDE_3 = languages_3.Items.FirstOrDefault(x => x.Iso2Code == "it"); Assert.Equal(new string[] { "fr", "de" }, languageDE_3.Fallback.ToArray()); @@ -314,25 +314,76 @@ namespace TestSuite.ApiTests var masterRequest = new UpdateLanguageDto { IsMaster = true }; var languages_4 = await _.Apps.PutLanguageAsync(appName, "it", masterRequest); + var language4_IT = languages_4.Items.FirstOrDefault(x => x.Iso2Code == "it"); + var language4_EN = languages_4.Items.FirstOrDefault(x => x.Iso2Code == "en"); - var languageIT_4 = languages_4.Items.First(x => x.Iso2Code == "it"); - var languageEN_4 = languages_4.Items.First(x => x.Iso2Code == "en"); - - Assert.True(languageIT_4.IsMaster); - Assert.False(languageIT_4.IsOptional); - Assert.False(languageEN_4.IsMaster); - Assert.Empty(languageIT_4.Fallback); + Assert.True(language4_IT.IsMaster); + Assert.False(language4_IT.IsOptional); + Assert.False(language4_EN.IsMaster); + Assert.Empty(language4_IT.Fallback); Assert.Equal(new string[] { "it", "de", "en", "fr" }, languages_4.Items.Select(x => x.Iso2Code).ToArray()); // STEP 6: Remove language. var languages_5 = await _.Apps.DeleteLanguageAsync(appName, "fr"); - var languageDE_5 = languages_5.Items.First(x => x.Iso2Code == "de"); + var language5_DE = languages_5.Items.FirstOrDefault(x => x.Iso2Code == "de"); - Assert.Equal(new string[] { "it" }, languageDE_5.Fallback.ToArray()); + Assert.Equal(new string[] { "it" }, language5_DE.Fallback.ToArray()); Assert.Equal(new string[] { "it", "de", "en" }, languages_5.Items.Select(x => x.Iso2Code).ToArray()); } + [Fact] + public async Task Should_manage_workflows() + { + var workflowName = Guid.NewGuid().ToString(); + + // STEP 0: Create workflow. + var createRequest = new AddWorkflowDto + { + Name = workflowName + }; + + var workflows_1 = await _.Apps.PostWorkflowAsync(_.AppName, createRequest); + var workflow_1 = workflows_1.Items.FirstOrDefault(x => x.Name == workflowName); + + Assert.NotNull(workflow_1); + Assert.NotNull(workflow_1.Name); + Assert.Equal(3, workflow_1.Steps.Count); + + + // STEP 1: Update workflow. + var updateRequest = new UpdateWorkflowDto + { + Initial = "Draft", + Steps = new Dictionary + { + ["Draft"] = new WorkflowStepDto + { + Transitions = new Dictionary + { + ["Published"] = new WorkflowTransitionDto() + } + }, + ["Published"] = new WorkflowStepDto(), + }, + Name = workflowName + }; + + var workflows_2 = await _.Apps.PutWorkflowAsync(_.AppName, workflow_1.Id, updateRequest); + var workflow_2 = workflows_2.Items.FirstOrDefault(x => x.Name == workflowName); + + Assert.NotNull(workflow_2); + Assert.NotNull(workflow_2.Name); + Assert.Equal(2, workflow_2.Steps.Count); + + + // STEP 2: Delete workflow. + var workflows_3 = await _.Apps.DeleteWorkflowAsync(_.AppName, workflow_1.Id); + var workflow_3 = workflows_3.Items.FirstOrDefault(x => x.Name == workflowName); + + Assert.Null(workflow_3); + } + [Fact] public async Task Should_manage_settings() { From 040ca5856cdc490906efe3bd3d3ac76356b4190e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 4 Jun 2021 18:08:31 +0200 Subject: [PATCH 06/37] Code unified. --- .../TestSuite/TestSuite.ApiTests/AppTests.cs | 54 ++++++++++--------- .../TestSuite.ApiTests/ContentUpdateTests.cs | 4 +- 2 files changed, 31 insertions(+), 27 deletions(-) diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index edb481555..282bf31e5 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -103,7 +103,7 @@ namespace TestSuite.ApiTests var createRequest = new CreateClientDto { Id = clientId }; var clients_1 = await _.Apps.PostClientAsync(_.AppName, createRequest); - var client_1 = clients_1.Items.Single(x => x.Id == clientId); + var client_1 = clients_1.Items.FirstOrDefault(x => x.Id == clientId); // Should return client with correct name and id. Assert.Equal(clientRole1, client_1.Role); @@ -114,7 +114,7 @@ namespace TestSuite.ApiTests var updateNameRequest = new UpdateClientDto { Name = clientName }; var clients_2 = await _.Apps.PutClientAsync(_.AppName, clientId, updateNameRequest); - var client_2 = clients_2.Items.Single(x => x.Id == clientId); + var client_2 = clients_2.Items.FirstOrDefault(x => x.Id == clientId); // Should update client name. Assert.Equal(clientName, client_2.Name); @@ -124,7 +124,7 @@ namespace TestSuite.ApiTests var updateRoleRequest = new UpdateClientDto { Role = clientRole2 }; var clients_3 = await _.Apps.PutClientAsync(_.AppName, clientId, updateRoleRequest); - var client_3 = clients_3.Items.Single(x => x.Id == clientId); + var client_3 = clients_3.Items.FirstOrDefault(x => x.Id == clientId); // Should update client role. Assert.Equal(clientRole2, client_3.Role); @@ -132,9 +132,10 @@ namespace TestSuite.ApiTests // STEP 4: Delete client var clients_4 = await _.Apps.DeleteClientAsync(_.AppName, clientId); + var client_4 = clients_4.Items.FirstOrDefault(x => x.Id == clientId); // Should not return deleted client. - Assert.DoesNotContain(clients_4.Items, x => x.Id == clientId); + Assert.Null(client_4); } [Fact] @@ -176,10 +177,11 @@ namespace TestSuite.ApiTests // STEP 3: Remove contributor. - var contributors3 = await _.Apps.DeleteContributorAsync(_.AppName, contributor_2.ContributorId); + var contributors_3 = await _.Apps.DeleteContributorAsync(_.AppName, contributor_2.ContributorId); + var contributor_3 = contributors_3.Items.FirstOrDefault(x => x.ContributorId == contributor_1.ContributorId); // Should not return deleted contributor. - Assert.DoesNotContain(contributors3.Items, x => x.ContributorId == contributor_2.ContributorId); + Assert.Null(contributor_3); } [Fact] @@ -194,7 +196,7 @@ namespace TestSuite.ApiTests var createRequest = new AddRoleDto { Name = roleName }; var roles_1 = await _.Apps.PostRoleAsync(_.AppName, createRequest); - var role_1 = roles_1.Items.Single(x => x.Name == roleName); + var role_1 = roles_1.Items.FirstOrDefault(x => x.Name == roleName); // Should return role with correct name. Assert.Empty(role_1.Permissions); @@ -204,7 +206,7 @@ namespace TestSuite.ApiTests var updateRequest = new UpdateRoleDto { Permissions = new List { "a", "b" } }; var roles_2 = await _.Apps.PutRoleAsync(_.AppName, roleName, updateRequest); - var role_2 = roles_2.Items.Single(x => x.Name == roleName); + var role_2 = roles_2.Items.FirstOrDefault(x => x.Name == roleName); // Should return role with correct name. Assert.Equal(updateRequest.Permissions, role_2.Permissions); @@ -220,7 +222,7 @@ namespace TestSuite.ApiTests await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor1, Role = roleName, Invite = true }); var roles_3 = await _.Apps.GetRolesAsync(_.AppName); - var role_3 = roles_3.Items.Single(x => x.Name == roleName); + var role_3 = roles_3.Items.FirstOrDefault(x => x.Name == roleName); // Should return role with correct number of users and clients. Assert.Equal(1, role_3.NumClients); @@ -248,9 +250,10 @@ namespace TestSuite.ApiTests await _.Apps.DeleteRoleAsync(_.AppName, roleName); var roles_4 = await _.Apps.GetRolesAsync(_.AppName); + var role_4 = roles_4.Items.FirstOrDefault(x => x.Name == roleName); // Should not return deleted role. - Assert.DoesNotContain(roles_4.Items, x => x.Name == roleName); + Assert.Null(role_4); } [Fact] @@ -270,10 +273,10 @@ namespace TestSuite.ApiTests await _.Apps.PostLanguageAsync(appName, new AddLanguageDto { Language = "fr" }); var languages_1 = await _.Apps.GetLanguagesAsync(appName); - var language1_EN = languages_1.Items.FirstOrDefault(x => x.Iso2Code == "en"); + var language_1_EN = languages_1.Items.FirstOrDefault(x => x.Iso2Code == "en"); Assert.Equal(new string[] { "en", "de", "fr", "it" }, languages_1.Items.Select(x => x.Iso2Code).ToArray()); - Assert.True(language1_EN.IsMaster); + Assert.True(language_1_EN.IsMaster); // STEP 3: Update German language. @@ -288,10 +291,10 @@ namespace TestSuite.ApiTests }; var languages_2 = await _.Apps.PutLanguageAsync(appName, "de", updateRequest1); - var language2_EN = languages_2.Items.FirstOrDefault(x => x.Iso2Code == "de"); + var language_2_DE = languages_2.Items.FirstOrDefault(x => x.Iso2Code == "de"); - Assert.Equal(new string[] { "fr", "it" }, language2_EN.Fallback.ToArray()); - Assert.True(language2_EN.IsOptional); + Assert.Equal(new string[] { "fr", "it" }, language_2_DE.Fallback.ToArray()); + Assert.True(language_2_DE.IsOptional); // STEP 4: Update Italian language. @@ -305,30 +308,30 @@ namespace TestSuite.ApiTests }; var languages_3 = await _.Apps.PutLanguageAsync(appName, "it", updateRequest2); - var languageDE_3 = languages_3.Items.FirstOrDefault(x => x.Iso2Code == "it"); + var language_3_DE = languages_3.Items.FirstOrDefault(x => x.Iso2Code == "it"); - Assert.Equal(new string[] { "fr", "de" }, languageDE_3.Fallback.ToArray()); + Assert.Equal(new string[] { "fr", "de" }, language_3_DE.Fallback.ToArray()); // STEP 5: Change master language. var masterRequest = new UpdateLanguageDto { IsMaster = true }; var languages_4 = await _.Apps.PutLanguageAsync(appName, "it", masterRequest); - var language4_IT = languages_4.Items.FirstOrDefault(x => x.Iso2Code == "it"); - var language4_EN = languages_4.Items.FirstOrDefault(x => x.Iso2Code == "en"); + var language_4_IT = languages_4.Items.FirstOrDefault(x => x.Iso2Code == "it"); + var language_4_EN = languages_4.Items.FirstOrDefault(x => x.Iso2Code == "en"); - Assert.True(language4_IT.IsMaster); - Assert.False(language4_IT.IsOptional); - Assert.False(language4_EN.IsMaster); - Assert.Empty(language4_IT.Fallback); + Assert.True(language_4_IT.IsMaster); + Assert.False(language_4_IT.IsOptional); + Assert.False(language_4_EN.IsMaster); + Assert.Empty(language_4_IT.Fallback); Assert.Equal(new string[] { "it", "de", "en", "fr" }, languages_4.Items.Select(x => x.Iso2Code).ToArray()); // STEP 6: Remove language. var languages_5 = await _.Apps.DeleteLanguageAsync(appName, "fr"); - var language5_DE = languages_5.Items.FirstOrDefault(x => x.Iso2Code == "de"); + var language_5_DE = languages_5.Items.FirstOrDefault(x => x.Iso2Code == "de"); - Assert.Equal(new string[] { "it" }, language5_DE.Fallback.ToArray()); + Assert.Equal(new string[] { "it" }, language_5_DE.Fallback.ToArray()); Assert.Equal(new string[] { "it", "de", "en" }, languages_5.Items.Select(x => x.Iso2Code).ToArray()); } @@ -381,6 +384,7 @@ namespace TestSuite.ApiTests var workflows_3 = await _.Apps.DeleteWorkflowAsync(_.AppName, workflow_1.Id); var workflow_3 = workflows_3.Items.FirstOrDefault(x => x.Name == workflowName); + // Should not return deleted workflow. Assert.Null(workflow_3); } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs index aefd2127b..aa228a480 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs @@ -619,7 +619,7 @@ namespace TestSuite.ApiTests // STEP 4: Check if we can find it again with a query. var contents_4 = await _.Contents.GetAsync(new ContentQuery { Filter = $"id eq '{content_1.Id}'" }); - Assert.NotNull(contents_4.Items.Find(x => x.Id == content_1.Id)); + Assert.NotNull(contents_4.Items.FirstOrDefault(x => x.Id == content_1.Id)); } [Theory] @@ -644,7 +644,7 @@ namespace TestSuite.ApiTests // STEP 4: Check if we can find it again with a query. var contents_4 = await _.Contents.GetAsync(new ContentQuery { Filter = $"id eq '{content_1.Id}'" }); - Assert.NotNull(contents_4.Items.Find(x => x.Id == content_1.Id)); + Assert.NotNull(contents_4.Items.FirstOrDefault(x => x.Id == content_1.Id)); } } } From 9d5cd2e56d10a5928ec844532d7d0dfbca1ee7d2 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 4 Jun 2021 18:09:13 +0200 Subject: [PATCH 07/37] Make tests able to run again. --- backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index 282bf31e5..767d5e751 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -395,7 +395,6 @@ namespace TestSuite.ApiTests var settings_0 = await _.Apps.GetAppSettingsAsync(_.AppName); Assert.NotEmpty(settings_0.Patterns); - Assert.Empty(settings_0.Editors); // STEP 2: Update settings with new state. From d8d6de4b67d93cf5ae6588b9a1bd55babb41452b Mon Sep 17 00:00:00 2001 From: GuoHai Date: Sun, 6 Jun 2021 00:37:54 +0800 Subject: [PATCH 08/37] Hotfix/typo issue (#721) * Fix little typo issue * Generating the final file * Fix typo issues Co-authored-by: gaogh2 --- backend/i18n/frontend_en.json | 4 ++-- backend/i18n/source/frontend_en.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 319fe9bd3..0e8a66fd7 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -339,7 +339,7 @@ "common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.", "common.skipped": "Skipped", "common.slug": "Slug", - "common.stars.max": "Must not have more more than 15 stars", + "common.stars.max": "Must not have more than 15 stars", "common.status": "Status", "common.statusChangeTo": "Change to", "common.submit": "Submit", @@ -416,7 +416,7 @@ "contents.loadContentFailed": "Failed to load content. Please reload.", "contents.loadDataFailed": "Failed to load data. Please reload.", "contents.loadFailed": "Failed to load contents. Please reload.", - "contents.loadVersionFailed": "Failed to version a new version. Please reload.", + "contents.loadVersionFailed": "Failed to load a new version. Please reload.", "contents.localizedFieldDescription": "The '{fieldName}' field of the content item (localized).", "contents.newStatusFieldDescription": "The new status of the content item.", "contents.noReference": "- No Reference -", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 319fe9bd3..0e8a66fd7 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -339,7 +339,7 @@ "common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.", "common.skipped": "Skipped", "common.slug": "Slug", - "common.stars.max": "Must not have more more than 15 stars", + "common.stars.max": "Must not have more than 15 stars", "common.status": "Status", "common.statusChangeTo": "Change to", "common.submit": "Submit", @@ -416,7 +416,7 @@ "contents.loadContentFailed": "Failed to load content. Please reload.", "contents.loadDataFailed": "Failed to load data. Please reload.", "contents.loadFailed": "Failed to load contents. Please reload.", - "contents.loadVersionFailed": "Failed to version a new version. Please reload.", + "contents.loadVersionFailed": "Failed to load a new version. Please reload.", "contents.localizedFieldDescription": "The '{fieldName}' field of the content item (localized).", "contents.newStatusFieldDescription": "The new status of the content item.", "contents.noReference": "- No Reference -", From 0abd01d7cb8b9405523c000105bad60c121c75da Mon Sep 17 00:00:00 2001 From: GuoHai Date: Sun, 6 Jun 2021 17:04:36 +0800 Subject: [PATCH 09/37] Add backend_zh.json (#722) --- backend/i18n/source/backend_zh.json | 375 ++++++++++++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 backend/i18n/source/backend_zh.json diff --git a/backend/i18n/source/backend_zh.json b/backend/i18n/source/backend_zh.json new file mode 100644 index 000000000..2c26e0a9b --- /dev/null +++ b/backend/i18n/source/backend_zh.json @@ -0,0 +1,375 @@ +{ + "annotations_AbsoluteUrl": "字段 '{name|lower}' 必须是绝对 URL。", + "annotations_Compare": "字段 '{name|lower}' 必须与 {other|lower} 相同。", + "annotations_EmailAddress": "字段 '{name|lower}' 不是有效的电子邮件地址。", + "annotations_Range": "字段 '{name|lower}' 必须介于 {min} 和 {max} 之间。", + "annotations_RegularExpression": "字段 '{name|lower}' 无法通过表达式验证。", + "annotations_Required": "字段 '{name|lower}' 是必需的。", + "annotations_StringLength": "字段 '{name|lower}' 必须是最大长度为 {max} 的字符串。", + "annotations_StringLengthMinimum": "字段 '{name|lower}' 必须是最小长度为 {min} 且最大长度为 {max} 的字符串。", + "apps.clients.idAlreadyExists": "已存在具有相同 ID 的客户端。", + "apps.contributors.cannotChangeYourself": "你不能改变你自己的角色。", + "apps.contributors.maxReached": "您已达到计划的最大贡献者数量。", + "apps.contributors.onlyOneOwner": "无法删除唯一的所有者。", + "apps.languages.fallbackNotFound": "应用没有后备语言 '{fallback}'。", + "apps.languages.languageAlreadyAdded": "语言已经添加。", + "apps.languages.masterLanguageNoFallbacks": "主语言不能有后备语言。", + "apps.languages.masterLanguageNotOptional": "主语言不能是可选的。", + "apps.languages.masterLanguageNotRemovable": "无法删除主语言。", + "apps.nameAlreadyExists": "同名应用已经存在。", + "apps.notImage": "文件不是图像", + "apps.plans.notFound": "不存在具有此 ID 的计划。", + "apps.plans.notPlanOwner": "计划只能由最初配置计划的用户更改。", + "apps.roles.defaultRoleNotRemovable": "无法删除默认角色。", + "apps.roles.defaultRoleNotUpdateable": "无法更新默认角色。", + "apps.roles.nameAlreadyExists": "已存在同名角色。", + "apps.roles.usedRoleByClientsNotRemovable": "分配客户端时无法删除角色。", + "apps.roles.usedRoleByContributorsNotRemovable": "当分配了贡献者时无法删除角色。", + "assets.folderNotFound": "Asset文件夹不存在。", + "assets.folderRecursion": "无法将文件夹添加到自己的子文件夹中。", + "assets.maxSizeReached": "您已达到最大Asset大小。", + "assets.referenced": "Asset被内容引用,无法删除。", + "backups.alreadyRunning": "另一个备份进程已经在运行。", + "backups.maxReached": "您不能拥有超过 {max} 个备份。", + "backups.restoreRunning": "还原操作已经在运行。", + "comments.noPermissions": "您只能访问您的通知。", + "comments.notUserComment": "评论是由另一个用户创建的。", + "common.action": "动作", + "common.aspectHeight": "纵横高度", + "common.aspectWidth": "纵横宽", + "common.calculatedDefaultValue": "计算的默认值", + "common.clientd": "客户端 ID", + "common.clientId": "客户端 ID", + "common.clientSecret": "客户端密码", + "common.contentType": "内容类型", + "common.contributorId": "贡献者 ID 或电子邮件", + "common.critical": "关键", + "common.data": "数据", + "common.defaultValue": "默认值", + "common.displayName": "显示名称", + "common.editor": "编辑器", + "common.email": "电子邮件", + "common.errorNoPermission": "您没有必要的权限。", + "common.field": "字段", + "common.fieldIds": "字段 ID", + "common.fieldName": "字段名", + "common.file": "文件", + "common.folderName": "文件夹名称", + "common.fullTextNotSupported": "不支持查询搜索子句。", + "common.httpContentTypeNotDefined": "文件内容类型未定义。", + "common.httpFileNameNotDefined": "文件名未定义。", + "common.httpInvalidRequest": "模型无效。", + "common.httpInvalidRequestFormat": "请求正文的格式无效。", + "common.httpOnlyAsUser": "客户端不允许。", + "common.httpValidationError": "验证错误", + "common.initialStep": "初始步骤", + "common.jsError": "执行脚本失败,Javascript 错误:{message}", + "common.jsNotAllowed": "脚本禁止操作。", + "common.jsParseError": "无法执行脚本,Javascript 语法错误:{message}", + "common.jsRejected": "脚本拒绝了操作。", + "common.language": "语言代码", + "common.login": "登录", + "common.logout": "注销", + "common.maxCharacters": "最大字符数", + "common.maxHeight": "最大高度", + "common.maxItems": "最大项目数", + "common.maxLength": "最大长度", + "common.maxSize": "最大尺寸", + "common.maxValue": "最大值", + "common.maxWidth": "最大宽度", + "common.maxWords": "最大字数", + "common.minCharacters": "最小字符数", + "common.minHeight": "最小高度", + "common.minItems": "最小项目", + "common.minLength": "最小长度", + "common.minSize": "最小尺寸", + "common.minValue": "最小值", + "common.minWidth": "最小宽度", + "common.minWords": "最小词", + "common.name": "名称", + "common.notFoundValue": "- 未找到 -", + "common.numDays": "天数", + "common.odataFailure": "无法解析查询:{message},查询:{odata}", + "common.odataFilterNotValid": "OData $filter 子句无效:{message}", + "common.odataNotSupported": "查询不支持 OData 操作:{odata}", + "common.odataSearchNotValid": "OData $search 子句无效:{message}", + "common.oldPassword": "旧密码", + "common.other": "其他", + "common.partitioning": "分区", + "common.password": "密码", + "common.passwordConfirm": "确认", + "common.pattern": "模式", + "common.permissions": "权限", + "common.planId": "计划 ID", + "common.previewUrls": "预览网址", + "common.product": "Squidex Headless CMS", + "common.properties": "属性", + "common.property": "属性", + "common.readonlyMode": "应用程序目前处于只读模式。", + "common.remove": "删除", + "common.resultTooLarge": "结果集太大无法检索。使用 $take 参数减少项目数。", + "common.role": "角色", + "common.save": "保存", + "common.schemaId": "Schema ID", + "common.signup": "注册", + "common.success": "成功", + "common.text": "文本", + "common.trigger": "触发器", + "common.warning": "警告", + "common.workflow": "工作流程", + "common.workflowStep": "步骤", + "common.workflowTransition": "过渡", + "contents.bulkInsertQueryNotUnique": "多个内容与查询匹配。", + "contents.draftNotCreateForUnpublished": "您只能在内容发布后创建新版本。", + "contents.draftToDeleteNotFound": "没有要删除的内容。", + "contents.invalidArrayOfObjects": "无效的 json 类型,预期的对象数组。", + "contents.invalidArrayOfStrings": "无效的 json 类型,需要字符串数组。", + "contents.invalidBoolean": "无效的 json 类型,应为布尔值。", + "contents.invalidComponentNoObject": "无效的 json 对象,带有 'schemaId' 字段的预期对象。", + "contents.invalidComponentNoType": "无效组件。未找到 'schemaId' 字段。", + "contents.invalidComponentUnknownSchema": "无效组件。找不到Schema。", + "contents.invalidGeolocation": "无效的 json 类型,预期的纬度/经度对象。", + "contents.invalidGeolocationLatitude": "纬度必须在 -90 到 90 之间。", + "contents.invalidGeolocationLongitude": "经度必须在 -180 到 180 之间。", + "contents.invalidNumber": "无效的 json 类型,需要的数字。", + "contents.invalidString": "无效的 json 类型,需要的字符串。", + "contents.listReferences": "{count} 个引用", + "contents.referenced": "内容被其他内容引用,无法删除或取消发布。", + "contents.schemaNotPublished": "Schema未发布。", + "contents.singletonNotChangeable": "无法更新单个内容。", + "contents.singletonNotCreatable": "无法创建单个内容。", + "contents.singletonNotDeletable": "无法删除单个内容。", + "contents.statusNotValid": "工作流中未定义状态。", + "contents.statusTransitionNotAllowed": "无法将状态从 {oldStatus} 更改为 {newStatus}。", + "contents.validation.aspectRatio": "必须有纵横比 {width}:{height}。", + "contents.validation.assetNotFound": "未找到 ID {id}。", + "contents.validation.between": "必须介于 {min} 和 {max} 之间。", + "contents.validation.characterCount": "必须正好有 {count} 个字符。", + "contents.validation.charactersBetween": "必须在 {min} 到 {max} 个字符之间。", + "contents.validation.duplicates": "不得包含重复值。", + "contents.validation.error": "验证失败,内部错误。", + "contents.validation.exactValue": "必须正好是 {value}。", + "contents.validation.extension": "必须是允许的扩展名。", + "contents.validation.image": "不是图片。", + "contents.validation.invalid": "无效值。", + "contents.validation.itemCount": "必须正好有 {count} 个项目。", + "contents.validation.itemCountBetween": "必须介于 {min} 和 {max} 项之间。", + "contents.validation.max": "必须小于或等于 {max}。", + "contents.validation.maxCharacters": "不得超过 {max} 个文本字符。", + "contents.validation.maximumHeight": "高度 {height}px 必须小于 {max}px。", + "contents.validation.maximumSize": "{size} 的大小必须小于 {max}。", + "contents.validation.maximumWidth": "宽度 {width}px 必须小于 {max}px。", + "contents.validation.maxItems": "不得超过 {max} 个项目。", + "contents.validation.maxLength": "不得超过 {max} 个字符。", + "contents.validation.maxWords": "不得超过 {max} 个单词。", + "contents.validation.min": "必须大于或等于 {min}。", + "contents.validation.minimumHeight": "高度 {height}px 必须大于 {min}px。", + "contents.validation.minimumSize": "{size} 的大小必须大于 {min}。", + "contents.validation.minimumWidth": "宽度 {width}px 必须大于 {min}px。", + "contents.validation.minItems": "必须至少有 {min} 个项目。", + "contents.validation.minLength": "必须至少有 {min} 个字符。", + "contents.validation.minNormalCharacters": "必须至少有 {min} 个文本字符。", + "contents.validation.minWords": "必须至少有 {min} 个单词。", + "contents.validation.mustBeEmpty": "不得定义值。", + "contents.validation.normalCharacterCount": "必须正好有 {count} 个文本字符。", + "contents.validation.normalCharactersBetween": "必须在 {min} 和 {max} 个文本字符之间。", + "contents.validation.notAllowed": "不允许的值。", + "contents.validation.pattern": "必须遵循模式。", + "contents.validation.referenceNotFound": "未找到引用 '{id}'。", + "contents.validation.referenceToInvalidSchema": "参考 '{id}' 的Schema无效。", + "contents.validation.regexTooSlow": "正则表达式太慢了。", + "contents.validation.required": "必填字段。", + "contents.validation.unique": "存在另一个具有相同值的内容。", + "contents.validation.unknownField": "不是已知的 {fieldType}。", + "contents.validation.wordCount": "必须正好有 {count} 个单词。", + "contents.validation.wordsBetween": "必须在 {min} 到 {max} 个单词之间。", + "contents.workflowErrorUpdate": "工作流不允许更新状态为 {status}", + "dotnet_identity_DefaultEror": "发生未知故障。", + "dotnet_identity_DuplicateEmail": "电子邮件已被占用。", + "dotnet_identity_DuplicateUserName": "用户名已被占用。", + "dotnet_identity_InvalidEmail": "电子邮件无效。", + "dotnet_identity_InvalidUserName": "用户名“{0}”无效,只能包含字母或数字。", + "dotnet_identity_LoginAlreadyAssociated": "已存在使用此登录名的用户。", + "dotnet_identity_PasswordMismatch": "密码不正确。", + "dotnet_identity_PasswordRequiresDigit": "密码必须至少有一位 ('0'-'9')。", + "dotnet_identity_PasswordRequiresLower": "密码必须至少有一个小写字母 ('a'-'z')。", + "dotnet_identity_PasswordRequiresNonAlphanumeric": "密码必须至少有一个非字母数字字符。", + "dotnet_identity_PasswordRequiresUniqueChars": "密码必须至少使用 {0} 个不同的字符。", + "dotnet_identity_PasswordRequiresUpper": "密码必须至少有一个大写字母 ('A'-'Z')。", + "dotnet_identity_PasswordTooShort": "密码太短。", + "dotnet_identity_PwnedError": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过,请更改它!", + "dotnet_identity_UserLockedOut": "用户被锁定。", + "exception.invalidJsonQuery": "Json 查询无效:{message}", + "exception.invalidJsonQueryJson": "Json 查询无效 json: {message}", + "exceptions.domainObjectConflict": "实体 ({id}) 已经存在。", + "exceptions.domainObjectDeleted": "实体 ({id}) 已被删除。", + "exceptions.domainObjectNotFound": "实体 ({id}) 不存在。", + "exceptions.domainObjectVersion": "实体 ({id}) 请求版本 {expectedVersion},但找到 {currentVersion}。", + "history.apps.clientAdded": "将客户端 {[Id]} 添加到应用程序", + "history.apps.clientRevoked": "已撤销客户端 {[Id]}", + "history.apps.clientUpdated": "更新的客户端 {[Id]}", + "history.apps.contributoreAssigned": "已分配 {user:[Contributor]} 作为 {[Role]}", + "history.apps.contributoreRemoved": "从应用中删除了 {user:[Contributor]}", + "history.apps.languagedAdded": "添加语言 {[Language]}", + "history.apps.languagedRemoved": "已删除语言 {[Language]}", + "history.apps.languagedSetToMaster": "将主语言更改为 {[Language]}", + "history.apps.languagedUpdated": "更新的语言 {[Language]}", + "history.apps.planChanged": "已将计划更改为 {[Plan]}", + "history.apps.planReset": "重置计划", + "history.apps.roleAdded": "添加角色 {[Name]}", + "history.apps.roleDeleted": "已删除角色 {[Name]}", + "history.apps.roleUpdated": "更新角色 {[Name]}", + "history.apps.settingsUpdated": "更新的 UI 设置", + "history.assets.replaced": "替换的Asset。", + "history.assets.updated": "更新的Asset。", + "history.assets.uploaded": "上传的Asset。", + "history.contents.created": "创建了 {[Schema]} 内容。", + "history.contents.deleted": "删除了 {[Schema]} 内容。", + "history.contents.draftCreated": "创建了新草稿。", + "history.contents.draftDeleted": "已删除的草稿。", + "history.contents.scheduleCompleted": "已安排将 {[Schema]} 内容的状态更改为 {[Status]}。", + "history.contents.scheduleFailed": "无法安排 {[Schema]} 内容的状态更改。", + "history.contents.updated": "更新了 {[Schema]} 内容。", + "history.schemas.created": "创建的Schema {[Name]}。", + "history.schemas.deleted": "已删除Schema {[Name]}。", + "history.schemas.fieldAdded": "将字段 {[Field]} 添加到Schema {[Name]}。", + "history.schemas.fieldDeleted": "已从Schema {[Name]} 中删除字段 {[Field]}。", + "history.schemas.fieldDisabled": "Schema{[Name]} 的禁用字段 {[Field]}。", + "history.schemas.fieldHidden": "有Schema {[Name]} 的隐藏字段 {[Field]}。", + "history.schemas.fieldLocked": "已锁定Schema {[Name]} 的字段 {[Field]}。", + "history.schemas.fieldShown": "已显示Schema {[Name]} 的字段 {[Field]}。", + "history.schemas.fieldsReordered": "Schema {[Name]} 的重新排序字段。", + "history.schemas.fieldUpdated": "已更新Schema {[Name]} 的字段 {[Field]}。", + "history.schemas.published": "已发布的Schema {[Name]}。", + "history.schemas.scriptsConfigured": "Schema {[Name]} 的配置脚本。", + "history.schemas.unpublished": "未发布的Schema {[Name]}。", + "history.schemas.updated": "更新的Schema {[Name]}。", + "history.statusChanged": "已将 {[Schema]} 内容的状态更改为 {[Status]}。", + "login.githubPrivateEmail": "您的邮箱在 Github 中设置为私有,请设置为公开以使用 Github 登录。", + "rules.ruleAlreadyRunning": "另一个规则已经在运行。", + "schemas.dateTimeCalculatedDefaultAndDefaultError": "计算出的默认值和默认值不能一起使用。", + "schemas.duplicateFieldName": "字段 '{field}' 已添加两次。", + "schemas.fieldCannotBeUIField": "字段不能是 UI 字段。", + "schema.fieldIsLocked": "Schema字段被锁定。", + "schemas.fieldNameAlreadyExists": "已存在同名字段。", + "schema.fieldNotInSchema": "字段不是Schema的一部分。", + "schemas.fieldsNotCovered": "字段 ID 未涵盖所有字段。", + "schemas.nameAlreadyExists": "一个同名的Schema已经存在。", + "schema.noPermission": "您没有此Schema的权限。", + "schema.notFoundId": "Schema {id} 不存在。", + "schemas.number.inlineEditorError": "无线电编辑器不允许内联编辑。", + "schemas.onlyArraysHaveNested": "只有数组字段可以有嵌套字段。", + "schemas.onylArraysInRoot": "嵌套字段不能是数组字段。", + "schemas.references.resolveError": "只有在 MaxItems 为 1 时才能解析引用。", + "schemas.string.inlineEditorError": "内联编辑只允许用于下拉菜单、slug 和输入字段。", + "schemas.stringEditorsNeedAllowedValuesError": "单选按钮或下拉列表需要允许的值。", + "schemas.tags.editorNeedsAllowedValues": "复选框或下拉列表需要允许的值。", + "schemas.uiFieldCannotBeDisabled": "UI 字段不能被禁用。", + "schemas.uiFieldCannotBeEnabled": "UI 字段无法启用。", + "schemas.uiFieldCannotBeHidden": "UI 字段不能隐藏。", + "schemas.uiFieldCannotBeShown": "UI 字段无法显示。", + "search.contentResult": "{name} 内容", + "search.contentsResult": "{name} 内容", + "search.schemaResult": "{name} Schema", + "security.passwordStolen": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前在任何地方使用过它,请更改它!", + "setup.createUser.button": "创建用户", + "setup.createUser.confirmPassword": "确认", + "setup.createUser.failure": "既没有配置密码认证,也没有配置外部认证提供商,如谷歌。请检查您的设置和日志。", + "setup.createUser.headline": "管理员用户", + "setup.createUser.headlineCreate": "创建管理员用户", + "setup.createUser.loginHint": "您已经配置了至少一个外部身份验证提供程序,例如 Google。只需转到登录页面并登录即可成为管理员。", + "setup.createUser.loginLink": "进入登录页面。", + "setup.createUser.separator": "OR", + "setup.headline": "安装", + "setup.hint": "您看到此屏幕是因为尚无用户存在。创建用户后,您将无法再次使用此屏幕。", + "setup.madeBy": "自豪地制作", + "setup.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021", + "setup.ruleAppCreation.warningAdmins": "通过你的设置,只有管理员可以创建新的应用程序。如果你想改变这个设置 UI__ONLYADMINCANCREATEAPPS=false 作为环境变量。", + "setup.ruleAppCreation.warningAll": "通过你的设置,每个用户都可以创建新的应用程序。如果你想改变这个设置 UI__ONLYADMINCANCREATEAPPS=true 作为环境变量。", + "setup.ruleFolder.warning": "您正在使用文件夹Asset存储,其中所有Asset都存储在文件系统中。请记住将Asset文件夹包含在您的备份策略中并将其映射到卷, 如果您使用的是 Docker。", + "setup.ruleFtp.warning": "您正在使用 FTP Asset存储。由于性能不佳,不建议使用这种存储类型。", + "setup.ruleHttps.failure": " 您不是通过 https 访问站点。如果此警告不正确,则 Squidex 无法检测 https Schema,因为您的实例位于反向代理(例如 nginx)之后。确保正确转发 http 标头, 通过 X-Forwarded-* 标头。", + "setup.ruleHttps.success": "恭喜您,您正在通过安全连接 (https) 访问 Squidex 安装。", + "setup.rules.headline": "系统清单", + "setup.ruleUrl.failure": "您应该仅通过一个规范 URL 访问 Squidex,并通过 URLS__BASEURL 环境变量配置此 URL。当前的基本 URL {actual}与基本 url {configured} 不匹配。", + "setup.ruleUrl.success": "恭喜 URLS__BASEURL 环境变量配置正确。", + "setup.title": "安装", + "users.accessDenied.text": "不允许此操作,您的帐户可能被锁定。", + "users.accessDenied.title": "拒绝访问", + "users.consent.agree": "我同意!", + "users.consent.cookiesHeadline": "Cookies & Analytics", + "users.consent.cookiesText": "

我理解并同意 Squidex 使用 cookie 来确保您在我们的平台上获得最佳体验并存储您的登录状态。

我理解并同意 Squidex已集成 Google Analytics(具有匿名器功能)。Google Analytics 是一项网络分析服务,用于收集和分析有关用户行为的数据。

我接受 隐私政策.

", + "users.consent.emailHeadline": "自动电子邮件(可选)", + "users.consent.emailText": "我理解并同意 Squidex 发送电子邮件来通知我有关新功能、重大更改和停机时间的信息。", + "users.consent.headline": "我们需要您的同意", + "users.consent.needed": "你必须同意。", + "users.consent.piiHeadline": "个人信息", + "users.consent.piiText": "我理解并同意 Squidex 收集从外部身份验证提供商(例如 Google、Microsoft 或 Github)检索到的以下私人信息。
  • 向所有其他用户提供基本的个人信息(名字、姓氏和照片),以便他们可以将您添加到他们的工作空间。
  • 您可以随时选择更改这些信息以使您的帐户匿名.
  • 您的用户帐户具有唯一标识符,对于我们跟踪的所有更改,您进行了这些更改并将此信息提供给其他用户。
", + "users.consent.title": "同意", + "users.deleteYourselfError": "你不能删除自己。", + "users.error.headline": "操作失败", + "users.error.text": "我们真的很抱歉出现问题。", + "users.error.title": "错误", + "users.errorHappened": "发生意外异常。", + "users.lockedOutText": "您的账户已被锁定,请联系管理员。", + "users.lockedOutTitle": "帐户已锁定", + "users.lockYourselfError": "你不能锁定自己。", + "users.login.askAdmin": "", + "users.login.emailPlaceholder": "输入邮箱", + "users.login.error": "电子邮件或密码不正确", + "users.login.loginWith": "{action} with {provider}", + "users.login.noAccountLoginAction": "点击此处登录", + "users.login.noAccountLoginQuestion": "已经注册?", + "users.login.noAccountSignupAction": "点击此处注册", + "users.login.noAccountSignupQuestion": "还没有账号?", + "users.login.passwordPlaceholder": "输入密码", + "users.login.separator": "或", + "users.logout.headline": "登出!", + "users.logout.text": "!请关闭这个弹窗。", + "users.logout.title": "注销", + "users.profile.addLoginDone": "登录添加成功。", + "users.profile.changePassword": "更改密码", + "users.profile.changePasswordDone": "密码更改成功。", + "users.profile.clientHint": "使用客户端凭据通过您的个人资料信息和权限访问 API", + "users.profile.clientTitle": "客户端", + "users.profile.confirmPassword": "确认", + "users.profile.generateClient": "生成", + "users.profile.generateClientDone": "客户端密码生成成功。", + "users.profile.headline": "编辑个人资料", + "users.profile.hideProfile": "不要向其他用户显示我的个人资料", + "users.profile.loginsTitle": "登录", + "users.profile.passwordTitle": "密码", + "users.profile.pii": "个人信息", + "users.profile.propertiesHint": "为规则和脚本使用自定义属性。", + "users.profile.propertiesTitle": "属性", + "users.profile.propertyAdd": "添加属性", + "users.profile.removeLoginDone": "登录提供程序删除成功。", + "users.profile.setPassword": "设置密码", + "users.profile.setPasswordDone": "密码设置成功。", + "users.profile.title": "个人资料", + "users.profile.updateProfileDone": "账户更新成功。", + "users.profile.updatePropertiesDone": "账户更新成功。", + "users.profile.uploadPicture": "上传图片", + "users.profile.uploadPictureDone": "图片上传成功。", + "users.unlockYourselfError": "您无法解锁自己。", + "users.userLocked": "不允许用户登录。", + "users.userNotFound": "找不到用户。", + "validation.between": "{property|upper} 必须介于 {min} 和 {max} 之间。", + "validation.greaterEqualsThan": "{property|upper} 必须大于或等于 {other|lower}。", + "validation.greaterThan": "{property|upper} 必须大于 {other|lower}。", + "validation.javascriptProperty": "{property|upper} 不是 Javascript 属性名称。", + "validation.lessEqualsThan": "{property|upper} 必须小于或等于 {other|lower}。", + "validation.lessThan": "{property|upper} 必须小于 {other|lower}。", + "validation.notAnImage": "图片不是有效图片。", + "validation.onlyOneFile": "只能上传一个文件。", + "validation.required": "{property|upper} 是必需的。", + "validation.requiredBoth": "如果使用 {property1|lower} 或 {property2|lower},则必须定义两者。", + "validation.requiredValue": "必须定义值。", + "validation.slug": "{property|upper} 不是有效的 slug。", + "validation.valid": "{property|upper} 不是一个有效值。", + "workflows.overlap": "多个工作流覆盖所有Schema。", + "workflows.publishedIsInitial": "初始步骤不能发布步骤。", + "workflows.publishedNotDefined": "工作流必须有一个已发布的步骤。", + "workflows.publishedStepNotFound": "转换的目标无效。", + "workflows.schemaOverlap": "Schema'{schema}' 被多个工作流覆盖。" +} \ No newline at end of file From 594b976ad1a4f3df984070f7e5a97e9a169aab12 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sun, 6 Jun 2021 15:36:56 +0200 Subject: [PATCH 10/37] Added support for chinese language. --- backend/i18n/source/backend_it.json | 2 -- backend/i18n/source/backend_nl.json | 1 - backend/i18n/source/backend_zh.json | 4 --- backend/i18n/source/frontend_it.json | 8 ----- backend/i18n/source/frontend_nl.json | 8 ----- .../translator/Squidex.Translator/Commands.cs | 28 ++++++++++++--- .../Squidex.Translator/Processes/Helper.cs | 35 ++++++++++++++++--- .../src/Squidex/Config/Web/WebExtensions.cs | 2 +- frontend/app/framework/utils/cookies.ts | 6 ++++ frontend/app/framework/utils/date-helper.ts | 9 +++-- frontend/app/shared/state/ui-languages.ts | 3 ++ .../internal/profile-menu.component.html | 10 +++--- .../pages/internal/profile-menu.component.ts | 14 ++++++-- 13 files changed, 88 insertions(+), 42 deletions(-) diff --git a/backend/i18n/source/backend_it.json b/backend/i18n/source/backend_it.json index 79cf41986..e8a7f0942 100644 --- a/backend/i18n/source/backend_it.json +++ b/backend/i18n/source/backend_it.json @@ -115,7 +115,6 @@ "common.success": "Successo", "common.text": "Testo", "common.trigger": "Trigger", - "common.url": "URL", "common.warning": "Warning", "common.workflow": "Workflow", "common.workflowStep": "Step", @@ -171,7 +170,6 @@ "contents.validation.normalCharactersBetween": "Deve essere un testo tra {min} e {max} carattere(i).", "contents.validation.notAllowed": "Non è un valore consentito.", "contents.validation.pattern": "Deve seguire il pattern.", - "contents.validation.reference": "La geolocalizzazione può avere come campi solamente come latitudine e longitudine.", "contents.validation.referenceNotFound": "Contiene un collegamento '{id}' non valido.", "contents.validation.referenceToInvalidSchema": "Contiene dei collegamenti '{id}' ad uno schema errato.", "contents.validation.regexTooSlow": "La regular expression è troppo lenta.", diff --git a/backend/i18n/source/backend_nl.json b/backend/i18n/source/backend_nl.json index 722021f95..39161207f 100644 --- a/backend/i18n/source/backend_nl.json +++ b/backend/i18n/source/backend_nl.json @@ -112,7 +112,6 @@ "common.signup": "Aanmelden", "common.text": "Tekst", "common.trigger": "Trigger", - "common.url": "URL", "common.workflow": "Workflow", "common.workflowStep": "Stap", "common.workflowTransition": "Overgang", diff --git a/backend/i18n/source/backend_zh.json b/backend/i18n/source/backend_zh.json index 2c26e0a9b..adfb0f506 100644 --- a/backend/i18n/source/backend_zh.json +++ b/backend/i18n/source/backend_zh.json @@ -250,13 +250,9 @@ "schemas.dateTimeCalculatedDefaultAndDefaultError": "计算出的默认值和默认值不能一起使用。", "schemas.duplicateFieldName": "字段 '{field}' 已添加两次。", "schemas.fieldCannotBeUIField": "字段不能是 UI 字段。", - "schema.fieldIsLocked": "Schema字段被锁定。", "schemas.fieldNameAlreadyExists": "已存在同名字段。", - "schema.fieldNotInSchema": "字段不是Schema的一部分。", "schemas.fieldsNotCovered": "字段 ID 未涵盖所有字段。", "schemas.nameAlreadyExists": "一个同名的Schema已经存在。", - "schema.noPermission": "您没有此Schema的权限。", - "schema.notFoundId": "Schema {id} 不存在。", "schemas.number.inlineEditorError": "无线电编辑器不允许内联编辑。", "schemas.onlyArraysHaveNested": "只有数组字段可以有嵌套字段。", "schemas.onylArraysInRoot": "嵌套字段不能是数组字段。", diff --git a/backend/i18n/source/frontend_it.json b/backend/i18n/source/frontend_it.json index 902d4767a..54b955dba 100644 --- a/backend/i18n/source/frontend_it.json +++ b/backend/i18n/source/frontend_it.json @@ -588,7 +588,6 @@ "roles.revokeFailed": "Non è stato possibile rimuovere il ruolo. Per favore ricarica.", "roles.roleNamePlaceholder": "Inserisci il nome del ruolo", "roles.updateFailed": "Non è stato possibile aggiornare il ruolo. Per favore ricarica.", - "rules.actionEdit": "Modifica l'Azione", "rules.cancelFailed": "Non è stato possibile eliminare la regola. Per favore ricarica.", "rules.create": "Crea un nuova Regola", "rules.createFailed": "Non è stato possibile creare una nuova regola. Per favore ricarica.", @@ -596,10 +595,8 @@ "rules.deleteConfirmText": "Sei sicuro di voler eliminare la regola?", "rules.deleteConfirmTitle": "Cancella la regola", "rules.deleteFailed": "Non è stato possibile eliminare la regola. Per favore ricarica.", - "rules.disableFailed": "Non è stato possibile disabilitare la regola. Per favore ricarica.", "rules.empty": "Nessuna regola è stato ancora creata.", "rules.emptyAddRule": "Aggiungi una regola", - "rules.enableFailed": "Non è stato possibile abilitare la regola. Per favore ricarica.", "rules.enqueued": "La regola è stata aggiunta alle code.", "rules.listPageTitle": "Regole", "rules.loadFailed": "Non è stato possibile caricare le regole. Per favore ricarica.", @@ -629,14 +626,9 @@ "rules.stop": "La regola si fermerà al più presto.", "rules.triggerConfirmText": "Sei sicuro che voler attivare la regola?", "rules.triggerConfirmTitle": "Attiva la regola", - "rules.triggerEdit": "Modifica l'Attivazione", "rules.triggerFailed": "Non è stato possibile attivare la regola. Per favore ricarica.", "rules.unnamed": "Regola senza nome", "rules.updateFailed": "Non è stato possibile aggiornare la regola. Per favore ricarica.", - "rules.wizard.actionHint": "La selezione del tipo di azione non potrà essere modificata successivamente.", - "rules.wizard.selectAction": "Seleziona l'Azione", - "rules.wizard.selectTrigger": "Seleziona l'Attivazione", - "rules.wizard.triggerHint": "La selezione del tipo di attivazione non potrà essere modificata successivamente.", "schemas.addField": "Aggiungi un Campo", "schemas.addFieldAndClose": "Crea e chiudi", "schemas.addFieldAndCreate": "Crea e aggiungi il campo", diff --git a/backend/i18n/source/frontend_nl.json b/backend/i18n/source/frontend_nl.json index 8a1b57ebf..00c21802c 100644 --- a/backend/i18n/source/frontend_nl.json +++ b/backend/i18n/source/frontend_nl.json @@ -560,7 +560,6 @@ "roles.revokeFailed": "Kan rol niet intrekken. Laad opnieuw.", "roles.roleNamePlaceholder": "Voer de rolnaam in", "roles.updateFailed": "Update rol mislukt. Laad opnieuw.", - "rules.actionEdit": "Bewerk actie", "rules.cancelFailed": "Annuleren van regel is mislukt. Laad opnieuw.", "rules.create": "Maak een nieuwe regel", "rules.createFailed": "Maken van regel is mislukt. Laad opnieuw.", @@ -568,10 +567,8 @@ "rules.deleteConfirmText": "Wil je de regel echt verwijderen?", "rules.deleteConfirmTitle": "Regel verwijderen", "rules.deleteFailed": "Verwijderen van regel is mislukt. Laad opnieuw.", - "rules.disableFailed": "Kan regel niet uitschakelen. Laad opnieuw.", "rules.empty": "Nog geen regel aangemaakt.", "rules.emptyAddRule": "Regel toevoegen", - "rules.enableFailed": "Kan regel niet inschakelen. Laad opnieuw.", "rules.enqueued": "Regel is toegevoegd aan de wachtrij.", "rules.listPageTitle": "Regels", "rules.loadFailed": "Laden van regels is mislukt. Laad opnieuw.", @@ -600,14 +597,9 @@ "rules.stop": "Regel stopt binnenkort.", "rules.triggerConfirmText": "Wil je echt de regel activeren?", "rules.triggerConfirmTitle": "Trigger regel", - "rules.triggerEdit": "Trigger bewerken", "rules.triggerFailed": "Kan regel niet activeren. Laad opnieuw.", "rules.unnamed": "Naamloos regel", "rules.updateFailed": "Update regel mislukt. Laad opnieuw.", - "rules.wizard.actionHint": "De selectie van het actietype kan later niet worden gewijzigd.", - "rules.wizard.selectAction": "Selecteer actie", - "rules.wizard.selectTrigger": "Selecteer Trigger", - "rules.wizard.triggerHint": "De selectie van het triggertype kan later niet worden gewijzigd.", "schemas.addField": "Veld toevoegen", "schemas.addFieldAndClose": "Maken en sluiten", "schemas.addFieldAndCreate": "Maak en voeg veld toe", diff --git a/backend/i18n/translator/Squidex.Translator/Commands.cs b/backend/i18n/translator/Squidex.Translator/Commands.cs index 64ea32a9d..4a6425a75 100644 --- a/backend/i18n/translator/Squidex.Translator/Commands.cs +++ b/backend/i18n/translator/Squidex.Translator/Commands.cs @@ -89,6 +89,26 @@ namespace Squidex.Translator new GenerateFrontendResources(folder, service).Run(); } + [Command(Name = "clean-backend", Description = "Clean the backend translations.")] + public void CleanBackend(TranslateArguments arguments) + { + var (folder, service) = Setup(arguments, "backend"); + + Helper.CleanOtherLocales(service); + + service.Save(); + } + + [Command(Name = "clean-frontend", Description = "Clean the frontend translations.")] + public void CleanFrontend(TranslateArguments arguments) + { + var (folder, service) = Setup(arguments, "frontend"); + + Helper.CleanOtherLocales(service); + + service.Save(); + } + [Command(Name = "gen-keys", Description = "Generate the keys for translations.")] public void GenerateBackendKeys(TranslateArguments arguments) { @@ -124,18 +144,18 @@ namespace Squidex.Translator throw new ArgumentException("Folder does not exist."); } - var supportedLocaled = new string[] { "en", "nl", "it" }; + var supportedLocales = new string[] { "en", "nl", "it", "zh" }; - var locales = supportedLocaled; + var locales = supportedLocales; if (arguments.Locales != null && arguments.Locales.Any()) { - locales = supportedLocaled.Intersect(arguments.Locales).ToArray(); + locales = supportedLocales.Intersect(arguments.Locales).ToArray(); } if (locales.Length == 0) { - locales = supportedLocaled; + locales = supportedLocales; } var translationsDirectory = new DirectoryInfo(Path.Combine(arguments.Folder, "backend", "i18n")); diff --git a/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs b/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs index 7eaa2b191..97612fc8a 100644 --- a/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs +++ b/backend/i18n/translator/Squidex.Translator/Processes/Helper.cs @@ -30,9 +30,9 @@ namespace Squidex.Translator.Processes Console.WriteLine("----- CHECKING <{0}> -----", locale); var notTranslated = mainTranslations.Keys.Except(texts.Keys).ToList(); - var notRequired = texts.Keys.Except(mainTranslations.Keys).ToList(); + var notUsed = texts.Keys.Except(mainTranslations.Keys).ToList(); - if (notTranslated.Count > 0 || notRequired.Count > 0) + if (notTranslated.Count > 0 || notUsed.Count > 0) { if (notTranslated.Count > 0) { @@ -46,12 +46,12 @@ namespace Squidex.Translator.Processes } } - if (notRequired.Count > 0) + if (notUsed.Count > 0) { Console.WriteLine(); Console.WriteLine("Translations not used:"); - foreach (var key in notRequired.OrderBy(x => x)) + foreach (var key in notUsed.OrderBy(x => x)) { Console.Write(" * "); Console.WriteLine(key); @@ -65,6 +65,33 @@ namespace Squidex.Translator.Processes } } + public static void CleanOtherLocales(TranslationService service) + { + var mainTranslations = service.MainTranslations; + + foreach (var (locale, texts) in service.Translations.Where(x => x.Key != service.MainLocale)) + { + Console.WriteLine(); + Console.WriteLine("----- CLEANING <{0}> -----", locale); + + var notUsed = texts.Keys.Except(mainTranslations.Keys).ToList(); + + if (notUsed.Count > 0) + { + foreach (var unused in notUsed) + { + texts.Remove(unused); + } + + Console.WriteLine("Cleaned {0} translations.", notUsed.Count); + } + else + { + Console.WriteLine("> No errors found"); + } + } + } + public static void CheckUnused(TranslationService service, HashSet translations) { var notUsed = new SortedSet(); diff --git a/backend/src/Squidex/Config/Web/WebExtensions.cs b/backend/src/Squidex/Config/Web/WebExtensions.cs index 7308e27ed..5eeece8ca 100644 --- a/backend/src/Squidex/Config/Web/WebExtensions.cs +++ b/backend/src/Squidex/Config/Web/WebExtensions.cs @@ -39,7 +39,7 @@ namespace Squidex.Config.Web public static IApplicationBuilder UseSquidexLocalization(this IApplicationBuilder app) { - var supportedCultures = new[] { "en", "nl", "it" }; + var supportedCultures = new[] { "en", "nl", "it", "zh" }; var localizationOptions = new RequestLocalizationOptions() .SetDefaultCulture(supportedCultures[0]) diff --git a/frontend/app/framework/utils/cookies.ts b/frontend/app/framework/utils/cookies.ts index e1681a508..b23adf381 100644 --- a/frontend/app/framework/utils/cookies.ts +++ b/frontend/app/framework/utils/cookies.ts @@ -20,6 +20,12 @@ export module Cookies { document.cookie = `${name}=${value || ''}${expires}; path=/`; } + export function replace(name: string, value: string, days: number) { + remove(name); + + set(name, value, days); + } + export function remove(name: string) { document.cookie = `${name}=; Max-Age=-99999999;`; } diff --git a/frontend/app/framework/utils/date-helper.ts b/frontend/app/framework/utils/date-helper.ts index 64a0c72c2..b52459b73 100644 --- a/frontend/app/framework/utils/date-helper.ts +++ b/frontend/app/framework/utils/date-helper.ts @@ -5,12 +5,17 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { enUS, it, nl } from 'date-fns/locale'; +import { enUS, it, nl, zhCN } from 'date-fns/locale'; export module DateHelper { let locale: string | null; - export const FNSLOCALES = { enUS, it, nl }; + export const FNSLOCALES = { + en: enUS, + it, + nl, + zh: zhCN, + }; export function setlocale(code: string | null) { locale = code; diff --git a/frontend/app/shared/state/ui-languages.ts b/frontend/app/shared/state/ui-languages.ts index 0835e40f4..d6dbf9a10 100644 --- a/frontend/app/shared/state/ui-languages.ts +++ b/frontend/app/shared/state/ui-languages.ts @@ -17,5 +17,8 @@ export module UILanguages { }, { iso2Code: 'it', localName: 'Italiano', + }, { + iso2Code: 'zh', + localName: '中国人', }]; } diff --git a/frontend/app/shell/pages/internal/profile-menu.component.html b/frontend/app/shell/pages/internal/profile-menu.component.html index 9dc28002b..f2471456d 100644 --- a/frontend/app/shell/pages/internal/profile-menu.component.html +++ b/frontend/app/shell/pages/internal/profile-menu.component.html @@ -5,7 +5,7 @@ - +