Browse Source

I18n (#554)

* First files and keys.

* Progress with translation.

* More progress.

* Temp

* Simplify validation and error messages to make i18n easier.

* Texts improved.

* Just code formatting.

* Much more translations.

* Cleanup.

* Texts fixed.

* More translations and test fixes.

* Translations fixed.

* Build fix.

* More progress with texts.

* Texts fixed.

* Translations migrated.

* Put texts to resources.

* Progress with typescript.

* Name of pipe fixed.

* TranslationService => Localizer.

* Do not translate recursive.

* Localization improvements.

* I18n martijn (#550)

* prepared the appsettings and the window options for the Google Analtyics Id setting

* removed white spaces

* refactored the analytics.service, also made use of the settings from the appsettings.json. The analtycsid from the appsettings will now be used.

* setting back the localhost statements for the gtag config

* Refactored the analytics.service again

* Made the uiOptions options, for the sake of testing...

* fixed a spelling mistake

* Removed the empty line after the if statement.

* Removed the analyticsIdConfig class. Fixed the hostname check for localhost

* i dont know why this didnt merge correctly

* started with the i18n translations

* created a custom translate pipe

* Started with the implementation of ngx-translate

* forgot the package-lock

* End of day commit

* Added a shell for the ngx-translate (maybe remove it in the future)

* TranslationService => LocalizerService in the frontend

* Fixes for the stylelint, and adding the localizer service to the app.module.ts

* Fixes for the stylelint

* Fixes for the remove translationsService

* Design for replacing the variables in a translate text

* Little bit of translation progress

* Changed the infrastructue for the translates

* added the localizerService to the prettifyError handler

* made the translate function better

* The localizer is now a singleton and did some fixes

* Implemented custom pipe options

* Cleanup

* Removed some testdata

* Cleanup

* Started with Code review feedback.
Started with translate variables as an object instead of an var. Fixed tests

* Removed the regex for finden text that needs to be replaced

Co-authored-by: Martijn Dijkgraaf <>

* More texts

* Progress

* More translations

* More texts

* More texts

* Simplified translations

* Cleanup

* More fixes

* Cleanup

* Consistency fixes

* More texts

* More fixes

* Prefixes unified.

* Embed texts.

* Dialog fixed.

* Tests fixed.

* Styling fix.

* Just a cleanup.

* Inline markdown

* Minor fix.

* Small bugfixes

* Indexes improvements.

* Added translator.

* Notifo fix.

Co-authored-by: MartijnDijkgraaf <martijndijkgraaf96@gmail.com>
pull/556/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
ce1a1386fc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryContentsByQuery.cs
  2. 2
      backend/src/Squidex.Infrastructure/Translations/ILocalizer.cs
  3. 8
      backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs
  4. 10
      backend/src/Squidex.Web/Services/StringLocalizer.cs
  5. 2
      backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  6. 2
      backend/src/Squidex/Areas/Api/Controllers/News/NewsController.cs
  7. 6
      backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs
  8. 38
      backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs
  9. 835
      backend/src/Squidex/Areas/Frontend/Resources/texts.en
  10. 2
      backend/src/Squidex/Config/Web/WebExtensions.cs
  11. 13
      backend/src/Squidex/Squidex.csproj
  12. 25
      backend/tests/Squidex.Infrastructure.Tests/Translations/TTests.cs
  13. 6
      frontend/app/app.component.html
  14. 13
      frontend/app/app.module.ts
  15. 10
      frontend/app/features/administration/administration-area.component.html
  16. 2
      frontend/app/features/administration/pages/cluster/cluster-page.component.html
  17. 6
      frontend/app/features/administration/pages/event-consumers/event-consumer.component.html
  18. 16
      frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  19. 16
      frontend/app/features/administration/pages/restore/restore-page.component.html
  20. 2
      frontend/app/features/administration/pages/restore/restore-page.component.ts
  21. 38
      frontend/app/features/administration/pages/users/user-page.component.html
  22. 6
      frontend/app/features/administration/pages/users/user.component.html
  23. 20
      frontend/app/features/administration/pages/users/users-page.component.html
  24. 8
      frontend/app/features/administration/services/event-consumers.service.ts
  25. 12
      frontend/app/features/administration/services/users.service.ts
  26. 2
      frontend/app/features/administration/state/event-consumers.state.ts
  27. 2
      frontend/app/features/administration/state/users.forms.ts
  28. 2
      frontend/app/features/administration/state/users.state.ts
  29. 10
      frontend/app/features/api/api-area.component.html
  30. 2
      frontend/app/features/api/pages/graphql/graphql-page.component.html
  31. 66
      frontend/app/features/apps/pages/apps-page.component.html
  32. 4
      frontend/app/features/apps/pages/news-dialog.component.html
  33. 90
      frontend/app/features/apps/pages/onboarding-dialog.component.html
  34. 2
      frontend/app/features/assets/pages/asset-tags.component.html
  35. 12
      frontend/app/features/assets/pages/assets-filters-page.component.html
  36. 24
      frontend/app/features/assets/pages/assets-page.component.html
  37. 6
      frontend/app/features/content/pages/content/content-event.component.html
  38. 2
      frontend/app/features/content/pages/content/content-field.component.html
  39. 48
      frontend/app/features/content/pages/content/content-history-page.component.html
  40. 2
      frontend/app/features/content/pages/content/content-history-page.component.ts
  41. 34
      frontend/app/features/content/pages/content/content-page.component.html
  42. 20
      frontend/app/features/content/pages/content/content-page.component.ts
  43. 6
      frontend/app/features/content/pages/content/field-languages.component.html
  44. 13
      frontend/app/features/content/pages/contents/contents-filters-page.component.html
  45. 32
      frontend/app/features/content/pages/contents/contents-page.component.html
  46. 7
      frontend/app/features/content/pages/contents/custom-view-editor.component.html
  47. 8
      frontend/app/features/content/pages/schemas/schemas-page.component.html
  48. 4
      frontend/app/features/content/shared/content-status.component.html
  49. 14
      frontend/app/features/content/shared/due-time-selector.component.html
  50. 13
      frontend/app/features/content/shared/forms/array-editor.component.html
  51. 14
      frontend/app/features/content/shared/forms/array-item.component.html
  52. 47
      frontend/app/features/content/shared/forms/assets-editor.component.html
  53. 15
      frontend/app/features/content/shared/forms/stock-photo-editor.component.html
  54. 6
      frontend/app/features/content/shared/list/content-list-field.component.html
  55. 17
      frontend/app/features/content/shared/list/content.component.html
  56. 2
      frontend/app/features/content/shared/preview-button.component.html
  57. 14
      frontend/app/features/content/shared/references/content-creator.component.html
  58. 2
      frontend/app/features/content/shared/references/content-creator.component.ts
  59. 19
      frontend/app/features/content/shared/references/content-selector.component.html
  60. 4
      frontend/app/features/content/shared/references/references-editor.component.html
  61. 4
      frontend/app/features/dashboard/pages/cards/api-calls-card.component.html
  62. 10
      frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.html
  63. 6
      frontend/app/features/dashboard/pages/cards/api-card.component.html
  64. 8
      frontend/app/features/dashboard/pages/cards/api-performance-card.component.html
  65. 8
      frontend/app/features/dashboard/pages/cards/api-traffic-card.component.html
  66. 2
      frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.html
  67. 2
      frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.html
  68. 10
      frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.html
  69. 2
      frontend/app/features/dashboard/pages/cards/content-summary-card.component.html
  70. 6
      frontend/app/features/dashboard/pages/cards/github-card.component.html
  71. 2
      frontend/app/features/dashboard/pages/cards/history-card.component.html
  72. 10
      frontend/app/features/dashboard/pages/cards/schema-card.component.html
  73. 6
      frontend/app/features/dashboard/pages/cards/support-card.component.html
  74. 22
      frontend/app/features/dashboard/pages/dashboard-config.component.html
  75. 26
      frontend/app/features/dashboard/pages/dashboard-config.component.ts
  76. 65
      frontend/app/features/dashboard/pages/dashboard-page.component.html
  77. 26
      frontend/app/features/rules/pages/events/rule-events-page.component.html
  78. 2
      frontend/app/features/rules/pages/rules/rule-element.component.html
  79. 53
      frontend/app/features/rules/pages/rules/rule-wizard.component.html
  80. 38
      frontend/app/features/rules/pages/rules/rule.component.html
  81. 29
      frontend/app/features/rules/pages/rules/rules-page.component.html
  82. 26
      frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html
  83. 10
      frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html
  84. 35
      frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html
  85. 65
      frontend/app/features/schemas/pages/schema/fields/field.component.html
  86. 22
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html
  87. 6
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html
  88. 4
      frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html
  89. 10
      frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html
  90. 14
      frontend/app/features/schemas/pages/schema/fields/schema-fields.component.html
  91. 6
      frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html
  92. 16
      frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html
  93. 42
      frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html
  94. 16
      frontend/app/features/schemas/pages/schema/fields/types/boolean-ui.component.html
  95. 2
      frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.html
  96. 10
      frontend/app/features/schemas/pages/schema/fields/types/date-time-ui.component.html
  97. 8
      frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html
  98. 4
      frontend/app/features/schemas/pages/schema/fields/types/geolocation-ui.component.html
  99. 22
      frontend/app/features/schemas/pages/schema/fields/types/number-ui.component.html
  100. 14
      frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.html

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

@ -48,16 +48,24 @@ namespace Squidex.Domain.Apps.Entities.MongoDb.Contents.Operations
this.indexer = indexer;
}
protected override Task PrepareAsync(CancellationToken ct = default)
protected override async Task PrepareAsync(CancellationToken ct = default)
{
var index =
var indexBySchemaWithRefs =
new CreateIndexModel<MongoContentEntity>(Index
.Ascending(x => x.IndexedSchemaId)
.Ascending(x => x.IsDeleted)
.Ascending(x => x.ReferencedIds)
.Descending(x => x.LastModified));
return Collection.Indexes.CreateOneAsync(index, cancellationToken: ct);
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);
}
public async Task<IResultList<IContentEntity>> DoAsync(IAppEntity app, ISchemaEntity schema, ClrQuery query, SearchScope scope)

2
backend/src/Squidex.Infrastructure/Translations/ILocalizer.cs

@ -11,6 +11,6 @@ namespace Squidex.Infrastructure.Translations
{
public interface ILocalizer
{
(string Result, bool NotFound) Get(CultureInfo culture, string key, string fallback, object? args = null);
(string Result, bool Found) Get(CultureInfo culture, string key, string fallback, object? args = null);
}
}

8
backend/src/Squidex.Infrastructure/Translations/ResourcesLocalizer.cs

@ -41,7 +41,7 @@ namespace Squidex.Infrastructure.Translations
#endif
}
public (string Result, bool NotFound) Get(CultureInfo culture, string key, string fallback, object? args = null)
public (string Result, bool Found) Get(CultureInfo culture, string key, string fallback, object? args = null)
{
Guard.NotNull(culture, nameof(culture));
Guard.NotNullOrEmpty(key, nameof(key));
@ -51,7 +51,7 @@ namespace Squidex.Infrastructure.Translations
if (translation == null)
{
return (fallback, true);
return (fallback, false);
}
if (args != null)
@ -153,10 +153,10 @@ namespace Squidex.Infrastructure.Translations
sb.Append(span);
return (sb.ToString(), false);
return (sb.ToString(), true);
}
return (translation, false);
return (translation, true);
}
private string? GetCore(CultureInfo culture, string key)

10
backend/src/Squidex.Web/Services/StringLocalizer.cs

