mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
48 changed files with 769 additions and 176 deletions
@ -0,0 +1,68 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.Json.Objects; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Contents |
||||
|
{ |
||||
|
public class TranslationStatus : Dictionary<string, int> |
||||
|
{ |
||||
|
public TranslationStatus() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public TranslationStatus(int capacity) |
||||
|
: base(capacity) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public static TranslationStatus Create(ContentData data, Schema schema, LanguagesConfig languages) |
||||
|
{ |
||||
|
Guard.NotNull(data); |
||||
|
Guard.NotNull(schema); |
||||
|
Guard.NotNull(languages); |
||||
|
|
||||
|
var result = new TranslationStatus(languages.Languages.Count); |
||||
|
|
||||
|
var localizedFields = schema.Fields.Where(x => x.Partitioning == Partitioning.Language).ToList(); |
||||
|
|
||||
|
foreach (var language in languages.AllKeys) |
||||
|
{ |
||||
|
var percent = 0; |
||||
|
|
||||
|
foreach (var field in localizedFields) |
||||
|
{ |
||||
|
if (IsValidValue(data.GetValueOrDefault(field.Name)?.GetValueOrDefault(language))) |
||||
|
{ |
||||
|
percent++; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (localizedFields.Count > 0) |
||||
|
{ |
||||
|
percent = (int)Math.Round(100 * (double)percent / localizedFields.Count); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
percent = 100; |
||||
|
} |
||||
|
|
||||
|
result[language] = percent; |
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
private static bool IsValidValue(JsonValue? value) |
||||
|
{ |
||||
|
return value != null && value.Value.Type != JsonValueType.Null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
||||
|
// All rights reserved. Licensed under the MIT license.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Squidex.Domain.Apps.Core.Apps; |
||||
|
using Squidex.Domain.Apps.Core.Contents; |
||||
|
using Squidex.Domain.Apps.Core.Schemas; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Squidex.Domain.Apps.Core.Model.Contents |
||||
|
{ |
||||
|
public class TranslationStatusTests |
||||
|
{ |
||||
|
private readonly LanguagesConfig languages = LanguagesConfig.English.Set(Language.DE).Set(Language.IT); |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_create_info_for_empty_schema() |
||||
|
{ |
||||
|
var schema = new Schema("my-schema"); |
||||
|
|
||||
|
var result = TranslationStatus.Create(new ContentData(), schema, languages); |
||||
|
|
||||
|
Assert.Equal(new TranslationStatus |
||||
|
{ |
||||
|
[Language.EN] = 100, |
||||
|
[Language.DE] = 100, |
||||
|
[Language.IT] = 100 |
||||
|
}, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_create_info_for_schema_without_localized_field() |
||||
|
{ |
||||
|
var schema = |
||||
|
new Schema("my-schema") |
||||
|
.AddString(1, "field1", Partitioning.Invariant); |
||||
|
|
||||
|
var result = TranslationStatus.Create(new ContentData(), schema, languages); |
||||
|
|
||||
|
Assert.Equal(new TranslationStatus |
||||
|
{ |
||||
|
[Language.EN] = 100, |
||||
|
[Language.DE] = 100, |
||||
|
[Language.IT] = 100 |
||||
|
}, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_create_info_for_schema_with_localized_field() |
||||
|
{ |
||||
|
var schema = |
||||
|
new Schema("my-schema") |
||||
|
.AddString(1, "field1", Partitioning.Language); |
||||
|
|
||||
|
var result = TranslationStatus.Create(new ContentData(), schema, languages); |
||||
|
|
||||
|
Assert.Equal(new TranslationStatus |
||||
|
{ |
||||
|
[Language.EN] = 0, |
||||
|
[Language.DE] = 0, |
||||
|
[Language.IT] = 0 |
||||
|
}, result); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_create_translation_info() |
||||
|
{ |
||||
|
var schema = |
||||
|
new Schema("my-schema") |
||||
|
.AddString(1, "field1", Partitioning.Language) |
||||
|
.AddString(2, "field2", Partitioning.Language) |
||||
|
.AddString(3, "field3", Partitioning.Language) |
||||
|
.AddString(4, "field4", Partitioning.Invariant); |
||||
|
|
||||
|
var data = |
||||
|
new ContentData() |
||||
|
.AddField("field1", |
||||
|
new ContentFieldData() |
||||
|
.AddLocalized(Language.EN, "en") |
||||
|
.AddLocalized(Language.DE, "de")) |
||||
|
.AddField("field2", |
||||
|
new ContentFieldData() |
||||
|
.AddLocalized(Language.EN, "en") |
||||
|
.AddLocalized(Language.DE, "de")) |
||||
|
.AddField("field3", |
||||
|
new ContentFieldData() |
||||
|
.AddLocalized(Language.EN, "en")); |
||||
|
|
||||
|
var result = TranslationStatus.Create(data, schema, languages); |
||||
|
|
||||
|
Assert.Equal(new TranslationStatus |
||||
|
{ |
||||
|
[Language.EN] = 100, |
||||
|
[Language.DE] = 67, |
||||
|
[Language.IT] = 0 |
||||
|
}, result); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,24 +1,38 @@ |
|||||
<div class="btn-group btn-group-{{size}}" *ngIf="isSmallMode"> |
<ng-container *ngIf="languages.length > 1"> |
||||
<button type="button" class="btn btn-outline-secondary" *ngFor="let supported of languages; trackBy: trackByLanguage" title="{{supported.englishName}}" [class.active]="supported === language" (click)="selectLanguage(supported)" tabindex="-1"> |
<ng-container *ngIf="languages.length > 3 || percents; else smallMode"> |
||||
{{supported.iso2Code}} |
<button type="button" class="btn btn-outline-secondary btn-{{size}} dropdown-toggle" title="{{language.englishName}}" (click)="dropdown.toggle()" #button tabindex="-1"> |
||||
</button> |
{{language.iso2Code}} |
||||
</div> |
</button> |
||||
|
|
||||
<ng-container *ngIf="isLargeMode"> |
<ng-container *sqxModal="dropdown;closeAlways:true"> |
||||
<button type="button" class="btn btn-outline-secondary btn-{{size}} dropdown-toggle" title="{{language.englishName}}" (click)="dropdown.toggle()" #button tabindex="-1"> |
<sqx-dropdown-menu [sqxAnchoredTo]="button" [scrollY]="true" [position]="dropdownPosition"> |
||||
{{language.iso2Code}} |
<table> |
||||
</button> |
<tbody> |
||||
|
<tr class="dropdown-item" *ngFor="let supported of languages; trackBy: trackByLanguage" |
||||
<ng-container *sqxModal="dropdown;closeAlways:true"> |
[class.active]="supported === language" |
||||
<sqx-dropdown-menu [sqxAnchoredTo]="button" [scrollY]="true"> |
[class.missing]="exists && !exists[supported.iso2Code]" |
||||
<table> |
(click)="selectLanguage(supported)"> |
||||
<tbody> |
<td class="text-language">{{supported.iso2Code}}</td> |
||||
<tr class="dropdown-item" *ngFor="let supported of languages; trackBy: trackByLanguage" [class.active]="supported === language" (click)="selectLanguage(supported)"> |
<td>({{supported.englishName}})</td> |
||||
<td><strong class="iso-code">{{supported.iso2Code}}</strong></td> |
|
||||
<td>({{supported.englishName}})</td> |
<td *ngIf="percents" class="text-right"> |
||||
</tr> |
{{percents[supported.iso2Code] || 0}} % |
||||
</tbody> |
</td> |
||||
</table> |
</tr> |
||||
</sqx-dropdown-menu> |
</tbody> |
||||
|
</table> |
||||
|
</sqx-dropdown-menu> |
||||
|
</ng-container> |
||||
</ng-container> |
</ng-container> |
||||
|
|
||||
|
<ng-template #smallMode> |
||||
|
<div class="btn-group btn-group-{{size}}"> |
||||
|
<button type="button" class="btn btn-outline-secondary" *ngFor="let supported of languages; trackBy: trackByLanguage" title="{{supported.englishName}}" |
||||
|
[class.active]="supported === language" |
||||
|
[class.missing]="exists && !exists[supported.iso2Code]" |
||||
|
(click)="selectLanguage(supported)" tabindex="-1"> |
||||
|
<span>{{supported.iso2Code}}</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</ng-template> |
||||
</ng-container> |
</ng-container> |
||||
@ -0,0 +1,135 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; |
||||
|
import { moduleMetadata } from '@storybook/angular'; |
||||
|
import { Meta, Story } from '@storybook/angular/types-6-0'; |
||||
|
import { LanguageSelectorComponent, SqxFrameworkModule } from '@app/framework'; |
||||
|
|
||||
|
export default { |
||||
|
title: 'Framework/Language-Selector', |
||||
|
component: LanguageSelectorComponent, |
||||
|
argTypes: { |
||||
|
size: { |
||||
|
control: 'enum', |
||||
|
options: [ |
||||
|
'sm', |
||||
|
'md', |
||||
|
'lg', |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
decorators: [ |
||||
|
moduleMetadata({ |
||||
|
imports: [ |
||||
|
BrowserAnimationsModule, |
||||
|
SqxFrameworkModule, |
||||
|
SqxFrameworkModule.forRoot(), |
||||
|
], |
||||
|
}), |
||||
|
], |
||||
|
} as Meta; |
||||
|
|
||||
|
const Template: Story<LanguageSelectorComponent> = (args: LanguageSelectorComponent) => ({ |
||||
|
props: args, |
||||
|
template: ` |
||||
|
<sqx-root-view> |
||||
|
<div class="text-center"> |
||||
|
<sqx-language-selector |
||||
|
[exists]="exists" |
||||
|
[language]="language" |
||||
|
[languages]="languages" |
||||
|
[percents]="percents"> |
||||
|
</sqx-language-selector> |
||||
|
</div> |
||||
|
</sqx-root-view> |
||||
|
`,
|
||||
|
}); |
||||
|
|
||||
|
export const Empty = Template.bind({}); |
||||
|
|
||||
|
Empty.args = { |
||||
|
languages: [], |
||||
|
}; |
||||
|
|
||||
|
export const OneLanguage = Template.bind({}); |
||||
|
|
||||
|
OneLanguage.args = { |
||||
|
languages: [ |
||||
|
{ iso2Code: 'en', englishName: 'English' }, |
||||
|
], |
||||
|
}; |
||||
|
|
||||
|
export const FewLanguages = Template.bind({}); |
||||
|
|
||||
|
FewLanguages.args = { |
||||
|
languages: [ |
||||
|
{ iso2Code: 'en', englishName: 'English' }, |
||||
|
{ iso2Code: 'it', englishName: 'Italian' }, |
||||
|
{ iso2Code: 'es', englishName: 'Spanish' }, |
||||
|
], |
||||
|
}; |
||||
|
|
||||
|
export const FewLanguagesWithExists = Template.bind({}); |
||||
|
|
||||
|
FewLanguagesWithExists.args = { |
||||
|
languages: [ |
||||
|
{ iso2Code: 'en', englishName: 'English' }, |
||||
|
{ iso2Code: 'it', englishName: 'Italian' }, |
||||
|
{ iso2Code: 'es', englishName: 'Spanish' }, |
||||
|
], |
||||
|
exists: { |
||||
|
en: true, |
||||
|
it: false, |
||||
|
es: true, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
export const ManyLanguages = Template.bind({}); |
||||
|
|
||||
|
ManyLanguages.args = { |
||||
|
languages: [ |
||||
|
{ iso2Code: 'en', englishName: 'English' }, |
||||
|
{ iso2Code: 'it', englishName: 'Italian' }, |
||||
|
{ iso2Code: 'es', englishName: 'Spanish' }, |
||||
|
{ iso2Code: 'de', englishName: 'German' }, |
||||
|
{ iso2Code: 'ru', englishName: 'Russian' }, |
||||
|
], |
||||
|
}; |
||||
|
|
||||
|
export const ManyLanguagesWithExists = Template.bind({}); |
||||
|
|
||||
|
ManyLanguagesWithExists.args = { |
||||
|
languages: [ |
||||
|
{ iso2Code: 'en', englishName: 'English' }, |
||||
|
{ iso2Code: 'it', englishName: 'Italian' }, |
||||
|
{ iso2Code: 'es', englishName: 'Spanish' }, |
||||
|
{ iso2Code: 'de', englishName: 'German' }, |
||||
|
{ iso2Code: 'ru', englishName: 'Russian' }, |
||||
|
], |
||||
|
exists: { |
||||
|
en: true, |
||||
|
it: false, |
||||
|
es: true, |
||||
|
de: false, |
||||
|
ru: true, |
||||
|
}, |
||||
|
}; |
||||
|
|
||||
|
export const WithPercents = Template.bind({}); |
||||
|
|
||||
|
WithPercents.args = { |
||||
|
languages: [ |
||||
|
{ iso2Code: 'en', englishName: 'English' }, |
||||
|
{ iso2Code: 'it', englishName: 'Italian' }, |
||||
|
{ iso2Code: 'es', englishName: 'Spanish' }, |
||||
|
], |
||||
|
percents: { |
||||
|
'en': 100, |
||||
|
'it': 67, |
||||
|
}, |
||||
|
}; |
||||
Loading…
Reference in new issue