pull/3853/head
Armağan Ünlü 6 years ago
commit 3800f1849e

@ -546,6 +546,8 @@ Entity extension system solves the main problem of the extra properties: It can
All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
See the [entity extension system](Customizing-Application-Modules-Extending-Entities.md) for details.
###### Creating a New Table
Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table.

@ -25,6 +25,12 @@ The following tools should be installed on your development machine:
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/): The applications use Redis as as [distributed cache](../Caching.md). So, you need to have Redis installed & running.
{{ end }}
> You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core.

@ -24,6 +24,11 @@
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
{{ if Tiered == "Yes" }}
* [Redis](https://redis.io/): 应用程序将Redis用作[分布式缓存](../Caching.md). 因此你需要安装并运行Redis.
{{ end }}
> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器.

@ -86,6 +86,21 @@ JSON文件位于 "/Localization/Resources/Test" 项目文件夹下, 如下图所
* 每个本地化文件都需要定义 `culture` (文化) 代码 (例如 "en" 或 "en-US").
* `texts` 部分只包含本地化字符串的键值集合 (键也可能有空格).
### 默认资源
可以将 `AbpLocalizationOptions.DefaultResourceType` 设置为资源类型,在未指定本地化资源时使用:
````csharp
Configure<AbpLocalizationOptions>(options =>
{
options.DefaultResourceType = typeof(TestResource);
});
````
> [启动模板]](Startup-Templates/Application.md) 设置 `DefaultResourceType` 为应用程序的本地化资源.
请参阅下面的*客户端*部分获取用例
##### 简短的本地化资源名称
本地化资源也可以在客户端(JavaScript)使用. 因此, 为本地化资源设置一个简短的名称可以更方便的本地化文本. 例如:
@ -165,6 +180,10 @@ public class MyService
}
````
##### 格式参数
格式参数可以在本地化Key参数后传递,如果你的消息是 `Hello {0}, welcome!`,可以将 `{0}` 传递给localizer,例如: `_localizer["HelloMessage", "John"]`.
###### 在Razor视图/Page中简单的用法
````c#
@ -179,7 +198,9 @@ public class MyService
ABP提供了JavaScript服务, 可以在客户端使用相同的本地化文本.
获取本地化资源:
#### getResource
`abp.localization.getResource` 函数用于获取本地化资源:
````js
var testResource = abp.localization.getResource('Test');
@ -191,6 +212,33 @@ var testResource = abp.localization.getResource('Test');
var str = testResource('HelloWorld');
````
## See Also
#### 本地化
`abp.localization.localize` 函数用于获取本地化文本,你可以传递本地化Key和资源名称:
````js
var str = abp.localization.localize('HelloWorld', 'Test');
````
`HelloWorld` 是本地化文本的Key, `Test` 是本地化资源的名称.
如果未指定本地化资源名称,它使用 `AbpLocalizationOptions` 中定义的默认本地化资源(参见上面的*默认资源*部分). 例:
````js
var str = abp.localization.localize('HelloWorld'); //uses the default resource
````
##### 格式参数
如果本地化字符串包含参数, 例如 `Hello {0}, welcome!`. 你可以将参数传递给本地化方法. 例:
````js
var str1 = abp.localization.getResource('Test')('HelloWelcomeMessage', 'John');
var str2 = abp.localization.localize('HelloWorld', 'Test', 'John');
````
上面的两个示例都会输出 `Hello John, welcome!`.
## 另请参阅
* [Angular UI中的本地化](UI/Angular/Localization.md)

@ -176,6 +176,106 @@ ObjectExtensionManager.Instance
`options` 有一个名为 `Configuration` 的字典,该字典存储对象扩展定义甚至可以扩展. EF Core使用它来将其他属性映射到数据库中的表字段. 请参阅[扩展实体文档](Customizing-Application-Modules-Extending-Entities.md).
#### CheckPairDefinitionOnMapping
控制在映射两个可扩展对象时如何检查属性定义. 请参阅*对象到对象映射*部分,了解 `CheckPairDefinitionOnMapping` 选项.
## Validation
你可能要为你定义的额外属性添加一些 **验证规则**. `AddOrUpdateProperty` 方法选项允许进行验证的方法有两种:
1. 你可以为属性添加 **数据注解 attributes**.
2. 你可以给定一个action(代码块)执行 **自定义验证**.
当你在**自动验证**的方法(例如:控制器操作,页面处理程序方法,应用程序服务方法...)中使用对象时,验证会工作. 因此,每当扩展对象被验证时,所有额外的属性都会被验证.
### 数据注解 Attributes
所有标准的数据注解Attributes对于额外属性都是有效的. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Attributes.Add(new RequiredAttribute());
options.Attributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
);
});
````
使用以上配置,如果没有提供有效的 `SocialSecurityNumber` 值, `IdentityUserCreateDto` 对象将是无效的.
### 自定义验证
如果需要,可以添加一个自定义action验证额外属性. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdateProperty<IdentityUserCreateDto, string>(
"SocialSecurityNumber",
options =>
{
options.Validators.Add(context =>
{
var socialSecurityNumber = context.Value as string;
if (socialSecurityNumber == null ||
socialSecurityNumber.StartsWith("X"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Invalid social security number: " + socialSecurityNumber,
new[] { "SocialSecurityNumber" }
)
);
}
});
});
````
`context.ServiceProvider` 可以解析服务.
除了为单个属性添加自定义验证逻辑外,还可以添加在对象级执行的自定义验证逻辑. 例:
````csharp
ObjectExtensionManager.Instance
.AddOrUpdate<IdentityUserCreateDto>(objConfig =>
{
//Define two properties with their own validation rules
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{
propertyConfig.Attributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties
objConfig.Validators.Add(context =>
{
if (context.ValidatingObject.GetProperty<string>("Password") !=
context.ValidatingObject.GetProperty<string>("PasswordRepeat"))
{
context.ValidationErrors.Add(
new ValidationResult(
"Please repeat the same password!",
new[] { "Password", "PasswordRepeat" }
)
);
}
});
});
````
## 对象到对象映射
假设你已向可扩展的实体对象添加了额外的属性并使用了自动[对象到对象的映射](Object-To-Object-Mapping.md)将该实体映射到可扩展的DTO类. 在这种情况下你需要格外小心,因为额外属性可能包含**敏感数据**,这些数据对于客户端不可用.

@ -0,0 +1,12 @@
{
"culture": "de",
"texts": {
"GivenTenantIsNotAvailable": "Der angegebene Mandant ist nicht verfügbar: {0}",
"Tenant": "Mandant",
"Switch": "wechseln",
"Name": "Name",
"SwitchTenantHint": "Lassen Sie das Namensfeld leer, um auf die Host-Seite zu wechseln.",
"SwitchTenant": "Mandant wechseln",
"NotSelected": "Nicht ausgewählt"
}
}

@ -0,0 +1,12 @@
{
"culture": "nl",
"texts": {
"GivenTenantIsNotAvailable": "Gegeven klant is niet beschikbaar: {0}",
"Tenant": "Klant",
"Switch": "Schakel over",
"Name": "Name",
"SwitchTenantHint": "Laat het naamveld leeg om over te schakelen naar de hostkant.",
"SwitchTenant": "Klant wisselen",
"NotSelected": "Niet geselecteerd"
}
}

@ -7,5 +7,7 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared.Components
public virtual string AppName => "MyApplication";
public virtual string LogoUrl => null;
public virtual string LogoReverseUrl => null;
}
}

@ -4,6 +4,14 @@
{
string AppName { get; }
/// <summary>
/// Logo on white background
/// </summary>
string LogoUrl { get; }
/// <summary>
/// Logo on dark background
/// </summary>
string LogoReverseUrl { get; }
}
}

@ -144,7 +144,8 @@
var tableProperty = properties[i];
columnConfigs.push({
title: localizeDisplayName(tableProperty.name, tableProperty.config.displayName),
data: "extraProperties." + tableProperty.name
data: "extraProperties." + tableProperty.name,
orderable: false
});
}

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Volo.Abp.DependencyInjection;
@ -91,9 +93,27 @@ namespace Volo.Abp.AspNetCore.VirtualFileSystem
protected virtual IFileProvider CreateFileProvider()
{
return new CompositeFileProvider(
var fileProviders = new List<IFileProvider>()
{
new PhysicalFileProvider(_hostingEnvironment.ContentRootPath),
_virtualFileProvider
};
if (_hostingEnvironment.IsDevelopment() &&
_hostingEnvironment.WebRootFileProvider is CompositeFileProvider compositeFileProvider)
{
var staticWebAssetsFileProvider = compositeFileProvider
.FileProviders
.FirstOrDefault(f => f.GetType().Name.Equals("StaticWebAssetsFileProvider"));
if (staticWebAssetsFileProvider != null)
{
fileProviders.Add(staticWebAssetsFileProvider);
}
}
return new CompositeFileProvider(
fileProviders
);
}

@ -20,6 +20,7 @@ namespace Volo.Abp.Cli.Commands
{
public static Dictionary<string, Dictionary<string, string>> propertyList = new Dictionary<string, Dictionary<string, string>>();
public ILogger<GenerateProxyCommand> Logger { get; set; }
public static string outputPrefix = "src/app";
protected TemplateProjectBuilder TemplateProjectBuilder { get; }
@ -100,14 +101,22 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation($"{rootPath} directory is creating");
Directory.CreateDirectory($"src/app/{rootPath}/shared/models");
Directory.CreateDirectory($"src/app/{rootPath}/shared/services");
if (rootPath == "app")
{
outputPrefix = "src";
}
else
{
outputPrefix = "src/app";
}
var serviceIndexList = new List<string>();
var modelIndexList = new List<string>();
Directory.CreateDirectory($"{outputPrefix}/{rootPath}");
foreach (var controller in moduleValue.Root.ToList().Select(item => item.First))
{
var serviceIndexList = new List<string>();
var modelIndexList = new List<string>();
var serviceFileText = new StringBuilder();
serviceFileText.AppendLine("[firstTypeList]");
@ -128,6 +137,12 @@ namespace Volo.Abp.Cli.Commands
var controllerName = (string)controller["controllerName"];
var controllerServiceName = controllerName.PascalToKebabCase() + ".service.ts";
var controllerPathName = controllerName.ToLower().Replace("controller", "");
controllerPathName = (controllerPathName.StartsWith(rootPath)) ? controllerPathName.Substring(rootPath.Length) : controllerPathName;
Directory.CreateDirectory($"{outputPrefix}/{rootPath}/{controllerPathName}/models");
Directory.CreateDirectory($"{outputPrefix}/{rootPath}/{controllerPathName}/services");
foreach (var actionItem in controller["actions"])
{
var action = actionItem.First;
@ -157,7 +172,7 @@ namespace Volo.Abp.Cli.Commands
var isOptional = (bool)parameter["isOptional"];
var defaultValue = (string)parameter["defaultValue"];
var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList);
var modelIndex = CreateType(data, (string)parameter["type"], rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
@ -194,7 +209,7 @@ namespace Volo.Abp.Cli.Commands
parameterModel = AddParameter(name, typeSimple, isOptional, defaultValue, bindingSourceId, parameterModel);
}
modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList);
modelIndex = CreateType(data, (string)parameterOnMethod["type"], rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
@ -225,7 +240,7 @@ namespace Volo.Abp.Cli.Commands
foreach (var parameterItem in parameterModel.OrderBy(p => p.DisplayOrder))
{
var parameterItemModelName = parameterItem.Type.PascalToKebabCase() + ".ts";
var parameterItemModelPath = $"src/app/{rootPath}/shared/models/{parameterItemModelName}";
var parameterItemModelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{parameterItemModelName}";
if (parameterItem.BindingSourceId == "body" && !File.Exists(parameterItemModelPath))
{
parameterItem.Type = "any";
@ -262,7 +277,7 @@ namespace Volo.Abp.Cli.Commands
var secondType = secondTypeArray[secondTypeArray.Length - 1].TrimEnd('>');
var secondTypeModelName = secondType.PascalToKebabCase() + ".ts";
var secondTypeModelPath = $"src/app/{rootPath}/shared/models/{secondTypeModelName}";
var secondTypeModelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{secondTypeModelName}";
if (firstType == "List" && !File.Exists(secondTypeModelPath))
{
secondType = "any";
@ -306,7 +321,7 @@ namespace Volo.Abp.Cli.Commands
}
}
var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList);
var modelIndex = CreateType(data, returnValueType, rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
@ -359,26 +374,31 @@ namespace Volo.Abp.Cli.Commands
serviceFileText.AppendLine("}");
serviceFileText.Replace("[controllerName]", controllerName);
File.WriteAllText($"src/app/{rootPath}/shared/services/{controllerServiceName}", serviceFileText.ToString());
}
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/services/{controllerServiceName}", serviceFileText.ToString());
var serviceIndexFileText = new StringBuilder();
foreach (var serviceIndexItem in serviceIndexList.Distinct())
{
serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';");
}
File.WriteAllText($"src/app/{rootPath}/shared/services/index.ts", serviceIndexFileText.ToString());
var serviceIndexFileText = new StringBuilder();
var modelIndexFileText = new StringBuilder();
foreach (var serviceIndexItem in serviceIndexList.Distinct())
{
serviceIndexFileText.AppendLine($"export * from './{serviceIndexItem}';");
}
foreach (var modelIndexItem in modelIndexList.Distinct())
{
modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';");
}
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/services/index.ts", serviceIndexFileText.ToString());
if (modelIndexList.Count > 0)
{
var modelIndexFileText = new StringBuilder();
foreach (var modelIndexItem in modelIndexList.Distinct())
{
modelIndexFileText.AppendLine($"export * from './{modelIndexItem}';");
}
File.WriteAllText($"src/app/{rootPath}/shared/models/index.ts", modelIndexFileText.ToString());
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/models/index.ts", modelIndexFileText.ToString());
}
}
}
Logger.LogInformation("Completed!");
@ -415,7 +435,7 @@ namespace Volo.Abp.Cli.Commands
return moduleList;
}
private static string CreateType(JObject data, string returnValueType, string rootPath, List<string> modelIndexList)
private static string CreateType(JObject data, string returnValueType, string rootPath, List<string> modelIndexList, string controllerPathName)
{
var type = data["types"][returnValueType];
@ -425,30 +445,45 @@ namespace Volo.Abp.Cli.Commands
}
if (returnValueType.StartsWith("Volo.Abp.Application.Dtos")
|| returnValueType.StartsWith("System.Collections")
|| returnValueType == "System.String"
|| returnValueType == "System.Void"
|| returnValueType.Contains("System.Net.HttpStatusCode?")
|| returnValueType.Contains("IActionResult")
|| returnValueType.Contains("ActionResult")
|| returnValueType.Contains("IStringValueType")
|| returnValueType.Contains("IValueValidator")
)
|| returnValueType.StartsWith("System.Collections")
|| returnValueType == "System.String"
|| returnValueType == "System.Void"
|| returnValueType.Contains("System.Net.HttpStatusCode?")
|| returnValueType.Contains("IActionResult")
|| returnValueType.Contains("ActionResult")
|| returnValueType.Contains("IStringValueType")
|| returnValueType.Contains("IValueValidator")
)
{
return null;
if (returnValueType.Contains("<"))
{
returnValueType = returnValueType.Split('<')[1].Split('>')[0];
if (returnValueType.StartsWith("Volo.Abp.Application.Dtos")
|| returnValueType.StartsWith("System.Collections")
|| returnValueType == "System.String"
|| returnValueType == "System.Void"
|| returnValueType.Contains("System.Net.HttpStatusCode?")
|| returnValueType.Contains("IActionResult")
|| returnValueType.Contains("ActionResult")
|| returnValueType.Contains("IStringValueType")
|| returnValueType.Contains("IValueValidator")
)
{
return null;
}
}
else
{
return null;
}
}
var typeNameSplit = returnValueType.Split(".");
var typeName = typeNameSplit[typeNameSplit.Length - 1];
if (typeName.Contains("HttpStatusCode"))
{
}
var typeModelName = typeName.Replace("<", "").Replace(">", "").Replace("?", "").PascalToKebabCase() + ".ts";
var path = $"src/app/{rootPath}/shared/models/{typeModelName}";
var path = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeModelName}";
var modelFileText = new StringBuilder();
@ -481,7 +516,7 @@ namespace Volo.Abp.Cli.Commands
modelFileText.AppendLine($"import {{ {baseTypeName} }} from '{baseTypeKebabCase}';");
extends = "extends " + (!string.IsNullOrWhiteSpace(customBaseTypeName) ? customBaseTypeName : baseTypeName);
var modelIndex = CreateType(data, baseType, rootPath, modelIndexList);
var modelIndex = CreateType(data, baseType, rootPath, modelIndexList, controllerPathName);
if (!string.IsNullOrWhiteSpace(modelIndex))
{
modelIndexList.Add(modelIndex);
@ -514,7 +549,7 @@ namespace Volo.Abp.Cli.Commands
propertyName = (char.ToLower(propertyName[0]) + propertyName.Substring(1));
var typeSimple = (string)property["typeSimple"];
var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList);
var modelIndex = CreateType(data, (string)property["type"], rootPath, modelIndexList, controllerPathName);
if (typeSimple.IndexOf("[") > -1 && typeSimple.IndexOf("]") > -1)
{
@ -547,7 +582,7 @@ namespace Volo.Abp.Cli.Commands
)
{
var typeSimpleModelName = typeSimple.PascalToKebabCase() + ".ts";
var modelPath = $"src/app/{rootPath}/shared/models/{typeSimpleModelName}";
var modelPath = $"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeSimpleModelName}";
if (!File.Exists(modelPath))
{
typeSimple = "any" + (typeSimple.Contains("[]") ? "[]" : "");
@ -561,10 +596,18 @@ namespace Volo.Abp.Cli.Commands
if (!string.IsNullOrWhiteSpace(modelIndex))
{
var from = "../models";
var propertyTypeSplit = ((string)property["type"]).Split(".");
var propertyType = propertyTypeSplit[propertyTypeSplit.Length - 1];
var propertyTypeKebabCase = propertyType.PascalToKebabCase();
if (File.Exists($"{outputPrefix}/{rootPath}/{controllerPathName}/models/{propertyTypeKebabCase}.ts"))
{
from = "./" + propertyTypeKebabCase;
}
modelFileText.Insert(0, "");
modelFileText.Insert(0, $"import {{ {propertyType} }} from '../models';");
modelFileText.Insert(0, $"import {{ {propertyType} }} from '{from}';");
modelFileText.Insert(0, "");
modelIndexList.Add(modelIndex);
}
@ -608,7 +651,7 @@ namespace Volo.Abp.Cli.Commands
modelFileText.AppendLine("}");
}
File.WriteAllText($"src/app/{rootPath}/shared/models/{typeModelName}", modelFileText.ToString());
File.WriteAllText($"{outputPrefix}/{rootPath}/{controllerPathName}/models/{typeModelName}", modelFileText.ToString());
return typeModelName.Replace(".ts", "");
}

