Browse Source

Merge branch 'master' of github.com:Squidex/squidex

pull/733/head
Sebastian 5 years ago
parent
commit
6dce9fdca8
  1. 24
      .github/workflows/dev.yml
  2. 14
      .github/workflows/marketplace-aws.yml
  3. 22
      .github/workflows/marketplace-azure.yml
  4. 14
      .github/workflows/marketplace-digitalocean.yml
  5. 20
      .github/workflows/marketplace-gcp.yml
  6. 44
      .github/workflows/marketplace-heroku.yml
  7. 44
      .github/workflows/marketplace-render.yml
  8. 17
      .github/workflows/marketplace-vultr.yml
  9. 37
      .github/workflows/release.yml
  10. 28
      CHANGELOG.md
  11. 2
      Dockerfile
  12. 18
      README.md
  13. 35
      app.json
  14. 2
      backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs
  15. 4
      backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs
  16. 2
      backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs
  17. 3
      backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs
  18. 3
      backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs
  19. 3
      backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs
  20. 2
      backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs
  21. 10
      backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj
  22. 3
      backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs
  23. 5
      backend/i18n/clean.bat
  24. 12
      backend/i18n/frontend_en.json
  25. 6
      backend/i18n/frontend_it.json
  26. 6
      backend/i18n/frontend_nl.json
  27. 979
      backend/i18n/frontend_zh.json
  28. 2
      backend/i18n/source/backend_it.json
  29. 1
      backend/i18n/source/backend_nl.json
  30. 374
      backend/i18n/source/backend_zh.json
  31. 12
      backend/i18n/source/frontend_en.json
  32. 8
      backend/i18n/source/frontend_it.json
  33. 8
      backend/i18n/source/frontend_nl.json
  34. 975
      backend/i18n/source/frontend_zh.json
  35. 28
      backend/i18n/translator/Squidex.Translator/Commands.cs
  36. 35
      backend/i18n/translator/Squidex.Translator/Processes/Helper.cs
  37. 6
      backend/src/Migrations/RebuildRunner.cs
  38. 8
      backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs
  39. 2
      backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs
  40. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs
  41. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs
  42. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs
  43. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs
  44. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs
  45. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs
  46. 6
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs
  47. 9
      backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs
  48. 3
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs
  49. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs
  50. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs
  51. 8
      backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj
  52. 4
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs
  53. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs
  54. 97
      backend/src/Squidex.Domain.Apps.Core.Operations/TextHelpers.cs
  55. 5
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs
  56. 2
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs
  57. 13
      backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs
  58. 6
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs
  59. 31
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs
  60. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs
  61. 45
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs
  62. 12
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs
  63. 14
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs
  64. 14
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs
  65. 42
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs
  66. 14
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs
  67. 12
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs
  68. 18
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs
  69. 8
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs
  70. 2
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj
  71. 5
      backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs
  72. 23
      backend/src/Squidex.Domain.Apps.Entities/AppTag.cs
  73. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs
  74. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs
  75. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs
  76. 4
      backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs
  77. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppImageStore.cs
  78. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs
  79. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs
  80. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs
  81. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs
  82. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs
  83. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs
  84. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs
  85. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/ConfigAppPlansProvider.cs
  86. 4
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageGate.cs
  87. 5
      backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs
  88. 3
      backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs
  89. 2
      backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs
  90. 5
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs
  91. 30
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs
  92. 8
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs
  93. 5
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs
  94. 2
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs
  95. 127
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs
  96. 71
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs
  97. 4
      backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs
  98. 8
      backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs
  99. 92
      backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs
  100. 8
      backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs

24
.github/workflows/dev.yml

@ -33,13 +33,23 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: BUILD
uses: docker/build-push-action@v2
with:
push: false
load: true
tags: squidex-tmp
build-args: "SQUIDEX__VERSION=4.0.0-dev-${{ env.BUILD_NUMBER }}"
build-args: "SQUIDEX__VERSION=5.0.0-dev-${{ env.BUILD_NUMBER }}"
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
- name: Export Image
run: docker save squidex-tmp | gzip > squidex-tmp.tar.gz
@ -50,6 +60,11 @@ jobs:
path: squidex-tmp.tar.gz
key: squidex-dev-image-${{ github.sha }}
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
test:
needs: build
runs-on: ubuntu-latest
@ -95,6 +110,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()

14
.github/workflows/marketplace-aws.yml

@ -17,8 +17,7 @@ jobs:
with:
command: validate
arguments: -syntax-only
target: aws.pkr.hcl
working-directory: packer/
target: packer/aws.pkr.hcl
publish:
needs: validate
@ -30,15 +29,22 @@ jobs:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: Init
uses: hashicorp/packer-github-actions@master
with:
command: init
target: packer/aws.pkr.hcl
env:
PACKER_LOG: 1
- name: Build
uses: hashicorp/packer-github-actions@master
with:
command: build
arguments: "-color=false -on-error=abort"
target: aws.pkr.hcl
target: packer/aws.pkr.hcl
env:
PACKER_LOG: 1
PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}"
AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
working-directory: packer/

22
.github/workflows/marketplace-azure.yml

@ -5,6 +5,8 @@ on:
release:
types: [ released ]
# az ad sp create-for-rbac --name squidex
jobs:
validate:
runs-on: ubuntu-latest
@ -13,13 +15,11 @@ jobs:
uses: actions/checkout@v2
- name: Validate Template
if: false
uses: hashicorp/packer-github-actions@master
with:
command: validate
arguments: -syntax-only
target: azure.pkr.hcl
working-directory: packer/
target: packer/azure.pkr.hcl
publish:
needs: validate
@ -31,14 +31,24 @@ jobs:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: Init
uses: hashicorp/packer-github-actions@master
with:
command: init
target: packer/azure.pkr.hcl
env:
PACKER_LOG: 1
- name: Build
if: false
uses: hashicorp/packer-github-actions@master
with:
command: build
arguments: "-color=false -on-error=abort"
target: azure.pkr.hcl
target: packer/azure.pkr.hcl
env:
PACKER_LOG: 1
PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}"
working-directory: packer/
PKR_VAR_subscription_id: "${{ secrets.AZURE_SUBSCRIPTION_ID }}"
PKR_VAR_tenant_id: "${{ secrets.AZURE_TENANT_ID }}"
PKR_VAR_client_id: "${{ secrets.AZURE_CLIENT_ID }}"
PKR_VAR_client_secret: "${{ secrets.AZURE_CLIENT_SECRET }}"

14
.github/workflows/marketplace-digitalocean.yml

@ -17,8 +17,7 @@ jobs:
with:
command: validate
arguments: -syntax-only
target: digitalocean.pkr.hcl
working-directory: packer/
target: packer/digitalocean.pkr.hcl
publish:
needs: validate
@ -30,14 +29,21 @@ jobs:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: Init
uses: hashicorp/packer-github-actions@master
with:
command: init
target: packer/digitalocean.pkr.hcl
env:
PACKER_LOG: 1
- name: Build
uses: hashicorp/packer-github-actions@master
with:
command: build
arguments: "-color=false -on-error=abort"
target: digitalocean.pkr.hcl
target: packer/digitalocean.pkr.hcl
env:
PACKER_LOG: 1
PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}"
DIGITALOCEAN_API_TOKEN: "${{ secrets.DIGITALOCEAN_API_TOKEN }}"
working-directory: packer/

20
.github/workflows/marketplace-gcp.yml

@ -13,13 +13,11 @@ jobs:
uses: actions/checkout@v2
- name: Validate Template
if: false
uses: hashicorp/packer-github-actions@master
with:
command: validate
arguments: -syntax-only
target: gcp.pkr.hcl
working-directory: packer/
target: packer/gcp.pkr.hcl
publish:
needs: validate
@ -31,14 +29,24 @@ jobs:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: Extract service account
run: echo "${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}" | base64 -d > /tmp/squidex.json
- name: Init
uses: hashicorp/packer-github-actions@master
with:
command: init
target: packer/gcp.pkr.hcl
env:
PACKER_LOG: 1
- name: Build
if: false
uses: hashicorp/packer-github-actions@master
with:
command: build
arguments: "-color=false -on-error=abort"
target: gcp.pkr.hcl
target: packer/gcp.pkr.hcl
env:
PACKER_LOG: 1
PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}"
working-directory: packer/
GOOGLE_APPLICATION_CREDENTIALS: "/tmp/squidex.json"

44
.github/workflows/marketplace-heroku.yml

@ -1,44 +0,0 @@
name: Marketplace - Heroku
concurrency: marketplace-heroku
on:
release:
types: [ released ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Validate Template
if: false
uses: hashicorp/packer-github-actions@master
with:
command: validate
arguments: -syntax-only
target: heroku.pkr.hcl
working-directory: packer/
publish:
needs: validate
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: Build
if: false
uses: hashicorp/packer-github-actions@master
with:
command: build
arguments: "-color=false -on-error=abort"
target: heroku.pkr.hcl
env:
PACKER_LOG: 1
PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}"
working-directory: packer/

44
.github/workflows/marketplace-render.yml

@ -1,44 +0,0 @@
name: Marketplace - Render
concurrency: marketplace-render
on:
release:
types: [ released ]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Validate Template
if: false
uses: hashicorp/packer-github-actions@master
with:
command: validate
arguments: -syntax-only
target: render.pkr.hcl
working-directory: packer/
publish:
needs: validate
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: Build
if: false
uses: hashicorp/packer-github-actions@master
with:
command: build
arguments: "-color=false -on-error=abort"
target: render.pkr.hcl
env:
PACKER_LOG: 1
PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}"
working-directory: packer/

17
.github/workflows/marketplace-vultr.yml

@ -13,13 +13,11 @@ jobs:
uses: actions/checkout@v2
- name: Validate Template
if: false
uses: hashicorp/packer-github-actions@master
with:
command: validate
arguments: -syntax-only
target: vultr.pkr.hcl
working-directory: packer/
target: packer/vultr.pkr.hcl
publish:
needs: validate
@ -31,14 +29,21 @@ jobs:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v3.x
- name: Init
uses: hashicorp/packer-github-actions@master
with:
command: init
target: packer/vultr.pkr.hcl
env:
PACKER_LOG: 1
- name: Build
if: false
uses: hashicorp/packer-github-actions@master
with:
command: build
arguments: "-color=false -on-error=abort"
target: vultr.pkr.hcl
target: packer/vultr.pkr.hcl
env:
PACKER_LOG: 1
PKR_VAR_squidex_version: "${{ env.GITHUB_REF_SLUG }}"
working-directory: packer/
VULTR_API_KEY: "${{ secrets.VULTR_API_KEY }}"

37
.github/workflows/release.yml

@ -22,6 +22,14 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: BUILD
uses: docker/build-push-action@v2
with:
@ -29,6 +37,8 @@ jobs:
load: true
tags: squidex-tmp
build-args: "SQUIDEX__VERSION=${{ env.GITHUB_REF_SLUG }}"
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
- name: Export Image
run: docker save squidex-tmp | gzip > squidex-tmp.tar.gz
@ -39,6 +49,11 @@ jobs:
path: squidex-tmp.tar.gz
key: squidex-release-image-${{ github.sha }}
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
test:
needs: build
runs-on: ubuntu-latest
@ -144,23 +159,23 @@ jobs:
- name: Load Image
run: docker load < squidex-tmp.tar.gz
- name: Get Binaries
run: |
mkdir /build 2> /dev/null
docker create --name squidex-container squidex-tmp
docker cp squidex-container:/app/. /build
- name: Make directories
run: sudo mkdir /build /release
- name: Create container
run: docker create --name squidex-container squidex-tmp:latest
- name: Get binaries
run: sudo docker cp squidex-container:/app/. /build
- name: ZIP Binaries
run: |
mkdir /release 2> /dev/null
zip -r /release/binaries.zip .
run: sudo zip -r /release/binaries.zip .
working-directory: /build
- name: Get Changelog Entry
id: changelog_reader
uses: mindsers/changelog-reader-action@v2
with:
validation_depth: 10
version: ${{ env.GITHUB_REF_SLUG }}
path: ./CHANGELOG.md
@ -169,10 +184,10 @@ jobs:
with:
allowUpdates: true
artifactErrorsFailBuild: true
artifact: "/release/binaries.zip"
artifacts: "/release/binaries.zip"
body: ${{ steps.changelog_reader.outputs.changes }}
name: ${{ env.GITHUB_REF_SLUG }}
replaceArtifacts: true
replacesArtifacts: true
token: ${{ secrets.GITHUB_TOKEN }}
- name: Cleanup Binaries

28
CHANGELOG.md

@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [5.8.0] - 2021-06-28
### Fixed
* **API**: Fixes for anonymous write access.
* **API**: Fixes for client access when creating apps.
* **Schemas**: Correct sync of field rules, especially for CLI.
* **UserManagement**: Fix pagination
* **UI**: Encode IDs to allow custom content IDs with slash.
* **UI**: Fixes typos for italian translation.
* **UI**: Allow scrolling when content is disabled.
* **UI**: Fixes references/referencing view for localized content.
* **UI**: Fix confirm click.
### Added
* **API**: Better timeout and cancellation handling.
* **API**: Default timeouts for most important MongoDB calls.
* **API**: Better API tests to improve stability.
* **Assets**: Additional configuration flag to allow one folder per asset.
* **Contents**: Read published contents from secondary MongodB instances for better load distribution.
* **Contents**: Better indexes for improved performance.
* **Rules**: New liquid and javascript extensions to read the asset as text in rules.
* **Rules**: Simpler syntax to resolve assets and contents in liquid templates.
* **Contents**: Array builder when building custom code extension.
* **UI**: Show SVG as images when in contnet overview.
* **UI**: Chinese translation.
## [5.7.1] - 2021-05-21
### Fixed

2
Dockerfile

@ -3,7 +3,7 @@
#
FROM mcr.microsoft.com/dotnet/sdk:5.0 as backend
ARG SQUIDEX__VERSION=4.0.0
ARG SQUIDEX__VERSION=5.0.0
WORKDIR /src

18
README.md

