Skip to content

Commit 2a5359e

Browse files
Bump to 2.5.1
fix labels cut off by long translations
1 parent 0852b50 commit 2a5359e

19 files changed

Lines changed: 389 additions & 40 deletions

‎.github/ISSUE_TEMPLATE/bug_report.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ body:
4141
attributes:
4242
label: SpaceSharp version
4343
description: Shown at the top of the About window (F1).
44-
placeholder: "2.5.0"
44+
placeholder: "2.5.1"
4545
validations:
4646
required: true
4747

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ SpaceSharp/Resources/Strings.resx.project.json
3737
# Website assets generated on demand (the site itself lives outside this repository)
3838
docs/m/
3939
tools/.fonts/BricolageGrotesque.ttf
40+
audit/

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
What changed in each SpaceSharp release from 2.0 on, newest first; the 1.x releases are on the [releases page](https://github.com/SpaceSharpApp/SpaceSharp/releases). A section marked unreleased is what the next version will say; the marker comes off when it ships, since the release script and the in-app What's new read the `## version` heading.
44

5+
## 2.5.1 (unreleased)
6+
7+
A small release: labels that a long translation cut off, found by a new layout check that runs the app through every language.
8+
9+
### Fixes
10+
- A pass over all eighteen languages at two window sizes, in both reading directions, found six labels that a long translation cut off; none is cut now. In Settings, the "Delete all saved scans" button, the file-count switch label and the "Horizontal" slider label take the room they need, and the Color by and filter layout dropdowns are sized for their widest entry (in every language, so they never change size with the selection). In Insights, the four age chips go two by two when the card is narrow instead of squeezing the sizes, and the "oldest largest" rows give a long date its room.
11+
12+
### Under the hood
13+
- `tools\layout-audit.ps1` runs the app in mock mode through 25 screens, two window sizes and both reading directions for every language, and writes one report per language listing any text wider than its box (`-Resume` skips languages already done). The same check is behind Ctrl+Shift+Alt+L in a debug build, with Ctrl+Shift+Alt+R mirroring the window for right-to-left. The check measures text the way the TextBlock draws it, so symbols and monospace runs are not misreported.
14+
515
## 2.5.0
616

717
Ten more palettes and a new picker, a map that draws itself while a scan runs, a Changes view in place of the change color mode, a Behavior tab, a scanning card that stays out of the way, meters in the status bar, a way to close a scan, a keyboard map that is right for ISO layouts, and a new mark.

‎DEVELOPING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ Until 2.3.0 the Windows app was a separate WPF project; git history has it. Next
9090

9191
**The icon.** The mark is a hexagonal well with the cube standing on its floor. `SpaceSharp/Assets/SpaceSharp.svg` is the source; the exports are maintained by hand and committed as files: `SpaceSharp.ico` (the mark on its rounded Graphite tile, with a frame for every size Windows asks for), `SpaceSharp-256.png`, `docs/wordmark.svg`, `docs/header.png`, `docs/social-preview.png` and the MSI bitmaps `installer/banner.bmp` and `logo.bmp`. There is no script that regenerates them.
9292

93-
**Trying things without a scan.** `SpaceSharp.exe --mock` (or `dotnet run --project SpaceSharp -- --mock`) starts on a pretend PC: a sample drive with generic names, sizes, dates and since-yesterday changes (`SpaceSharp.Core/Services/MockSystem.cs`), with five pretend drives in the Drives list; nothing on screen marks it as pretend. Drive clicks and rescans stay on the pretend PC. Two hidden hotkeys in the main window do the same without restarting: Ctrl+Shift+Alt+M loads the pretend PC, and Ctrl+Shift+Alt+U opens the update prompt as if the next minor version were out, with canned notes and an Install that only plays the download. Neither is in the help text or the Shortcuts window.
93+
**Trying things without a scan.** `SpaceSharp.exe --mock` (or `dotnet run --project SpaceSharp -- --mock`) starts on a pretend PC: a sample drive with generic names, sizes, dates and since-yesterday changes (`SpaceSharp.Core/Services/MockSystem.cs`), with five pretend drives in the Drives list; nothing on screen marks it as pretend. Drive clicks and rescans stay on the pretend PC. Two hidden hotkeys in the main window do the same without restarting: Ctrl+Shift+Alt+M loads the pretend PC, and Ctrl+Shift+Alt+U opens the update prompt as if the next minor version were out, with canned notes and an Install that only plays the download. Two more test the layout: Ctrl+Shift+Alt+R mirrors every open window the way a right-to-left language does (`--rtl` starts that way), and Ctrl+Shift+Alt+L runs a layout audit that lists every label wider than the room it was given, in the status bar and in `%LocalAppData%\SpaceSharp\logs\layout-audit.txt`; the mirror runs the audit by itself. `.\tools\layout-audit.ps1` runs the whole sweep unattended: `SpaceSharp.exe --audit <report> --lang <code>` poses every screen and dialog on the pretend PC in both directions and at the default and the minimum window size, audits each, writes one report with a summary of distinct findings on top, and exits with their count; the script does that for every language the app ships (English plus one per `Strings.*.resx`; `-Languages de,pl` narrows it) and writes the reports to `audit\`. None of these are in the help text or the Shortcuts window.
9494

9595
**Screenshots.** The README pictures are the app itself, posed on the pretend PC: `.\tools\screenshots.ps1` builds Release, then starts `SpaceSharp.exe --scene <name> --shot docs\<file>.png --size 1600x900` for each of the eleven scenes (none, drawer, insights, changes, inspect, settings, menu, start, about, update, light; `MainWindow.Showcase.cs`), which pose the window and capture it off the screen on Windows, title bar, dialog and menu included, then exit. Pass scene names to redo a few. The monitor's scaling sets the pixel size (125% or 150% looks right on GitHub), and the settings are frozen during a run so a scene's theme or palette never sticks. `tools/screenshots_wiki.ps1` does the same for the wiki's pictures (the `WikiScenes` list in the same file: color modes, shading, the rail, the palette picker, the Shortcuts window, one scene per Settings page, and a sheet of every palette) and writes them into the wiki clone beside the repository. Then `python tools/make-mobile-shots.py` for the small WebP copies the website shows on phones (written to `docs/m/`, which is ignored by git; copy them and the PNGs into the site when they change). The website (spacesharp.app, kept outside this repository) shows the same pictures. When a screen changes, redo its scene.
9696

‎SpaceSharp.Core/SpaceSharp.Core.csproj‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
2020
<AnalysisLevel>latest-recommended</AnalysisLevel>
2121
<Product>SpaceSharp</Product>
22-
<Version>2.5.0</Version>
22+
<Version>2.5.1</Version>
2323
<Authors>ClearanceClarence</Authors>
2424
<Company>ClearanceClarence</Company>
2525
<Copyright>© 2026 ClearanceClarence</Copyright>

‎SpaceSharp.Core/Util/AppLanguages.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ public static void Apply(string? setting)
6767
IsRightToLeft = culture.TextInfo.IsRightToLeft;
6868
}
6969

70-
/// <summary>True when the chosen language reads right to left.</summary>
71-
public static bool IsRightToLeft { get; private set; }
70+
/// <summary>True when the chosen language reads right to left, or the right-to-left preview is on (<c>--rtl</c>, Ctrl+Shift+Alt+R).</summary>
71+
public static bool IsRightToLeft { get; set; }
7272

7373
/// <summary>The culture's native name with the first letter capitalized.</summary>
7474
public static string DisplayName(CultureInfo culture)

‎SpaceSharp.Core/Util/CommandLine.cs‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
namespace SpaceSharp.Util;
44

55
/// <summary>Parsed command-line arguments; unknown arguments are ignored.</summary>
6-
/// <remarks>See <see cref="HelpText"/>; hidden flags are --mock, --scene, --shot and --size.</remarks>
6+
/// <remarks>See <see cref="HelpText"/>; hidden flags are --mock, --rtl, --lang, --audit, --scene, --shot and --size.</remarks>
77
public sealed class CommandLine
88
{
99
public string? ScanPath { get; private set; }
@@ -12,6 +12,12 @@ public sealed class CommandLine
1212
public bool ShowHelp { get; private set; }
1313
/// <summary>Start on <see cref="Services.MockSystem"/>.</summary>
1414
public bool Mock { get; private set; }
15+
/// <summary>Mirror every window as a right-to-left language would, whatever the language; for testing the layout.</summary>
16+
public bool RightToLeft { get; private set; }
17+
/// <summary>Run the layout audit over every scene, both directions and two window sizes, write the report here, and exit.</summary>
18+
public string? Audit { get; private set; }
19+
/// <summary>A culture code to run in, instead of the settings' language.</summary>
20+
public string? Language { get; private set; }
1521
/// <summary>A screenshot scene to pose; implies <see cref="Mock"/>.</summary>
1622
public string? Scene { get; private set; }
1723
/// <summary>With a scene: write the window to this PNG and exit.</summary>
@@ -33,6 +39,16 @@ public static CommandLine Parse(string[] args)
3339
case "--mock":
3440
c.Mock = true;
3541
break;
42+
case "--rtl":
43+
c.RightToLeft = true;
44+
break;
45+
case "--audit":
46+
c.Audit = i + 1 < args.Length && !args[i + 1].StartsWith('-') ? Full(args[++i]) : string.Empty;
47+
c.Mock = true;
48+
break;
49+
case "--lang" when i + 1 < args.Length:
50+
c.Language = args[++i];
51+
break;
3652
case "--scene" when i + 1 < args.Length:
3753
c.Scene = args[++i].ToLowerInvariant();
3854
c.Mock = true;

‎SpaceSharp/App.axaml.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ public override void OnFrameworkInitializationCompleted()
3838
}
3939

4040
HookErrors();
41-
AppLanguages.Apply(AppSettings.Current.Language); // before any window is created
41+
AppLanguages.Apply(Args.Language ?? AppSettings.Current.Language); // before any window is created
42+
if (Args.RightToLeft) AppLanguages.IsRightToLeft = true;
4243
ApplyTheme();
4344

4445
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)

‎SpaceSharp/MainWindow.Debug.cs‎

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
using Avalonia.Controls;
2+
using Avalonia.Media;
3+
using Avalonia.Threading;
4+
using SpaceSharp.Util;
5+
6+
namespace SpaceSharp;
7+
8+
/// <summary>Hidden development aids: the right-to-left preview and the layout audit. Neither is in the help text or the Shortcuts window.</summary>
9+
public partial class MainWindow
10+
{
11+
/// <summary>Mirrors every open window as a right-to-left language would, or back (Ctrl+Shift+Alt+R); windows opened afterwards follow.</summary>
12+
private void ToggleRightToLeft()
13+
{
14+
AppLanguages.IsRightToLeft = !AppLanguages.IsRightToLeft;
15+
var direction = AppLanguages.IsRightToLeft ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
16+
FlowDirection = direction;
17+
foreach (var owned in OwnedWindows) owned.FlowDirection = direction;
18+
StatusText.Text = AppLanguages.IsRightToLeft
19+
? "Right-to-left preview on (Ctrl+Shift+Alt+R turns it off, Ctrl+Shift+Alt+L lists clipped text)"
20+
: "Right-to-left preview off";
21+
_ = AuditLayoutAsync(quiet: true);
22+
}
23+
24+
/// <summary>Lists text the window cuts off, in the status bar and in a file next to the crash logs (Ctrl+Shift+Alt+L).</summary>
25+
private async Task AuditLayoutAsync(bool quiet = false)
26+
{
27+
await Dispatcher.UIThread.InvokeAsync(() => { }, DispatcherPriority.Background); // after the layout pass the toggle caused
28+
var windows = new List<Window> { this };
29+
windows.AddRange(OwnedWindows);
30+
var findings = windows.SelectMany(LayoutAudit.ClippedText).ToList();
31+
string title = $"Layout audit {DateTime.Now:yyyy-MM-dd HH:mm:ss} · {(AppLanguages.IsRightToLeft ? "right-to-left" : "left-to-right")} · {windows.Count} window(s) · {Bounds.Width:0}×{Bounds.Height:0}";
32+
string report = LayoutAudit.Report(findings, title);
33+
Console.WriteLine(report);
34+
string? file = null;
35+
try
36+
{
37+
Directory.CreateDirectory(CrashLog.Folder);
38+
file = Path.Combine(CrashLog.Folder, "layout-audit.txt");
39+
await File.WriteAllTextAsync(file, report);
40+
}
41+
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { }
42+
if (quiet && findings.Count == 0) return;
43+
StatusText.Text = findings.Count == 0
44+
? "Layout audit: no clipped text."
45+
: $"Layout audit: {findings.Count} clipped, first \"{findings[0].Text}\" in {findings[0].Where}" + (file is null ? string.Empty : $" · list in {file}");
46+
}
47+
48+
// ---------------------------------------------------------------- the automated audit (--audit)
49+
50+
/// <summary>The scenes the audit walks: every screen and dialog that has words in it. The menu is a popup the audit cannot see; the palette sheet is a picture.</summary>
51+
private static readonly string[] AuditScenes =
52+
{
53+
"start", "none", "wiki-colorby-type", "wiki-zoomed", "insights", "changes", "wiki-changes-everything", "drawer", "wiki-rail", "wiki-filtered",
54+
"wiki-sidepanel-changes", "inspect", "wiki-inspect-selection", "settings", "wiki-settings-general", "wiki-settings-behavior", "wiki-settings-appearance",
55+
"wiki-settings-text", "wiki-settings-map", "wiki-settings-scanning", "wiki-shortcuts", "about", "update", "wiki-palette-editor", "light"
56+
};
57+
58+
private static readonly (int Width, int Height)[] AuditSizes = { (1600, 900), (1040, 620) };
59+
60+
/// <summary>Poses every scene in both directions and both sizes, audits each, writes one report and exits; the exit code is the number of distinct findings, capped at 250.</summary>
61+
private async Task RunAuditAsync(string file)
62+
{
63+
AppSettings.Frozen = true;
64+
if (file.Length == 0) file = Path.Combine(CrashLog.Folder, "layout-audit.txt");
65+
var report = new System.Text.StringBuilder();
66+
var distinct = new Dictionary<string, (LayoutAudit.Finding Finding, List<string> States)>(StringComparer.Ordinal);
67+
int states = 0;
68+
report.AppendLine($"SpaceSharp {AppInfo.Version} layout audit · {DateTime.Now:yyyy-MM-dd HH:mm} · language {System.Globalization.CultureInfo.CurrentUICulture.Name}");
69+
foreach (bool rtl in new[] { false, true })
70+
{
71+
AppLanguages.IsRightToLeft = rtl;
72+
FlowDirection = rtl ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
73+
foreach (var (width, height) in AuditSizes)
74+
{
75+
Width = width; Height = height;
76+
await Settle(200);
77+
foreach (var scene in AuditScenes)
78+
{
79+
string state = $"{scene} · {(rtl ? "rtl" : "ltr")} · {width}×{height}";
80+
Window? dialog = null;
81+
try
82+
{
83+
dialog = await PoseSceneAsync(scene);
84+
await Settle(350);
85+
var windows = new List<Window> { this }; windows.AddRange(OwnedWindows);
86+
var findings = windows.SelectMany(LayoutAudit.ClippedText).ToList();
87+
states++;
88+
report.AppendLine().AppendLine(LayoutAudit.Report(findings, $"[{state}]"));
89+
foreach (var f in findings)
90+
{
91+
string key = f.Text + "|" + f.Where;
92+
if (!distinct.TryGetValue(key, out var entry)) distinct[key] = entry = (f, new List<string>());
93+
entry.States.Add(state);
94+
}
95+
}
96+
catch (Exception ex)
97+
{
98+
report.AppendLine().AppendLine($"[{state}] could not be posed: {ex.GetType().Name}: {ex.Message}");
99+
}
100+
finally
101+
{
102+
await UnposeSceneAsync(dialog);
103+
}
104+
}
105+
}
106+
}
107+
108+
// the summary first, since that is what gets read
109+
var summary = new System.Text.StringBuilder();
110+
summary.AppendLine($"{states} states audited · {distinct.Count} distinct clipped label(s)");
111+
foreach (var (f, where) in distinct.Values.OrderByDescending(v => v.States.Count).Select(v => (v.Finding, v.States)))
112+
summary.AppendLine($" \"{f.Text}\" needs {f.Needed:0} px, has {f.Given:0} px ({f.Where}) in {where.Count} state(s), e.g. {where[0]}");
113+
string text = summary.ToString() + Environment.NewLine + "---- every state ----" + report;
114+
try
115+
{
116+
Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(file)) ?? ".");
117+
await File.WriteAllTextAsync(file, text);
118+
Console.WriteLine($"{summary} report: {file}");
119+
}
120+
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
121+
{
122+
Console.Error.WriteLine($"Could not write {file}: {ex.Message}");
123+
Console.WriteLine(text);
124+
}
125+
Environment.ExitCode = Math.Min(250, distinct.Count);
126+
(Avalonia.Application.Current?.ApplicationLifetime as Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime)?.Shutdown(Environment.ExitCode);
127+
}
128+
}

0 commit comments

Comments
 (0)