@ -0,0 +1,6 @@
{
"culture": "de",
"texts": {
"MaxResultCountExceededExceptionMessage": "{0} kann nicht mehr als {1} sein! Erhöhen Sie {2}.{3} auf der Serverseite, um mehr Ergebnisse zu ermöglichen."
}
}

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"MaxResultCountExceededExceptionMessage": "{0} kan niet meer dan {1} zijn! Vergroot {2}.{3} op de server om een groter resultaat toe te staan."
}
}

@ -0,0 +1,23 @@
{
"culture": "de",
"texts": {
"DisplayName:Abp.Mailing.DefaultFromAddress": "Standard-Absenderadresse",
"DisplayName:Abp.Mailing.DefaultFromDisplayName": "Standard-Absendername",
"DisplayName:Abp.Mailing.Smtp.Host": "Host",
"DisplayName:Abp.Mailing.Smtp.Port": "Port",
"DisplayName:Abp.Mailing.Smtp.UserName": "Benutzername",
"DisplayName:Abp.Mailing.Smtp.Password": "Passwort",
"DisplayName:Abp.Mailing.Smtp.Domain": "Domain",
"DisplayName:Abp.Mailing.Smtp.EnableSsl": "SSL aktivieren",
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Standard-Anmeldeinformationen verwenden",
"Description:Abp.Mailing.DefaultFromAddress": "Die Standard-Absenderadresse",
"Description:Abp.Mailing.DefaultFromDisplayName": "Der Standard-Absendername",
"Description:Abp.Mailing.Smtp.Host": "Der Name oder die IP-Adresse des für SMTP-Transaktionen verwendeten Hosts.",
"Description:Abp.Mailing.Smtp.Port": "Der für SMTP-Transaktionen verwendete Port.",
"Description:Abp.Mailing.Smtp.UserName": "Benutzername, der mit den Anmeldedaten verknüpft ist.",
"Description:Abp.Mailing.Smtp.Password": "Das Passwort für den Benutzernamen, der mit den Anmeldeinformationen verknüpft ist.",
"Description:Abp.Mailing.Smtp.Domain": "Die Domäne oder der Computername, der die Anmeldeinformationen verifiziert.",
"Description:Abp.Mailing.Smtp.EnableSsl": "Bestimmt, ob der SmptClient Secure Sockets Layer (SSL) zur Verschlüsselung der Verbindung verwendet.",
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Bestimmt, ob die DefaultCredentials mit Anfragen gesendet werden."
}
}

@ -0,0 +1,23 @@
{
"culture": "nl",
"texts": {
"DisplayName:Abp.Mailing.DefaultFromAddress": "Standard vanaf adres",
"DisplayName:Abp.Mailing.DefaultFromDisplayName": "Standaard vanaf weergave naam",
"DisplayName:Abp.Mailing.Smtp.Host": "Host",
"DisplayName:Abp.Mailing.Smtp.Port": "Poort",
"DisplayName:Abp.Mailing.Smtp.UserName": "Gebruiker naam",
"DisplayName:Abp.Mailing.Smtp.Password": "wachtwoord",
"DisplayName:Abp.Mailing.Smtp.Domain": "Domein",
"DisplayName:Abp.Mailing.Smtp.EnableSsl": "SSL toestaan",
"DisplayName:Abp.Mailing.Smtp.UseDefaultCredentials": "Gebruik standaard inloggegevens",
"Description:Abp.Mailing.DefaultFromAddress": "Standard vanaf adres",
"Description:Abp.Mailing.DefaultFromDisplayName": "Standaard vanaf weergave naam",
"Description:Abp.Mailing.Smtp.Host": "De naam of het IP-adres van de host die wordt gebruikt voor SMTP-transacties.",
"Description:Abp.Mailing.Smtp.Port": "De poort die wordt gebruikt voor SMTP-transacties.",
"Description:Abp.Mailing.Smtp.UserName": "Gebruikersnaam gekoppeld aan de inloggegevens.",
"Description:Abp.Mailing.Smtp.Password": "Het wachtwoord voor de gebruikersnaam die bij de inloggegevens hoort.",
"Description:Abp.Mailing.Smtp.Domain": "Het domein of de computernaam die de inloggegevens verifieert.",
"Description:Abp.Mailing.Smtp.EnableSsl": "Of de SmtpClient Secure Sockets Layer (SSL) gebruikt om de verbinding te versleutelen.",
"Description:Abp.Mailing.Smtp.UseDefaultCredentials": "Of de standaard inloggegevens worden verzonden met verzoeken."
}
}

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"DisplayName:Abp.Localization.DefaultLanguage": "Standardsprache",
"Description:Abp.Localization.DefaultLanguage": "Die Standardsprache der Anwendung."
}
}

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"DisplayName:Abp.Localization.DefaultLanguage": "Standaard taal",
"Description:Abp.Localization.DefaultLanguage": "De standaardtaal van de applicatie."
}
}

@ -0,0 +1,6 @@
{
"culture": "de",
"texts": {
"Menu:Administration": "Administration"
}
}

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"Menu:Administration": "Administratie"
}
}

@ -0,0 +1,63 @@
{
"culture": "de",
"texts": {
"InternalServerErrorMessage": "Während Ihrer Anfrage ist ein interner Fehler aufgetreten!",
"ValidationErrorMessage": "Ihre Anfrage ist nicht gültig!",
"ValidationNarrativeErrorMessageTitle": "Die folgenden Fehler wurden bei der Validierung entdeckt.",
"DefaultErrorMessage": "Ein Fehler ist aufgetreten!",
"DefaultErrorMessageDetail": "Es wurden keine Fehlerdetails vom Server gesendet.",
"DefaultErrorMessage401": "Sie sind nicht authentifiziert.",
"DefaultErrorMessage401Detail": "Sie sollten sich anmelden, um diese Operation durchzuführen.",
"DefaultErrorMessage403": "Sie sind nicht autorisiert!",
"DefaultErrorMessage403Detail": "Es ist Ihnen nicht erlaubt, diese Operation durchzuführen!",
"DefaultErrorMessage404": "Ressource nicht gefunden!",
"DefaultErrorMessage404Detail": "Die angeforderte Ressource konnte auf dem Server nicht gefunden werden!",
"EntityNotFoundErrorMessage": "Es gibt keine Entität {0} mit id = {1}!",
"Languages": "Sprachen",
"Error": "Fehler",
"AreYouSure": "Sind Sie sicher?",
"Cancel": "Abbrechen",
"Yes": "Ja",
"No": "Nein",
"Ok": "Ok",
"Close": "Schließen",
"Save": "Speichern",
"SavingWithThreeDot": "Speichere...",
"Actions": "Aktionen",
"Delete": "Löschen",
"Edit": "Bearbeiten",
"Refresh": "Aktualisieren",
"Language": "Sprache",
"LoadMore": "Mehr laden",
"ProcessingWithThreeDot": "Verarbeite...",
"LoadingWithThreeDot": "Lade...",
"Welcome": "Willkommen",
"Login": "Anmelden",
"Register": "Registrieren",
"Logout": "Abmelden",
"Submit": "Absenden",
"Back": "Zurück",
"PagerSearch": "Suchen",
"PagerNext": "Nächste",
"PagerPrevious": "Vorherige",
"PagerFirst": "Erste",
"PagerLast": "Letzte",
"PagerInfo": "Zeige _START_ bis _END_ von _TOTAL_ Einträgen",
"PagerInfo{0}{1}{2}": "Zeige {0} bis {1} von {2} Einträgen",
"PagerInfoEmpty": "Zeige 0 bis 0 von 0 Einträgen",
"PagerInfoFiltered": "(gefiltert von _MAX_ Einträgen insgesamt)",
"NoDataAvailableInDatatable": "Keine Daten verfügbar",
"PagerShowMenuEntries": "Zeige _MENU_ Einträge",
"DatatableActionDropdownDefaultText": "Aktionen",
"ChangePassword": "Passwort ändern",
"PersonalInfo": "Mein Profil",
"AreYouSureYouWantToCancelEditingWarningMessage": "Sie haben ungespeicherte Änderungen.",
"UnhandledException": "Unerwartete Ausnahme!",
"401Message": "Unauthorisiert",
"403Message": "Verboten",
"404Message": "Seite nicht gefunden",
"500Message": "Internet Server Fehler",
"GoHomePage": "Zur Startseite",
"GoBack": "Zurück"
}
}

@ -0,0 +1,63 @@
{
"culture": "nl",
"texts": {
"InternalServerErrorMessage": "Er is een interne fout opgetreden tijdens uw verzoek!",
"ValidationErrorMessage": "Uw verzoek is niet geldig!",
"ValidationNarrativeErrorMessageTitle": "Tijdens de validatie zijn de volgende fouten gedetecteerd.",
"DefaultErrorMessage": "er is een fout opgetreden!",
"DefaultErrorMessageDetail": "Foutdetails niet verzonden door server.",
"DefaultErrorMessage401": "U bent niet geverifieerd!",
"DefaultErrorMessage401Detail": "U moet inloggen om deze bewerking uit te voeren.",
"DefaultErrorMessage403": "U bent niet geautoriseerd!",
"DefaultErrorMessage403Detail": "U mag deze bewerking niet uitvoeren!",
"DefaultErrorMessage404": "Bron niet gevonden!",
"DefaultErrorMessage404Detail": "De gevraagde bron kan niet worden gevonden op de server!",
"EntityNotFoundErrorMessage": "Er is geen entiteit {0} met id = {1}!",
"Languages": "Talen",
"Error": "Fout",
"AreYouSure": "Bent u zeker?",
"Cancel": "Annuleren",
"Yes": "Ja",
"No": "Nee",
"Ok": "Ok",
"Close": "Sluiten",
"Save": "Opslaan",
"SavingWithThreeDot": "Opslaan...",
"Actions": "Acties",
"Delete": "Verwijder",
"Edit": "Bewerk",
"Refresh": "Ververs",
"Language": "Taal",
"LoadMore": "Meer laden",
"ProcessingWithThreeDot": "Verwerken...",
"LoadingWithThreeDot": "Laden...",
"Welcome": "Welkom",
"Login": "Log in",
"Register": "Registreren",
"Logout": "Afmelden",
"Submit": "Verzenden",
"Back": "Terug",
"PagerSearch": "Zoeken",
"PagerNext": "Volgende",
"PagerPrevious": "Vorige",
"PagerFirst": "Eerste",
"PagerLast": "Laatste",
"PagerInfo": "Toont _START_ tot _END_ van _TOTAL_ vermeldingen",
"PagerInfo{0}{1}{2}": "{0} tot {1} van {2} vermeldingen weergeven",
"PagerInfoEmpty": "Toont 0 tot 0 van 0 vermeldingen",
"PagerInfoFiltered": "(gefilterd uit in totaal _MAX_ vermeldingen)",
"NoDataAvailableInDatatable": "Geen gegevens beschikbaar",
"PagerShowMenuEntries": "Toon _MENU_-vermeldingen",
"DatatableActionDropdownDefaultText": "Acties",
"ChangePassword": "Verander wachtwoord",
"PersonalInfo": "Mijn profiel",
"AreYouSureYouWantToCancelEditingWarningMessage": "U heeft nog niet-opgeslagen wijzigingen.",
"UnhandledException": "Onverwerkte uitzondering!",
"401Message": "Ongeautoriseerd",
"403Message": "Verboden",
"404Message": "Pagina niet gevonden",
"500Message": "Interne Server Fout",
"GoHomePage": "Ga naar de homepage",
"GoBack": "Ga terug"
}
}

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "{0} není platný.",
"ThisFieldIsNotAValidEmailAddress.": "V poli {0} není platný email.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Pole přijímá soubory pouze s následujícími koncovkami: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Vy poli musí být řežezec nebo řada o maximální délce '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Vy poli musí být řežezec nebo řada o maximální délce '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "V poli musí být řežezec nebo řada o minimální délce '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "V poli není platné telefonní číslo.",
"ThisFieldMustBeBetween{0}And{1}": "Pole musí být mezi {0} a {1}.",

@ -0,0 +1,34 @@
{
"culture": "de",
"texts": {
"'{0}' and '{1}' do not match.": "'{0}' und '{1}' stimmen nicht überein.",
"The {0} field is not a valid credit card number.": "Das Feld {0} ist keine gültige Kreditkartennummer.",
"{0} is not valid.": "{0} ist nicht gültig.",
"The {0} field is not a valid e-mail address.": "Das Feld {0} ist keine gültige E-Mail-Adresse.",
"The {0} field only accepts files with the following extensions: {1}": "Das Feld {0} akzeptiert nur Dateien mit den folgenden Erweiterungen: {1}",
"The field {0} must be a string or array type with a maximum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{1}' sein.",
"The field {0} must be a string or array type with a minimum length of '{1}'.": "Das Feld {0} muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{1}' sein.",
"The {0} field is not a valid phone number.": "Das Feld {0} ist keine gültige Telefonnummer.",
"The field {0} must be between {1} and {2}.": "Das Feld {0} muss zwischen {1} und {2} liegen.",
"The field {0} must match the regular expression '{1}'.": "Das Feld {0} muss dem regulären Ausdruck '{1}' entsprechen.",
"The {0} field is required.": "Das Feld {0} ist erforderlich.",
"The field {0} must be a string with a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer maximalen Länge von {1} sein.",
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Das Feld {0} muss eine Zeichenfolge mit einer minimalen Länge von {2} und einer maximalen Länge von {1} sein.",
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Das {0}-Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.",
"The field {0} is invalid.": "Das Feld {0} ist ungültig.",
"ThisFieldIsNotAValidCreditCardNumber.": "Dieses Feld ist keine gültige Kreditkartennummer.",
"ThisFieldIsNotValid.": "Dieses Feld ist nicht gültig.",
"ThisFieldIsNotAValidEmailAddress.": "Dieses Feld ist keine gültige E-Mail-Adresse.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Dieses Feld akzeptiert nur Dateien mit den folgenden Erweiterungen: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer maximalen Länge von '{0}' sein.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge oder Auflistung mit einer Mindestlänge von '{0}' sein.",
"ThisFieldIsNotAValidPhoneNumber.": "Dieses Feld ist keine gültige Telefonnummer.",
"ThisFieldMustBeBetween{0}And{1}": "Dieses Feld muss zwischen {0} und {1} liegen.",
"ThisFieldMustMatchTheRegularExpression{0}": "Dieses Feld muss dem regulären Ausdruck '{0}' entsprechen.",
"ThisFieldIsRequired.": "Dieses Feld ist erforderlich.",
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer maximalen Länge von {0} sein.",
"ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dieses Feld muss eine Zeichenfolge mit einer Mindestlänge von '{0}' sein.",
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dieses Feld ist keine gültige vollqualifizierte http-, https- oder ftp-URL.",
"ThisFieldIsInvalid.": "Dieses Feld ist ungültig."
}
}

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "This field is not valid.",
"ThisFieldIsNotAValidEmailAddress.": "This field is not a valid e-mail address.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "This field only accepts files with the following extensions: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "This field is not a valid phone number.",
"ThisFieldMustBeBetween{0}And{1}": "This field must be between {0} and {1}.",

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Este campo no es válido.",
"ThisFieldIsNotAValidEmailAddress.": "Este campo no es una dirección de correo electrónico válida.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Este campo sólo acepta archivos con las siguientes extensiones: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Este campo debe ser una cadena o un array con una longitud máxima de '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Este campo debe ser una cadena o un array con una longitud máxima de '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Este campo debe ser una cadena o un array con una longitud mínima de '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Este campo no es un número de teléfono válido.",
"ThisFieldMustBeBetween{0}And{1}": "Este campo debe tener un valor entre {0} y {1}.",

