diff --git a/Components/Composer.razor b/Components/Composer.razor index 0944b54..931ef2d 100644 --- a/Components/Composer.razor +++ b/Components/Composer.razor @@ -129,6 +129,17 @@ } + @if (!form.IsLocated && GroupId is null && Editing is null) + { + @* the language the post is written in, as each language names itself *@ + + @foreach (var (languageCode, languageName) in Languages.All) + { + + } + + } + @@ -258,6 +269,10 @@ _ => default }; + // a new post starts in the language the persona chose in Settings, else in the reader's + string DefaultLanguage => CascadingState.Persona?.Source?.Language is { Length: > 0 } chosen ? chosen + : System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName; + // a new post starts with the visibility the persona chose in Settings MessageType DefaultVisibility => CascadingState.Persona?.Source?.Privacy switch { @@ -270,6 +285,7 @@ { form.GroupId = GroupId; form.MessageType = DefaultVisibility; + form.Language = DefaultLanguage; form.QuotedStatusId = Quoting?.Id; if (Editing is not null) { @@ -290,6 +306,8 @@ "unlisted" => MessageType.Unlisted, _ => MessageType.Public }; + // a reply is written in its thread's language, as a rule + form.Language = InReplyTo.Language is { Length: 2 } language && Languages.All.Any(l => l.Code == language) ? language : form.Language; form.HasContentWarning = InReplyTo.SpoilerText is { Length: > 0 }; form.SpoilerText = InReplyTo.SpoilerText; var mentions = InReplyTo.Mentions.Select(m => m.Acct).Prepend(InReplyTo.Account?.Acct) @@ -430,14 +448,14 @@ if (result.Data is ScheduledStatus scheduled) { Toast.AddResponseSuccess(Localizer["Scheduled for {0}.", scheduled.ScheduledAt.ToLocalTime().ToString(SUtility.dateTimeFormatExt)]); - form = new() { GroupId = GroupId, MessageType = DefaultVisibility }; + form = new() { GroupId = GroupId, MessageType = DefaultVisibility, Language = DefaultLanguage }; await OnPublished.InvokeAsync(null); return; } Toast.AddResponseSuccess(form.IsLocated ? Localizer["Located post published."] : Localizer["Published."]); var published = result.Data as FeedPost; - form = new() { GroupId = GroupId, MessageType = DefaultVisibility }; + form = new() { GroupId = GroupId, MessageType = DefaultVisibility, Language = DefaultLanguage }; await OnPublished.InvokeAsync(published); } diff --git a/Models/ComposeForm.cs b/Models/ComposeForm.cs index 2c485ed..62f9bb2 100644 --- a/Models/ComposeForm.cs +++ b/Models/ComposeForm.cs @@ -43,6 +43,9 @@ public class ComposeForm public int PollExpiresIn { get; set; } = 86400; public bool PollMultiple { get; set; } + /// The language the post is written in (ISO 639-1); null leaves it to PrivaPub. + public string Language { get; set; } + /// The post this one quotes. public string QuotedStatusId { get; set; } diff --git a/Services/StatusService.cs b/Services/StatusService.cs index 5fc2cd9..63787d1 100644 --- a/Services/StatusService.cs +++ b/Services/StatusService.cs @@ -54,7 +54,7 @@ namespace decePubClient.Services Sensitive = form.HasContentWarning, SpoilerText = form.HasContentWarning ? form.SpoilerText : null, Visibility = form.Visibility, - Language = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, + Language = form.Language ?? CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, ScheduledAt = form.ScheduledAtUtc?.ToString("o", CultureInfo.InvariantCulture), Poll = form.HasPoll ? new PollForm { Options = [.. form.PollOptions.Select(o => o.Trim()).Where(o => o.Length > 0)], ExpiresIn = form.PollExpiresIn, Multiple = form.PollMultiple } : null, QuotedStatusId = form.QuotedStatusId diff --git a/tests/e2e/Actions/ComposerTests.cs b/tests/e2e/Actions/ComposerTests.cs index 03549a5..618fbb5 100644 --- a/tests/e2e/Actions/ComposerTests.cs +++ b/tests/e2e/Actions/ComposerTests.cs @@ -6,7 +6,8 @@ using Microsoft.Playwright; namespace decePubClient.E2E.Actions { - // The composer beyond words: a poll, the persona's own post edited, and a quote, each checked on PrivaPub + // The composer beyond words: a poll in a language picked, the persona's own post edited, and a quote, each checked on + // PrivaPub public sealed class ComposerTests(Browser browser) { static async Task OpenHome(IPage page) @@ -39,12 +40,15 @@ namespace decePubClient.E2E.Actions var choices = page.Locator("[data-poll-editor] input[type=text]"); await choices.Nth(0).FillAsync("tea"); await choices.Nth(1).FillAsync("coffee"); + // and in the language picked + await page.Locator("[data-compose-language]").First.SelectOptionAsync("it"); await page.GetByRole(AriaRole.Button, new() { Name = "Post" }).Last.ClickAsync(); JsonNode latest = null; Assert.True(await Pasture.Eventually(async () => (latest = await Latest(persona))?["content"]?.GetValue().Contains(text) == true, 15), "the poll was never published"); made = latest!["id"]!.GetValue(); Assert.Equal(new[] { "tea", "coffee" }, latest["poll"]!["options"]!.AsArray().Select(o => o!["title"]!.GetValue())); + Assert.Equal("it", latest["language"]?.GetValue()); await session.AssertGuards(); }, page); }