@ -38,14 +38,14 @@ namespace Squidex.Web.Services
TranslateProperty(name, arguments, currentCulture);
var (result, notFound) = translationService.Get(currentCulture, $"aspnet_{name}", name);
var (result, found) = translationService.Get(currentCulture, $"aspnet_{name}", name);
if (arguments != null && !notFound)
if (arguments != null && found)
{
result = string.Format(currentCulture, result, arguments);
}
return new LocalizedString(name, result, notFound);
return new LocalizedString(name, result, !found);
}
}
@ -55,9 +55,9 @@ namespace Squidex.Web.Services
{
var key = $"common.{arguments[0].ToString()?.ToCamelCase()}";
var (result, notFound) = translationService.Get(currentCulture, key, name);
var (result, found) = translationService.Get(currentCulture, key, name);
if (!notFound)
if (found)
{
arguments[0] = result;
}

2
backend/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs

@ -284,7 +284,7 @@ namespace Squidex.Areas.Api.Controllers.Apps
/// </returns>
[HttpDelete]
[Route("apps/{app}/")]
[ApiPermissionOrAnonymous(Permissions.AppDelete)]
[ApiPermission(Permissions.AppDelete)]
[ApiCosts(0)]
public async Task<IActionResult> DeleteApp(string app)
{

2
backend/src/Squidex/Areas/Api/Controllers/News/NewsController.cs

@ -39,7 +39,7 @@ namespace Squidex.Areas.Api.Controllers.News
[Route("news/features/")]
[ProducesResponseType(typeof(FeaturesDto), 200)]
[ApiPermission]
public async Task<IActionResult> GetLanguages([FromQuery] int version = 0)
public async Task<IActionResult> GetNews([FromQuery] int version = 0)
{
var features = await featuresService.GetFeaturesAsync(version);

6
backend/src/Squidex/Areas/Api/Controllers/Users/UsersController.cs

@ -37,11 +37,11 @@ namespace Squidex.Areas.Api.Controllers.Users
{
var assembly = typeof(UsersController).Assembly;
using (var avatarStream = assembly.GetManifestResourceStream("Squidex.Areas.Api.Controllers.Users.Assets.Avatar.png"))
using (var resourceStream = assembly.GetManifestResourceStream("Squidex.Areas.Api.Controllers.Users.Assets.Avatar.png"))
{
AvatarBytes = new byte[avatarStream!.Length];
AvatarBytes = new byte[resourceStream!.Length];
avatarStream.Read(AvatarBytes, 0, AvatarBytes.Length);
resourceStream.Read(AvatarBytes, 0, AvatarBytes.Length);
}
}

38
backend/src/Squidex/Areas/Frontend/Middlewares/IndexExtensions.cs

@ -6,6 +6,9 @@
// ==========================================================================
using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.IO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@ -18,6 +21,8 @@ namespace Squidex.Areas.Frontend.Middlewares
{
public static class IndexExtensions
{
private static readonly ConcurrentDictionary<string, string> Texts = new ConcurrentDictionary<string, string>();
public static bool IsIndex(this HttpContext context)
{
return context.Request.Path.Value.EndsWith("/index.html", StringComparison.OrdinalIgnoreCase);
@ -61,9 +66,38 @@ namespace Squidex.Areas.Frontend.Middlewares
}
var jsonSerializer = httpContext.RequestServices.GetRequiredService<IJsonSerializer>();
var jsonOptions = jsonSerializer.Serialize(uiOptions);
var jsonOptions = jsonSerializer.Serialize(uiOptions, true);
var texts = GetText(CultureInfo.CurrentUICulture.Name);
result = result.Replace("<body>", $"<body>\n<script>\nvar options = {jsonOptions};\nvar texts = {texts};</script>");
}
return result;
}
private static string GetText(string culture)
{
if (!Texts.TryGetValue(culture, out var result))
{
var assembly = typeof(IndexExtensions).Assembly;
var resourceName = $"Squidex.Areas.Frontend.Resources.texts.{culture}";
var resourceStream = assembly.GetManifestResourceStream(resourceName);
if (resourceStream != null)
{
using (var reader = new StreamReader(resourceStream))
{
result = reader.ReadToEnd();
result = result.Replace("<body>", $"<body><script>var options = {jsonOptions};</script>");
Texts[culture] = result;
}
}
else
{
return GetText("en");
}
}
return result;

835
backend/src/Squidex/Areas/Frontend/Resources/texts.en

@ -0,0 +1,835 @@
{
"api.contentApi": "Content API",
"api.generalApi": "General API",
"api.graphql": "GraphQL",
"api.graphqlPageTitle": "GraphQL",
"api.pageTitle": "API",
"api.title": "API",
"apps.allApps": "All Apps",
"apps.appLoadFailed": "Failed to load app. Please reload.",
"apps.appNameHint": "You can only use letters, numbers and dashes and not more than 40 characters.",
"apps.appNameValidationMessage": "Name can contain lower case letters (a-z), numbers and dashes between.",
"apps.appNameWarning": "The app name cannot be changed later.",
"apps.appsButtonCreate": "Apps Overview",
"apps.appsButtonFallbackTitle": "Apps Overview",
"apps.archieve": "Archive App",
"apps.archieveConfirmText": "Remove pattern",
"apps.archieveConfirmTitle": "Do you really want to archive this app?",
"apps.archieveWarning": "Once you archive an app, there is no going back. Please be certain.",
"apps.archiveFailed": "Failed to archive app. Please reload.",
"apps.create": "Create App",
"apps.createBlankApp": "New App.",
"apps.createBlankAppDescription": "Create a new blank app without content and schemas.",
"apps.createBlogApp": "New Blog Sample",
"apps.createBlogAppDescription": "Start with our ready to use blog.",
"apps.createFailed": "Failed to create app. Please reload.",
"apps.createIdentityApp": "New Identity App",
"apps.createIdentityAppDescription": "Create app for Squidex Identity.",
"apps.createIdentityAppV2": "New Identity App V2",
"apps.createIdentityAppV2Description": "Create app for Squidex Identity V2.",
"apps.createProfileApp": "New Profile Sample",
"apps.createProfileAppDescription": "Create your profile page.",
"apps.createWithTemplate": "Create {template} Sample",
"apps.empty": "You are not collaborating to any app yet",
"apps.generalSettings": "General",
"apps.generalSettingsDangerZone": "General",
"apps.image": "Image",
"apps.imageDrop": "Drop to upload",
"apps.listPageTitle": "Apps",
"apps.loadFailed": "Failed to load apps. Please reload.",
"apps.removeImage": "Remove image",
"apps.removeImageFailed": "Failed to remove app image. Please reload.",
"apps.updateFailed": "Failed to update app. Please reload.",
"apps.upgradeHintCurrent": "You are on the {plan} plan.",
"apps.upgradeHintUpgrade": "Upgrade!",
"apps.uploadImage": "Drop an file to replace the app image. Use a square size.",
"apps.uploadImageButton": "Upload File",
"apps.uploadImageFailed": "Failed to upload image. Please reload.",
"apps.uploadImageTooBig": "App image is too big.",
"apps.welcomeSubtitle": "Welcome to Squidex.",
"apps.welcomeTitle": "Hi {user}",
"assets.createFolder": "Create Folder",
"assets.createFolderFailed": "Failed to create asset folder. Please reload.",
"assets.createFolderTooltip": "Create new folder (CTRL + SHIFT + G)",
"assets.deleteConfirmText": "Do you really want to delete the asset?",
"assets.deleteConfirmTitle": "Delete asset",
"assets.deleteFailed": "Failed to delete asset. Please reload.",
"assets.deleteFolderConfirmText": "Do you really want to delete the folder and all assets?",
"assets.deleteFolderConfirmTitle": "Delete folder",
"assets.deleteMetadataConfirmText": "Do you really want to remove this metadata?",
"assets.deleteMetadataConfirmTitle": "Remove metadata",
"assets.downloadVersion": "Download this Version",
"assets.dropToUpdate": "Drop to update",
"assets.duplicateFile": "Asset has already been uploaded.",
"assets.editor.flipHorizontally": "Flip Horizontally",
"assets.editor.flipVertically": "Flip Vertically",
"assets.editor.focusPointLabel": "Select position of focus point",
"assets.editor.focusPointPreview": "Preview for different sizes",
"assets.editor.rotateLeft": "Rotate Left",
"assets.editor.rotateRight": "Rotate Right",
"assets.fileTooBig": "Asset is too big.",
"assets.folderName": "Folder Name",
"assets.folderNameHint": "The folder name is used as display name and must not be unique.",
"assets.insertAssets": "Insert Assets",
"assets.linkSelected": "Link selected assets ({count})",
"assets.listPageTitle": "Assets",
"assets.loadFailed": "Failed to load assets. Please reload.",
"assets.loadFoldersFailed": "Failed to load asset folders. Please reload.",
"assets.metadata": "Metadata",
"assets.metadataAdd": "Add Metadata",
"assets.moveFailed": "Failed to move asset. Please reload.",
"assets.protected": "Protected",
"assets.refreshTooltip": "Refresh Assets (CTRL + SHIFT + R)",
"assets.reloaded": "Assets reloaded.",
"assets.renameFolder": "Rename Folder",
"assets.replaceConfirmText": "Do you really want to replace the asset with a newer version",
"assets.replaceConfirmTitle": "Replace asset?",
"assets.replaceFailed": "Failed to replace asset. Please reload.",
"assets.searchByName": "Search by name",
"assets.searchByTags": "Search by tags",
"assets.selectMany": "Select assets",
"assets.tabFocusPoint": "Focus Point",
"assets.tabHistory": "History",
"assets.tabImage": "Image",
"assets.tabMetadata": "Metadata",
"assets.updated": "Asset has been updated.",
"assets.updateFailed": "Failed to update asset. Please reload.",
"assets.updateFolderFailed": "Failed to update asset folder. Please reload.",
"assets.uploadByDialog": "Select File(s",
"assets.uploadByDrop": "Drop files here to upload",
"assets.uploaderUploadHere": "No upload in progress, drop files here.",
"assets.uploadFailed": "Failed to upload asset. Please reload.",
"assets.uploadHint": "Drop file on existing item to replace the asset with a newer version.",
"backups.backupCountAssets": "Assets",
"backups.backupCountAssetsTooltip": "Archived assets",
"backups.backupCountEvents": "Events",
"backups.backupCountEventsTooltip": "Archived events",
"backups.backupDownload": "Download",
"backups.backupDownloadLink": "Ready",
"backups.backupDuration": "Duration",
"backups.deleteConfirmText": "Do you really want to delete the backup?",
"backups.deleteConfirmTitle": "Delete backup",
"backups.deleted": "Backup is about to be deleted.",
"backups.deleteFailed": "Failed to delete backup.",
"backups.empty": "No backups created yet.",
"backups.loadFailed": "Failed to load backups.",
"backups.maximumReached": "Your have reached the maximum number of backups: 10.",
"backups.refreshTooltip": "Refresh backups (CTRL + SHIFT + R)",
"backups.reloaded": "Backups reloaded.",
"backups.restore": "Restore Backup",
"backups.restoreFailed": "Failed to start restore.",
"backups.restoreLastStatus": "Last Restore Operation",
"backups.restoreLastUrl": "Url to backup",
"backups.restoreNewAppName": "Optional app name",
"backups.restorePageTitle": "Restore Backup",
"backups.restoreStarted": "Restore started, it can take several minutes to complete.",
"backups.restoreStartedLabel": "Started",
"backups.restoreStoppedLabel": "Stopped",
"backups.restoreTitle": "Restore Backup",
"backups.start": "Start Backup",
"backups.started": "Backup started, it can take several minutes to complete.",
"backups.startFailed": "Failed to start backup.",
"clients.add": "Add Client",
"clients.addFailed": "Failed to add client. Please reload.",
"clients.allowAnonymous": "Allow anonymous access.",
"clients.allowAnonymousHint": "Allow access to the API without an access token to all resources that are configured via the role of this client. Do not give more than one client anonymous access.",
"clients.clientIdValidationMessage": "Name can only contain letters, numbers, dashes and spaces.",
"clients.clientNamePlaceholder": "Enter client name",
"clients.connect": "Connect",
"clients.connectWizard.cli": "Connect with Squidex CLI",
"clients.connectWizard.cliHint": "Download the CLI and connect to this app to start backups, sync schemas or export content.",
"clients.connectWizard.cliStep1": "Get the latest Squidex CLI",
"clients.connectWizard.cliStep1Download": "[Download the CLI from Github](https://github.com/Squidex/squidex-samples/releases)",
"clients.connectWizard.cliStep1Hint": "The releases contains binaries for all major operation system and a small download if you have .NET Core installed.",
"clients.connectWizard.cliStep2": "Add `<your Squidex CLI download directory>` to your `$PATH` variable",
"clients.connectWizard.cliStep3": "Add your app name the CLI config",
"clients.connectWizard.cliStep3Hint": "You can manage configuration to multiple apps in the CLI and switch to an app.",
"clients.connectWizard.cliStep4": "Switch to your app in the CLI",
"clients.connectWizard.manually": "Connect manually",
"clients.connectWizard.manuallyHint": "Get instructions how to establish a connection with Postman or curl.",
"clients.connectWizard.manuallyStep1": "Get a token using curl",
"clients.connectWizard.manuallyStep2": "Just use the following token",
"clients.connectWizard.manuallyStep3": "Add the token as HTTP header to all requests",
"clients.connectWizard.manuallyTokenHint": "Tokens usally expire after 30days, but you can request multiple tokens.",
"clients.connectWizard.postManDocs": "Start with the Postman tutorial in the [Documentation](https://docs.squidex.io/02-documentation/developer-guides/api-overview/postman).",
"clients.connectWizard.sdk": "Connect to your App with SDK",
"clients.connectWizard.sdkHelp": "You need another SDK?",
"clients.connectWizard.sdkHelpLink": "Contact us in the Support Forum",
"clients.connectWizard.sdkHint": "Download an SDK and establish a connection to this app.",
"clients.connectWizard.sdkStep1": "Install the .NET SDK",
"clients.connectWizard.sdkStep1Download": "The SDK is available on [nuget](https://www.nuget.org/packages/Squidex.ClientLibrary/)",
"clients.connectWizard.sdkStep2": "Create a client manager",
"clients.connectWizard.step0Title": "Setup client",
"clients.connectWizard.step1Title": "Choose connection method",
"clients.connectWizard.step2Title": "Connect",
"clients.deleteConfirmText": "Do you really want to revoke the client?",
"clients.deleteConfirmTitle": "Revoke client",
"clients.empty": "No client created yet.",
"clients.loadFailed": "Failed to load clients. Please reload.",
"clients.refreshTooltip": "Refresh clients (CTRL + SHIFT + R)",
"clients.reloaded": "Clients reloaded.",
"clients.revokeFailed": "Failed to revoke client. Please reload.",
"clients.tokenFailed": "Failed to create token. Please retry.",
"comments.create": "Create a comment",
"comments.createFailed": "Failed to create comment.",
"comments.deleteConfirmText": "Do you really want to delete the comment?",
"comments.deleteConfirmTitle": "Delete comment",
"comments.deleteFailed": "Failed to delete comment.",
"comments.follow": "Follow",
"comments.loadFailed": "Failed to load comments.",
"comments.title": "Comments",
"comments.updateFailed": "Failed to update comment.",
"common.actions": "Actions",
"common.administration": "Administration",
"common.administrationPageTitle": "Administration",
"common.api": "API",
"common.apps": "Apps",
"common.aspectRatio": "AspectRatio",
"common.assets": "Assets",
"common.back": "Back",
"common.backups": "Backups",
"common.bytes": "bytes",
"common.cancel": "Cancel",
"common.clear": "Clear",
"common.clientId": "Client Id",
"common.clients": "Clients",
"common.clientSecret": "Client Secret",
"common.clipboardAdded": "Value has been added to your clipboard.",
"common.clone": "Clone",
"common.cluster": "Cluster",
"common.clusterPageTitle": "Cluster",
"common.comments": "Comments",
"common.confirm": "Confirm",
"common.consumers": "Consumers",
"common.content": "Content",
"common.contents": "Contents",
"common.continue": "Continue",
"common.contributors": "Contributors",
"common.create": "Create",
"common.created": "Created",
"common.date": "Date",
"common.dateTimeEditor.now": "Now",
"common.dateTimeEditor.nowTooltip": "Use Now (UTC)",
"common.dateTimeEditor.today": "Today",
"common.dateTimeEditor.todayTooltip": "Use Today (UTC)",
"common.delete": "Delete",
"common.description": "Description",
"common.displayName": "Display Name",
"common.edit": "Edit",
"common.email": "Email",
"common.error": "Error",
"common.errorBack": "Back to previous page.",
"common.errorNoPermission": "You do not have the permissions to do this.",
"common.errorNotFound": "Not Found",
"common.event": "Event",
"common.events": "Events",
"common.executed": "Executed",
"common.expertMode": "Expert Mode",
"common.failed": "Failed",
"common.fallback": "Fallback",
"common.field": "Field",
"common.files": "Files",
"common.filters": "Filters",
"common.folders": "Folders",
"common.generalSettings": "common",
"common.generate": "Generate",
"common.github": "Github",
"common.height": "Height",
"common.help": "Help",
"common.helpTour": "Click the help icon to show a context specific help page. Go to",
"common.hide": "Hide",
"common.hints": "Hints",
"common.history": "History",
"common.httpConflict": "Failed to make the update. Another user has made a change. Please reload.",
"common.httpLimit": "You have exceeded the maximum limit of API calls.",
"common.label": "Label",
"common.languages": "Languages",
"common.latitudeShort": "Lat",
"common.loading": "Loading",
"common.logout": "Logout",
"common.logs": "Logs",
"common.longitudeShort": "Lon",
"common.mapHide": "Hide map",
"common.mapShow": "Show map",
"common.message": "Message",
"common.name": "Name",
"common.no": "No",
"common.nothingChanged": "Nothing has been changed.",
"common.noValue": "No value",
"common.or": "or",
"common.pagerInfo": "{itemFirst}-{itemLast} of {numberOfItems}",
"common.password": "Password",
"common.passwordConfirm": "Confirm Password",
"common.pattern": "Pattern",
"common.patterns": "Patterns",
"common.permissions": "Permissions",
"common.preview": "Preview",
"common.product": "Squidex Headless CMS",
"common.project": "Project",
"common.refresh": "Refresh",
"common.rename": "Rename",
"common.requiredHint": "required",
"common.reset": "Reset",
"common.restore": "Restore",
"common.role": "Role",
"common.roles": "Roles",
"common.rules": "Rules",
"common.sampleCodeLabel": "Sample Code at",
"common.save": "Save",
"common.saveShortcut": "CTRL + S",
"common.schemas": "Schemas",
"common.searchGoogleMaps": "Search Google Maps",
"common.searchResults": "Search Results",
"common.separateByLine": "Separate by line",
"common.settings": "Settings",
"common.sidebarTour": "The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.",
"common.slug": "Slug",
"common.stars.max": "Must not have more more than 15 stars",
"common.status": "Status",
"common.statusChangeTo": "Change to",
"common.submit": "Submit",
"common.subscription": "Subscription",
"common.succeeded": "Succeeded",
"common.tagAdd": ", to add tag",
"common.tagAddReference": ", to add reference",
"common.tagAddSchema": ", to add schema",
"common.tags": "Tags",
"common.tagsAll": "All tags",
"common.time": "Time",
"common.update": "Update",
"common.users": "Users",
"common.value": "Value",
"common.width": "Width",
"common.workflow": "Workflow",
"common.workflows": "Workflows",
"common.yes": "Yes",
"contents.arrayAddItem": "Add Item",
"contents.arrayCloneItem": "Clone this item",
"contents.arrayCollapseAll": "Collapse all items",
"contents.arrayCollapseItem": "Collapse this item",
"contents.arrayExpandAll": "Expand all items",
"contents.arrayExpandItem": "Expand this item",
"contents.arrayMoveBottom": "Move this item to bottom",
"contents.arrayMoveDown": "Move this item down",
"contents.arrayMoveTop": "Move this item to top",
"contents.arrayMoveUp": "Move this item up",
"contents.arrayNoFields": "Add a nested field first to add items.",
"contents.assetsUpload": "Drop files or click",
"contents.autotranslate": "Autotranslate from master language",
"contents.changeStatusTo": "Change content item(s) to {action}",
"contents.changeStatusToImmediately": "Set to {action} immediately.",
"contents.changeStatusToLater": "Set to {action} at a later point date and time.",
"contents.contentNotValid": "Content element not valid, please check the field with the red bar on the left in all languages (if localizable).",
"contents.create": "New",
"contents.createContentTooltip": "New Content (CTRL + SHIFT + G)",
"contents.created": "Content created successfully.",
"contents.createdByFieldDescription": "The user who created the content item.",
"contents.createFailed": "Failed to create content. Please reload.",
"contents.createFieldDescription": "The date time when the content item was created.",
"contents.createPageTitle": "Create Content",
"contents.createTitle": "New Content",
"contents.currentStatusLabel": "Current Version",
"contents.deleteConfirmText": "Do you really want to delete the content?",
"contents.deleteConfirmTitle": "Delete content",
"contents.deleteFailed": "Failed to delete content. Please reload.",
"contents.deleteManyConfirmText": "Do you really want to delete the selected content items?",
"contents.deleteVersionConfirmText": "Do you really want to delete this version?",
"contents.deleteVersionFailed": "Failed to delete version. Please reload.",
"contents.draftNew": "New Draft",
"contents.draftStatus": "New Version",
"contents.editPageTitle": "Edit Content",
"contents.editTitle": "Edit Content",
"contents.languageModeAll": "All Languages",
"contents.languageModeSingle": "Single Language",
"contents.lastModifiedByFieldDescription": "The user who modified the content item the last time.",
"contents.lastModifiedFieldDescription": "The date time when the content item was modified the last time.",
"contents.lastUpdatedLabel": "Last Updated",
"contents.loadContent": "Load",
"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.noReference": "- No Reference -",
"contents.pendingChangesTextToChange": "You have unsaved changes.\n\nWhen you change the status you will loose them.\n\n**Do you want to continue anyway?**",
"contents.pendingChangesTextToClose": "You have unsaved changes.\n\nWhen you close the current content view you will loose them.\n\n**Do you want to continue anyway?**",
"contents.pendingChangesTitle": "Unsaved changes",
"contents.referencesCreateNew": "Add New",
"contents.referencesCreatePublish": "Create and Publish",
"contents.referencesLink": "Link selected contents ({count})",
"contents.referencesSelectExisting": "Select Existing",
"contents.referencesSelectSchema": "Select {schema}",
"contents.refreshTooltip": "Refresh Contents (CTRL + SHIFT + R)",
"contents.reloaded": "Contents reloaded.",
"contents.saveAndPublish": "Save and Publish",
"contents.scheduledAt": "at",
"contents.scheduledAtLabel": "at",
"contents.scheduledTo": "to",
"contents.schemasPageTitle": "Contents",
"contents.searchPlaceholder": "Fulltext search",
"contents.searchSchemasPlaceholder": "Search for schemas...",
"contents.selectionCount": "{count} items selected",
"contents.statusFieldDescription": "The status of the content item.",
"contents.statusQueries": "Status Queries",
"contents.stockPhotoEmpty": "Nothing selected",
"contents.stockPhotoSearch": "Search for Photos by Unsplash",
"contents.unsavedChangesText": "You have unsaved changes. Do you want to load them now?",
"contents.unsavedChangesTitle": "Unsaved changes",
"contents.updated": "Content updated successfully.",
"contents.updateFailed": "Failed to update content. Please reload.",
"contents.validationHint": "Please remember to check all languages when you see validation errors.",
"contents.versionCompare": "Compare",
"contents.versionDelete": "Delete this Version",
"contents.versionFieldDescription": "The version of the content item",
"contents.versionViewing": "Viewing version **{version}**.",
"contents.viewLatest": "View latest",
"contents.viewReset": "Reset Default View",
"contributors.add": "Add Contributor",
"contributors.addFailed": "Failed to add contributors. Please reload.",
"contributors.contributorAssigned": "A new user with the entered email address has been created and assigned as contributor.",
"contributors.contributorAssignedExisting": "User has been assigned",
"contributors.contributorAssignedInvited": "User has been invited and assigned.",
"contributors.contributorAssignedOld": "User has been added as contributor.",
"contributors.deleteConfirmText": "Do you really want to remove the contributor?",
"contributors.deleteConfirmTitle": "Remove contributor",
"contributors.deleteFailed": "Failed to delete contributors. Please reload.",
"contributors.emailPlaceholder": "Find existing user or invite by email",
"contributors.empty": "No contributors found.",
"contributors.import.emailsDetected": "Emails detected: {count}",
"contributors.import.run": "Add Contributors",
"contributors.import.run2": "Import",
"contributors.importButton": "Add many contributors at once",
"contributors.importHintg": "Big team?",
"contributors.importTitle": "Import contributors",
"contributors.loadFailed": "Failed to load contributors. Please reload.",
"contributors.planHint": "Your plan allows up to {maxContributors} contributors.",
"contributors.refreshTooltip": "Refresh contributors (CTRL + SHIFT + R)",
"contributors.reloaded": "Contributors reloaded.",
"contributors.search": "Search",
"contributors.userNotFound": "The user does not exist.",
"dashboard.apiCallsCard": "API Calls",
"dashboard.apiCallsChart": "API Calls Chart",
"dashboard.apiCallsLimitLabel": "Monthly limit",
"dashboard.apiCallsSummaryCard": "API Calls Summary",
"dashboard.apiDocumentation": "API Documentation",
"dashboard.apiPerformanceCard": "API Performance (ms): {summary}ms avg",
"dashboard.apiPerformanceChart": "API Performance Chart",
"dashboard.assetSizeCard": "Assets Size (MB",
"dashboard.assetSizeLabel": "Total Size",
"dashboard.assetSizeLimitLabel": "Total limit",
"dashboard.assetTotalSize": "Asset Total Storage Size",
"dashboard.assetUpdloadsCountChart": "Asset Uploads Count Chart",
"dashboard.assetUploadsCard": "Assets Uploads",
"dashboard.assetUploadsSizeChart": "Asset Uploads Size Chart",
"dashboard.configSaved": "Configuration saved.",
"dashboard.contentApi": "Content API",
"dashboard.contentApiDescription": "OpenAPI 3.0 compatible documentation for your app content.",
"dashboard.contentsSummaryCard": "Number of items",
"dashboard.currentMonthLabel": "This month",
"dashboard.downloadLog": "Download Log",
"dashboard.editConfig": "Edit Config",
"dashboard.github": "Github",
"dashboard.githubCardDescription": "Get the source code from Github and report bugs or ask for support.",
"dashboard.history": "History",
"dashboard.historyCard": "History",
"dashboard.pageTitle": "Dashboard",
"dashboard.resetConfigConfirmText": "Do you really want to reset the dashboard to the default?",
"dashboard.resetConfigConfirmTitle": "Reset config",
"dashboard.schemaNewCard": "New Schema",
"dashboard.schemaNewCardDescription": "A schema defines the structure of your content element.",
"dashboard.schemasCard": "Schemas",
"dashboard.schemasCardDescription": "Get an insight to the data model of this app.",
"dashboard.stackedChart": "Stacked",
"dashboard.supportCard": "Feedback & Support",
"dashboard.supportCardDescription": "Provide feedback and request features to help us to improve Squidex.",
"dashboard.trafficChart": "API Traffic Chart",
"dashboard.trafficSummaryCard": "Traffic (MB)",
"dashboard.welcomeText": "Welcome to **{app}** dashboard.",
"dashboard.welcomeTitle": "Hi {user}",
"eventConsumers.loadFailed": "Failed to load event consumers. Please reload.",
"eventConsumers.pageTitle": "Event Consumers",
"eventConsumers.position": "Position",
"eventConsumers.refreshTooltip": "Refresh event consumers (CTRL + SHIFT + R)",
"eventConsumers.reloaded": "Event Consumers reloaded.",
"eventConsumers.resetFailed": "Failed to reset event consumer. Please reload.",
"eventConsumers.resetTooltip": "Reset Event Consumer",
"eventConsumers.startFailed": "Failed to start event consumer. Please reload.",
"eventConsumers.startTooltip": "Start Event Consumer",
"eventConsumers.stopFailed": "Failed to stop event consumer. Please reload.",
"eventConsumers.stopTooltip": "Stop Event Consumer",
"features.loadFailed": "Failed to load features. Please reload.",
"history.loadFailed": "Failed to load history. Please reload.",
"history.title": "Activity",
"languages.add": "Add Language",
"languages.addFailed": "Failed to add language. Please reload.",
"languages.deleteConfirmText": "Do you really want to remove the language?",
"languages.deleteConfirmTitle": "Remove language",
"languages.deleteFailed": "Failed to delete language. Please reload.",
"languages.loadFailed": "Failed to load languages. Please reload.",
"languages.master": "Is Master",
"languages.masterHint": "Other languages fall back to the master if no fallback is defined.",
"languages.optional": "Is Optional",
"languages.optionalHint": "Values for optional languages must not be entered, even if field is required.",
"languages.refreshTooltip": "Refresh languages (CTRL + SHIFT + R)",
"languages.reloaded": "Languages reloaded.",
"languages.updateFailed": "Failed to change language. Please reload.",
"news.headline": "What's new?",
"news.title": "New Features",
"notifo.subscripeTooltip": "Click this button to subscribe to all changes and to receive push notifications.",
"patterns.deleteConfirmText": "Do you really want to remove this pattern?",
"patterns.deleteConfirmTitle": "Delete pattern",
"patterns.deleteFailed": "Failed to remove pattern. Please reload.",
"patterns.empty": "No pattern created yet.",
"patterns.loadFailed": "Failed to add pattern. Please reload.",
"patterns.nameValidationMessage": "Name can only contain letters, numbers, dashes and spaces.",
"patterns.refreshTooltip": "Refresh patterns (CTRL + SHIFT + R)",
"patterns.reloaded": "Patterns reloaded.",
"patterns.updateFailed": "Failed to update pattern. Please reload.",
"plans.billingPortal": "Billing Portal",
"plans.billingPortalHint": "Go to Billing Portal for payment history and subscription overview.",
"plans.change": "Change",
"plans.changeConfirmTitle": "Change subscription",
"plans.changeFailed": "Failed to change plan. Please reload.",
"plans.includedCalls": "API Calls",
"plans.includedContributors": "Contributors",
"plans.includedStorage": "Storage",
"plans.loadFailed": "Failed to load plans. Please reload.",
"plans.noPlanConfigured": "No plan configured, this app has unlimited usage.",
"plans.notPlanOwner": "You have not created the subscription. Therefore you cannot change the plan.",
"plans.perMonth": "Per Month",
"plans.perYear": "Per Year",
"plans.refreshTooltip": "Refresh Plans (CTRL + SHIFT + R)",
"plans.reloaded": "Plans reloaded.",
"plans.selected": "&#10003; Selected",
"profile.title": "Profile",
"profile.userEmail": "Signed in with",
"roles.add": "Add role",
"roles.addFailed": "Failed to add role. Please reload.",
"roles.default.owner": "Can do everything, including deleting the app.",
"roles.default.reader": "Can only read assets and contents.",
"roles.defaults.developer": "Can use the API view, edit assets, contents, schemas, rules, workflows and patterns.",
"roles.defaults.editor": "Can edit assets and contents and view workflows.",
"roles.deleteConfirmText": "Delete role",
"roles.deleteConfirmTitle": "Do you really want to delete the role?",
"roles.loadFailed": "Failed to load roles. Please reload.",
"roles.loadPermissionsFailed": "Failed to load permissions. Please reload.",
"roles.refreshTooltip": "Refresh roles (CTRL + SHIFT + R)",
"roles.reloaded": "Roles reloaded.",
"roles.revokeFailed": "Failed to revoke role. Please reload.",
"roles.roleNamePlaceholder": "Enter role name",
"roles.updateFailed": "Failed to update role. Please reload.",
"rules.actionEdit": "Edit Action",
"rules.cancelFailed": "Failed to cancel rule. Please reload.",
"rules.create": "Create new Rule",
"rules.createFailed": "Failed to create rule. Please reload.",
"rules.createTooltip": "New Rule (CTRL + M)",
"rules.deleteConfirmText": "Do you really want to delete the rule?",
"rules.deleteConfirmTitle": "Delete rule",
"rules.deleteFailed": "Failed to delete rule. Please reload.",
"rules.disableFailed": "Failed to disable rule. Please reload.",
"rules.empty": "No rule created yet.",
"rules.emptyAddRule": "Add Rule",
"rules.enableFailed": "Failed to enable rule. Please reload.",
"rules.enqueued": "Rule has been added to the queue.",
"rules.listPageTitle": "Rules",
"rules.loadFailed": "Failed to load Rules. Please reload.",
"rules.readMore": "Read More",
"rules.refreshEventsTooltip": "Refresh Events (CTRL + SHIFT + R)",
"rules.refreshTooltip": "Refresh Rules (CTRL + SHIFT + R)",
"rules.reloaded": "Rules reloaded.",
"rules.restarted": "Rule will start to run in a few seconds.",
"rules.ruleEvents.cancelFailed": "Failed to cancel rule event. Please reload.",
"rules.ruleEvents.enqueue": "Enqueue",
"rules.ruleEvents.enqueued": "Events enqueued. Will be resend in a few seconds.",
"rules.ruleEvents.enqueueFailed": "Failed to enqueue rule event. Please reload.",
"rules.ruleEvents.lastInvokedLabel": "Last Invocation",
"rules.ruleEvents.listPageTitle": "Rule Events",
"rules.ruleEvents.loadFailed": "Failed to load events. Please reload.",
"rules.ruleEvents.nextAttemptLabel": "Next",
"rules.ruleEvents.numAttemptsLabel": "Attempts",
"rules.ruleEvents.reloaded": "RuleEvents reloaded.",
"rules.ruleSyntax.if": "If",
"rules.ruleSyntax.then": "then",
"rules.run": "Run",
"rules.runFailed": "Failed to run rule. Please reload.",
"rules.runningRule": "Rule '{name}' is currently running.",
"rules.runRuleConfirmText": "Do you really want to run the rule for all events?",
"rules.runRuleConfirmTitle": "Run rule",
"rules.stop": "Rule will stop soon.",
"rules.triggerConfirmText": "Do you really want to trigger the rule?",
"rules.triggerConfirmTitle": "Trigger rule",
"rules.triggerEdit": "Edit Trigger",
"rules.triggerFailed": "Failed to trigger rule. Please reload.",
"rules.unnamed": "Unnamed Rule",
"rules.updateFailed": "Failed to update rule. Please reload.",
"rules.wizard.actionHint": "The selection of the action type cannot be changed later.",
"rules.wizard.selectAction": "Select Action",
"rules.wizard.selectTrigger": "Select Trigger",
"rules.wizard.triggerHint": "The selection of the trigger type cannot be changed later.",
"schemas.addField": "Add Field",
"schemas.addFieldAndClose": "Create and close",
"schemas.addFieldAndCreate": "Create and add field",
"schemas.addFieldAndEdit": "Create and edit field",
"schemas.addFieldFailed": "Failed to add field. Please reload.",
"schemas.addNestedField": "Add Nested Field",
"schemas.changeCategoryFailed": "Failed to change category. Please reload.",
"schemas.clone": "Clone Schema",
"schemas.contextMenuTour": "Open the context menu to delete the schema or to create some scripts for content changes.",
"schemas.create": "Create Schema",
"schemas.createCategory": "Create new category...",
"schemas.createFailed": "Failed to create schema. Please reload.",
"schemas.createSchemaTooltip": "New Schema (CTRL + SHIFT + G)",
"schemas.deleteConfirmText": "Do you really want to delete the schema?",
"schemas.deleteConfirmTitle": "Delete schema",
"schemas.deleteFailed": "Failed to delete schema. Please reload.",
"schemas.deleteFieldFailed": "Failed to delete field. Please reload.",
"schemas.deleteRuleConfirmText": "Do you really want to remove this rule?",
"schemas.deleteRuleConfirmTitle": "Remove rule",
"schemas.deleteUrlConfirmText": "Do you really want to remove this URL?",
"schemas.deleteUrlConfirmTitle": "Remove URL",
"schemas.disableFieldFailed": "Failed to disable field. Please reload.",
"schemas.enableFieldFailed": "Failed to enable field. Please reload.",
"schemas.export.deleteFields": "Delete fields",
"schemas.export.recreateFields": "Recreate fields",
"schemas.export.synchronize": "Synchronize",
"schemas.field.allowedValues": "Allowed Values",
"schemas.field.deleteConfirmText": "Do you really want to delete the field?",
"schemas.field.deleteConfirmTitle": "Delete field",
"schemas.field.disable": "Disable in UI",
"schemas.field.disabledMarker": "Disabled",
"schemas.field.editorUrl": "Editor Url",
"schemas.field.editorUrlHint": "Url to your plugin if you use a custom editor.",
"schemas.field.enable": "Enable in UI",
"schemas.field.enabledMarker": "Enabled",
"schemas.field.hide": "Hide in API",
"schemas.field.labelHint": "Describe this field for documentation and user interfaces.",
"schemas.field.localizable": "Localizable",
"schemas.field.localizableHint": "You can mark the field as localizable. It means that is dependent on the language, for example a city name.",
"schemas.field.localizableMarker": "localizable",
"schemas.field.lock": "Lock and prevent changes",
"schemas.field.lockConfirmText": "WARNING: Locking a field cannot be undone! Locked field definitions cannot be unlocked, deleted, or changed anymore. Do you really want to lock this field?",
"schemas.field.lockedMarker": "Locked",
"schemas.field.nameHint": "The name of the field in the API response.",
"schemas.field.namePlaceholder": "Enter field name",
"schemas.field.nameValidationMessage": "Name must be a valid javascript name in camel case.",
"schemas.field.required": "Required",
"schemas.field.show": "Show in API",
"schemas.field.tabCommon": "Common",
"schemas.field.tabUI": "UI",
"schemas.field.tabValidation": "Validation",
"schemas.field.tagsHint": "Tags to annotate your field for automation processes.",
"schemas.field.unique": "Unique",
"schemas.fields.defaultValue": "Default Value",
"schemas.fields.editor": "Editor",
"schemas.fields.empty": "No field created yet.",
"schemas.fields.inlineEditable": "Inline Editable",
"schemas.fields.placeholder": "Placeholder",
"schemas.fields.placeholderHint": "Define the placeholder for the input control.",
"schemas.fieldTypes.array.count": "Items",
"schemas.fieldTypes.array.countMax": "Max Items",
"schemas.fieldTypes.array.countMin": "Min Items",
"schemas.fieldTypes.array.description": "List of embedded objects.",
"schemas.fieldTypes.assets.allowDuplicates": "Allow duplicate values",
"schemas.fieldTypes.assets.count": "Count",
"schemas.fieldTypes.assets.countMax": "Max Assets",
"schemas.fieldTypes.assets.countMin": "Min Assets",
"schemas.fieldTypes.assets.description": "Images, videos, documents.",
"schemas.fieldTypes.assets.fileExtensions": "File Extensions",
"schemas.fieldTypes.assets.mustBeImage": "Must be Image",
"schemas.fieldTypes.assets.previewMode": "PreviewMode",
"schemas.fieldTypes.assets.previewModeHint": "The preview mode for assets in content lists.",
"schemas.fieldTypes.assets.previewModeName": "Only file name",
"schemas.fieldTypes.assets.previewModeTumbnailName": "Thumbnail and file name",
"schemas.fieldTypes.assets.previewModeTumbnailOrName": "Only thumbnail or file name if not an image",
"schemas.fieldTypes.assets.resolve": "Resolve first asset",
"schemas.fieldTypes.assets.resolveHint": "Show the first referenced asset in the content list.",
"schemas.fieldTypes.assets.size": "Size",
"schemas.fieldTypes.assets.sizeMax": "Min Size",
"schemas.fieldTypes.assets.sizeMin": "Max Size",
"schemas.fieldTypes.boolean.description": "Yes or no, true or false.",
"schemas.fieldTypes.dateTime.defaultMode": "Default Mode",
"schemas.fieldTypes.dateTime.description": "Events date, opening hours.",
"schemas.fieldTypes.dateTime.rangeMax": "Max Value",
"schemas.fieldTypes.dateTime.rangeMin": "Min Value",
"schemas.fieldTypes.geolocation.description": "Coordinates: latitude and longitude.",
"schemas.fieldTypes.json.description": "Data in JSON format, for developers.",
"schemas.fieldTypes.number.description": "ID, order number, rating, quantity.",
"schemas.fieldTypes.number.range": "Range",
"schemas.fieldTypes.number.rangeMax": "Max Value",
"schemas.fieldTypes.number.rangeMin": "Min Value",
"schemas.fieldTypes.references.count": "Items",
"schemas.fieldTypes.references.countMax": "Max Items",
"schemas.fieldTypes.references.countMin": "Min Items",
"schemas.fieldTypes.references.description": "Links to other content items.",
"schemas.fieldTypes.references.resolveHint": "Show the name of the referenced item in content list when MaxItems is set to 1.",
"schemas.fieldTypes.string.description": "Titles, names, paragraphs.",
"schemas.fieldTypes.string.length": "Length",
"schemas.fieldTypes.string.lengthMax": "Max Length",
"schemas.fieldTypes.string.lengthMin": "Min Length",
"schemas.fieldTypes.string.pattern": "Regex Pattern",
"schemas.fieldTypes.string.patternMessage": "Pattern Message",
"schemas.fieldTypes.string.suggestions": "Suggestions",
"schemas.fieldTypes.tags.count": "Items",
"schemas.fieldTypes.tags.countMax": "Max Items",
"schemas.fieldTypes.tags.countMin": "Min Items",
"schemas.fieldTypes.tags.description": "Special format for tags.",
"schemas.fieldTypes.ui.description": "Separator for editing UI.",
"schemas.hideFieldFailed": "Failed to hide field. Please reload.",
"schemas.import": "Import schema",
"schemas.labelHint": "Display name for documentation and user interfaces.",
"schemas.listFields": "List Fields",
"schemas.listFieldsEmpty": "Drop field here or reorder them to show the fields in the content list. When no list field is defined, the first field is used.",
"schemas.loadFailed": "Failed to load schemas. Please reload.",
"schemas.loadSchemaFailed": "Failed to load schema. Please reload.",
"schemas.lockFieldFailed": "Failed to lock field. Please reload.",
"schemas.modeMultiple": "Multiple contents",
"schemas.modeMultipleDescription": "Best for multiple instances like blog posts, pages, authors, products...",
"schemas.modeSingle": "Single content",
"schemas.modeSingleDescription": "Best for single instances like the home page, privacy policies, settings...",
"schemas.nameWarning": "These values cannot be changed later.",
"schemas.previewUrls.empty": "No preview urls configured.",
"schemas.previewUrls.help": "Checkout the integrated help page to learn more about preview URL's.",
"schemas.previewUrls.namePlaceholder": "Web or Mobile",
"schemas.previewUrls.title": "Preview URLs",
"schemas.previewUrls.urlPlaceholder": "URL with variables",
"schemas.published": "Published",
"schemas.publishedTour": "Note, that you have to publish the schema before you can add content to it.",
"schemas.publishFailed": "Failed to publish schema. Please reload.",
"schemas.referenceFields": "Reference Fields",
"schemas.referenceFieldsEmpty": "Drop field here or reorder them to show the fields when referenced by another content. When no reference field is defined, the list fields are used instead.",
"schemas.reloaded": "Schemas reloaded.",
"schemas.reorderFieldsFailed": "Failed to reorder fields. Please reload.",
"schemas.rules.condition": "Condition in Javascript",
"schemas.rules.disable": "Disable",
"schemas.rules.empty": "No field rules configured.",
"schemas.rules.require": "Require",
"schemas.rules.title": "Field rules",
"schemas.rules.when": "when",
"schemas.saved": "Schema saved successfully.",
"schemas.saveFieldAndClose": "Save and close",
"schemas.saveFieldAndNew": "Save and add field",
"schemas.schemaNameHint": "You can only use letters, numbers and dashes and not more than 40 characters.",
"schemas.schemaNameValidationMessage": "Name can only contain letters, numbers, dashes and spaces.",
"schemas.schemaTagsHint": "Tags to annotate your schema for automation processes.",
"schemas.searchPlaceholder": "Search for schemas...",
"schemas.showFieldFailed": "Failed to show field. Please reload.",
"schemas.synchronized": "Schema synchronized successfully.",
"schemas.synchronizeFailed": "Failed to synchronize schema. Please reload.",
"schemas.ui": "Assigned fields",
"schemas.ui.unassignedFields": "Unassigned fields",
"schemas.unpublished": "Unpublished",
"schemas.unpublishFailed": "Failed to unpublish schema. Please reload.",
"schemas.updateFailed": "Failed to update schema. Please reload.",
"schemas.updateFieldFailed": "Failed to update field. Please reload.",
"schemas.updatePreviewUrlsFailed": "Failed to configure preview urls. Please reload.",
"schemas.updateRulesFailed": "Failed to update schema rules. Please reload.",
"schemas.updateScriptsFailed": "Failed to update schema scripts. Please reload.",
"schemas.updateUIFieldsFailed": "Failed to update UI fields. Please reload.",
"search.addFilter": "Add Filter",
"search.addGroup": "Add Group",
"search.addSorting": "Add Sorting",
"search.advancedTour": "Click this icon to show the advanced search menu!",
"search.customQuery": "Custom Query",
"search.fullTextTour": "Search for content using full text search over all fields and languages!",
"search.help": "Read more about filtering in the [Documentation](https://https://docs.squidex.io/04-guides/02-api.html).",
"search.myQueries": "My queries",
"search.nameQuery": "Name your query",
"search.queriesEmpty": "Search for {types} and use <i class=\"icon-star-empty\"></i> icon in search form to save query for all contributors.",
"search.queryAllNewestFirst": "All (newest first)",
"search.queryAllOldestFirst": "All (oldest first)",
"search.quickNavPlaceholder": "Quick Nav (Press 'q')",
"search.saveQueryMyself": "Save the query only for myself.",
"search.searchFailed": "Failed to make search. Please reload.",
"search.sharedQueries": "Shared queries",
"search.sorting": "Sorting",
"start.login": "Login to Squidex",
"start.loginHint": "The login button will open a new popup. Once you are logged in successful we will redirect you to the Squidex management portal.",
"start.madeBy": "Proudly made by",
"start.madeByCopyright": "Qaisar Ahmad &amp; Sebastian Stehle, 2016-2020",
"tour.joinForum": "Join our Forum",
"tour.joinGithub": "Join us on Github",
"tour.skip": "Skip Tour",
"tour.step0Next": "Let's take a look around",
"tour.step0Text": "You can start managing and distributing your content right away, but we we'd like to walk you through some basics first...\n\nHow'that",
"tour.step1Next": "Continue",
"tour.step1Text": "An App is the repository for your project, e.g. (blog, web shop or mobile app). You can assign contributors to your app to work together.\n\nYou can create an unlimited number of Apps in Squidex to manage multiple projects at the same time.",
"tour.step2Next": "Keep going!",
"tour.step2Text": "Schemas define the structure of your content, the fields and the data types of a content item.\n\nBefore you can add content to your schema, make sure to hit the 'Publish' button at the top to make the schema available to your content editors.",
"tour.step3Next": "Almost there!",
"tour.step3Text": "Content is the actual data in your app which is grouped by the schema.\n\nSelect a published schema first, then add content for this schema.",
"tour.step4Next": "Got It!",
"tour.step4Text": "The assets contains all files that can also be linked to your content. For example images, videos or documents.\n\nYou can upload the assets here and use them later or also upload them directly when you create a new content item with an asset field.",
"tour.step5Text": "But that's not all of the support we can provide.\n\nYou can got to https://docs.squidex.io/> to read more.\n\nDo you want to join our community?",
"tour.step5Title": "Awesome, now you know the basics!",
"tour.tooltipConfirm": "Got It",
"tour.tooltipStop": "Stop Tour",
"tour.welcome": "Welcome to",
"tour.welcomeProduct": "Squidex CMS",
"translate.translateFailed": "Failed to translate text. Please reload.",
"usages.loadCallsFailed": "Failed to load calls usage. Please reload.",
"usages.loadMonthlyCallsFailed": "Failed to load monthly API calls. Please reload.",
"usages.loadStorageFailed": "Failed to load storage usage. Please reload.",
"usages.loadTodayStorageFailed": "Failed to load todays storage size. Please reload.",
"users.create": "New",
"users.createFailed": "Failed to create user. Please reload.",
"users.createPageTitle": "Create User",
"users.createTitle": "New User",
"users.createTooltip": "New User (CTRL + N)",
"users.editPageTitle": "Edit User",
"users.editTitle": "Edit User",
"users.listPageTitle": "User Management",
"users.listTitle": "Users",
"users.loadFailed": "Failed to load users. Please reload.",
"users.loadUserFailed": "Failed to load user. Please reload.",
"users.lockTooltip": "Lock User",
"users.passwordConfirmValidationMessage": "Passwords must be the same.",
"users.refreshTooltip": "Refresh Users (CTRL + SHIFT + R)",
"users.reloaded": "Users reloaded.",
"users.search": "Search for user",
"users.unlockTooltip": "Unlock User",
"users.updateFailed": "Failed to update user. Please reload.",
"validation.between": "{field} must be between '{min}' and '{max}'.",
"validation.betweenlength": "{field|upper} must have between {minlength} and {maxlength} item(s).",
"validation.betweenlengthstring": "{field|upper} must have between {minlength} and {maxlength} character(s).",
"validation.email": "{field|upper} must be an email address.",
"validation.exactly": "{field|upper} must be exactly '{expected}'.",
"validation.exactlylength": "{field|upper} must have exactly {expected} item(s).",
"validation.exactlylengthstring": "{field|upper} must have exactly {expected} character(s).",
"validation.match": "{message}",
"validation.max": "{field|upper} must be less or equal to '{max}'.",
"validation.maxlength": "{field|upper} must not have more than {requiredlength} item(s).",
"validation.maxlengthstring": "{field|upper} must not have more than {requiredlength} character(s).",
"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.pattern": "{field|upper} does not match to the pattern.",
"validation.patternmessage": "{message}",
"validation.required": "{field|upper} is required.",
"validation.requiredTrue": "{field|upper} is required.",
"validation.uniquestrings": "{field|upper} must not contain duplicate values.",
"validation.validarrayvalues": "{field|upper} contains an invalid value: {invalidvalue}.",
"validation.validdatetime": "{field|upper} is not a valid date time.",
"validation.validvalues": "{field|upper} is not a valid value.",
"workflows.add": "Add Workflow",
"workflows.addStep": "Add Step",
"workflows.createFailed": "Failed to create workflow. Please reload.",
"workflows.deleteConfirmText": "Do you really want to remove the workflow?",
"workflows.deleteConfirmTitle": "Delete workflow",
"workflows.deleteFailed": "Failed to delete Workflow. Please reload.",
"workflows.empty": "No workflows created yet.",
"workflows.loadFailed": "Failed to load workflows. Please reload.",
"workflows.notNamed": "Unnamed Workflow",
"workflows.preventUpdates": "Prevent updates",
"workflows.publishedNotRemovable": "Cannot be removed",
"workflows.refreshTooltip": "Refresh workflows (CTRL + SHIFT + R)",
"workflows.reloaded": "Workflows reloaded.",
"workflows.saved": "Workflow has been saved.",
"workflows.schemasHint": "Restrict this workflow to specific schemas or keep it empty for all schemas.",
"workflows.syntax.expression": "Expression",
"workflows.syntax.for": "for",
"workflows.syntax.when": "when",
"workflows.tabEdit": "Editing",
"workflows.tabVisualize": "Visualize",
"workflows.updateFailed": "Failed to update Workflow. Please reload.",
"workflows.workflowNameHint": "Optional name for the workflow.",
"workflows.workflowNamePlaceholder": "Enter workflow name"
}

2
backend/src/Squidex/Config/Web/WebExtensions.cs

@ -35,7 +35,7 @@ namespace Squidex.Config.Web
public static IApplicationBuilder UseSquidexLocalization(this IApplicationBuilder app)
{
var supportedCultures = new[] { "en-US" };
var supportedCultures = new[] { "en" };
var localizationOptions = new RequestLocalizationOptions()
.SetDefaultCulture(supportedCultures[0])

13
backend/src/Squidex/Squidex.csproj

@ -86,6 +86,7 @@
<ItemGroup>
<EmbeddedResource Include="Areas\Api\Controllers\Users\Assets\Avatar.png" />
<EmbeddedResource Include="Areas\Frontend\Resources\texts.en" />
<EmbeddedResource Include="Docs\schemabody.md" />
<EmbeddedResource Include="Docs\schemaquery.md" />
<EmbeddedResource Include="Docs\security.md" />
@ -94,19 +95,15 @@
<EmbeddedResource Include="Pipeline\Squid\icon-sad-sm.svg" />
<EmbeddedResource Include="Pipeline\Squid\icon-sad.svg" />
<EmbeddedResource Remove="Assets\**" />
<EmbeddedResource Remove="_test-output\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Assets\**" />
</ItemGroup>
<ItemGroup>
<Content Remove="Assets\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Areas\Api\Controllers\Users\Assets\Avatar.png" />
<None Remove="Areas\Frontend\Resources\texts.en" />
<None Remove="Docs\schemabody.md" />
<None Remove="Docs\schemaquery.md" />
<None Remove="Docs\security.md" />
@ -120,9 +117,11 @@
<None Remove="Assets\**" />
</ItemGroup>
<ItemGroup>
<Content Remove="Assets\**" />
</ItemGroup>
<PropertyGroup>
<NoWarn>$(NoWarn);CS1591;1591;1573;1572;NU1605;IDE0060</NoWarn>
</PropertyGroup>
<ProjectExtensions><VisualStudio><UserProperties appsettings_1development_1json__JsonSchema="http://json.schemastore.org/BizTalkServerApplicationSchema" /></VisualStudio></ProjectExtensions>
</Project>

25
backend/tests/Squidex.Infrastructure.Tests/Translations/TTests.cs

@ -22,42 +22,41 @@ namespace Squidex.Infrastructure.Translations
[Fact]
public void Should_return_key_if_not_found()
{
var (result, notFound) = sut.Get(CultureInfo.CurrentUICulture, "key", "fallback");
var result = sut.Get(CultureInfo.CurrentUICulture, "key", "fallback");
Assert.Equal("fallback", result);
Assert.True(notFound);
Assert.Equal(("fallback", false), result);
}
[Fact]
public void Should_return_simple_key()
{
var (result, _) = sut.Get(CultureInfo.CurrentUICulture, "simple", "fallback");
var result = sut.Get(CultureInfo.CurrentUICulture, "simple", "fallback");
Assert.Equal("Simple Result", result);
Assert.Equal(("Simple Result", true), result);
}
[Fact]
public void Should_return_text_with_variable()
{
var (result, _) = sut.Get(CultureInfo.CurrentUICulture, "withVar", "fallback", new { var = 5 });
var result = sut.Get(CultureInfo.CurrentUICulture, "withVar", "fallback", new { var = 5 });
Assert.Equal("Var: 5.", result);
Assert.Equal(("Var: 5.", true), result);
}
[Fact]
public void Should_return_text_with_lower_var()
public void Should_return_text_with_lower_variable()
{
var (result, _) = sut.Get(CultureInfo.CurrentUICulture, "withLowerVar", "fallback", new { var = "Lower" });
var result = sut.Get(CultureInfo.CurrentUICulture, "withLowerVar", "fallback", new { var = "Lower" });
Assert.Equal("Var: lower.", result);
Assert.Equal(("Var: lower.", true), result);
}
[Fact]
public void Should_return_text_with_upper_var()
public void Should_return_text_with_upper_variable()
{
var (result, _) = sut.Get(CultureInfo.CurrentUICulture, "withUpperVar", "fallback", new { var = "upper" });
var result = sut.Get(CultureInfo.CurrentUICulture, "withUpperVar", "fallback", new { var = "upper" });
Assert.Equal("Var: Upper.", result);
Assert.Equal(("Var: Upper.", true), result);
}
}
}

6
frontend/app/app.component.html

@ -1,11 +1,11 @@
<main>
<main>
<sqx-root-view>
<sqx-dialog-renderer>
<router-outlet (activate)="isLoaded = true">
<div class="loading" *ngIf="!isLoaded">
<img alt="Loading" src="./images/loader.gif" />
<img alt="Loading" src="./images/loader.gif">
<div>Loading Squidex</div>
<div>{{ 'common.loading' | sqxTranslate }}</div>
</div>
</router-outlet>
</sqx-dialog-renderer>

13
frontend/app/app.module.ts

@ -14,7 +14,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { routing } from './app.routes';
import { ApiUrlConfig, CurrencyConfig, DecimalSeparatorConfig, SqxFrameworkModule, SqxSharedModule, TitlesConfig, UIOptions } from './shared';
import { ApiUrlConfig, CurrencyConfig, DecimalSeparatorConfig, LocalizerService, SqxFrameworkModule, SqxSharedModule, TitlesConfig, UIOptions } from './shared';
import { SqxShellModule } from './shell';
export function configApiUrl() {
@ -42,7 +42,7 @@ export function configUIOptions() {
}
export function configTitles() {
return new TitlesConfig(undefined, 'Squidex Headless CMS');
return new TitlesConfig(undefined, 'i18n:common.product');
}
export function configDecimalSeparator() {
@ -53,6 +53,14 @@ export function configCurrency() {
return new CurrencyConfig('EUR', '€', true);
}
export function configTranslations() {
if (process.env.NODE_ENV === 'production') {
return new LocalizerService(window['texts']);
} else {
return new LocalizerService(require('./../../i18n/translations-frontend.en.json')).logMissingKeys();
}
}
@NgModule({
imports: [
BrowserAnimationsModule,
@ -75,6 +83,7 @@ export function configCurrency() {
{ provide: CurrencyConfig, useFactory: configCurrency },
{ provide: DecimalSeparatorConfig, useFactory: configDecimalSeparator },
{ provide: TitlesConfig, useFactory: configTitles },
{ provide: LocalizerService, useFactory: configTranslations },
{ provide: UIOptions, useFactory: configUIOptions }
],
entryComponents: [AppComponent]

10
frontend/app/features/administration/administration-area.component.html

@ -1,25 +1,25 @@
<sqx-title message="Administration"></sqx-title>
<sqx-title message="i18n:common.administrationPageTitle"></sqx-title>
<div class="sidebar">
<ul class="nav nav-panel flex-column">
<li class="nav-item" *ngIf="uiState.canReadEvents | async">
<a class="nav-link" routerLink="event-consumers" routerLinkActive="active">
<i class="nav-icon icon-time"></i> <div class="nav-text">Consumers</div>
<i class="nav-icon icon-time"></i> <div class="nav-text">{{ 'common.consumers' | sqxTranslate }}</div>
</a>
</li>
<li class="nav-item" *ngIf="uiState.canReadUsers | async">
<a class="nav-link" routerLink="users" routerLinkActive="active">
<i class="nav-icon icon-user-o"></i> <div class="nav-text">Users</div>
<i class="nav-icon icon-user-o"></i> <div class="nav-text">{{ 'common.users' | sqxTranslate }}</div>
</a>
</li>
<li class="nav-item" *ngIf="uiState.canRestore | async">
<a class="nav-link" routerLink="restore" routerLinkActive="active">
<i class="nav-icon icon-backup"></i> <div class="nav-text">Restore</div>
<i class="nav-icon icon-backup"></i> <div class="nav-text">{{ 'common.restore' | sqxTranslate }}</div>
</a>
</li>
<li class="nav-item" *ngIf="uiState.canUseOrleans | async">
<a class="nav-link" routerLink="cluster" routerLinkActive="active">
<i class="nav-icon icon-orleans"></i> <div class="nav-text">Cluster</div>
<i class="nav-icon icon-orleans"></i> <div class="nav-text">{{ 'common.cluster' | sqxTranslate }}</div>
</a>
</li>
</ul>

2
frontend/app/features/administration/pages/cluster/cluster-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="Cluster"></sqx-title>
<sqx-title message="i18n:common.clusterPageTitle"></sqx-title>
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true">
<div inner>

6
frontend/app/features/administration/pages/event-consumers/event-consumer.component.html

@ -10,13 +10,13 @@
<span>{{eventConsumer.position}}</span>
</td>
<td class="cell-actions-lg">
<button type="button" class="btn btn-text" (click)="reset()" *ngIf="eventConsumer.canReset" title="Reset Event Consumer">
<button type="button" class="btn btn-text" (click)="reset()" *ngIf="eventConsumer.canReset" title="i18n:eventConsumers.resetTooltip">
<i class="icon icon-reset"></i>
</button>
<button type="button" class="btn btn-text" (click)="start()" *ngIf="eventConsumer.canStart" title="Start Event Consumer">
<button type="button" class="btn btn-text" (click)="start()" *ngIf="eventConsumer.canStart" title="i18n:eventConsumers.startTooltip">
<i class="icon icon-play"></i>
</button>
<button type="button" class="btn btn-text" (click)="stop()" *ngIf="eventConsumer.canStop" title="Stop Event Consumer">
<button type="button" class="btn btn-text" (click)="stop()" *ngIf="eventConsumer.canStop" title="i18n:eventConsumers.stopTooltip">
<i class="icon icon-pause"></i>
</button>
</td>

16
frontend/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -1,13 +1,13 @@
<sqx-title message="Event Consumers"></sqx-title>
<sqx-title message="i18n:eventConsumers.pageTitle"></sqx-title>
<sqx-panel theme="light" desiredWidth="50rem" grid="true">
<ng-container title>
Consumers
{{ 'common.consumers' | sqxTranslate }}
</ng-container>
<ng-container menu>
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="Refresh event consumers (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="i18n:eventConsumers.refreshTooltip">
<i class="icon-reset"></i> {{ 'common.refresh' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
@ -20,13 +20,13 @@
<thead>
<tr>
<th class="cell-auto">
Name
{{ 'common.name' | sqxTranslate }}
</th>
<th class="cell-auto-right">
Position
{{ 'eventConsumers.position' | sqxTranslate }}
</th>
<th class="cell-actions-lg">
Actions
{{ 'common.actions' | sqxTranslate }}
</th>
</tr>
</thead>
@ -47,7 +47,7 @@
<ng-container *sqxModal="eventConsumerErrorDialog">
<sqx-modal-dialog (close)="eventConsumerErrorDialog.hide()">
<ng-container title>
Error
{{ 'common.error' | sqxTranslate }}
</ng-container>
<ng-container content>

16
frontend/app/features/administration/pages/restore/restore-page.component.html

@ -1,8 +1,8 @@
<sqx-title message="Restore Backup"></sqx-title>
<sqx-title message="i18n:backups.restorePageTitle"></sqx-title>
<sqx-panel theme="light" desiredWidth="70rem">
<ng-container title>
Restore Backup
{{ 'backups.restoreTitle' | sqxTranslate }}
</ng-container>
<ng-container content>
@ -22,7 +22,7 @@
</div>
<div class="col">
<h3>Last Restore Operation</h3>
<h3>{{ 'backups.restoreLastStatus' | sqxTranslate }}</h3>
</div>
<div class="col text-right restore-url">
@ -38,10 +38,10 @@
<div class="card-footer text-muted">
<div class="row">
<div class="col">
Started: {{job.started | sqxISODate}}
{{ 'backups.restoreStartedLabel' | sqxTranslate }}: {{job.started | sqxISODate}}
</div>
<div class="col text-right" *ngIf="job.stopped">
Stopped: {{job.stopped | sqxISODate}}
{{ 'backups.restoreStoppedLabel' | sqxTranslate }}: {{job.stopped | sqxISODate}}
</div>
</div>
</div>
@ -51,13 +51,13 @@
<form [formGroup]="restoreForm.form" (ngSubmit)="restore()">
<div class="row no-gutters">
<div class="col">
<input class="form-control" formControlName="url" placeholder="Url to backup" />
<input class="form-control" formControlName="url" placeholder="{{ 'backups.restoreLastUrl' | sqxTranslate }}">
</div>
<div class="col pl-1">
<input class="form-control" formControlName="name" placeholder="Optional app name" />
<input class="form-control" formControlName="name" placeholder="{{ 'backups.restoreNewAppName' | sqxTranslate }}">
</div>
<div class="col-auto pl-1">
<button type="submit" class="btn btn-success" [disabled]="restoreForm.hasNoUrl | async">Restore Backup</button>
<button type="submit" class="btn btn-success" [disabled]="restoreForm.hasNoUrl | async">{{ 'backups.restore' | sqxTranslate }}</button>
</div>
</div>
</form>

2
frontend/app/features/administration/pages/restore/restore-page.component.ts

@ -37,7 +37,7 @@ export class RestorePageComponent {
this.backupsService.postRestore(value)
.subscribe(() => {
this.dialogs.notifyInfo('Restore started, it can take several minutes to complete.');
this.dialogs.notifyInfo('i18n:backups.restoreStarted');
}, error => {
this.dialogs.notifyError(error);
});

38
frontend/app/features/administration/pages/users/user-page.component.html

@ -1,27 +1,27 @@
<form [formGroup]="userForm.form" (ngSubmit)="save()">
<input style="display:none" type="password" name="foilautofill"/>
<input style="display:none" type="password" name="foilautofill">
<sqx-panel desiredWidth="26rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
<ng-container *ngIf="usersState.selectedUser | async; else noUserTitle">
<sqx-title message="Edit User"></sqx-title>
<sqx-title message="i18n:users.editPageTitle"></sqx-title>
Edit User
{{ 'users.editTitle' | sqxTranslate }}
</ng-container>
<ng-template #noUserTitle>
<sqx-title message="New User"></sqx-title>
<sqx-title message="i18n:users.createPageTitle"></sqx-title>
New User
{{ 'users.createTitle' | sqxTranslate }}
</ng-template>
</ng-container>
<ng-container menu>
<ng-container *ngIf="usersState.selectedUser | async; let user; else noUserMenu">
<ng-container *ngIf="isEditable">
<button type="submit" class="btn btn-primary" title="CTRL + S">
Save
<button type="submit" class="btn btn-primary" title="i18n:common.saveShortcut">
{{ 'common.save' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+s" (trigger)="save()"></sqx-shortcut>
@ -29,8 +29,8 @@
</ng-container>
<ng-template #noUserMenu>
<button type="submit" class="btn btn-primary" title="CTRL + S">
Save
<button type="submit" class="btn btn-primary" title="i18n:common.saveShortcut">
{{ 'common.save' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+s" (trigger)="save()"></sqx-shortcut>
@ -41,44 +41,44 @@
<sqx-form-error [error]="userForm.error | async"></sqx-form-error>
<div class="form-group">
<label for="email">Email <small class="hint">(required)</small></label>
<label for="email">{{ 'common.email' | sqxTranslate }} <small class="hint">({{ 'common.requiredHint' | sqxTranslate }})</small></label>
<sqx-control-errors for="email"></sqx-control-errors>
<input type="email" class="form-control" id="email" maxlength="100" formControlName="email" autocomplete="off" />
<input type="email" class="form-control" id="email" maxlength="100" formControlName="email" autocomplete="off">
</div>
<div class="form-group">
<label for="displayName">Display Name <small class="hint">(required)</small></label>
<label for="displayName">{{ 'common.displayName' | sqxTranslate }} <small class="hint">({{ 'common.requiredHint' | sqxTranslate }})</small></label>
<sqx-control-errors for="displayName"></sqx-control-errors>
<input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="off" spellcheck="false" />
<input type="text" class="form-control" id="displayName" maxlength="100" formControlName="displayName" autocomplete="off" spellcheck="false">
</div>
<div class="form-group form-group-section">
<div class="form-group">
<label for="password">Password</label>
<label for="password">{{ 'common.password' | sqxTranslate }}</label>
<sqx-control-errors for="password"></sqx-control-errors>
<input type="password" class="form-control" id="password" maxlength="100" formControlName="password" autocomplete="off" />
<input type="password" class="form-control" id="password" maxlength="100" formControlName="password" autocomplete="off">
</div>
<div class="form-group">
<label for="password">Confirm Password</label>
<label for="password">{{ 'common.passwordConfirm' | sqxTranslate }}</label>
<sqx-control-errors for="passwordConfirm"></sqx-control-errors>
<input type="password" class="form-control" id="passwordConfirm" maxlength="100" formControlName="passwordConfirm" autocomplete="off" />
<input type="password" class="form-control" id="passwordConfirm" maxlength="100" formControlName="passwordConfirm" autocomplete="off">
</div>
</div>
<div class="form-group form-group-section">
<label for="permissions">Permissions</label>
<label for="permissions">{{ 'common.permissions' | sqxTranslate }}</label>
<sqx-control-errors for="permissions"></sqx-control-errors>
<textarea class="form-control" id="permissions" formControlName="permissions" placeholder="Separate by line" autocomplete="off" spellcheck="false"></textarea>
<textarea class="form-control" id="permissions" formControlName="permissions" placeholder="{{ 'common.separateByLine' | sqxTranslate }}" autocomplete="off" spellcheck="false"></textarea>
</div>
</ng-container>
</sqx-panel>

6
frontend/app/features/administration/pages/users/user.component.html

@ -1,6 +1,6 @@
<tr [routerLink]="user.id" queryParamsHandling="merge" routerLinkActive="active">
<td class="cell-user">
<img class="user-picture" title="{{user.displayName}}" [src]="user | sqxUserDtoPicture" />
<img class="user-picture" title="{{user.displayName}}" [src]="user | sqxUserDtoPicture">
</td>
<td class="cell-auto">
<span class="user-name table-cell">{{user.displayName}}</span>
@ -9,10 +9,10 @@
<span class="user-email table-cell">{{user.email}}</span>
</td>
<td class="cell-actions">
<button type="button" class="btn btn-text" (click)="lock()" sqxStopClick *ngIf="user.canLock" title="Lock User">
<button type="button" class="btn btn-text" (click)="lock()" sqxStopClick *ngIf="user.canLock" title="i18n:users.lockTooltip">
<i class="icon icon-unlocked"></i>
</button>
<button type="button" class="btn btn-text" (click)="unlock()" sqxStopClick *ngIf="user.canUnlock" title="Unlock User">
<button type="button" class="btn btn-text" (click)="unlock()" sqxStopClick *ngIf="user.canUnlock" title="i18n:users.unlockTooltip">
<i class="icon icon-lock"></i>
</button>
</td>

20
frontend/app/features/administration/pages/users/users-page.component.html

@ -1,27 +1,27 @@
<sqx-title message="User Management"></sqx-title>
<sqx-title message="i18n:users.listPageTitle"></sqx-title>
<sqx-panel desiredWidth="50rem" grid="true">
<ng-container title>
Users
{{ 'users.listTitle' | sqxTranslate }}
</ng-container>
<ng-container menu>
<button type="button" class="btn btn-text-secondary mr-1" (click)="reload()" title="Refresh Users (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
<button type="button" class="btn btn-text-secondary mr-1" (click)="reload()" title="i18n:users.refreshTooltip">
<i class="icon-reset"></i> {{ 'common.refresh' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<form class="form-inline mr-1" (ngSubmit)="search()">
<input class="form-control" #inputFind [formControl]="usersFilter" placeholder="Search for user" />
<input class="form-control" #inputFind [formControl]="usersFilter" placeholder="{{ 'users.search' | sqxTranslate }}">
</form>
<ng-container *ngIf="usersState.canCreate | async">
<sqx-shortcut keys="ctrl+shift+n" (trigger)="buttonNew.click()"></sqx-shortcut>
<button type="button" class="btn btn-success" #buttonNew routerLink="new" title="New User (CTRL + N)">
<i class="icon-plus"></i> New
<button type="button" class="btn btn-success" #buttonNew routerLink="new" title="i18n:users.createTooltip">
<i class="icon-plus"></i> {{ 'users.create' | sqxTranslate }}
</button>
</ng-container>
</ng-container>
@ -36,13 +36,13 @@
&nbsp;
</th>
<th class="cell-auto">
Name
{{ 'common.name' | sqxTranslate }}
</th>
<th class="cell-auto">
Email
{{ 'common.email' | sqxTranslate }}
</th>
<th class="cell-actions">
Actions
{{ 'common.actions' | sqxTranslate }}
</th>
</tr>
</thead>

8
frontend/app/features/administration/services/event-consumers.service.ts

@ -60,7 +60,7 @@ export class EventConsumersService {
return new EventConsumersDto(eventConsumers, _links);
}),
pretifyError('Failed to load event consumers. Please reload.'));
pretifyError('i18n:eventConsumers.loadFailed'));
}
public putStart(eventConsumer: Resource): Observable<EventConsumerDto> {
@ -72,7 +72,7 @@ export class EventConsumersService {
map(body => {
return parseEventConsumer(body);
}),
pretifyError('Failed to start event consumer. Please reload.'));
pretifyError('i18n:eventConsumers.startFailed'));
}
public putStop(eventConsumer: Resource): Observable<EventConsumerDto> {
@ -84,7 +84,7 @@ export class EventConsumersService {
map(body => {
return parseEventConsumer(body);
}),
pretifyError('Failed to stop event consumer. Please reload.'));
pretifyError('i18n:eventConsumers.stopFailed'));
}
public putReset(eventConsumer: Resource): Observable<EventConsumerDto> {
@ -96,7 +96,7 @@ export class EventConsumersService {
map(body => {
return parseEventConsumer(body);
}),
pretifyError('Failed to reset event consumer. Please reload.'));
pretifyError('i18n:eventConsumers.resetFailed'));
}
}

12
frontend/app/features/administration/services/users.service.ts

@ -70,7 +70,7 @@ export class UsersService {
return new UsersDto(total, users, _links);
}),
pretifyError('Failed to load users. Please reload.'));
pretifyError('i18n:users.loadFailed'));
}
public getUser(id: string): Observable<UserDto> {
@ -80,7 +80,7 @@ export class UsersService {
map(body => {
return parseUser(body);
}),
pretifyError('Failed to load user. Please reload.'));
pretifyError('i18n:users.loadUserFailed'));
}
public postUser(dto: CreateUserDto): Observable<UserDto> {
@ -90,7 +90,7 @@ export class UsersService {
map(body => {
return parseUser(body);
}),
pretifyError('Failed to create user. Please reload.'));
pretifyError('i18n:users.createFailed'));
}
public putUser(user: Resource, dto: UpdateUserDto): Observable<UserDto> {
@ -102,7 +102,7 @@ export class UsersService {
map(body => {
return parseUser(body);
}),
pretifyError('Failed to update user. Please reload.'));
pretifyError('i18n:users.updateFailed'));
}
public lockUser(user: Resource): Observable<UserDto> {
@ -114,7 +114,7 @@ export class UsersService {
map(body => {
return parseUser(body);
}),
pretifyError('Failed to load users. Please retry.'));
pretifyError('i18n:users.loadFailed'));
}
public unlockUser(user: Resource): Observable<UserDto> {
@ -126,7 +126,7 @@ export class UsersService {
map(body => {
return parseUser(body);
}),
pretifyError('Failed to load users. Please retry.'));
pretifyError('i18n:users.loadFailed'));
}
}