@ -0,0 +1,34 @@
{
"culture": "nl",
"texts": {
"'{0}' and '{1}' do not match.": "'{0}' en '{1}' komen niet overeen.",
"The {0} field is not a valid credit card number.": "Het veld {0} is geen geldig krediet kaartnummer.",
"{0} is not valid.": "{0} is niet geldig.",
"The {0} field is not a valid e-mail address.": "Het veld {0} is geen geldig e-mailadres.",
"The {0} field only accepts files with the following extensions: {1}": "Het veld {0} accepteert alleen bestanden met de volgende extensies: {1}",
"The field {0} must be a string or array type with a maximum length of '{1}'.": "Het veld {0} moet een tekenreeks- of arraytype zijn met een maximale lengte van '{1}'.",
"The field {0} must be a string or array type with a minimum length of '{1}'.": "Het veld {0} moet een tekenreeks- of arraytype zijn met een minimale lengte van '{1}'.",
"The {0} field is not a valid phone number.": "Het veld {0} is geen geldig telefoonnummer.",
"The field {0} must be between {1} and {2}.": "Het veld {0} moet tussen {1} en {2} liggen.",
"The field {0} must match the regular expression '{1}'.": "Het veld {0} moet overeenkomen met de reguliere expressie '{1}'.",
"The {0} field is required.": "Het veld {0} is verplicht.",
"The field {0} must be a string with a maximum length of {1}.": "Het veld {0} moet een tekenreeks zijn met een maximale lengte van {1}.",
"The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.": "Het veld {0} moet een tekenreeks zijn met een minimale lengte van {2} en een maximale lengte van {1}.",
"The {0} field is not a valid fully-qualified http, https, or ftp URL.": "Het veld {0} is geen geldige, volledig gekwalificeerde http-, https- of ftp-URL.",
"The field {0} is invalid.": "Het veld {0} is ongeldig.",
"ThisFieldIsNotAValidCreditCardNumber.": "Dit veld is geen geldig krediet kaartnummer.",
"ThisFieldIsNotValid.": "Dir veld is ongeldig.",
"ThisFieldIsNotAValidEmailAddress.": "Dit veld is geen geldig e-mail adres.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Dit veld accepteert alleen bestanden met de volgende extensies: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Dit veld moet een tekenreeks- of arraytype zijn met een maximale lengte van '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Dit veld moet een tekenreeks- of arraytype zijn met een minimale lengte van '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Dit veld is geen geldig telefoonnummer.",
"ThisFieldMustBeBetween{0}And{1}": "Dit veld moet tussen {0} en {1} liggen.",
"ThisFieldMustMatchTheRegularExpression{0}": "Dit veld moet overeenkomen met de reguliere expressie '{0}'.",
"ThisFieldIsRequired.": "Dit veld is verplicht.",
"ThisFieldMustBeAStringWithAMaximumLengthOf{0}": "Dit veld moet een tekenreeks zijn met een maximale lengte van {0}.",
"ThisFieldMustBeAStringWithAMinimumLengthOf{1}AndAMaximumLengthOf{0}": "Dit veld moet een tekenreeks zijn met een minimale lengte van {1} en een maximale lengte van {0}.",
"ThisFieldIsNotAValidFullyQualifiedHttpHttpsOrFtpUrl": "Dit veld is geen geldige, volledig gekwalificeerde http-, https- of ftp-URL.",
"ThisFieldIsInvalid.": "Dit veld is ongeldig."
}
}

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Campo inválido.",
"ThisFieldIsNotAValidEmailAddress.": "E-mail inválido.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Este campo só aceita arquivos com asseguintes extensões: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "This field must be a string or array type with a maximum length of '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "This field must be a string or array type with a minimum length of '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Número de telefone inválido.",
"ThisFieldMustBeBetween{0}And{1}": "Este campo deve estar entre {0} e {1}.",

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Значение в этом поле недействительно.",
"ThisFieldIsNotAValidEmailAddress.": "Это поле не содержит действительный адрес электронной почты.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Вы можете загрузить файлы только следующих форматов: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Это поле должно иметь тип строки или массива с максимальной длиной '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Это поле должно иметь тип строки или массива с максимальной длиной '{0}'.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Это поле должно иметь тип строки или массива с минимальной длиной '{0}'.",
"ThisFieldIsNotAValidPhoneNumber.": "Это поле не содержит действительный номер телефона.",
"ThisFieldMustBeBetween{0}And{1}": "Это поле должно быть между {0} и {1}.",

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "Bu alan geçerli değil.",
"ThisFieldIsNotAValidEmailAddress.": "Bu alan geçerli bir e-posta adresi olmalıdır.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "Bu alan sadece şu uzantılarda dosyaları kabul eder: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "Bu alan en fazla '{0}' uzunluğunda bir metin ya da dizi olmalıdır.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "Bu alan en fazla '{0}' uzunluğunda bir metin ya da dizi olmalıdır.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "Bu alan en az '{0}' uzunluğunda bir metin ya da dizi olmalıdır.",
"ThisFieldIsNotAValidPhoneNumber.": "Bu alan geçerli bir telefon numarası olmalıdır.",
"ThisFieldMustBeBetween{0}And{1}": "Bu alanın değeri {0} ile {1} arasında olmalıdır.",

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "验证未通过.",
"ThisFieldIsNotAValidEmailAddress.": "字段不是有效的邮箱地址.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "字段只允许以下扩展名的文件: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "字段必须是最大长度为'{0}'的字符串或数组.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "字段必须是最大长度为'{0}'的字符串或数组.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "字段必须是最小长度为'{0}'的字符串或数组.",
"ThisFieldIsNotAValidPhoneNumber.": "字段不是有效的手机号码.",
"ThisFieldMustBeBetween{0}And{1}": "字段值必须在{0}和{1}范围内.",

@ -20,7 +20,7 @@
"ThisFieldIsNotValid.": "此驗證未通過.",
"ThisFieldIsNotAValidEmailAddress.": "此欄位不是有效的郵箱地址.",
"ThisFieldOnlyAcceptsFilesWithTheFollowingExtensions:{0}": "此欄位只允許以下副檔名的文件: {0}",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}": "此欄位必須是最大長度為'{0}'的字串或陣列.",
"ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}": "此欄位必須是最大長度為'{0}'的字串或陣列.",
"ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}": "此欄位必須是最小長度為'{0}'的字串或陣列.",
"ThisFieldIsNotAValidPhoneNumber.": "此欄位不是有效的電話號碼.",
"ThisFieldMustBeBetween{0}And{1}": "此欄位值必須在{0}和{1}範圍內.",

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"BirthDate": "Geburtsdatum",
"Value1": "Wert Eins"
}
}

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"BirthDate": "Geboortedatum",
"Value1": "Waarde een"
}
}

@ -0,0 +1,6 @@
{
"culture": "de",
"texts": {
"hello": "Hallo"
}
}

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"hello": "hallo"
}
}

@ -73,6 +73,11 @@ namespace Volo.Abp.Localization
_localizer["Car"].Value.ShouldBe("Auto");
}
using (CultureHelper.Use("de"))
{
_localizer["Car"].Value.ShouldBe("Auto");
}
}
[Fact]
@ -98,6 +103,11 @@ namespace Volo.Abp.Localization
_localizer["SeeYou"].Value.ShouldBe("Nos vemos");
}
using (CultureHelper.Use("de"))
{
_localizer["SeeYou"].Value.ShouldBe("Bis bald");
}
}
[Fact]

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"USA": "Vereinigte Staaten von Amerika",
"Brazil": "Brasilien"
}
}

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"USA": "Verenigde Staten van Amerika",
"Brazil": "Brazilië"
}
}

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"ThisFieldIsRequired": "Dieses Feld ist ein Pflichtfeld",
"MaxLenghtErrorMessage": "Die Länge dieses Feldes kann maximal '{0}'-Zeichen betragen"
}
}

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"ThisFieldIsRequired": "Dit veld is verplicht",
"MaxLenghtErrorMessage": "Dit veld mag maximaal '{0}' tekens bevatten"
}
}

@ -0,0 +1,11 @@
{
"culture": "de",
"texts": {
"Hello <b>{0}</b>.": "Hallo <b>{0}</b>.",
"Car": "Auto",
"CarPlural": "Autos",
"MaxLenghtErrorMessage": "Die Länge dieses Feldes kann maximal '{0}'-Zeichen betragen",
"Universe": "Universum",
"FortyTwo": "Zweiundvierzig"
}
}

@ -0,0 +1,11 @@
{
"culture": "nl",
"texts": {
"Hello <b>{0}</b>.": "Hallo <b>{0}</b>.",
"Car": "Auto",
"CarPlural": "Auto's",
"MaxLenghtErrorMessage": "De lengte van dit veld mag maximaal '{0}' tekens zijn",
"Universe": "Universum",
"FortyTwo": "Tweeënveertig"
}
}

@ -0,0 +1,8 @@
{
"culture": "de",
"texts": {
"Hello <b>{0}</b>.": "Hallo <b>{0}</b>.",
"Car": "Auto",
"SeeYou": "Bis bald"
}
}

@ -0,0 +1,6 @@
{
"culture": "nl",
"texts": {
"SeeYou": "Tot ziens"
}
}

@ -0,0 +1,45 @@
{
"culture": "de",
"texts": {
"UserName": "Benutzername",
"EmailAddress": "E-Mail-Adresse",
"UserNameOrEmailAddress": "Benutzername oder E-Mail-Adresse",
"Password": "Passwort",
"RememberMe": "Angemeldet bleiben",
"UseAnotherServiceToLogin": "Einen anderen Dienst zum Anmelden verwenden",
"UserLockedOutMessage": "Das Benutzerkonto wurde aufgrund fehlgeschlagener Anmeldeversuche gesperrt. Bitte warten Sie eine Weile und versuchen Sie es erneut.",
"InvalidUserNameOrPassword": "Ungültiger Benutzername oder Passwort!",
"LoginIsNotAllowed": "Sie dürfen sich nicht anmelden! Sie müssen Ihre E-Mail/Telefonnummer bestätigen.",
"SelfRegistrationDisabledMessage": "Die Selbstregistrierung ist für diese Anwendung deaktiviert. Bitte wenden Sie sich an den Anwendungsadministrator, um einen neuen Benutzer zu registrieren.",
"LocalLoginDisabledMessage": "Die lokale Anmeldung ist für diese Anwendung deaktiviert.",
"Login": "Anmelden",
"Cancel": "Abbrechen",
"Register": "Registrieren",
"AreYouANewUser": "Neuer Benutzer?",
"AlreadyRegistered": "Bereits registriert?",
"InvalidLoginRequest": "Ungültige Login-Anfrage",
"ThereAreNoLoginSchemesConfiguredForThisClient": "Es sind keine Anmeldeschemata für diesen Client konfiguriert.",
"LogInUsingYourProviderAccount": "Melden Sie sich mit Ihrem {0}-Konto an",
"DisplayName:CurrentPassword": "Aktuelles Passwort",
"DisplayName:NewPassword": "Neues Passwort",
"DisplayName:NewPasswordConfirm": "Neues Passwort bestätigen",
"PasswordChangedMessage": "Ihr Passwort wurde erfolgreich geändert.",
"DisplayName:UserName": "Benutzername",
"DisplayName:Email": "E-Mail",
"DisplayName:Name": "Name",
"DisplayName:Surname": "Nachname",
"DisplayName:Password": "Passwort",
"DisplayName:EmailAddress": "E-Mail-Adresse",
"DisplayName:PhoneNumber": "Telefonnummer",
"PersonalSettings": "Persönliche Einstellungen",
"PersonalSettingsSaved": "Persönliche Einstellungen gespeichert",
"PasswordChanged": "Passwort geändert",
"NewPasswordConfirmFailed": "Bitte bestätigen Sie das neue Passwort.",
"Manage": "Verwalten",
"ManageYourProfile": "Ihr profil verwalten",
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Ist die Selbstregistrierung aktiviert",
"Description:Abp.Account.IsSelfRegistrationEnabled": "Gibt an, ob ein Benutzer das Konto selbst registrieren kann.",
"DisplayName:Abp.Account.EnableLocalLogin": "Authentifizierung mit einem lokalen Konto",
"Description:Abp.Account.EnableLocalLogin": "Gibt an, ob der Server Benutzern die Authentifizierung mit einem lokalen Konto erlaubt."
}
}

@ -0,0 +1,45 @@
{
"culture": "nl",
"texts": {
"UserName": "Gebruikersnaam",
"EmailAddress": "E-mailadres",
"UserNameOrEmailAddress": "Gebruikersnaam of e-mail adres",
"Password": "Wachtwoord",
"RememberMe": "Herinner me",
"UseAnotherServiceToLogin": "Gebruik een andere dienst om in te loggen",
"UserLockedOutMessage": "Het gebruikersaccount is geblokkeerd vanwege ongeldige inlogpogingen. Wacht even en probeer het opnieuw.",
"InvalidUserNameOrPassword": "Ongeldige gebruikersnaam of wachtwoord!",
"LoginIsNotAllowed": "U mag niet inloggen! U moet uw e-mailadres / telefoonnummer bevestigen.",
"SelfRegistrationDisabledMessage": "Zelfregistratie is uitgeschakeld voor deze applicatie. Neem contact op met de applicatiebeheerder om een nieuwe gebruiker te registreren.",
"LocalLoginDisabledMessage": "Lokale aanmelding is uitgeschakeld voor deze applicatie.",
"Login": "Log in",
"Cancel": "Annuleer",
"Register": "Registreer",
"AreYouANewUser": "Bent u een nieuwe gebruiker?",
"AlreadyRegistered": "Al geregistreerd?",
"InvalidLoginRequest": "Ongeldig inlogverzoek",
"ThereAreNoLoginSchemesConfiguredForThisClient": "Er zijn geen aanmeldingsschema's geconfigureerd voor deze client.",
"LogInUsingYourProviderAccount": "Log in met uw {0} -account",
"DisplayName:CurrentPassword": "Huidig wachtwoord",
"DisplayName:NewPassword": "Nieuw wachtwoord",
"DisplayName:NewPasswordConfirm": "Bevestig nieuw wachtwoord",
"PasswordChangedMessage": "Uw wachtwoord is met succes veranderd.",
"DisplayName:UserName": "Gebruikersnaam",
"DisplayName:Email": "E-mail",
"DisplayName:Name": "Naam",
"DisplayName:Surname": "Achternaam",
"DisplayName:Password": "Wachtwoord",
"DisplayName:EmailAddress": "E-mail adres",
"DisplayName:PhoneNumber": "Telefoonnummer",
"PersonalSettings": "Persoonlijke instellingen",
"PersonalSettingsSaved": "Persoonlijke instellingen opgeslagen",
"PasswordChanged": "wachtwoord veranderd",
"NewPasswordConfirmFailed": "Bevestig het nieuwe wachtwoord a.u.b..",
"Manage": "Beheer",
"ManageYourProfile": "Beheer uw profiel",
"DisplayName:Abp.Account.IsSelfRegistrationEnabled": "Is zelfregistratie ingeschakeld",
"Description:Abp.Account.IsSelfRegistrationEnabled": "Of een gebruiker het account zelf kan registreren.",
"DisplayName:Abp.Account.EnableLocalLogin": "Verifieer met een lokaal account",
"Description:Abp.Account.EnableLocalLogin": "Geeft aan of de server gebruikers toestaat zich te verifiëren met een lokaal account."
}
}