@ -25,6 +25,24 @@ Current Version ![GitHub release](https://img.shields.io/github/release/squidex/
* [MongoDB](https://www.mongodb.com/)
* [.NET Core SDK](https://www.microsoft.com/net/download/core#/current) (Already part of Visual Studio 2017)
## Deployment Options
| Platform | Documentation | Quicklink |
| -------- | ------------- | ---- |
| Azure | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-azure) |
| AWS | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-aws) |
| Docker | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-docker) | [![Docker Compose](https://img.shields.io/badge/-docker--compose.yml-2496ED?style=for-the-badge&logo=docker&logoColor=ffffff)](https://github.com/Squidex/squidex-hosting/blob/master/docker-compose/docker-compose.yml) |
| GCP | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-gcp) |
| Heroku | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-heroku) | [![Deploy to Heroku](https://img.shields.io/badge/-Deploy%20to%20Heroku-430098?style=for-the-badge&logo=heroku&logoColor=ffffff)](https://heroku.com/deploy?template=https://github.com/Squidex/squidex) |
| IIS | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-azure) |
| k8 | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-kubernetes) |
| Render | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-render) | [![Deploy to Render](https://img.shields.io/badge/-Deploy%20to%20Render-44E4B4?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAADyklEQVR4Xu1aT0gUURh/k5qV2y5pEbpqHiqqy4IIQUGghbNOK53WkwQFdekqRHXNDkHXIKhuXdZTZu6MRkbkJRFJKAKNUnQPEir+IXdr98XbdXbfzrznjLvvObM7s5dld7553/f93u/78/4IwOEfweH+AxcAlwEOR8ANAYcTwE2CfEIAAqEzpjSmQPLobhj2zi9N4fLiotxKet87vvZloKcnqT6jyW2sx2fGz1xb38kGJgCEv0b2h/yBvuu+0/0kZRAAAtX0/wqCkGcPhBAJ6T7SbNQXPSWtqQ9ocsFFpUNpDI5xBeBnYn2+pcrTtJuZpsnSANBCRQNAK8cVgM7F4W6lvmuQZRYxy4De2ajvlSkGvO1QGkPsGXB/aeJF/7G2myxmHR/DGIBMREizsnUhcO/3xMtHdW03WDuPxjMGIKPVshwgLshXZb84lO88OcUVApDtAYAwBUn5XHV2Kbn1tzc2FhptlkYKAcCKd0yXwbnExnxzVQ012z9emb5ztzbw1AonitFpCgBU5yPnwnGaouCCcl5pCn4uxhCr3jUFwLPVbw9v+84+0BkJAXiyMn2rry7w3CoHkN7u2JtDJP0HPv2J4x0jScYUALROaym5lTheebDaSueRbu6dIE2BOBe9PNIivS9vACAQIIApIn00vbtVQOgnKFOambTCV2KDzaP13XOlBUDGWiYAoKVmtEGcJCULbeNiHwYwBkBuECdLiwEMQwAxwO4AFMM8wzLoAuAyYDsECIs+uyRBx4dACkJIimVmZTAvCWJMsAsDuLbCpZAE9xYACADiG6KcAxmQ4b8aBQ4EIJNvHQ+AWnbswgDuZdDuiyHuAOTKYH435BgGuIuhklsO89wP2I4C9LXPjltiWJTyaYWxjGOXHMC3E/wlt8onSm1HiGcIZNsgG7XCKZjeu9aeWzoqBEhn1EwACMNIxcaypyZNqmUAQG0uCUTrcvd0imlGin23a2bYSxrDc7Jrc0AQspepCj4aK9ZAO79vuClqZ+NZ2OYCYISi2R0hdEQ9WB/aNHOAQqvb1Csyaobb/t7TO0Kirg/I5VvcYBUAUjY2e/fHSC49NgRA+kG5KIkrhwAEYwwuSuIM0DpHAqAQBtA2WEzfFE33AXrNTMpgFgCDcwGuIaDxbW9DwOTJkAsAzyRoJQMufn992OOpbsvZ8A8AUJn+id/EDkciFWsXvJdwW5EUktbe2BYX5HZSrtDLDbWrunB5r1/8iHd4tPHiNdVTH460r+5U6dw+wKgPKPfnLgPKfYaN/HMZYIRQuT93GVDuM2zk338nAalfI74e1QAAAABJRU5ErkJggg==&logoColor=ffffff)](https://render.com/deploy?repo=https://github.com/Squidex/squidex) |
| Vultr | [Docs](https://docs.squidex.io/01-getting-started/installation/platforms/install-vultr) | [![Deploy to Vultr](https://img.shields.io/badge/-Deploy%20to%20Vultr-007BFC?style=for-the-badge&logo=vultr&logoColor=ffffff)](https://www.vultr.com/marketplace/apps/squidex) |
## Contributors
### Core Team and Founders

35
app.json

@ -0,0 +1,35 @@
{
"name": "Squidex",
"description": "Headless CMS and Content Managment Hub",
"website": "https://squidex.io/",
"repository": "https://github.com/Squidex/squidex",
"logo": "https://avatars.githubusercontent.com/u/25371797?s=200&v=4",
"success_url": "/",
"env": {
"DOMAIN": {
"description": "The domain name under which your instance is available",
"value": "https://[YOUR-HEROKU-APPNAME].herokuapp.com"
},
"MONGO_USERNAME": {
"description": "Mongo Username, follow https://devcenter.heroku.com/articles/ormongo#open-the-dashboard to create a database named Squidex and then edit the deployment to reflect the credentials you used",
"value": "Squidex"
},
"MONGO_PASSWORD": {
"description": "Mongo Password (see MONGO_USERNAME)",
"value": "Squidex123"
}
},
"formation": {
"web": {
"quantity": 1,
"size": "standard-1x"
}
},
"addons": [
{
"plan": "ormongo:2-mmap",
"as": "MONGO"
}
],
"stack": "container"
}

2
backend/extensions/Squidex.Extensions/Actions/Comment/CommentActionHandler.cs

@ -23,8 +23,6 @@ namespace Squidex.Extensions.Actions.Comment
public CommentActionHandler(RuleEventFormatter formatter, ICommandBus commandBus)
: base(formatter)
{
Guard.NotNull(commandBus, nameof(commandBus));
this.commandBus = commandBus;
}

4
backend/extensions/Squidex.Extensions/Actions/CreateContent/CreateContentActionHandler.cs

@ -30,10 +30,6 @@ namespace Squidex.Extensions.Actions.CreateContent
public CreateContentActionHandler(RuleEventFormatter formatter, IAppProvider appProvider, ICommandBus commandBus, IJsonSerializer jsonSerializer)
: base(formatter)
{
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(commandBus, nameof(commandBus));
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
this.appProvider = appProvider;
this.commandBus = commandBus;
this.jsonSerializer = jsonSerializer;

2
backend/extensions/Squidex.Extensions/Actions/Fastly/FastlyActionHandler.cs

@ -24,8 +24,6 @@ namespace Squidex.Extensions.Actions.Fastly
public FastlyActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{
Guard.NotNull(httpClientFactory, nameof(httpClientFactory));
this.httpClientFactory = httpClientFactory;
}

3
backend/extensions/Squidex.Extensions/Actions/Notification/NotificationActionHandler.cs

@ -27,9 +27,6 @@ namespace Squidex.Extensions.Actions.Notification
public NotificationActionHandler(RuleEventFormatter formatter, ICommandBus commandBus, IUserResolver userResolver)
: base(formatter)
{
Guard.NotNull(commandBus, nameof(commandBus));
Guard.NotNull(userResolver, nameof(userResolver));
this.commandBus = commandBus;
this.userResolver = userResolver;

3
backend/extensions/Squidex.Extensions/Actions/Slack/SlackActionHandler.cs

@ -12,7 +12,6 @@ using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure;
namespace Squidex.Extensions.Actions.Slack
{
@ -25,8 +24,6 @@ namespace Squidex.Extensions.Actions.Slack
public SlackActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{
Guard.NotNull(httpClientFactory, nameof(httpClientFactory));
this.httpClientFactory = httpClientFactory;
}

3
backend/extensions/Squidex.Extensions/Actions/Twitter/TweetActionHandler.cs

@ -12,7 +12,6 @@ using CoreTweet;
using Microsoft.Extensions.Options;
using Squidex.Domain.Apps.Core.HandleRules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure;
namespace Squidex.Extensions.Actions.Twitter
{
@ -25,8 +24,6 @@ namespace Squidex.Extensions.Actions.Twitter
public TweetActionHandler(RuleEventFormatter formatter, IOptions<TwitterOptions> twitterOptions)
: base(formatter)
{
Guard.NotNull(twitterOptions, nameof(twitterOptions));
this.twitterOptions = twitterOptions.Value;
}

2
backend/extensions/Squidex.Extensions/Actions/Webhook/WebhookActionHandler.cs

@ -23,8 +23,6 @@ namespace Squidex.Extensions.Actions.Webhook
public WebhookActionHandler(RuleEventFormatter formatter, IHttpClientFactory httpClientFactory)
: base(formatter)
{
Guard.NotNull(httpClientFactory, nameof(httpClientFactory));
this.httpClientFactory = httpClientFactory;
}

10
backend/extensions/Squidex.Extensions/Squidex.Extensions.csproj

@ -8,16 +8,16 @@
<ProjectReference Include="..\..\src\Squidex.Web\Squidex.Web.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Algolia.Search" Version="6.9.1" />
<PackageReference Include="Algolia.Search" Version="6.10.1" />
<PackageReference Include="Confluent.Apache.Avro" Version="1.7.7.7" />
<PackageReference Include="Confluent.Kafka" Version="1.6.3" />
<PackageReference Include="Confluent.Kafka" Version="1.7.0" />
<PackageReference Include="Confluent.SchemaRegistry.Serdes" Version="1.3.0" />
<PackageReference Include="CoreTweet" Version="1.0.0.483" />
<PackageReference Include="Datadog.Trace" Version="1.25.0" />
<PackageReference Include="Elasticsearch.Net" Version="7.12.0" />
<PackageReference Include="Datadog.Trace" Version="1.27.1" />
<PackageReference Include="Elasticsearch.Net" Version="7.13.1" />
<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.17.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.8.3" />
<PackageReference Include="Microsoft.OData.Core" Version="7.9.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NodaTime" Version="3.0.5" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />

3
backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidatorFactory.cs

@ -9,7 +9,6 @@ using System;
using System.Collections.Generic;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Entities.Contents.Repositories;
using Squidex.Infrastructure;
namespace Squidex.Extensions.Validation
{
@ -20,8 +19,6 @@ namespace Squidex.Extensions.Validation
public CompositeUniqueValidatorFactory(IContentRepository contentRepository)
{
Guard.NotNull(contentRepository, nameof(contentRepository));
this.contentRepository = contentRepository;
}

5
backend/i18n/clean.bat

@ -0,0 +1,5 @@
cd translator\Squidex.Translator
dotnet run translate clean-backend ..\..\..\..
dotnet run translate clean-frontend ..\..\..\..

12
backend/i18n/frontend_en.json

@ -336,10 +336,10 @@
"common.separateByLine": "Separate by line",
"common.settings": "Settings",
"common.sidebar": "Sidebar Extension",
"common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.",
"common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history of 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 -",
@ -761,9 +761,11 @@
"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.format": "Pattern",
"schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format",
"schemas.fieldTypes.dateTime.rangeMax": "Max Value",
"schemas.fieldTypes.dateTime.rangeMin": "Min Value",
"schemas.fieldTypes.geolocation.description": "Coordinates: latitude and longitude.",
@ -777,6 +779,7 @@
"schemas.fieldTypes.references.countMin": "Min Items",
"schemas.fieldTypes.references.description": "Links to other content items.",
"schemas.fieldTypes.references.mustBePublished": "References must be published",
"schemas.fieldTypes.references.resolve": "Resolve references",
"schemas.fieldTypes.references.resolveHint": "Show the name of the referenced item in content list when MaxItems is set to 1.",
"schemas.fieldTypes.string.characters": "Characters",
"schemas.fieldTypes.string.charactersMax": "Max Characters",
@ -941,6 +944,7 @@
"validation.min": "{field|upper} must be greater or equal to '{min}'.",
"validation.minlength": "{field|upper} must have at least {requiredlength} item(s).",
"validation.minlengthstring": "{field|upper} must have at least {requiredlength} character(s).",
"validation.notAnValidSvg": "The SVG is malicious and contains script tags.",
"validation.pattern": "{field|upper} does not match to the pattern.",
"validation.patternmessage": "{message}",
"validation.required": "{field|upper} is required.",

6
backend/i18n/frontend_it.json

@ -761,9 +761,11 @@
"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.format": "Pattern",
"schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format",
"schemas.fieldTypes.dateTime.rangeMax": "Valore Max",
"schemas.fieldTypes.dateTime.rangeMin": "Valore Min",
"schemas.fieldTypes.geolocation.description": "Coordinate: latitudine e longitudine.",
@ -777,6 +779,7 @@
"schemas.fieldTypes.references.countMin": "Numero Min Elementi",
"schemas.fieldTypes.references.description": "Link ad altri elementi del contenuto.",
"schemas.fieldTypes.references.mustBePublished": "I contenuti collegati devono essere pubblicati",
"schemas.fieldTypes.references.resolve": "Resolve references",
"schemas.fieldTypes.references.resolveHint": "Mostra il nome dell'elemento collegato (riferimento) nella lista dei contenuti quando il numero massimo di elementi è impostato a 1.",
"schemas.fieldTypes.string.characters": "Caratteri",
"schemas.fieldTypes.string.charactersMax": "Max numero di Caratteri",
@ -941,6 +944,7 @@
"validation.min": "{field|upper} deve essere maggiore o uguale a '{min}'.",
"validation.minlength": "{field|upper} deve avere almeno {requiredlength} elemento(i).",
"validation.minlengthstring": "{field|upper} deve avere almeno {requiredlength} carattere(i).",
"validation.notAnValidSvg": "The SVG is malicious and contains script tags.",
"validation.pattern": "{field|upper} non corrisponde al pattern.",
"validation.patternmessage": "{message}",
"validation.required": "{field|upper} è obbligatorio.",

6
backend/i18n/frontend_nl.json

@ -761,9 +761,11 @@
"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.format": "Pattern",
"schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format",
"schemas.fieldTypes.dateTime.rangeMax": "Max. waarde",
"schemas.fieldTypes.dateTime.rangeMin": "Min. waarde",
"schemas.fieldTypes.geolocation.description": "Coördinaten: lengte- en breedtegraad.",
@ -777,6 +779,7 @@
"schemas.fieldTypes.references.countMin": "Min. items",
"schemas.fieldTypes.references.description": "Links naar andere inhoudsitems.",
"schemas.fieldTypes.references.mustBePublished": "References must be published",
"schemas.fieldTypes.references.resolve": "Resolve references",
"schemas.fieldTypes.references.resolveHint": "Toon de naam van het item waarnaar wordt verwezen in de inhoudslijst wanneer MaxItems is ingesteld op 1.",
"schemas.fieldTypes.string.characters": "Karakters",
"schemas.fieldTypes.string.charactersMax": "Max. karakters",
@ -941,6 +944,7 @@
"validation.min": "{field|upper} moet groter zijn dan of gelijk zijn aan '{min}'.",
"validation.minlength": "{field|upper} moet minimaal {requiredlength} item (s) bevatten.",
"validation.minlengthstring": "{field|upper} moet minimaal {requiredlength} teken (s) bevatten.",
"validation.notAnValidSvg": "The SVG is malicious and contains script tags.",
"validation.pattern": "{field|upper} komt niet overeen met het patroon.",
"validation.patternmessage": "{message}",
"validation.required": "{field|upper} is vereist.",

979
backend/i18n/frontend_zh.json

@ -0,0 +1,979 @@
{
"api.contentApi": "内容 API",
"api.generalApi": "通用 API",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.pageTitle": "API",
"api.title": "API",
"apps.allApps": "所有应用程序",
"apps.appLoadFailed": "加载应用失败。请重新加载。",
"apps.appNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。",
"apps.appNameValidationMessage": "名称可以包含小写字母 (a-z)、数字和破折号。",
"apps.appNameWarning": "以后不能更改应用名称。",
"apps.appsButtonCreate": "应用概览",
"apps.appsButtonFallbackTitle": "应用概览",
"apps.archive": "存档应用",
"apps.archiveConfirmText": "你真的要存档这个应用程序吗?",
"apps.archiveConfirmTitle": "存档应用程序",
"apps.archiveFailed": "存档应用失败。请重新加载。",
"apps.archiveWarning": "一旦你归档了一个应用程序,就没有回头路了。请确定。",
"apps.create": "创建应用程序",
"apps.createBlankApp": "新应用程序",
"apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。",
"apps.createBlogApp": "新博客示例",
"apps.createBlogAppDescription": "从我们准备使用的博客开始。",
"apps.createFailed": "创建应用失败。请重新加载。",
"apps.createProfileApp": "新配置文件示例",
"apps.createProfileAppDescription": "创建您的个人资料页面。",
"apps.createWithTemplate": "创建 {template} 示例",
"apps.empty": "您还没有与任何应用协作",
"apps.generalSettings": "通用",
"apps.generalSettingsDangerZone": "通用",
"apps.image": "图片",
"apps.imageDrop": "拖放上传",
"apps.leave": "离开应用程序",
"apps.leaveConfirmText": "你真的要离开这个应用程序吗?",
"apps.leaveConfirmTitle": "离开应用程序。",
"apps.leaveFailed": "退出应用失败,请重新加载。",
"apps.listPageTitle": "应用程序",
"apps.loadFailed": "加载应用失败。请重新加载。",
"apps.loadSettingsFailed": "更新界面设置失败。请重新加载。",
"apps.removeImage": "删除图片",
"apps.removeImageFailed": "删除应用图片失败。请重新加载。",
"apps.updateFailed": "更新应用失败。请重新加载。",
"apps.updateSettingsFailed": "更新界面设置失败。请重新加载。",
"apps.upgradeHintCurrent": "你在 {plan} 计划中。",
"apps.upgradeHintUpgrade": "升级!",
"apps.uploadImage": "拖放文件以替换应用图片。使用正方形大小。",
"apps.uploadImageButton": "上传文件",
"apps.uploadImageFailed": "上传图片失败,请重新加载。",
"apps.uploadImageTooBig": "应用图片太大。",
"apps.welcomeSubtitle": "欢迎来到 Squidex。",
"apps.welcomeTitle": "Hi {user}",
"appSettings.editors.deleteConfirmText": "你真的要删除这个编辑器 URL 吗?",
"appSettings.editors.deleteConfirmTitle": "删除编辑器 URL",
"appSettings.editors.empty": "尚未创建编辑器 URL。",
"appSettings.editors.title": "自定义编辑器",
"appSettings.hideScheduler": "隐藏预定发布对话框",
"appSettings.patterns.deleteConfirmText": "你真的要删除这个模式吗?",
"appSettings.patterns.deleteConfirmTitle": "删除模式",
"appSettings.patterns.empty": "尚未创建模式。",
"appSettings.patterns.title": "模式",
"appSettings.refreshTooltip": "刷新 UI 设置 (CTRL + SHIFT + R)",
"appSettings.reloaded": "UI 设置重新加载。",
"appSettings.title": "界面设置",
"assets.createFolder": "创建文件夹",
"assets.createFolderFailed": "资源文件夹创建失败,请重新加载。",
"assets.createFolderTooltip": "创建新文件夹 (CTRL + SHIFT + G)",
"assets.deleteConfirmText": "你真的要删除资源吗?",
"assets.deleteConfirmTitle": "删除资源",
"assets.deleteFailed": "资源删除失败,请重新加载。",
"assets.deleteFolderConfirmText": "您真的要删除文件夹和所有资源吗?",
"assets.deleteFolderConfirmTitle": "删除文件夹",
"assets.deleteMetadataConfirmText": "你真的要删除这个元数据吗?",
"assets.deleteMetadataConfirmTitle": "删除元数据",
"assets.deleteReferrerConfirmText": "资源被内容项引用。\n\n您真的要删除资源吗?",
"assets.deleteReferrerConfirmTitle": "删除资源",
"assets.downloadVersion": "下载此版本",
"assets.dropToUpdate": "下拉更新",
"assets.duplicateFile": "资源已经上传。",
"assets.editor.flipHorizontally": "水平翻转",
"assets.editor.flipVertically": "垂直翻转",
"assets.editor.focusPointLabel": "选择焦点位置",
"assets.editor.focusPointPreview": "不同尺寸的预览",
"assets.editor.rotateLeft": "向左旋转",
"assets.editor.rotateRight": "向右旋转",
"assets.fileTooBig": "资源太大。",
"assets.folderName": "文件夹名称",
"assets.folderNameHint": "文件夹名称用作显示名称,不能唯一。",
"assets.insertAssets": "插入资源",
"assets.linkSelected": "链接选定的资源 ({count})",
"assets.listPageTitle": "资源",
"assets.loadFailed": "资源加载失败,请重新加载。",
"assets.loadFoldersFailed": "加载资源文件夹失败。请重新加载。",
"assets.metadata": "元数据",
"assets.metadataAdd": "添加元数据",
"assets.moveFailed": "资源移动失败。请重新加载。",
"assets.protected": "受保护",
"assets.refreshTooltip": "刷新资源 (CTRL + SHIFT + R)",
"assets.reloaded": "资源重新加载。",
"assets.removeConfirmText": "你真的要移除资源吗?",
"assets.removeConfirmTitle": "移除资源",
"assets.renameFolder": "重命名文件夹",
"assets.replaceConfirmText": "你真的想用更新的版本替换资源吗",
"assets.replaceConfirmTitle": "替换资源?",
"assets.replaceFailed": "替换资源失败。请重新加载。",
"assets.searchByName": "按名称搜索",
"assets.searchByTags": "按标签搜索",
"assets.selectMany": "选择资源",
"assets.specialFolder.parent": "<Parent>",
"assets.specialFolder.root": "<Root>",
"assets.tabFocusPoint": "焦点",
"assets.tabHistory": "历史",
"assets.tabImage": "图片",
"assets.tabMetadata": "元数据",
"assets.tabPreview": "预览",
"assets.tabTextEditor": "文本编辑器",
"assets.updated": "资源已更新。",
"assets.updateFailed": "更新资源失败。请重新加载。",
"assets.updateFolderFailed": "更新资源文件夹失败。请重新加载。",
"assets.uploadByDialog": "选择文件",
"assets.uploadByDrop": "将文件拖放到此处进行上传",
"assets.uploaderUploadHere": "没有正在进行的上传,将文件拖到这里。",
"assets.uploadFailed": "资源上传失败,请重新加载。",
"assets.uploadHint": "在现有项目上放置文件以使用更新版本替换资源。",
"backups.backupCountAssetsLabel": "资源",
"backups.backupCountAssetsTooltip": "存档资源",
"backups.backupCountEventsLabel": "事件",
"backups.backupCountEventsTooltip": "存档事件",
"backups.backupDownload": "下载",
"backups.backupDownloadLink": "准备就绪",
"backups.backupDuration": "持续时间",
"backups.deleteConfirmText": "你真的要删除备份吗?",
"backups.deleteConfirmTitle": "删除备份",
"backups.deleted": "备份即将被删除。",
"backups.deleteFailed": "删除备份失败。",
"backups.empty": "尚未创建备份。",
"backups.loadFailed": "加载备份失败。",
"backups.maximumReached": "您已达到最大备份数:10。",
"backups.refreshTooltip": "刷新备份 (CTRL + SHIFT + R)",
"backups.reloaded": "备份已重新加载。",
"backups.restore": "恢复备份",
"backups.restoreFailed": "无法开始恢复。",
"backups.restoreLastStatus": "上次还原操作",
"backups.restoreLastUrl": "要备份的网址",
"backups.restoreNewAppName": "可选的应用程序名称",
"backups.restorePageTitle": "恢复备份",
"backups.restoreStarted": "恢复开始,可能需要几分钟才能完成。",
"backups.restoreStartedLabel": "开始",
"backups.restoreStoppedLabel": "已停止",
"backups.restoreTitle": "恢复备份",
"backups.start": "开始备份",
"backups.started": "备份已开始,可能需要几分钟才能完成。",
"backups.startedLabel": "开始",
"backups.startFailed": "启动备份失败。",
"clients.add": "添加客户端",
"clients.addFailed": "添加客户端失败,请重新加载。",
"clients.allowAnonymous": "允许匿名访问。",
"clients.allowAnonymousHint": "允许在没有访问令牌的情况下访问通过此客户端角色配置的所有资源的 API。不要给多个客户端匿名访问。",
"clients.apiCallsLimit": "最大 API 调用数",
"clients.apiCallsLimitHint": "限制此客户端每月可以进行的 API 调用次数,以保护您的 API 队伍为其他更重要的客户端提供服务。",
"clients.clientIdValidationMessage": "名称只能包含字母、数字、破折号和空格。",
"clients.clientNamePlaceholder": "输入客户端名称",
"clients.connect": "连接",
"clients.connectWizard.cli": "连接 Squidex CLI",
"clients.connectWizard.cliHint": "下载 CLI 并连接到此应用程序以启动备份、同步Schemas或导出内容。",
"clients.connectWizard.cliStep1": "获取最新的 Squidex CLI",
"clients.connectWizard.cliStep1Download": "[从 Github 下载 CLI](https://github.com/Squidex/squidex-samples/releases)",
"clients.connectWizard.cliStep1Hint": "这些版本包含适用于所有主要操作系统的二进制文件,如果您安装了 .NET Core,则可以下载一小部分。",
"clients.connectWizard.cliStep2": "将 `<你的 Squidex CLI 下载目录>` 添加到你的 `$PATH` 变量中",
"clients.connectWizard.cliStep3": "在 CLI 配置中添加你的应用名称",
"clients.connectWizard.cliStep3Hint": "您可以在 CLI 中管理多个应用程序的配置并切换到一个应用程序。",
"clients.connectWizard.cliStep4": "在 CLI 中切换到您的应用程序",
"clients.connectWizard.manually": "手动连接",
"clients.connectWizard.manuallyHint": "获取如何与 Postman 或 curl 建立连接的说明。",
"clients.connectWizard.manuallyStep1": "使用 curl 获取令牌",
"clients.connectWizard.manuallyStep2": "只需使用以下令牌",
"clients.connectWizard.manuallyStep3": "将令牌作为 HTTP 标头添加到所有请求中",
"clients.connectWizard.manuallyTokenHint": "令牌通常会在 30 天后过期,但您可以请求多个令牌。",
"clients.connectWizard.postManDocs": "从 [文档](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman) 中的 Postman 教程开始。",
"clients.connectWizard.sdk": "使用 SDK 连接到您的应用程序",
"clients.connectWizard.sdkHelp": "你需要另一个 SDK?",
"clients.connectWizard.sdkHelpLink": "在支持论坛联系我们",
"clients.connectWizard.sdkHint": "下载 SDK 并建立与此应用程序的连接。",
"clients.connectWizard.sdkStep1": "安装.NET SDK",
"clients.connectWizard.sdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "创建客户端管理器",
"clients.connectWizard.step0Title": "设置客户端",
"clients.connectWizard.step1Title": "选择连接方式",
"clients.connectWizard.step2Title": "连接",
"clients.deleteConfirmText": "你真的要撤销客户端吗?",
"clients.deleteConfirmTitle": "撤销客户端",
"clients.empty": "尚未创建客户端。",
"clients.loadFailed": "加载客户端失败。请重新加载。",
"clients.refreshTooltip": "刷新客户端 (CTRL + SHIFT + R)",
"clients.reloaded": "客户端重新加载。",
"clients.revokeFailed": "撤销客户端失败。请重新加载。",
"clients.tokenFailed": "创建令牌失败。请重试。",
"comments.create": "创建评论",
"comments.createFailed": "创建评论失败。",
"comments.deleteConfirmText": "你真的要删除评论吗?",
"comments.deleteConfirmTitle": "删除评论",
"comments.deleteFailed": "删除评论失败。",
"comments.follow": "关注",
"comments.loadFailed": "加载评论失败。",
"comments.title": "评论",
"comments.updateFailed": "更新评论失败。",
"common.actions": "动作",
"common.administration": "管理",
"common.administrationPageTitle": "管理",
"common.api": "API",
"common.apps": "应用程序",
"common.aspectRatio": "纵横比",
"common.assets": "资源",
"common.back": "返回",
"common.backendError": "后端错误",
"common.backups": "备份",
"common.bookmarks": "书签",
"common.bytes": "bytes",
"common.cancel": "取消",
"common.category": "类别",
"common.clear": "清除",
"common.clientId": "客户端 ID",
"common.clients": "客户端",
"common.clientSecret": "客户端密码",
"common.clipboardAdded": "值已添加到您的剪贴板。",
"common.clone": "克隆",
"common.cluster": "集群",
"common.clusterPageTitle": "集群",
"common.comments": "评论",
"common.components": "组件",
"common.confirm": "确认",
"common.consumers": "消费者",
"common.content": "内容",
"common.contents": "内容",
"common.continue": "继续",
"common.contributors": "贡献者",
"common.create": "创建",
"common.created": "创建",
"common.date": "日期",
"common.dateTimeEditor.local": "本地",
"common.dateTimeEditor.now": "现在",
"common.dateTimeEditor.nowTooltip": "现在使用 (UTC)",
"common.dateTimeEditor.today": "今天",
"common.dateTimeEditor.todayTooltip": "使用今天 (UTC)",
"common.dateTimeEditor.utc": "UTC",
"common.delete": "删除",
"common.description": "说明",
"common.designer": "设计师",
"common.disabled": "已禁用",
"common.displayName": "显示名称",
"common.edit": "编辑",
"common.email": "电子邮件",
"common.enabled": "已启用",
"common.error": "错误",
"common.errorBack": "返回上一页。",
"common.errorNoPermission": "您无权执行此操作。",
"common.errorNotFound": "未找到",
"common.event": "事件",
"common.events": "事件",
"common.executed": "已执行",
"common.expertMode": "专家模式",
"common.extension": "扩展名",
"common.failed": "失败",
"common.fallback": "后备",
"common.field": "字段",
"common.files": "文件",
"common.filters": "过滤器",
"common.folder": "文件夹",
"common.folders": "文件夹",
"common.generalSettings": "通用",
"common.generate": "生成",
"common.github": "Github",
"common.height": "高度",
"common.help": "帮助",
"common.helpTour": "单击帮助图标以显示上下文特定的帮助页面。转到",
"common.hide": "隐藏",
"common.hints": "提示",
"common.history": "历史",
"common.httpConflict": "更新失败。其他用户进行了更改。请重新加载。",
"common.httpLimit": "您已超出 API 调用的最大限制。",
"common.id": "身份",
"common.label": "标签",
"common.language": "语言",
"common.languages": "语言",
"common.latitudeShort": "纬度",
"common.loading": "正在加载",
"common.logout": "注销",
"common.logs": "日志",
"common.longitudeShort": "Lon",
"common.mapHide": "隐藏地图",
"common.mapShow": "显示地图",
"common.message": "消息",
"common.name": "名称",
"common.no": "不",
"common.nothingChanged": "什么都没有改变。",
"common.noValue": "- 无值 -",
"common.or": "或",
"common.pagerInfo": "{itemFirst}-{itemLast} 的 {numberOfItems}",
"common.password": "密码",
"common.passwordConfirm": "确认密码",
"common.pattern": "模式",
"common.patterns": "模式",
"common.permissions": "权限",
"common.preview": "预览",
"common.product": "Squidex Headless CMS",
"common.project": "项目",
"common.queryOperators.contains": "包含",
"common.queryOperators.empty": "为空",
"common.queryOperators.endsWith": "以",
"common.queryOperators.eq": "等于",
"common.queryOperators.exists": "exists",
"common.queryOperators.ge": "大于或等于",
"common.queryOperators.gt": "大于",
"common.queryOperators.le": "小于或等于",
"common.queryOperators.lt": "小于",
"common.queryOperators.matchs": "匹配",
"common.queryOperators.ne": "不等于",
"common.queryOperators.startsWith": "开始于",
"common.refresh": "刷新",
"common.remember": "不要再问了",
"common.rename": "重命名",
"common.requiredHint": "必需的",
"common.reset": "重置",
"common.restore": "恢复",
"common.role": "角色",
"common.roles": "角色",
"common.rule": "规则",
"common.rules": "规则",
"common.sampleCodeLabel": "示例代码在",
"common.save": "保存",
"common.saveShortcut": "CTRL + S",
"common.schemas": "Schemas",
"common.search": "搜索",
"common.searchGoogleMaps": "搜索谷歌地图",
"common.searchResults": "搜索结果",
"common.separateByLine": "按行分隔",
"common.settings": "设置",
"common.sidebar": "侧边栏扩展",
"common.sidebarTour": "侧边栏导航包含有用的上下文特定链接。您可以在此处查看此Schemas随时间变化的历史记录。",
"common.skipped": "Skipped",
"common.slug": "弹头",
"common.stars.max": "不得超过 15 颗星",
"common.status": "状态",
"common.statusChangeTo": "更改为",
"common.submit": "提交",
"common.subscription": "订阅",
"common.succeeded": "成功",
"common.tagAdd": ", 添加标签",
"common.tagAddReference": ", 添加引用",
"common.tagAddSchema": ", 添加Schemas",
"common.tags": "标签",
"common.tagsAll": "所有标签",
"common.time": "时间",
"common.update": "更新",
"common.upload": "上传",
"common.url": "URL",
"common.users": "用户",
"common.value": "值",
"common.width": "宽度",
"common.workflow": "工作流程",
"common.workflows": "工作流程",
"common.yes": "是",
"contents.addComponent": "添加组件",
"contents.arrayAddItem": "添加项目",
"contents.arrayClear": "清除",
"contents.arrayClearConfirmText": "你真的要清空数组吗?",
"contents.arrayClearConfirmTitle": "清除数组",
"contents.arrayCloneItem": "克隆这个项目",
"contents.arrayCollapseAll": "折叠所有项目",
"contents.arrayCollapseItem": "收起此项",
"contents.arrayExpandAll": "展开所有项目",
"contents.arrayExpandItem": "展开此项",
"contents.arrayMoveBottom": "将此项目移到底部",
"contents.arrayMoveDown": "将此项向下移动",
"contents.arrayMoveTop": "将此项移至顶部",
"contents.arrayMoveUp": "将此项向上移动",
"contents.arrayNoFields": "先添加一个嵌套字段来添加项目。",
"contents.assetsUpload": "删除文件或点击",
"contents.autotranslate": "从母语自动翻译",
"contents.bulkFailed": "删除或更新内容失败。请重新加载。",
"contents.changeStatusTo": "将内容项更改为 {action}",
"contents.changeStatusToImmediately": "立即设置为 {action}。",
"contents.changeStatusToLater": "在稍后的日期和时间设置为 {action}。",
"contents.componentNoSchema": "添加至少一个Schemas来设置组件。",
"contents.componentsNoSchema": "添加至少一个Schemas来添加组件。",
"contents.contentNotValid": "内容元素无效,请用所有语言(如果可本地化)检查左侧带有红色条的字段。",
"contents.contentTab.editor": "编辑器",
"contents.contentTab.references": "参考资料",
"contents.contentTab.referencing": "引用",
"contents.create": "新建",
"contents.createContentTooltip": "新建内容 (CTRL + SHIFT + G)",
"contents.created": "内容创建成功。",
"contents.createdByFieldDescription": "创建内容项的用户。",
"contents.createFailed": "创建内容失败,请重新加载。",
"contents.createFieldDescription": "创建内容项的日期时间。",
"contents.createPageTitle": "创建内容",
"contents.createTitle": "新内容",
"contents.currentStatusLabel": "当前版本",
"contents.deleteConfirmText": "你真的要删除内容吗?",
"contents.deleteConfirmTitle": "删除内容",
"contents.deleteManyConfirmText": "您真的要删除选定的内容项吗?",
"contents.deleteReferrerConfirmText": "该内容被另一个内容项引用。\n\n您真的要删除该内容吗?",
"contents.deleteReferrerConfirmTitle": "删除内容",
"contents.deleteVersionConfirmText": "你真的要删除这个版本吗?",
"contents.deleteVersionFailed": "删除版本失败。请重新加载。",
"contents.draftNew": "新草稿",
"contents.draftStatus": "新版本",
"contents.editPageTitle": "编辑内容",
"contents.invariantFieldDescription": "内容项的 '{fieldName}' 字段。",
"contents.languageModeAll": "所有语言",
"contents.languageModeSingle": "单一语言",
"contents.lastModifiedByFieldDescription": "上次修改内容项的用户。",
"contents.lastModifiedFieldDescription": "上次修改内容项的日期时间。",
"contents.lastUpdatedLabel": "上次更新",
"contents.loadContent": "加载",
"contents.loadContentFailed": "加载内容失败,请重新加载。",
"contents.loadDataFailed": "加载数据失败,请重新加载。",
"contents.loadFailed": "加载内容失败,请重新加载。",
"contents.loadVersionFailed": "加载新版本失败。请重新加载。",
"contents.localizedFieldDescription": "内容项的 '{fieldName}' 字段(本地化)。",
"contents.newStatusFieldDescription": "内容项的新状态。",
"contents.noReference": "- 无引用 -",
"contents.noReferences": "此内容没有引用。",
"contents.noReferencing": "此内容未被其他项目引用。",
"contents.pendingChangesTextToChange": "您有未保存的更改。\n\n当您更改状态时,您将丢失它们。\n\n**仍要继续吗?**",
"contents.pendingChangesTextToClose": "您有未保存的更改。\n\n当您关闭当前内容视图时,您将丢失它们。\n\n**您是否仍要继续?**",
"contents.pendingChangesTextToPreview": "您有未保存的更改。\n\n您不会在预览中看到它们。\n\n**您是否仍要继续?**",
"contents.pendingChangesTitle": "未保存的更改",
"contents.publishAll": "全部发布",
"contents.referencesCreateNew": "新增",
"contents.referencesCreatePublish": "创建和发布",
"contents.referencesLink": "链接所选内容 ({count})",
"contents.referencesSelectExisting": "选择现有",
"contents.referencesSelectSchema": "选择 {schema}",
"contents.refreshTooltip": "刷新内容 (CTRL + SHIFT + R)",
"contents.reloaded": "内容已重新加载。",
"contents.removeConfirmText": "您真的要删除内容吗?",
"contents.removeConfirmTitle": "删除内容",
"contents.saveAndPublish": "保存并发布",
"contents.scheduledAt": "at",
"contents.scheduledAtLabel": "at",
"contents.scheduledTo": "to",
"contents.schemasPageTitle": "内容",
"contents.searchPlaceholder": "全文搜索",
"contents.searchSchemasPlaceholder": "搜索Schemas...",
"contents.selectionCount": "{count} 个选定的项目",
"contents.statusFieldDescription": "内容项的状态。",
"contents.statusQueries": "状态查询",
"contents.stockPhotoEmpty": "未选择任何内容",
"contents.stockPhotoSearch": "通过 Unsplash 搜索照片",
"contents.tableHeaders.created": "创建",
"contents.tableHeaders.createdBy": "创建者",
"contents.tableHeaders.createdByShort": "By",
"contents.tableHeaders.id": "Id",
"contents.tableHeaders.lastModified": "更新",
"contents.tableHeaders.lastModifiedBy": "更新者",
"contents.tableHeaders.lastModifiedByShort": "By",
"contents.tableHeaders.nextStatus": "下一个状态",
"contents.tableHeaders.status": "状态",
"contents.tableHeaders.version": "版本",
"contents.unpublishReferrerConfirmText": "该内容被另一个已发布的内容项引用。\n\n您真的要取消发布此内容吗?",
"contents.unpublishReferrerConfirmTitle": "取消发布内容",
"contents.unsavedChangesText": "您有未保存的更改。要立即加载吗?",
"contents.unsavedChangesTitle": "未保存的更改",
"contents.unsetValue": "未设置值",
"contents.unsetValueConfirmText": "如果您取消设置该值,您可能会丢失更改。\n\n您真的要这样做吗?",
"contents.unsetValueConfirmTitle": "你想取消设置吗?",
"contents.updated": "内容更新成功。",
"contents.updateFailed": "更新内容失败,请重新加载。",
"contents.validate": "验证",
"contents.validationHint": "当您看到验证错误时,请记住检查所有语言。",
"contents.versionCompare": "比较",
"contents.versionDelete": "删除此版本",
"contents.versionFieldDescription": "内容项的版本",
"contents.versionViewing": "查看版本**{version}**。",
"contents.viewLatest": "查看最新",
"contents.viewReset": "重置默认视图",
"contributors.add": "添加贡献者",
"contributors.addFailed": "添加贡献者失败。请重新加载。",
"contributors.contributorAssigned": "一个具有输入电子邮件地址的新用户已被创建并被指定为贡献者。",
"contributors.contributorAssignedExisting": "用户已被分配",
"contributors.contributorAssignedInvited": "用户已被邀请并分配。",
"contributors.contributorAssignedOld": "用户已被添加为贡献者。",
"contributors.deleteConfirmText": "你真的要移除贡献者吗?",
"contributors.deleteConfirmTitle": "移除贡献者",
"contributors.deleteFailed": "删除贡献者失败。请重新加载。",
"contributors.emailPlaceholder": "查找现有用户或通过电子邮件邀请",
"contributors.empty": "没有找到贡献者。",
"contributors.import.emailsDetected": "检测到的电子邮件:{count}",
"contributors.import.run": "添加贡献者",
"contributors.import.run2": "导入",
"contributors.importButton": "一次添加多个贡献者",
"contributors.importHintg": "大团队?",
"contributors.importTitle": "导入贡献者",
"contributors.loadFailed": "加载贡献者失败。请重新加载。",
"contributors.planHint": "您的计划允许最多 {maxContributors} 个贡献者。",
"contributors.refreshTooltip": "刷新贡献者 (CTRL + SHIFT + R)",
"contributors.reloaded": "贡献者重新加载。",
"contributors.search": "搜索",
"contributors.userNotFound": "用户不存在。",
"dashboard.apiCallsCard": "API 调用",
"dashboard.apiCallsChart": "API 调用图表",
"dashboard.apiCallsLimitLabel": "每月限制",
"dashboard.apiCallsSummaryCard": "API 调用摘要",
"dashboard.apiDocumentationCard": "API 文档",
"dashboard.apiPerformanceCard": "API 性能 (ms): {summary}ms avg",
"dashboard.apiPerformanceChart": "API 性能图表",
"dashboard.assetSizeCard": "资源大小 (MB",
"dashboard.assetSizeLabel": "总大小",
"dashboard.assetSizeLimitLabel": "总限制",
"dashboard.assetTotalSize": "资源总存储大小",
"dashboard.assetUpdloadsCountChart": "资源上传计数图表",
"dashboard.assetUploadsCard": "资源上传",
"dashboard.assetUploadsSizeChart": "资源上传大小图表",
"dashboard.configSaved": "配置已保存。",
"dashboard.contentApi": "内容 API",
"dashboard.contentApiDescription": "适用于您的应用内容的 OpenAPI 3.0 兼容文档。",
"dashboard.contentsSummaryCard": "项目数量",
"dashboard.currentMonthLabel": "本月",
"dashboard.downloadLog": "下载日志",
"dashboard.editConfig": "编辑配置",
"dashboard.githubCard": "Github",
"dashboard.githubCardDescription": "从 Github 获取源代码并报告错误或寻求支持。",
"dashboard.historyCard": "历史",
"dashboard.pageTitle": "仪表盘",
"dashboard.resetConfigConfirmText": "您真的要将仪表板重置为默认设置吗?",
"dashboard.resetConfigConfirmTitle": "重置配置",
"dashboard.schemaNewCard": "新Schemas",
"dashboard.schemaNewCardDescription": "Schemas定义了内容元素的结构。",
"dashboard.schemasCard": "Schemas",
"dashboard.schemasCardDescription": "深入了解此应用的数据模型。",
"dashboard.stackedChart": "折叠",
"dashboard.supportCard": "反馈与支持",
"dashboard.supportCardDescription": "提供反馈和请求功能以帮助我们改进 Squidex。",
"dashboard.trafficChart": "API 流量图表",
"dashboard.trafficHeader": "流量 (MB)",
"dashboard.trafficLimitLabel": "每月限制",
"dashboard.trafficSummaryCard": "API 流量汇总",
"dashboard.welcomeText": "欢迎使用 **{app}** 仪表板。",
"dashboard.welcomeTitle": "Hi {user}",
"eventConsumers.count": "计数",
"eventConsumers.loadFailed": "加载事件消费者失败。请重新加载。",
"eventConsumers.pageTitle": "事件消费者",
"eventConsumers.position": "位置",
"eventConsumers.refreshTooltip": "刷新事件消费者 (CTRL + SHIFT + R)",
"eventConsumers.reloaded": "事件消费者重新加载。",
"eventConsumers.resetFailed": "无法重置事件消费者。请重新加载。",
"eventConsumers.resetTooltip": "重置事件消费者",
"eventConsumers.startFailed": "无法启动事件消费者。请重新加载。",
"eventConsumers.startTooltip": "启动事件消费者",
"eventConsumers.stopFailed": "无法停止事件消费者。请重新加载。",
"eventConsumers.stopTooltip": "停止事件消费者",
"features.loadFailed": "加载功能失败。请重新加载。",
"history.loadFailed": "加载历史记录失败。请重新加载。",
"history.title": "活动",
"languages.add": "添加语言",
"languages.addFailed": "添加语言失败。请重新加载。",
"languages.deleteConfirmText": "你真的要删除语言吗?",
"languages.deleteConfirmTitle": "删除语言",
"languages.deleteFailed": "删除语言失败。请重新加载。",
"languages.loadFailed": "加载语言失败。请重新加载。",
"languages.master": "是大师",
"languages.masterHint": "如果没有定义回退,其他语言回退到母版。",
"languages.optional": "是可选的",
"languages.optionalHint": "不得输入可选语言的值,即使字段是必需的。",
"languages.refreshTooltip": "刷新语言 (CTRL + SHIFT + R)",
"languages.reloaded": "语言已重新加载。",
"languages.updateFailed": "更改语言失败。请重新加载。",
"news.headline": "有什么新鲜事?",
"news.title": "新功能",
"notifo.subscripeTooltip": "单击此按钮可订阅所有更改并接收推送通知。",
"plans.billingPortal": "计费门户",
"plans.billingPortalHint": "前往账单门户查看付款历史和订阅概览。",
"plans.change": "改变",
"plans.changeConfirmTitle": "更改订阅",
"plans.changeFailed": "更改计划失败。请重新加载。",
"plans.includedCalls": "API 调用",
"plans.includedContributors": "贡献者",
"plans.includedStorage": "存储",
"plans.includedTraffic": "交通",
"plans.loadFailed": "加载计划失败。请重新加载。",
"plans.noPlanConfigured": "未配置计划,此应用无限制使用。",
"plans.notPlanOwner": "您尚未创建订阅。因此您无法更改计划。",
"plans.perMonth": "每月",
"plans.perYear": "每年",
"plans.refreshTooltip": "刷新计划 (CTRL + SHIFT + R)",
"plans.reloaded": "计划重新加载。",
"plans.selected": "已选择",
"profile.title": "个人资料",
"profile.userEmail": "登录方式",
"roles.add": "添加角色",
"roles.addFailed": "添加角色失败,请重新加载。",
"roles.default.owner": "可以做任何事情,包括删除应用程序。",
"roles.default.reader": "只能读取资源和内容。",
"roles.defaults.developer": "可以使用 API 视图,编辑资源、内容、Schemas、规则、工作流和 appSettings.patterns。",
"roles.defaults.editor": "可以编辑资源和内容并查看工作流程。",
"roles.deleteConfirmText": "删除角色",
"roles.deleteConfirmTitle": "你真的要删除角色吗?",
"roles.loadFailed": "加载角色失败。请重新加载。",
"roles.loadPermissionsFailed": "加载权限失败。请重新加载。",
"roles.permissions": "权限",
"roles.permissionsDescription": "权限在 API 级别限制允许的操作和查询,是一项安全功能。",
"roles.permissionsPlaceholder": "开始输入以搜索权限",
"roles.properties": "属性",
"roles.properties.hideAPI": "隐藏 API",
"roles.properties.hideAssets": "隐藏资源",
"roles.properties.hideContents": "隐藏 {schema} 内容",
"roles.properties.hideSchemas": "隐藏Schemas",
"roles.properties.hideSettings": "隐藏设置",
"roles.propertiesDescription": "属性描述管理 UI 的行为,但不为 API 提供安全性。",
"roles.refreshTooltip": "刷新角色 (CTRL + SHIFT + R)",
"roles.reloaded": "重新加载角色。",
"roles.revokeFailed": "撤销角色失败,请重新加载。",
"roles.roleNamePlaceholder": "输入角色名称",
"roles.updateFailed": "更新角色失败。请重新加载。",
"rules.actionData": "动作数据",
"rules.actionHint": "动作类型的选择以后不能更改。",
"rules.cancelFailed": "取消规则失败,请重新加载。",
"rules.create": "新规则",
"rules.createFailed": "创建规则失败。请重新加载。",
"rules.createTooltip": "新规则 (CTRL + SHIFT + G)",
"rules.deleteConfirmText": "你真的要删除规则吗?",
"rules.deleteConfirmTitle": "删除规则",
"rules.deleteFailed": "删除规则失败,请重新加载。",
"rules.empty": "尚未创建规则。",
"rules.emptyAddRule": "添加规则",
"rules.enqueued": "规则已加入队列。",
"rules.itemPageTitle": "规则",
"rules.listPageTitle": "规则",
"rules.loadFailed": "加载规则失败。请重新加载。",
"rules.readMore": "阅读更多",
"rules.refreshEventsTooltip": "刷新事件 (CTRL + SHIFT + R)",
"rules.refreshTooltip": "刷新规则 (CTRL + SHIFT + R)",
"rules.reloaded": "规则重新加载。",
"rules.restarted": "规则将在几秒钟后开始运行。",
"rules.ruleEvents.cancelFailed": "取消规则事件失败。请重新加载。",
"rules.ruleEvents.enqueue": "入队",
"rules.ruleEvents.enqueued": "事件已入队。将在几秒钟后重新发送。",
"rules.ruleEvents.enqueueFailed": "无法将规则事件入队。请重新加载。",
"rules.ruleEvents.lastInvokedLabel": "上次调用",
"rules.ruleEvents.listPageTitle": "规则事件",
"rules.ruleEvents.loadFailed": "加载事件失败。请重新加载。",
"rules.ruleEvents.nextAttemptLabel": "下一个",
"rules.ruleEvents.numAttemptsLabel": "尝试次数",
"rules.ruleEvents.reloaded": "RuleEvents 重新加载。",
"rules.ruleSimulator.listPageTitle": "模拟器",
"rules.ruleSyntax.if": "如果",
"rules.ruleSyntax.then": "那么",
"rules.run": "运行",
"rules.runFailed": "运行规则失败。请重新加载。",
"rules.runFromSnapshots": "以最新状态运行",
"rules.runningRule": "规则 '{name}' 当前正在运行。",
"rules.runRuleConfirmText": "你真的想为所有事件运行规则吗?",
"rules.runRuleConfirmTitle": "运行规则",
"rules.simulate": "模拟",
"rules.simulateTooltip": "使用最近 100 个事件模拟此规则。",
"rules.simulator": "模拟器",
"rules.stop": "规则很快就会停止。",
"rules.triggerConfirmText": "你真的要触发规则吗?",
"rules.triggerConfirmTitle": "触发规则",
"rules.triggerFailed": "触发规则失败。请重新加载。",
"rules.triggerHint": "以后不能更改触发器类型的选择。",
"rules.unnamed": "未命名规则",
"rules.updateFailed": "更新规则失败。请重新加载。",
"schemas.addField": "添加字段",
"schemas.addFieldAndClose": "创建并关闭",
"schemas.addFieldAndCreate": "创建并添加字段",
"schemas.addFieldAndEdit": "创建和编辑字段",
"schemas.addFieldButton": "添加字段",
"schemas.addFieldFailed": "添加字段失败。请重新加载。",
"schemas.addNestedField": "添加嵌套字段",
"schemas.changeCategoryFailed": "更改类别失败。请重新加载。",
"schemas.clone": "Clone Schema",
"schemas.contentEditorUrl": "内容编辑器扩展",
"schemas.contentSidebarUrl": "内容侧边栏扩展",
"schemas.contentSidebarUrlHint": "详细信息视图中侧边栏插件的 URL。",
"schemas.contentsSidebarUrl": "内容侧边栏扩展",
"schemas.contentsSidebarUrlHint": "列表视图中侧边栏插件的 URL。",
"schemas.contextMenuTour": "打开上下文菜单以删除Schemas或为内容更改创建一些脚本。",
"schemas.create": "Create Schema",
"schemas.createCategory": "创建新类别...",
"schemas.createFailed": "无法创建Schemas。请重新加载。",
"schemas.createSchemaTooltip": "新Schemas (CTRL + SHIFT + G)",
"schemas.deleteConfirmText": "您真的要删除Schemas吗?",
"schemas.deleteConfirmTitle": "删除Schemas",
"schemas.deleteFailed": "删除Schemas失败。请重新加载。",
"schemas.deleteFieldFailed": "删除字段失败。请重新加载。",
"schemas.deleteRuleConfirmText": "你真的要删除这个字段规则吗?",
"schemas.deleteRuleConfirmTitle": "删除字段规则",
"schemas.deleteUrlConfirmText": "你真的要删除这个 URL 吗?",
"schemas.deleteUrlConfirmTitle": "删除 URL",
"schemas.disableFieldFailed": "禁用字段失败。请重新加载。",
"schemas.enableFieldFailed": "无法启用字段。请重新加载。",
"schemas.export.deleteFields": "删除字段",
"schemas.export.recreateFields": "重新创建字段",
"schemas.export.synchronize": "同步",
"schemas.field.allowedValues": "允许的值",
"schemas.field.defaultValue": "Default Value",
"schemas.field.defaultValues": "默认值",
"schemas.field.defaultValuesHint": "设置每种语言的默认值并覆盖默认值属性(如果已定义)。仅在真正需要时才使用它。",
"schemas.field.deleteConfirmText": "您真的要删除该字段吗?",
"schemas.field.deleteConfirmTitle": "删除字段",
"schemas.field.disable": "在 UI 中禁用",
"schemas.field.disabledMarker": "已禁用",
"schemas.field.editor": "Editor",
"schemas.field.editorUrl": "编辑器网址",
"schemas.field.editorUrlHint": "如果您使用自定义编辑器,请访问您的插件的 URL。",
"schemas.field.empty": "尚未创建字段。",
"schemas.field.enable": "在 UI 中启用",
"schemas.field.enabledMarker": "已启用",
"schemas.field.halfWidth": "半宽",
"schemas.field.halfWidthHint": "在编辑或创建页面上,当有足够的空间时,只显示半宽的字段。",
"schemas.field.hiddenMarker": "Hidden",
"schemas.field.hide": "隐藏在 API",
"schemas.field.hintsHint": "为文档和 UI 描述这个字段。",
"schemas.field.inlineEditable": "内联可编辑",
"schemas.field.labelHint": "文档和 UI 的显示名称。",
"schemas.field.localizable": "Localizable",
"schemas.field.localizableHint": "您可以将字段标记为可本地化。这意味着这取决于语言,例如城市名称。",
"schemas.field.localizableMarker": "localizable",
"schemas.field.lock": "锁定并防止更改",
"schemas.field.lockConfirmText": "警告:无法撤消锁定字段!锁定的字段定义无法再解锁、删除或更改。\n\n您真的要锁定此字段吗?",
"schemas.field.lockConfirmTitle": "锁定字段",
"schemas.field.lockedMarker": "锁定",
"schemas.field.nameHint": "API 响应中的字段名称。",
"schemas.field.namePlaceholder": "输入字段名称",
"schemas.field.nameValidationMessage": "名称必须是骆驼大小写的有效 javascript 名称。",
"schemas.field.placeholder": "Placeholder",
"schemas.field.placeholderHint": "定义输入控件的占位符。",
"schemas.field.required": "Required",
"schemas.field.requiredOnPublish": "发布时需要",
"schemas.field.show": "在 API 中显示",
"schemas.field.tabCommon": "通用",
"schemas.field.tabEditing": "Editing",
"schemas.field.tabValidation": "Validation",
"schemas.field.tagsHint": "为自动化流程注释您的领域的标签。",
"schemas.field.unique": "Unique",
"schemas.field.visibleMarker": "Visible",
"schemas.fieldTypes.array.count": "Items",
"schemas.fieldTypes.array.countMax": "最大项目数",
"schemas.fieldTypes.array.countMin": "最小项目",
"schemas.fieldTypes.array.description": "嵌入对象列表。",
"schemas.fieldTypes.assets.allowDuplicates": "允许重复值",
"schemas.fieldTypes.assets.count": "计数",
"schemas.fieldTypes.assets.countMax": "最大资源",
"schemas.fieldTypes.assets.countMin": "最小资源",
"schemas.fieldTypes.assets.description": "图片、视频、文档。",
"schemas.fieldTypes.assets.fileExtensions": "文件扩展名",
"schemas.fieldTypes.assets.folderId": "文件夹",
"schemas.fieldTypes.assets.folderIdHint": "新资源将上传到的资源文件夹。",
"schemas.fieldTypes.assets.mustBeImage": "必须是图片",
"schemas.fieldTypes.assets.previewFileName": "仅文件名",
"schemas.fieldTypes.assets.previewImage": "如果不是图像,则只有缩略图或文件名",
"schemas.fieldTypes.assets.previewImageAndFileName": "缩略图和文件名",
"schemas.fieldTypes.assets.previewMode": "PreviewMode",
"schemas.fieldTypes.assets.previewModeHint": "内容列表中资源的预览模式。",
"schemas.fieldTypes.assets.resolve": "解析第一个资源",
"schemas.fieldTypes.assets.resolveHint": "显示内容列表中第一个引用的资源。",
"schemas.fieldTypes.assets.size": "Size",
"schemas.fieldTypes.assets.sizeMax": "最小尺寸",
"schemas.fieldTypes.assets.sizeMin": "最大尺寸",
"schemas.fieldTypes.boolean.description": "是或否,对或错。",
"schemas.fieldTypes.component.description": "在此内容中嵌入另一个Schemas。",
"schemas.fieldTypes.components.description": "将其他Schemas作为数组嵌入到此内容中。",
"schemas.fieldTypes.dateTime.defaultMode": "默认模式",
"schemas.fieldTypes.dateTime.description": "活动日期,开放时间。",
"schemas.fieldTypes.dateTime.format": "Pattern",
"schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format",
"schemas.fieldTypes.dateTime.rangeMax": "最大值",
"schemas.fieldTypes.dateTime.rangeMin": "最小值",
"schemas.fieldTypes.geolocation.description": "坐标:纬度和经度。",
"schemas.fieldTypes.json.description": "JSON 格式的数据,供开发人员使用。",
"schemas.fieldTypes.number.description": "ID、订单号、评级、数量。",
"schemas.fieldTypes.number.range": "范围",
"schemas.fieldTypes.number.rangeMax": "最大值",
"schemas.fieldTypes.number.rangeMin": "最小值",
"schemas.fieldTypes.references.count": "项目",
"schemas.fieldTypes.references.countMax": "最大项目数",
"schemas.fieldTypes.references.countMin": "最小项目",
"schemas.fieldTypes.references.description": "链接到其他内容项。",
"schemas.fieldTypes.references.mustBePublished": "必须发布参考文献",
"schemas.fieldTypes.references.resolve": "Resolve references",
"schemas.fieldTypes.references.resolveHint": "当 MaxItems 设置为 1 时,在内容列表中显示引用项的名称。",
"schemas.fieldTypes.string.characters": "字符",
"schemas.fieldTypes.string.charactersMax": "最大字符数",
"schemas.fieldTypes.string.charactersMin": "最小字符数",
"schemas.fieldTypes.string.contentType": "内容类型",
"schemas.fieldTypes.string.description": "标题、名称、段落。",
"schemas.fieldTypes.string.folderId": "资源文件夹",
"schemas.fieldTypes.string.folderIdHint": "新资源将上传到的资源文件夹。",
"schemas.fieldTypes.string.length": "Length",
"schemas.fieldTypes.string.lengthMax": "Max Length",
"schemas.fieldTypes.string.lengthMin": "最小长度",
"schemas.fieldTypes.string.pattern": "Regex Pattern",
"schemas.fieldTypes.string.patternMessage": "模式消息",
"schemas.fieldTypes.string.suggestions": "建议",
"schemas.fieldTypes.string.wordHint": "字数和字符数在纯文本上计算。纯文本根据定义的内容类型计算,可以是 Markdown 或 HTML。",
"schemas.fieldTypes.string.words": "单词",
"schemas.fieldTypes.string.wordsMax": "最大字数",
"schemas.fieldTypes.string.wordsMin": "最小字数",
"schemas.fieldTypes.tags.count": "项目",
"schemas.fieldTypes.tags.countMax": "最大项目数",
"schemas.fieldTypes.tags.countMin": "最小项目",
"schemas.fieldTypes.tags.description": "标签的特殊格式。",
"schemas.fieldTypes.ui.description": "编辑 UI 的分隔符。",
"schemas.hideFieldFailed": "隐藏字段失败。请重新加载。",
"schemas.import": "导入Schemas",
"schemas.listFields": "列表字段",
"schemas.listFieldsEmpty": "将字段拖放到此处或重新排序以显示内容列表中的字段。当未定义列表字段时,使用第一个字段。",
"schemas.loadFailed": "加载Schemas失败。请重新加载。",
"schemas.loadSchemaFailed": "Failed to load schema. Please reload.",
"schemas.lockFieldFailed": "无法锁定字段。请重新加载。",
"schemas.modeComponent": "Component",
"schemas.modeComponentDescription": "只能嵌入到组件字段...",
"schemas.modeMultiple": "多个内容",
"schemas.modeMultipleDescription": "最适合多个实例,如博客文章、页面、作者、产品...",
"schemas.modeSingle": "单一内容",
"schemas.modeSingleDescription": "最适合单个实例,如主页、隐私政策、设置...",
"schemas.nameWarning": "这些值以后不能更改。",
"schemas.previewUrls.empty": "未配置预览网址。",
"schemas.previewUrls.help": "查看集成帮助页面以了解有关预览 URL 的更多信息。",
"schemas.previewUrls.namePlaceholder": "网络或移动",
"schemas.previewUrls.title": "预览网址",
"schemas.previewUrls.urlPlaceholder": "带变量的 URL",
"schemas.published": "Published",
"schemas.publishedTour": "请注意,您必须先发布Schemas,然后才能向其中添加内容。",
"schemas.publishFailed": "无法发布Schemas。请重新加载。",
"schemas.referenceFields": "参考字段",
"schemas.referenceFieldsEmpty": "将字段拖放到此处或重新排序以在被其他内容引用时显示字段。当未定义引用字段时,将使用列表字段代替。",
"schemas.reloaded": "Schemas reloaded.",
"schemas.reorderFieldsFailed": "重新排序字段失败。请重新加载。",
"schemas.rules.action": "动作",
"schemas.rules.condition": "Javascript 中的条件",
"schemas.rules.empty": "没有配置字段规则。",
"schemas.rules.title": "字段规则",
"schemas.rules.when": "何时",
"schemas.saved": "Schema saved successfully.",
"schemas.saveFieldAndClose": "保存并关闭",
"schemas.saveFieldAndNew": "保存并添加字段",
"schemas.schemaHintsHint": "为文档和用户界面描述这个Schemas。",
"schemas.schemaLabelHint": "文档和用户界面的显示名称。",
"schemas.schemaNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。",
"schemas.schemaNameValidationMessage": "名称只能包含字母、数字、破折号和空格。",
"schemas.schemaTagsHint": "用于注释自动化流程Schemas的标签。",
"schemas.searchPlaceholder": "搜索Schemas...",
"schemas.showFieldFailed": "显示字段失败。请重新加载。",
"schemas.synchronized": "Schema synchronized successfully.",
"schemas.synchronizeFailed": "同步Schemas失败。请重新加载。",
"schemas.tabFields": "字段",
"schemas.tabJson": "Json",
"schemas.tabMore": "More",
"schemas.tabScripts": "Scripts",
"schemas.tabUI": "UI",
"schemas.ui": "指定的字段",
"schemas.ui.unassignedFields": "未分配的字段",
"schemas.unpublished": "Unpublished",
"schemas.unpublishFailed": "无法取消发布Schemas。请重新加载。",
"schemas.updateFailed": "更新Schemas失败。请重新加载。",
"schemas.updateFieldFailed": "更新字段失败。请重新加载。",
"schemas.updatePreviewUrlsFailed": "无法配置预览网址。请重新加载。",
"schemas.updateRulesFailed": "更新Schemas规则失败。请重新加载。",
"schemas.updateScriptsFailed": "更新Schemas脚本失败。请重新加载。",
"schemas.updateUIFieldsFailed": "无法更新 UI 字段。请重新加载。",
"schemas.validateOnPublish": "发布时验证",
"search.addFilter": "添加过滤器",
"search.addGroup": "添加组",
"search.addSorting": "添加排序",
"search.advancedTour": "单击此图标可显示高级搜索菜单!",
"search.customQuery": "自定义查询",
"search.fullTextTour": "使用全文搜索在所有领域和语言中搜索内容!",
"search.help": "在 [文档](https://docs.squidex.io/04-guides/02-api.html) 中阅读有关过滤的更多信息。",
"search.myQueries": "我的查询",
"search.nameQuery": "命名查询",
"search.queriesEmpty": "搜索 {types} 并在搜索表单中使用 <i class=\"icon-star-empty\"></i> 图标来保存所有贡献者的查询。",
"search.queryAllNewestFirst": "全部(最新的在前)",
"search.queryAllOldestFirst": "所有(最旧的在前)",
"search.quickNavPlaceholder": "快速导航(按'q')",
"search.saveQueryMyself": "只为我保存查询。",
"search.searchFailed": "搜索失败,请重新加载。",
"search.sharedQueries": "共享查询",
"search.sorting": "排序",
"start.login": "登录 Squidex",
"start.loginHint": "登录按钮将打开一个新的弹出窗口。一旦您登录成功,我们会将您重定向到 Squidex 管理门户。",
"start.madeBy": "自豪地制作",
"start.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021",
"tour.joinForum": "加入我们的论坛",
"tour.joinGithub": "加入我们的 Github",
"tour.skip": "跳过游览",
"tour.step0Next": "我们看看周围",
"tour.step0Text": "您可以立即开始管理和分发您的内容,但我们想先向您介绍一些基础知识...\n\n如何",
"tour.step1Next": "继续",
"tour.step1Text": "应用程序是您项目的存储库,例如(博客、网上商店或移动应用程序)。您可以为您的应用程序分配贡献者以协同工作。\n\n您可以在其中创建无限数量的应用程序Squidex 同时管理多个项目。",
"tour.step2Next": "继续!",
"tour.step2Text": "Schemas定义内容的结构、内容项的字段和数据类型。\n\n在向Schemas添加内容之前,请确保点击顶部的“发布”按钮使Schemas可用于您的内容 appSettings.editors。",
"tour.step3Next": "快到了!",
"tour.step3Text": "内容是您的应用程序中按Schemas分组的实际数据。\n\n首先选择一个已发布的Schemas,然后为此Schemas添加内容。",
"tour.step4Next": "知道了!",
"tour.step4Text": "资源包含所有也可以链接到您的内容的文件。例如图像、视频或文档。\n\n您可以在此处上传资源供以后使用,也可以在创建时直接上传带有资源字段的新内容项。",
"tour.step5Text": "但这还不是我们可以提供的全部支持。\n\n您可以访问 https://docs.squidex.io/> 阅读更多信息。\n\n您想加入我们的社区吗? ?",
"tour.step5Title": "太棒了,现在你知道基础了!",
"tour.tooltipConfirm": "知道了",
"tour.tooltipStop": "停止游览",
"tour.welcome": "欢迎来到",
"tour.welcomeProduct": "Squidex CMS",
"translate.translateFailed": "无法翻译文本。请重新加载。",
"usages.loadCallsFailed": "加载调用使用失败。请重新加载。",
"usages.loadMonthlyCallsFailed": "未能加载每月 API 调用。请重新加载。",
"usages.loadStorageFailed": "加载存储使用失败。请重新加载。",
"usages.loadTodayStorageFailed": "无法加载今天的存储大小。请重新加载。",
"users.create": "新建",
"users.createFailed": "创建用户失败,请重新加载。",
"users.createPageTitle": "创建用户",
"users.createTitle": "新用户",
"users.createTooltip": "新用户 (CTRL + N)",
"users.deleteConfirmText": "你真的要删除这个用户吗?",
"users.deleteConfirmTitle": "删除用户",
"users.deleteFailed": "删除用户失败,请重新加载。",
"users.editPageTitle": "编辑用户",
"users.editTitle": "编辑用户",
"users.listPageTitle": "用户管理",
"users.listTitle": "用户",
"users.loadFailed": "加载用户失败,请重新加载。",
"users.loadUserFailed": "加载用户失败。请重新加载。",
"users.lockFailed": "锁定用户失败。请重新加载。",
"users.lockTooltip": "锁定用户",
"users.passwordConfirmValidationMessage": "密码必须相同。",
"users.refreshTooltip": "刷新用户 (CTRL + SHIFT + R)",
"users.reloaded": "用户重新加载。",
"users.search": "搜索用户",
"users.unlockFailed": "解锁用户失败。请重新加载。",
"users.unlockTooltip": "解锁用户",
"users.updateFailed": "更新用户失败。请重新加载。",
"validation.between": "{field} 必须介于 '{min}' 和 '{max}' 之间。",
"validation.betweenlength": "{field|upper} 必须介于 {minlength} 和 {maxlength} 项之间。",
"validation.betweenlengthstring": "{field|upper} 必须介于 {minlength} 和 {maxlength} 个字符之间。",
"validation.email": "{field|upper} 必须是电子邮件地址。",
"validation.exactly": "{field|upper} 必须正好是 '{expected}'。",
"validation.exactlylength": "{field|upper} 必须正好有 {expected} 项。",
"validation.exactlylengthstring": "{field|upper} 必须正好有 {expected} 个字符。",
"validation.match": "{message}",
"validation.max": "{field|upper} 必须小于或等于 '{max}'。",
"validation.maxlength": "{field|upper} 不能超过 {requiredlength} 个项目。",
"validation.maxlengthstring": "{field|upper} 不能超过 {requiredlength} 个字符。",
"validation.min": "{field|upper} 必须大于或等于 '{min}'。",
"validation.minlength": "{field|upper} 必须至少有 {requiredlength} 项。",
"validation.minlengthstring": "{field|upper} 必须至少有 {requiredlength} 个字符。",
"validation.notAnValidSvg": "The SVG is malicious and contains script tags.",
"validation.pattern": "{field|upper} 与模式不匹配。",
"validation.patternmessage": "{message}",
"validation.required": "{field|upper} 是必需的。",
"validation.requiredTrue": "{field|upper} 是必需的。",
"validation.uniquestrings": "{field|upper} 不得包含重复值。",
"validation.validarrayvalues": "{field|upper} 包含无效值:{invalidvalue}。",
"validation.validdatetime": "{field|upper} 不是有效的日期时间。",
"validation.validvalues": "{field|upper} 不是一个有效值。",
"workflows.add": "添加工作流",
"workflows.addStep": "添加步骤",
"workflows.createFailed": "创建工作流失败。请重新加载。",
"workflows.deleteConfirmText": "你真的要删除工作流吗?",
"workflows.deleteConfirmTitle": "删除工作流",
"workflows.deleteFailed": "删除工作流失败。请重新加载。",
"workflows.empty": "尚未创建工作流。",
"workflows.loadFailed": "加载工作流失败。请重新加载。",
"workflows.notNamed": "未命名的工作流程",
"workflows.preventUpdates": "防止更新",
"workflows.publishedNotRemovable": "无法删除",
"workflows.refreshTooltip": "刷新工作流 (CTRL + SHIFT + R)",
"workflows.reloaded": "工作流已重新加载。",
"workflows.saved": "工作流已保存。",
"workflows.schemasHint": "将此工作流限制为特定Schemas或将其保留为所有Schemas的空。",
"workflows.syntax.expression": "表达式",
"workflows.syntax.for": "for",
"workflows.syntax.when": "when",
"workflows.tabEdit": "编辑",
"workflows.tabVisualize": "可视化",
"workflows.updateFailed": "无法更新工作流。请重新加载。",
"workflows.workflowNameHint": "工作流的可选名称。",
"workflows.workflowNamePlaceholder": "输入工作流名称"
}

2
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.",

1
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",

374
backend/i18n/source/backend_zh.json

@ -0,0 +1,374 @@
{
"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": "资源文件夹不存在。",
"assets.folderRecursion": "无法将文件夹添加到自己的子文件夹中。",
"assets.maxSizeReached": "您已达到最大资源大小。",
"assets.referenced": "资源被内容引用,无法删除。",
"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": "Schemas 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": "无效组件。找不到Schemas。",
"contents.invalidGeolocation": "无效的 json 类型,预期的纬度/经度对象。",
"contents.invalidGeolocationLatitude": "纬度必须在 -90 到 90 之间。",
"contents.invalidGeolocationLongitude": "经度必须在 -180 到 180 之间。",
"contents.invalidNumber": "无效的 json 类型,需要的数字。",
"contents.invalidString": "无效的 json 类型,需要的字符串。",
"contents.listReferences": "{count} 个引用",
"contents.referenced": "内容被其他内容引用,无法删除或取消发布。",
"contents.schemaNotPublished": "Schemas未发布。",
"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}' 的Schemas无效。",
"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": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它!",
"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": "替换的资源。",
"history.assets.updated": "更新的资源。",
"history.assets.uploaded": "上传的资源。",
"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": "创建的Schemas {[Name]}。",
"history.schemas.deleted": "已删除Schemas {[Name]}。",
"history.schemas.fieldAdded": "将字段 {[Field]} 添加到Schemas {[Name]}。",
"history.schemas.fieldDeleted": "已从Schemas {[Name]} 中删除字段 {[Field]}。",
"history.schemas.fieldDisabled": "Schemas {[Name]} 的禁用字段 {[Field]}。",
"history.schemas.fieldHidden": "有Schemas {[Name]} 的隐藏字段 {[Field]}。",
"history.schemas.fieldLocked": "已锁定Schemas {[Name]} 的字段 {[Field]}。",
"history.schemas.fieldShown": "已显示Schemas {[Name]} 的字段 {[Field]}。",
"history.schemas.fieldsReordered": "Schemas {[Name]} 的重新排序字段。",
"history.schemas.fieldUpdated": "已更新Schemas {[Name]} 的字段 {[Field]}。",
"history.schemas.published": "已发布的Schemas {[Name]}。",
"history.schemas.scriptsConfigured": "Schemas {[Name]} 的配置脚本。",
"history.schemas.unpublished": "未发布的Schemas {[Name]}。",
"history.schemas.updated": "更新的Schemas {[Name]}。",
"history.statusChanged": "已将 {[Schema]} 内容的状态更改为 {[Status]}。",
"login.githubPrivateEmail": "您的邮箱在 Github 中设置为私有。请设置为公开以使用 Github 登录。",
"rules.ruleAlreadyRunning": "另一个规则已经在运行。",
"schema.fieldIsLocked": "Schemas字段被锁定。",
"schema.fieldNotInSchema": "字段不是Schemas的一部分。",
"schema.noPermission": "您没有此Schemas的权限。",
"schema.notFoundId": "Schemas {id} 不存在。",
"schemas.dateTimeCalculatedDefaultAndDefaultError": "计算出的默认值和默认值不能一起使用。",
"schemas.duplicateFieldName": "字段 '{field}' 已添加两次。",
"schemas.fieldCannotBeUIField": "字段不能是 UI 字段。",
"schemas.fieldNameAlreadyExists": "已存在同名字段。",
"schemas.fieldsNotCovered": "字段 ID 未涵盖所有字段。",
"schemas.nameAlreadyExists": "一个同名的Schemas已经存在。",
"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} Schemas",
"security.passwordStolen": "此密码以前曾出现在数据泄露事件中,永远不应使用。如果您以前曾在任何地方使用过它,请更改它!",
"setup.createUser.button": "创建用户",
"setup.createUser.confirmPassword": "确认",
"setup.createUser.failure": "既未配置密码认证,也未配置 Google 等外部认证提供商。请检查您的设置和日志。",
"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": "通过你的设置,只有管理员可以创建新的应用程序。如果你想改变这个设置 <code>UI__ONLYADMINCANCREATEAPPS=false</code> 作为环境变量。",
"setup.ruleAppCreation.warningAll": "通过你的设置,每个用户都可以创建新的应用程序。如果你想改变这个设置 <code>UI__ONLYADMINCANCREATEAPPS=true</code> 作为环境变量。",
"setup.ruleFolder.warning": "您正在使用<strong>文件夹资源存储</strong>,其中所有资源都存储在文件系统中。请记住将资源文件夹包含在您的备份策略中并将其映射到卷, 如果您使用的是 Docker。",
"setup.ruleFtp.warning": "您正在使用 <strong>FTP 资源存储</strong>。由于性能不佳,不建议使用这种存储类型。",
"setup.ruleHttps.failure": " 您不是通过 https 访问站点。如果此警告不正确,则 Squidex 无法检测 https 模式,因为您的实例位于反向代理(例如 nginx)之后。确保正确转发 http 标头, 通过 <code>X-Forwarded-*</code> 标头。",
"setup.ruleHttps.success": "恭喜您,您正在通过安全连接 (https) 访问 Squidex 安装。",
"setup.rules.headline": "系统清单",
"setup.ruleUrl.failure": "您应该仅通过一个规范 URL 访问 Squidex,并通过 <code>URLS__BASEURL</code> 环境变量配置此 URL。当前的基本 URL <code>{actual}</code>与基本 url <code>{configured}</code> 不匹配。",
"setup.ruleUrl.success": "恭喜 <code>URLS__BASEURL</code> 环境变量配置正确。",
"setup.title": "安装",
"users.accessDenied.text": "不允许此操作,您的帐户可能被锁定。",
"users.accessDenied.title": "拒绝访问",
"users.consent.agree": "我同意!",
"users.consent.cookiesHeadline": "Cookies & Analytics",
"users.consent.cookiesText": "<p>我理解并同意 Squidex 使用 cookie 来确保您在我们的平台上获得最佳体验并存储您的登录状态。</p><p>我理解并同意 Squidex 已集成 Google Analytics(具有匿名器功能)。Google Analytics 是一项网络分析服务,用于收集和分析有关用户行为的数据。</p><p> 我接受 <a href=\"{privacyUrl}\" target=\"_blank\" rel=\"noopener\">隐私政策</a>.</p>",
"users.consent.emailHeadline": "自动电子邮件(可选)",
"users.consent.emailText": "我理解并同意 Squidex 发送电子邮件来通知我有关新功能、重大更改和停机时间的信息。",
"users.consent.headline": "我们需要您的同意",
"users.consent.needed": "你必须同意。",
"users.consent.piiHeadline": "个人信息",
"users.consent.piiText": "我理解并同意 Squidex 收集从外部身份验证提供商(例如 Google、Microsoft 或 Github)检索到的以下私人信息。<ul class=\"personal-information\"> <li>向所有其他用户提供基本的个人信息(名字、姓氏和照片),以便他们可以将您添加到他们的工作空间。</li><li>您可以随时选择更改这些信息以使您的帐户匿名. </li><li>您的用户帐户具有唯一标识符,对于我们跟踪的所有更改,您进行了这些更改并将此信息提供给其他用户。</li></ul>",
"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 <strong>{provider}</strong>",
"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": "多个工作流覆盖所有Schemas。",
"workflows.publishedIsInitial": "初始步骤不能发布步骤。",
"workflows.publishedNotDefined": "工作流必须有一个已发布的步骤。",
"workflows.publishedStepNotFound": "转换的目标无效。",
"workflows.schemaOverlap": "Schemas'{schema}' 被多个工作流覆盖。"
}

12
backend/i18n/source/frontend_en.json

@ -336,10 +336,10 @@
"common.separateByLine": "Separate by line",
"common.settings": "Settings",
"common.sidebar": "Sidebar Extension",
"common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.",
"common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history of 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 -",
@ -761,9 +761,11 @@
"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.format": "Pattern",
"schemas.fieldTypes.dateTime.formatHint": "The pattern when shown in the UI, see: https://date-fns.org/v2.22.1/docs/format",
"schemas.fieldTypes.dateTime.rangeMax": "Max Value",
"schemas.fieldTypes.dateTime.rangeMin": "Min Value",
"schemas.fieldTypes.geolocation.description": "Coordinates: latitude and longitude.",
@ -777,6 +779,7 @@
"schemas.fieldTypes.references.countMin": "Min Items",
"schemas.fieldTypes.references.description": "Links to other content items.",
"schemas.fieldTypes.references.mustBePublished": "References must be published",
"schemas.fieldTypes.references.resolve": "Resolve references",
"schemas.fieldTypes.references.resolveHint": "Show the name of the referenced item in content list when MaxItems is set to 1.",
"schemas.fieldTypes.string.characters": "Characters",
"schemas.fieldTypes.string.charactersMax": "Max Characters",
@ -941,6 +944,7 @@
"validation.min": "{field|upper} must be greater or equal to '{min}'.",
"validation.minlength": "{field|upper} must have at least {requiredlength} item(s).",
"validation.minlengthstring": "{field|upper} must have at least {requiredlength} character(s).",
"validation.notAnValidSvg": "The SVG is malicious and contains script tags.",
"validation.pattern": "{field|upper} does not match to the pattern.",
"validation.patternmessage": "{message}",
"validation.required": "{field|upper} is required.",

8
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",

8
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",

975
backend/i18n/source/frontend_zh.json

@ -0,0 +1,975 @@
{
"api.contentApi": "内容 API",
"api.generalApi": "通用 API",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.pageTitle": "API",
"api.title": "API",
"apps.allApps": "所有应用程序",
"apps.appLoadFailed": "加载应用失败。请重新加载。",
"apps.appNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。",
"apps.appNameValidationMessage": "名称可以包含小写字母 (a-z)、数字和破折号。",
"apps.appNameWarning": "以后不能更改应用名称。",
"apps.appsButtonCreate": "应用概览",
"apps.appsButtonFallbackTitle": "应用概览",
"apps.archive": "存档应用",
"apps.archiveConfirmText": "你真的要存档这个应用程序吗?",
"apps.archiveConfirmTitle": "存档应用程序",
"apps.archiveFailed": "存档应用失败。请重新加载。",
"apps.archiveWarning": "一旦你归档了一个应用程序,就没有回头路了。请确定。",
"apps.create": "创建应用程序",
"apps.createBlankApp": "新应用程序",
"apps.createBlankAppDescription": "创建一个没有内容和Schemas的新空白应用程序。",
"apps.createBlogApp": "新博客示例",
"apps.createBlogAppDescription": "从我们准备使用的博客开始。",
"apps.createFailed": "创建应用失败。请重新加载。",
"apps.createProfileApp": "新配置文件示例",
"apps.createProfileAppDescription": "创建您的个人资料页面。",
"apps.createWithTemplate": "创建 {template} 示例",
"apps.empty": "您还没有与任何应用协作",
"apps.generalSettings": "通用",
"apps.generalSettingsDangerZone": "通用",
"apps.image": "图片",
"apps.imageDrop": "拖放上传",
"apps.leave": "离开应用程序",
"apps.leaveConfirmText": "你真的要离开这个应用程序吗?",
"apps.leaveConfirmTitle": "离开应用程序。",
"apps.leaveFailed": "退出应用失败,请重新加载。",
"apps.listPageTitle": "应用程序",
"apps.loadFailed": "加载应用失败。请重新加载。",
"apps.loadSettingsFailed": "更新界面设置失败。请重新加载。",
"apps.removeImage": "删除图片",
"apps.removeImageFailed": "删除应用图片失败。请重新加载。",
"apps.updateFailed": "更新应用失败。请重新加载。",
"apps.updateSettingsFailed": "更新界面设置失败。请重新加载。",
"apps.upgradeHintCurrent": "你在 {plan} 计划中。",
"apps.upgradeHintUpgrade": "升级!",
"apps.uploadImage": "拖放文件以替换应用图片。使用正方形大小。",
"apps.uploadImageButton": "上传文件",
"apps.uploadImageFailed": "上传图片失败,请重新加载。",
"apps.uploadImageTooBig": "应用图片太大。",
"apps.welcomeSubtitle": "欢迎来到 Squidex。",
"apps.welcomeTitle": "Hi {user}",
"appSettings.editors.deleteConfirmText": "你真的要删除这个编辑器 URL 吗?",
"appSettings.editors.deleteConfirmTitle": "删除编辑器 URL",
"appSettings.editors.empty": "尚未创建编辑器 URL。",
"appSettings.editors.title": "自定义编辑器",
"appSettings.hideScheduler": "隐藏预定发布对话框",
"appSettings.patterns.deleteConfirmText": "你真的要删除这个模式吗?",
"appSettings.patterns.deleteConfirmTitle": "删除模式",
"appSettings.patterns.empty": "尚未创建模式。",
"appSettings.patterns.title": "模式",
"appSettings.refreshTooltip": "刷新 UI 设置 (CTRL + SHIFT + R)",
"appSettings.reloaded": "UI 设置重新加载。",
"appSettings.title": "界面设置",
"assets.createFolder": "创建文件夹",
"assets.createFolderFailed": "资源文件夹创建失败,请重新加载。",
"assets.createFolderTooltip": "创建新文件夹 (CTRL + SHIFT + G)",
"assets.deleteConfirmText": "你真的要删除资源吗?",
"assets.deleteConfirmTitle": "删除资源",
"assets.deleteFailed": "资源删除失败,请重新加载。",
"assets.deleteFolderConfirmText": "您真的要删除文件夹和所有资源吗?",
"assets.deleteFolderConfirmTitle": "删除文件夹",
"assets.deleteMetadataConfirmText": "你真的要删除这个元数据吗?",
"assets.deleteMetadataConfirmTitle": "删除元数据",
"assets.deleteReferrerConfirmText": "资源被内容项引用。\n\n您真的要删除资源吗?",
"assets.deleteReferrerConfirmTitle": "删除资源",
"assets.downloadVersion": "下载此版本",
"assets.dropToUpdate": "下拉更新",
"assets.duplicateFile": "资源已经上传。",
"assets.editor.flipHorizo​​ntally": "水平翻转",
"assets.editor.flipVertically": "垂直翻转",
"assets.editor.focusPointLabel": "选择焦点位置",
"assets.editor.focusPointPreview": "不同尺寸的预览",
"assets.editor.rotateLeft": "向左旋转",
"assets.editor.rotateRight": "向右旋转",
"assets.fileTooBig": "资源太大。",
"assets.folderName": "文件夹名称",
"assets.folderNameHint": "文件夹名称用作显示名称,不能唯一。",
"assets.insertAssets": "插入资源",
"assets.linkSelected": "链接选定的资源 ({count})",
"assets.listPageTitle": "资源",
"assets.loadFailed": "资源加载失败,请重新加载。",
"assets.loadFoldersFailed": "加载资源文件夹失败。请重新加载。",
"assets.metadata": "元数据",
"assets.metadataAdd": "添加元数据",
"assets.moveFailed": "资源移动失败。请重新加载。",
"assets.protected": "受保护",
"assets.refreshTooltip": "刷新资源 (CTRL + SHIFT + R)",
"assets.reloaded": "资源重新加载。",
"assets.removeConfirmText": "你真的要移除资源吗?",
"assets.removeConfirmTitle": "移除资源",
"assets.renameFolder": "重命名文件夹",
"assets.replaceConfirmText": "你真的想用更新的版本替换资源吗",
"assets.replaceConfirmTitle": "替换资源?",
"assets.replaceFailed": "替换资源失败。请重新加载。",
"assets.searchByName": "按名称搜索",
"assets.searchByTags": "按标签搜索",
"assets.selectMany": "选择资源",
"assets.specialFolder.parent": "<Parent>",
"assets.specialFolder.root": "<Root>",
"assets.tabFocusPoint": "焦点",
"assets.tabHistory": "历史",
"assets.tabImage": "图片",
"assets.tabMetadata": "元数据",
"assets.tabPreview": "预览",
"assets.tabTextEditor": "文本编辑器",
"assets.updated": "资源已更新。",
"assets.updateFailed": "更新资源失败。请重新加载。",
"assets.updateFolderFailed": "更新资源文件夹失败。请重新加载。",
"assets.uploadByDialog": "选择文件",
"assets.uploadByDrop": "将文件拖放到此处进行上传",
"assets.uploaderUploadHere": "没有正在进行的上传,将文件拖到这里。",
"assets.uploadFailed": "资源上传失败,请重新加载。",
"assets.uploadHint": "在现有项目上放置文件以使用更新版本替换资源。",
"backups.backupCountAssetsLabel": "资源",
"backups.backupCountAssetsTooltip": "存档资源",
"backups.backupCountEventsLabel": "事件",
"backups.backupCountEventsTooltip": "存档事件",
"backups.backupDownload": "下载",
"backups.backupDownloadLink": "准备就绪",
"backups.backupDuration": "持续时间",
"backups.deleteConfirmText": "你真的要删除备份吗?",
"backups.deleteConfirmTitle": "删除备份",
"backups.deleted": "备份即将被删除。",
"backups.deleteFailed": "删除备份失败。",
"backups.empty": "尚未创建备份。",
"backups.loadFailed": "加载备份失败。",
"backups.maximumReached": "您已达到最大备份数:10。",
"backups.refreshTooltip": "刷新备份 (CTRL + SHIFT + R)",
"backups.reloaded": "备份已重新加载。",
"backups.restore": "恢复备份",
"backups.restoreFailed": "无法开始恢复。",
"backups.restoreLastStatus": "上次还原操作",
"backups.restoreLastUrl": "要备份的网址",
"backups.restoreNewAppName": "可选的应用程序名称",
"backups.restorePageTitle": "恢复备份",
"backups.restoreStarted": "恢复开始,可能需要几分钟才能完成。",
"backups.restoreStartedLabel": "开始",
"backups.restoreStoppedLabel": "已停止",
"backups.restoreTitle": "恢复备份",
"backups.start": "开始备份",
"backups.started": "备份已开始,可能需要几分钟才能完成。",
"backups.startedLabel": "开始",
"backups.startFailed": "启动备份失败。",
"clients.add": "添加客户端",
"clients.addFailed": "添加客户端失败,请重新加载。",
"clients.allowAnonymous": "允许匿名访问。",
"clients.allowAnonymousHint": "允许在没有访问令牌的情况下访问通过此客户端角色配置的所有资源的 API。不要给多个客户端匿名访问。",
"clients.apiCallsLimit": "最大 API 调用数",
"clients.apiCallsLimitHint": "限制此客户端每月可以进行的 API 调用次数,以保护您的 API 队伍为其他更重要的客户端提供服务。",
"clients.clientIdValidationMessage": "名称只能包含字母、数字、破折号和空格。",
"clients.clientNamePlaceholder": "输入客户端名称",
"clients.connect": "连接",
"clients.connectWizard.cli": "连接 Squidex CLI",
"clients.connectWizard.cliHint": "下载 CLI 并连接到此应用程序以启动备份、同步Schemas或导出内容。",
"clients.connectWizard.cliStep1": "获取最新的 Squidex CLI",
"clients.connectWizard.cliStep1Download": "[从 Github 下载 CLI](https://github.com/Squidex/squidex-samples/releases)",
"clients.connectWizard.cliStep1Hint": "这些版本包含适用于所有主要操作系统的二进制文件,如果您安装了 .NET Core,则可以下载一小部分。",
"clients.connectWizard.cliStep2": "将 `<你的 Squidex CLI 下载目录>` 添加到你的 `$PATH` 变量中",
"clients.connectWizard.cliStep3": "在 CLI 配置中添加你的应用名称",
"clients.connectWizard.cliStep3Hint": "您可以在 CLI 中管理多个应用程序的配置并切换到一个应用程序。",
"clients.connectWizard.cliStep4": "在 CLI 中切换到您的应用程序",
"clients.connectWizard.manually": "手动连接",
"clients.connectWizard.manuallyHint": "获取如何与 Postman 或 curl 建立连接的说明。",
"clients.connectWizard.manuallyStep1": "使用 curl 获取令牌",
"clients.connectWizard.manuallyStep2": "只需使用以下令牌",
"clients.connectWizard.manuallyStep3": "将令牌作为 HTTP 标头添加到所有请求中",
"clients.connectWizard.manuallyTokenHint": "令牌通常会在 30 天后过期,但您可以请求多个令牌。",
"clients.connectWizard.postManDocs": "从 [文档](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman) 中的 Postman 教程开始。",
"clients.connectWizard.sdk": "使用 SDK 连接到您的应用程序",
"clients.connectWizard.sdkHelp": "你需要另一个 SDK?",
"clients.connectWizard.sdkHelpLink": "在支持论坛联系我们",
"clients.connectWizard.sdkHint": "下载 SDK 并建立与此应用程序的连接。",
"clients.connectWizard.sdkStep1": "安装.NET SDK",
"clients.connectWizard.sdkStep1Download": "SDK 可在 [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "创建客户端管理器",
"clients.connectWizard.step0Title": "设置客户端",
"clients.connectWizard.step1Title": "选择连接方式",
"clients.connectWizard.step2Title": "连接",
"clients.deleteConfirmText": "你真的要撤销客户端吗?",
"clients.deleteConfirmTitle": "撤销客户端",
"clients.empty": "尚未创建客户端。",
"clients.loadFailed": "加载客户端失败。请重新加载。",
"clients.refreshTooltip": "刷新客户端 (CTRL + SHIFT + R)",
"clients.reloaded": "客户端重新加载。",
"clients.revokeFailed": "撤销客户端失败。请重新加载。",
"clients.tokenFailed": "创建令牌失败。请重试。",
"comments.create": "创建评论",
"comments.createFailed": "创建评论失败。",
"comments.deleteConfirmText": "你真的要删除评论吗?",
"comments.deleteConfirmTitle": "删除评论",
"comments.deleteFailed": "删除评论失败。",
"comments.follow": "关注",
"comments.loadFailed": "加载评论失败。",
"comments.title": "评论",
"comments.updateFailed": "更新评论失败。",
"common.actions": "动作",
"common.administration": "管理",
"common.administrationPageTitle": "管理",
"common.api": "API",
"common.apps": "应用程序",
"common.aspectRatio": "纵横比",
"common.assets": "资源",
"common.back": "返回",
"common.backendError": "后端错误",
"common.backups": "备份",
"common.bookmarks": "书签",
"common.bytes": "bytes",
"common.cancel": "取消",
"common.category": "类别",
"common.clear": "清除",
"common.clientId": "客户端 ID",
"common.clients": "客户端",
"common.clientSecret": "客户端密码",
"common.clipboardAdded": "值已添加到您的剪贴板。",
"common.clone": "克隆",
"common.cluster": "集群",
"common.clusterPageTitle": "集群",
"common.comments": "评论",
"common.components": "组件",
"common.confirm": "确认",
"common.consumers": "消费者",
"common.content": "内容",
"common.contents": "内容",
"common.continue": "继续",
"common.contributors": "贡献者",
"common.create": "创建",
"common.created": "创建",
"common.date": "日期",
"common.dateTimeEditor.local": "本地",
"common.dateTimeEditor.now": "现在",
"common.dateTimeEditor.nowTooltip": "现在使用 (UTC)",
"common.dateTimeEditor.today": "今天",
"common.dateTimeEditor.todayTooltip": "使用今天 (UTC)",
"common.dateTimeEditor.utc": "UTC",
"common.delete": "删除",
"common.description": "说明",
"common.designer": "设计师",
"common.disabled": "已禁用",
"common.displayName": "显示名称",
"common.edit": "编辑",
"common.email": "电子邮件",
"common.enabled": "已启用",
"common.error": "错误",
"common.errorBack": "返回上一页。",
"common.errorNoPermission": "您无权执行此操作。",
"common.errorNotFound": "未找到",
"common.event": "事件",
"common.events": "事件",
"common.executed": "已执行",
"common.expertMode": "专家模式",
"common.extension": "扩展名",
"common.failed": "失败",
"common.fallback": "后备",
"common.field": "字段",
"common.files": "文件",
"common.filters": "过滤器",
"common.folder": "文件夹",
"common.folders": "文件夹",
"common.generalSettings": "通用",
"common.generate": "生成",
"common.github": "Github",
"common.height": "高度",
"common.help": "帮助",
"common.helpTour": "单击帮助图标以显示上下文特定的帮助页面。转到",
"common.hide": "隐藏",
"common.hints": "提示",
"common.history": "历史",
"common.httpConflict": "更新失败。其他用户进行了更改。请重新加载。",
"common.httpLimit": "您已超出 API 调用的最大限制。",
"common.id": "身份",
"common.label": "标签",
"common.language": "语言",
"common.languages": "语言",
"common.latitudeShort": "纬度",
"common.loading": "正在加载",
"common.logout": "注销",
"common.logs": "日志",
"common.longitudeShort": "Lon",
"common.mapHide": "隐藏地图",
"common.mapShow": "显示地图",
"common.message": "消息",
"common.name": "名称",
"common.no": "不",
"common.nothingChanged": "什么都没有改变。",
"common.noValue": "- 无值 -",
"common.or": "或",
"common.pagerInfo": "{itemFirst}-{itemLast} 的 {numberOfItems}",
"common.password": "密码",
"common.passwordConfirm": "确认密码",
"common.pattern": "模式",
"common.patterns": "模式",
"common.permissions": "权限",
"common.preview": "预览",
"common.product": "Squidex Headless CMS",
"common.project": "项目",
"common.queryOperators.contains": "包含",
"common.queryOperators.empty": "为空",
"common.queryOperators.endsWith": "以",
"common.queryOperators.eq": "等于",
"common.queryOperators.exists": "exists",
"common.queryOperators.ge": "大于或等于",
"common.queryOperators.gt": "大于",
"common.queryOperators.le": "小于或等于",
"common.queryOperators.lt": "小于",
"common.queryOperators.matchs": "匹配",
"common.queryOperators.ne": "不等于",
"common.queryOperators.startsWith": "开始于",
"common.refresh": "刷新",
"common.remember": "不要再问了",
"common.rename": "重命名",
"common.requiredHint": "必需的",
"common.reset": "重置",
"common.restore": "恢复",
"common.role": "角色",
"common.roles": "角色",
"common.rule": "规则",
"common.rules": "规则",
"common.sampleCodeLabel": "示例代码在",
"common.save": "保存",
"common.saveShortcut": "CTRL + S",
"common.schema": "Schemas",
"common.search": "搜索",
"common.searchGoogleMaps": "搜索谷歌地图",
"common.searchResults": "搜索结果",
"common.separateByLine": "按行分隔",
"common.settings": "设置",
"common.sidebar": "侧边栏扩展",
"common.sidebarTour": "侧边栏导航包含有用的上下文特定链接。您可以在此处查看此Schemas随时间变化的历史记录。",
"common.skiped": "跳过",
"common.slug": "弹头",
"common.stars.max": "不得超过 15 颗星",
"common.status": "状态",
"common.statusChangeTo": "更改为",
"common.submit": "提交",
"common.subscription": "订阅",
"common.succeeded": "成功",
"common.tagAdd": ", 添加标签",
"common.tagAddReference": ", 添加引用",
"common.tagAddSchema": ", 添加Schemas",
"common.tags": "标签",
"common.tagsAll": "所有标签",
"common.time": "时间",
"common.update": "更新",
"common.upload": "上传",
"common.url": "URL",
"common.users": "用户",
"common.value": "值",
"common.width": "宽度",
"common.workflow": "工作流程",
"common.workflows": "工作流程",
"common.yes": "是",
"contents.addComponent": "添加组件",
"contents.arrayAddItem": "添加项目",
"contents.arrayClear": "清除",
"contents.arrayClearConfirmText": "你真的要清空数组吗?",
"contents.arrayClearConfirmTitle": "清除数组",
"contents.arrayCloneItem": "克隆这个项目",
"contents.arrayCollapseAll": "折叠所有项目",
"contents.arrayCollapseItem": "收起此项",
"contents.arrayExpandAll": "展开所有项目",
"contents.arrayExpandItem": "展开此项",
"contents.arrayMoveBottom": "将此项目移到底部",
"contents.arrayMoveDown": "将此项向下移动",
"contents.arrayMoveTop": "将此项移至顶部",
"contents.arrayMoveUp": "将此项向上移动",
"contents.arrayNoFields": "先添加一个嵌套字段来添加项目。",
"contents.assetsUpload": "删除文件或点击",
"contents.autotranslate": "从母语自动翻译",
"contents.bulkFailed": "删除或更新内容失败。请重新加载。",
"contents.changeStatusTo": "将内容项更改为 {action}",
"contents.changeStatusToImmediately": "立即设置为 {action}。",
"contents.changeStatusToLater": "在稍后的日期和时间设置为 {action}。",
"contents.componentNoSchema": "添加至少一个Schemas来设置组件。",
"contents.componentsNoSchema": "添加至少一个Schemas来添加组件。",
"contents.contentNotValid": "内容元素无效,请用所有语言(如果可本地化)检查左侧带有红色条的字段。",
"contents.contentTab.editor": "编辑器",
"contents.contentTab.references": "参考资料",
"contents.contentTab.referencing": "引用",
"contents.create": "新建",
"contents.createContentTooltip": "新建内容 (CTRL + SHIFT + G)",
"contents.created": "内容创建成功。",
"contents.createdByFieldDescription": "创建内容项的用户。",
"contents.createFailed": "创建内容失败,请重新加载。",
"contents.createFieldDescription": "创建内容项的日期时间。",
"contents.createPageTitle": "创建内容",
"contents.createTitle": "新内容",
"contents.currentStatusLabel": "当前版本",
"contents.deleteConfirmText": "你真的要删除内容吗?",
"contents.deleteConfirmTitle": "删除内容",
"contents.deleteManyConfirmText": "您真的要删除选定的内容项吗?",
"contents.deleteReferrerConfirmText": "该内容被另一个内容项引用。\n\n您真的要删除该内容吗?",
"contents.deleteReferrerConfirmTitle": "删除内容",
"contents.deleteVersionConfirmText": "你真的要删除这个版本吗?",
"contents.deleteVersionFailed": "删除版本失败。请重新加载。",
"contents.draftNew": "新草稿",
"contents.draftStatus": "新版本",
"contents.editPageTitle": "编辑内容",
"contents.invariantFieldDescription": "内容项的 '{fieldName}' 字段。",
"contents.languageModeAll": "所有语言",
"contents.languageModeSingle": "单一语言",
"contents.lastModifiedByFieldDescription": "上次修改内容项的用户。",
"contents.lastModifiedFieldDescription": "上次修改内容项的日期时间。",
"contents.lastUpdatedLabel": "上次更新",
"contents.loadContent": "加载",
"contents.loadContentFailed": "加载内容失败,请重新加载。",
"contents.loadDataFailed": "加载数据失败,请重新加载。",
"contents.loadFailed": "加载内容失败,请重新加载。",
"contents.loadVersionFailed": "加载新版本失败。请重新加载。",
"contents.localizedFieldDescription": "内容项的 '{fieldName}' 字段(本地化)。",
"contents.newStatusFieldDescription": "内容项的新状态。",
"contents.noReference": "- 无引用 -",
"contents.noReferences": "此内容没有引用。",
"contents.noReferencing": "此内容未被其他项目引用。",
"contents.pendingChangesTextToChange": "您有未保存的更改。\n\n当您更改状态时,您将丢失它们。\n\n**仍要继续吗?**",
"contents.pendingChangesTextToClose": "您有未保存的更改。\n\n当您关闭当前内容视图时,您将丢失它们。\n\n**您是否仍要继续?**",
"contents.pendingChangesTextToPreview": "您有未保存的更改。\n\n您不会在预览中看到它们。\n\n**您是否仍要继续?**",
"contents.pendingChangesTitle": "未保存的更改",
"contents.publishAll": "全部发布",
"contents.referencesCreateNew": "新增",
"contents.referencesCreatePublish": "创建和发布",
"contents.referencesLink": "链接所选内容 ({count})",
"contents.referencesSelectExisting": "选择现有",
"contents.referencesSelectSchema": "选择 {schema}",
"contents.refreshTooltip": "刷新内容 (CTRL + SHIFT + R)",
"contents.reloaded": "内容已重新加载。",
"contents.removeConfirmText": "您真的要删除内容吗?",
"contents.removeConfirmTitle": "删除内容",
"contents.saveAndPublish": "保存并发布",
"contents.scheduledAt": "at",
"contents.scheduledAtLabel": "at",
"contents.scheduledTo": "to",
"contents.schemasPageTitle": "内容",
"contents.searchPlaceholder": "全文搜索",
"contents.searchSchemasPlaceholder": "搜索Schemas...",
"contents.selectionCount": "{count} 个选定的项目",
"contents.statusFieldDescription": "内容项的状态。",
"contents.statusQueries": "状态查询",
"contents.stockPhotoEmpty": "未选择任何内容",
"contents.stockPhotoSearch": "通过 Unsplash 搜索照片",
"contents.tableHeaders.created": "创建",
"contents.tableHeaders.createdBy": "创建者",
"contents.tableHeaders.createdByShort": "By",
"contents.tableHeaders.id": "Id",
"contents.tableHeaders.lastModified": "更新",
"contents.tableHeaders.lastModifiedBy": "更新者",
"contents.tableHeaders.lastModifiedByShort": "By",
"contents.tableHeaders.nextStatus": "下一个状态",
"contents.tableHeaders.status": "状态",
"contents.tableHeaders.version": "版本",
"contents.unpublishReferrerConfirmText": "该内容被另一个已发布的内容项引用。\n\n您真的要取消发布此内容吗?",
"contents.unpublishReferrerConfirmTitle": "取消发布内容",
"contents.unsavedChangesText": "您有未保存的更改。要立即加载吗?",
"contents.unsavedChangesTitle": "未保存的更改",
"contents.unsetValue": "未设置值",
"contents.unsetValueConfirmText": "如果您取消设置该值,您可能会丢失更改。\n\n您真的要这样做吗?",
"contents.unsetValueConfirmTitle": "你想取消设置吗?",
"contents.updated": "内容更新成功。",
"contents.updateFailed": "更新内容失败,请重新加载。",
"contents.validate": "验证",
"contents.validationHint": "当您看到验证错误时,请记住检查所有语言。",
"contents.versionCompare": "比较",
"contents.versionDelete": "删除此版本",
"contents.versionFieldDescription": "内容项的版本",
"contents.versionViewing": "查看版本**{version}**。",
"contents.viewLatest": "查看最新",
"contents.viewReset": "重置默认视图",
"contributors.add": "添加贡献者",
"contributors.addFailed": "添加贡献者失败。请重新加载。",
"contributors.contributorAssigned": "一个具有输入电子邮件地址的新用户已被创建并被指定为贡献者。",
"contributors.contributorAssignedExisting": "用户已被分配",
"contributors.contributorAssignedInvited": "用户已被邀请并分配。",
"contributors.contributorAssignedOld": "用户已被添加为贡献者。",
"contributors.deleteConfirmText": "你真的要移除贡献者吗?",
"contributors.deleteConfirmTitle": "移除贡献者",
"contributors.deleteFailed": "删除贡献者失败。请重新加载。",
"contributors.emailPlaceholder": "查找现有用户或通过电子邮件邀请",
"contributors.empty": "没有找到贡献者。",
"contributors.import.emailsDetected": "检测到的电子邮件:{count}",
"contributors.import.run": "添加贡献者",
"contributors.import.run2": "导入",
"contributors.importButton": "一次添加多个贡献者",
"contributors.importHintg": "大团队?",
"contributors.importTitle": "导入贡献者",
"contributors.loadFailed": "加载贡献者失败。请重新加载。",
"contributors.planHint": "您的计划允许最多 {maxContributors} 个贡献者。",
"contributors.refreshTooltip": "刷新贡献者 (CTRL + SHIFT + R)",
"contributors.reloaded": "贡献者重新加载。",
"contributors.search": "搜索",
"contributors.userNotFound": "用户不存在。",
"dashboard.apiCallsCard": "API 调用",
"dashboard.apiCallsChart": "API 调用图表",
"dashboard.apiCallsLimitLabel": "每月限制",
"dashboard.apiCallsSummaryCard": "API 调用摘要",
"dashboard.apiDocumentationCard": "API 文档",
"dashboard.apiPerformanceCard": "API 性能 (ms): {summary}ms avg",
"dashboard.apiPerformanceChart": "API 性能图表",
"dashboard.assetSizeCard": "资源大小 (MB",
"dashboard.assetSizeLabel": "总大小",
"dashboard.assetSizeLimitLabel": "总限制",
"dashboard.assetTotalSize": "资源总存储大小",
"dashboard.assetUpdloadsCountChart": "资源上传计数图表",
"dashboard.assetUploadsCard": "资源上传",
"dashboard.assetUploadsSizeChart": "资源上传大小图表",
"dashboard.configSaved": "配置已保存。",
"dashboard.contentApi": "内容 API",
"dashboard.contentApiDescription": "适用于您的应用内容的 OpenAPI 3.0 兼容文档。",
"dashboard.contentsSummaryCard": "项目数量",
"dashboard.currentMonthLabel": "本月",
"dashboard.downloadLog": "下载日志",
"dashboard.editConfig": "编辑配置",
"dashboard.githubCard": "Github",
"dashboard.githubCardDescription": "从 Github 获取源代码并报告错误或寻求支持。",
"dashboard.historyCard": "历史",
"dashboard.pageTitle": "仪表盘",
"dashboard.resetConfigConfirmText": "您真的要将仪表板重置为默认设置吗?",
"dashboard.resetConfigConfirmTitle": "重置配置",
"dashboard.schemaNewCard": "新Schemas",
"dashboard.schemaNewCardDescription": "Schemas定义了内容元素的结构。",
"dashboard.schemasCard": "Schemas",
"dashboard.schemasCardDescription": "深入了解此应用的数据模型。",
"dashboard.stackedChart": "折叠",
"dashboard.supportCard": "反馈与支持",
"dashboard.supportCardDescription": "提供反馈和请求功能以帮助我们改进 Squidex。",
"dashboard.trafficChart": "API 流量图表",
"dashboard.trafficHeader": "流量 (MB)",
"dashboard.trafficLimitLabel": "每月限制",
"dashboard.trafficSummaryCard": "API 流量汇总",
"dashboard.welcomeText": "欢迎使用 **{app}** 仪表板。",
"dashboard.welcomeTitle": "Hi {user}",
"eventConsumers.count": "计数",
"eventConsumers.loadFailed": "加载事件消费者失败。请重新加载。",
"eventConsumers.pageTitle": "事件消费者",
"eventConsumers.position": "位置",
"eventConsumers.refreshTooltip": "刷新事件消费者 (CTRL + SHIFT + R)",
"eventConsumers.reloaded": "事件消费者重新加载。",
"eventConsumers.resetFailed": "无法重置事件消费者。请重新加载。",
"eventConsumers.resetTooltip": "重置事件消费者",
"eventConsumers.startFailed": "无法启动事件消费者。请重新加载。",
"eventConsumers.startTooltip": "启动事件消费者",
"eventConsumers.stopFailed": "无法停止事件消费者。请重新加载。",
"eventConsumers.stopTooltip": "停止事件消费者",
"features.loadFailed": "加载功能失败。请重新加载。",
"history.loadFailed": "加载历史记录失败。请重新加载。",
"history.title": "活动",
"languages.add": "添加语言",
"languages.addFailed": "添加语言失败。请重新加载。",
"languages.deleteConfirmText": "你真的要删除语言吗?",
"languages.deleteConfirmTitle": "删除语言",
"languages.deleteFailed": "删除语言失败。请重新加载。",
"languages.loadFailed": "加载语言失败。请重新加载。",
"languages.master": "是大师",
"languages.masterHint": "如果没有定义回退,其他语言回退到母版。",
"languages.optional": "是可选的",
"languages.optionalHint": "不得输入可选语言的值,即使字段是必需的。",
"languages.refreshTooltip": "刷新语言 (CTRL + SHIFT + R)",
"languages.reloaded": "语言已重新加载。",
"languages.updateFailed": "更改语言失败。请重新加载。",
"news.headline": "有什么新鲜事?",
"news.title": "新功能",
"notifo.subscripeTooltip": "单击此按钮可订阅所有更改并接收推送通知。",
"plans.billingPortal": "计费门户",
"plans.billingPortalHint": "前往账单门户查看付款历史和订阅概览。",
"plans.change": "改变",
"plans.changeConfirmTitle": "更改订阅",
"plans.changeFailed": "更改计划失败。请重新加载。",
"plans.includedCalls": "API 调用",
"plans.includedContributors": "贡献者",
"plans.includedStorage": "存储",
"plans.includedTraffic": "交通",
"plans.loadFailed": "加载计划失败。请重新加载。",
"plans.noPlanConfigured": "未配置计划,此应用无限制使用。",
"plans.notPlanOwner": "您尚未创建订阅。因此您无法更改计划。",
"plans.perMonth": "每月",
"plans.perYear": "每年",
"plans.refreshTooltip": "刷新计划 (CTRL + SHIFT + R)",
"plans.reloaded": "计划重新加载。",
"plans.selected": "已选择",
"profile.title": "个人资料",
"profile.userEmail": "登录方式",
"roles.add": "添加角色",
"roles.addFailed": "添加角色失败,请重新加载。",
"roles.default.owner": "可以做任何事情,包括删除应用程序。",
"roles.default.reader": "只能读取资源和内容。",
"roles.defaults.developer": "可以使用 API 视图,编辑资源、内容、Schemas、规则、工作流和 appSettings.patterns。",
"roles.defaults.editor": "可以编辑资源和内容并查看工作流程。",
"roles.deleteConfirmText": "删除角色",
"roles.deleteConfirmTitle": "你真的要删除角色吗?",
"roles.loadFailed": "加载角色失败。请重新加载。",
"roles.loadPermissionsFailed": "加载权限失败。请重新加载。",
"roles.permissions": "权限",
"roles.permissionsDescription": "权限在 API 级别限制允许的操作和查询,是一项安全功能。",
"roles.permissionsPlaceholder": "开始输入以搜索权限",
"roles.properties": "属性",
"roles.properties.hideAPI": "隐藏 API",
"roles.properties.hideAssets": "隐藏资源",
"roles.properties.hideContents": "隐藏 {schema} 内容",
"roles.properties.hideSchemas": "隐藏Schemas",
"roles.properties.hideSettings": "隐藏设置",
"roles.propertiesDescription": "属性描述管理 UI 的行为,但不为 API 提供安全性。",
"roles.refreshTooltip": "刷新角色 (CTRL + SHIFT + R)",
"roles.reloaded": "重新加载角色。",
"roles.revokeFailed": "撤销角色失败,请重新加载。",
"roles.roleNamePlaceholder": "输入角色名称",
"roles.updateFailed": "更新角色失败。请重新加载。",
"rules.actionData": "动作数据",
"rules.actionHint": "动作类型的选择以后不能更改。",
"rules.cancelFailed": "取消规则失败,请重新加载。",
"rules.create": "新规则",
"rules.createFailed": "创建规则失败。请重新加载。",
"rules.createTooltip": "新规则 (CTRL + SHIFT + G)",
"rules.deleteConfirmText": "你真的要删除规则吗?",
"rules.deleteConfirmTitle": "删除规则",
"rules.deleteFailed": "删除规则失败,请重新加载。",
"rules.empty": "尚未创建规则。",
"rules.emptyAddRule": "添加规则",
"rules.enqueued": "规则已加入队列。",
"rules.itemPageTitle": "规则",
"rules.listPageTitle": "规则",
"rules.loadFailed": "加载规则失败。请重新加载。",
"rules.readMore": "阅读更多",
"rules.refreshEventsTooltip": "刷新事件 (CTRL + SHIFT + R)",
"rules.refreshTooltip": "刷新规则 (CTRL + SHIFT + R)",
"rules.reloaded": "规则重新加载。",
"rules.restarted": "规则将在几秒钟后开始运行。",
"rules.ruleEvents.cancelFailed": "取消规则事件失败。请重新加载。",
"rules.ruleEvents.enqueue": "入队",
"rules.ruleEvents.enqueued": "事件已入队。将在几秒钟后重新发送。",
"rules.ruleEvents.enqueueFailed": "无法将规则事件入队。请重新加载。",
"rules.ruleEvents.lastInvokedLabel": "上次调用",
"rules.ruleEvents.listPageTitle": "规则事件",
"rules.ruleEvents.loadFailed": "加载事件失败。请重新加载。",
"rules.ruleEvents.nextAttemptLabel": "下一个",
"rules.ruleEvents.numAttemptsLabel": "尝试次数",
"rules.ruleEvents.reloaded": "RuleEvents 重新加载。",
"rules.ruleSimulator.listPageTitle": "模拟器",
"rules.ruleSyntax.if": "如果",
"rules.ruleSyntax.then": "那么",
"rules.run": "运行",
"rules.runFailed": "运行规则失败。请重新加载。",
"rules.runFromSnapshots": "以最新状态运行",
"rules.runningRule": "规则 '{name}' 当前正在运行。",
"rules.runRuleConfirmText": "你真的想为所有事件运行规则吗?",
"rules.runRuleConfirmTitle": "运行规则",
"rules.simulate": "模拟",
"rules.simulateTooltip": "使用最近 100 个事件模拟此规则。",
"rules.simulator": "模拟器",
"rules.stop": "规则很快就会停止。",
"rules.triggerConfirmText": "你真的要触发规则吗?",
"rules.triggerConfirmTitle": "触发规则",
"rules.triggerFailed": "触发规则失败。请重新加载。",
"rules.triggerHint": "以后不能更改触发器类型的选择。",
"rules.unnamed": "未命名规则",
"rules.updateFailed": "更新规则失败。请重新加载。",
"schema.clone": "克隆Schemas",
"schema.create": "创建Schemas",
"schema.field.defaultValue": "默认值",
"schema.field.editor": "编辑器",
"schema.field.hiddenMarker": "隐藏",
"schema.field.localizable": "Localizable",
"schema.field.localizableMarker": "localizable",
"schema.field.placeholder": "占位符",
"schema.field.required": "必需",
"schema.field.tabEditing": "编辑",
"schema.field.tabValidation": "验证",
"schema.field.unique": "唯一",
"schema.field.visibleMarker": "可见",
"schema.fieldTypes.array.count": "项目",
"schema.fieldTypes.assets.size": "大小",
"schema.fieldTypes.string.length": "长度",
"schema.fieldTypes.string.lengthMax": "最大长度",
"schema.fieldTypes.string.pattern": "正则表达式",
"schema.loadSchemaFailed": "加载Schemas失败。请重新加载。",
"schema.modeComponent": "组件",
"schema.published": "已发布",
"schema.reloaded": "Schemas已重新加载。",
"schema.saved": "Schemas保存成功。",
"schema.synchronized": "Schemas同步成功。",
"schema.tabMore": "更多",
"schema.tabScripts": "脚本",
"schema.tabUI": "UI",
"schema.unpublished": "未发布",
"schemas.addField": "添加字段",
"schemas.addFieldAndClose": "创建并关闭",
"schemas.addFieldAndCreate": "创建并添加字段",
"schemas.addFieldAndEdit": "创建和编辑字段",
"schemas.addFieldButton": "添加字段",
"schemas.addFieldFailed": "添加字段失败。请重新加载。",
"schemas.addNestedField": "添加嵌套字段",
"schemas.changeCategoryFailed": "更改类别失败。请重新加载。",
"schemas.contentEditorUrl": "内容编辑器扩展",
"schemas.contentSidebarUrl": "内容侧边栏扩展",
"schemas.contentSidebarUrlHint": "详细信息视图中侧边栏插件的 URL。",
"schemas.contentsSidebarUrl": "内容侧边栏扩展",
"schemas.contentsSidebarUrlHint": "列表视图中侧边栏插件的 URL。",
"schemas.contextMenuTour": "打开上下文菜单以删除Schemas或为内容更改创建一些脚本。",
"schemas.createCategory": "创建新类别...",
"schemas.createFailed": "无法创建Schemas。请重新加载。",
"schemas.createSchemaTooltip": "新Schemas (CTRL + SHIFT + G)",
"schemas.deleteConfirmText": "您真的要删除Schemas吗?",
"schemas.deleteConfirmTitle": "删除Schemas",
"schemas.deleteFailed": "删除Schemas失败。请重新加载。",
"schemas.deleteFieldFailed": "删除字段失败。请重新加载。",
"schemas.deleteRuleConfirmText": "你真的要删除这个字段规则吗?",
"schemas.deleteRuleConfirmTitle": "删除字段规则",
"schemas.deleteUrlConfirmText": "你真的要删除这个 URL 吗?",
"schemas.deleteUrlConfirmTitle": "删除 URL",
"schemas.disableFieldFailed": "禁用字段失败。请重新加载。",
"schemas.enableFieldFailed": "无法启用字段。请重新加载。",
"schemas.export.deleteFields": "删除字段",
"schemas.export.recreateFields": "重新创建字段",
"schemas.export.synchronize": "同步",
"schemas.field.allowedValues": "允许的值",
"schemas.field.defaultValues": "默认值",
"schemas.field.defaultValuesHint": "设置每种语言的默认值并覆盖默认值属性(如果已定义)。仅在真正需要时才使用它。",
"schemas.field.deleteConfirmText": "您真的要删除该字段吗?",
"schemas.field.deleteConfirmTitle": "删除字段",
"schemas.field.disable": "在 UI 中禁用",
"schemas.field.disabledMarker": "已禁用",
"schemas.field.editorUrl": "编辑器网址",
"schemas.field.editorUrlHint": "如果您使用自定义编辑器,请访问您的插件的 URL。",
"schemas.field.empty": "尚未创建字段。",
"schemas.field.enable": "在 UI 中启用",
"schemas.field.enabledMarker": "已启用",
"schemas.field.halfWidth": "半宽",
"schemas.field.halfWidthHint": "在编辑或创建页面上,当有足够的空间时,只显示半宽的字段。",
"schemas.field.hide": "隐藏在 API",
"schemas.field.hintsHint": "为文档和 UI 描述这个字段。",
"schemas.field.inlineEditable": "内联可编辑",
"schemas.field.labelHint": "文档和 UI 的显示名称。",
"schemas.field.localizableHint": "您可以将字段标记为可本地化。这意味着这取决于语言,例如城市名称。",
"schemas.field.lock": "锁定并防止更改",
"schemas.field.lockConfirmText": "警告:无法撤消锁定字段!锁定的字段定义无法再解锁、删除或更改。\n\n您真的要锁定此字段吗?",
"schemas.field.lockConfirmTitle": "锁定字段",
"schemas.field.lockedMarker": "锁定",
"schemas.field.nameHint": "API 响应中的字段名称。",
"schemas.field.namePlaceholder": "输入字段名称",
"schemas.field.nameValidationMessage": "名称必须是骆驼大小写的有效 javascript 名称。",
"schemas.field.placeholderHint": "定义输入控件的占位符。",
"schemas.field.requiredOnPublish": "发布时需要",
"schemas.field.show": "在 API 中显示",
"schemas.field.tabCommon": "通用",
"schemas.field.tagsHint": "为自动化流程注释您的领域的标签。",
"schemas.fieldTypes.array.countMax": "最大项目数",
"schemas.fieldTypes.array.countMin": "最小项目",
"schemas.fieldTypes.array.description": "嵌入对象列表。",
"schemas.fieldTypes.assets.allowDuplicates": "允许重复值",
"schemas.fieldTypes.assets.count": "计数",
"schemas.fieldTypes.assets.countMax": "最大资源",
"schemas.fieldTypes.assets.countMin": "最小资源",
"schemas.fieldTypes.assets.description": "图片、视频、文档。",
"schemas.fieldTypes.assets.fileExtensions": "文件扩展名",
"schemas.fieldTypes.assets.folderId": "文件夹",
"schemas.fieldTypes.assets.folderIdHint": "新资源将上传到的资源文件夹。",
"schemas.fieldTypes.assets.mustBeImage": "必须是图片",
"schemas.fieldTypes.assets.previewFileName": "仅文件名",
"schemas.fieldTypes.assets.previewImage": "如果不是图像,则只有缩略图或文件名",
"schemas.fieldTypes.assets.previewImageAndFileName": "缩略图和文件名",
"schemas.fieldTypes.assets.previewMode": "PreviewMode",
"schemas.fieldTypes.assets.previewModeHint": "内容列表中资源的预览模式。",
"schemas.fieldTypes.assets.resolve": "解析第一个资源",
"schemas.fieldTypes.assets.resolveHint": "显示内容列表中第一个引用的资源。",
"schemas.fieldTypes.assets.sizeMax": "最小尺寸",
"schemas.fieldTypes.assets.sizeMin": "最大尺寸",
"schemas.fieldTypes.boolean.description": "是或否,对或错。",
"schemas.fieldTypes.component.description": "在此内容中嵌入另一个Schemas。",
"schemas.fieldTypes.components.description": "将其他Schemas作为数组嵌入到此内容中。",
"schemas.fieldTypes.dateTime.defaultMode": "默认模式",
"schemas.fieldTypes.dateTime.description": "活动日期,开放时间。",
"schemas.fieldTypes.dateTime.rangeMax": "最大值",
"schemas.fieldTypes.dateTime.rangeMin": "最小值",
"schemas.fieldTypes.geolocation.description": "坐标:纬度和经度。",
"schemas.fieldTypes.json.description": "JSON 格式的数据,供开发人员使用。",
"schemas.fieldTypes.number.description": "ID、订单号、评级、数量。",
"schemas.fieldTypes.number.range": "范围",
"schemas.fieldTypes.number.rangeMax": "最大值",
"schemas.fieldTypes.number.rangeMin": "最小值",
"schemas.fieldTypes.references.count": "项目",
"schemas.fieldTypes.references.countMax": "最大项目数",
"schemas.fieldTypes.references.countMin": "最小项目",
"schemas.fieldTypes.references.description": "链接到其他内容项。",
"schemas.fieldTypes.references.mustBePublished": "必须发布参考文献",
"schemas.fieldTypes.references.resolveHint": "当 MaxItems 设置为 1 时,在内容列表中显示引用项的名称。",
"schemas.fieldTypes.string.characters": "字符",
"schemas.fieldTypes.string.charactersMax": "最大字符数",
"schemas.fieldTypes.string.charactersMin": "最小字符数",
"schemas.fieldTypes.string.contentType": "内容类型",
"schemas.fieldTypes.string.description": "标题、名称、段落。",
"schemas.fieldTypes.string.folderId": "资源文件夹",
"schemas.fieldTypes.string.folderIdHint": "新资源将上传到的资源文件夹。",
"schemas.fieldTypes.string.lengthMin": "最小长度",
"schemas.fieldTypes.string.patternMessage": "模式消息",
"schemas.fieldTypes.string.suggestions": "建议",
"schemas.fieldTypes.string.wordHint": "字数和字符数在纯文本上计算。纯文本根据定义的内容类型计算,可以是 Markdown 或 HTML。",
"schemas.fieldTypes.string.words": "单词",
"schemas.fieldTypes.string.wordsMax": "最大字数",
"schemas.fieldTypes.string.wordsMin": "最小字数",
"schemas.fieldTypes.tags.count": "项目",
"schemas.fieldTypes.tags.countMax": "最大项目数",
"schemas.fieldTypes.tags.countMin": "最小项目",
"schemas.fieldTypes.tags.description": "标签的特殊格式。",
"schemas.fieldTypes.ui.description": "编辑 UI 的分隔符。",
"schemas.hideFieldFailed": "隐藏字段失败。请重新加载。",
"schemas.import": "导入Schemas",
"schemas.listFields": "列表字段",
"schemas.listFieldsEmpty": "将字段拖放到此处或重新排序以显示内容列表中的字段。当未定义列表字段时,使用第一个字段。",
"schemas.loadFailed": "加载Schemas失败。请重新加载。",
"schemas.lockFieldFailed": "无法锁定字段。请重新加载。",
"schemas.modeComponentDescription": "只能嵌入到组件字段...",
"schemas.modeMultiple": "多个内容",
"schemas.modeMultipleDescription": "最适合多个实例,如博客文章、页面、作者、产品...",
"schemas.modeSingle": "单一内容",
"schemas.modeSingleDescription": "最适合单个实例,如主页、隐私政策、设置...",
"schemas.nameWarning": "这些值以后不能更改。",
"schemas.previewUrls.empty": "未配置预览网址。",
"schemas.previewUrls.help": "查看集成帮助页面以了解有关预览 URL 的更多信息。",
"schemas.previewUrls.namePlaceholder": "网络或移动",
"schemas.previewUrls.title": "预览网址",
"schemas.previewUrls.urlPlaceholder": "带变量的 URL",
"schemas.publishedTour": "请注意,您必须先发布Schemas,然后才能向其中添加内容。",
"schemas.publishFailed": "无法发布Schemas。请重新加载。",
"schemas.referenceFields": "参考字段",
"schemas.referenceFieldsEmpty": "将字段拖放到此处或重新排序以在被其他内容引用时显示字段。当未定义引用字段时,将使用列表字段代替。",
"schemas.reorderFieldsFailed": "重新排序字段失败。请重新加载。",
"schemas.rules.action": "动作",
"schemas.rules.condition": "Javascript 中的条件",
"schemas.rules.empty": "没有配置字段规则。",
"schemas.rules.title": "字段规则",
"schemas.rules.when": "何时",
"schemas.saveFieldAndClose": "保存并关闭",
"schemas.saveFieldAndNew": "保存并添加字段",
"schemas.schemaHintsHint": "为文档和用户界面描述这个Schemas。",
"schemas.schemaLabelHint": "文档和用户界面的显示名称。",
"schemas.schemaNameHint": "您只能使用字母、数字和破折号,并且不能超过 40 个字符。",
"schemas.schemaNameValidationMessage": "名称只能包含字母、数字、破折号和空格。",
"schemas.schemaTagsHint": "用于注释自动化流程Schemas的标签。",
"schemas.searchPlaceholder": "搜索Schemas...",
"schemas.showFieldFailed": "显示字段失败。请重新加载。",
"schemas.synchronizeFailed": "同步Schemas失败。请重新加载。",
"schemas.tabFields": "字段",
"schemas.tabJson": "Json",
"schemas.ui": "指定的字段",
"schemas.ui.unassignedFields": "未分配的字段",
"schemas.unpublishFailed": "无法取消发布Schemas。请重新加载。",
"schemas.updateFailed": "更新Schemas失败。请重新加载。",
"schemas.updateFieldFailed": "更新字段失败。请重新加载。",
"schemas.updatePreviewUrlsFailed": "无法配置预览网址。请重新加载。",
"schemas.updateRulesFailed": "更新Schemas规则失败。请重新加载。",
"schemas.updateScriptsFailed": "更新Schemas脚本失败。请重新加载。",
"schemas.updateUIFieldsFailed": "无法更新 UI 字段。请重新加载。",
"schemas.validateOnPublish": "发布时验证",
"search.addFilter": "添加过滤器",
"search.addGroup": "添加组",
"search.addSorting": "添加排序",
"search.advancedTour": "单击此图标可显示高级搜索菜单!",
"search.customQuery": "自定义查询",
"search.fullTextTour": "使用全文搜索在所有领域和语言中搜索内容!",
"search.help": "在 [文档](https://docs.squidex.io/04-guides/02-api.html) 中阅读有关过滤的更多信息。",
"search.myQueries": "我的查询",
"search.nameQuery": "命名查询",
"search.queriesEmpty": "搜索 {types} 并在搜索表单中使用 <i class=\"icon-star-empty\"></i> 图标来保存所有贡献者的查询。",
"search.queryAllNewestFirst": "全部(最新的在前)",
"search.queryAllOldestFirst": "所有(最旧的在前)",
"search.quickNavPlaceholder": "快速导航(按'q')",
"search.saveQueryMyself": "只为我保存查询。",
"search.searchFailed": "搜索失败,请重新加载。",
"search.sharedQueries": "共享查询",
"search.sorting": "排序",
"start.login": "登录 Squidex",
"start.loginHint": "登录按钮将打开一个新的弹出窗口。一旦您登录成功,我们会将您重定向到 Squidex 管理门户。",
"start.madeBy": "自豪地制作",
"start.madeByCopyright": "Sebastian Stehle 和贡献者,2016-2021",
"tour.joinForum": "加入我们的论坛",
"tour.joinGithub": "加入我们的 Github",
"tour.skip": "跳过游览",
"tour.step0Next": "我们看看周围",
"tour.step0Text": "您可以立即开始管理和分发您的内容,但我们想先向您介绍一些基础知识...\n\n如何",
"tour.step1Next": "继续",
"tour.step1Text": "应用程序是您项目的存储库,例如(博客、网上商店或移动应用程序)。您可以为您的应用程序分配贡献者以协同工作。\n\n您可以在其中创建无限数量的应用程序Squidex 同时管理多个项目。",
"tour.step2Next": "继续!",
"tour.step2Text": "Schemas定义内容的结构、内容项的字段和数据类型。\n\n在向Schemas添加内容之前,请确保点击顶部的“发布”按钮使Schemas可用于您的内容 appSettings.editors。",
"tour.step3Next": "快到了!",
"tour.step3Text": "内容是您的应用程序中按Schemas分组的实际数据。\n\n首先选择一个已发布的Schemas,然后为此Schemas添加内容。",
"tour.step4Next": "知道了!",
"tour.step4Text": "资源包含所有也可以链接到您的内容的文件。例如图像、视频或文档。\n\n您可以在此处上传资源供以后使用,也可以在创建时直接上传带有资源字段的新内容项。",
"tour.step5Text": "但这还不是我们可以提供的全部支持。\n\n您可以访问 https://docs.squidex.io/> 阅读更多信息。\n\n您想加入我们的社区吗? ?",
"tour.step5Title": "太棒了,现在你知道基础了!",
"tour.tooltipConfirm": "知道了",
"tour.tooltipStop": "停止游览",
"tour.welcome": "欢迎来到",
"tour.welcomeProduct": "Squidex CMS",
"translate.translateFailed": "无法翻译文本。请重新加载。",
"usages.loadCallsFailed": "加载调用使用失败。请重新加载。",
"usages.loadMonthlyCallsFailed": "未能加载每月 API 调用。请重新加载。",
"usages.loadStorageFailed": "加载存储使用失败。请重新加载。",
"usages.loadTodayStorageFailed": "无法加载今天的存储大小。请重新加载。",
"users.create": "新建",
"users.createFailed": "创建用户失败,请重新加载。",
"users.createPageTitle": "创建用户",
"users.createTitle": "新用户",
"users.createTooltip": "新用户 (CTRL + N)",
"users.deleteConfirmText": "你真的要删除这个用户吗?",
"users.deleteConfirmTitle": "删除用户",
"users.deleteFailed": "删除用户失败,请重新加载。",
"users.editPageTitle": "编辑用户",
"users.editTitle": "编辑用户",
"users.listPageTitle": "用户管理",
"users.listTitle": "用户",
"users.loadFailed": "加载用户失败,请重新加载。",
"users.loadUserFailed": "加载用户失败。请重新加载。",
"users.lockFailed": "锁定用户失败。请重新加载。",
"users.lockTooltip": "锁定用户",
"users.passwordConfirmValidationMessage": "密码必须相同。",
"users.refreshTooltip": "刷新用户 (CTRL + SHIFT + R)",
"users.reloaded": "用户重新加载。",
"users.search": "搜索用户",
"users.unlockFailed": "解锁用户失败。请重新加载。",
"users.unlockTooltip": "解锁用户",
"users.updateFailed": "更新用户失败。请重新加载。",
"validation.between": "{field} 必须介于 '{min}' 和 '{max}' 之间。",
"validation.betweenlength": "{field|upper} 必须介于 {minlength} 和 {maxlength} 项之间。",
"validation.betweenlengthstring": "{field|upper} 必须介于 {minlength} 和 {maxlength} 个字符之间。",
"validation.email": "{field|upper} 必须是电子邮件地址。",
"validation.exactly": "{field|upper} 必须正好是 '{expected}'。",
"validation.exactlylength": "{field|upper} 必须正好有 {expected} 项。",
"validation.exactlylengthstring": "{field|upper} 必须正好有 {expected} 个字符。",
"validation.match": "{message}",
"validation.max": "{field|upper} 必须小于或等于 '{max}'。",
"validation.maxlength": "{field|upper} 不能超过 {requiredlength} 个项目。",
"validation.maxlengthstring": "{field|upper} 不能超过 {requiredlength} 个字符。",
"validation.min": "{field|upper} 必须大于或等于 '{min}'。",
"validation.minlength": "{field|upper} 必须至少有 {requiredlength} 项。",
"validation.minlengthstring": "{field|upper} 必须至少有 {requiredlength} 个字符。",
"validation.pattern": "{field|upper} 与模式不匹配。",
"validation.patternmessage": "{message}",
"validation.required": "{field|upper} 是必需的。",
"validation.requiredTrue": "{field|upper} 是必需的。",
"validation.uniquestrings": "{field|upper} 不得包含重复值。",
"validation.validarrayvalues": "{field|upper} 包含无效值:{invalidvalue}。",
"validation.validdatetime": "{field|upper} 不是有效的日期时间。",
"validation.validvalues": "{field|upper} 不是一个有效值。",
"workflows.add": "添加工作流",
"workflows.addStep": "添加步骤",
"workflows.createFailed": "创建工作流失败。请重新加载。",
"workflows.deleteConfirmText": "你真的要删除工作流吗?",
"workflows.deleteConfirmTitle": "删除工作流",
"workflows.deleteFailed": "删除工作流失败。请重新加载。",
"workflows.empty": "尚未创建工作流。",
"workflows.loadFailed": "加载工作流失败。请重新加载。",
"workflows.notNamed": "未命名的工作流程",
"workflows.preventUpdates": "防止更新",
"workflows.publishedNotRemovable": "无法删除",
"workflows.refreshTooltip": "刷新工作流 (CTRL + SHIFT + R)",
"workflows.reloaded": "工作流已重新加载。",
"workflows.saved": "工作流已保存。",
"workflows.schemasHint": "将此工作流限制为特定Schemas或将其保留为所有Schemas的空。",
"workflows.syntax.expression": "表达式",
"workflows.syntax.for": "for",
"workflows.syntax.when": "when",
"workflows.tabEdit": "编辑",
"workflows.tabVisualize": "可视化",
"workflows.updateFailed": "无法更新工作流。请重新加载。",
"workflows.workflowNameHint": "工作流的可选名称。",
"workflows.workflowNamePlaceholder": "输入工作流名称"
}

28
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"));

35
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<string> translations)
{
var notUsed = new SortedSet<string>();

6
backend/src/Migrations/RebuildRunner.cs

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Migrations.Migrations;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
namespace Migrations
@ -28,11 +27,6 @@ namespace Migrations
RebuildFiles rebuildFiles,
PopulateGrainIndexes populateGrainIndexes)
{
Guard.NotNull(rebuildFiles, nameof(rebuildFiles));
Guard.NotNull(rebuilder, nameof(rebuilder));
Guard.NotNull(rebuildOptions, nameof(rebuildOptions));
Guard.NotNull(populateGrainIndexes, nameof(populateGrainIndexes));
this.rebuildFiles = rebuildFiles;
this.rebuilder = rebuilder;
this.rebuildOptions = rebuildOptions.Value;

8
backend/src/Squidex.Domain.Apps.Core.Model/Contents/GeoJsonValue.cs

@ -28,9 +28,7 @@ namespace Squidex.Domain.Apps.Core.Contents
{
try
{
var stream = DefaultPools.MemoryStream.Get();
try
using (var stream = DefaultPools.MemoryStream.GetStream())
{
serializer.Serialize(value, stream, true);
@ -40,10 +38,6 @@ namespace Squidex.Domain.Apps.Core.Contents
return GeoJsonParseResult.Success;
}
finally
{
DefaultPools.MemoryStream.Return(stream);
}
}
catch
{

2
backend/src/Squidex.Domain.Apps.Core.Model/Schemas/DateTimeFieldProperties.cs

@ -19,6 +19,8 @@ namespace Squidex.Domain.Apps.Core.Schemas
public Instant? MinValue { get; init; }
public string? Format { get; set; }
public DateTimeCalculatedDefaultValue? CalculatedDefaultValue { get; init; }
public DateTimeFieldEditor Editor { get; init; }

3
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventEnricher.cs

@ -24,9 +24,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public EventEnricher(IMemoryCache userCache, IUserResolver userResolver)
{
Guard.NotNull(userCache, nameof(userCache));
Guard.NotNull(userResolver, nameof(userResolver));
this.userCache = userCache;
this.userResolver = userResolver;
}

2
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/EventJsonSchemaGenerator.cs

@ -26,8 +26,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public EventJsonSchemaGenerator(JsonSchemaGenerator schemaGenerator)
{
Guard.NotNull(schemaGenerator, nameof(schemaGenerator));
this.schemaGenerator = schemaGenerator;
schemas = new Lazy<Dictionary<string, JsonSchema>>(GenerateSchemas);

2
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventFluidExtensions.cs

@ -20,8 +20,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions
public EventFluidExtensions(IUrlGenerator urlGenerator)
{
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.urlGenerator = urlGenerator;
}

3
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/Extensions/EventJintExtension.cs

@ -8,7 +8,6 @@
using Jint.Native;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Infrastructure;
using Squidex.Text;
namespace Squidex.Domain.Apps.Core.HandleRules.Extensions
@ -20,8 +19,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules.Extensions
public EventJintExtension(IUrlGenerator urlGenerator)
{
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.urlGenerator = urlGenerator;
}

3
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/PredefinedPatternsFormatter.cs

@ -9,7 +9,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure;
using Squidex.Shared.Identity;
using Squidex.Text;
@ -22,8 +21,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public PredefinedPatternsFormatter(IUrlGenerator urlGenerator)
{
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.urlGenerator = urlGenerator;
AddPattern("APP_ID", AppId);

3
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleActionHandler.cs

@ -10,7 +10,6 @@ using System.Threading;
using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Rules;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Infrastructure;
#pragma warning disable RECS0083 // Shows NotImplementedException throws in the quick task bar
@ -32,8 +31,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
protected RuleActionHandler(RuleEventFormatter formatter)
{
Guard.NotNull(formatter, nameof(formatter));
this.formatter = formatter;
}

6
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleEventFormatter.cs

@ -16,7 +16,6 @@ using NodaTime.Text;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Core.Templates;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Json;
using Squidex.Text;
using ValueTaskSupplement;
@ -72,11 +71,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
public RuleEventFormatter(IJsonSerializer jsonSerializer, IEnumerable<IRuleEventFormatter> formatters, ITemplateEngine templateEngine, IScriptEngine scriptEngine)
{
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(templateEngine, nameof(templateEngine));
Guard.NotNull(formatters, nameof(formatters));
this.jsonSerializer = jsonSerializer;
this.formatters = formatters;
this.templateEngine = templateEngine;

9
backend/src/Squidex.Domain.Apps.Core.Operations/HandleRules/RuleService.cs

@ -46,15 +46,6 @@ namespace Squidex.Domain.Apps.Core.HandleRules
ISemanticLog log,
TypeNameRegistry typeNameRegistry)
{
Guard.NotNull(jsonSerializer, nameof(jsonSerializer));
Guard.NotNull(ruleOptions, nameof(ruleOptions));
Guard.NotNull(ruleTriggerHandlers, nameof(ruleTriggerHandlers));
Guard.NotNull(ruleActionHandlers, nameof(ruleActionHandlers));
Guard.NotNull(typeNameRegistry, nameof(typeNameRegistry));
Guard.NotNull(eventEnricher, nameof(eventEnricher));
Guard.NotNull(clock, nameof(clock));
Guard.NotNull(log, nameof(log));
this.typeNameRegistry = typeNameRegistry;
this.ruleOptions = ruleOptions.Value;

3
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/HttpJintExtension.cs

@ -12,7 +12,6 @@ using Jint;
using Jint.Native;
using Jint.Native.Json;
using Jint.Runtime;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Tasks;
namespace Squidex.Domain.Apps.Core.Scripting.Extensions
@ -24,8 +23,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
public HttpJintExtension(IHttpClientFactory httpClientFactory)
{
Guard.NotNull(httpClientFactory, nameof(httpClientFactory));
this.httpClientFactory = httpClientFactory;
}

4
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Extensions/StringJintExtension.cs

@ -81,7 +81,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{
try
{
return TextHelpers.Html2Text(text);
return text.Html2Text();
}
catch
{
@ -93,7 +93,7 @@ namespace Squidex.Domain.Apps.Core.Scripting.Extensions
{
try
{
return TextHelpers.Markdown2Text(text);
return text.Markdown2Text();
}
catch
{

2
backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/Internal/Parser.cs

@ -25,8 +25,6 @@ namespace Squidex.Domain.Apps.Core.Scripting.Internal
public Parser(IMemoryCache cache)
{
Guard.NotNull(cache, nameof(cache));
this.cache = cache;
}

8
backend/src/Squidex.Domain.Apps.Core.Operations/Squidex.Domain.Apps.Core.Operations.csproj

@ -17,12 +17,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Fluid.Core.Squidex" Version="1.0.0-beta" />
<PackageReference Include="GeoJSON.Net" Version="1.2.19" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
<PackageReference Include="Markdig" Version="0.24.0" />
<PackageReference Include="GeoJSON.Net" Version="1.2.19" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Microsoft.OData.Core" Version="7.8.3" />
<PackageReference Include="NJsonSchema" Version="10.4.1" />
<PackageReference Include="Microsoft.OData.Core" Version="7.9.0" />
<PackageReference Include="NJsonSchema" Version="10.4.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="Squidex.Jint" Version="3.0.0-beta-0" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />

4
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/Extensions/StringFluidExtension.cs

@ -39,12 +39,12 @@ namespace Squidex.Domain.Apps.Core.Templates.Extensions
private static readonly FilterDelegate Markdown2Text = (input, arguments, context) =>
{
return FluidValue.Create(TextHelpers.Markdown2Text(input.ToStringValue()));
return FluidValue.Create(input.ToStringValue().Markdown2Text());
};
private static readonly FilterDelegate Html2Text = (input, arguments, context) =>
{
return FluidValue.Create(TextHelpers.Html2Text(input.ToStringValue()));
return FluidValue.Create(input.ToStringValue().Html2Text());
};
private static readonly FilterDelegate Trim = (input, arguments, context) =>

2
backend/src/Squidex.Domain.Apps.Core.Operations/Templates/FluidTemplateEngine.cs

@ -57,8 +57,6 @@ namespace Squidex.Domain.Apps.Core.Templates
public FluidTemplateEngine(IEnumerable<IFluidExtension> extensions)
{
Guard.NotNull(extensions, nameof(extensions));
this.extensions = extensions;
SquidexTemplate.Setup(extensions);

97
backend/src/Squidex.Domain.Apps.Core.Operations/TextHelpers.cs

@ -1,97 +0,0 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Text;
using HtmlAgilityPack;
using Markdig;
namespace Squidex.Domain.Apps.Core
{
public static class TextHelpers
{
public static string Markdown2Text(string markdown)
{
return Markdown.ToPlainText(markdown).Trim(' ', '\n', '\r');
}
public static string Html2Text(string html)
{
var document = LoadHtml(html);
var sb = new StringBuilder();
WriteTextTo(document.DocumentNode, sb);
return sb.ToString().Trim(' ', '\n', '\r');
}
private static HtmlDocument LoadHtml(string text)
{
var document = new HtmlDocument();
document.LoadHtml(text);
return document;
}
private static void WriteTextTo(HtmlNode node, StringBuilder sb)
{
switch (node.NodeType)
{
case HtmlNodeType.Comment:
break;
case HtmlNodeType.Document:
WriteChildrenTextTo(node, sb);
break;
case HtmlNodeType.Text:
var html = ((HtmlTextNode)node).Text;
if (HtmlNode.IsOverlappedClosingElement(html))
{
break;
}
if (!string.IsNullOrWhiteSpace(html))
{
sb.Append(HtmlEntity.DeEntitize(html));
}
break;
case HtmlNodeType.Element:
switch (node.Name)
{
case "p":
sb.AppendLine();
break;
case "br":
sb.AppendLine();
break;
case "style":
return;
case "script":
return;
}
if (node.HasChildNodes)
{
WriteChildrenTextTo(node, sb);
}
break;
}
}
private static void WriteChildrenTextTo(HtmlNode node, StringBuilder sb)
{
foreach (var child in node.ChildNodes)
{
WriteTextTo(child, sb);
}
}
}
}

5
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/DefaultFieldValueValidatorsFactory.cs

@ -11,6 +11,7 @@ using NodaTime;
using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Domain.Apps.Core.ValidateContent.Validators;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Text;
namespace Squidex.Domain.Apps.Core.ValidateContent
{
@ -203,10 +204,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
switch (properties.ContentType)
{
case StringContentType.Markdown:
transform = TextHelpers.Markdown2Text;
transform = MarkdownExtensions.Markdown2Text;
break;
case StringContentType.Html:
transform = TextHelpers.Html2Text;
transform = HtmlExtensions.Html2Text;
break;
}

2
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IAssetInfo.cs

@ -20,6 +20,8 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
string FileHash { get; }
string MimeType { get; }
string Slug { get; }
AssetMetadata Metadata { get; }

13
backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs

@ -1,4 +1,4 @@
// ==========================================================================
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
@ -76,12 +76,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
foundIds.Add(asset.AssetId);
ValidateCommon(asset, path, addError);
ValidateIsImage(asset, path, addError);
if (asset.Type != AssetType.Image)
{
ValidateNonImage(path, addError);
}
else
if (asset.Type == AssetType.Image)
{
ValidateImage(asset, path, addError);
}
@ -123,9 +120,9 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators
}
}
private void ValidateNonImage(ImmutableQueue<string> path, AddError addError)
private void ValidateIsImage(IAssetInfo asset, ImmutableQueue<string> path, AddError addError)
{
if (properties.MustBeImage)
if (properties.MustBeImage && asset.Type != AssetType.Image && asset.MimeType != "image/svg+xml")
{
addError(path, T.Get("contents.validation.image"));
}

6
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetFolderRepository.cs

@ -52,7 +52,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var assetFolderEntities =
await Collection.Find(filter).SortBy(x => x.FolderName)
.ToListAsync(ct = default);
.ToListAsync(ct);
return ResultList.Create<IAssetFolderEntity>(assetFolderEntities.Count, assetFolderEntities);
}
@ -67,7 +67,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var assetFolderEntities =
await Collection.Find(filter).Only(x => x.Id)
.ToListAsync(ct = default);
.ToListAsync(ct);
var field = Field.Of<MongoAssetFolderEntity>(x => nameof(x.Id));
@ -84,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var assetFolderEntity =
await Collection.Find(x => x.DocumentId == documentId && !x.IsDeleted)
.FirstOrDefaultAsync(ct = default);
.FirstOrDefaultAsync(ct);
return assetFolderEntity;
}

31
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/MongoAssetRepository.cs

@ -46,11 +46,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
new CreateIndexModel<MongoAssetEntity>(
Index
.Descending(x => x.LastModified)
.Ascending(x => x.Id)
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.ParentId)
.Ascending(x => x.Tags)
.Descending(x => x.LastModified)),
.Ascending(x => x.Tags)),
new CreateIndexModel<MongoAssetEntity>(
Index
.Ascending(x => x.IndexedAppId)
@ -75,9 +76,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
var find = Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted);
using (var cursor = await find.ToCursorAsync(ct = default))
using (var cursor = await find.ToCursorAsync(ct))
{
while (await cursor.MoveNextAsync(ct = default))
while (await cursor.MoveNextAsync(ct))
{
foreach (var entity in cursor.Current)
{
@ -99,10 +100,10 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var filter = BuildFilter(appId, q.Ids.ToHashSet());
var assetEntities =
await Collection.Find(filter).SortByDescending(x => x.LastModified)
await Collection.Find(filter).SortByDescending(x => x.LastModified).ThenBy(x => x.Id)
.QueryLimit(q.Query)
.QuerySkip(q.Query)
.ToListAsync(ct = default);
.ToListAsync(ct);
long assetTotal = assetEntities.Count;
if (q.NoTotal)
@ -111,7 +112,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}
else if (assetEntities.Count >= q.Query.Take || q.Query.Skip > 0)
{
assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct = default);
assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct);
}
return ResultList.Create(assetTotal, assetEntities.OfType<IAssetEntity>());
@ -127,7 +128,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
.QueryLimit(query)
.QuerySkip(query)
.QuerySort(query)
.ToListAsync(ct = default);
.ToListAsync(ct);
long assetTotal = assetEntities.Count;
if (q.NoTotal)
@ -136,7 +137,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
}
else if (assetEntities.Count >= q.Query.Take || q.Query.Skip > 0)
{
assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct = default);
assetTotal = await Collection.Find(filter).CountDocumentsAsync(ct);
}
return ResultList.Create<IAssetEntity>(assetTotal, assetEntities);
@ -156,7 +157,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
var assetEntities =
await Collection.Find(BuildFilter(appId, ids)).Only(x => x.Id)
.ToListAsync(ct = default);
.ToListAsync(ct);
var field = Field.Of<MongoAssetFolderEntity>(x => nameof(x.Id));
@ -171,7 +172,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
var assetEntities =
await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.ParentId == parentId).Only(x => x.Id)
.ToListAsync(ct = default);
.ToListAsync(ct);
var field = Field.Of<MongoAssetFolderEntity>(x => nameof(x.Id));
@ -186,7 +187,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
var assetEntity =
await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.FileHash == hash && x.FileName == fileName && x.FileSize == fileSize)
.FirstOrDefaultAsync(ct = default);
.FirstOrDefaultAsync(ct);
return assetEntity;
}
@ -199,7 +200,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
var assetEntity =
await Collection.Find(x => x.IndexedAppId == appId && !x.IsDeleted && x.Slug == slug)
.FirstOrDefaultAsync(ct = default);
.FirstOrDefaultAsync(ct);
return assetEntity;
}
@ -214,7 +215,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
var assetEntity =
await Collection.Find(x => x.DocumentId == documentId && !x.IsDeleted)
.FirstOrDefaultAsync(ct = default);
.FirstOrDefaultAsync(ct);
return assetEntity;
}
@ -227,7 +228,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets
{
var assetEntity =
await Collection.Find(x => x.Id == id && !x.IsDeleted)
.FirstOrDefaultAsync(ct = default);
.FirstOrDefaultAsync(ct);
return assetEntity;
}

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Assets/Visitors/FindExtensions.cs

@ -42,6 +42,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Assets.Visitors
{
var filters = new List<FilterDefinition<MongoAssetEntity>>
{
Filter.Exists(x => x.LastModified),
Filter.Exists(x => x.Id),
Filter.Eq(x => x.IndexedAppId, appId)
};

45
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentCollection.cs

@ -7,6 +7,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
@ -33,9 +34,9 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
private readonly QueryReferrers queryReferrers;
private readonly QueryScheduled queryScheduled;
private readonly string name;
private readonly bool useWildcardIndex;
private readonly ReadPreference readPreference;
public MongoContentCollection(string name, IMongoDatabase database, IAppProvider appProvider, bool useWildcardIndex)
public MongoContentCollection(string name, IMongoDatabase database, IAppProvider appProvider, ReadPreference readPreference)
: base(database)
{
this.name = name;
@ -48,7 +49,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
queryReferrers = new QueryReferrers();
queryScheduled = new QueryScheduled();
this.useWildcardIndex = useWildcardIndex;
this.readPreference = readPreference;
}
public IMongoCollection<MongoContentEntity> GetInternalCollection()
@ -61,26 +62,34 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
return name;
}
protected override async Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection,
protected override MongoCollectionSettings CollectionSettings()
{
return new MongoCollectionSettings
{
ReadPreference = readPreference
};
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoContentEntity> collection,
CancellationToken ct)
{
if (useWildcardIndex)
var operations = new OperationBase[]
{
queryAsStream,
queryBdId,
queryByIds,
queryByQuery,
queryReferences,
queryReferrers,
queryScheduled
};
foreach (var operation in operations)
{
await collection.Indexes.CreateOneAsync(
new CreateIndexModel<MongoContentEntity>(
Index.Wildcard()
), null, ct);
operation.Setup(collection);
}
var skipIndex = useWildcardIndex;
await queryAsStream.PrepareAsync(collection, skipIndex, ct);
await queryBdId.PrepareAsync(collection, skipIndex, ct);
await queryByIds.PrepareAsync(collection, skipIndex, ct);
await queryByQuery.PrepareAsync(collection, skipIndex, ct);
await queryReferences.PrepareAsync(collection, skipIndex, ct);
await queryReferrers.PrepareAsync(collection, skipIndex, ct);
await queryScheduled.PrepareAsync(collection, skipIndex, ct);
return collection.Indexes.CreateManyAsync(operations.SelectMany(x => x.CreateIndexes()), ct);
}
public Task ResetScheduledAsync(DomainId documentId,

12
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/MongoContentRepository.cs

@ -34,17 +34,15 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents
TypeConverterStringSerializer<Status>.Register();
}
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider, bool useWildcardIndex)
public MongoContentRepository(IMongoDatabase database, IAppProvider appProvider)
{
Guard.NotNull(appProvider, nameof(appProvider));
collectionAll =
new MongoContentCollection(
"States_Contents_All3", database, appProvider, useWildcardIndex);
new MongoContentCollection("States_Contents_All3", database, appProvider,
ReadPreference.Primary);
collectionPublished =
new MongoContentCollection(
"States_Contents_Published3", database, appProvider, useWildcardIndex);
new MongoContentCollection("States_Contents_Published3", database, appProvider,
ReadPreference.Secondary);
this.appProvider = appProvider;
}

14
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/OperationBase.cs

@ -5,8 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using MongoDB.Driver;
namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
@ -21,19 +20,14 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
public IMongoCollection<MongoContentEntity> Collection { get; private set; }
public async Task PrepareAsync(IMongoCollection<MongoContentEntity> collection, bool skipIndex, CancellationToken ct = default)
public void Setup(IMongoCollection<MongoContentEntity> collection)
{
Collection = collection;
if (!skipIndex)
{
await PrepareAsync(ct);
}
}
protected virtual Task PrepareAsync(CancellationToken ct)
public virtual IEnumerable<CreateIndexModel<MongoContentEntity>> CreateIndexes()
{
return Task.CompletedTask;
yield break;
}
}
}

14
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryAsStream.cs

@ -8,7 +8,6 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Infrastructure;
@ -17,15 +16,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
public sealed class QueryAsStream : OperationBase
{
protected override async Task PrepareAsync(CancellationToken ct)
public override IEnumerable<CreateIndexModel<MongoContentEntity>> CreateIndexes()
{
var indexBySchema =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.IndexedSchemaId));
await Collection.Indexes.CreateOneAsync(indexBySchema, cancellationToken: ct);
yield return new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.IndexedSchemaId));
}
public async IAsyncEnumerable<IContentEntity> StreamAll(DomainId appId, HashSet<DomainId>? schemaIds,

42
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryByQuery.cs

@ -41,25 +41,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
this.appProvider = appProvider;
}
protected override async Task PrepareAsync(CancellationToken ct)
public override IEnumerable<CreateIndexModel<MongoContentEntity>> CreateIndexes()
{
var indexBySchemaWithRefs =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.ReferencedIds)
.Descending(x => x.LastModified));
await Collection.Indexes.CreateOneAsync(indexBySchemaWithRefs, cancellationToken: ct);
var indexBySchema =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Descending(x => x.LastModified));
await Collection.Indexes.CreateOneAsync(indexBySchema, cancellationToken: ct);
yield return new CreateIndexModel<MongoContentEntity>(Index
.Descending(x => x.LastModified)
.Ascending(x => x.Id)
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.ReferencedIds));
yield return new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Descending(x => x.LastModified));
}
public async Task<IReadOnlyList<(DomainId SchemaId, DomainId Id, Status Status)>> QueryIdsAsync(DomainId appId, DomainId schemaId, FilterNode<ClrValue> filterNode,
@ -202,13 +197,20 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
private static bool IsSatisfiedByIndex(ClrQuery query)
{
return query.Sort?.All(x => x.Path.ToString() == "mt" && x.Order == SortOrder.Descending) == true;
return query.Sort != null &&
query.Sort.Count == 2 &&
query.Sort[0].Path.ToString() == "mt" &&
query.Sort[0].Order == SortOrder.Descending &&
query.Sort[1].Path.ToString() == "id" &&
query.Sort[1].Order == SortOrder.Ascending;
}
private static FilterDefinition<MongoContentEntity> BuildFilter(DomainId appId, DomainId schemaId, FilterNode<ClrValue>? filter)
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Exists(x => x.LastModified),
Filter.Exists(x => x.Id),
Filter.Eq(x => x.IndexedAppId, appId),
Filter.Eq(x => x.IndexedSchemaId, schemaId)
};
@ -231,6 +233,8 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
var filters = new List<FilterDefinition<MongoContentEntity>>
{
Filter.Exists(x => x.LastModified),
Filter.Exists(x => x.Id),
Filter.Eq(x => x.IndexedAppId, appId),
Filter.In(x => x.IndexedSchemaId, schemaIds),
};

