Add Dutch, German, French, Italian, Spanish, Portuguese and Polish with their calendars and Home Assistant's own words
tools/i18n.py gets cldr (day and month names, date orders, numbers and the clock from Node's Intl), ha-words (the words for states from Home Assistant's own translations, English included) and lint (English left in the firmware's code). The screens write numbers from Home Assistant in the chosen format, and every text font carries the apostrophe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
// Day and month names, date orders, the decimal mark and the clock of languages, from the Unicode CLDR that Node's Intl
|
||||
// carries (app 0.2.90); tools/i18n.py cldr writes them into the translations. Prints JSON: {code: {...}}.
|
||||
const codes = process.argv.slice(2);
|
||||
const space = (text) => text.replace(/[ ]/g, ' ');
|
||||
// 2026-03-01 is a Sunday: day i of that week is Sunday + i, as the screens count (ESPHome's day_of_week 1 is Sunday).
|
||||
const day = (i) => new Date(Date.UTC(2026, 2, 1 + i, 12));
|
||||
const month = (m) => new Date(Date.UTC(2026, m, 14, 12));
|
||||
const pattern = (code, options, names) => {
|
||||
const parts = new Intl.DateTimeFormat(code, { ...options, timeZone: 'UTC' }).formatToParts(new Date(Date.UTC(2026, 8, 14, 14, 5)));
|
||||
let out = '';
|
||||
for (const part of parts) {
|
||||
if (names[part.type]) out += `{${names[part.type]}}`;
|
||||
else if (part.type === 'literal') out += space(part.value);
|
||||
}
|
||||
return out.replace(/\{hour\}:\{minute\}/, '{time}').replace(/\{hour\}\.\{minute\}/, '{time}');
|
||||
};
|
||||
const result = {};
|
||||
for (const code of codes) {
|
||||
const short = new Intl.DateTimeFormat(code, { weekday: 'short', timeZone: 'UTC' });
|
||||
const long = new Intl.DateTimeFormat(code, { weekday: 'long', timeZone: 'UTC' });
|
||||
const monthLong = (m) => new Intl.DateTimeFormat(code, { day: 'numeric', month: 'long', timeZone: 'UTC' }).formatToParts(month(m)).find((p) => p.type === 'month').value;
|
||||
const monthShort = (m) => new Intl.DateTimeFormat(code, { day: 'numeric', month: 'short', timeZone: 'UTC' }).formatToParts(month(m)).find((p) => p.type === 'month').value;
|
||||
const numbers = new Intl.NumberFormat(code);
|
||||
const parts = numbers.formatToParts(12345.5);
|
||||
const decimal = parts.find((p) => p.type === 'decimal').value;
|
||||
const group = space(parts.find((p) => p.type === 'group').value);
|
||||
const period = (hour) => (new Intl.DateTimeFormat(code, { hour: 'numeric', hour12: true, timeZone: 'UTC' })
|
||||
.formatToParts(new Date(Date.UTC(2026, 0, 1, hour))).find((p) => p.type === 'dayPeriod') || { value: hour < 12 ? 'AM' : 'PM' }).value;
|
||||
const cycle = new Intl.DateTimeFormat(code, { hour: 'numeric', timeZone: 'UTC' }).resolvedOptions().hourCycle;
|
||||
// A locale that writes a short date with the month as a number (Portugal: "14/09") gets the standalone short names
|
||||
// and "day month" instead: the screens' calendar shows a month's name beside the big day.
|
||||
const numericMonth = /^\d+$/.test(monthShort(8));
|
||||
const standalone = (m) => space(new Intl.DateTimeFormat(code, { month: 'short', timeZone: 'UTC' }).format(month(m)));
|
||||
const withNames = (text) => (numericMonth ? text.replace(/\{day\}\W*\{month\}/, '{day} {month}') : text);
|
||||
result[code] = {
|
||||
clock: cycle === 'h12' || cycle === 'h11' ? '12' : '24',
|
||||
number: { decimal, group, group_min: numbers.format(1234).includes(parts.find((p) => p.type === 'group').value) ? '1' : '2' },
|
||||
time: { am: space(period(9)), pm: space(period(21)) },
|
||||
date: {
|
||||
weekdays: [0, 1, 2, 3, 4, 5, 6].map((i) => long.format(day(i))),
|
||||
weekdays_short: [0, 1, 2, 3, 4, 5, 6].map((i) => short.format(day(i))),
|
||||
weekdays_min: [0, 1, 2, 3, 4, 5, 6].map((i) => short.format(day(i))),
|
||||
months: [...Array(12).keys()].map(monthLong),
|
||||
months_short: [...Array(12).keys()].map(numericMonth ? standalone : monthShort),
|
||||
top_bar: withNames(pattern(code, { weekday: 'short', day: 'numeric', month: 'short' }, { weekday: 'weekday', day: 'day', month: 'month' })),
|
||||
full: pattern(code, { weekday: 'long', day: 'numeric', month: 'long' }, { weekday: 'weekday', day: 'day', month: 'month' }),
|
||||
day_month: withNames(pattern(code, { day: 'numeric', month: 'short' }, { day: 'day', month: 'month' })),
|
||||
weekday_time: pattern(code, { weekday: 'short', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }, { weekday: 'weekday', hour: 'hour', minute: 'minute' }),
|
||||
},
|
||||
};
|
||||
}
|
||||
console.log(JSON.stringify(result, null, 1));
|
||||
Reference in New Issue
Block a user