@ -0,0 +1,14 @@
{
"culture": "de",
"texts": {
"Permission:Blogging": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Posts": "Beiträge",
"Permission:Tags": "Tags",
"Permission:Comments": "Kommentare",
"Permission:Management": "Verwaltung",
"Permission:Edit": "Bearbeiten",
"Permission:Create": "Erstellen",
"Permission:Delete": "Löschen"
}
}

@ -0,0 +1,14 @@
{
"culture": "nl",
"texts": {
"Permission:Blogging": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Posts": "Posts",
"Permission:Tags": "Tags",
"Permission:Comments": "Kommentaar",
"Permission:Management": "Beheer",
"Permission:Edit": "Bewerk",
"Permission:Create": "Maak aan",
"Permission:Delete": "Verwijder"
}
}

@ -0,0 +1,49 @@
{
"culture": "de",
"texts": {
"Menu:Blogs": "Blogs",
"Menu:BlogManagement": "Blog-Verwaltung",
"Title": "Titel",
"Delete": "Löschen",
"Reply": "Antwort",
"ReplyTo": "Antwort auf {0}",
"ContinueReading": "Weiterlesen",
"DaysAgo": "vor {0} Tagen",
"YearsAgo": "vor {0} Jahren",
"MonthsAgo": "vor {0} Monaten",
"WeeksAgo": "vor {0} Wochen",
"MinutesAgo": "vor {0} Minuten",
"SecondsAgo": "vor {0} Sekunden",
"HoursAgo": "vor {0} Stunden",
"Now": "jetzt",
"Content": "Inhalt",
"SeeAll": "Alle anzeigen",
"PopularTags": "Beliebte Tags",
"WiewsWithCount": "{0} Aufrufe",
"LastPosts": "Letzte Beiträge",
"LeaveComment": "Kommentar hinterlassen",
"TagsInThisArticle": "Tags in diesem Artikel",
"Posts": "Beiträge",
"Edit": "Bearbeiten",
"BLOG": "BLOG",
"CommentDeletionWarningMessage": "Kommentar wird gelöscht.",
"PostDeletionWarningMessage": "Beitrag wird gelöscht.",
"BlogDeletionWarningMessage": "Blog wird gelöscht.",
"AreYouSure": "Sind Sie sicher?",
"CommentWithCount": "{0} Kommentare",
"Comment": "Kommentar",
"ShareOnTwitter": "Auf Twitter teilen",
"CoverImage": "Titelbild",
"CreateANewPost": "Neuen Beitrag erstellen",
"CreateANewBlog": "Neuen Blog erstellen",
"WhatIsNew": "Was ist neu?",
"Name": "Name",
"ShortName": "Kurzname",
"CreationTime": "Erstellungszeit",
"Description": "Beschreibung",
"Blogs": "Blogs",
"Tags": "Tags",
"ShareOn": "Teilen auf",
"TitleLengthWarning": "Halten Sie Ihren Titel unter 60 Zeichen, um SEO-freundlich zu sein!"
}
}

@ -0,0 +1,49 @@
{
"culture": "nl",
"texts": {
"Menu:Blogs": "Blogs",
"Menu:BlogManagement": "Blog Beheer",
"Title": "Titel",
"Delete": "Verwijder",
"Reply": "Antwoord",
"ReplyTo": "Antwoord aan {0}",
"ContinueReading": "Lees verder",
"DaysAgo": "{0} dagen geleden",
"YearsAgo": "{0} jaar geleden",
"MonthsAgo": "{0} maanden geleden",
"WeeksAgo": "{0} weken geleden",
"MinutesAgo": "{0} minuten geleden",
"SecondsAgo": "{0} seconden geleden",
"HoursAgo": "{0} uur geleden",
"Now": "nu",
"Content": "Inhoud",
"SeeAll": "Alles zien",
"PopularTags": "Populaire tags",
"WiewsWithCount": "{0} keer bekeken",
"LastPosts": "Laatste berichten",
"LeaveComment": "Laat commentaar achter",
"TagsInThisArticle": "Tags in dit artikel",
"Posts": "Berichten",
"Edit": "Bewerk",
"BLOG": "BLOG",
"CommentDeletionWarningMessage": "Reactie wordt verwijderd.",
"PostDeletionWarningMessage": "Berich wordt verwijderd.",
"BlogDeletionWarningMessage": "Blog wordt verwijderd.",
"AreYouSure": "Weet u het zeker?",
"CommentWithCount": "{0} reacties",
"Comment": "Reactie",
"ShareOnTwitter": "Delen op Twitter",
"CoverImage": "Omslagfoto",
"CreateANewPost": "Maak een nieuw bericht",
"CreateANewBlog": "Maak een nieuwe Blog",
"WhatIsNew": "Wat is nieuw?",
"Name": "Naam",
"ShortName": "Korte naam",
"CreationTime": "Creatie tijd",
"Description": "Beschrijving",
"Blogs": "Blogs",
"Tags": "Tags",
"ShareOn": "Delen op",
"TitleLengthWarning": "Houd uw titel kleiner dan 60 tekens om SEO-vriendelijk te zijn!"
}
}

@ -0,0 +1,10 @@
{
"culture": "de",
"texts": {
"DocsTitle": "VoloDocs",
"WelcomeVoloDocs": "Willkommen bei den VoloDocs!",
"NoProjectWarning": "Es gibt noch kein definiertes Projekt!",
"CreateYourFirstProject": "Klicken Sie hier, um Ihr erstes Projekt zu starten",
"NoProject": "Kein Projekt!"
}
}

@ -0,0 +1,10 @@
{
"culture": "nl",
"texts": {
"DocsTitle": "VoloDocs",
"WelcomeVoloDocs": "Welkom bij de VoloDocs!",
"NoProjectWarning": "Er is nog geen gedefinieerd project!",
"CreateYourFirstProject": "Klik hier om uw eerste project te starten",
"NoProject": "Geen project!"
}
}

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Services;
namespace Volo.Docs.Admin.Documents
@ -10,7 +11,5 @@ namespace Volo.Docs.Admin.Documents
Task PullAllAsync(PullAllDocumentInput input);
Task PullAsync(PullDocumentInput input);
Task ReindexAsync();
}
}
}

@ -0,0 +1,37 @@
{
"culture": "de",
"texts": {
"Permission:DocumentManagement": "Dokumentenverwaltung",
"Permission:Projects": "Projekte",
"Permission:Edit": "Bearbeiten",
"Permission:Delete": "Löschen",
"Permission:Create": "Erstellen",
"Permission:Documents": "Dokumente",
"Menu:DocumentManagement": "Dokumente",
"Menu:ProjectManagement": "Projekte",
"CreateANewProject": "Neues Projekt erstellen",
"Edit": "Bearbeiten",
"Create": "Erstellen",
"Pull": "Pull",
"Projects": "Projekte",
"Name": "Name",
"ShortName": "Kurzname",
"DocumentStoreType": "DocumentStoreType",
"Format": "Format",
"ShortNameInfoText": "Wird für eindeutige URL verwendet.",
"DisplayName:Name": "Name",
"DisplayName:ShortName": "Kurzname",
"DisplayName:Format": "Format",
"DisplayName:DefaultDocumentName": "Standard-Dokumentname",
"DisplayName:NavigationDocumentName": "Name des Navigationsdokuments",
"DisplayName:MinimumVersion": "Mindestversion",
"DisplayName:MainWebsiteUrl": "Haupt-URL der Website",
"DisplayName:LatestVersionBranchName": "Zweigname der neuesten Version",
"DisplayName:GitHubRootUrl": "GitHub-Stamm-URL",
"DisplayName:GitHubAccessToken": "GitHub-Zugriffstoken",
"DisplayName:GitHubUserAgent": "GitHub-Benutzer-Agent",
"DisplayName:All": "Pull all",
"DisplayName:LanguageCode": "Sprachcode",
"DisplayName:Version": "Version"
}
}

@ -0,0 +1,37 @@
{
"culture": "nl",
"texts": {
"Permission:DocumentManagement": "Document beheer",
"Permission:Projects": "Projecten",
"Permission:Edit": "Bewerk",
"Permission:Delete": "Verwijder",
"Permission:Create": "Maak aan",
"Permission:Documents": "Documenten",
"Menu:DocumentManagement": "Documenten",
"Menu:ProjectManagement": "Projecten",
"CreateANewProject": "Maak een nieuw project",
"Edit": "Bewerk",
"Create": "Maak aan",
"Pull": "Pull",
"Projects": "Projecten",
"Name": "Naam",
"ShortName": "Korte naam",
"DocumentStoreType": "DocumentStoreType",
"Format": "Formaat",
"ShortNameInfoText": "Wordt gebruikt voor unieke URL.",
"DisplayName:Name": "Naam",
"DisplayName:ShortName": "Korte naam",
"DisplayName:Format": "Formaat",
"DisplayName:DefaultDocumentName": "Standaard documentnaam",
"DisplayName:NavigationDocumentName": "Navigatiedocumentnaam",
"DisplayName:MinimumVersion": "Minimale versie",
"DisplayName:MainWebsiteUrl": "URL van hoofdwebsite",
"DisplayName:LatestVersionBranchName": "Laatste versie vertakkingsnaam",
"DisplayName:GitHubRootUrl": "GitHub root URL",
"DisplayName:GitHubAccessToken": "GitHub-toegangstoken",
"DisplayName:GitHubUserAgent": "GitHub-gebruikersagent",
"DisplayName:All": "Pull all",
"DisplayName:LanguageCode": "Taalcode",
"DisplayName:Version": "Versie"
}
}

@ -12,9 +12,13 @@ namespace Volo.Docs.Admin.Projects
Task<ProjectDto> GetAsync(Guid id);
Task<ProjectDto> CreateAsync(CreateProjectDto input);
Task<ProjectDto> UpdateAsync(Guid id, UpdateProjectDto input);
Task DeleteAsync(Guid id);
Task ReindexAsync(ReindexInput input);
Task ReindexAllAsync();
}
}
}

@ -0,0 +1,9 @@
using System;
namespace Volo.Docs.Admin.Projects
{
public class ReindexInput
{
public Guid ProjectId { get; set; }
}
}

@ -25,17 +25,13 @@ namespace Volo.Docs.Admin.Documents
private readonly IDistributedCache<DocumentUpdateInfo> _documentUpdateCache;
private readonly IDistributedCache<List<VersionInfo>> _versionCache;
private readonly IDistributedCache<LanguageConfig> _languageCache;
private readonly IDistributedCache<DocumentResource> _resourceCache;
private readonly IDocumentFullSearch _documentFullSearch;
public DocumentAdminAppService(IProjectRepository projectRepository,
IDocumentRepository documentRepository,
IDocumentSourceFactory documentStoreFactory,
IDistributedCache<DocumentUpdateInfo> documentUpdateCache,
IDistributedCache<List<VersionInfo>> versionCache,
IDistributedCache<LanguageConfig> languageCache,
IDistributedCache<DocumentResource> resourceCache,
IDocumentFullSearch documentFullSearch)
IDistributedCache<LanguageConfig> languageCache)
{
_projectRepository = projectRepository;
_documentRepository = documentRepository;
@ -43,8 +39,6 @@ namespace Volo.Docs.Admin.Documents
_documentUpdateCache = documentUpdateCache;
_versionCache = versionCache;
_languageCache = languageCache;
_resourceCache = resourceCache;
_documentFullSearch = documentFullSearch;
LocalizationResource = typeof(DocsResource);
}
@ -125,32 +119,6 @@ namespace Volo.Docs.Admin.Documents
await UpdateDocumentUpdateInfoCache(sourceDocument);
}
public async Task ReindexAsync()
{
var docs = await _documentRepository.GetListAsync();
var projects = await _projectRepository.GetListAsync();
foreach (var doc in docs)
{
var project = projects.FirstOrDefault(x => x.Id == doc.ProjectId);
if (project == null)
{
continue;
}
if (doc.FileName == project.NavigationDocumentName)
{
continue;
}
if (doc.FileName == project.ParametersDocumentName)
{
continue;
}
await _documentFullSearch.AddOrUpdateAsync(doc);
}
}
private async Task UpdateDocumentUpdateInfoCache(Document document)
{
var cacheKey = $"DocumentUpdateInfo{document.ProjectId}#{document.Name}#{document.LanguageCode}#{document.Version}";
@ -174,4 +142,4 @@ namespace Volo.Docs.Admin.Documents
return document;
}
}
}
}

@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Guids;
using Volo.Docs.Documents;
using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Docs.Localization;
using Volo.Docs.Projects;
@ -14,15 +17,22 @@ namespace Volo.Docs.Admin.Projects
public class ProjectAdminAppService : ApplicationService, IProjectAdminAppService
{
private readonly IProjectRepository _projectRepository;
private readonly IDocumentRepository _documentRepository;
private readonly IDocumentFullSearch _documentFullSearch;
private readonly IGuidGenerator _guidGenerator;
public ProjectAdminAppService(
IProjectRepository projectRepository, IGuidGenerator guidGenerator)
IProjectRepository projectRepository,
IDocumentRepository documentRepository,
IDocumentFullSearch documentFullSearch,
IGuidGenerator guidGenerator)
{
ObjectMapperContext = typeof(DocsAdminApplicationModule);
LocalizationResource = typeof(DocsResource);
_projectRepository = projectRepository;
_documentRepository = documentRepository;
_documentFullSearch = documentFullSearch;
_guidGenerator = guidGenerator;
}
@ -51,7 +61,7 @@ namespace Volo.Docs.Admin.Projects
{
throw new ProjectShortNameAlreadyExistsException(input.ShortName);
}
var project = new Project(_guidGenerator.Create(),
input.Name,
input.ShortName,
@ -71,7 +81,7 @@ namespace Volo.Docs.Admin.Projects
{
project.ExtraProperties.Add(extraProperty.Key, extraProperty.Value);
}
project = await _projectRepository.InsertAsync(project);
return ObjectMapper.Map<Project, ProjectDto>(project);
@ -106,6 +116,57 @@ namespace Volo.Docs.Admin.Projects
{
await _projectRepository.DeleteAsync(id);
}
public async Task ReindexAsync(ReindexInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);
await _documentFullSearch.DeleteAllByProjectIdAsync(project.Id);
var docs = await _documentRepository.GetListByProjectId(project.Id);
foreach (var doc in docs)
{
if (doc.FileName == project.NavigationDocumentName)
{
continue;
}
if (doc.FileName == project.ParametersDocumentName)
{
continue;
}
await _documentFullSearch.AddOrUpdateAsync(doc);
}
}
public async Task ReindexAllAsync()
{
await _documentFullSearch.DeleteAllAsync();
var docs = await _documentRepository.GetListAsync();
var projects = await _projectRepository.GetListAsync();
foreach (var doc in docs)
{
var project = projects.FirstOrDefault(x => x.Id == doc.ProjectId);
if (project == null)
{
continue;
}
if (doc.FileName == project.NavigationDocumentName)
{
continue;
}
if (doc.FileName == project.ParametersDocumentName)
{
continue;
}
await _documentFullSearch.AddOrUpdateAsync(doc);
}
}
}
}
}

@ -39,12 +39,5 @@ namespace Volo.Docs.Admin
{
return _documentAdminAppService.PullAsync(input);
}
[HttpPost]
[Route("Reindex")]
public Task ReindexAsync()
{
return _documentAdminAppService.ReindexAsync();
}
}
}

@ -53,5 +53,19 @@ namespace Volo.Docs.Admin
{
return _projectAppService.DeleteAsync(id);
}
[HttpPost]
[Route("ReindexAll")]
public Task ReindexAllAsync()
{
return _projectAppService.ReindexAllAsync();
}
[HttpPost]
[Route("Reindex")]
public Task ReindexAsync(ReindexInput input)
{
return _projectAppService.ReindexAsync(input);
}
}
}