14
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrers.cs

@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
@ -15,15 +16,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class QueryReferrers : OperationBase
{
protected override Task PrepareAsync(CancellationToken ct)
public override IEnumerable<CreateIndexModel<MongoContentEntity>> CreateIndexes()
{
var index =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ReferencedIds)
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
yield return new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ReferencedIds)
.Ascending(x => x.IndexedAppId)
.Ascending(x => x.IsDeleted));
}
public async Task<bool> CheckExistsAsync(DomainId appId, DomainId contentId,

12
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryScheduled.cs

@ -6,6 +6,7 @@
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Driver;
@ -18,14 +19,11 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
{
internal sealed class QueryScheduled : OperationBase
{
protected override Task PrepareAsync(CancellationToken ct)
public override IEnumerable<CreateIndexModel<MongoContentEntity>> CreateIndexes()
{
var index =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ScheduledAt)
.Ascending(x => x.IsDeleted));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
yield return new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.ScheduledAt)
.Ascending(x => x.IsDeleted));
}
public Task QueryAsync(Instant now, Func<IContentEntity, Task> callback,

18
backend/src/Squidex.Domain.Apps.Entities.MongoDb/FullText/MongoTextIndex.cs

@ -82,7 +82,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
public async Task<List<DomainId>?> SearchAsync(IAppEntity app, GeoQuery query, SearchScope scope)
{
var byGeo =
await Collection.Find(
await GetCollection(scope).Find(
Filter.And(
Filter.Eq(x => x.AppId, app.Id),
Filter.Eq(x => x.SchemaId, query.SchemaId),
@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
private async Task<List<DomainId>> SearchBySchemaAsync(string queryText, IAppEntity app, TextFilter filter, SearchScope scope, int limit)
{
var bySchema =
await Collection.Find(
await GetCollection(scope).Find(
Filter.And(
Filter.Eq(x => x.AppId, app.Id),
Filter.In(x => x.SchemaId, filter.SchemaIds),
@ -143,7 +143,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
private async Task<List<DomainId>> SearchByAppAsync(string queryText, IAppEntity app, SearchScope scope, int limit)
{
var bySchema =
await Collection.Find(
await GetCollection(scope).Find(
Filter.And(
Filter.Eq(x => x.AppId, app.Id),
Filter.Exists(x => x.SchemaId),
@ -168,5 +168,17 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.FullText
return Filter.Eq(x => x.ServePublished, true);
}
}
private IMongoCollection<MongoTextIndexEntity> GetCollection(SearchScope scope)
{
if (scope == SearchScope.All)
{
return Collection;
}
else
{
return Collection.WithReadPreference(ReadPreference.Secondary);
}
}
}
}

8
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Rules/MongoRuleEventRepository.cs

@ -39,7 +39,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
protected override async Task SetupCollectionAsync(IMongoCollection<MongoRuleEventEntity> collection,
CancellationToken ct = default)
{
await statisticsCollection.InitializeAsync(ct = default);
await statisticsCollection.InitializeAsync(ct);
await collection.Indexes.CreateManyAsync(new[]
{
@ -73,12 +73,12 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
filter = Filter.And(filter, Filter.Eq(x => x.RuleId, ruleId.Value));
}
var ruleEventEntities = await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.Created).ToListAsync(ct = default);
var ruleEventEntities = await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.Created).ToListAsync(ct);
var ruleEventTotal = (long)ruleEventEntities.Count;
if (ruleEventTotal >= take || skip > 0)
{
ruleEventTotal = await Collection.Find(filter).CountDocumentsAsync(ct = default);
ruleEventTotal = await Collection.Find(filter).CountDocumentsAsync(ct);
}
return ResultList.Create(ruleEventTotal, ruleEventEntities);
@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Rules
{
var ruleEvent =
await Collection.Find(x => x.DocumentId == id)
.FirstOrDefaultAsync(ct = default);
.FirstOrDefaultAsync(ct);
return ruleEvent;
}

2
backend/src/Squidex.Domain.Apps.Entities.MongoDb/Squidex.Domain.Apps.Entities.MongoDb.csproj

@ -17,7 +17,7 @@
<ProjectReference Include="..\Squidex.Domain.Apps.Entities\Squidex.Domain.Apps.Entities.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MongoDB.Driver" Version="2.12.2" />
<PackageReference Include="MongoDB.Driver" Version="2.12.4" />
<PackageReference Include="RefactoringEssentials" Version="5.6.0" PrivateAssets="all" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="all" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />

5
backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs

@ -31,11 +31,6 @@ namespace Squidex.Domain.Apps.Entities
public AppProvider(ILocalCache localCache, IAppsIndex indexForApps, IRulesIndex indexRules, ISchemasIndex indexSchemas)
{
Guard.NotNull(indexForApps, nameof(indexForApps));
Guard.NotNull(indexRules, nameof(indexRules));
Guard.NotNull(indexSchemas, nameof(indexSchemas));
Guard.NotNull(localCache, nameof(localCache));
this.localCache = localCache;
this.indexForApps = indexForApps;
this.indexRules = indexRules;

23
backend/src/Squidex.Domain.Apps.Entities/AppTag.cs

@ -0,0 +1,23 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using Fluid.Tags;
using Microsoft.Extensions.DependencyInjection;
namespace Squidex.Domain.Apps.Entities
{
internal abstract class AppTag : ArgumentsTag
{
protected IAppProvider AppProvider { get; }
protected AppTag(IServiceProvider serviceProvider)
{
AppProvider = serviceProvider.GetRequiredService<IAppProvider>();
}
}
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/AppSettingsSearchSource.cs

@ -22,8 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public AppSettingsSearchSource(IUrlGenerator urlGenerator)
{
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.urlGenerator = urlGenerator;
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettings.cs

@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public AppUISettings(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
this.grainFactory = grainFactory;
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/AppUISettingsGrain.cs

@ -27,8 +27,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public AppUISettingsGrain(IGrainState<State> state)
{
Guard.NotNull(state, nameof(state));
this.state = state;
}

4
backend/src/Squidex.Domain.Apps.Entities/Apps/BackupApps.cs

@ -32,10 +32,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public BackupApps(IAppImageStore appImageStore, IAppsIndex appsIndex, IAppUISettings appUISettings)
{
Guard.NotNull(appImageStore, nameof(appImageStore));
Guard.NotNull(appsIndex, nameof(appsIndex));
Guard.NotNull(appUISettings, nameof(appUISettings));
this.appsIndex = appsIndex;
this.appImageStore = appImageStore;
this.appUISettings = appUISettings;

2
backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppImageStore.cs

@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public DefaultAppImageStore(IAssetStore assetStore)
{
Guard.NotNull(assetStore, nameof(assetStore));
this.assetStore = assetStore;
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs

@ -41,8 +41,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public DefaultAppLogStore(IRequestLogStore requestLogStore)
{
Guard.NotNull(requestLogStore, nameof(requestLogStore));
this.requestLogStore = requestLogStore;
}

3
backend/src/Squidex.Domain.Apps.Entities/Apps/Diagnostics/OrleansAppsHealthCheck.cs

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Orleans;
using Squidex.Domain.Apps.Entities.Apps.Indexes;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Orleans;
namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
@ -21,8 +20,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Diagnostics
public OrleansAppsHealthCheck(IGrainFactory grainFactory)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
this.grainFactory = grainFactory;
}

5
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppCommandMiddleware.cs

@ -9,7 +9,6 @@ using System.Threading.Tasks;
using Orleans;
using Squidex.Assets;
using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Translations;
using Squidex.Infrastructure.Validation;
@ -29,10 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
IContextProvider contextProvider)
: base(grainFactory)
{
Guard.NotNull(contextProvider, nameof(contextProvider));
Guard.NotNull(appImageStore, nameof(appImageStore));
Guard.NotNull(assetThumbnailGenerator, nameof(assetThumbnailGenerator));
this.appImageStore = appImageStore;
this.assetThumbnailGenerator = assetThumbnailGenerator;
this.contextProvider = contextProvider;

5
backend/src/Squidex.Domain.Apps.Entities/Apps/DomainObject/AppDomainObject.cs

@ -38,11 +38,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.DomainObject
IUserResolver userResolver)
: base(persistence, log)
{
Guard.NotNull(initialPatterns, nameof(initialPatterns));
Guard.NotNull(userResolver, nameof(userResolver));
Guard.NotNull(appPlansProvider, nameof(appPlansProvider));
Guard.NotNull(appPlansBillingManager, nameof(appPlansBillingManager));
this.userResolver = userResolver;
this.appPlansProvider = appPlansProvider;
this.appPlansBillingManager = appPlansBillingManager;

3
backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs

@ -33,9 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes
public AppsIndex(IGrainFactory grainFactory, IReplicatedCache grainCache)
{
Guard.NotNull(grainFactory, nameof(grainFactory));
Guard.NotNull(grainCache, nameof(grainCache));
this.grainFactory = grainFactory;
this.grainCache = grainCache;
}

5
backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InvitationEventConsumer.cs

@ -9,7 +9,6 @@ using System.Threading.Tasks;
using NodaTime;
using Squidex.Domain.Apps.Entities.Notifications;
using Squidex.Domain.Apps.Events.Apps;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Log;
using Squidex.Shared.Users;
@ -35,10 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
public InvitationEventConsumer(INotificationSender emailSender, IUserResolver userResolver, ISemanticLog log)
{
Guard.NotNull(emailSender, nameof(emailSender));
Guard.NotNull(userResolver, nameof(userResolver));
Guard.NotNull(log, nameof(log));
this.emailSender = emailSender;
this.userResolver = userResolver;

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Invitation/InviteUserCommandMiddleware.cs

@ -19,8 +19,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Invitation
public InviteUserCommandMiddleware(IUserResolver userResolver)
{
Guard.NotNull(userResolver, nameof(userResolver));
this.userResolver = userResolver;
}

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/ConfigAppPlansProvider.cs

@ -30,8 +30,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
public ConfigAppPlansProvider(IEnumerable<ConfigAppLimitsPlan> config)
{
Guard.NotNull(config, nameof(config));
foreach (var plan in config.OrderBy(x => x.MaxApiCalls).Select(x => x.Clone()))
{
plansList.Add(plan);

4
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageGate.cs

@ -28,10 +28,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
public UsageGate(IAppPlansProvider appPlansProvider, IApiUsageTracker apiUsageTracker, IGrainFactory grainFactory)
{
Guard.NotNull(apiUsageTracker, nameof(apiUsageTracker));
Guard.NotNull(appPlansProvider, nameof(appPlansProvider));
Guard.NotNull(grainFactory, nameof(grainFactory));
this.appPlansProvider = appPlansProvider;
this.apiUsageTracker = apiUsageTracker;
this.grainFactory = grainFactory;

5
backend/src/Squidex.Domain.Apps.Entities/Apps/Plans/UsageNotifierGrain.cs

@ -34,11 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Plans
public UsageNotifierGrain(IGrainState<State> state, INotificationSender notificationSender, IUserResolver userResolver, IClock clock)
{
Guard.NotNull(state, nameof(state));
Guard.NotNull(notificationSender, nameof(notificationSender));
Guard.NotNull(userResolver, nameof(userResolver));
Guard.NotNull(clock, nameof(clock));
this.state = state;
this.notificationSender = notificationSender;
this.userResolver = userResolver;

3
backend/src/Squidex.Domain.Apps.Entities/Apps/RolePermissionsProvider.cs

@ -10,7 +10,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Security;
using Squidex.Shared;
@ -28,8 +27,6 @@ namespace Squidex.Domain.Apps.Entities.Apps
public RolePermissionsProvider(IAppProvider appProvider)
{
Guard.NotNull(appProvider, nameof(appProvider));
this.appProvider = appProvider;
foreach (var field in typeof(Permissions).GetFields(BindingFlags.Public | BindingFlags.Static))

2
backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs

@ -23,8 +23,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
public TemplateCommandMiddleware(IEnumerable<ITemplate> templates)
{
Guard.NotNull(templates, nameof(templates));
this.templates = templates.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
}

5
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetChangedTriggerHandler.cs

@ -16,7 +16,6 @@ using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Assets.Repositories;
using Squidex.Domain.Apps.Events;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
@ -37,10 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
IAssetLoader assetLoader,
IAssetRepository assetRepository)
{
Guard.NotNull(scriptEngine, nameof(scriptEngine));
Guard.NotNull(assetLoader, nameof(assetLoader));
Guard.NotNull(assetRepository, nameof(assetRepository));
this.scriptEngine = scriptEngine;
this.assetLoader = assetLoader;
this.assetRepository = assetRepository;

30
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetExtensions.cs

@ -5,6 +5,12 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using System.Text;
using System.Threading.Tasks;
using Squidex.Infrastructure;
using Squidex.Infrastructure.ObjectPool;
namespace Squidex.Domain.Apps.Entities.Assets
{
public static class AssetExtensions
@ -20,5 +26,29 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
return builder.WithBoolean(HeaderNoEnrichment, value);
}
public static async Task<string> GetTextAsync(this IAssetFileStore assetFileStore, DomainId appId, DomainId id, long fileVersion, string? encoding)
{
using (var stream = DefaultPools.MemoryStream.GetStream())
{
await assetFileStore.DownloadAsync(appId, id, fileVersion, null, stream);
stream.Position = 0;
var bytes = stream.ToArray();
switch (encoding?.ToLowerInvariant())
{
case "base64":
return Convert.ToBase64String(bytes);
case "ascii":
return Encoding.ASCII.GetString(bytes);
case "unicode":
return Encoding.Unicode.GetString(bytes);
default:
return Encoding.UTF8.GetString(bytes);
}
}
}
}
}

8
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetOptions.cs

@ -5,14 +5,22 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class AssetOptions
{
public bool FolderPerApp { get; set; } = false;
public int DefaultPageSize { get; set; } = 200;
public int MaxResults { get; set; } = 200;
public long MaxSize { get; set; } = 5 * 1024 * 1024;
public TimeSpan TimeoutFind { get; set; } = TimeSpan.FromSeconds(1);
public TimeSpan TimeoutQuery { get; set; } = TimeSpan.FromSeconds(5);
}
}

5
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetPermanentDeleter.cs

@ -8,7 +8,6 @@
using System.Threading.Tasks;
using Squidex.Assets;
using Squidex.Domain.Apps.Events.Assets;
using Squidex.Infrastructure;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Reflection;
@ -31,8 +30,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
public AssetPermanentDeleter(IAssetFileStore assetFileStore, TypeNameRegistry typeNameRegistry)
{
Guard.NotNull(assetFileStore, nameof(assetFileStore));
this.assetFileStore = assetFileStore;
deletedType = typeNameRegistry?.GetName<AssetDeleted>();
@ -56,7 +53,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
try
{
await assetFileStore.DeleteAsync(assetDeleted.AppId.Id, assetDeleted.AssetId, version);
await assetFileStore.DeleteAsync(assetDeleted.AppId.Id, assetDeleted.AssetId, version, null);
}
catch (AssetNotFoundException)
{

2
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetUsageTracker.cs

@ -24,8 +24,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
public AssetUsageTracker(IUsageTracker usageTracker)
{
Guard.NotNull(usageTracker, nameof(usageTracker));
this.usageTracker = usageTracker;
}

127
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsFluidExtension.cs

@ -11,74 +11,55 @@ using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Fluid;
using Fluid.Ast;
using Fluid.Tags;
using Fluid.Values;
using GraphQL.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Templates;
using Squidex.Domain.Apps.Core.ValidateContent;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Entities.Assets
{
public sealed class AssetsFluidExtension : IFluidExtension
{
private static readonly FluidValue ErrorNullAsset = FluidValue.Create(null);
private static readonly FluidValue ErrorNoAsset = new StringValue("NoAsset");
private static readonly FluidValue ErrorTooBig = new StringValue("ErrorTooBig");
private readonly IServiceProvider serviceProvider;
private sealed class AssetTag : ArgumentsTag
private sealed class AssetTag : AppTag
{
private readonly IServiceProvider serviceProvider;
private readonly IAssetQueryService assetQuery;
public AssetTag(IServiceProvider serviceProvider)
: base(serviceProvider)
{
this.serviceProvider = serviceProvider;
assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>();
}
public override async ValueTask<Completion> WriteToAsync(TextWriter writer, TextEncoder encoder, TemplateContext context, FilterArgument[] arguments)
{
if (arguments.Length == 2 && context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent)
{
var app = await GetAppAsync(enrichedEvent);
var id = await arguments[1].Expression.EvaluateAsync(context);
if (app == null)
{
return Completion.Normal;
}
var requestContext =
Context.Admin(app).Clone(b => b
.WithoutTotal());
var id = (await arguments[1].Expression.EvaluateAsync(context)).ToStringValue();
var content = await ResolveAssetAsync(AppProvider, assetQuery, enrichedEvent.AppId.Id, id);
var assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>();
var asset = await assetQuery.FindAsync(requestContext, DomainId.Create(id));
if (asset != null)
if (content != null)
{
var name = (await arguments[0].Expression.EvaluateAsync(context)).ToStringValue();
context.SetValue(name, asset);
context.SetValue(name, content);
}
}
return Completion.Normal;
}
private Task<IAppEntity?> GetAppAsync(EnrichedEvent enrichedEvent)
{
var appProvider = serviceProvider.GetRequiredService<IAppProvider>();
return appProvider.GetAppAsync(enrichedEvent.AppId.Id, false);
}
}
public AssetsFluidExtension(IServiceProvider serviceProvider)
{
Guard.NotNull(serviceProvider, nameof(serviceProvider));
this.serviceProvider = serviceProvider;
}
@ -91,11 +72,95 @@ namespace Squidex.Domain.Apps.Entities.Assets
memberAccessStrategy.Register<IEntityWithLastModifiedBy>();
memberAccessStrategy.Register<IEntityWithVersion>();
memberAccessStrategy.Register<IEnrichedAssetEntity>();
AddAssetFilter();
AddAssetTextFilter();
}
private void AddAssetFilter()
{
var appProvider = serviceProvider.GetRequiredService<IAppProvider>();
var assetQuery = serviceProvider.GetRequiredService<IAssetQueryService>();
TemplateContext.GlobalFilters.AddAsyncFilter("asset", async (input, arguments, context) =>
{
if (context.GetValue("event")?.ToObjectValue() is EnrichedEvent enrichedEvent)
{
var asset = await ResolveAssetAsync(appProvider, assetQuery, enrichedEvent.AppId.Id, input);
if (asset == null)
{
return ErrorNullAsset;
}
return FluidValue.Create(asset);
}
return ErrorNullAsset;
});
}
private void AddAssetTextFilter()
{
var assetFileStore = serviceProvider.GetRequiredService<IAssetFileStore>();
TemplateContext.GlobalFilters.AddAsyncFilter("assetText", async (input, arguments, context) =>
{
if (input is not ObjectValue objectValue)
{
return ErrorNoAsset;
}
async Task<FluidValue> ResolveAssetText(DomainId appId, DomainId id, long fileSize, long fileVersion)
{
if (fileSize > 256_000)
{
return ErrorTooBig;
}
var encoding = arguments.At(0).ToStringValue()?.ToUpperInvariant();
var encoded = await assetFileStore.GetTextAsync(appId, id, fileVersion, encoding);
return new StringValue(encoded);
}
switch (objectValue.ToObjectValue())
{
case IAssetEntity asset:
return await ResolveAssetText(asset.AppId.Id, asset.Id, asset.FileSize, asset.FileVersion);
case EnrichedAssetEvent @event:
return await ResolveAssetText(@event.AppId.Id, @event.Id, @event.FileSize, @event.FileVersion);
}
return ErrorNoAsset;
});
}
public void RegisterLanguageExtensions(FluidParserFactory factory)
{
factory.RegisterTag("asset", new AssetTag(serviceProvider));
}
private static async Task<IAssetEntity?> ResolveAssetAsync(IAppProvider appProvider, IAssetQueryService assetQuery, DomainId appId, FluidValue id)
{
var app = await appProvider.GetAppAsync(appId);
if (app == null)
{
return null;
}
var domainId = DomainId.Create(id.ToStringValue());
var requestContext =
Context.Admin(app).Clone(b => b
.WithoutTotal());
var asset = await assetQuery.FindAsync(requestContext, domainId);
return asset;
}
}
}

71
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsJintExtension.cs

@ -12,7 +12,9 @@ using System.Security.Claims;
using System.Threading.Tasks;
using Jint.Native;
using Jint.Runtime;
using Jint.Runtime.Interop;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Core.Rules.EnrichedEvents;
using Squidex.Domain.Apps.Core.Scripting;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Infrastructure;
@ -23,16 +25,21 @@ namespace Squidex.Domain.Apps.Entities.Assets
public sealed class AssetsJintExtension : IJintExtension
{
private delegate void GetAssetsDelegate(JsValue references, Action<JsValue> callback);
private delegate void GetAssetTextDelegate(JsValue references, Action<JsValue> callback, JsValue encoding);
private readonly IServiceProvider serviceProvider;
public AssetsJintExtension(IServiceProvider serviceProvider)
{
Guard.NotNull(serviceProvider, nameof(serviceProvider));
this.serviceProvider = serviceProvider;
}
public void ExtendAsync(ExecutionContext context)
{
AddAssetText(context);
AddAsset(context);
}
private void AddAsset(ExecutionContext context)
{
if (!context.TryGetValue<DomainId>(nameof(ScriptVars.AppId), out var appId))
{
@ -50,6 +57,66 @@ namespace Squidex.Domain.Apps.Entities.Assets
context.Engine.SetValue("getAssets", action);
}
private void AddAssetText(ExecutionContext context)
{
var action = new GetAssetTextDelegate((references, callback, encoding) => GetText(context, references, callback, encoding));
context.Engine.SetValue("getAssetText", action);
}
private void GetText(ExecutionContext context, JsValue input, Action<JsValue> callback, JsValue encoding)
{
GetTextAsync(context, input, callback, encoding).Forget();
}
private async Task GetTextAsync(ExecutionContext context, JsValue input, Action<JsValue> callback, JsValue encoding)
{
Guard.NotNull(callback, nameof(callback));
if (input is not ObjectWrapper objectWrapper)
{
callback(JsValue.FromObject(context.Engine, "ErrorNoAsset"));
return;
}
async Task ResolveAssetText(DomainId appId, DomainId id, long fileSize, long fileVersion)
{
if (fileSize > 256_000)
{
callback(JsValue.FromObject(context.Engine, "ErrorTooBig"));
return;
}
context.MarkAsync();
try
{
var assetFileStore = serviceProvider.GetRequiredService<IAssetFileStore>();
var encoded = await assetFileStore.GetTextAsync(appId, id, fileVersion, encoding?.ToString());
callback(JsValue.FromObject(context.Engine, encoded));
}
catch (Exception ex)
{
context.Fail(ex);
}
}
switch (objectWrapper.Target)
{
case IAssetEntity asset:
await ResolveAssetText(asset.AppId.Id, asset.Id, asset.FileSize, asset.FileVersion);
return;
case EnrichedAssetEvent @event:
await ResolveAssetText(@event.AppId.Id, @event.Id, @event.FileSize, @event.FileVersion);
return;
}
callback(JsValue.FromObject(context.Engine, "ErrorNoAsset"));
}
private void GetAssets(ExecutionContext context, DomainId appId, ClaimsPrincipal user, JsValue references, Action<JsValue> callback)
{
GetReferencesAsync(context, appId, user, references, callback).Forget();

4
backend/src/Squidex.Domain.Apps.Entities/Assets/AssetsSearchSource.cs

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Squidex.Domain.Apps.Core;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Search;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Queries;
using Squidex.Shared;
@ -23,9 +22,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
public AssetsSearchSource(IAssetQueryService assetQuery, IUrlGenerator urlGenerator)
{
Guard.NotNull(assetQuery, nameof(assetQuery));
Guard.NotNull(urlGenerator, nameof(urlGenerator));
this.assetQuery = assetQuery;
this.urlGenerator = urlGenerator;

8
backend/src/Squidex.Domain.Apps.Entities/Assets/BackupAssets.cs

@ -33,10 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Assets
public BackupAssets(Rebuilder rebuilder, IAssetFileStore assetFileStore, ITagService tagService)
{
Guard.NotNull(rebuilder, nameof(rebuilder));
Guard.NotNull(assetFileStore, nameof(assetFileStore));
Guard.NotNull(tagService, nameof(tagService));
this.rebuilder = rebuilder;
this.assetFileStore = assetFileStore;
this.tagService = tagService;
@ -131,7 +127,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
await writer.WriteBlobAsync(GetName(assetId, fileVersion), stream =>
{
return assetFileStore.DownloadAsync(appId, assetId, fileVersion, stream);
return assetFileStore.DownloadAsync(appId, assetId, fileVersion, null, stream);
});
}
catch (AssetNotFoundException)
@ -146,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Assets
{
await reader.ReadBlobAsync(GetName(assetId, fileVersion), stream =>
{
return assetFileStore.UploadAsync(appId, assetId, fileVersion, stream);
return assetFileStore.UploadAsync(appId, assetId, fileVersion, null, stream);
});
}
catch (FileNotFoundException)

92
backend/src/Squidex.Domain.Apps.Entities/Assets/DefaultAssetFileStore.cs

@ -8,6 +8,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Squidex.Assets;
using Squidex.Infrastructure;
@ -16,59 +17,63 @@ namespace Squidex.Domain.Apps.Entities.Assets
public sealed class DefaultAssetFileStore : IAssetFileStore
{
private readonly IAssetStore assetStore;
private readonly AssetOptions options;
public DefaultAssetFileStore(IAssetStore assetStore)
public DefaultAssetFileStore(IAssetStore assetStore,
IOptions<AssetOptions> options)
{
this.assetStore = assetStore;
this.options = options.Value;
}
public string? GeneratePublicUrl(DomainId appId, DomainId id, long fileVersion)
public string? GeneratePublicUrl(DomainId appId, DomainId id, long fileVersion, string? suffix)
{
var fileName = GetFileName(appId, id, fileVersion);
var fileName = GetFileName(appId, id, fileVersion, suffix);
return assetStore.GeneratePublicUrl(fileName);
}
public async Task<long> GetFileSizeAsync(DomainId appId, DomainId id, long fileVersion,
public async Task<long> GetFileSizeAsync(DomainId appId, DomainId id, long fileVersion, string? suffix,
CancellationToken ct = default)
{
try
{
var fileNameNew = GetFileName(appId, id, fileVersion);
var fileNameNew = GetFileName(appId, id, fileVersion, suffix);
return await assetStore.GetSizeAsync(fileNameNew, ct);
}
catch (AssetNotFoundException)
catch (AssetNotFoundException) when (!options.FolderPerApp)
{
var fileNameOld = GetFileName(id, fileVersion);
var fileNameOld = GetFileName(id, fileVersion, suffix);
return await assetStore.GetSizeAsync(fileNameOld, ct);
}
}
public async Task DownloadAsync(DomainId appId, DomainId id, long fileVersion, Stream stream, BytesRange range = default,
public async Task DownloadAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, Stream stream, BytesRange range = default,
CancellationToken ct = default)
{
try
{
var fileNameNew = GetFileName(appId, id, fileVersion);
var fileNameNew = GetFileName(appId, id, fileVersion, suffix);
await assetStore.DownloadAsync(fileNameNew, stream, range, ct);
}
catch (AssetNotFoundException)
catch (AssetNotFoundException) when (!options.FolderPerApp)
{
var fileNameOld = GetFileName(id, fileVersion);
var fileNameOld = GetFileName(id, fileVersion, suffix);
await assetStore.DownloadAsync(fileNameOld, stream, range, ct);
}
}
public Task UploadAsync(DomainId appId, DomainId id, long fileVersion, Stream stream,
public Task UploadAsync(DomainId appId, DomainId id, long fileVersion, string? suffix, Stream stream, bool overwrite = true,
CancellationToken ct = default)
{
var fileName = GetFileName(appId, id, fileVersion);
var fileName = GetFileName(appId, id, fileVersion, suffix);
return assetStore.UploadAsync(fileName, stream, true, ct);
return assetStore.UploadAsync(fileName, stream, overwrite, ct);
}
public Task UploadAsync(string tempFile, Stream stream,
@ -77,22 +82,29 @@ namespace Squidex.Domain.Apps.Entities.Assets
return assetStore.UploadAsync(tempFile, stream, false, ct);
}
public Task CopyAsync(string tempFile, DomainId appId, DomainId id, long fileVersion,
public Task CopyAsync(string tempFile, DomainId appId, DomainId id, long fileVersion, string? suffix,
CancellationToken ct = default)
{
var fileName = GetFileName(appId, id, fileVersion);
var fileName = GetFileName(appId, id, fileVersion, suffix);
return assetStore.CopyAsync(tempFile, fileName, ct);
}
public async Task DeleteAsync(DomainId appId, DomainId id, long fileVersion)
public Task DeleteAsync(DomainId appId, DomainId id, long fileVersion, string? suffix)
{
var fileNameOld = GetFileName(id, fileVersion);
var fileNameNew = GetFileName(appId, id, fileVersion);
var fileNameOld = GetFileName(id, fileVersion, suffix);
var fileNameNew = GetFileName(appId, id, fileVersion, suffix);
await Task.WhenAll(
assetStore.DeleteAsync(fileNameOld),
assetStore.DeleteAsync(fileNameNew));
if (options.FolderPerApp)
{
return assetStore.DeleteAsync(fileNameNew);
}
else
{
return Task.WhenAll(
assetStore.DeleteAsync(fileNameOld),
assetStore.DeleteAsync(fileNameNew));
}
}
public Task DeleteAsync(string tempFile)
@ -100,14 +112,42 @@ namespace Squidex.Domain.Apps.Entities.Assets
return assetStore.DeleteAsync(tempFile);
}
private static string GetFileName(DomainId id, long fileVersion)
private static string GetFileName(DomainId id, long fileVersion, string? suffix)
{
return $"{id}_{fileVersion}";
if (!string.IsNullOrWhiteSpace(suffix))
{
return $"{id}_{fileVersion}_{suffix}";
}
else
{
return $"{id}_{fileVersion}";
}
}
private static string GetFileName(DomainId appId, DomainId id, long fileVersion)
private string GetFileName(DomainId appId, DomainId id, long fileVersion, string? suffix)
{
return $"{appId}_{id}_{fileVersion}";
if (options.FolderPerApp)
{
if (!string.IsNullOrWhiteSpace(suffix))
{
return $"derived/{appId}/{id}_{fileVersion}_{suffix}";
}
else
{
return $"{appId}/{id}_{fileVersion}";
}
}
else
{
if (!string.IsNullOrWhiteSpace(suffix))
{
return $"{appId}_{id}_{fileVersion}_{suffix}";
}
else
{
return $"{appId}_{id}_{fileVersion}";
}
}
}
}
}

8
backend/src/Squidex.Domain.Apps.Entities/Assets/DomainObject/AssetCommandMiddleware.cs

@ -34,12 +34,6 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
IEnumerable<IAssetMetadataSource> assetMetadataSources)
: base(grainFactory)
{
Guard.NotNull(assetEnricher, nameof(assetEnricher));
Guard.NotNull(assetFileStore, nameof(assetFileStore));
Guard.NotNull(assetMetadataSources, nameof(assetMetadataSources));
Guard.NotNull(assetQuery, nameof(assetQuery));
Guard.NotNull(contextProvider, nameof(contextProvider));
this.assetEnricher = assetEnricher;
this.assetFileStore = assetFileStore;
this.assetMetadataSources = assetMetadataSources.OrderBy(x => x.Order).ToList();
@ -148,7 +142,7 @@ namespace Squidex.Domain.Apps.Entities.Assets.DomainObject
try
{
await assetFileStore.CopyAsync(tempFile, asset.AppId.Id, asset.AssetId, asset.FileVersion);
await assetFileStore.CopyAsync(tempFile, asset.AppId.Id, asset.AssetId, asset.FileVersion, null);
}
catch (AssetAlreadyExistsException) when (context.Command is not UpsertAsset)
{

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save