Skip to content

Commit

Permalink
Merge pull request #340 from LiamMorrow/i18n
Browse files Browse the repository at this point in the history
Use localised strings on all user facing text
  • Loading branch information
LiamMorrow authored Dec 8, 2024
2 parents 219f716 + 112e0f7 commit 9b52f6e
Show file tree
Hide file tree
Showing 74 changed files with 1,726 additions and 434 deletions.
4 changes: 2 additions & 2 deletions LiftLog.Cypress/cypress/e2e/completing-a-session.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('Completing a session', () => {
cy.containsA('Add Exercise', { includeShadowDom: true }).click()
cy.dialog().find('md-filled-text-field').find('input', { includeShadowDom: true }).first().click().type('Squat')

cy.dialog().contains("Update").click()
cy.dialog().find("[data-cy=dialog-action]").click()


cy.getA('.repcount').first().click()
Expand All @@ -41,7 +41,7 @@ describe('Completing a session', () => {
beforeEach(() => {
cy.navigate('Settings')
// Disable tips
cy.containsA('App Configuration').click()
cy.containsA('App configuration').click()
cy.containsA('Show tips').click()
cy.navigate('Settings')
cy.containsA('Manage plans').click()
Expand Down
8 changes: 4 additions & 4 deletions LiftLog.Cypress/cypress/e2e/creating-a-plan.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('Creating a plan', () => {
beforeEach(() => {
cy.navigate('Settings')
// Disable tips
cy.containsA('App Configuration').click()
cy.containsA('App configuration').click()
cy.containsA('Show tips').click()
cy.navigate('Settings')
cy.containsA('Manage plans').click()
Expand All @@ -24,7 +24,7 @@ describe('Creating a plan', () => {
beforeEach(() => {
cy.navigate('Settings')
// Disable tips
cy.containsA('App Configuration').click()
cy.containsA('App configuration').click()
cy.containsA('Show tips').click()
cy.navigate('Settings')
cy.containsA('Manage plans').click()
Expand All @@ -50,7 +50,7 @@ describe('Creating a plan', () => {

function populatePlanFromEditPage(exerciseName) {

cy.containsA('Add Session', { includeShadowDom: true }).click()
cy.containsA('Add workout', { includeShadowDom: true }).click()
cy.containsA('Add Exercise', { includeShadowDom: true }).click()
cy.dialog().find('[data-cy=exercise-name]').find('input', { includeShadowDom: true }).clear().type(exerciseName)
cy.dialog().find('[data-cy=exercise-sets]').should('contain.text', '3').find('[data-cy=fixed-increment]').click()
Expand All @@ -66,7 +66,7 @@ function populatePlanFromEditPage(exerciseName) {

function assertPlanFromEditPage(exerciseName) {

cy.containsA('Session 1').click()
cy.containsA('Workout 1').click()
cy.getA('.itemlist').children().first().click()
cy.dialog().find('[data-cy=exercise-name]').find('input', { includeShadowDom: true }).should('have.value', exerciseName)
cy.dialog().find('[data-cy=exercise-sets]').should('contain.text', '4')
Expand Down
6 changes: 3 additions & 3 deletions LiftLog.Cypress/cypress/e2e/settings.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ describe('Settings', () => {
beforeEach(() => {
cy.navigate('Settings')
// Disable tips
cy.containsA('App Configuration').click()
cy.containsA('App configuration').click()
cy.containsA('Show tips').click()

cy.navigate('Settings')
Expand All @@ -25,7 +25,7 @@ describe('Settings', () => {
cy.navigate('Settings')
// Navigate twice, because settings remembers its last page when you click it
cy.navigate('Settings')
cy.containsA('App Configuration').click()
cy.containsA('App configuration').click()
cy.containsA('Use imperial units').click()
assertCorrectWeightUnitsOnAllPages('lbs')
})
Expand All @@ -37,7 +37,7 @@ describe('Settings', () => {
cy.navigate('Settings')
// Navigate twice, because settings remembers its last page when you click it
cy.navigate('Settings')
cy.containsA('App Configuration').click()
cy.containsA('App configuration').click()
cy.containsA('Show bodyweight').click()
assertShowsBodyweightOnAllPages(false)
})
Expand Down
2 changes: 1 addition & 1 deletion LiftLog.Lib/Models/SessionModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public RecordedExercise? NextExercise
get
{
var latestExerciseIndex = RecordedExercises
.IndexedTuples()
.Index()
.Where(x => x.Item.LastRecordedSet is not null)
.OrderByDescending(x => x.Item.LastRecordedSet?.Set?.CompletionTime)
.Select(x => x.Index)
Expand Down
7 changes: 0 additions & 7 deletions LiftLog.Lib/Util.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,4 @@ public static ImmutableListValue<T> ListOf<T>(IEnumerable<T> items)

return new ImmutableListValue<T>(ImmutableList.CreateRange(items));
}

public static IEnumerable<(TSource Item, int Index)> IndexedTuples<TSource>(
this IEnumerable<TSource> source
)
{
return source.Select((item, index) => (item, index));
}
}
15 changes: 4 additions & 11 deletions LiftLog.Lib/Util/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ namespace System.Linq;

public static class LinqExtensions
{
public static IEnumerable<(TSource Item, int Index)> IndexedTuples<TSource>(
this IEnumerable<TSource> source
)
{
return source.Select((item, index) => (item, index));
}

public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
where T : notnull
{
Expand Down Expand Up @@ -50,12 +43,12 @@ public static int IndexOf<TSource>(
Func<TSource, bool> predicate
)
{
var tuples = source.IndexedTuples();
var tuples = source.Index();
var matchingTuple = tuples
.Where(x => predicate(x.Item))
.Cast<(TSource?, int)>()
.DefaultIfEmpty((default, -1));
return matchingTuple.First().Item2;
.Cast<(int, TSource?)>()
.DefaultIfEmpty((-1, default));
return matchingTuple.First().Item1;
}

public static async Task<ImmutableListValue<T>> ToImmutableListValueAsync<T>(
Expand Down
37 changes: 33 additions & 4 deletions LiftLog.Ui/LiftLog.Ui.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,36 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<!-- Enums not handled exhaustively with numbers; No await in async -->
<NoWarn>CS8524;CS1998</NoWarn>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<!-- Enums not handled exhaustively with numbers; No await in async -->
<NoWarn>CS8524;CS1998</NoWarn>
<!-- Required for intellisense -->
<AdditionalFileItemNames>$(AdditionalFileItemNames);EmbeddedResource</AdditionalFileItemNames>
<CoreCompileDependsOn>PrepareResources;$(CompileDependsOn)</CoreCompileDependsOn>

<TypealizR_UseParamNamesInMethodNames>false</TypealizR_UseParamNamesInMethodNames>

</PropertyGroup>

<ItemGroup>
<CompilerVisibleProperty Include="TypealizR_UseParamNamesInMethodNames" />
</ItemGroup>


<ItemGroup>
<!-- https://www.paraesthesia.com/archive/2022/09/30/strongly-typed-resources-with-net-core/ -->
<EmbeddedResource Update="i18n\UiStrings.resx">
<Generator>MSBuild:Compile</Generator>
<LastGenOutput>UiStrings.Designer.cs</LastGenOutput>
<!-- Make sure this won't clash with other generated files! -->
<StronglyTypedFileName>$(IntermediateOutputPath)\UiStrings.Designer.cs</StronglyTypedFileName>
<StronglyTypedLanguage>CSharp</StronglyTypedLanguage>
<StronglyTypedNamespace>LiftLog.Ui.i18n</StronglyTypedNamespace>
<StronglyTypedClassName>UiStrings</StronglyTypedClassName>
</EmbeddedResource>

</ItemGroup>

<PropertyGroup>
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
</PropertyGroup>
Expand All @@ -32,6 +57,7 @@
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="9.0.0" />
<PackageReference Include="Fluxor" Version="6.1.0" />
<PackageReference Include="Fluxor.Blazor.Web" Version="6.1.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />

<PackageReference Include="Google.Protobuf" Version="3.29.0" />
Expand All @@ -44,9 +70,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="TypealizR" Version="0.9.9">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>


<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Fluxor.Blazor.Web.ReduxDevTools" Version="6.1.0" />
</ItemGroup>
Expand Down
38 changes: 21 additions & 17 deletions LiftLog.Ui/Pages/Feed/FeedPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
@inject IJSRuntime JSRuntime
@inject IState<FeedState> FeedState
@inject NavigationManager NavigationManager

@inject IDispatcher Dispatcher

@inherits Fluxor.Blazor.Web.Components.FluxorComponent

<div class="sticky top-0 z-10 @TopNavColorClass transition-colors">
<md-tabs @ref="tabs" @onchange="HandleTabChange">
<md-primary-tab id="mainfeed-tab" active=@(GetTabActiveClass("mainfeed-panel")) aria-controls="mainfeed-panel">Feed</md-primary-tab>
<md-primary-tab id="following-tab" active=@(GetTabActiveClass("following-panel")) aria-controls="following-panel">Following</md-primary-tab>
<md-primary-tab id="mainfeed-tab" active=@(GetTabActiveClass("mainfeed-panel")) aria-controls="mainfeed-panel">@UiStrings.Feed</md-primary-tab>
<md-primary-tab id="following-tab" active=@(GetTabActiveClass("following-panel")) aria-controls="following-panel">@UiStrings.Feed_Following</md-primary-tab>
<md-primary-tab id="followers-tab" active=@(GetTabActiveClass("followers-panel")) aria-controls="followers-panel">
<span class="flex items-center gap-1">
<span>Followers</span>
<span>@UiStrings.Feed_Followers</span>
@if (FeedState.Value.FollowRequests is { Count: > 0 })
{
<div class="w-2 h-2 bg-error rounded-full"></div>
Expand All @@ -34,15 +35,15 @@
}else {
<Card class="flex flex-col items-center justify-between m-2" Type=Card.CardType.Filled>
<md-icon class="mb-4">error</md-icon>
<span class="text-on-surface">You are not publishing your workouts</span>
<AppButton Type=AppButtonType.Text OnClick="@(async ()=>{settingsDialog?.Open(); await Task.Delay(500); SetPublishWorkouts(true); })">Start publishing</AppButton>
<span class="text-on-surface">@UiStrings.NotPublishingWorkouts</span>
<AppButton Type=AppButtonType.Text OnClick="@(async ()=>{settingsDialog?.Open(); await Task.Delay(500); SetPublishWorkouts(true); })">@UiStrings.StartPublishing</AppButton>
</Card>
}

@if (FeedState.Value.Feed is { Count: 0 })
{
<EmptyInfo class="mx-4 mt-10">
<p>Nothing here yet!<br> Nobody you're following has published anything yet! Come back later.</p>
<p>@UiStrings.NothingHereYet<br>@UiStrings.NoFollowingData</p>
</EmptyInfo>
}
<VirtualizedCardList Items="@(FeedState.Value.Feed.Select(x => (Item: x, User: FeedState.Value.FollowedUsers.GetValueOrDefault(x.UserId))).Where(x => x.User is not null).ToList())" OnClick="@(x => ViewFeedSession(x.Item))" CardClass="animate-fade-zoom-in">
Expand All @@ -54,7 +55,7 @@
@if (FeedState.Value.FollowedUsers is { Count: 0 })
{
<EmptyInfo class="mx-4 mt-10">
<p>Start following someone!<br>You're not currently following anyone, ask a friend for their share link to start!</p>
<p>@UiStrings.StartFollowingSomeone<br>@UiStrings.NotFollowingAnyone</p>
</EmptyInfo>
}
<VirtualizedCardList Items="@(FeedState.Value.FollowedUsers.Values.ToList())" CardClass="animate-fade-zoom-in">
Expand All @@ -70,14 +71,14 @@
}else {
<Card class="flex flex-col items-center justify-between m-2" Type=Card.CardType.Filled>
<md-icon class="mb-4">error</md-icon>
<span class="text-on-surface">You are not publishing your workouts</span>
<AppButton Type=AppButtonType.Text OnClick="@(async ()=>{settingsDialog?.Open(); await Task.Delay(500); SetPublishWorkouts(true); })">Start publishing</AppButton>
<span class="text-on-surface">@UiStrings.NotPublishingWorkouts</span>
<AppButton Type=AppButtonType.Text OnClick="@(async ()=>{settingsDialog?.Open(); await Task.Delay(500); SetPublishWorkouts(true); })">@UiStrings.StartPublishing</AppButton>
</Card>
}
@if (FeedState.Value.Followers is { Count: 0 } && FeedState.Value.FollowRequests is { Count: 0 })
{
<EmptyInfo class="mx-4 mt-10">
<p>Nobody is following you yet!<br>Share your link with friends to get followers</p>
<p>@UiStrings.NobodyFollowingYou<br>@UiStrings.ShareLinkToGetFollowers</p>
</EmptyInfo>
}
<VirtualizedCardList Items="@(FeedState.Value.FollowRequests.ToList())" CardClass="animate-fade-zoom-in">
Expand All @@ -102,14 +103,17 @@
}


<ConfirmationDialog @ref="deleteUserDialog" OkText="Unfollow" OnOk="DeleteUser">
<Headline>Unfollow user</Headline>
<TextContent>This will unfollow <span class="text-primary font-bold">@(userToDelete?.Nickname ?? userToDelete?.Name ?? "Anonymous User")</span> and remove all their content on your device.</TextContent>
<ConfirmationDialog @ref="deleteUserDialog" OkText="@UiStrings.Unfollow" OnOk="DeleteUser">
<Headline>@UiStrings.UnfollowUser</Headline>
<TextContent>
<LimitedHtml
Value=@(UiStrings.Unfollow_Msg(userToDelete?.Nickname ?? userToDelete?.Name ?? "Anonymous User"))/>
</TextContent>
</ConfirmationDialog>

<ConfirmationDialog @ref="removeFollowerDialog" OkText="Remove" OnOk="RemoveFollower">
<Headline>Remove follower</Headline>
<TextContent>This will stop <span class="text-primary font-bold">@(userToDelete?.Nickname ?? userToDelete?.Name ?? "Anonymous User")</span> from following you and remove all your content from their device.</TextContent>
<ConfirmationDialog @ref="removeFollowerDialog" OkText="@UiStrings.Remove" OnOk="RemoveFollower">
<Headline>@UiStrings.RemoveFollower</Headline>
<TextContent>@UiStrings.RemoveFollowerMsgPart1 <span class="text-primary font-bold">@(userToDelete?.Nickname ?? userToDelete?.Name ?? "Anonymous User")</span> @UiStrings.RemoveFollowerMsgPart2</TextContent>
</ConfirmationDialog>

@code
Expand All @@ -132,7 +136,7 @@
protected override void OnInitialized()
{
base.OnInitialized();
Dispatcher.Dispatch(new SetPageTitleAction("Feed"));
Dispatcher.Dispatch(new SetPageTitleAction(UiStrings.Feed));
Dispatcher.Dispatch(new SetBackNavigationUrlAction(null));
Dispatcher.Dispatch(new FetchSessionFeedItemsAction(FromUserAction: false));
Dispatcher.Dispatch(new FetchInboxItemsAction(FromUserAction: false));
Expand Down
14 changes: 8 additions & 6 deletions LiftLog.Ui/Pages/Feed/FeedUserPlanPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@
<SessionSummary Session="context.GetEmptySession()" ShowSets="true" ShowWeight="false"/>
</CardList>
<div>
<AppButton OnClick="() => importPlanDialog?.Open()"><md-icon slot="icon">assignment</md-icon>Import their plan</AppButton>
<AppButton OnClick="() => importPlanDialog?.Open()"><md-icon slot="icon">assignment</md-icon>@UiStrings.ImportTheirPlan</AppButton>
</div>
</div>

@if (user != null)
{
<Dialog @ref="importPlanDialog">

<span slot="headline">Import their plan</span>
<span slot="content" class="block text-left">This will import <span class="font-bold text-primary">@(user.DisplayName)'s</span> plan. It will not overwrite your existing plan.</span>
<span slot="headline">@UiStrings.ImportTheirPlan</span>
<span slot="content" class="block text-left">
<LimitedHtml Value="@UiStrings.ImportTheirPlanMessage(user.DisplayName)"/>
</span>
<div slot="actions">
<AppButton Type="AppButtonType.Text" OnClick=@(() => importPlanDialog?.Close())>Cancel</AppButton>
<AppButton Type="AppButtonType.Text" OnClick=ImportPlan>Import</AppButton>
<AppButton Type="AppButtonType.Text" OnClick=@(() => importPlanDialog?.Close())>@UiStrings.Cancel</AppButton>
<AppButton Type="AppButtonType.Text" OnClick=ImportPlan>@UiStrings.Import</AppButton>
</div>
</Dialog>
}
Expand All @@ -47,7 +49,7 @@
protected override void OnInitialized()
{
base.OnInitialized();
Dispatcher.Dispatch(new SetPageTitleAction("User Plan"));
Dispatcher.Dispatch(new SetPageTitleAction("User plan"));
Dispatcher.Dispatch(new SetBackNavigationUrlAction("/feed"));
if (!Guid.TryParse(UserIdStr, out var userId) || !FeedState.Value.FollowedUsers.TryGetValue(userId, out user))
{
Expand Down
2 changes: 1 addition & 1 deletion LiftLog.Ui/Pages/History/HistoryEditPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{
<div class="flex w-full my-2 px-2">
<TextField
Label="Date"
Label="@UiStrings.Date"
class="w-full"
TextFieldType="TextFieldType.Filled"
Value=@(CurrentSessionState.Value.HistorySession.Date.ToString("o"))
Expand Down
14 changes: 7 additions & 7 deletions LiftLog.Ui/Pages/History/HistoryPage.razor
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
@inject IDispatcher Dispatcher
@inject ILogger<HistoryPage> Logger


@if (!_latestSessions.Any())
{
<EmptyInfo>
<p>Nothing recorded yet!<br> Complete a session and check again.</p>
<p>@UiStrings.NothingHereYet<br>@UiStrings.CompleteAndComeBack</p>
</EmptyInfo>
}
else
Expand Down Expand Up @@ -45,16 +46,15 @@ else
@if (!filteredToMonthSessions.Any())
{
<EmptyInfo>
<p>Nothing recorded in @(currentMonth.ToString("MMMM"))<br> Complete a session or switch to another month to view your sessions.</p>
<p><LimitedHtml Value="@UiStrings.NoSessionsInMonth(currentMonth.ToString("MMMM"))"/></p>
</EmptyInfo>
}
}

<ConfirmationDialog @ref="_deleteDialog" OkText="Delete" OnOk=DeleteSession PreventCancel=true >
<Headline>Delete Session?</Headline>
<ConfirmationDialog @ref="_deleteDialog" OkText="@UiStrings.Delete" OnOk=DeleteSession PreventCancel=true >
<Headline>@UiStrings.DeleteSessionQuestion</Headline>
<TextContent>
The session named <span class="font-bold text-primary">@_selectedSession.Blueprint.Name</span>,
on @(_selectedSession.Date) will be deleted from history. This cannot be undone.
<LimitedHtml Value="@UiStrings.DeleteSessionMessage(@_selectedSession.Blueprint.Name,_selectedSession.Date)" />
</TextContent>
</ConfirmationDialog>

Expand Down Expand Up @@ -104,7 +104,7 @@ else
protected override async Task OnInitializedAsync()
{
var sw = System.Diagnostics.Stopwatch.StartNew();
Dispatcher.Dispatch(new SetPageTitleAction("History"));
Dispatcher.Dispatch(new SetPageTitleAction(UiStrings.History));
Dispatcher.Dispatch(new SetBackNavigationUrlAction(null));
Dispatcher.Dispatch(new SetReopenCurrentSessionAction(SessionTarget.HistorySession, false));
Dispatcher.Dispatch(new SetCurrentSessionAction(SessionTarget.HistorySession, null));
Expand Down
Loading

0 comments on commit 9b52f6e

Please sign in to comment.