@ -39,6 +39,10 @@
</abp-dropdown-menu>
</abp-dropdown>
}
@if (await Authorization.IsGrantedAsync(DocsAdminPermissions.Projects.Default))
{
<abp-button button-type="Primary" icon="plus" text="@L["ReIndexAllProjects"].Value" id="ReIndexAllProjects" />
}
</abp-column>
</abp-row>
</abp-card-header>

@ -74,6 +74,18 @@
_dataTable.ajax.reload();
});
}
},
{
text: l('ReIndexProject'),
visible: abp.auth.isGranted('Docs.Admin.Documents'),
confirmMessage: function (data) { return l('ReIndexProjectConfirmationMessage', data.record.name); },
action: function (data) {
volo.docs.admin.projectsAdmin
.reindex({ projectId: data.record.id})
.then(function () {
abp.message.success(l('SuccessfullyReIndexProject', data.record.name));
});
}
}
]
}
@ -110,6 +122,19 @@
_createModal.open({source:"GitHub"});
});
$("#ReIndexAllProjects").click(function (event) {
abp.message.confirm(l('ReIndexAllProjectConfirmationMessage'))
.done(function (accepted) {
if (accepted) {
volo.docs.admin.projectsAdmin
.reindexAll()
.then(function () {
abp.message.success(l('SuccessfullyReIndexAllProject'));
});
}
});
});
_createModal.onClose(function () {
_dataTable.ajax.reload();
});
@ -118,4 +143,4 @@
_dataTable.ajax.reload();
});
});
});

@ -92,6 +92,48 @@ namespace Volo.Docs.Documents.FullSearch.Elastic
.DeleteAsync(DocumentPath<Document>.Id(id), x => x.Index(_options.IndexName), cancellationToken));
}
public async Task DeleteAllAsync(CancellationToken cancellationToken = default)
{
ValidateElasticSearchEnabled();
var request = new DeleteByQueryRequest(_options.IndexName)
{
Query = new MatchAllQuery()
};
HandleError(await _clientProvider.GetClient()
.DeleteByQueryAsync(request, cancellationToken));
}
public async Task DeleteAllByProjectIdAsync(Guid projectId, CancellationToken cancellationToken = default)
{
ValidateElasticSearchEnabled();
var request = new DeleteByQueryRequest(_options.IndexName)
{
Query = new BoolQuery
{
Filter = new QueryContainer[]
{
new BoolQuery
{
Must = new QueryContainer[]
{
new TermQuery
{
Field = "projectId",
Value = projectId
}
}
}
}
},
};
HandleError(await _clientProvider.GetClient()
.DeleteByQueryAsync(request, cancellationToken));
}
public async Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode,
string version, int? skipCount = null, int? maxResultCount = null,
CancellationToken cancellationToken = default)
@ -185,4 +227,4 @@ namespace Volo.Docs.Documents.FullSearch.Elastic
}
}
}
}
}

@ -13,8 +13,12 @@ namespace Volo.Docs.Documents.FullSearch.Elastic
Task DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task DeleteAllAsync(CancellationToken cancellationToken = default);
Task DeleteAllByProjectIdAsync(Guid projectId, CancellationToken cancellationToken = default);
Task<List<EsDocument>> SearchAsync(string context, Guid projectId, string languageCode,
string version, int? skipCount = null, int? maxResultCount = null,
CancellationToken cancellationToken = default);
}
}
}

@ -0,0 +1,31 @@
{
"culture": "de",
"texts": {
"Documents": "Dokumente",
"BackToWebsite": "Zurück zur Website",
"Contributors": "Mitwirkende",
"ShareOn": "Teilen auf",
"Version": "Version",
"Edit": "Bearbeiten",
"LastEditTime": "Letzte Bearbeitung",
"Delete": "Löschen",
"ClearCache": "Cache leeren",
"ClearCacheConfirmationMessage": "Sind Sie sicher, dass Sie alle Caches für das Projekt löschen \"{0}\"",
"InThisDocument": "In diesem Dokument",
"GoToTop": "Nach oben",
"Projects": "Projekt(e)",
"NoProjectWarning": "Es gibt noch keine Projekte!",
"DocumentNotFound": "Hoppla, das angeforderte Dokument wurde nicht gefunden!",
"NavigationDocumentNotFound": "Diese Version hat kein Navigationsdokument!",
"DocumentNotFoundInSelectedLanguage": "Das Dokument wurde nicht in der von Ihnen gewünschten Sprache gefunden. Das Dokument wird in der Standardsprache angezeigt.",
"FilterTopics": "Themen filtern",
"FullSearch": "Suche in Dokumenten",
"Volo.Docs.Domain:010001": "Elastic search ist nicht aktiviert.",
"MultipleVersionDocumentInfo": "Dieses Dokument hat mehrere Versionen. Wählen Sie die für Sie am besten geeigneten Optionen aus.",
"New": "Neu",
"Upd": "Upd",
"NewExplanation": "Erstellt in den letzten zwei Wochen.",
"UpdatedExplanation": "Aktualisiert in den letzten zwei Wochen.",
"Volo.Docs.Domain:010002": "Kurzname {ShortName} existiert bereits."
}
}

@ -11,6 +11,12 @@
"Delete": "Delete",
"ClearCache": "Clear cache",
"ClearCacheConfirmationMessage": "Are you sure to clear all caches for project \"{0}\"",
"ReIndexAllProjects": "ReIndex all projects",
"ReIndexProject": "ReIndex project",
"ReIndexProjectConfirmationMessage":"Are you sure to reindex for project \"{0}\"",
"SuccessfullyReIndexProject": "Successfully reindex for project \"{0}\"",
"ReIndexAllProjectConfirmationMessage": "Are you sure to reindex all project?",
"SuccessfullyReIndexAllProject": "Successfully reindex for all projects",
"InThisDocument": "In this document",
"GoToTop": "Go to top",
"Projects": "Project(s)",

@ -0,0 +1,31 @@
{
"culture": "nl",
"texts": {
"Documents": "Documenten",
"BackToWebsite": "Terug naar website",
"Contributors": "Bijdragers",
"ShareOn": "Delen op",
"Version": "Versie",
"Edit": "Bewerk",
"LastEditTime": "Laatste bewerking",
"Delete": "Verwijder",
"ClearCache": "Cache wissen",
"ClearCacheConfirmationMessage": "Weet u zeker dat u alle caches voor het project \"{0}\" wilt wissen?",
"InThisDocument": "In dit document",
"GoToTop": "Ga naar boven",
"Projects": "Project (en)",
"NoProjectWarning": "Er zijn nog geen projecten!",
"DocumentNotFound": "Oeps, het opgevraagde document is niet gevonden!",
"NavigationDocumentNotFound": "Deze versie heeft geen navigatiedocument!",
"DocumentNotFoundInSelectedLanguage": "Document in de gewenste taal is niet gevonden. Document in de standaardtaal wordt weergegeven.",
"FilterTopics": "Filter onderwerpen",
"FullSearch": "Zoek in documenten",
"Volo.Docs.Domain:010001": "Elastisch zoeken is niet ingeschakeld.",
"MultipleVersionDocumentInfo": "Dit document heeft meerdere versies. Selecteer de opties die het beste bij u passen.",
"New": "Nieuw",
"Upd": "Bew",
"NewExplanation": "Gemaakt in de afgelopen twee weken.",
"UpdatedExplanation": "Bewerkt in de afgelopen twee weken.",
"Volo.Docs.Domain:010002": "KorteNaam {ShortName} bestaat al."
}
}

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"Features": "Funktionen",
"NoFeatureFoundMessage": "Es ist keine Funktion verfügbar."
}
}

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"Features": "Functies",
"NoFeatureFoundMessage": "Er is geen functie beschikbaar."
}
}

@ -0,0 +1,104 @@
{
"culture": "de",
"texts": {
"Menu:IdentityManagement": "Identitätsverwaltung",
"Users": "Benutzer",
"NewUser": "Neuer Benutzer",
"UserName": "Benutzername",
"EmailAddress": "E-Mail-Adresse",
"PhoneNumber": "Telefonnummer",
"UserInformations": "Benutzerinformationen",
"DisplayName:IsDefault": "Standard",
"DisplayName:IsStatic": "Statisch",
"DisplayName:IsPublic": "Öffentlich",
"Roles": "Rollen",
"Password": "Passwort",
"PersonalInfo": "Mein Profil",
"PersonalSettings": "Persönliche Einstellungen",
"UserDeletionConfirmationMessage": "Der Benutzer '{0}' wird gelöscht. Sind Sie sicher?",
"RoleDeletionConfirmationMessage": "Die Rolle '{0}' wird gelöscht. Sind Sie sicher?",
"DisplayName:RoleName": "Rollenname",
"DisplayName:UserName": "Benutzername",
"DisplayName:Name": "Name",
"DisplayName:Surname": "Nachname",
"DisplayName:Password": "Passwort",
"DisplayName:Email": "E-Mail-Adresse",
"DisplayName:PhoneNumber": "Telefonnummer",
"DisplayName:TwoFactorEnabled": "Zwei-Faktor-Verifizierung",
"DisplayName:LockoutEnabled": "Konto nach fehlgeschlagenen Anmeldeversuchen sperren",
"NewRole": "Neue Rolle",
"RoleName": "Rollenname",
"CreationTime": "Erstellungszeit",
"Permissions": "Berechtigungen",
"DisplayName:CurrentPassword": "Aktuelles Passwort",
"DisplayName:NewPassword": "Neues Passwort",
"DisplayName:NewPasswordConfirm": "Neues Passwort bestätigen",
"PasswordChangedMessage": "Ihr Passwort wurde erfolgreich geändert.",
"PersonalSettingsSavedMessage": "Ihre persönlichen Einstellungen wurden erfolgreich gespeichert.",
"Identity.DefaultError": "Ein unbekannter Fehler ist aufgetreten.",
"Identity.ConcurrencyFailure": "Optimistischer Nebenläufigkeitsfehler, Objekt wurde geändert.",
"Identity.DuplicateEmail": "E-Mail '{0}' ist bereits vergeben.",
"Identity.DuplicateRoleName": "Der Rollenname '{0}' ist bereits vergeben.",
"Identity.DuplicateUserName": "Der Benutzername '{0}' ist bereits vergeben.",
"Identity.InvalidEmail": "E-Mail '{0}' ist ungültig.",
"Identity.InvalidPasswordHasherCompatibilityMode": "Der angegebene PasswordHasherCompatibilityMode ist ungültig.",
"Identity.InvalidPasswordHasherIterationCount": "Die Iterationszahl muss eine positive Ganzzahl sein.",
"Identity.InvalidRoleName": "Der Rollenname '{0}' ist ungültig.",
"Identity.InvalidToken": "Ungültiges Token.",
"Identity.InvalidUserName": "Benutzername '{0}' ist ungültig, kann nur Buchstaben oder Ziffern enthalten.",
"Identity.LoginAlreadyAssociated": "Ein Benutzer mit diesem Login existiert bereits.",
"Identity.PasswordMismatch": "Falsches Passwort.",
"Identity.PasswordRequiresDigit": "Passwörter müssen mindestens eine Ziffer ('0'-'9') enthalten.",
"Identity.PasswordRequiresLower": "Passwörter müssen mindestens einen Kleinbuchstaben ('a'-'z') enthalten.",
"Identity.PasswordRequiresNonAlphanumeric": "Passwörter müssen mindestens ein Sonderzeichen enthalten.",
"Identity.PasswordRequiresUpper": "Passwörter müssen mindestens einen Großbuchstaben ('A'-'Z') enthalten.",
"Identity.PasswordTooShort": "Passwörter müssen mindestens {0} Zeichen enthalten.",
"Identity.RoleNotFound": "Die Rolle {0} existiert nicht.",
"Identity.UserAlreadyHasPassword": "Der Benutzer hat bereits ein Passwort gesetzt.",
"Identity.UserAlreadyInRole": "Benutzer ist bereits in der Rolle '{0}'.",
"Identity.UserLockedOut": "Benutzer ist ausgesperrt.",
"Identity.UserLockoutNotEnabled": "Aussperrung ist für diesen Benutzer nicht aktiviert.",
"Identity.UserNameNotFound": "Benutzer {0} existiert nicht.",
"Identity.UserNotInRole": "Benutzer ist nicht in der Rolle '{0}'.",
"Identity.PasswordConfirmationFailed": "Passwort stimmt nicht mit dem Bestätigungspasswort überein.",
"Identity.StaticRoleRenamingErrorMessage": "Statische Rollen können nicht umbenannt werden.",
"Identity.StaticRoleDeletionErrorMessage": "Statische Rollen können nicht gelöscht werden.",
"Volo.Abp.Identity:010001": "Sie können Ihr eigenes Konto nicht löschen!",
"Permission:IdentityManagement": "Identitätsverwaltung",
"Permission:RoleManagement": "Rollenverwaltung",
"Permission:Create": "Erstellen",
"Permission:Edit": "Bearbeiten",
"Permission:Delete": "Löschen",
"Permission:ChangePermissions": "Berechtigungen ändern",
"Permission:UserManagement": "Benutzerverwaltung",
"Permission:UserLookup": "Benutzer-Lookup",
"DisplayName:Abp.Identity.Password.RequiredLength": "Erforderliche Länge",
"DisplayName:Abp.Identity.Password.RequiredUniqueChars": "Erforderliche eindeutige Zeichenanzahl",
"DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "Erforderliches Sonderzeichen",
"DisplayName:Abp.Identity.Password.RequireLowercase": "Erforderliche Kleinbuchstaben",
"DisplayName:Abp.Identity.Password.RequireUppercase": "Erforderliche Großbuchstaben",
"DisplayName:Abp.Identity.Password.RequireDigit": "Erforderliche Ziffer",
"DisplayName:Abp.Identity.Lockout.AllowedForNewUsers": "Aktiviert für neue Benutzer",
"DisplayName:Abp.Identity.Lockout.LockoutDuration": "Aussperrdauer (Sekunden)",
"DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Max fehlgeschlagene Zugriffsversuche",
"DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail": "Bestätigte E-Mail erforderlich",
"DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Telefonnummer-Bestätigung aktivieren",
"DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Bestätigte Telefonnummer erforderlich",
"DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled": "Ist die Aktualisierung des Benutzernamens aktiviert?",
"DisplayName:Abp.Identity.User.IsEmailUpdateEnabled": "Ist die E-Mail-Aktualisierung aktiviert?",
"Description:Abp.Identity.Password.RequiredLength": "Die Mindestlänge, die ein Passwort haben muss.",
"Description:Abp.Identity.Password.RequiredUniqueChars": "Die Mindestanzahl eindeutiger Zeichen, die ein Passwort enthalten muss.",
"Description:Abp.Identity.Password.RequireNonAlphanumeric": "Ob Passwörter ein nicht-alphanumerisches Zeichen enthalten müssen.",
"Description:Abp.Identity.Password.RequireLowercase": "Ob Passwörter ein kleingeschriebenes ASCII-Zeichen enthalten müssen.",
"Description:Abp.Identity.Password.RequireUppercase": "Ob Passwörter ein ASCII-Zeichen in Großbuchstaben enthalten müssen.",
"Description:Abp.Identity.Password.RequireDigit": "Ob Passwörter eine Ziffer enthalten müssen.",
"Description:Abp.Identity.Lockout.AllowedForNewUsers": "Ob ein neuer Benutzer ausgesperrt werden kann.",
"Description:Abp.Identity.Lockout.LockoutDuration": "Die Dauer, für die ein Benutzer ausgesperrt ist, wenn eine Sperre auftritt.",
"Description:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Die Anzahl der fehlgeschlagenen Zugriffsversuche, die erlaubt sind, bevor ein Benutzer gesperrt wird, vorausgesetzt, die Sperre ist aktiviert.",
"Description:Abp.Identity.SignIn.RequireConfirmedEmail": "Whether a confirmed email address is required to sign in.",
"Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Ob für die Anmeldung eine bestätigte E-Mail-Adresse erforderlich ist.",
"Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Ob für die Anmeldung eine bestätigte Telefonnummer erforderlich ist.",
"Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Ob der Benutzername vom Benutzer aktualisiert werden kann.",
"Description:Abp.Identity.User.IsEmailUpdateEnabled": "Ob die E-Mail durch den Benutzer aktualisiert werden kann."
}
}