2
frontend/app/features/administration/state/event-consumers.state.ts

@ -58,7 +58,7 @@ export class EventConsumersState extends State<Snapshot> {
return this.eventConsumersService.getEventConsumers().pipe(
tap(({ items: eventConsumers }) => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Event Consumers reloaded.');
this.dialogs.notifyInfo('i18n:eventConsumers.reloaded');
}
this.next({

2
frontend/app/features/administration/state/users.forms.ts

@ -34,7 +34,7 @@ export class UserForm extends Form<FormGroup, UpdateUserDto, UserDto> {
],
passwordConfirm: ['',
[
ValidatorsEx.match('password', 'Passwords must be the same.')
ValidatorsEx.match('password', 'i18n:users.passwordConfirmValidationMessage')
]
],
permissions: ['']

2
frontend/app/features/administration/state/users.state.ts

@ -119,7 +119,7 @@ export class UsersState extends State<Snapshot> {
this.snapshot.usersQuery).pipe(
tap(({ total, items: users, canCreate }) => {
if (isReload) {
this.dialogs.notifyInfo('Users reloaded.');
this.dialogs.notifyInfo('i18n:users.reloaded');
}
this.next(s => {

10
frontend/app/features/api/api-area.component.html

@ -1,26 +1,26 @@
<sqx-title message="API"></sqx-title>
<sqx-title message="i18n:api.pageTitle"></sqx-title>
<sqx-panel theme="dark" desiredWidth="12rem">
<ng-container title>
API
{{ 'api.title' | sqxTranslate }}
</ng-container>
<ng-container content>
<ul class="nav nav-panel nav-dark flex-column">
<li class="nav-item">
<a class="nav-link" routerLink="graphql" routerLinkActive="active">
GraphQL
{{ 'api.graphql' | sqxTranslate }}
<i class="icon-angle-right"></i>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/api/content/{{appsState.appName}}/docs" sqxExternalLink>
Content API
{{ 'api.contentApi' | sqxTranslate }}
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/api/docs" sqxExternalLink>
General API
{{ 'api.generalApi' | sqxTranslate }}
</a>
</li>
</ul>

2
frontend/app/features/api/pages/graphql/graphql-page.component.html

@ -1,4 +1,4 @@
<sqx-title message="GraphQL"></sqx-title>
<sqx-title message="i18n:api.graphqlPageTitle"></sqx-title>
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true">
<div inner #graphiQLContainer></div>

66
frontend/app/features/apps/pages/apps-page.component.html

@ -1,17 +1,17 @@
<sqx-title message="Apps"></sqx-title>
<sqx-title message="i18n:apps.listPageTitle"></sqx-title>
<div class="apps-section">
<h1 class="apps-title">Hi {{authState.user?.displayName}}</h1>
<h1 class="apps-title">{{ 'apps.welcomeTitle' | sqxTranslate: { user: authState.user?.displayName } }}</h1>
<div class="subtext">
Welcome to Squidex.
{{ 'apps.welcomeSubtitle' | sqxTranslate }}
</div>
</div>
<ng-container *ngIf="appsState.apps | async; let apps">
<div class="apps-section">
<div class="empty" *ngIf="apps.length === 0">
<h3 class="empty-headline">You are not collaborating to any app yet</h3>
<h3 class="empty-headline">{{ 'apps.empty' | sqxTranslate }}</h3>
</div>
<div class="card card-href card-app" *ngFor="let app of apps; trackBy: trackByApp" [routerLink]="['/app', app.name]">
@ -24,13 +24,13 @@
<h3 class="card-title">{{app.displayName}}</h3>
<div class="card-text card-links">
<a [routerLink]="['/app', app.name]" sqxStopClick>Edit</a>
<a [routerLink]="['/app', app.name]" sqxStopClick>{{ 'common.edit' | sqxTranslate }}</a>
<span class="deeplinks">
&nbsp;|
<a [routerLink]="['/app', app.name, 'content']" sqxStopClick>Content</a> &middot;
<a [routerLink]="['/app', app.name, 'assets']" sqxStopClick>Assets</a> &middot;
<a [routerLink]="['/app', app.name, 'settings']" sqxStopClick>Settings</a>
<a [routerLink]="['/app', app.name, 'content']" sqxStopClick>{{ 'common.content' | sqxTranslate }}</a> &middot;
<a [routerLink]="['/app', app.name, 'assets']" sqxStopClick>{{ 'common.assets' | sqxTranslate }}</a> &middot;
<a [routerLink]="['/app', app.name, 'settings']" sqxStopClick>{{ 'common.settings' | sqxTranslate }}</a>
</span>
</div>
@ -48,13 +48,13 @@
<div class="card card-template card-href" (click)="createNewApp('')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-app.svg" />
<img src="./images/add-app.svg">
</div>
<h3 class="card-title">New App</h3>
<h3 class="card-title">{{ 'apps.createBlankApp' | sqxTranslate }}</h3>
<div class="card-text">
Create a new blank app without content and schemas.
{{ 'apps.createBlankAppDescription' | sqxTranslate }}
</div>
</div>
</div>
@ -62,15 +62,15 @@
<div class="card card-template card-href" (click)="createNewApp('Blog')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-blog.svg" />
<img src="./images/add-blog.svg">
</div>
<h3 class="card-title">New Blog Sample</h3>
<h3 class="card-title">{{ 'apps.createBlogApp' | sqxTranslate }}</h3>
<div class="card-text">
<div>Start with our ready to use blog.</div>
<div>{{ 'apps.createBlogAppDescription' | sqxTranslate }}</div>
<div>
Sample Code at <a href="https://github.com/Squidex/squidex-samples" sqxStopClick sqxExternalLink>Github</a>
{{ 'common.sampleCodeLabel' | sqxTranslate }} <a href="https://github.com/Squidex/squidex-samples" sqxStopClick sqxExternalLink>{{ 'common.github' | sqxTranslate }}</a>
</div>
</div>
</div>
@ -79,15 +79,15 @@
<div class="card card-template card-href" (click)="createNewApp('Identity')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-identity.svg" />
<img src="./images/add-identity.svg">
</div>
<h3 class="card-title">New Identity App</h3>
<h3 class="card-title">{{ 'apps.createIdentityApp' | sqxTranslate }}</h3>
<div class="card-text">
<div>Create app for Squidex Identity.</div>
<div>{{ 'apps.createIdentityAppDescription' | sqxTranslate }}</div>
<div>
<a href="https://github.com/Squidex/squidex-identity" sqxStopClick sqxExternalLink>Project</a>
<a href="https://github.com/Squidex/squidex-identity" sqxStopClick sqxExternalLink>{{ 'common.project' | sqxTranslate }}</a>
</div>
</div>
</div>
@ -96,15 +96,15 @@
<div class="card card-template card-href" (click)="createNewApp('IdentityV2')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-identity.svg" />
<img src="./images/add-identity.svg">
</div>
<h3 class="card-title">New Identity App V2</h3>
<h3 class="card-title">{{ 'apps.createIdentityAppV2' | sqxTranslate }}</h3>
<div class="card-text">
<div>Create app for Squidex Identity V2.</div>
<div>{{ 'apps.createIdentityAppV2Description' | sqxTranslate }}</div>
<div>
<a href="https://github.com/Squidex/squidex-identity" sqxStopClick sqxExternalLink>Project</a>
<a href="https://github.com/Squidex/squidex-identity" sqxStopClick sqxExternalLink>{{ 'common.project' | sqxTranslate }}</a>
</div>
</div>
</div>
@ -113,15 +113,15 @@
<div class="card card-template card-href" (click)="createNewApp('Profile')">
<div class="card-body">
<div class="card-image">
<img src="./images/add-profile.svg" />
<img src="./images/add-profile.svg">
</div>
<h3 class="card-title">New Profile Sample</h3>
<h3 class="card-title">{{ 'apps.createProfileApp' | sqxTranslate }}</h3>
<div class="card-text">
<div>Create your profile page.</div>
<div>{{ 'apps.createProfileAppDescription' | sqxTranslate }}</div>
<div>
Sample Code at <a href="https://github.com/Squidex/squidex-samples" sqxStopClick sqxExternalLink>Github</a>
{{ 'common.sampleCodeLabel' | sqxTranslate }} <a href="https://github.com/Squidex/squidex-samples" sqxStopClick sqxExternalLink>{{ 'common.github' | sqxTranslate }}</a>
</div>
</div>
</div>
@ -133,19 +133,13 @@
</div>
<ng-container *sqxModal="addAppDialog">
<sqx-app-form [template]="addAppTemplate"
(complete)="addAppDialog.hide()">
</sqx-app-form>
<sqx-app-form [template]="addAppTemplate" (complete)="addAppDialog.hide()"></sqx-app-form>
</ng-container>
<ng-container *sqxModal="onboardingDialog">
<sqx-onboarding-dialog
(close)="onboardingDialog.hide()">
</sqx-onboarding-dialog>
<sqx-onboarding-dialog (close)="onboardingDialog.hide()"></sqx-onboarding-dialog>
</ng-container>
<ng-container *sqxModal="newsDialog">
<sqx-news-dialog [features]="newsFeatures"
(close)="newsDialog.hide()">
</sqx-news-dialog>
<sqx-news-dialog [features]="newsFeatures" (close)="newsDialog.hide()"></sqx-news-dialog>
</ng-container>

4
frontend/app/features/apps/pages/news-dialog.component.html

@ -1,11 +1,11 @@
<sqx-modal-dialog size="lg" (close)="close.emit()">
<ng-container title>
New Features
{{ 'news.title' | sqxTranslate }}
</ng-container>
<ng-container content>
<div class="help">
<h1>What's new?</h1>
<h1>{{ 'news.headline' | sqxTranslate }}</h1>
<div *ngFor="let feature of features; trackBy: trackByFeature">
<h4>{{feature.name}}</h4>

90
frontend/app/features/apps/pages/onboarding-dialog.component.html

@ -1,143 +1,105 @@
<sqx-modal-dialog [showHeader]="false">
<ng-container content>
<a class="header-right modal-close" (click)="close.emit()">Skip Tour</a>
<a class="header-right modal-close" (click)="close.emit()">{{ 'tour.skip' | sqxTranslate }}</a>
<div class="onboarding-step" *ngIf="step === 0">
<img @fade class="header-left" src="./images/logo-white-small.png" />
<img @fade class="header-left" src="./images/logo-white-small.png">
<div @slide class="onboarding-enter-leave">
<h1>Welcome to <span class="header-focus">Squidex CMS</span></h1>
<h1>{{ 'tour.welcome' | sqxTranslate }} <span class="header-focus">{{ 'tour.welcomeProduct' | sqxTranslate }}</span></h1>
<p>
You can start managing and distributing your content right away, but we we'd like to walk you through some basics first...
</p>
<p>
How's that?
</p>
<div [innerHTML]="'tour.step0Text' | sqxTranslate | sqxMarkdown"></div>
<button (click)="next()" class="btn btn-success">Let's take a look around</button>
<button (click)="next()" class="btn btn-success">{{ 'tour.step0Next' | sqxTranslate }}</button>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 1">
<h1 @fade class="header-left header-focus">Apps</h1>
<h1 @fade class="header-left header-focus">{{ 'common.apps' | sqxTranslate }}</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
An App is the repository for your project, e.g. (blog, web shop or mobile app). You can assign contributors to your app to work together.
</p>
<p>
You can create an unlimited number of Apps in Squidex to manage multiple projects at the same time.
</p>
</div>
<div class="onboarding-text" [innerHTML]="'tour.step1Text' | sqxTranslate | sqxMarkdown"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step1.png" />
<img src="./images/onboarding-step1.png">
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Continue</button>
<button (click)="next()" class="btn btn-success">{{ 'tour.step1Next' | sqxTranslate }}</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 2">
<h1 @fade class="header-left header-focus">Schemas</h1>
<h1 @fade class="header-left header-focus">{{ 'common.schemas' | sqxTranslate }}</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
Schemas define the structure of your content, the fields and the data types of a content item.
</p>
<p>
Before you can add content to your schema, make sure to hit the 'Publish' button at the top to make the schema available to your content editors.
</p>
</div>
<div class="onboarding-text" [innerHTML]="'tour.step2Text' | sqxTranslate | sqxMarkdown"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step2.png" />
<img src="./images/onboarding-step2.png">
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Keep going!</button>
<button (click)="next()" class="btn btn-success">{{ 'tour.step2Next' | sqxTranslate }}</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 3">
<h1 @fade class="header-left header-focus">Contents</h1>
<h1 @fade class="header-left header-focus">{{ 'common.contents' | sqxTranslate }}</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
Content is the actual data in your app which is grouped by the schema.
</p>
<p>
Select a published schema first, then add content for this schema.
</p>
</div>
<div class="onboarding-text" [innerHTML]="'tour.step3Text' | sqxTranslate | sqxMarkdown"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step3.png" />
<img src="./images/onboarding-step3.png">
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Almost there!</button>
<button (click)="next()" class="btn btn-success">{{ 'tour.step3Next' | sqxTranslate }}</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 4">
<h1 @fade class="header-left header-focus">Assets</h1>
<h1 @fade class="header-left header-focus">{{ 'common.assets' | sqxTranslate }}</h1>
<div @slide>
<div class="row">
<div class="col">
<div class="onboarding-text">
<p>
The assets contains all files that can also be linked to your content. For example images, videos or documents.
</p>
<p>
You can upload the assets here and use them later or also upload them directly when you create a new content item with an asset field.
</p>
</div>
<div class="onboarding-text" [innerHTML]="'tour.step4Text' | sqxTranslate | sqxMarkdown"></div>
</div>
<div class="col col-image">
<img src="./images/onboarding-step4.png" />
<img src="./images/onboarding-step4.png">
</div>
</div>
<div class="footer">
<button (click)="next()" class="btn btn-success">Got It!</button>
<button (click)="next()" class="btn btn-success">{{ 'tour.step4Next' | sqxTranslate }}</button>
</div>
</div>
</div>
<div class="onboarding-step" *ngIf="step === 5">
<img @fade class="header-left" src="./images/logo-white-small.png" />
<img @fade class="header-left" src="./images/logo-white-small.png">
<div @slide class="onboarding-enter-leave">
<h1>Awesome, now you know the basics!</h1>
<h1>{{ 'tour.step5Title' | sqxTranslate }}</h1>
<p>
But that's not all of the support we can provide. <br />You can go to <a href="https://docs.squidex.io/" sqxExternalLink>https://docs.squidex.io/></a> to read more.
</p>
<p>
Do you want to join our community?
</p>
<div [innerHTML]="'tour.step5Text' | sqxTranslate | sqxMarkdown"></div>
<div>
<a class="btn btn-success" href="https://support.squidex.io" sqxExternalLink>
Join our Forum
{{ 'tour.joinForum' | sqxTranslate }}
</a> &nbsp;
<a class="btn btn-success" href="https://github.com/squidex/squidex" sqxExternalLink>
Join us on Github
{{ 'tour.joinGithub' | sqxTranslate }}
</a>
</div>
</div>

2
frontend/app/features/assets/pages/asset-tags.component.html

@ -1,7 +1,7 @@
<a class="sidebar-item" (click)="reset.emit()" [class.active]="isEmpty()">
<div class="row">
<div class="col">
All tags
{{ 'common.tagsAll' | sqxTranslate }}
</div>
</div>
</a>

12
frontend/app/features/assets/pages/assets-filters-page.component.html

@ -1,21 +1,21 @@
<sqx-panel desiredWidth="20rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
Filters
{{ 'common.filters' | sqxTranslate }}
</ng-container>
<ng-container content>
<h3>Tags</h3>
<h3>{{ 'common.tags' | sqxTranslate }}</h3>
<sqx-asset-tags
(reset)="resetTags()"
<sqx-asset-tags (reset)="resetTags()"
[tags]="assetsState.tags | async"
[tagsSelected]="assetsState.tagsSelected | async"
(toggle)="toggleTag($event)">
</sqx-asset-tags>
<hr />
<hr>
<sqx-shared-queries types="contents"
<sqx-shared-queries
[types]="'common.assets' | sqxTranslate"
[queryUsed]="assetsState.assetsQuery | async"
[queries]="assetsQueries"
(search)="search($event)">

24
frontend/app/features/assets/pages/assets-page.component.html

@ -1,8 +1,8 @@
<sqx-title message="Assets"></sqx-title>
<sqx-title message="i18n:assets.listPageTitle"></sqx-title>
<sqx-panel desiredWidth="*" minWidth="50rem" showSidebar="true" grid="true">
<ng-container title>
Assets
{{ 'common.assets' | sqxTranslate }}
</ng-container>
<ng-container menu>
@ -10,14 +10,14 @@
<div class="col-auto offset-xl-2">
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="Refresh Contents (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="i18n:assets.refreshTooltip">
<i class="icon-reset"></i> {{ 'common.refresh' | sqxTranslate }}
</button>
</div>
<div class="col pl-1" style="width: 300px">
<div class="row no-gutters search">
<div class="col-6">
<sqx-tag-editor class="tags" singleLine="true" placeholder="Search by tags"
<sqx-tag-editor class="tags" singleLine="true" placeholder="{{ 'assets.searchByTags' | sqxTranslate }}"
[suggestions]="assetsState.tagsNames | async"
[ngModel]="assetsState.selectedTagNames | async"
(ngModelChange)="selectTags($event)"
@ -25,7 +25,7 @@
</sqx-tag-editor>
</div>
<div class="col-6">
<sqx-search-form formClass="form" placeholder="Search by name" fieldExample="fileSize"
<sqx-search-form formClass="form" placeholder="{{ 'assets.searchByName' | sqxTranslate }}" fieldExample="fileSize"
[query]="assetsState.assetsQuery | async"
[queries]="queries"
(queryChange)="search($event)"
@ -47,7 +47,7 @@
<div class="col-auto pl-1">
<sqx-shortcut keys="ctrl+shift+g" (trigger)="reload()"></sqx-shortcut>
<button type="button" class="btn btn-success" (click)="addAssetFolderDialog.show()" title="Create new folder (CTRL + SHIFT + G)">
<button type="button" class="btn btn-success" (click)="addAssetFolderDialog.show()" title="i18n:assets.createFolderTooltip">
<i class="icon-create_new_folder"></i>
</button>
</div>
@ -57,9 +57,7 @@
<ng-container content>
<sqx-list-view [isLoading]="assetsState.isLoading | async">
<ng-container header>
<sqx-asset-path [path]="assetsState.path | async"
(navigate)="assetsState.navigate($event)">
</sqx-asset-path>
<sqx-asset-path [path]="assetsState.path | async" (navigate)="assetsState.navigate($event)"></sqx-asset-path>
</ng-container>
<div content>
@ -74,7 +72,7 @@
<ng-container sidebar>
<div class="panel-nav">
<a class="panel-link" routerLink="filters" routerLinkActive="active" title="Filters" titlePosition="left">
<a class="panel-link" routerLink="filters" routerLinkActive="active" title="i18n:common.filters" titlePosition="left">
<i class="icon-filter"></i>
</a>
</div>
@ -82,9 +80,7 @@
</sqx-panel>
<ng-container *sqxModal="addAssetFolderDialog">
<sqx-asset-folder-dialog
(complete)="addAssetFolderDialog.hide()">
</sqx-asset-folder-dialog>
<sqx-asset-folder-dialog (complete)="addAssetFolderDialog.hide()"></sqx-asset-folder-dialog>
</ng-container>
<router-outlet></router-outlet>

6
frontend/app/features/content/pages/content/content-event.component.html

@ -1,6 +1,6 @@
<div class="event row no-gutters">
<div class="col-auto">
<img class="user-picture" title="{{event.actor | sqxUserNameRef}}" [src]="event.actor | sqxUserPictureRef" />
<img class="user-picture" title="{{event.actor | sqxUserNameRef}}" [src]="event.actor | sqxUserPictureRef">
</div>
<div class="col pl-2 event-right">
<div class="event-message">
@ -11,11 +11,11 @@
<div class="event-created">{{event.created | sqxFromNow}}</div>
<ng-container *ngIf="canLoadOrCompare">
<a class="event-load force" (click)="dataLoad.emit()">Load</a>
<a class="event-load force" (click)="dataLoad.emit()">{{ 'contents.loadContent' | sqxTranslate }}</a>
&middot;
<a class="event-load force" (click)="dataCompare.emit()">Compare</a>
<a class="event-load force" (click)="dataCompare.emit()">{{ 'contents.versionCompare' | sqxTranslate }}</a>
</ng-container>
</div>
</div>

2
frontend/app/features/content/pages/content/content-field.component.html

@ -3,7 +3,7 @@
<div class="table-items-row" [class.field-invalid]="isInvalid | async" *ngIf="!(formModel.hiddenChanges | async)">
<div class="languages-container">
<div class="languages-buttons">
<button *ngIf="canTranslate" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="translate()" title="Autotranslate from master language">
<button *ngIf="canTranslate" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="translate()" title="i18n:contents.autotranslate">
<i class="icon-translate"></i>
</button>

48
frontend/app/features/content/pages/content/content-history-page.component.html

@ -1,18 +1,18 @@
<sqx-panel desiredWidth="20rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
Workflow
{{ 'common.workflow' | sqxTranslate }}
</ng-container>
<ng-container content>
<div class="section mb-4" *ngIf="content.canDraftCreate || content.canDraftDelete">
<ng-container *ngIf="!content.newStatus; else newVersion">
<button class="btn btn-success btn-block" (click)="createDraft() ">
New Draft
{{ 'contents.draftNew' | sqxTranslate }}
</button>
</ng-container>
<ng-template #newVersion>
<label>New Version</label>
<label>{{ 'contents.draftStatus' | sqxTranslate }}</label>
<button type="button" class="btn btn-outline-secondary btn-block btn-status" (click)="dropdownNew.toggle()" [class.active]="dropdownNew.isOpen | async" #buttonOptions>
<sqx-content-status
@ -27,28 +27,26 @@
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<ng-container *ngIf="content.statusUpdates.length > 0">
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="changeStatus(info.status)">
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}}
{{ 'common.statusChangeTo' | sqxTranslate }} <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}}
</a>
<div class="dropdown-divider"></div>
</ng-container>
<a class="dropdown-item dropdown-item-delete"
[class.disabled]="!content.canDraftDelete"
<a class="dropdown-item dropdown-item-delete" [class.disabled]="!content.canDraftDelete"
(sqxConfirmClick)="deleteDraft()"
confirmTitle="Delete content"
confirmText="Do you really want to delete this version?">
Delete this Version
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteVersionConfirmText">
{{ 'contents.versionDelete' | sqxTranslate }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete"
[class.disabled]="!content.canDelete"
<a class="dropdown-item dropdown-item-delete" [class.disabled]="!content.canDelete"
(sqxConfirmClick)="delete()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the content?">
Delete
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteConfirmText">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</ng-container>
@ -56,7 +54,7 @@
</div>
<div class="section">
<label>Current Version</label>
<label>{{ 'contents.currentStatusLabel' | sqxTranslate }}</label>
<div *ngIf="!content.newStatus; else newStatusOld">
<button type="button" class="btn btn-outline-secondary btn-block btn-status" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions>
@ -72,7 +70,7 @@
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<ng-container *ngIf="content.statusUpdates.length > 0">
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="changeStatus(info.status)">
Change to
{{ 'common.statusChangeTo' | sqxTranslate }}
<sqx-content-status small="true"
layout="text"
@ -84,12 +82,11 @@
<div class="dropdown-divider"></div>
</ng-container>
<a class="dropdown-item dropdown-item-delete"
[class.disabled]="!content.canDelete"
<a class="dropdown-item dropdown-item-delete" [class.disabled]="!content.canDelete"
(sqxConfirmClick)="delete()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the content?">
Delete
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteConfirmText">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</ng-container>
@ -97,21 +94,18 @@
<ng-template #newStatusOld>
<button type="button" class="btn btn-outline-secondary btn-block btn-status">
<sqx-content-status
[status]="content.status"
[statusColor]="content.statusColor"
layout="multiline">
<sqx-content-status [status]="content.status" [statusColor]="content.statusColor" layout="multiline">
</sqx-content-status>
</button>
</ng-template>
<sqx-form-hint marginTop="1">
Last Updated: {{content.lastModified | sqxFromNow}}
{{ 'contents.lastUpdatedLabel' | sqxTranslate }}: {{content.lastModified | sqxFromNow}}
</sqx-form-hint>
</div>
<div class="section">
<h3 class="bordered">History</h3>
<h3 class="bordered">{{ 'common.history' | sqxTranslate }}</h3>
<sqx-content-event *ngFor="let event of contentEvents | async; trackBy: trackByEvent"
[content]="content"

2
frontend/app/features/content/pages/content/content-history-page.component.ts

@ -70,7 +70,7 @@ export class ContentHistoryPageComponent extends ResourceOwner implements OnInit
}
public changeStatus(status: string) {
this.contentPage.checkPendingChanges('change the status').pipe(
this.contentPage.checkPendingChangesBeforeChangingStatus().pipe(
filter(x => !!x),
switchMap(_ => this.dueTimeSelector.selectDueTime(status)),
switchMap(d => this.contentsState.changeStatus(this.content, status, d)),

34
frontend/app/features/content/pages/content/content-page.component.html

@ -8,14 +8,14 @@
</a>
<ng-container *ngIf="content else noContentTitle">
<sqx-title message="Edit Content"></sqx-title>
<sqx-title message="i18n:contents.editPageTitle"></sqx-title>
Edit Content
{{ 'contents.editTitle' | sqxTranslate }}
</ng-container>
<ng-template #noContentTitle>
<sqx-title message="New Content"></sqx-title>
<sqx-title message="i18n:contents.createPageTitle"></sqx-title>
New Content
{{ 'contents.createTitle' | sqxTranslate }}
</ng-template>
</ng-container>
@ -35,9 +35,9 @@
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="delete()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the content?">
Delete
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteConfirmText">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</ng-container>
@ -45,8 +45,8 @@
</ng-container>
<ng-container *ngIf="content?.canUpdate">
<button type="submit" class="btn btn-primary ml-1" title="CTRL + S">
Save
<button type="submit" class="btn btn-primary ml-1" title="i18n:common.saveShortcut">
{{ 'common.save' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+s" (trigger)="saveAndPublish()"></sqx-shortcut>
@ -55,11 +55,11 @@
<ng-template #noContent>
<button type="button" class="btn btn-secondary" (click)="save()" *ngIf="contentsState.canCreate | async">
Save
{{ 'common.save' | sqxTranslate }}
</button>
<button type="submit" class="btn btn-primary ml-1" title="CTRL + S" *ngIf="contentsState.canCreateAndPublish | async">
Save and Publish
<button type="submit" class="btn btn-primary ml-1" title="i18n:common.saveShortcut" *ngIf="contentsState.canCreateAndPublish | async">
{{ 'contents.saveAndPublish' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+s" (trigger)="saveAndPublish()"></sqx-shortcut>
@ -73,10 +73,10 @@
<ng-container topHeader>
<div class="panel-alert panel-alert-danger" *ngIf="contentVersion">
<div class="float-right">
<a class="force" (click)="loadLatest()">View latest</a>
<a class="force" (click)="loadLatest()">{{ 'contents.viewLatest' | sqxTranslate }}</a>
</div>
Viewing <strong>version {{contentVersion}}</strong>.
<div [innerHTML]="'contents.versionViewing' | sqxTranslate: { version: contentVersion } | sqxMarkdownInline"></div>
</div>
</ng-container>
@ -97,16 +97,16 @@
<ng-container sidebar>
<div class="panel-nav">
<a class="panel-link" routerLink="history" routerLinkActive="active" title="Workflow" titlePosition="left" #linkHistory>
<a class="panel-link" routerLink="history" routerLinkActive="active" title="i18n:common.workflow" titlePosition="left" #linkHistory>
<i class="icon-time"></i>
</a>
<a class="panel-link" routerLink="comments" routerLinkActive="active" title="Comments" titlePosition="left">
<a class="panel-link" routerLink="comments" routerLinkActive="active" title="i18n:common.comments" titlePosition="left">
<i class="icon-comments"></i>
</a>
<sqx-onboarding-tooltip helpId="history" [for]="linkHistory" position="left-top" after="120000">
The sidebar navigation contains useful context specific links. Here you can view the history how this schema has changed over time.
{{ 'common.sidebarTour' | sqxTranslate }}
</sqx-onboarding-tooltip>
</div>
</ng-container>

20
frontend/app/features/content/pages/content/content-page.component.ts

@ -98,7 +98,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
if (clone) {
this.loadContent(clone, true);
} else if (isNewContent && autosaved && this.contentForm.hasChanges(autosaved)) {
this.dialogs.confirm('Unsaved changes', 'You have unsaved changes. Do you want to load them now?')
this.dialogs.confirm('i18n:contents.unsavedChangesTitle', 'i18n:contents.unsavedChangesText')
.subscribe(shouldLoad => {
if (shouldLoad) {
this.loadContent(autosaved, false);
@ -120,7 +120,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
}
public canDeactivate(): Observable<boolean> {
return this.checkPendingChanges('close the current content view').pipe(
return this.checkPendingChangesBeforeClose().pipe(
tap(confirmed => {
if (confirmed) {
this.autoSaveService.remove(this.autoSaveKey);
@ -167,7 +167,7 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
});
}
} else {
this.contentForm.submitFailed('Content element not valid, please check the field with the red bar on the left in all languages (if localizable).');
this.contentForm.submitFailed('i18n:contents.contentNotValid');
}
}
@ -194,13 +194,23 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD
}
}
public checkPendingChanges(action: string) {
public checkPendingChangesBeforeClose() {
if (this.content && !this.content.canUpdate) {
return of(true);
}
return this.contentForm.hasChanged() ?
this.dialogs.confirm('Unsaved changes', `You have unsaved changes.\n\nWhen you ${action} you will loose them.\n\n**Do you want to continue anyway?**`) :
this.dialogs.confirm('i18n:contents.pendingChangesTitle', 'i18n:contents.pendingChangesTextToClose') :
of(true);
}
public checkPendingChangesBeforeChangingStatus() {
if (this.content && !this.content.canUpdate) {
return of(true);
}
return this.contentForm.hasChanged() ?
this.dialogs.confirm('i18n:contents.pendingChangesTitle', 'i18n:contents.pendingChangesTextToChange') :
of(true);
}

6
frontend/app/features/content/pages/content/field-languages.component.html

@ -1,11 +1,11 @@
<ng-container *ngIf="field.isLocalizable && languages.length > 1">
<button *ngIf="!field.properties.isComplexUI" type="button" class="btn btn-text-secondary btn-sm mr-1" (click)="toggleShowAllControls()">
<ng-container *ngIf="showAllControls; else singleLanguage">
<span>Single Language</span>
<span>{{ 'contents.languageModeSingle' | sqxTranslate }}</span>
</ng-container>
<ng-template #singleLanguage>
<span>All Languages</span>
<span>{{ 'contents.languageModeAll' | sqxTranslate }}</span>
</ng-template>
</button>
@ -17,7 +17,7 @@
</sqx-language-selector>
<sqx-onboarding-tooltip helpId="languages" [for]="buttonLanguages" position="top-right" after="120000">
Please remember to check all languages when you see validation errors.
{{ 'contents.validationHint' | sqxTranslate }}
</sqx-onboarding-tooltip>
</ng-container>
</ng-container>

13
frontend/app/features/content/pages/contents/contents-filters-page.component.html

@ -1,30 +1,33 @@
<sqx-panel desiredWidth="20rem" isBlank="true" [isLazyLoaded]="false">
<ng-container title>
Filters
{{ 'common.filters' | sqxTranslate }}
</ng-container>
<ng-container content>
<sqx-query-list
[types]="'common.contents' | sqxTranslate"
[queryUsed]="contentsState.contentsQuery | async"
[queries]="schemaQueries.defaultQueries"
(search)="search($event)">
</sqx-query-list>
<hr />
<hr>
<div class="sidebar-section">
<h3>Status Queries</h3>
<h3>{{ 'contents.statusQueries' | sqxTranslate }}</h3>
<sqx-query-list
[types]="'common.contents' | sqxTranslate"
[queryUsed]="contentsState.contentsQuery | async"
[queries]="contentsState.statusQueries | async"
(search)="search($event)">
</sqx-query-list>
</div>
<hr />
<hr>
<sqx-shared-queries types="contents"
<sqx-shared-queries
[types]="'common.contents' | sqxTranslate"
[queryUsed]="contentsState.contentsQuery | async"
[queries]="schemaQueries"
(search)="search($event)">

32
frontend/app/features/content/pages/contents/contents-page.component.html

@ -2,7 +2,7 @@
<sqx-panel desiredWidth="*" minWidth="50rem" showSidebar="true" grid="true">
<ng-container title>
Contents
{{ 'common.contents' | sqxTranslate }}
</ng-container>
<ng-container menu>
@ -12,26 +12,25 @@
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="Refresh Contents (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="i18n:contents.refreshTooltip">
<i class="icon-reset"></i> {{ 'common.refresh' | sqxTranslate }}
</button>
</div>
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Fulltext search"
<sqx-search-form formClass="form" placeholder="{{ 'contents.searchPlaceholder' | sqxTranslate }}"
(queryChange)="search($event)"
[query]="contentsState.contentsQuery | async"
[queries]="queries"
[queryModel]="queryModel"
[language]="languageMaster"
enableShortcut="true">
[language]="languageMaster" enableShortcut="true">
</sqx-search-form>
</div>
<div class="col-auto pl-1" *ngIf="languages.length > 1">
<sqx-language-selector class="languages-buttons" (selectedLanguageChange)="selectLanguage($event)" [languages]="languages"></sqx-language-selector>
</div>
<div class="col-auto pl-1">
<button type="button" class="btn btn-success" #newButton routerLink="new" title="New Content (CTRL + SHIFT + G)" [disabled]="(contentsState.canCreateAny | async) === false">
<i class="icon-plus"></i> New
<button type="button" class="btn btn-success" #newButton routerLink="new" title="i18n:contents.createContentTooltip" [disabled]="(contentsState.canCreateAny | async) === false">
<i class="icon-plus"></i> {{ 'contents.create' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+shift+g" (trigger)="newButton.click()"></sqx-shortcut>
@ -44,7 +43,7 @@
<sqx-list-view [isLoading]="contentsState.isLoading | async" syncedHeader="true" table="true">
<ng-container topHeader>
<div class="selection" *ngIf="selectionCount > 0">
{{selectionCount}} items selected&nbsp;&nbsp;
{{ 'contents.selectionCount' | sqxTranslate: { count: selectionCount } }}&nbsp;&nbsp;
<button type="button" class="btn btn-outline-secondary btn-status mr-1" *ngFor="let status of nextStatuses | sqxKeys" (click)="changeSelectedStatus(status)">
<sqx-content-status layout="text"
@ -55,9 +54,9 @@
<button type="button" class="btn btn-danger" *ngIf="selectionCanDelete"
(sqxConfirmClick)="deleteSelected()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the selected content items?">
Delete
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteManyConfirmText">
{{ 'common.delete' | sqxTranslate }}
</button>
</div>
@ -68,8 +67,7 @@
<ng-container *sqxModal="tableViewModal">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSettings" @fade position="bottom-right">
<sqx-custom-view-editor
[allFields]="tableView.allFields"
<sqx-custom-view-editor [allFields]="tableView.allFields"
(fieldNamesChange)="tableView.updateFields($event)"
[fieldNames]="tableView.listFieldNames | async">
</sqx-custom-view-editor>
@ -83,10 +81,10 @@
<thead>
<tr>
<th class="cell-select">
<input type="checkbox" class="form-check" [ngModel]="selectedAll" (ngModelChange)="selectAll($event)" />
<input type="checkbox" class="form-check" [ngModel]="selectedAll" (ngModelChange)="selectAll($event)">
</th>
<th class="cell-actions cell-actions-left">
Actions
{{ 'common.actions' | sqxTranslate }}
</th>
<th *ngFor="let field of listFields" [sqxContentListCell]="field">
<sqx-content-list-header
@ -129,7 +127,7 @@
<ng-container sidebar>
<div class="panel-nav">
<a class="panel-link" routerLink="filters" routerLinkActive="active" title="Filters" titlePosition="left">
<a class="panel-link" routerLink="filters" routerLinkActive="active" title="i18n:common.filters" titlePosition="left">
<i class="icon-filter"></i>
</a>
</div>

7
frontend/app/features/content/pages/contents/custom-view-editor.component.html

@ -1,17 +1,16 @@
<div class="container">
<div class="header">
<button type="button" class="btn btn-secondary btn-sm" (click)="resetDefault()">
Reset Default View
{{ 'contents.viewReset' | sqxTranslate }}
</button>
</div>
<hr />
<hr>
<div
cdkDropList
[cdkDropListData]="fieldNames"
(cdkDropListDropped)="drop($event)">
<div *ngFor="let field of fieldNames" cdkDrag>
<i class="icon-drag2 drag-handle"></i>
@ -24,7 +23,7 @@
</div>
</div>
<hr />
<hr>
<div>
<div *ngFor="let field of fieldsNotAdded">

8
frontend/app/features/content/pages/schemas/schemas-page.component.html

@ -1,9 +1,9 @@
<sqx-title message="Contents"></sqx-title>
<sqx-title message="i18n:contents.schemasPageTitle"></sqx-title>
<sqx-panel theme="dark" [desiredWidth]="width" [showClose]="!isCollapsed" showSecondHeader="true">
<ng-container title>
<ng-container *ngIf="!isCollapsed">
Schemas
{{ 'common.schemas' | sqxTranslate }}
</ng-container>
</ng-container>
@ -18,7 +18,7 @@
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<div class="search-form">
<input class="form-control form-control-dark" #inputFind [formControl]="schemasFilter" placeholder="Search for schemas" />
<input class="form-control form-control-dark" #inputFind [formControl]="schemasFilter" placeholder="{{ 'contents.searchSchemasPlaceholder' | sqxTranslate }}">
<i class="icon-search"></i>
</div>
@ -37,7 +37,7 @@
</div>
<div class="headline" [class.hidden]="!isCollapsed">
Schemas
{{ 'common.schemas' | sqxTranslate }}
</div>
</ng-container>
</sqx-panel>

4
frontend/app/features/content/shared/content-status.component.html

@ -9,7 +9,7 @@
<div class="content-status-scheduled mt-2" *ngIf="scheduled">
<div>
<span class="label">to&nbsp;</span>
<span class="label">{{ 'contents.scheduledTo' | sqxTranslate }}&nbsp;</span>
<span class="content-status default mr-1" [style.color]="scheduled?.color" title="{{tooltipText}}">
<i class="icon-circle icon-sm"></i>
@ -19,7 +19,7 @@
</div>
<div class="truncate">
<span class="label">at&nbsp;</span>
<span class="label">{{ 'contents.scheduledAt' | sqxTranslate }}&nbsp;</span>
<span>{{scheduled?.dueTime | sqxFullDateTime}}</span>
</div>

14
frontend/app/features/content/shared/due-time-selector.component.html

@ -1,21 +1,21 @@
<ng-container *sqxModal="dueTimeDialog">
<sqx-modal-dialog (close)="cancelStatusChange()">
<ng-container title>
Change content item(s) to {{dueTimeAction}}
{{ 'contents.changeStatusTo' | sqxTranslate: { action: dueTimeAction } }}
</ng-container>
<ng-container content>
<div class="form-check">
<input class="form-check-input" type="radio" [(ngModel)]="dueTimeMode" value="Immediately" id="immediately" name="dueTimeMode" />
<input class="form-check-input" type="radio" [(ngModel)]="dueTimeMode" value="Immediately" id="immediately" name="dueTimeMode">
<label class="form-check-label" for="immediately">
Set to {{dueTimeAction}} immediately.
{{ 'contents.changeStatusToImmediately' | sqxTranslate: { action: dueTimeAction } }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" [(ngModel)]="dueTimeMode" value="Scheduled" id="scheduled" name="dueTimeMode" />
<input class="form-check-input" type="radio" [(ngModel)]="dueTimeMode" value="Scheduled" id="scheduled" name="dueTimeMode">
<label class="form-check-label" for="scheduled">
Set to {{dueTimeAction}} at a later point date and time.
{{ 'contents.changeStatusToLater' | sqxTranslate: { action: dueTimeAction } }}
</label>
</div>
@ -23,8 +23,8 @@
</ng-container>
<ng-container footer>
<button type="button" class="btn btn-secondary" (click)="cancelStatusChange()">Cancel</button>
<button type="button" class="btn btn-primary" [disabled]="dueTimeMode === 'Scheduled' && !dueTime" (click)="confirmStatusChange()" sqxFocusOnInit>Confirm</button>
<button type="button" class="btn btn-secondary" (click)="cancelStatusChange()">{{ 'common.cancel' | sqxTranslate }}</button>
<button type="button" class="btn btn-primary" [disabled]="dueTimeMode === 'Scheduled' && !dueTime" (click)="confirmStatusChange()" sqxFocusOnInit>{{ 'common.confirm' | sqxTranslate }}</button>
</ng-container>
</sqx-modal-dialog>
</ng-container>

13
frontend/app/features/content/shared/forms/array-editor.component.html

@ -3,10 +3,7 @@
[cdkDropListDisabled]="false"
[cdkDropListData]="formModel.items"
(cdkDropListDropped)="sort($event)">
<div *ngFor="let itemForm of formModel.items; index as i; last as isLast; first as isFirst"
class="table-drag item"
cdkDrag
cdkDragLockAxis="y">
<div *ngFor="let itemForm of formModel.items; index as i; last as isLast; first as isFirst" class="table-drag item" cdkDrag cdkDragLockAxis="y">
<sqx-array-item
[canUnset]="canUnset"
[form]="form"
@ -27,20 +24,20 @@
<div class="row">
<div class="col">
<button type="button" class="btn btn-success" [disabled]="field.nested.length === 0 || formModel.form.disabled" (click)="itemAdd(undefined)">
Add Item
{{ 'contents.arrayAddItem' | sqxTranslate }}
</button>
</div>
<div class="col-auto" *ngIf="formModel.items.length > 0">
<button type="button" class="btn btn-text-secondary" (click)="expandAll()" title="Expand all items">
<button type="button" class="btn btn-text-secondary" (click)="expandAll()" title="i18n:contents.arrayExpandAll">
<i class="icon-plus-square"></i>
</button>
<button type="button" class="btn btn-text-secondary" (click)="collapseAll()" title="Collapse all items">
<button type="button" class="btn btn-text-secondary" (click)="collapseAll()" title="i18n:contents.arrayCollapseAll">
<i class="icon-minus-square"></i>
</button>
</div>
</div>
<small class="text-muted ml-2" *ngIf="field.nested.length === 0">
Add a nested field first to add items.
{{ 'contents.arrayNoFields' | sqxTranslate }}
</small>

14
frontend/app/features/content/shared/forms/array-item.component.html

@ -11,27 +11,27 @@
</div>
</div>
<div class="col-auto pr-4">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="moveTop()" title="Move this item to top">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="moveTop()" title="i18n:contents.arrayMoveTop">
<i class="icon-caret-top"></i>
</button>
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="moveUp()" title="Move this item up">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isFirst" (click)="moveUp()" title="i18n:contents.arrayMoveUp">
<i class="icon-caret-up"></i>
</button>
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveDown()" title="Move this item down">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveDown()" title="i18n:contents.arrayMoveDown">
<i class="icon-caret-down"></i>
</button>
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveBottom()" title="Move this item to bottom">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled || isLast" (click)="moveBottom()" title="i18n:contents.arrayMoveBottom">
<i class="icon-caret-bottom"></i>
</button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="!isCollapsed" (click)="expand()" title="Expand this item">
<button type="button" class="btn btn-text-secondary" [class.hidden]="!isCollapsed" (click)="expand()" title="i18n:contents.arrayExpandItem">
<i class="icon-plus-square"></i>
</button>
<button type="button" class="btn btn-text-secondary" [class.hidden]="isCollapsed" (click)="collapse()" title="Collapse this item">
<button type="button" class="btn btn-text-secondary" [class.hidden]="isCollapsed" (click)="collapse()" title="i18n:contents.arrayCollapseItem">
<i class="icon-minus-square"></i>
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled" (click)="clone.emit()" title="Clone this item">
<button type="button" class="btn btn-text-secondary" [disabled]="isDisabled" (click)="clone.emit()" title="i18n:contents.arrayCloneItem">
<i class="icon-clone"></i>
</button>

47
frontend/app/features/content/shared/forms/assets-editor.component.html

@ -1,14 +1,9 @@
<div class="assets-container"
(sqxDropFile)="addFiles($event)"
(sqxDropDisabled)="snapshot.isDisabled"
tabindex="1000">
<div class="assets-container" (sqxDropFile)="addFiles($event)" (sqxDropDisabled)="snapshot.isDisabled" tabindex="1000">
<div class="header list">
<div class="row no-gutters">
<div class="col" [class.disabled]="snapshot.isDisabled">
<div class="drop-area align-items-center" (click)="assetsDialog.show()"
(sqxDropFile)="addFiles($event)"
(sqxDropDisabled)="snapshot.isDisabled">
Drop files or click
<div class="drop-area align-items-center" (click)="assetsDialog.show()" (sqxDropFile)="addFiles($event)" (sqxDropDisabled)="snapshot.isDisabled">
{{ 'contents.assetsUpload' | sqxTranslate }}
</div>
</div>
<div class="col-auto pl-1">
@ -24,48 +19,44 @@
</div>
</div>
<div class="body"
(sqxResizeCondition)="setCompact($event)"
[sqxResizeMinWidth]="600"
[sqxResizeMaxWidth]="0">
<div class="body" (sqxResizeCondition)="setCompact($event)" [sqxResizeMinWidth]="600" [sqxResizeMaxWidth]="0">
<ng-container *ngIf="!snapshot.isListView; else listTemplate">
<div class="row no-gutters">
<sqx-asset *ngFor="let file of snapshot.assetFiles" [assetFile]="file"
<sqx-asset *ngFor="let file of snapshot.assetFiles"
[assetFile]="file"
[isDisabled]="snapshot.isDisabled"
[isCompact]="snapshot.isCompact"
(loadError)="removeLoadingAsset(file)"
(load)="addAsset(file, $event)">
</sqx-asset>
<sqx-asset *ngFor="let asset of snapshot.assets; trackBy: trackByAsset"
[asset]="asset"
[isDisabled]="snapshot.isDisabled"
[isCompact]="snapshot.isCompact"
(update)="notifyOthers(asset)"
[removeMode]="true"
(remove)="removeLoadedAsset(asset)">
(remove)="removeLoadedAsset(asset)"
(update)="notifyOthers(asset)">
</sqx-asset>
</div>
</ng-container>
<ng-template #listTemplate>
<div class="list-view">
<sqx-asset *ngFor="let file of snapshot.assetFiles" [assetFile]="file"
[isListView]="true"
[isDisabled]="snapshot.isDisabled"
[isCompact]="snapshot.isCompact"
<sqx-asset *ngFor="let file of snapshot.assetFiles"
(load)="addAsset(file, $event)"
(loadError)="removeLoadingAsset(file)"
(load)="addAsset(file, $event)">
[assetFile]="file"
[isCompact]="snapshot.isCompact"
[isDisabled]="snapshot.isDisabled"
[isListView]="true">
</sqx-asset>
<div
cdkDropList
<div cdkDropList
[cdkDropListDisabled]="snapshot.isDisabled"
[cdkDropListData]="snapshot.assets"
(cdkDropListDropped)="sortAssets($event)">
<div *ngFor="let asset of snapshot.assets; trackBy: trackByAsset"
class="table-drag"
cdkDrag
cdkDragLockAxis="y">
<div *ngFor="let asset of snapshot.assets; trackBy: trackByAsset" class="table-drag" cdkDrag cdkDragLockAxis="y">
<sqx-asset
[asset]="asset"
[isListView]="true"
@ -83,7 +74,5 @@
</div>
<ng-container *sqxModal="assetsDialog">
<sqx-assets-selector
(select)="selectAssets($event)">
</sqx-assets-selector>
<sqx-assets-selector (select)="selectAssets($event)"></sqx-assets-selector>
</ng-container>

15
frontend/app/features/content/shared/forms/stock-photo-editor.component.html

@ -1,36 +1,33 @@
<div class="row no-gutters"
(sqxResizeCondition)="setCompact($event)"
[sqxResizeMinWidth]="600"
[sqxResizeMaxWidth]="0">
<div class="row no-gutters" (sqxResizeCondition)="setCompact($event)" [sqxResizeMinWidth]="600" [sqxResizeMaxWidth]="0">
<div class="col-auto col-image" [class.expand]="snapshot.isCompact">
<input class="form-control value" [formControl]="valueControl" readonly />
<input class="form-control value" [formControl]="valueControl" readonly>
<button type="button" class="btn btn-text-secondary value-clear" (click)="reset()">
<i class="icon-close"></i>
</button>
<div *ngIf="stockPhotoThumbnail | async; let url; else noThumb" class="preview">
<img [src]="url" />
<img [src]="url">
</div>
<ng-template #noThumb>
<div class="preview preview-empty">
Nothing selected
{{ 'contents.stockPhotoEmpty' | sqxTranslate }}
</div>
</ng-template>
</div>
<div class="col pl-4" *ngIf="!snapshot.isCompact">
<i class="icon-angle-left icon"></i>
<input class="form-control" [formControl]="stockPhotoSearch" placeholder="Search for Photos by Unsplash" />
<input class="form-control" [formControl]="stockPhotoSearch" placeholder="{{ 'contents.stockPhotoSearch' | sqxTranslate }}">
<sqx-list-view [isLoading]="snapshot.isLoading" table="true">
<div content>
<div class="photos">
<ng-container *ngIf="stockPhotos | async; let photos">
<div *ngFor="let photo of photos" class="photo" [class.selected]="isSelected(photo)" (click)="selectPhoto(photo)">
<img [src]="photo.thumbUrl" />
<img [src]="photo.thumbUrl">
<div class="photo-user">
<a class="photo-user-link" [href]="photo.userProfileUrl" sqxExternalLink sqxStopClick>

6
frontend/app/features/content/shared/list/content-list-field.component.html

@ -6,7 +6,7 @@
<small class="truncate">{{content.created | sqxFromNow}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByAvatar">
<img class="user-picture" title="{{content.createdBy | sqxUserNameRef}}" [src]="content.createdBy | sqxUserPictureRef" />
<img class="user-picture" title="{{content.createdBy | sqxUserNameRef}}" [src]="content.createdBy | sqxUserPictureRef">
</ng-container>
<ng-container *ngSwitchCase="metaFields.createdByName">
<small class="truncate">{{content.createdBy | sqxUserNameRef}}</small>
@ -15,7 +15,7 @@
<small class="truncate">{{content.lastModified | sqxFromNow}}</small>
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByAvatar">
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [src]="content.lastModifiedBy | sqxUserPictureRef" />
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [src]="content.lastModifiedBy | sqxUserPictureRef">
</ng-container>
<ng-container *ngSwitchCase="metaFields.lastModifiedByName">
<small class="truncate">{{content.lastModifiedBy | sqxUserNameRef}}</small>
@ -61,7 +61,7 @@
[statusColor]="content.scheduleJob?.color">
</sqx-content-status>
at {{content.scheduleJob?.dueTime | sqxShortDate}}
{{ 'contents.scheduledAtLabel' | sqxTranslate }}&nbsp;{{content.scheduleJob?.dueTime | sqxShortDate}}
</span>
</ng-container>
<ng-container *ngSwitchCase="metaFields.statusColor">

17
frontend/app/features/content/shared/list/content.component.html

@ -1,8 +1,6 @@
<tr [sqxTabRouterLink]="link">
<td class="cell-select inline-edit" sqxStopClick>
<input type="checkbox" class="form-check"
[ngModel]="selected"
(ngModelChange)="selectedChange.emit($event)" />
<input type="checkbox" class="form-check" [ngModel]="selected" (ngModelChange)="selectedChange.emit($event)">
<ng-container *ngIf="isDirty">
<div class="edit-menu">
@ -26,24 +24,25 @@
<ng-container *sqxModal="dropdown;closeAlways:true">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade>
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="statusChange.emit(info.status)">
Change to
{{ 'common.statusChangeTo' | sqxTranslate }}
<sqx-content-status layout="text" small="true"
<sqx-content-status small="true"
layout="text"
[status]="info.status"
[statusColor]="info.color">
</sqx-content-status>
</a>
<a class="dropdown-item" (click)="clone.emit(); dropdown.hide()" *ngIf="canClone">
Clone
{{ 'common.clone' | sqxTranslate }}
</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete"
(sqxConfirmClick)="delete.emit()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the content?">
Delete
confirmTitle="i18n:contents.deleteConfirmTitle"
confirmText="i18n:contents.deleteConfirmText">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</ng-container>

2
frontend/app/features/content/shared/preview-button.component.html

@ -1,5 +1,5 @@
<ng-container *ngIf="snapshot.previewNameSelected">
<span>Preview: </span>
<span>{{ 'common.preview' | sqxTranslate }}: </span>
<div class="btn-group ml-1" #buttonGroup>
<button type="button" class="btn btn-secondary" (click)="follow(snapshot.previewNameSelected)">

14
frontend/app/features/content/shared/references/content-creator.component.html

@ -2,11 +2,9 @@
<ng-container title>
<div class="row">
<div class="col-selector">
<select class="form-control form-control-dark"
[ngModel]="schema?.id"
(ngModelChange)="selectSchema($event)">
<select class="form-control form-control-dark" [ngModel]="schema?.id" (ngModelChange)="selectSchema($event)">
<option *ngFor="let schema of schemas" [ngValue]="schema.id">
Select {{schema.displayName}}
{{ 'contents.referencesSelectSchema' | sqxTranslate: { schema: schema.displayName } }}
</option>
</select>
</div>
@ -17,19 +15,17 @@
<div class="row no-gutters">
<div class="col-auto">
<div *ngIf="schema && languages.length > 1">
<sqx-language-selector class="languages-buttons"
(selectedLanguageChange)="selectLanguage($event)" [languages]="languages">
</sqx-language-selector>
<sqx-language-selector class="languages-buttons"(selectedLanguageChange)="selectLanguage($event)" [languages]="languages"></sqx-language-selector>
</div>
</div>
<div class="col text-right">
<button type="button" class="btn btn-outline-success" (click)="save()">
Create
{{ 'common.create' | sqxTranslate }}
</button>
<button type="button" class="btn btn-success ml-1" (click)="saveAndPublish()" *ngIf="schema?.canContentsCreateAndPublish">
Create and Publish
{{ 'contents.referencesCreatePublish' | sqxTranslate }}
</button>
<sqx-form-error bubble="true" closeable="true" [error]="contentForm?.error | async"></sqx-form-error>

2
frontend/app/features/content/shared/references/content-creator.component.ts

@ -100,7 +100,7 @@ export class ContentCreatorComponent extends ResourceOwner implements OnInit {
this.contentForm.submitFailed(error);
});
} else {
this.contentForm.submitFailed('Content element not valid, please check the field with the red bar on the left in all languages (if localizable).');
this.contentForm.submitFailed('i18n:contents.contentNotValid');
}
}

19
frontend/app/features/content/shared/references/content-selector.component.html

@ -2,11 +2,9 @@
<ng-container title>
<div class="row">
<div class="col-selector">
<select class="form-control form-control-dark"
[ngModel]="schema?.id"
(ngModelChange)="selectSchema($event)">
<select class="form-control form-control-dark" [ngModel]="schema?.id" (ngModelChange)="selectSchema($event)">
<option *ngFor="let schema of schemas" [ngValue]="schema.id">
Select {{schema.displayName}}
{{ 'contents.referencesSelectSchema' | sqxTranslate: { schema: schema.displayName } }}
</option>
</select>
</div>
@ -22,7 +20,7 @@
</button>
</div>
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Fulltext search"
<sqx-search-form formClass="form" placeholder="{{ 'contents.searchPlaceholder' | sqxTranslate }}"
[query]="contentsState.contentsQuery | async"
[queryModel]="queryModel"
(queryChange)="search($event)">
@ -46,7 +44,7 @@
<thead>
<tr>
<th class="cell-select">
<input type="checkbox" class="form-check" [ngModel]="selectedAll" (ngModelChange)="selectAll($event)" />
<input type="checkbox" class="form-check" [ngModel]="selectedAll" (ngModelChange)="selectAll($event)">
</th>
<th sqxContentListCell="meta.lastModifiedBy.avatar">
<sqx-content-list-header field="meta.lastModifiedBy.avatar"></sqx-content-list-header>
@ -87,7 +85,12 @@
</ng-container>
<ng-container footer>
<button type="button" class="btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="submit" class="btn btn-success" (click)="emitSelect()" [disabled]="selectionCount === 0">Link selected contents ({{selectionCount}})</button>
<button type="button" class="btn btn-secondary" (click)="emitComplete()">
{{ 'common.cancel' | sqxTranslate }}
</button>
<button type="submit" class="btn btn-success" (click)="emitSelect()" [disabled]="selectionCount === 0">
{{ 'contents.referencesLink' | sqxTranslate: { count: selectionCount} }})
</button>
</ng-container>
</sqx-modal-dialog>

4
frontend/app/features/content/shared/references/references-editor.component.html

@ -5,11 +5,11 @@
<ng-container>
<div class="drop-area-container">
<div class="drop-area">
<a (click)="contentCreatorDialog.show()">Add New</a>
<a (click)="contentCreatorDialog.show()">{{ 'contents.referencesCreateNew' | sqxTranslate }}</a>
&middot;
<a (click)="contentSelectorDialog.show()">Select Existing</a>
<a (click)="contentSelectorDialog.show()">{{ 'contents.referencesSelectExisting' | sqxTranslate }}</a>
</div>
</div>

4
frontend/app/features/dashboard/pages/cards/api-calls-card.component.html

@ -1,10 +1,10 @@
<div class="card card-lg">
<div class="card-header">
API Calls
{{ 'dashboard.apiCallsCard' | sqxTranslate }}
<div class="float-right">
<a class="force" (click)="downloadLog()">
<small>Download Log</small>
<small>{{ 'dashboard.downloadLog' | sqxTranslate }}</small>
</a>
</div>
</div>

10
frontend/app/features/dashboard/pages/cards/api-calls-summary-card.component.html

@ -1,10 +1,14 @@
<div class="card card">
<div class="card-header">API Calls</div>
<div class="card-header">{{ 'dashboard.apiCallsSummaryCard' | sqxTranslate }}</div>
<div class="card-body">
<div class="aggregation" *ngIf="callsTotal >= 0">
<div class="aggregation-label">This month</div>
<div class="aggregation-label">{{ 'dashboard.currentMonthLabel' | sqxTranslate }}</div>
<div class="aggregation-value">{{callsTotal | sqxKNumber}}</div>
<div class="aggregation-label" *ngIf="callsAllowed > 0">Monthly limit: {{callsAllowed | sqxKNumber}}</div>
<div class="aggregation-label" *ngIf="callsAllowed > 0">
{{ 'dashboard.apiCallsLimitLabel' | sqxTranslate }}: {{callsAllowed | sqxKNumber}}
</div>
</div>
</div>
</div>

6
frontend/app/features/dashboard/pages/cards/api-card.component.html

@ -1,15 +1,15 @@
<div class="card card-href">
<div class="card-body">
<div class="card-image">
<img src="./images/dashboard-api.svg" />
<img src="./images/dashboard-api.svg">
</div>
<h4 class="card-title">
<a href="/api/content/{{app.name}}/docs" sqxExternalLink>Content API</a>
<a href="/api/content/{{app.name}}/docs" sqxExternalLink>{{ 'dashboard.contentApi' | sqxTranslate }}</a>
</h4>
<div class="card-text">
OpenAPI 3.0 compatible documentation for your app content.
{{ 'dashboard.contentApiDescription' | sqxTranslate }}
</div>
</div>
</div>

8
frontend/app/features/dashboard/pages/cards/api-performance-card.component.html

@ -1,15 +1,13 @@
<div class="card card-lg">
<div class="card-header">
API Performance (ms): {{chartSummary}}ms avg
{{ 'dashboard.apiPerformanceCard' | sqxTranslate: { summary: chartSummary } }}
<div class="float-right">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="stacked"
[ngModel]="isStacked"
(ngModelChange)="isStackedChange.emit($event)" />
<input class="form-check-input" type="checkbox" id="stacked" [ngModel]="isStacked" (ngModelChange)="isStackedChange.emit($event)">
<label class="form-check-label" for="stacked">
Stacked
{{ 'dashboard.stackedChart' | sqxTranslate }}
</label>
</div>
</div>

8
frontend/app/features/dashboard/pages/cards/api-traffic-card.component.html

@ -1,15 +1,13 @@
<div class="card card-lg">
<div class="card-header">
Traffic (MB): {{chartSummary | sqxFileSize}} total
{{ 'dashboard.trafficSummaryCard' | sqxTranslate }}: {{chartSummary | sqxFileSize}}
<div class="float-right">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="stacked"
[ngModel]="isStacked"
(ngModelChange)="isStackedChange.emit($event)" />
<input class="form-check-input" type="checkbox" id="stacked" [ngModel]="isStacked" (ngModelChange)="isStackedChange.emit($event)">
<label class="form-check-label" for="stacked">
Stacked
{{ 'dashboard.stackedChart' | sqxTranslate }}
</label>
</div>
</div>

2
frontend/app/features/dashboard/pages/cards/asset-uploads-count-card.component.html

@ -1,5 +1,5 @@
<div class="card card-lg">
<div class="card-header">Assets Uploads</div>
<div class="card-header">{{ 'dashboard.assetUploadsCard' | sqxTranslate }}</div>
<div class="card-body">
<chart type="line" [data]="chartData" [options]="chartOptions"></chart>
</div>

2
frontend/app/features/dashboard/pages/cards/asset-uploads-size-card.component.html

@ -1,5 +1,5 @@
<div class="card card-lg">
<div class="card-header">Assets Uploads</div>
<div class="card-header">{{ 'dashboard.assetUploadsCard' | sqxTranslate }}</div>
<div class="card-body">
<chart type="line" [data]="chartData" [options]="chartOptions"></chart>
</div>

10
frontend/app/features/dashboard/pages/cards/asset-uploads-size-summary-card.component.html

@ -1,10 +1,14 @@
<div class="card card">
<div class="card-header">Assets Size (MB)</div>
<div class="card-header">{{ 'dashboard.assetSizeCard' | sqxTranslate }})</div>
<div class="card-body">
<div class="aggregation" *ngIf="storageCurrent >= 0">
<div class="aggregation-label">Total Size</div>
<div class="aggregation-label">{{ 'dashboard.assetSizeLabel' | sqxTranslate }}</div>
<div class="aggregation-value">{{storageCurrent | sqxFileSize}}</div>
<div class="aggregation-label" *ngIf="storageAllowed > 0">Total limit: {{storageAllowed | sqxFileSize}}</div>
<div class="aggregation-label" *ngIf="storageAllowed > 0">
{{ 'dashboard.assetSizeLimitLabel' | sqxTranslate }}: {{storageAllowed | sqxFileSize}}
</div>
</div>
</div>
</div>

2
frontend/app/features/dashboard/pages/cards/content-summary-card.component.html

@ -2,7 +2,7 @@
<div class="card-header">{{options?.name}}</div>
<div class="card-body">
<div class="aggregation">
<div class="aggregation-label">Number of items</div>
<div class="aggregation-label">{{ 'dashboard.contentsSummaryCard' | sqxTranslate }}</div>
<div class="aggregation-value">{{itemCount}}</div>
</div>
</div>

6
frontend/app/features/dashboard/pages/cards/github-card.component.html

@ -1,15 +1,15 @@
<div class="card card-href">
<div class="card-body">
<div class="card-image">
<img src="./images/dashboard-github.svg" />
<img src="./images/dashboard-github.svg">
</div>
<h4 class="card-title">
<a href="https://github.com/squidex/squidex" sqxExternalLink>Github</a>
<a href="https://github.com/squidex/squidex" sqxExternalLink>{{ 'dashboard.github' | sqxTranslate }}</a>
</h4>
<div class="card-text">
Get the source code from Github and report bugs or ask for support.
{{ 'dashboard.githubCardDescription' | sqxTranslate }}
</div>
</div>
</div>

2
frontend/app/features/dashboard/pages/cards/history-card.component.html

@ -1,5 +1,5 @@
<div class="card card-lg">
<div class="card-header">History</div>
<div class="card-header">{{ 'dashboard.historyCard' | sqxTranslate }}</div>
<div class="card-body card-history card-body-scroll">
<sqx-history-list [events]="history | async"></sqx-history-list>
</div>

10
frontend/app/features/dashboard/pages/cards/schema-card.component.html

@ -1,26 +1,26 @@
<div class="card card-href">
<div class="card-body">
<div class="card-image">
<img src="./images/dashboard-schema.svg" />
<img src="./images/dashboard-schema.svg">
</div>
<ng-container *ngIf="app.canReadSchemas; else noPermission">
<h4 class="card-title">
<a [routerLink]="['schemas', { showDialog: true }]">New Schema</a>
<a [routerLink]="['schemas', { showDialog: true }]">{{ 'dashboard.schemaNewCard' | sqxTranslate }}</a>
</h4>
<div class="card-text">
A schema defines the structure of your content element.
{{ 'dashboard.schemaNewCardDescription' | sqxTranslate }}
</div>
</ng-container>
<ng-template #noPermission>
<h4 class="card-title">
<a [routerLink]="['schemas']">Schemas</a>
<a [routerLink]="['schemas']">{{ 'dashboard.schemasCard' | sqxTranslate }}</a>
</h4>
<div class="card-text">
Get an insight to the data model of this app.
{{ 'dashboard.schemasCardDescription' | sqxTranslate }}
</div>
</ng-template>
</div>

6
frontend/app/features/dashboard/pages/cards/support-card.component.html

@ -1,15 +1,15 @@
<div class="card card-href">
<div class="card-body">
<div class="card-image">
<img src="./images/dashboard-feedback.svg" />
<img src="./images/dashboard-feedback.svg">
</div>
<h4 class="card-title">
<a href="https://support.squidex.io" sqxExternalLink>Feedback & Support</a>
<a href="https://support.squidex.io" sqxExternalLink>{{ 'dashboard.supportCard' | sqxTranslate }}</a>
</h4>
<div class="card-text">
Provide feedback and request features to help us to improve Squidex.
{{ 'dashboard.supportCardDescription' | sqxTranslate }}
</div>
</div>
</div>

22
frontend/app/features/dashboard/pages/dashboard-config.component.html

@ -7,29 +7,27 @@
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSettings" @fade position="bottom-right">
<div class="dropdown-item" *ngFor="let item of configDefaults">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="field_{{item.type}}"
[ngModel]="isSelected(item)"
(ngModelChange)="addOrRemove(item)" />
<input class="form-check-input" type="checkbox" id="field_{{item.type}}" [ngModel]="isSelected(item)" (ngModelChange)="addOrRemove(item)">
<label class="form-check-label" for="field_{{item.type}}">
{{item.name}}
{{item.name | sqxTranslate}}
</label>
</div>
</div>
<div class="dropdown-divider"></div>
<a class="dropdown-item" (click)="startExpertMode()">Expert Mode</a>
<a class="dropdown-item" (click)="startExpertMode()">{{ 'common.expertMode' | sqxTranslate }}</a>
<a class="dropdown-item" (click)="saveConfig()">Save</a>
<a class="dropdown-item" (click)="saveConfig()">{{ 'common.save' | sqxTranslate }}</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete" (beforeClick)="dropdownModal.hide()"
(sqxConfirmClick)="resetConfig()"
confirmTitle="Reset config"
confirmText="Do you really want to reset the dashboard to the default?">
Reset
confirmTitle="i18n:dashboard.resetConfigConfirmTitle"
confirmText="i18n:dashboard.resetConfigConfirmText">
{{ 'common.reset' | sqxTranslate }}
</a>
</div>
</ng-container>
@ -38,7 +36,7 @@
<ng-container *sqxModal="expertDialog">
<sqx-modal-dialog (close)="expertDialog.hide()" fullHeight="true" size="lg">
<ng-container title>
Edit Config
{{ 'dashboard.editConfig' | sqxTranslate }}
</ng-container>
<ng-container content>
@ -48,9 +46,9 @@
</ng-container>
<ng-container footer>
<button type="button" class="btn btn-secondary" (click)="expertDialog.hide()">Cancel</button>
<button type="button" class="btn btn-secondary" (click)="expertDialog.hide()">{{ 'common.cancel' | sqxTranslate }}</button>
<button type="button" class="btn btn-primary" (click)="completeExpertMode()">Update</button>
<button type="button" class="btn btn-primary" (click)="completeExpertMode()">{{ 'common.update' | sqxTranslate }}</button>
</ng-container>
</sqx-modal-dialog>
</ng-container>

26
frontend/app/features/dashboard/pages/dashboard-config.component.ts

@ -88,7 +88,7 @@ export class DashboardConfigComponent implements OnChanges {
public saveConfig() {
this.uiState.set('dashboard.grid', this.config, true);
this.dialogs.notifyInfo('Configuration saved.');
this.dialogs.notifyInfo('i18n:dashboard.configSaved');
}
public addOrRemove(item: GridsterItem) {
@ -107,20 +107,20 @@ export class DashboardConfigComponent implements OnChanges {
}
const DEFAULT_CONFIG: GridsterItem[] = [
{ cols: 1, rows: 1, x: 0, y: 0, type: 'schemas', name: 'Schema' },
{ cols: 1, rows: 1, x: 1, y: 0, type: 'api', name: 'API Documentation' },
{ cols: 1, rows: 1, x: 2, y: 0, type: 'support', name: 'Support' },
{ cols: 1, rows: 1, x: 3, y: 0, type: 'github', name: 'Github' },
{ cols: 1, rows: 1, x: 0, y: 0, type: 'schemas', name: 'i18n:common.schemas' },
{ cols: 1, rows: 1, x: 1, y: 0, type: 'api', name: 'i18n:dashboard.apiDocumentation' },
{ cols: 1, rows: 1, x: 2, y: 0, type: 'support', name: 'i18n:common.schemas' },
{ cols: 1, rows: 1, x: 3, y: 0, type: 'github', name: 'i18n:dashboard.github' },
{ cols: 2, rows: 1, x: 0, y: 1, type: 'api-calls', name: 'API Calls Chart' },
{ cols: 2, rows: 1, x: 2, y: 1, type: 'api-performance', name: 'API Performance Chart' },
{ cols: 2, rows: 1, x: 0, y: 1, type: 'api-calls', name: 'i18n:dashboard.apiCallsChart' },
{ cols: 2, rows: 1, x: 2, y: 1, type: 'api-performance', name: 'i18n:dashboard.apiPerformanceChart' },
{ cols: 1, rows: 1, x: 0, y: 2, type: 'api-calls-summary', name: 'API Calls Summary' },
{ cols: 2, rows: 1, x: 1, y: 2, type: 'asset-uploads-count', name: 'Asset Uploads Count Chart' },
{ cols: 1, rows: 1, x: 2, y: 2, type: 'asset-uploads-size-summary', name: 'Asset Uploads Size Chart' },
{ cols: 1, rows: 1, x: 0, y: 2, type: 'api-calls-summary', name: 'i18n:dashboard.apiCallsSummaryCard' },
{ cols: 2, rows: 1, x: 1, y: 2, type: 'asset-uploads-count', name: 'i18n:dashboard.assetUpdloadsCountChart' },
{ cols: 1, rows: 1, x: 2, y: 2, type: 'asset-uploads-size-summary', name: 'i18n:dashboard.assetUploadsSizeChart' },
{ cols: 2, rows: 1, x: 0, y: 3, type: 'asset-uploads-size', name: 'Asset Total Storage Size' },
{ cols: 2, rows: 1, x: 2, y: 3, type: 'api-traffic', name: 'API Traffic Chart' },
{ cols: 2, rows: 1, x: 0, y: 3, type: 'asset-uploads-size', name: 'i18n:dashboard.assetTotalSize' },
{ cols: 2, rows: 1, x: 2, y: 3, type: 'api-traffic', name: 'i18n:dashboard.trafficChart' },
{ cols: 2, rows: 1, x: 0, y: 4, type: 'history', name: 'History' }
{ cols: 2, rows: 1, x: 0, y: 4, type: 'history', name: 'i18n:dashboard.history' }
];

65
frontend/app/features/dashboard/pages/dashboard-page.component.html

@ -1,90 +1,70 @@
<sqx-title message="Dashboard"></sqx-title>
<sqx-title message="i18n:dashboard.pageTitle"></sqx-title>
<ng-container *ngIf="appsState.selectedApp | async; let app">
<div class="dashboard" @fade>
<div class="dashboard-summary" *ngIf="!isScrolled" @fade>
<h1 class="dashboard-title">Hi {{authState.user?.displayName}}</h1>
<div class="dashboard" @fade="">
<div class="dashboard-summary" *ngIf="!isScrolled" @fade="">
<h1 class="dashboard-title">{{ 'dashboard.welcomeTitle' | sqxTranslate: { user: authState.user?.displayName } }}</h1>
<div class="subtext">
Welcome to <strong>{{app.displayName}}</strong> dashboard.
</div>
<div class="subtext" [innerHTML]="'dashboard.welcomeText' | sqxTranslate: { app: app.displayName } | sqxMarkdown"></div>
</div>
<gridster [options]="gridOptions" #grid>
<gridster [options]="gridOptions" #grid="">
<gridster-item [item]="item" *ngFor="let item of gridConfig">
<ng-container [ngSwitch]="item.type">
<ng-container *ngSwitchCase="'schemas'">
<sqx-schema-card
[app]="app">
<sqx-schema-card [app]="app">
</sqx-schema-card>
</ng-container>
<ng-container *ngSwitchCase="'api'">
<sqx-api-card
[app]="app">
<sqx-api-card [app]="app">
</sqx-api-card>
</ng-container>
<ng-container *ngSwitchCase="'support'">
<sqx-support-card
[app]="app">
<sqx-support-card [app]="app">
</sqx-support-card>
</ng-container>
<ng-container *ngSwitchCase="'github'">
<sqx-github-card
[app]="app">
<sqx-github-card [app]="app">
</sqx-github-card>
</ng-container>
<ng-container *ngSwitchCase="'api-calls'">
<sqx-api-calls-card
[app]="app" [usage]="callsUsage">
<sqx-api-calls-card [app]="app" [usage]="callsUsage">
</sqx-api-calls-card>
</ng-container>
<ng-container *ngSwitchCase="'api-performance'">
<sqx-api-performance-card
[isStacked]="isStacked"
(isStackedChange)="changeIsStacked($event)"
[app]="app" [usage]="callsUsage">
<sqx-api-performance-card [isStacked]="isStacked" (isStackedChange)="changeIsStacked($event)" [app]="app" [usage]="callsUsage">
</sqx-api-performance-card>
</ng-container>
<ng-container *ngSwitchCase="'api-calls-summary'">
<sqx-api-calls-summary-card
[app]="app" [usage]="callsUsage">
<sqx-api-calls-summary-card [app]="app" [usage]="callsUsage">
</sqx-api-calls-summary-card>
</ng-container>
<ng-container *ngSwitchCase="'asset-uploads-count'">
<sqx-asset-uploads-count-card
[app]="app" [usage]="storageUsage">
<sqx-asset-uploads-count-card [app]="app" [usage]="storageUsage">
</sqx-asset-uploads-count-card>
</ng-container>
<ng-container *ngSwitchCase="'asset-uploads-size-summary'">
<sqx-asset-uploads-size-summary-card
[app]="app" [usage]="storageCurrent">
<sqx-asset-uploads-size-summary-card [app]="app" [usage]="storageCurrent">
</sqx-asset-uploads-size-summary-card>
</ng-container>
<ng-container *ngSwitchCase="'asset-uploads-size'">
<sqx-asset-uploads-size-card
[app]="app" [usage]="storageUsage">
<sqx-asset-uploads-size-card [app]="app" [usage]="storageUsage">
</sqx-asset-uploads-size-card>
</ng-container>
<ng-container *ngSwitchCase="'api-traffic'">
<sqx-api-traffic-card
[isStacked]="isStacked"
(isStackedChange)="changeIsStacked($event)"
[app]="app" [usage]="callsUsage">
<sqx-api-traffic-card [isStacked]="isStacked" (isStackedChange)="changeIsStacked($event)" [app]="app" [usage]="callsUsage">
</sqx-api-traffic-card>
</ng-container>
<ng-container *ngSwitchCase="'history'">
<sqx-history-card
[app]="app">
<sqx-history-card [app]="app">
</sqx-history-card>
</ng-container>
<ng-container *ngSwitchCase="'content-summary'">
<sqx-content-summary-card
[app]="app" [options]="item">
<sqx-content-summary-card [app]="app" [options]="item">
</sqx-content-summary-card>
</ng-container>
<ng-container *ngSwitchCase="'iframe'">
<sqx-iframe-card
[app]="app" [options]="item">
<sqx-iframe-card [app]="app" [options]="item">
</sqx-iframe-card>
</ng-container>
</ng-container>
@ -92,10 +72,7 @@
</gridster>
<div class="dashboard-settings">
<sqx-dashboard-config [app]="app"
[needsAttention]="isScrolled"
[config]="gridConfig"
(configChange)="changeConfig($event)">
<sqx-dashboard-config [app]="app" [needsAttention]="isScrolled" [config]="gridConfig" (configChange)="changeConfig($event)">
</sqx-dashboard-config>
</div>
</div>

26
frontend/app/features/rules/pages/events/rule-events-page.component.html

@ -1,13 +1,13 @@
<sqx-title message="Events"></sqx-title>
<sqx-title message="i18n:rules.ruleEvents.listPageTitle"></sqx-title>
<sqx-panel desiredWidth="63rem" grid="true">
<ng-container title>
Events
{{ 'common.events' | sqxTranslate }}
</ng-container>
<ng-container menu>
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="Refresh Events (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
<button type="button" class="btn btn-text-secondary" (click)="reload()" title="i18n:rules.refreshEventsTooltip">
<i class="icon-reset"></i> {{ 'common.refresh' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
@ -20,16 +20,16 @@
<thead>
<tr>
<th class="cell-label">
Status
{{ 'common.status' | sqxTranslate }}
</th>
<th class="cell-40">
Event
{{ 'common.event' | sqxTranslate }}
</th>
<th class="cell-60">
Description
{{ 'common.description' | sqxTranslate }}
</th>
<th class="cell-time">
Created
{{ 'common.created' | sqxTranslate }}
</th>
<th class="cell-actions"></th>
</tr>
@ -58,7 +58,7 @@
<tr *ngIf="selectedEventId === event.id">
<td colspan="5">
<div class="event-header">
<h3>Last Invocation</h3>
<h3>{{ 'rules.ruleEvents.lastInvokedLabel' | sqxTranslate }}</h3>
</div>
<div class="row no-gutters event-stats align-items-center">
@ -66,18 +66,18 @@
<span class="badge badge-pill badge-{{event.result | sqxRuleEventBadgeClass}}">{{event.result}}</span>
</div>
<div class="col-2">
Attempts: {{event.numCalls}}
{{ 'rules.ruleEvents.numAttemptsLabel' | sqxTranslate }}: {{event.numCalls}}
</div>
<div class="col-4">
Next: <ng-container *ngIf="event.nextAttempt">{{event.nextAttempt | sqxFromNow}}</ng-container>
{{ 'rules.ruleEvents.nextAttemptLabel' | sqxTranslate }}: <ng-container *ngIf="event.nextAttempt">{{event.nextAttempt | sqxFromNow}}</ng-container>
</div>
<div class="col-3 text-right">
<button type="button" class="btn btn-text-danger btn-sm mr-1" (click)="cancel(event)" [class.hidden]="!event.nextAttempt">
Cancel
{{ 'common.cancel' | sqxTranslate }}
</button>
<button type="button" class="btn btn-success btn-sm" (click)="enqueue(event)">
Enqueue
{{ 'rules.ruleEvents.enqueue' | sqxTranslate }}
</button>
</div>
</div>

2
frontend/app/features/rules/pages/rules/rule-element.component.html

@ -24,7 +24,7 @@
</div>
<div class="large-link" *ngIf="element.readMore">
<a [href]="element.readMore" sqxStopClick sqxExternalLink>Read More</a>
<a [href]="element.readMore" sqxStopClick sqxExternalLink>{{ 'rules.readMore' | sqxTranslate }}</a>
</div>
</div>
</div>

53
frontend/app/features/rules/pages/rules/rule-wizard.component.html

@ -1,13 +1,13 @@
<sqx-modal-dialog size="lg" fullHeight="true" (close)="emitComplete()" [showFooter]="step === 2 || step === 4">
<ng-container title>
<ng-container *ngIf="mode === 'EditTrigger'">
Edit Trigger
{{ 'rules.triggerEdit' | sqxTranslate }}
</ng-container>
<ng-container *ngIf="mode === 'EditAction'">
Edit Action
{{ 'rules.actionEdit' | sqxTranslate }}
</ng-container>
<ng-container *ngIf="mode === 'Wizard'">
Create new Rule
{{ 'rules.create' | sqxTranslate }}
</ng-container>
</ng-container>
@ -16,21 +16,21 @@
<ol class="breadcrumb steps">
<li class="breadcrumb-item" [class.active]="step === 1" [class.done]="step > 1">
<a class="force" (click)="go(1)" [class.disabled]="step <= 1">
<i class="icon-checkmark"></i> Select Trigger
<i class="icon-checkmark"></i> {{ 'rules.wizard.selectTrigger' | sqxTranslate }}
</a>
</li>
<li class="breadcrumb-item" [class.active]="step === 2" [class.done]="step > 2">
<a class="force" (click)="go(2)" [class.disabled]="step <= 2">
<i class="icon-checkmark"></i> Edit Trigger
<i class="icon-checkmark"></i> {{ 'rules.triggerEdit' | sqxTranslate }}
</a>
</li>
<li class="breadcrumb-item" [class.active]="step === 3" [class.done]="step > 3">
<a class="force" (click)="go(3)" [class.disabled]="step <= 3">
<i class="icon-checkmark"></i> Select Action
<i class="icon-checkmark"></i> {{ 'rules.wizard.selectAction' | sqxTranslate }}
</a>
</li>
<li class="breadcrumb-item" [class.active]="step === 4">
<i class="icon-checkmark"></i> Edit Action
<i class="icon-checkmark"></i> {{ 'rules.actionEdit' | sqxTranslate }}
</li>
</ol>
</nav>
@ -38,7 +38,7 @@
<ng-container [ngSwitch]="step">
<ng-container *ngSwitchCase="1">
<sqx-form-alert>
The selection of the trigger type cannot be changed later.
{{ 'rules.wizard.triggerHint' | sqxTranslate }}
</sqx-form-alert>
<div class="row no-gutters">
@ -57,34 +57,23 @@
<ng-container [ngSwitch]="trigger.triggerType">
<ng-container *ngSwitchCase="'AssetChanged'">
<sqx-asset-changed-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form">
<sqx-asset-changed-trigger [trigger]="trigger" [triggerForm]="triggerForm.form">
</sqx-asset-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'Comment'">
<sqx-comment-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form">
<sqx-comment-trigger [trigger]="trigger" [triggerForm]="triggerForm.form">
</sqx-comment-trigger>
</ng-container>
<ng-container *ngSwitchCase="'ContentChanged'">
<sqx-content-changed-trigger
[schemas]="schemas"
[trigger]="trigger"
[triggerForm]="triggerForm.form">
<sqx-content-changed-trigger [schemas]="schemas" [trigger]="trigger" [triggerForm]="triggerForm.form">
</sqx-content-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'SchemaChanged'">
<sqx-schema-changed-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form">
<sqx-schema-changed-trigger [trigger]="trigger" [triggerForm]="triggerForm.form">
</sqx-schema-changed-trigger>
</ng-container>
<ng-container *ngSwitchCase="'Usage'">
<sqx-usage-trigger
[trigger]="trigger"
[triggerForm]="triggerForm.form">
<sqx-usage-trigger [trigger]="trigger" [triggerForm]="triggerForm.form">
</sqx-usage-trigger>
</ng-container>
</ng-container>
@ -92,7 +81,7 @@
</ng-container>
<ng-container *ngSwitchCase="3">
<sqx-form-alert marginTop="0">
The selection of the action type cannot be changed later.
{{ 'rules.wizard.actionHint' | sqxTranslate }}
</sqx-form-alert>
<div class="row no-gutters">
@ -109,11 +98,7 @@
{{actionElement.display}}
</h3>
<sqx-generic-action
[definition]="actionElement"
[action]="action"
[actionForm]="actionForm.form">
</sqx-generic-action>
<sqx-generic-action [definition]="actionElement" [action]="action" [actionForm]="actionForm.form"></sqx-generic-action>
</form>
</ng-container>
</ng-container>
@ -121,14 +106,14 @@
<ng-container footer>
<ng-container *ngIf="step === 2 || step === 4">
<button type="button" class="btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="button" class="btn btn-secondary" (click)="emitComplete()">{{ 'common.cancel' | sqxTranslate }}</button>
</ng-container>
<ng-container *ngIf="isEditable">
<button *ngIf="step === 2 && isWizard" type="submit" class="btn btn-primary" (click)="saveTrigger()">Next</button>
<button *ngIf="step === 2 && !isWizard" type="submit" class="btn btn-primary" (click)="saveTrigger()">Save</button>
<button *ngIf="step === 2 && isWizard" type="submit" class="btn btn-primary" (click)="saveTrigger()">{{ 'common.continue' | sqxTranslate }}</button>
<button *ngIf="step === 2 && !isWizard" type="submit" class="btn btn-primary" (click)="saveTrigger()">{{ 'common.save' | sqxTranslate }}</button>
<button *ngIf="step === 4" type="submit" class="btn btn-primary" (click)="saveAction()">Save</button>
<button *ngIf="step === 4" type="submit" class="btn btn-primary" (click)="saveAction()">{{ 'common.save' | sqxTranslate }}</button>
</ng-container>
</ng-container>
</sqx-modal-dialog>

38
frontend/app/features/rules/pages/rules/rule.component.html

@ -2,13 +2,12 @@
<div class="card-header">
<div class="row">
<div class="col col-name">
<sqx-editable-title
fallback="Unnamed Rule"
<sqx-editable-title [disabled]="!rule.canUpdate"
[fallback]="'rules.unnamed' | sqxTranslate"
[name]="rule.name"
(nameChange)="rename($event)"
[maxLength]="60"
[isRequired]="false"
[disabled]="!rule.canUpdate">
[isRequired]="false">
</sqx-editable-title>
</div>
<div class="col-auto" *ngIf="rule.canDelete || rule.canRun">
@ -20,16 +19,16 @@
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-right" @fade>
<a class="dropdown-item" *ngIf="rule.canRun"
(sqxConfirmClick)="run()"
confirmTitle="Run rule"
confirmText="Do you really want to run the rule for all events?">
Run
confirmTitle="i18n:rules.runRuleConfirmTitle"
confirmText="i18n:rules.runRuleConfirmText">
{{ 'rules.run' | sqxTranslate }}
</a>
<a class="dropdown-item dropdown-item-delete" *ngIf="rule.canDelete"
(sqxConfirmClick)="delete()"
confirmTitle="Delete rule"
confirmText="Do you really want to delete the rule?">
Delete
confirmTitle="i18n:rules.deleteConfirmTitle"
confirmText="i18n:rules.deleteConfirmText">
{{ 'common.delete' | sqxTranslate }}
</a>
</div>
</ng-container>
@ -39,7 +38,7 @@
<div class="card-body">
<div class="row align-items-center">
<div class="col-auto">
<h3>If</h3>
<h3>{{ 'rules.ruleSyntax.if' | sqxTranslate }}</h3>
</div>
<div class="col">
<span (click)="emitEditTrigger()">
@ -47,7 +46,7 @@
</span>
</div>
<div class="col-auto">
<h3>then</h3>
<h3>{{ 'rules.ruleSyntax.then' | sqxTranslate }}</h3>
</div>
<div class="col">
<span (click)="emitEditAction()">
@ -56,11 +55,10 @@
</div>
<div class="col col-last text-right">
<ng-container *ngIf="isManual; else notManual">
<button class="btn btn-secondary"
[disabled]="!rule.canTrigger"
<button class="btn btn-secondary" [disabled]="!rule.canTrigger"
(sqxConfirmClick)="trigger()"
confirmTitle="Trigger rule"
confirmText="Do you really want to trigger the rule?">
confirmTitle="i18n:rules.triggerConfirmTitle"
confirmText="i18n:rules.triggerConfirmText">
<i class="icon-play-line"></i>
</button>
</ng-container>
@ -74,17 +72,17 @@
<div class="card-footer">
<div class="row">
<div class="col-3">
Succeeded: <strong>{{rule.numSucceeded}}</strong>
{{ 'common.succeeded' | sqxTranslate }}: <strong>{{rule.numSucceeded}}</strong>
</div>
<div class="col-3">
Failed: <strong>{{rule.numFailed}}</strong>
{{ 'common.failed' | sqxTranslate }}: <strong>{{rule.numFailed}}</strong>
</div>
<div class="col">
Executed: <span>{{rule.lastExecuted | sqxFromNow:'-'}}</span>
{{ 'common.executed' | sqxTranslate }}: <span>{{rule.lastExecuted | sqxFromNow:'-'}}</span>
</div>
<div class="col-auto">
<a routerLink="events" [queryParams]="{ ruleId: rule.id }" *ngIf="rule.canTrigger">
Logs
{{ 'common.logs' | sqxTranslate }}
</a>
</div>
</div>

29
frontend/app/features/rules/pages/rules/rules-page.component.html

@ -1,13 +1,13 @@
<sqx-title message="Rules"></sqx-title>
<sqx-title message="i18n:rules.listPageTitle"></sqx-title>
<sqx-panel desiredWidth="54rem" showSidebar="true" grid="true">
<ng-container title>
Rules
{{ 'common.rules' | sqxTranslate }}
</ng-container>
<ng-container menu>
<button type="button" class="btn btn-text-secondary mr-1" (click)="reload()" title="Refresh Rules (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
<button type="button" class="btn btn-text-secondary mr-1" (click)="reload()" title="i18n:rules.refreshTooltip">
<i class="icon-reset"></i> {{ 'common.refresh' | sqxTranslate }}
</button>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
@ -15,8 +15,8 @@
<ng-container *ngIf="rulesState.canCreate | async">
<sqx-shortcut keys="ctrl+shift+g" (trigger)="buttonNew.click()"></sqx-shortcut>
<button type="button" class="btn btn-success" #buttonNew (click)="createNew()" title="New Rule (CTRL + M)">
<i class="icon-plus"></i> New
<button type="button" class="btn btn-success" #buttonNew (click)="createNew()" title="i18n:rules.createTooltip">
<i class="icon-plus"></i> {{ 'rules.create' | sqxTranslate }}
</button>
</ng-container>
</ng-container>
@ -26,20 +26,20 @@
<ng-container topHeader>
<div class="panel-alert panel-alert-danger" *ngIf="rulesState.runningRule | async; let runningRule">
<div class="float-right">
<a class="force" (click)="cancelRun()">Cancel</a>
<a class="force" (click)="cancelRun()">{{ 'common.cancel' | sqxTranslate }}</a>
</div>
Rule '{{runningRule.name || 'Unnamed Rule'}}' is currently running.
{{ 'rules.runningRule' | sqxTranslate: { name: runningRule.name || 'Unnamed Rule' } }}
</div>
</ng-container>
<div content>
<ng-container *ngIf="ruleActions && ruleTriggers && (rulesState.isLoaded | async) && (rulesState.rules | async); let rules">
<div class="table-items-row table-items-row-empty" *ngIf="rules.length === 0">
No rule created yet.
{{ 'rules.empty' | sqxTranslate }}
<button type="button" class="btn btn-success btn-sm ml-2" (click)="createNew()" *ngIf="rulesState.canCreate | async">
<i class="icon icon-plus"></i> Add Rule
<i class="icon icon-plus"></i> {{ 'rules.emptyAddRule' | sqxTranslate }}
</button>
</div>
@ -56,8 +56,7 @@
</table>
<ng-container *sqxModal="addRuleDialog">
<sqx-rule-wizard
[schemas]="schemasState.schemas | async"
<sqx-rule-wizard [schemas]="schemasState.schemas | async"
[rule]="wizardRule"
[ruleActions]="ruleActions"
[ruleTriggers]="ruleTriggers"
@ -73,17 +72,17 @@
<ng-container sidebar>
<div class="panel-nav">
<ng-container *ngIf="rulesState.canReadEvents | async">
<a class="panel-link panel-link-gray" routerLink="events" routerLinkActive="active" title="History" titlePosition="left">
<a class="panel-link panel-link-gray" routerLink="events" routerLinkActive="active" title="i18n:common.history" titlePosition="left">
<i class="icon-time"></i>
</a>
</ng-container>
<a class="panel-link" routerLink="help" routerLinkActive="active" title="Help" titlePosition="left" #helpLink>
<a class="panel-link" routerLink="help" routerLinkActive="active" title="i18n:common.help" titlePosition="left" #helpLink>
<i class="icon-help2"></i>
</a>
<sqx-onboarding-tooltip helpId="help" [for]="helpLink" position="left-top" after="180000">
Click the help icon to show a context specific help page. Go to <a href="https://docs.squidex.io" sqxExternalLink>https://docs.squidex.io</a> for the full documentation.
<span [innerHTML]="'common.helpTour' | sqxTranslate | sqxMarkdownInline"></span>
</sqx-onboarding-tooltip>
</div>
</ng-container>

26
frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.html

@ -1,45 +1,47 @@
<form [formGroup]="fieldForm.form" (ngSubmit)="saveSchema()">
<div class="card">
<div class="card-header">Common</div>
<div class="card-header">{{ 'common.generalSettings' | sqxTranslate }}</div>
<div class="card-body">
<div class="form-group">
<label for="schemaName">Name</label>
<label for="name">{{ 'common.name' | sqxTranslate }}</label>
<input type="text" class="form-control" id="schemaName" readonly [ngModel]="schema.name" [ngModelOptions]="standalone" />
<input type="text" class="form-control" id="name" readonly [ngModel]="schema.name" [ngModelOptions]="standalone">
</div>
<div class="form-group">
<label for="schemaLabel">Label</label>
<label for="label">{{ 'common.label' | sqxTranslate }}</label>
<sqx-control-errors for="label"></sqx-control-errors>
<input type="text" class="form-control" id="schemaLabel" formControlName="label" />
<input type="text" class="form-control" id="label" formControlName="label">
<sqx-form-hint>Display name for documentation and user interfaces.</sqx-form-hint>
<sqx-form-hint>{{ 'schemas.schemaLabelHint' | sqxTranslate }}</sqx-form-hint>
</div>
<div class="form-group">
<label for="schemaHints">Hints</label>
<label for="hints">{{ 'common.hints' | sqxTranslate }}</label>
<sqx-control-errors for="hints"></sqx-control-errors>
<textarea type="text" class="form-control" id="schemaHints" formControlName="hints" rows="4"></textarea>
<textarea type="text" class="form-control" id="hints" formControlName="hints" rows="4"></textarea>
<sqx-form-hint>{{ 'schemas.schemaHintsHint' | sqxTranslate }}</sqx-form-hint>
</div>
<div class="form-group">
<label for="schemaTags">Tags</label>
<label for="tags">{{ 'common.tags' | sqxTranslate }}</label>
<sqx-control-errors for="tags"></sqx-control-errors>
<sqx-tag-editor id="schemaTags" formControlName="tags"></sqx-tag-editor>
<sqx-tag-editor id="tags" formControlName="tags"></sqx-tag-editor>
<sqx-form-hint>Tags to annotate your schema for automation processes.</sqx-form-hint>
<sqx-form-hint>{{ 'schemas.schemaTagsHint' | sqxTranslate }}</sqx-form-hint>
</div>
</div>
<div class="card-footer">
<button type="submit" class="float-right btn btn-primary" *ngIf="isEditable">Save</button>
<button type="submit" class="float-right btn btn-primary" *ngIf="isEditable">{{ 'common.save' | sqxTranslate }}</button>
</div>
</div>
</form>

10
frontend/app/features/schemas/pages/schema/export/schema-export-form.component.html

@ -5,22 +5,22 @@
<div class="col-auto">
<div class="form-inline">
<div class="form-check pr-4">
<input class="form-check-input" type="checkbox" id="fieldsDelete" formControlName="fieldsDelete" />
<input class="form-check-input" type="checkbox" id="fieldsDelete" formControlName="fieldsDelete">
<label class="form-check-label" for="fieldsDelete">
Delete fields
{{ 'schemas.export.deleteFields' | sqxTranslate }}
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="fieldsRecreate" formControlName="fieldsRecreate" />
<input class="form-check-input" type="checkbox" id="fieldsRecreate" formControlName="fieldsRecreate">
<label class="form-check-label" for="fieldsRecreate">
Recreate fields
{{ 'schemas.export.recreateFields' | sqxTranslate }}
</label>
</div>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary">Synchronize</button>
<button type="submit" class="btn btn-primary">{{ 'schemas.export.synchronize' | sqxTranslate }}</button>
</div>
</div>
</div>

35
frontend/app/features/schemas/pages/schema/fields/field-wizard.component.html

@ -1,7 +1,7 @@
<sqx-modal-dialog (close)="emitComplete()" size="lg">
<ng-container title>
<ng-container *ngIf="parent; else noParent">
Add Nested Field
{{ 'schemas.addNestedField' | sqxTranslate }}
</ng-container>
<ng-template #noParent>
@ -18,7 +18,7 @@
<div class="row">
<div class="col-4 type" *ngFor="let fieldType of fieldTypes">
<label>
<input type="radio" class="radio-input" name="type" formControlName="type" value="{{fieldType.type}}" />
<input type="radio" class="radio-input" name="type" formControlName="type" value="{{fieldType.type}}">
<div class="row no-gutters">
<div class="col-auto">
@ -28,7 +28,7 @@
</div>
<div class="col">
<div class="type-title">{{fieldType.type}}</div>
<div class="type-text text-muted">{{fieldType.description}}</div>
<div class="type-text text-muted">{{fieldType.description | sqxTranslate}}</div>
</div>
</div>
</label>
@ -39,53 +39,48 @@
<div class="form-group">
<sqx-control-errors for="name" submitOnly="true"></sqx-control-errors>
<input type="text" class="form-control" formControlName="name" maxlength="40" #nameInput
placeholder="Enter field name" sqxFocusOnInit />
<input type="text" class="form-control" formControlName="name" maxlength="40" #nameInput placeholder="{{ 'schemas.field.namePlaceholder' | sqxTranslate }}" sqxFocusOnInit>
</div>
<div class="form-group" *ngIf="!parent && (addFieldForm.isContentField | async)">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="isLocalizable" formControlName="isLocalizable" />
<input class="form-check-input" type="checkbox" id="isLocalizable" formControlName="isLocalizable">
<label class="form-check-label" for="isLocalizable">
Localizable
{{ 'schemas.field.localizable' | sqxTranslate }}
</label>
</div>
<sqx-form-hint>
You can mark the field as localizable. It means that is dependent on the language, for example a city name.
{{ 'schemas.field.localizableHint' | sqxTranslate }}
</sqx-form-hint>
</div>
<sqx-form-alert class="mt-4">
These values cannot be changed later.
{{ 'schemas.nameWarning' | sqxTranslate }}
</sqx-form-alert>
</form>
</ng-container>
<ng-template #notEditing>
<form [formGroup]="editForm.form" class="edit-form" (ngSubmit)="save()">
<sqx-field-form
[isEditable]="true"
[field]="field"
[fieldForm]="editForm.form"
[patterns]="patternsState.patterns | async">
<sqx-field-form [isEditable]="true" [field]="field" [fieldForm]="editForm.form" [patterns]="patternsState.patterns | async">
</sqx-field-form>
</form>
</ng-template>
</ng-container>
<ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">Cancel</button>
<button type="reset" class="float-left btn btn-secondary" (click)="emitComplete()">{{ 'common.cancel' | sqxTranslate }}</button>
<div *ngIf="!editing">
<button type="button" class="btn btn-outline-success" (click)="addField(false)">Create and close</button>
<button type="button" class="btn btn-success ml-1" (click)="addField(true)">Create and add field</button>
<button type="button" class="btn btn-success ml-1" (click)="addField(false, true)">Create and edit field</button>
<button type="button" class="btn btn-outline-success" (click)="addField(false)">{{ 'schemas.addFieldAndClose' | sqxTranslate }}</button>
<button type="button" class="btn btn-success ml-1" (click)="addField(true)">{{ 'schemas.addFieldAndCreate' | sqxTranslate }}</button>
<button type="button" class="btn btn-success ml-1" (click)="addField(false, true)">{{ 'schemas.addFieldAndEdit' | sqxTranslate }}</button>
</div>
<div *ngIf="editing">
<button type="button" class="btn btn-success" (click)="save(true)">Save and add field</button>
<button type="button" class="btn btn-primary ml-1" (click)="save()">Save and close</button>
<button type="button" class="btn btn-success" (click)="save(true)">{{ 'schemas.saveFieldAndNew' | sqxTranslate }}</button>
<button type="button" class="btn btn-primary ml-1" (click)="save()">{{ 'schemas.saveFieldAndClose' | sqxTranslate }}</button>
</div>
</ng-container>
</sqx-modal-dialog>

65
frontend/app/features/schemas/pages/schema/fields/field.component.html

@ -9,15 +9,30 @@
<span class="field-name">
<i class="field-icon icon-type-{{field.properties.fieldType}}"></i>
<span [class.field-hidden]="field.isHidden" title="{{field.isHidden ? 'Hidden Field' : 'Visible Field'}}">{{field.displayName}}</span>
<span class="field-partitioning ml-2" *ngIf="field['isLocalizable']">localizable</span>
<ng-container *ngIf="field.isHidden else visible">
<span class="field-hidden" title="i18n:schemas.field.hiddenMarker">{{field.displayName}}</span>
</ng-container>
<ng-template #visible>
<span title="schema.field.visibleMarker" title="i18n:schema.field.visibleMarker">{{field.displayName}}</span>
</ng-template>
<span class="field-partitioning ml-2" *ngIf="field['isLocalizable']">{{ 'schemas.field.localizableMarker' | sqxTranslate }}</span>
</span>
</div>
<div class="col col-tags">
<div class="float-right">
<span class="ml-1 badge badge-pill badge-danger" *ngIf="field.isLocked">Locked</span>
<span class="ml-1 badge badge-pill badge-success" *ngIf="!field.isDisabled">Enabled</span>
<span class="ml-1 badge badge-pill badge-danger" *ngIf="field.isDisabled">Disabled</span>
<span class="ml-1 badge badge-pill badge-danger" *ngIf="field.isLocked">
{{ 'schemas.field.lockedMarker' | sqxTranslate }}
</span>
<span class="ml-1 badge badge-pill badge-success" *ngIf="!field.isDisabled">
{{ 'schemas.field.enabledMarker' | sqxTranslate }}
</span>
<span class="ml-1 badge badge-pill badge-danger" *ngIf="field.isDisabled">
{{ 'schemas.field.disabledMarker' | sqxTranslate }}
</span>
</div>
</div>
<div class="col col-options">
@ -35,16 +50,16 @@
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" @fade>
<ng-container *ngIf="field.properties.isContentField">
<a class="dropdown-item" (click)="enableField()" *ngIf="field.canEnable">
Enable in UI
{{ 'schemas.field.enable' | sqxTranslate }}
</a>
<a class="dropdown-item" (click)="disableField()" *ngIf="field.canDisable">
Disable in UI
{{ 'schemas.field.disable' | sqxTranslate }}
</a>
<a class="dropdown-item" (click)="hideField()" *ngIf="field.canHide">
Hide in API
{{ 'schemas.field.hide' | sqxTranslate }}
</a>
<a class="dropdown-item" (click)="showField()" *ngIf="field.canShow">
Show in API
{{ 'schemas.field.show' | sqxTranslate }}
</a>
</ng-container>
@ -53,21 +68,20 @@
<a class="dropdown-item"
(sqxConfirmClick)="lockField()"
confirmTitle="Lock field"
confirmText="WARNING: Locking a field cannot be undone! Locked field definitions cannot be unlocked, deleted, or changed anymore. Do you really want to lock this field?">
Lock and prevent changes
confirmTitle="i18n:schemas.field.lockConfirmText"
confirmText="i18n:schemas.field.lockConfirmText">
{{ 'schemas.field.lock' | sqxTranslate }}
</a>
</ng-container>
<ng-container>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete"
[class.disabled]="!field.canDelete"
<a class="dropdown-item dropdown-item-delete" [class.disabled]="!field.canDelete"
(sqxConfirmClick)="deleteField()"
confirmTitle="Delete field"
confirmText="Do you really want to delete the field?">
Delete
confirmTitle="i18n:schemas.field.deleteConfirmTitle"
confirmText="i18n:schemas.field.deleteConfirmText">
{{ 'common.delete' | sqxTranslate }}
</a>
</ng-container>
</div>
@ -80,12 +94,7 @@
<div class="table-items-row-details" *ngIf="isEditing">
<form [formGroup]="editForm.form" (ngSubmit)="save()">
<sqx-field-form showButtons="true"
(cancel)="toggleEditing()"
[patterns]="patterns"
[fieldForm]="editForm.form"
[field]="field"
[isEditable]="isEditable">
<sqx-field-form showButtons="true" (cancel)="toggleEditing()" [patterns]="patterns" [fieldForm]="editForm.form" [field]="field" [isEditable]="isEditable">
</sqx-field-form>
</form>
</div>
@ -99,10 +108,7 @@
[cdkDropListDisabled]="!isEditable"
[cdkDropListData]="nested"
(cdkDropListDropped)="sortFields($event)">
<div *ngFor="let nested of nested; trackBy: trackByFieldFn"
class="nested-field table-drag"
cdkDrag
cdkDragLockAxis="y">
<div *ngFor="let nested of nested; trackBy: trackByFieldFn" class="nested-field table-drag" cdkDrag cdkDragLockAxis="y">
<span class="nested-field-line-h"></span>
@ -116,13 +122,12 @@
<span class="nested-field-line-h"></span>
<button type="button" class="btn btn-success btn-sm" (click)="addFieldDialog.show()">
<i class="icon icon-plus"></i> Add Nested Field
<i class="icon icon-plus"></i> {{ 'schemas.addNestedField' | sqxTranslate }}
</button>
</div>
<ng-container *sqxModal="addFieldDialog">
<sqx-field-wizard [schema]="schema" [parent]="field"
(complete)="addFieldDialog.hide()">
<sqx-field-wizard [schema]="schema" [parent]="field" (complete)="addFieldDialog.hide()">
</sqx-field-wizard>
</ng-container>
</ng-container>

22
frontend/app/features/schemas/pages/schema/fields/forms/field-form-common.component.html

@ -1,52 +1,52 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldName">Name</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldName">{{ 'common.name' | sqxTranslate }}</label>
<div class="col-7">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldName" readonly [ngModel]="field.name" [ngModelOptions]="standalone" />
<input type="text" class="form-control" id="{{field.fieldId}}_fieldName" readonly [ngModel]="field.name" [ngModelOptions]="standalone">
<sqx-form-hint>
The name of the field in the API response.
{{ 'schemas.field.nameHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldLabel">Label</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldLabel">{{ 'common.label' | sqxTranslate }}</label>
<div class="col-7">
<sqx-control-errors for="label"></sqx-control-errors>
<input type="text" class="form-control" id="{{field.fieldId}}_fieldLabel" maxlength="100" formControlName="label" />
<input type="text" class="form-control" id="{{field.fieldId}}_fieldLabel" maxlength="100" formControlName="label">
<sqx-form-hint>
Display name for documentation and user interfaces.
{{ 'schemas.field.labelHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldHints">Hints</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldHints">{{ 'common.hints' | sqxTranslate }}</label>
<div class="col-7">
<sqx-control-errors for="hints"></sqx-control-errors>
<input type="text" class="form-control" id="{{field.fieldId}}_fieldHints" maxlength="100" formControlName="hints" />
<input type="text" class="form-control" id="{{field.fieldId}}_fieldHints" maxlength="100" formControlName="hints">
<sqx-form-hint>
Describe this field for documentation and user interfaces.
{{ 'schemas.field.hintsHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
<div class="form-group row" *ngIf="field.properties.isContentField">
<label class="col-3 col-form-label">Tags</label>
<label class="col-3 col-form-label">{{ 'common.tags' | sqxTranslate }}</label>
<div class="col-7">
<sqx-tag-editor id="schemaTags" formControlName="tags"></sqx-tag-editor>
<sqx-form-hint>
Tags to annotate your field for automation processes.
{{ 'schemas.field.tagsHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>

6
frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html

@ -1,12 +1,12 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_editorUrl">Editor Url</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_editorUrl">{{ 'schemas.field.editorUrl' | sqxTranslate }}</label>
<div class="col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl" />
<input type="text" class="form-control" id="{{field.fieldId}}_editorUrl" formControlName="editorUrl">
<sqx-form-hint>
Url to your plugin if you use a custom editor.
{{ 'schemas.field.editorUrlHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>

4
frontend/app/features/schemas/pages/schema/fields/forms/field-form-validation.component.html

@ -2,9 +2,9 @@
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldRequired" formControlName="isRequired" />
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldRequired" formControlName="isRequired">
<label class="form-check-label" for="{{field.fieldId}}_fieldRequired">
Required
{{ 'schemas.field.required' | sqxTranslate }}
</label>
</div>
</div>

10
frontend/app/features/schemas/pages/schema/fields/forms/field-form.component.html

@ -1,20 +1,20 @@
<div class="table-items-row-details-tabs clearfix">
<ul class="nav nav-tabs2">
<li class="nav-item">
<a class="nav-link" (click)="selectTab(0)" [class.active]="selectedTab === 0">Common</a>
<a class="nav-link" (click)="selectTab(0)" [class.active]="selectedTab === 0">{{ 'schemas.field.tabCommon' | sqxTranslate }}</a>
</li>
<li class="nav-item" [class.hidden]="!field.properties.isContentField">
<a class="nav-link" (click)="selectTab(1)" [class.active]="selectedTab === 1">Validation</a>
<a class="nav-link" (click)="selectTab(1)" [class.active]="selectedTab === 1">{{ 'schemas.field.tabValidation' | sqxTranslate }}</a>
</li>
<li class="nav-item" [class.hidden]="!field.properties.isContentField || field.properties.fieldType === 'Array'">
<a class="nav-link" (click)="selectTab(2)" [class.active]="selectedTab === 2">Editing</a>
<a class="nav-link" (click)="selectTab(2)" [class.active]="selectedTab === 2">{{ 'schemas.field.tabEditing' | sqxTranslate }}</a>
</li>
</ul>
<div class="float-right" *ngIf="showButtons">
<button [disabled]="field.isLocked" type="reset" class="btn btn-text-secondary2" (click)="cancel.emit()">Cancel</button>
<button [disabled]="field.isLocked" type="reset" class="btn btn-text-secondary2" (click)="cancel.emit()">{{ 'common.cancel' | sqxTranslate }}</button>
<button [disabled]="field.isLocked" type="submit" class="btn btn-primary ml-1" *ngIf="isEditable">Save</button>
<button [disabled]="field.isLocked" type="submit" class="btn btn-primary ml-1" *ngIf="isEditable">{{ 'common.save' | sqxTranslate }}</button>
</div>
</div>

14
frontend/app/features/schemas/pages/schema/fields/schema-fields.component.html

@ -1,8 +1,8 @@
<div class="table-items-row table-items-row-empty" *ngIf="schema && schema.fields.length === 0">
No field created yet.
{{ 'schemas.field.empty' | sqxTranslate }}
<button type="button" class="btn btn-success btn-sm ml-2" (click)="addFieldDialog.show()" *ngIf="schema.canAddField">
<i class="icon icon-plus"></i> Add Field
<i class="icon icon-plus"></i> {{ 'schemas.addField' | sqxTranslate }}
</button>
</div>
@ -12,10 +12,7 @@
[cdkDropListDisabled]="!schema.canOrderFields"
[cdkDropListData]="schema.fields"
(cdkDropListDropped)="sortFields($event)">
<div *ngFor="let field of schema.fields; trackBy: trackByFieldFn"
class="table-drag"
cdkDrag
cdkDragLockAxis="y">
<div *ngFor="let field of schema.fields; trackBy: trackByFieldFn" class="table-drag" cdkDrag cdkDragLockAxis="y">
<sqx-field [field]="field" [schema]="schema" [patterns]="patterns">
<i cdkDragHandle class="icon-drag2 drag-handle"></i>
</sqx-field>
@ -23,12 +20,11 @@
</div>
<button type="button" class="btn btn-success field-button" (click)="addFieldDialog.show()" *ngIf="schema.canAddField">
<i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">Add Field</div>
<i class="icon icon-plus field-button-icon"></i> <div class="field-button-text">{{ 'schemas.addField' | sqxTranslate }}</div>
</button>
</ng-container>
<ng-container *sqxModal="addFieldDialog">
<sqx-field-wizard [schema]="schema"
(complete)="addFieldDialog.hide()">
<sqx-field-wizard [schema]="schema" (complete)="addFieldDialog.hide()">
</sqx-field-wizard>
</ng-container>

6
frontend/app/features/schemas/pages/schema/fields/types/array-validation.component.html

@ -1,14 +1,14 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label">Items</label>
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.array.count' | sqxTranslate }}</label>
<div class="col-3 minmax-col">
<input type="number" class="form-control" formControlName="minItems" placeholder="Min Items" />
<input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.array.countMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div>
<div class="col-3">
<input type="number" class="form-control" formControlName="maxItems" placeholder="Max Items" />
<input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.array.countMax' | sqxTranslate }}">
</div>
</div>
</div>

16
frontend/app/features/schemas/pages/schema/fields/types/assets-ui.component.html

@ -1,17 +1,17 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_previewMode">PreviewMode</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_previewMode">{{ 'schemas.fieldTypes.assets.previewMode' | sqxTranslate }}</label>
<div class="col-6">
<select type="text" class="form-control" id="{{field.fieldId}}_previewMode" formControlName="previewMode">
<option value="ImageAndFileName">Thumbnail and file name</option>
<option value="Image">Only thumbnail or file name if not an image</option>
<option value="FileName">Only file name</option>
<option value="ImageAndFileName">{{ 'schemas.fieldTypes.assets.previewModeTumbnailName' | sqxTranslate }}</option>
<option value="Image">{{ 'schemas.fieldTypes.assets.previewModeTumbnailOrName' | sqxTranslate }}</option>
<option value="FileName">{{ 'schemas.fieldTypes.assets.previewModeName' | sqxTranslate }}</option>
</select>
<sqx-form-hint>
The preview mode for assets in content lists.
{{ 'schemas.fieldTypes.assets.previewModeHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
@ -19,14 +19,14 @@
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldResolveFirst" formControlName="resolveFirst" />
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldResolveFirst" formControlName="resolveFirst">
<label class="form-check-label" for="{{field.fieldId}}_fieldResolveFirst">
Resolve first asset
{{ 'schemas.fieldTypes.assets.resolve' | sqxTranslate }}
</label>
</div>
<sqx-form-hint>
Show the first referenced asset in the content list.
{{ 'schemas.fieldTypes.assets.resolveHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>

42
frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html

@ -1,54 +1,54 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label">Items</label>
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.assets.count' | sqxTranslate }}</label>
<div class="col-3 minmax-col">
<input type="number" class="form-control" formControlName="minItems" placeholder="Min Assets" />
<input type="number" class="form-control" formControlName="minItems" placeholder="{{ 'schemas.fieldTypes.assets.countMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div>
<div class="col-3">
<input type="number" class="form-control" formControlName="maxItems" placeholder="Max Assets" />
<input type="number" class="form-control" formControlName="maxItems" placeholder="{{ 'schemas.fieldTypes.assets.countMax' | sqxTranslate }}">
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Size</label>
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.assets.size' | sqxTranslate }}</label>
<div class="col-3 minmax-col">
<input type="number" class="form-control" formControlName="minSize" placeholder="Min Size" />
<input type="number" class="form-control" formControlName="minSize" placeholder="{{ 'schemas.fieldTypes.assets.sizeMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div>
<div class="col-3">
<input type="number" class="form-control" formControlName="maxSize" placeholder="Max Size" />
<input type="number" class="form-control" formControlName="maxSize" placeholder="{{ 'schemas.fieldTypes.assets.sizeMax' | sqxTranslate }}">
</div>
<div class="col-3">
<label class="col-form-label">bytes</label>
<label class="col-form-label">{{ 'common.bytes' | sqxTranslate }}</label>
</div>
</div>
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldMustBeImage" formControlName="mustBeImage" />
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldMustBeImage" formControlName="mustBeImage">
<label class="form-check-label" for="{{field.fieldId}}_fieldMustBeImage">
Must be Image
{{ 'schemas.fieldTypes.assets.mustBeImage' | sqxTranslate }}
</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Width</label>
<label class="col-3 col-form-label">{{ 'common.width' | sqxTranslate }}</label>
<div class="col-2 minmax-col">
<input type="number" class="form-control" formControlName="minWidth" />
<input type="number" class="form-control" formControlName="minWidth">
<label class="col-form-label minmax-label">-</label>
</div>
<div class="col-2">
<input type="number" class="form-control" formControlName="maxWidth" />
<input type="number" class="form-control" formControlName="maxWidth">
</div>
<div class="col-2">
<label class="col-form-label">px</label>
@ -56,15 +56,15 @@
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Height</label>
<label class="col-3 col-form-label">{{ 'common.height' | sqxTranslate }}</label>
<div class="col-2 minmax-col">
<input type="number" class="form-control" formControlName="minHeight" />
<input type="number" class="form-control" formControlName="minHeight">
<label class="col-form-label minmax-label">-</label>
</div>
<div class="col-2">
<input type="number" class="form-control" formControlName="maxHeight" />
<input type="number" class="form-control" formControlName="maxHeight">
</div>
<div class="col-2">
<label class="col-form-label">px</label>
@ -72,15 +72,15 @@
</div>
<div class="form-group row">
<label class="col-3 col-form-label">AspectRatio</label>
<label class="col-3 col-form-label">{{ 'common.aspectRatio' | sqxTranslate }}</label>
<div class="col-2 minmax-col">
<input type="number" class="form-control" formControlName="aspectWidth" placeholder="4" />
<input type="number" class="form-control" formControlName="aspectWidth" placeholder="4">
<label class="col-form-label minmax-label">:</label>
</div>
<div class="col-2">
<input type="number" class="form-control" formControlName="aspectHeight" placeholder="3" />
<input type="number" class="form-control" formControlName="aspectHeight" placeholder="3">
</div>
<div class="col-2">
<label class="col-form-label">px</label>
@ -90,9 +90,9 @@
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldAllowDuplicates" formControlName="allowDuplicates" />
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldAllowDuplicates" formControlName="allowDuplicates">
<label class="form-check-label" for="{{field.fieldId}}_fieldAllowDuplicates">
Allow duplicate values
{{ 'schemas.fieldTypes.assets.allowDuplicates' | sqxTranslate }}
</label>
</div>
</div>
@ -100,7 +100,7 @@
<div class="form-group2 row">
<label class="col-3 col-form-label">
File Extensions
{{ 'schemas.fieldTypes.assets.fileExtensions' | sqxTranslate }}
</label>
<div class="col-6">

16
frontend/app/features/schemas/pages/schema/fields/types/boolean-ui.component.html

@ -1,28 +1,28 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label>
<div class="col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder" />
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint>
Define the placeholder for the input control.
{{ 'schemas.field.placeholderHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Editor</label>
<label class="col-3 col-form-label">{{ 'schemas.field.editor' | sqxTranslate }}</label>
<div class="col-9">
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Checkbox'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Checkbox" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Checkbox">
<i class="icon-control-Checkbox"></i>
<span class="radio-label">Checkbox</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Toggle'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Toggle" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Toggle">
<i class="icon-control-Toggle"></i>
@ -33,9 +33,9 @@
<div class="form-group row">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldInlineEditable" formControlName="inlineEditable" />
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldInlineEditable" formControlName="inlineEditable">
<label class="form-check-label" for="{{field.fieldId}}_fieldInlineEditable">
Inline Editable
{{ 'schemas.field.inlineEditable' | sqxTranslate }}
</label>
</div>
</div>

2
frontend/app/features/schemas/pages/schema/fields/types/boolean-validation.component.html

@ -4,7 +4,7 @@
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldDefaultValue" formControlName="defaultValue" sqxIndeterminateValue />
<label class="form-check-label" for="{{field.fieldId}}_fieldDefaultValue">
Default Value
{{ 'schemas.field.defaultValue' | sqxTranslate }}
</label>
</div>
</div>

10
frontend/app/features/schemas/pages/schema/fields/types/date-time-ui.component.html

@ -3,26 +3,26 @@
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>
<div class="col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder" />
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint>
Define the placeholder for the input control.
{{ 'schemas.field.placeholderHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Editor</label>
<label class="col-3 col-form-label">{{ 'schemas.field.editor' | sqxTranslate }}</label>
<div class="col-9">
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Date'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Date" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Date">
<i class="icon-control-Date"></i>
<span class="radio-label">Date</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'DateTime'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="DateTime" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="DateTime">
<i class="icon-control-DateTime"></i>

8
frontend/app/features/schemas/pages/schema/fields/types/date-time-validation.component.html

@ -1,6 +1,6 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label">Min Value</label>
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.dateTime.rangeMin' | sqxTranslate }}</label>
<div class="col-9">
<sqx-date-time-editor enforceTime="true" mode="DateTime" formControlName="minValue"></sqx-date-time-editor>
@ -8,7 +8,7 @@
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Max Value</label>
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.dateTime.rangeMax' | sqxTranslate }}</label>
<div class="col-9">
<sqx-date-time-editor enforceTime="true" mode="DateTime" formControlName="maxValue"></sqx-date-time-editor>
@ -17,7 +17,7 @@
<ng-container *ngIf="showDefaultValues | async">
<div class="form-group row">
<label class="col-3 col-form-label">Default Mode</label>
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.dateTime.defaultMode' | sqxTranslate }}</label>
<div class="col-3">
<select class="form-control" formControlName="calculatedDefaultValue">
@ -28,7 +28,7 @@
</div>
<div class="form-group row" *ngIf="showDefaultValue | async">
<label class="col-3 col-form-label">Default Value</label>
<label class="col-3 col-form-label">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label>
<div class="col-9">
<sqx-date-time-editor enforceTime="true" mode="DateTime" formControlName="defaultValue"></sqx-date-time-editor>

4
frontend/app/features/schemas/pages/schema/fields/types/geolocation-ui.component.html

@ -1,10 +1,10 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label">Editor</label>
<label class="col-3 col-form-label">{{ 'schemas.field.editor' | sqxTranslate }}</label>
<div class="col-9">
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Map'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Map" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Map">
<i class="icon-control-Map"></i>

22
frontend/app/features/schemas/pages/schema/fields/types/number-ui.component.html

@ -1,42 +1,42 @@
<div [formGroup]="fieldForm">
<div class="form-group row">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">Placeholder</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPlaceholder">{{ 'schemas.field.placeholder' | sqxTranslate }}</label>
<div class="col-6">
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder" />
<input type="text" class="form-control" id="{{field.fieldId}}_fieldPlaceholder" maxlength="100" formControlName="placeholder">
<sqx-form-hint>
Define the placeholder for the input control.
{{ 'schemas.field.placeholderHint' | sqxTranslate }}
</sqx-form-hint>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Editor</label>
<label class="col-3 col-form-label">{{ 'schemas.field.editor' | sqxTranslate }}</label>
<div class="col-9">
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Input'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Input" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Input">
<i class="icon-control-Input"></i>
<span class="radio-label">Input</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Dropdown'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Dropdown" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Dropdown">
<i class="icon-control-Dropdown"></i>
<span class="radio-label" clas>Dropdown</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Radio'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Radio" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Radio">
<i class="icon-control-Radio"></i>
<span class="radio-label">Radio</span>
</label>
<label class="btn btn-radio" [class.active]="fieldForm.controls['editor'].value === 'Stars'">
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Stars" />
<input type="radio" class="radio-input" name="editor" formControlName="editor" value="Stars">
<i class="icon-control-Stars"></i>
@ -45,7 +45,7 @@
</div>
</div>
<div class="form-group row" [class.hidden]="hideAllowedValues | async">
<label class="col-3 col-form-label">Allowed Values</label>
<label class="col-3 col-form-label">{{ 'schemas.field.allowedValues' | sqxTranslate }}</label>
<div class="col-6">
<sqx-tag-editor formControlName="allowedValues" [converter]="converter"></sqx-tag-editor>
@ -54,9 +54,9 @@
<div class="form-group row" [class.hidden]="hideInlineEditable | async">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldInlineEditable" formControlName="inlineEditable" />
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldInlineEditable" formControlName="inlineEditable">
<label class="form-check-label" for="{{field.fieldId}}_fieldInlineEditable">
Inline Editable
{{ 'schemas.field.inlineEditable' | sqxTranslate }}
</label>
</div>
</div>

14
frontend/app/features/schemas/pages/schema/fields/types/number-validation.component.html

@ -2,32 +2,32 @@
<div class="form-group row" *ngIf="showUnique">
<div class="col-9 offset-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldUnique" formControlName="isUnique" />
<input class="form-check-input" type="checkbox" id="{{field.fieldId}}_fieldUnique" formControlName="isUnique">
<label class="form-check-label" for="{{field.fieldId}}_fieldUnique">
Unique
{{ 'schemas.field.unique' | sqxTranslate }}
</label>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-3 col-form-label">Range</label>
<label class="col-3 col-form-label">{{ 'schemas.fieldTypes.number.range' | sqxTranslate }}</label>
<div class="col-3 minmax-col">
<input type="number" class="form-control" formControlName="minValue" placeholder="Min Value" />
<input type="number" class="form-control" formControlName="minValue" placeholder="{{ 'schemas.fieldTypes.number.rangeMin' | sqxTranslate }}">
<label class="col-form-label minmax-label">-</label>
</div>
<div class="col-3">
<input type="number" class="form-control" formControlName="maxValue" placeholder="Max Value" />
<input type="number" class="form-control" formControlName="maxValue" placeholder="{{ 'schemas.fieldTypes.number.rangeMax' | sqxTranslate }}">
</div>
</div>
<div class="form-group row" *ngIf="showDefaultValue | async">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValue">Default Value</label>
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldDefaultValue">{{ 'schemas.field.defaultValue' | sqxTranslate }}</label>
<div class="col-6">
<input type="number" class="form-control" id="{{field.fieldId}}_fieldDefaultValue" formControlName="defaultValue" />
<input type="number" class="form-control" id="{{field.fieldId}}_fieldDefaultValue" formControlName="defaultValue">
</div>
</div>
</div>

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

Loading…
Cancel
Save