Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(breaking): Rename CultureCode to LanguageCode #871

Merged
merged 2 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/schema/V1/schema.verified.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ type GuiAction {

type Localization {
value: String!
cultureCode: String!
languageCode: String!
}

type Queries @authorize(policy: "enduser") {
Expand Down Expand Up @@ -205,8 +205,8 @@ input SearchDialogInput {
dueBefore: DateTime
"Search string for free text search. Will attempt to fuzzily match in all free text fields in the aggregate"
search: String
"Limit free text search to texts with this culture code, e.g. \"nb-NO\". Default: search all culture codes"
searchCultureCode: String
"Limit free text search to texts with this language code, e.g. 'no', 'en'. Culture codes will be normalized to neutral language codes (ISO 639). Default: search all culture codes"
searchLanguageCode: String
}

enum ActivityType {
Expand Down
24 changes: 12 additions & 12 deletions docs/schema/V1/swagger.verified.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@
"Value": [
{
"Value": "Some Title",
"CultureCode": "en-us"
"LanguageCode": "en"
}
],
"MediaType": null
Expand All @@ -237,7 +237,7 @@
"Value": [
{
"Value": "Some Summary",
"CultureCode": "en-us"
"LanguageCode": "en"
}
],
"MediaType": null
Expand All @@ -257,7 +257,7 @@
"DisplayName": [
{
"Value": "Some display name",
"CultureCode": "en-us"
"LanguageCode": "en"
}
],
"Urls": [
Expand All @@ -282,11 +282,11 @@
"Title": [
{
"Value": "GUI action title",
"CultureCode": "en-us"
"LanguageCode": "en"
},
{
"Value": "GUI action-tittel",
"CultureCode": "nb-no"
"LanguageCode": "nb"
}
],
"Prompt": null
Expand Down Expand Up @@ -323,11 +323,11 @@
"Description": [
{
"Value": "Some description",
"CultureCode": "en-us"
"LanguageCode": "en"
},
{
"Value": "En beskrivelse",
"CultureCode": "nb-no"
"LanguageCode": "nb"
}
]
}
Expand Down Expand Up @@ -786,9 +786,9 @@
}
},
{
"name": "searchCultureCode",
"name": "searchLanguageCode",
"in": "query",
"description": "Limit free text search to texts with this culture code, e.g. \\\"nb-NO\\\". Default: search all culture codes",
"description": "Limit free text search to texts with this language code, e.g. 'no', 'en'. Culture codes will be normalized to neutral language codes (ISO 639). Default: search all culture codes",
"schema": {
"type": "string",
"nullable": true
Expand Down Expand Up @@ -1503,9 +1503,9 @@
}
},
{
"name": "searchCultureCode",
"name": "searchLanguageCode",
"in": "query",
"description": "Limit free text search to texts with this culture code, e.g. \\\"nb-NO\\\". Default: search all culture codes",
"description": "Limit free text search to texts with this language code, e.g. 'no', 'en'. Culture codes will be normalized to neutral language codes (ISO 639). Default: search all culture codes",
"schema": {
"type": "string",
"nullable": true
Expand Down Expand Up @@ -2024,7 +2024,7 @@
"value": {
"type": "string"
},
"cultureCode": {
"languageCode": {
"type": "string"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ internal static Expression<Func<T, bool>> Or<T>(Expression<Func<T, bool>> expr1,
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}

internal static Expression<Func<Localization, bool>> LocalizedSearchExpression(string? search, string? cultureCode)
internal static Expression<Func<Localization, bool>> LocalizedSearchExpression(string? search, string? languageCode)
{
return localization =>
(cultureCode == null || localization.CultureCode == cultureCode) &&
(languageCode == null || localization.LanguageCode == languageCode) &&
EF.Functions.ILike(localization.Value, $"%{search}%");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localization

public sealed class LocalizationDto
{
private readonly string _cultureCode = null!;
private readonly string _languageCode = null!;

public required string Value { get; init; }
public required string CultureCode
public required string LanguageCode
{
get => _cultureCode;
init => _cultureCode = Localization.NormalizeCultureCode(value)!;
get => _languageCode;
init => _languageCode = Localization.NormalizeCultureCode(value)!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,58 @@

namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations;

internal static class LocalizationValidatorContants
{
public const int MaximumLength = 255;

public const string NormalizationErrorMessage =
"Culture specific codes like 'en_GB' and 'en-US' are normalized to 'en' (ISO 639).";

public const string InvalidCultureCodeErrorMessageWithNorwegianHint =
InvalidCultureCodeErrorMessage + "Use 'nb' or 'nn' for Norwegian. ";

public const string InvalidCultureCodeErrorMessage =
"'{PropertyName}' '{PropertyValue}' is not a valid language code. ";
}

internal sealed class LocalizationDtosValidator : AbstractValidator<IEnumerable<LocalizationDto>>
{
public LocalizationDtosValidator(int maximumLength = 255)
public LocalizationDtosValidator(int maximumLength = LocalizationValidatorContants.MaximumLength)
{
RuleFor(x => x)
.UniqueBy(x => x.CultureCode)
.UniqueBy(x => x.LanguageCode)
.WithMessage(localizations =>
{
var duplicates = localizations
.GroupBy(y => y.LanguageCode)
.Where(g => g.Count() > 1)
.Select(g => g.Key);

return $"Can not contain duplicate items: [{string.Join(", ", duplicates)}]. " +
$"{LocalizationValidatorContants.NormalizationErrorMessage}";
})
.ForEach(x => x.SetValidator(new LocalizationDtoValidator(maximumLength)));
}
}

internal sealed class LocalizationDtoValidator : AbstractValidator<LocalizationDto>
{
public LocalizationDtoValidator(int maximumLength = 255)
public LocalizationDtoValidator(int maximumLength = LocalizationValidatorContants.MaximumLength)
{
RuleFor(x => x).NotNull();
RuleFor(x => x)
.NotNull();

RuleFor(x => x.Value)
.NotEmpty()
.NotNull()
.MaximumLength(maximumLength);

RuleFor(x => x.CultureCode)
RuleFor(x => x.LanguageCode)
.NotEmpty()
.Must(x => x is null || Localization.IsValidCultureCode(x))
.WithMessage("'{PropertyName}' must be a valid culture code.");
.Must(Localization.IsValidCultureCode)
.WithMessage(localization =>
(localization.LanguageCode == "no"
? LocalizationValidatorContants.InvalidCultureCodeErrorMessageWithNorwegianHint
: LocalizationValidatorContants.InvalidCultureCodeErrorMessage) +
LocalizationValidatorContants.NormalizationErrorMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ internal sealed class LocalizationSetConverter<TLocalizationSet> : ITypeConverte
set ??= new TLocalizationSet();
set.Localizations.Merge(
sources: concreteDtos,
destinationKeySelector: x => x.CultureCode,
sourceKeySelector: x => x.CultureCode,
destinationKeySelector: x => x.LanguageCode,
sourceKeySelector: x => x.LanguageCode,
create: context.Mapper.Map<List<Localization>>,
update: context.Mapper.Update,
delete: DeleteDelegate.NoOp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public MappingProfile()
CreateMap<LocalizationSet?, List<LocalizationDto>?>()
.ConvertUsing(src => src == null ? null :
src.Localizations
.Select(x => new LocalizationDto { CultureCode = x.CultureCode, Value = x.Value })
.Select(x => new LocalizationDto { LanguageCode = x.LanguageCode, Value = x.Value })
.ToList());

// In
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,15 @@ public async Task<GetDialogResult> Handle(GetDialogQuery request, CancellationTo
// layer behaviours in an expected manner. Therefore, we need to be a bit more verbose about it.
var dialog = await _db.Dialogs
.Include(x => x.Content.OrderBy(x => x.Id).ThenBy(x => x.CreatedAt))
.ThenInclude(x => x.Value.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x.Value.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.Attachments.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.DisplayName!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x.DisplayName!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.Attachments.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.Urls.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.Include(x => x.GuiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.Title!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x.Title!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.GuiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x!.Prompt!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x!.Prompt!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.ApiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.Endpoints.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.Include(x => x.Activities).ThenInclude(x => x.Description!.Localizations)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Que

public sealed class SearchDialogQuery : SortablePaginationParameter<SearchDialogQueryOrderDefinition, SearchDialogDto>, IRequest<SearchDialogResult>
{
private readonly string? _searchCultureCode;
private readonly string? _searchLanguageCode;

/// <summary>
/// Filter by one or more service owner codes
Expand Down Expand Up @@ -87,12 +87,12 @@ public sealed class SearchDialogQuery : SortablePaginationParameter<SearchDialog
public string? Search { get; init; }

/// <summary>
/// Limit free text search to texts with this culture code, e.g. \"nb-NO\". Default: search all culture codes
/// Limit free text search to texts with this language code, e.g. 'no', 'en'. Culture codes will be normalized to neutral language codes (ISO 639). Default: search all culture codes
/// </summary>
public string? SearchCultureCode
public string? SearchLanguageCode
{
get => _searchCultureCode;
init => _searchCultureCode = Localization.NormalizeCultureCode(value);
get => _searchLanguageCode;
init => _searchLanguageCode = Localization.NormalizeCultureCode(value);
}
}

Expand Down Expand Up @@ -138,7 +138,7 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
{
var currentUserInfo = await _userRegistry.GetCurrentUserInformation(cancellationToken);

var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchCultureCode);
var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchLanguageCode);
var authorizedResources = await _altinnAuthorization.GetAuthorizedResourcesForSearch(
request.Party ?? [],
request.ServiceResource ?? [],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Digdir.Domain.Dialogporten.Application.Common.Extensions.Enumerables;
using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation;
using Digdir.Domain.Dialogporten.Application.Common.Pagination;
using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations;
using Digdir.Domain.Dialogporten.Domain.Localizations;
using FluentValidation;

Expand All @@ -15,9 +16,13 @@ public SearchDialogQueryValidator()
.MinimumLength(3)
.When(x => x.Search is not null);

RuleFor(x => x.SearchCultureCode)
RuleFor(x => x.SearchLanguageCode)
.Must(x => x is null || Localization.IsValidCultureCode(x))
.WithMessage("'{PropertyName}' must be a valid culture code.");
.WithMessage(searchQuery =>
(searchQuery.SearchLanguageCode == "no"
? LocalizationValidatorContants.InvalidCultureCodeErrorMessageWithNorwegianHint
: LocalizationValidatorContants.InvalidCultureCodeErrorMessage) +
LocalizationValidatorContants.NormalizationErrorMessage);

RuleFor(x => x)
.Must(x => !x.ServiceResource.IsNullOrEmpty() || !x.Party.IsNullOrEmpty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,16 @@ public async Task<GetDialogResult> Handle(GetDialogQuery request, CancellationTo
// layer behaviours in an expected manner. Therefore we need to be a bit more verbose about it.
var dialog = await _db.Dialogs
.Include(x => x.Content.OrderBy(x => x.Id).ThenBy(x => x.CreatedAt))
.ThenInclude(x => x.Value.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x.Value.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.SearchTags.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.Include(x => x.Attachments.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.DisplayName!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x.DisplayName!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.Attachments.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.Urls.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.Include(x => x.GuiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.Title!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x.Title!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.GuiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x!.Prompt!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.CultureCode))
.ThenInclude(x => x!.Prompt!.Localizations.OrderBy(x => x.CreatedAt).ThenBy(x => x.LanguageCode))
.Include(x => x.ApiActions.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.ThenInclude(x => x.Endpoints.OrderBy(x => x.CreatedAt).ThenBy(x => x.Id))
.Include(x => x.Activities).ThenInclude(x => x.Description!.Localizations)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace Digdir.Domain.Dialogporten.Application.Features.V1.ServiceOwner.Dialog

public sealed class SearchDialogQuery : SortablePaginationParameter<SearchDialogQueryOrderDefinition, SearchDialogDto>, IRequest<SearchDialogResult>
{
private string? _searchCultureCode;
private string? _searchLanguageCode;

/// <summary>
/// Filter by one or more service resources
Expand Down Expand Up @@ -95,12 +95,12 @@ public sealed class SearchDialogQuery : SortablePaginationParameter<SearchDialog
public string? Search { get; init; }

/// <summary>
/// Limit free text search to texts with this culture code, e.g. \"nb-NO\". Default: search all culture codes
/// Limit free text search to texts with this language code, e.g. 'no', 'en'. Culture codes will be normalized to neutral language codes (ISO 639). Default: search all culture codes
/// </summary>
public string? SearchCultureCode
public string? SearchLanguageCode
{
get => _searchCultureCode;
init => _searchCultureCode = Localization.NormalizeCultureCode(value);
get => _searchLanguageCode;
init => _searchLanguageCode = Localization.NormalizeCultureCode(value);
}
}
public sealed class SearchDialogQueryOrderDefinition : IOrderDefinition<SearchDialogDto>
Expand Down Expand Up @@ -141,7 +141,7 @@ public SearchDialogQueryHandler(
public async Task<SearchDialogResult> Handle(SearchDialogQuery request, CancellationToken cancellationToken)
{
var resourceIds = await _userResourceRegistry.GetCurrentUserResourceIds(cancellationToken);
var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchCultureCode);
var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchLanguageCode);

var query = _db.Dialogs
.WhereIf(!request.ServiceResource.IsNullOrEmpty(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Digdir.Domain.Dialogporten.Application.Common.Extensions.Enumerables;
using Digdir.Domain.Dialogporten.Application.Common.Extensions.FluentValidation;
using Digdir.Domain.Dialogporten.Application.Common.Pagination;
using Digdir.Domain.Dialogporten.Application.Features.V1.Common.Localizations;
using Digdir.Domain.Dialogporten.Domain.Localizations;
using Digdir.Domain.Dialogporten.Domain.Parties;
using Digdir.Domain.Dialogporten.Domain.Parties.Abstractions;
Expand All @@ -17,9 +18,13 @@ public SearchDialogQueryValidator()
.MinimumLength(3)
.When(x => x.Search is not null);

RuleFor(x => x.SearchCultureCode)
RuleFor(x => x.SearchLanguageCode)
.Must(x => x is null || Localization.IsValidCultureCode(x))
.WithMessage("'{PropertyName}' must be a valid culture code.");
.WithMessage(searchQuery =>
(searchQuery.SearchLanguageCode == "no"
? LocalizationValidatorContants.InvalidCultureCodeErrorMessageWithNorwegianHint
: LocalizationValidatorContants.InvalidCultureCodeErrorMessage) +
LocalizationValidatorContants.NormalizationErrorMessage);

RuleFor(x => x)
.Must(x => PartyIdentifier.TryParse(x.EndUserId, out var id) && id is NorwegianPersonIdentifier or SystemUserIdentifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ private static ReadOnlyCollection<string> ToPaths(this IEnumerable<AggregateNode
}

private static IEnumerable<string> ToLocalizationPathStrings(this LocalizationSet localizationSet, string parentPath) =>
localizationSet.Localizations.Select(x => $"{parentPath}/{x.CultureCode}");
localizationSet.Localizations.Select(x => $"{parentPath}/{x.LanguageCode}");

private static string ToName(this object obj) => obj switch
{
Expand Down
Loading