@ -0,0 +1,104 @@
{
"culture": "nl",
"texts": {
"Menu:IdentityManagement": "Identiteitsbeheer",
"Users": "Gebruikers",
"NewUser": "Nieuwe Gebruiker",
"UserName": "Gebruikersnaam",
"EmailAddress": "E-mail adres",
"PhoneNumber": "Telefoonnummer",
"UserInformations": "Gebruikers informatie",
"DisplayName:IsDefault": "Standaard",
"DisplayName:IsStatic": "Statisch",
"DisplayName:IsPublic": "Openbaar",
"Roles": "Roles",
"Password": "Wachtwoord",
"PersonalInfo": "Mijn profiel",
"PersonalSettings": "Persoonlijke instellingen",
"UserDeletionConfirmationMessage": "Gebruiker '{0}' wordt verwijderd. Bevestigt U dat?",
"RoleDeletionConfirmationMessage": "Rol '{0}' wordt verwijderd. Bevestigt U dat?",
"DisplayName:RoleName": "Rol naam",
"DisplayName:UserName": "Gebruikersnaam",
"DisplayName:Name": "Naam",
"DisplayName:Surname": "Achternaam",
"DisplayName:Password": "Wachtwoord",
"DisplayName:Email": "E-mail adres",
"DisplayName:PhoneNumber": "Telefoonnummer",
"DisplayName:TwoFactorEnabled": "Twee-factor-verificatie",
"DisplayName:LockoutEnabled": "Account vergrendelen na mislukte inlogpogingen",
"NewRole": "Nieuwe rol",
"RoleName": "Rol naam",
"CreationTime": "Creatie tijd",
"Permissions": "Rechten",
"DisplayName:CurrentPassword": "Huidig wachtwoord",
"DisplayName:NewPassword": "Nieuw wachtwoord",
"DisplayName:NewPasswordConfirm": "Bevestig nieuw wachtwoord",
"PasswordChangedMessage": "Uw wachtwoord is met succes veranderd.",
"PersonalSettingsSavedMessage": "Uw persoonlijke instellingen zijn succesvol opgeslagen.",
"Identity.DefaultError": "Er is een onbekende fout opgetreden.",
"Identity.ConcurrencyFailure": "Optimistische gelijktijdigheidsfout, object is gewijzigd.",
"Identity.DuplicateEmail": "E-mail '{0}' is al in gebruik.",
"Identity.DuplicateRoleName": "De rolnaam '{0}' is al in gebruik.",
"Identity.DuplicateUserName": "Gebruikersnaam '{0}' is al in gebruik.",
"Identity.InvalidEmail": "E-mail' {0} 'is ongeldig.",
"Identity.InvalidPasswordHasherCompatibilityMode": "De opgegeven PasswordHasherCompatibilityMode is ongeldig.",
"Identity.InvalidPasswordHasherIterationCount": "De iteratietelling moet een positief geheel getal zijn.",
"Identity.InvalidRoleName": "Rolnaam '{0}' is ongeldig.",
"Identity.InvalidToken": "Ongeldig token.",
"Identity.InvalidUserName": "Gebruikersnaam '{0}' is ongeldig, mag alleen letters of cijfers bevatten.",
"Identity.LoginAlreadyAssociated": "Er bestaat al een gebruiker met deze login.",
"Identity.PasswordMismatch": "Incorrect wachtwoord.",
"Identity.PasswordRequiresDigit": "Wachtwoorden moeten minimaal één cijfer bevatten ('0' - '9').",
"Identity.PasswordRequiresLower": "Wachtwoorden moeten minstens één kleine letter bevatten ('a' - 'z').",
"Identity.PasswordRequiresNonAlphanumeric": "Wachtwoorden moeten minimaal één niet-alfanumeriek teken bevatten.",
"Identity.PasswordRequiresUpper": "Wachtwoorden moeten ten minste één hoofdletter bevatten ('A' - 'Z').",
"Identity.PasswordTooShort": "Wachtwoorden moeten uit minimaal {0} tekens bestaan.",
"Identity.RoleNotFound": "Rol {0} bestaat niet.",
"Identity.UserAlreadyHasPassword": "Gebruiker heeft al een wachtwoord ingesteld.",
"Identity.UserAlreadyInRole": "Gebruiker al in rol '{0}'.",
"Identity.UserLockedOut": "Gebruiker is geblokkeerd.",
"Identity.UserLockoutNotEnabled": "Lockout is niet ingeschakeld voor deze gebruiker.",
"Identity.UserNameNotFound": "Gebruiker {0} bestaat niet.",
"Identity.UserNotInRole": "Gebruiker speelt geen rol '{0}'.",
"Identity.PasswordConfirmationFailed": "Wachtwoord komt niet overeen met de wachtwoord bevestiging.",
"Identity.StaticRoleRenamingErrorMessage": "Statische rollen kunnen niet worden hernoemd.",
"Identity.StaticRoleDeletionErrorMessage": "Statische rollen kunnen niet worden verwijderd.",
"Volo.Abp.Identity:010001": "U kunt uw eigen account niet verwijderen!",
"Permission:IdentityManagement": "Identiteitsbeheer",
"Permission:RoleManagement": "Rolbeheer",
"Permission:Create": "Maak aan",
"Permission:Edit": "Verander",
"Permission:Delete": "Verwijder",
"Permission:ChangePermissions": "Wijzig de rechten",
"Permission:UserManagement": "Gebruikersbeheer",
"Permission:UserLookup": "Gebruiker opzoeken",
"DisplayName:Abp.Identity.Password.RequiredLength": "Vereiste lengte",
"DisplayName:Abp.Identity.Password.RequiredUniqueChars": "Vereist aantal unieke tekens",
"DisplayName:Abp.Identity.Password.RequireNonAlphanumeric": "Vereist niet-alfanumeriek teken",
"DisplayName:Abp.Identity.Password.RequireLowercase": "Vereist kleine letter",
"DisplayName:Abp.Identity.Password.RequireUppercase": "Vereist hoofdletter",
"DisplayName:Abp.Identity.Password.RequireDigit": "Vereist cijfer",
"DisplayName:Abp.Identity.Lockout.AllowedForNewUsers": "Ingeschakeld voor nieuwe gebruikers",
"DisplayName:Abp.Identity.Lockout.LockoutDuration": "Blokkeringsduur (seconden)",
"DisplayName:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Max mislukte toegangspogingen",
"DisplayName:Abp.Identity.SignIn.RequireConfirmedEmail": "Vereist bevestigde e-mail",
"DisplayName:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Schakel telefoonnummerbevestiging in",
"DisplayName:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Vereist bevestigd telefoonnummer",
"DisplayName:Abp.Identity.User.IsUserNameUpdateEnabled": "Is gebruikersnaam aktualisering ingeschakeld",
"DisplayName:Abp.Identity.User.IsEmailUpdateEnabled": "Is e-mail aktualisering ingeschakeld",
"Description:Abp.Identity.Password.RequiredLength": "De minimale lengte die een wachtwoord moet hebben.",
"Description:Abp.Identity.Password.RequiredUniqueChars": "Het minimumaantal unieke tekens dat een wachtwoord moet bevatten.",
"Description:Abp.Identity.Password.RequireNonAlphanumeric": "Als wachtwoorden een niet-alfanumeriek teken moeten bevatten.",
"Description:Abp.Identity.Password.RequireLowercase": "Als wachtwoorden een ASCII-teken in kleine letters moeten bevatten.",
"Description:Abp.Identity.Password.RequireUppercase": "Als wachtwoorden een ASCII-teken in hoofdletters moeten bevatten.",
"Description:Abp.Identity.Password.RequireDigit": "Als wachtwoorden een cijfer moeten bevatten.",
"Description:Abp.Identity.Lockout.AllowedForNewUsers": "Of een nieuwe gebruiker kan worden geblokkeerd.",
"Description:Abp.Identity.Lockout.LockoutDuration": "De duur dat een gebruiker wordt geblokkeerd wanneer een blokkering optreedt.",
"Description:Abp.Identity.Lockout.MaxFailedAccessAttempts": "Het aantal mislukte toegangspogingen dat is toegestaan voordat een gebruiker wordt geblokkeerd, ervan uitgaande dat blokkering is ingeschakeld.",
"Description:Abp.Identity.SignIn.RequireConfirmedEmail": "Of een bevestigd e-mail adres vereist is om in te loggen.",
"Description:Abp.Identity.SignIn.EnablePhoneNumberConfirmation": "Of het telefoonnummer kan worden bevestigd door de gebruiker.",
"Description:Abp.Identity.SignIn.RequireConfirmedPhoneNumber": "Of een bevestigd telefoonnummer vereist is om in te loggen.",
"Description:Abp.Identity.User.IsUserNameUpdateEnabled": "Of de gebruikersnaam kan worden bijgewerkt door de gebruiker.",
"Description:Abp.Identity.User.IsEmailUpdateEnabled": "Of de e-mail door de gebruiker kan worden veranderd."
}
}

@ -0,0 +1,12 @@
{
"culture": "de",
"texts": {
"Volo.IdentityServer:DuplicateIdentityResourceName": "Identität Ressourcenname existiert bereits: {Name}",
"Volo.IdentityServer:DuplicateApiResourceName": "Api Ressourcenname existiert bereits: {Name}",
"Volo.IdentityServer:DuplicateClientId": "ClientId bereits vorhanden: {ClientId}",
"UserLockedOut": "Das Benutzerkonto wurde aufgrund ungültiger Anmeldeversuche ausgesperrt. Bitte warten Sie eine Weile und versuchen Sie es erneut.",
"InvalidUserNameOrPassword": "Ungültiger Benutzername oder Passwort!",
"LoginIsNotAllowed": "Sie dürfen sich nicht anmelden! Sie müssen Ihre E-Mail/Telefonnummer bestätigen.",
"InvalidUsername": "Ungültiger Benutzername oder Passwort!"
}
}

@ -0,0 +1,12 @@
{
"culture": "nl",
"texts": {
"Volo.IdentityServer:DuplicateIdentityResourceName": "Identiteitsbronnaam bestaat al: {Name}",
"Volo.IdentityServer:DuplicateApiResourceName": "API-bronnaam bestaat al: {Name}",
"Volo.IdentityServer:DuplicateClientId": "ClientId bestaat al: {ClientId}",
"UserLockedOut": "Het gebruikersaccount is geblokkeerd vanwege ongeldige inlogpogingen. Wacht even en probeer het opnieuw.",
"InvalidUserNameOrPassword": "ongeldige gebruikersnaam of wachtwoord!",
"LoginIsNotAllowed": "U mag niet inloggen! U moet uw e-mailadres / telefoonnummer bevestigen.",
"InvalidUsername": "Ongeldige gebruikersnaam of wachtwoord!"
}
}

@ -0,0 +1,10 @@
{
"culture": "de",
"texts": {
"Permissions": "Berechtigungen",
"OnlyProviderPermissons": "Nur dieser Anbieter",
"All": "Alle",
"SelectAllInAllTabs": "Alle Berechtigungen erteilen",
"SelectAllInThisTab": "Alle auswählen"
}
}

@ -0,0 +1,10 @@
{
"culture": "nl",
"texts": {
"Permissions": "Rechten",
"OnlyProviderPermissons": "Alleen deze provider",
"All": "Alle",
"SelectAllInAllTabs": "Verleen alle rechten",
"SelectAllInThisTab": "Selecteer alles"
}
}

@ -0,0 +1,7 @@
{
"culture": "de",
"texts": {
"Settings": "Einstellungen",
"SuccessfullySaved": "Erfolgreich gespeichert"
}
}

@ -0,0 +1,7 @@
{
"culture": "nl",
"texts": {
"Settings": "Instellingen",
"SuccessfullySaved": "Succesvol opgeslagen"
}
}

@ -0,0 +1,22 @@
{
"culture": "de",
"texts": {
"Menu:TenantManagement": "Mandantenverwaltung",
"Tenants": "Mandanten",
"NewTenant": "Neuer Mandant",
"TenantName": "Mandantenname",
"DisplayName:TenantName": "Mandantenname",
"TenantDeletionConfirmationMessage": "Der Mandant '{0}' wird gelöscht. Sind Sie sicher?",
"ConnectionStrings": "Verbindungszeichenfolgen",
"DisplayName:DefaultConnectionString": "Standard Verbindungszeichenfolge",
"DisplayName:UseSharedDatabase": "Nutze die gemeinsame Datenbank",
"Permission:TenantManagement": "Mandantenverwaltung",
"Permission:Create": "Erstellen",
"Permission:Edit": "Bearbeiten",
"Permission:Delete": "Löschen",
"Permission:ManageConnectionStrings": "Verbindungszeichenfolgen verwalten",
"Permission:ManageFeatures": "Funktionen verwalten",
"DisplayName:AdminEmailAddress": "Admin E-Mail-Adresse",
"DisplayName:AdminPassword": "Admin Password"
}
}

@ -0,0 +1,22 @@
{
"culture": "nl",
"texts": {
"Menu:TenantManagement": "Klanten beheer",
"Tenants": "Klanten",
"NewTenant": "Nieuwe klant",
"TenantName": "Naam klant",
"DisplayName:TenantName": "Naam klant",
"TenantDeletionConfirmationMessage": "Klant '{0}' wordt verwijderd. Bevestigt u dat?",
"ConnectionStrings": "Connection Strings",
"DisplayName:DefaultConnectionString": "Standaard Connection String",
"DisplayName:UseSharedDatabase": "Gebruik de gedeelde database",
"Permission:TenantManagement": "Klanten beheer",
"Permission:Create": "Maak aan",
"Permission:Edit": "Bewerk",
"Permission:Delete": "Verwijder",
"Permission:ManageConnectionStrings": "Beheer connection strings",
"Permission:ManageFeatures": "Beheer functies",
"DisplayName:AdminEmailAddress": "Admin e-mail adres",
"DisplayName:AdminPassword": "Admin wachtwoord"
}
}

@ -24,18 +24,18 @@
"devDependencies": {
"@abp/ng.account": "~2.6.2",
"@abp/ng.account.config": "~2.6.2",
"@abp/ng.core": "^2.6.2",
"@abp/ng.feature-management": "^2.6.2",
"@abp/ng.core": "~2.6.2",
"@abp/ng.feature-management": "~2.6.2",
"@abp/ng.identity": "~2.6.2",
"@abp/ng.identity.config": "~2.6.2",
"@abp/ng.permission-management": "^2.6.2",
"@abp/ng.permission-management": "~2.6.2",
"@abp/ng.setting-management": "~2.6.2",
"@abp/ng.setting-management.config": "~2.6.2",
"@abp/ng.tenant-management": "~2.6.2",
"@abp/ng.tenant-management.config": "~2.6.2",
"@abp/ng.theme.basic": "~2.6.2",
"@abp/ng.theme.shared": "^2.6.2",
"@abp/utils": "^2.6.0",
"@abp/ng.theme.shared": "~2.6.2",
"@abp/utils": "~2.6.2",
"@angular-builders/jest": "^8.2.0",
"@angular-devkit/build-angular": "~0.803.21",
"@angular-devkit/build-ng-packagr": "~0.803.21",
@ -54,7 +54,7 @@
"@fortawesome/fontawesome-free": "^5.12.1",
"@ng-bootstrap/ng-bootstrap": "^5.3.0",
"@ngneat/spectator": "^4.5.0",
"@ngx-validate/core": "^0.0.7",
"@ngx-validate/core": "^0.0.8",
"@ngxs/devtools-plugin": "^3.5.1",
"@ngxs/logger-plugin": "^3.5.1",
"@ngxs/router-plugin": "^3.6.2",
@ -97,5 +97,8 @@
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"resolutions": {
"@ngx-validate/core": "^0.0.8"
}
}

