Weekdays that fit the weather columns, the top bar's own abbreviation, tile texts that fit, and a check that measures them

- The CYD's weather columns hold two letters beside the icon: every language has its own two-letter days (the CLDR's
  short width, Polish nd pn wt); French mar became nar there. check fails on a longer one.
- The top bar takes the language's abbreviation (sam. 19 sept., Sa., 19. Sept.), English keeps Sa 19 Sep.
- The editor's clock and date follow the screens: 7:05 PM in the top bar, 7:05 on the clock card.
- A long weather word ends in an ellipsis before the days.
- Tile lines that were cut on a screen are shorter (Zum Öffnen, Ouvrir, Otwórz, Sin imagen, ...); check measures
  them with the screens' font against the tile width of both boards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Max Gramser
2026-09-19 17:13:59 +02:00
parent 365ed158b9
commit f02f429188
26 changed files with 145 additions and 81 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ import type { HeaderItem } from "../types";
const query = ref("");
const taken = computed(() => new Set(topbarItems().map(itemKey)));
const samples = computed(() => ({
clock: clockText(clock24.value, new Date(state.now)),
clock: clockText(clock24.value, new Date(state.now), screenLanguage.value),
analog: t("editor.topbar.analog_sample"),
date: dateText(new Date(state.now), screenLanguage.value),
} as Record<string, string>));
+1 -1
View File
@@ -51,7 +51,7 @@ const liveNote = computed(() => {
return !view.shown ? t("editor.topbar.live.hidden") : overflow.value.has(props.index) ? t("editor.topbar.live.overflow") : t("editor.topbar.live.looks");
});
const samples = computed(() => ({
clock: clockText(clock24.value, new Date(state.now)),
clock: clockText(clock24.value, new Date(state.now), screenLanguage.value),
analog: t("editor.topbar.analog_sample"),
date: dateText(new Date(state.now), screenLanguage.value),
} as Record<string, string>));
+10 -6
View File
@@ -12,14 +12,18 @@ export const BUILTIN_ICONS: Record<string, string> = { clock: "clock-outline", a
export const itemKey = (item: HeaderItem) => JSON.stringify(item);
export const glyph = (cp: string) => String.fromCodePoint(parseInt(cp, 16));
export function clockText(clock24: boolean, now = new Date()) {
let hours = now.getHours();
// The screen formats with %I:%M when the 24-hour clock is off.
if (!clock24) hours = hours % 12 || 12;
return `${String(hours).padStart(2, "0")}:${String(now.getMinutes()).padStart(2, "0")}`;
// The time as the screens write it (screen_text::clock_text): "07:12" on 24 hours; on 12 "7:12 PM" in the top bar, with
// the language's day periods, and "7:12" in the clock card's big digits, which have no letters.
export function clockText(clock24: boolean, now = new Date(), locale?: string) {
const minutes = String(now.getMinutes()).padStart(2, "0");
if (clock24) return `${String(now.getHours()).padStart(2, "0")}:${minutes}`;
const time = `${now.getHours() % 12 || 12}:${minutes}`;
return locale ? `${time} ${t(`screen.time.${now.getHours() < 12 ? "am" : "pm"}`, {}, { locale })}` : time;
}
// "Sa 19 Sep", "za 19 sep", "sam. 19 sept.": the language's pattern with its abbreviation or its two letters.
export const dateText = (now = new Date(), locale = "en") => t("screen.date.top_bar", {
weekday: t(`screen.date.weekdays_min.${now.getDay()}`, {}, { locale }),
weekday: t(`screen.date.weekdays_short.${now.getDay()}`, {}, { locale }),
weekday_min: t(`screen.date.weekdays_min.${now.getDay()}`, {}, { locale }),
day: now.getDate(),
month: t(`screen.date.months_short.${now.getMonth()}`, {}, { locale }),
}, { locale });
+1 -1
View File
@@ -583,7 +583,7 @@ export function topbarLabel(item: HeaderItem) {
// What the item shows right now: { icon, text, color, shown }. Entities wait for the add-on's preview.
export function topbarView(item: HeaderItem): ItemView {
const now = new Date(state.now);
if (item.type === "clock") return { text: clockText(clock24.value, now), shown: true };
if (item.type === "clock") return { text: clockText(clock24.value, now, screenLanguage.value), shown: true };
if (item.type === "date") return { text: dateText(now, screenLanguage.value), shown: true };
if (item.type === "analog") return { analog: true, shown: true };
const p = state.topbarPreviews[itemKey(item)];
+2 -1
View File
@@ -175,7 +175,8 @@ describe("the mockup speaks the screens' language", () => {
ha: { unavailable: "Nicht verfügbar", on: "An", weather: { windy: "Windig" }, binary: { door_on: "Offen" }, button: { run: "Ausführen" } },
tile: { page: "Seite {n}" },
time: { hours_ago: "Vor 1 Stunde | Vor {n} Stunden", just_now: "Gerade eben" },
date: { weekdays_min: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], months_short: ["Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez."], top_bar: "{weekday}., {day}. {month}" },
date: { weekdays_min: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"], weekdays_short: ["So.", "Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa."],
months_short: ["Jan.", "Feb.", "März", "Apr.", "Mai", "Juni", "Juli", "Aug.", "Sept.", "Okt.", "Nov.", "Dez."], top_bar: "{weekday}, {day}. {month}" },
number: { decimal: ",", group: ".", group_min: "2" },
},
}, meta("one_other"));
+6 -1
View File
@@ -28,8 +28,13 @@ describe("clock and date", () => {
const evening = new Date(2026, 8, 15, 19, 5);
it("formats the clock the way the screen does", () => {
expect(clockText(true, evening)).toBe("19:05");
expect(clockText(false, evening)).toBe("07:05");
// The clock card's big digits: no leading zero, no day period (its font has no letters).
expect(clockText(false, evening)).toBe("7:05");
expect(clockText(false, new Date(2026, 8, 15, 0, 30))).toBe("12:30");
// The top bar: with the language's day period, as screen_text::clock_text writes it.
expect(clockText(false, evening, "en")).toBe("7:05 PM");
expect(clockText(false, new Date(2026, 8, 15, 0, 30), "en")).toBe("12:30 AM");
expect(clockText(true, evening, "en")).toBe("19:05");
});
it("writes the date short", () => {
expect(dateText(evening)).toBe("Tu 15 Sep");