@ -1,51 +1,63 @@
<div class="card shadow-sm rounded mb-3">
<div class="card-body px-5">
<div class="row">
<div class="col">
<span style="font-size: 0.8em;" class="text-uppercase text-muted">{{
'AbpUiMultiTenancy::Tenant' | abpLocalization
}}</span
><br />
<h6 class="m-0 d-inline-block">
<span>
{{ tenantName || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}
</span>
</h6>
</div>
<div class="col-auto">
<a
id="AbpTenantSwitchLink"
href="javascript:void(0);"
class="btn btn-sm mt-3 btn-outline-primary"
(click)="onSwitch()"
>{{ 'AbpUiMultiTenancy::Switch' | abpLocalization }}</a
>
<ng-container *ngIf="(currentTenant$ | async) || {} as currentTenant">
<div class="card shadow-sm rounded mb-3">
<div class="card-body px-5">
<div class="row">
<div class="col">
<span style="font-size: 0.8em;" class="text-uppercase text-muted">{{
'AbpUiMultiTenancy::Tenant' | abpLocalization
}}</span
><br />
<h6 class="m-0 d-inline-block">
<i>{{ currentTenant.name || ('AbpUiMultiTenancy::NotSelected' | abpLocalization) }}</i>
</h6>
</div>
<div class="col-auto">
<a
id="AbpTenantSwitchLink"
href="javascript:void(0);"
class="btn btn-sm mt-3 btn-outline-primary"
(click)="onSwitch()"
>{{ 'AbpUiMultiTenancy::Switch' | abpLocalization }}</a
>
</div>
</div>
</div>
</div>
</div>
<abp-modal size="md" [(visible)]="isModalVisible" [busy]="inProgress">
<ng-template #abpHeader>
<h5>Switch Tenant</h5>
</ng-template>
<ng-template #abpBody>
<form (ngSubmit)="save()">
<div class="mt-2">
<div class="form-group">
<label for="name">{{ 'AbpUiMultiTenancy::Name' | abpLocalization }}</label>
<input [(ngModel)]="tenant.name" type="text" id="name" name="tenant" class="form-control" autofocus />
<abp-modal size="md" [(visible)]="isModalVisible" [busy]="modalBusy">
<ng-template #abpHeader>
<h5>Switch Tenant</h5>
</ng-template>
<ng-template #abpBody>
<form (ngSubmit)="save()">
<div class="mt-2">
<div class="form-group">
<label for="name">{{ 'AbpUiMultiTenancy::Name' | abpLocalization }}</label>
<input
[(ngModel)]="name"
type="text"
id="name"
name="tenant"
class="form-control"
autofocus
/>
</div>
<p>{{ 'AbpUiMultiTenancy::SwitchTenantHint' | abpLocalization }}</p>
</div>
<p>{{ 'AbpUiMultiTenancy::SwitchTenantHint' | abpLocalization }}</p>
</div>
</form>
</ng-template>
<ng-template #abpFooter>
<button #abpClose type="button" class="btn btn-secondary">
{{ 'AbpTenantManagement::Cancel' | abpLocalization }}
</button>
<abp-button buttonType="button" buttonClass="btn btn-primary" (click)="save()">
<i class="fa fa-check mr-1"></i> <span>{{ 'AbpTenantManagement::Save' | abpLocalization }}</span>
</abp-button>
</ng-template>
</abp-modal>
</form>
</ng-template>
<ng-template #abpFooter>
<button #abpClose type="button" class="btn btn-secondary">
{{ 'AbpTenantManagement::Cancel' | abpLocalization }}
</button>
<abp-button
type="abp-button"
iconClass="fa fa-check"
(click)="save()"
[disabled]="currentTenant?.name === name"
>
<span>{{ 'AbpTenantManagement::Save' | abpLocalization }}</span>
</abp-button>
</ng-template>
</abp-modal>
</ng-container>

@ -1,26 +1,26 @@
import { ABP, SetTenant, SessionState, GetAppConfiguration } from '@abp/ng.core';
import { ABP, GetAppConfiguration, SessionState, SetTenant } from '@abp/ng.core';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngxs/store';
import { throwError } from 'rxjs';
import { catchError, take, finalize, switchMap } from 'rxjs/operators';
import snq from 'snq';
import { AccountService } from '../../services/account.service';
import { Component } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { Observable } from 'rxjs';
import { finalize, take } from 'rxjs/operators';
import { Account } from '../../models/account';
import { AccountService } from '../../services/account.service';
@Component({
selector: 'abp-tenant-box',
templateUrl: './tenant-box.component.html',
})
export class TenantBoxComponent
implements OnInit, Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs {
tenant = {} as ABP.BasicItem;
implements Account.TenantBoxComponentInputs, Account.TenantBoxComponentOutputs {
@Select(SessionState.getTenant)
currentTenant$: Observable<ABP.BasicItem>;
tenantName: string;
name: string;
isModalVisible: boolean;
inProgress: boolean;
modalBusy: boolean;
constructor(
private store: Store,
@ -28,58 +28,41 @@ export class TenantBoxComponent
private accountService: AccountService,
) {}
ngOnInit() {
this.tenant = this.store.selectSnapshot(SessionState.getTenant) || ({} as ABP.BasicItem);
this.tenantName = this.tenant.name || '';
}
onSwitch() {
const tenant = this.store.selectSnapshot(SessionState.getTenant);
this.name = (tenant || ({} as ABP.BasicItem)).name;
this.isModalVisible = true;
}
save() {
if (this.tenant.name && !this.inProgress) {
this.inProgress = true;
this.accountService
.findTenant(this.tenant.name)
.pipe(
finalize(() => (this.inProgress = false)),
take(1),
catchError(err => {
this.toasterService.error(
snq(() => err.error.error_description, 'AbpUi::DefaultErrorMessage'),
'AbpUi::Error',
);
return throwError(err);
}),
switchMap(({ success, tenantId }) => {
if (success) {
this.tenant = {
id: tenantId,
name: this.tenant.name,
};
this.tenantName = this.tenant.name;
this.isModalVisible = false;
} else {
this.toasterService.error(
'AbpUiMultiTenancy::GivenTenantIsNotAvailable',
'AbpUi::Error',
{
messageLocalizationParams: [this.tenant.name],
},
);
this.tenant = {} as ABP.BasicItem;
this.tenantName = '';
}
this.store.dispatch(new SetTenant(success ? this.tenant : null));
return this.store.dispatch(new GetAppConfiguration());
}),
)
.subscribe();
} else {
this.store.dispatch([new SetTenant(null), new GetAppConfiguration()]);
this.tenantName = null;
if (!this.name) {
this.setTenant(null);
this.isModalVisible = false;
return;
}
this.modalBusy = true;
this.accountService
.findTenant(this.name)
.pipe(finalize(() => (this.modalBusy = false)))
.subscribe(({ success, tenantId: id, name }) => {
if (!success) {
this.showError();
return;
}
this.setTenant({ id, name });
this.isModalVisible = false;
});
}
private setTenant(tenant: ABP.BasicItem) {
return this.store.dispatch([new SetTenant(tenant), new GetAppConfiguration()]);
}
private showError() {
this.toasterService.error('AbpUiMultiTenancy::GivenTenantIsNotAvailable', 'AbpUi::Error', {
messageLocalizationParams: [this.name],
});
}
}

@ -1,4 +1,5 @@
export interface TenantIdResponse {
success: boolean;
tenantId: string;
name: string;
}

@ -28,11 +28,13 @@ import { LocalizationPipe, MockLocalizationPipe } from './pipes/localization.pip
import { SortPipe } from './pipes/sort.pipe';
import { ConfigPlugin, NGXS_CONFIG_PLUGIN_OPTIONS } from './plugins/config.plugin';
import { LocaleProvider } from './providers/locale.provider';
import { LocalizationService } from './services/localization.service';
import { ConfigState } from './states/config.state';
import { ProfileState } from './states/profile.state';
import { ReplaceableComponentsState } from './states/replaceable-components.state';
import { SessionState } from './states/session.state';
import { CORE_OPTIONS } from './tokens/options.token';
import { noop } from './utils/common-utils';
import './utils/date-extensions';
import { getInitialData, localeInitializer } from './utils/initial-utils';
@ -187,6 +189,12 @@ export class CoreModule {
deps: [Injector],
useFactory: localeInitializer,
},
{
provide: APP_INITIALIZER,
multi: true,
deps: [LocalizationService],
useFactory: noop,
},
...OAuthModule.forRoot().providers,
{ provide: OAuthStorage, useFactory: storageFactory },
],

@ -10,8 +10,10 @@ export namespace ApplicationConfiguration {
}
export interface Localization {
values: LocalizationValue;
currentCulture: CurrentCulture;
defaultResourceName: string;
languages: Language[];
values: LocalizationValue;
}
export interface LocalizationValue {
@ -25,6 +27,27 @@ export namespace ApplicationConfiguration {
flagIcon: string;
}
export interface CurrentCulture {
cultureName: string;
dateTimeFormat: DateTimeFormat;
displayName: string;
englishName: string;
isRightToLeft: boolean;
name: string;
nativeName: string;
threeLetterIsoLanguageName: string;
twoLetterIsoLanguageName: string;
}
export interface DateTimeFormat {
calendarAlgorithmType: string;
dateSeparator: string;
fullDateTimePattern: string;
longTimePattern: string;
shortDatePattern: string;
shortTimePattern: string;
}
export interface Auth {
policies: Policy;
grantedPolicies: Policy;

@ -83,6 +83,26 @@ const CONFIG_STATE_DATA = {
flagIcon: null,
},
],
currentCulture: {
displayName: 'English',
englishName: 'English',
threeLetterIsoLanguageName: 'eng',
twoLetterIsoLanguageName: 'en',
isRightToLeft: false,
cultureName: 'en',
name: 'en',
nativeName: 'English',
dateTimeFormat: {
calendarAlgorithmType: 'SolarCalendar',
dateTimeFormatLong: 'dddd, MMMM d, yyyy',
shortDatePattern: 'M/d/yyyy',
fullDateTimePattern: 'dddd, MMMM d, yyyy h:mm:ss tt',
dateSeparator: '/',
shortTimePattern: 'h:mm tt',
longTimePattern: 'h:mm:ss tt',
},
},
defaultResourceName: null,
},
auth: {
policies: {

@ -105,6 +105,26 @@ export const CONFIG_STATE_DATA = {
flagIcon: null,
},
],
currentCulture: {
displayName: 'English',
englishName: 'English',
threeLetterIsoLanguageName: 'eng',
twoLetterIsoLanguageName: 'en',
isRightToLeft: false,
cultureName: 'en',
name: 'en',
nativeName: 'English',
dateTimeFormat: {
calendarAlgorithmType: 'SolarCalendar',
dateTimeFormatLong: 'dddd, MMMM d, yyyy',
shortDatePattern: 'M/d/yyyy',
fullDateTimePattern: 'dddd, MMMM d, yyyy h:mm:ss tt',
dateSeparator: '/',
shortTimePattern: 'h:mm tt',
longTimePattern: 'h:mm:ss tt',
},
},
defaultResourceName: null,
},
auth: {
policies: {

@ -3,16 +3,8 @@
id="main-navbar"
style="min-height: 4rem;"
>
<div class="container ">
<a class="navbar-brand" routerLink="/">
<img
*ngIf="appInfo.logoUrl; else appName"
[src]="appInfo.logoUrl"
[alt]="appInfo.name"
width="100%"
height="auto"
/>
</a>
<div class="container">
<abp-logo *abpReplaceableTemplate="{ componentKey: logoComponentKey }"></abp-logo>
<button
class="navbar-toggler"
type="button"
@ -29,124 +21,17 @@
</div>
<ng-template #navigations>
<ul class="navbar-nav mx-auto">
<ng-container
*ngFor="let route of visibleRoutes$ | async; trackBy: trackByFn"
[ngTemplateOutlet]="route?.children?.length ? dropdownLink : defaultLink"
[ngTemplateOutletContext]="{ $implicit: route }"
>
</ng-container>
<ng-template #defaultLink let-route>
<li class="nav-item" *abpPermission="route.requiredPolicy">
<a class="nav-link" [routerLink]="[route.url]"
><i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i>
{{ route.name | abpLocalization }}</a
>
</li>
</ng-template>
<ng-template #dropdownLink let-route>
<li
#navbarRootDropdown
*abpPermission="route.requiredPolicy"
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
(click)="
navbarRootDropdown.expand
? (navbarRootDropdown.expand = false)
: (navbarRootDropdown.expand = true)
"
>
<a
class="nav-link dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
href="javascript:void(0)"
>
<i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i>
{{ route.name | abpLocalization }}
</a>
<div
#routeContainer
class="dropdown-menu border-0 shadow-sm"
(click)="$event.preventDefault(); $event.stopPropagation()"
[class.d-block]="smallScreen && navbarRootDropdown.expand"
>
<ng-template
#forTemplate
ngFor
[ngForOf]="route.children"
[ngForTrackBy]="trackByFn"
[ngForTemplate]="childWrapper"
></ng-template>
</div>
</li>
</ng-template>
<ng-template #childWrapper let-child>
<ng-template
[ngTemplateOutlet]="child?.children?.length ? dropdownChild : defaultChild"
[ngTemplateOutletContext]="{ $implicit: child }"
></ng-template>
</ng-template>
<ng-template #defaultChild let-child>
<div class="dropdown-submenu" *abpPermission="child.requiredPolicy">
<a class="dropdown-item" [routerLink]="[child.url]">
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}</a
>
</div>
</ng-template>
<ng-template #dropdownChild let-child>
<div
[abpVisibility]="childrenContainer"
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
[display]="isDropdownChildDynamic ? 'dynamic' : 'static'"
placement="right-top"
[autoClose]="true"
*abpPermission="child.requiredPolicy"
(openChange)="openChange($event, childrenContainer)"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="isDropdownChildDynamic"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<ng-template
ngFor
[ngForOf]="child.children"
[ngForTrackBy]="trackByFn"
[ngForTemplate]="childWrapper"
></ng-template>
</div>
</div>
</ng-template>
</ul>
<ul class="navbar-nav">
<ng-container
*ngFor="let element of rightPartElements; trackBy: trackElementByFn"
[ngTemplateOutlet]="element"
></ng-container>
</ul>
<abp-routes
*abpReplaceableTemplate="{ componentKey: routesComponentKey }"
class="mx-auto"
[smallScreen]="smallScreen"
[isDropdownChildDynamic]="isDropdownChildDynamic"
></abp-routes>
<abp-nav-items
*abpReplaceableTemplate="{ componentKey: navItemsComponentKey }"
[smallScreen]="smallScreen"
></abp-nav-items>
</ng-template>
</div>
</div>
@ -160,81 +45,3 @@
>
<router-outlet #outlet="outlet"></router-outlet>
</div>
<ng-template #appName>
{{ appInfo.name }}
</ng-template>
<ng-template #language>
<li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
<div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{{ defaultLanguage$ | async }}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && languageDropdown.isOpen()"
>
<a
*ngFor="let lang of dropdownLanguages$ | async"
href="javascript:void(0)"
class="dropdown-item"
(click)="onChangeLang(lang.cultureName)"
>{{ lang?.displayName }}</a
>
</div>
</div>
</li>
</ng-template>
<ng-template #currentUser>
<li class="nav-item">
<ng-template #loginBtn>
<a role="button" class="nav-link" routerLink="/account/login">{{
'AbpAccount::Login' | abpLocalization
}}</a>
</ng-template>
<div
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
ngbDropdown
class="dropdown"
#currentUserDropdown="ngbDropdown"
display="static"
>
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{{ (currentUser$ | async)?.userName }}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item" routerLink="/account/manage-profile"
><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
><i class="fa fa-power-off mr-1"></i>{{ 'AbpUi::Logout' | abpLocalization }}</a
>
</div>
</div>
</li>
</ng-template>

@ -1,34 +1,10 @@
import {
ABP,
ApplicationConfiguration,
AuthService,
Config,
ConfigState,
eLayoutType,
SessionState,
SetLanguage,
takeUntilDestroy,
} from '@abp/ng.core';
import { eLayoutType, takeUntilDestroy } from '@abp/ng.core';
import { collapseWithMargin, slideFromBottom } from '@abp/ng.theme.shared';
import {
AfterViewInit,
Component,
OnDestroy,
Renderer2,
TemplateRef,
TrackByFunction,
ViewChild,
} from '@angular/core';
import { Navigate, RouterState } from '@ngxs/router-plugin';
import { Select, Store } from '@ngxs/store';
import compare from 'just-compare';
import { fromEvent, Observable } from 'rxjs';
import { debounceTime, filter, map } from 'rxjs/operators';
import snq from 'snq';
import { AddNavigationElement } from '../../actions';
import { Layout } from '../../models/layout';
import { LayoutState } from '../../states';
import { eNavigationElementNames } from '../../enums/navigation-element-names';
import { AfterViewInit, Component, OnDestroy } from '@angular/core';
import { Store } from '@ngxs/store';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
import { eThemeBasicComponents } from '../../enums/components';
@Component({
selector: 'abp-layout-application',
@ -39,75 +15,19 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
// required for dynamic component
static type = eLayoutType.application;
@Select(ConfigState.getOne('routes'))
routes$: Observable<ABP.FullRoute[]>;
@Select(ConfigState.getOne('currentUser'))
currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
@Select(LayoutState.getNavigationElements)
navElements$: Observable<Layout.NavigationElement[]>;
@ViewChild('currentUser', { static: false, read: TemplateRef })
currentUserRef: TemplateRef<any>;
@ViewChild('language', { static: false, read: TemplateRef })
languageRef: TemplateRef<any>;
isDropdownChildDynamic: boolean;
isCollapsed = true;
smallScreen: boolean; // do not set true or false
get appInfo(): Config.Application {
return this.store.selectSnapshot(ConfigState.getApplicationInfo);
}
get visibleRoutes$(): Observable<ABP.FullRoute[]> {
return this.routes$.pipe(map(routes => getVisibleRoutes(routes)));
}
get defaultLanguage$(): Observable<string> {
return this.languages$.pipe(
map(
languages =>
snq(
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
),
'',
),
);
}
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
return this.languages$.pipe(
map(
languages =>
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
[],
),
);
}
get selectedLangCulture(): string {
return this.store.selectSnapshot(SessionState.getLanguage);
}
rightPartElements: TemplateRef<any>[] = [];
logoComponentKey = eThemeBasicComponents.Logo;
trackByFn: TrackByFunction<ABP.FullRoute> = (_, item) => item.name;
routesComponentKey = eThemeBasicComponents.Routes;
trackElementByFn: TrackByFunction<ABP.FullRoute> = (_, element) => element;
navItemsComponentKey = eThemeBasicComponents.NavItems;
constructor(
private store: Store,
private renderer: Renderer2,
private authService: AuthService,
) {}
constructor(private store: Store) {}
private checkWindowWidth() {
setTimeout(() => {
@ -128,29 +48,6 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
}
ngAfterViewInit() {
const navigations = this.store
.selectSnapshot(LayoutState.getNavigationElements)
.map(({ name }) => name);
if (navigations.indexOf(eNavigationElementNames.Language) < 0) {
this.store.dispatch(
new AddNavigationElement([
{ element: this.languageRef, order: 4, name: eNavigationElementNames.Language },
{ element: this.currentUserRef, order: 5, name: eNavigationElementNames.User },
]),
);
}
this.navElements$
.pipe(
map(elements => elements.map(({ element }) => element)),
filter(elements => !compare(elements, this.rightPartElements)),
takeUntilDestroy(this),
)
.subscribe(elements => {
setTimeout(() => (this.rightPartElements = elements), 0);
});
this.checkWindowWidth();
fromEvent(window, 'resize')
@ -161,41 +58,4 @@ export class ApplicationLayoutComponent implements AfterViewInit, OnDestroy {
}
ngOnDestroy() {}
onChangeLang(cultureName: string) {
this.store.dispatch(new SetLanguage(cultureName));
}
logout() {
this.authService.logout().subscribe(() => {
this.store.dispatch(
new Navigate(['/'], null, {
state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
}),
);
});
}
openChange(event: boolean, childrenContainer: HTMLDivElement) {
if (!event) {
Object.keys(childrenContainer.style)
.filter(key => Number.isInteger(+key))
.forEach(key => {
this.renderer.removeStyle(childrenContainer, childrenContainer.style[key]);
});
this.renderer.removeStyle(childrenContainer, 'left');
}
}
}
function getVisibleRoutes(routes: ABP.FullRoute[]) {
return routes.reduce((acc, val) => {
if (val.invisible) return acc;
if (val.children && val.children.length) {
val.children = getVisibleRoutes(val.children);
}
return [...acc, val];
}, []);
}

@ -1,4 +1,7 @@
export * from './account-layout/account-layout.component';
export * from './application-layout/application-layout.component';
export * from './empty-layout/empty-layout.component';
export * from './logo/logo.component';
export * from './nav-items/nav-items.component';
export * from './routes/routes.component';
export * from './validation-error/validation-error.component';

@ -0,0 +1,29 @@
import { Config, ConfigState } from '@abp/ng.core';
import { Component } from '@angular/core';
import { Store } from '@ngxs/store';
@Component({
selector: 'abp-logo',
template: `
<a class="navbar-brand" routerLink="/">
<img
*ngIf="appInfo.logoUrl; else appName"
[src]="appInfo.logoUrl"
[alt]="appInfo.name"
width="100%"
height="auto"
/>
</a>
<ng-template #appName>
{{ appInfo.name }}
</ng-template>
`,
})
export class LogoComponent {
get appInfo(): Config.Application {
return this.store.selectSnapshot(ConfigState.getApplicationInfo);
}
constructor(private store: Store) {}
}

@ -0,0 +1,81 @@
<ul class="navbar-nav">
<ng-container
*ngFor="let element of rightPartElements; trackBy: trackByFn"
[ngTemplateOutlet]="element"
[ngTemplateOutletContext]="{ smallScreen: smallScreen }"
></ng-container>
</ul>
<ng-template #language let-smallScreen="smallScreen">
<li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
<div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{{ defaultLanguage$ | async }}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && languageDropdown.isOpen()"
>
<a
*ngFor="let lang of dropdownLanguages$ | async"
href="javascript:void(0)"
class="dropdown-item"
(click)="onChangeLang(lang.cultureName)"
>{{ lang?.displayName }}</a
>
</div>
</div>
</li>
</ng-template>
<ng-template #currentUser let-smallScreen="smallScreen">
<li class="nav-item">
<ng-template #loginBtn>
<a role="button" class="nav-link" routerLink="/account/login">{{
'AbpAccount::Login' | abpLocalization
}}</a>
</ng-template>
<div
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
ngbDropdown
class="dropdown"
#currentUserDropdown="ngbDropdown"
display="static"
>
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{{ (currentUser$ | async)?.userName }}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item" routerLink="/account/manage-profile"
><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
><i class="fa fa-power-off mr-1"></i>{{ 'AbpUi::Logout' | abpLocalization }}</a
>
</div>
</div>
</li>
</ng-template>

@ -0,0 +1,125 @@
import {
Component,
AfterViewInit,
TrackByFunction,
TemplateRef,
ViewChild,
OnDestroy,
Input,
} from '@angular/core';
import {
ABP,
takeUntilDestroy,
SetLanguage,
AuthService,
ConfigState,
ApplicationConfiguration,
SessionState,
} from '@abp/ng.core';
import { LayoutState } from '../../states/layout.state';
import { Store, Select } from '@ngxs/store';
import { eNavigationElementNames } from '../../enums/navigation-element-names';
import { AddNavigationElement } from '../../actions/layout.actions';
import { map, filter } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Layout } from '../../models/layout';
import { Navigate, RouterState } from '@ngxs/router-plugin';
import snq from 'snq';
import compare from 'just-compare';
@Component({
selector: 'abp-nav-items',
templateUrl: 'nav-items.component.html',
})
export class NavItemsComponent implements AfterViewInit, OnDestroy {
@Select(LayoutState.getNavigationElements)
navElements$: Observable<Layout.NavigationElement[]>;
@Select(ConfigState.getOne('currentUser'))
currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
@ViewChild('currentUser', { static: false, read: TemplateRef })
currentUserRef: TemplateRef<any>;
@ViewChild('language', { static: false, read: TemplateRef })
languageRef: TemplateRef<any>;
@Input()
smallScreen: boolean;
rightPartElements: TemplateRef<any>[] = [];
trackByFn: TrackByFunction<ABP.FullRoute> = (_, element) => element;
get defaultLanguage$(): Observable<string> {
return this.languages$.pipe(
map(
languages =>
snq(
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
),
'',
),
);
}
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
return this.languages$.pipe(
map(
languages =>
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
[],
),
);
}
get selectedLangCulture(): string {
return this.store.selectSnapshot(SessionState.getLanguage);
}
constructor(private store: Store, private authService: AuthService) {}
ngAfterViewInit() {
const navigations = this.store
.selectSnapshot(LayoutState.getNavigationElements)
.map(({ name }) => name);
if (navigations.indexOf(eNavigationElementNames.Language) < 0) {
this.store.dispatch(
new AddNavigationElement([
{ element: this.languageRef, order: 4, name: eNavigationElementNames.Language },
{ element: this.currentUserRef, order: 5, name: eNavigationElementNames.User },
]),
);
}
this.navElements$
.pipe(
map(elements => elements.map(({ element }) => element)),
filter(elements => !compare(elements, this.rightPartElements)),
takeUntilDestroy(this),
)
.subscribe(elements => {
setTimeout(() => (this.rightPartElements = elements), 0);
});
}
ngOnDestroy() {}
onChangeLang(cultureName: string) {
this.store.dispatch(new SetLanguage(cultureName));
}
logout() {
this.authService.logout().subscribe(() => {
this.store.dispatch(
new Navigate(['/'], null, {
state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
}),
);
});
}
}

@ -0,0 +1,111 @@
<ul class="navbar-nav">
<ng-container
*ngFor="let route of visibleRoutes$ | async; trackBy: trackByFn"
[ngTemplateOutlet]="route?.children?.length ? dropdownLink : defaultLink"
[ngTemplateOutletContext]="{ $implicit: route }"
>
</ng-container>
<ng-template #defaultLink let-route>
<li class="nav-item" *abpPermission="route.requiredPolicy">
<a class="nav-link" [routerLink]="[route.url]"
><i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i>
{{ route.name | abpLocalization }}</a
>
</li>
</ng-template>
<ng-template #dropdownLink let-route>
<li
#navbarRootDropdown
*abpPermission="route.requiredPolicy"
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
(click)="
navbarRootDropdown.expand
? (navbarRootDropdown.expand = false)
: (navbarRootDropdown.expand = true)
"
>
<a
class="nav-link dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
href="javascript:void(0)"
>
<i *ngIf="route.iconClass" [ngClass]="route.iconClass"></i>
{{ route.name | abpLocalization }}
</a>
<div
#routeContainer
class="dropdown-menu border-0 shadow-sm"
(click)="$event.preventDefault(); $event.stopPropagation()"
[class.d-block]="smallScreen && navbarRootDropdown.expand"
>
<ng-template
#forTemplate
ngFor
[ngForOf]="route.children"
[ngForTrackBy]="trackByFn"
[ngForTemplate]="childWrapper"
></ng-template>
</div>
</li>
</ng-template>
<ng-template #childWrapper let-child>
<ng-template
[ngTemplateOutlet]="child?.children?.length ? dropdownChild : defaultChild"
[ngTemplateOutletContext]="{ $implicit: child }"
></ng-template>
</ng-template>
<ng-template #defaultChild let-child>
<div class="dropdown-submenu" *abpPermission="child.requiredPolicy">
<a class="dropdown-item" [routerLink]="[child.url]">
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}</a
>
</div>
</ng-template>
<ng-template #dropdownChild let-child>
<div
[abpVisibility]="childrenContainer"
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
[display]="isDropdownChildDynamic ? 'dynamic' : 'static'"
placement="right-top"
[autoClose]="true"
*abpPermission="child.requiredPolicy"
(openChange)="openChange($event, childrenContainer)"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="isDropdownChildDynamic"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i *ngIf="child.iconClass" [ngClass]="child.iconClass"></i>
{{ child.name | abpLocalization }}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<ng-template
ngFor
[ngForOf]="child.children"
[ngForTrackBy]="trackByFn"
[ngForTemplate]="childWrapper"
></ng-template>
</div>
</div>
</ng-template>
</ul>

@ -0,0 +1,51 @@
import { Component, OnInit, TrackByFunction, Input, Renderer2 } from '@angular/core';
import { Observable } from 'rxjs';
import { ABP, ConfigState } from '@abp/ng.core';
import { map } from 'rxjs/operators';
import { Select } from '@ngxs/store';
@Component({
selector: 'abp-routes',
templateUrl: 'routes.component.html',
})
export class RoutesComponent {
@Select(ConfigState.getOne('routes'))
routes$: Observable<ABP.FullRoute[]>;
@Input()
smallScreen: boolean;
@Input()
isDropdownChildDynamic: boolean;
get visibleRoutes$(): Observable<ABP.FullRoute[]> {
return this.routes$.pipe(map(routes => getVisibleRoutes(routes)));
}
trackByFn: TrackByFunction<ABP.FullRoute> = (_, item) => item.name;
constructor(private renderer: Renderer2) {}
openChange(event: boolean, childrenContainer: HTMLDivElement) {
if (!event) {
Object.keys(childrenContainer.style)
.filter(key => Number.isInteger(+key))
.forEach(key => {
this.renderer.removeStyle(childrenContainer, childrenContainer.style[key]);
});
this.renderer.removeStyle(childrenContainer, 'left');
}
}
}
function getVisibleRoutes(routes: ABP.FullRoute[]) {
return routes.reduce((acc, val) => {
if (val.invisible) return acc;
if (val.children && val.children.length) {
val.children = getVisibleRoutes(val.children);
}
return [...acc, val];
}, []);
}

@ -2,4 +2,7 @@ export const enum eThemeBasicComponents {
ApplicationLayout = 'Theme.ApplicationLayoutComponent',
AccountLayout = 'Theme.AccountLayoutComponent',
EmptyLayout = 'Theme.EmptyLayoutComponent',
Logo = 'Theme.LogoComponent',
Routes = 'Theme.RoutesComponent',
NavItems = 'Theme.NavItemsComponent',
}

@ -10,11 +10,20 @@ import { EmptyLayoutComponent } from './components/empty-layout/empty-layout.com
import { LayoutState } from './states/layout.state';
import { ValidationErrorComponent } from './components/validation-error/validation-error.component';
import { InitialService } from './services/initial.service';
import { LogoComponent } from './components/logo/logo.component';
import { RoutesComponent } from './components/routes/routes.component';
import { NavItemsComponent } from './components/nav-items/nav-items.component';
export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent];
@NgModule({
declarations: [...LAYOUTS, ValidationErrorComponent],
declarations: [
...LAYOUTS,
ValidationErrorComponent,
LogoComponent,
NavItemsComponent,
RoutesComponent,
],
imports: [
CoreModule,
ThemeSharedModule,
@ -28,7 +37,7 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt
email: 'AbpAccount::ThisFieldIsNotAValidEmailAddress.',
max: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
maxlength:
'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthoOf{0}[{{ requiredLength }}]',
'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMaximumLengthOf{0}[{{ requiredLength }}]',
min: 'AbpAccount::ThisFieldMustBeBetween{0}And{1}[{{ min }},{{ max }}]',
minlength:
'AbpAccount::ThisFieldMustBeAStringOrArrayTypeWithAMinimumLengthOf{0}[{{ requiredLength }}]',
@ -38,7 +47,7 @@ export const LAYOUTS = [ApplicationLayoutComponent, AccountLayoutComponent, Empt
errorTemplate: ValidationErrorComponent,
}),
],
exports: [...LAYOUTS],
exports: [...LAYOUTS, LogoComponent, ValidationErrorComponent],
entryComponents: [...LAYOUTS, ValidationErrorComponent],
})
export class ThemeBasicModule {

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, Input } from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { ConfigState, ABP } from '@abp/ng.core';
@ -8,18 +8,22 @@ import { ConfigState, ABP } from '@abp/ng.core';
templateUrl: './breadcrumb.component.html',
})
export class BreadcrumbComponent implements OnInit {
show: boolean;
@Input()
segments: string[] = [];
show: boolean;
constructor(private router: Router, private store: Store) {}
ngOnInit(): void {
this.show = !!this.store.selectSnapshot(state => state.LeptonLayoutState);
if (this.show) {
if (this.show && !this.segments.length) {
let splittedUrl = this.router.url.split('/').filter(chunk => chunk);
let currentUrl: ABP.FullRoute = this.store.selectSnapshot(ConfigState.getRoute(splittedUrl[0]));
let currentUrl: ABP.FullRoute = this.store.selectSnapshot(
ConfigState.getRoute(splittedUrl[0]),
);
if (!currentUrl) {
currentUrl = this.store.selectSnapshot(ConfigState.getRoute(null, null, this.router.url));
@ -38,6 +42,8 @@ export class BreadcrumbComponent implements OnInit {
let childRoute: ABP.FullRoute = currentUrl;
for (let i = 0; i < arr.length; i++) {
const element = arr[i];
if (!childRoute.children || !childRoute.children.length) return;
childRoute = childRoute.children.find(child => child.path === element);
this.segments.push(childRoute.name);

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

Loading…
Cancel
Save