Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion shell/plugins/panels/clock/BarWidget.qml
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,18 @@ BarWidget {
root.bar.shell.updateEntryInline(root.moduleName, entry)
}

// Qt renders day and month names in English whenever it is handed an
// explicit format string. An optional "locale" setting ("nb_NO", "de_DE",
// ...) formats the label through that locale instead; unset keeps the
// stock names.
readonly property string localeName: Model.localeName(setting("locale", ""))
readonly property var timeLocale: localeName ? Qt.locale(localeName) : null
readonly property var abbreviatedNames: Model.abbreviatedNames(timeLocale)

function formatted(date) {
return Qt.formatDateTime(date, activeFormat.replace(/ww/g, Model.isoWeekLiteral(date.getFullYear(), date.getMonth(), date.getDate())))
var format = activeFormat.replace(/ww/g, Model.isoWeekLiteral(date.getFullYear(), date.getMonth(), date.getDate()))
if (!timeLocale) return Qt.formatDateTime(date, format)
return Model.stripAbbreviationPeriods(date.toLocaleString(timeLocale, format), abbreviatedNames)
}

// ---- Calendar popup. Shape contract for shell.summon/hide/toggle
Expand Down
45 changes: 45 additions & 0 deletions shell/plugins/panels/clock/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,48 @@ var MS_PER_DAY = 86400000
// Locale.Saturday, so a locale's firstDayOfWeek can be passed straight in.
var WEEKDAY_NAMES = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"]

// The locale to format day and month names in, from the "locale" setting.
// "nb_NO", "nb-NO.UTF-8" and "nb_NO.utf8" all mean nb_NO; blank, C and POSIX
// mean "not set", which keeps the stock English names.
//
// This lives here rather than in the QML because it is pure string math, and
// because both the bar label and the calendar need the same answer.
function localeName(value) {
var name = String(value || "").trim().split(".")[0].replace(/-/g, "_")
return (name === "C" || name === "POSIX") ? "" : name
}

// How the calendar's hero date is written follows the locale's own order,
// read off its short date format: month-first locales (en_US, "M/d/yy") keep
// "MMMM d"; day-first ones get "d MMMM", with the ordinal period where the
// short form uses periods ("dd.MM.yyyy" gives "25. august").
// Several locales abbreviate day and month names with a trailing period
// ("tir.", "aug."), which next to an ordinal ("30.") stacks up as clutter in
// a bar label. Given the locale's short names, this takes the period off
// those and nothing else, so the ordinal in the format string survives.
function abbreviatedNames(locale) {
var out = []
if (!locale) return out
for (var d = 1; d <= 7; d++) out.push(String(locale.dayName(d, Locale.ShortFormat)))
for (var m = 1; m <= 12; m++) out.push(String(locale.monthName(m, Locale.ShortFormat)))
return out.filter(function (n) { return /\.$/.test(n) })
}
function stripAbbreviationPeriods(text, names) {
var out = String(text)
for (var i = 0; i < names.length; i++) {
var bare = names[i].slice(0, -1)
if (bare) out = out.split(names[i]).join(bare)
}
return out
}

function heroDateFormat(shortDateFormat) {
var f = String(shortDateFormat || "")
var d = f.search(/d/), m = f.search(/M/)
if (d < 0 || m < 0 || m < d) return "MMMM d"
return /d\./.test(f) ? "d. MMMM" : "d MMMM"
}

// ---- Bar label formats. Right-clicking the clock walks these in order and
// writes the result back to shell.json, so the label the bar shows and
// the format the config stores are always the same thing.
Expand Down Expand Up @@ -282,6 +324,9 @@ if (typeof module !== "undefined") {
module.exports = {
dateKey: dateKey,
keyForDate: keyForDate,
localeName: localeName,
stripAbbreviationPeriods: stripAbbreviationPeriods,
heroDateFormat: heroDateFormat,
normalizedWeekStart: normalizedWeekStart,
weekStartSettingName: weekStartSettingName,
toggledWeekStart: toggledWeekStart,
Expand Down
25 changes: 17 additions & 8 deletions shell/plugins/panels/clock/Panel.qml
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,15 @@ Panel {
// convention. Clicking the grid's "W" heading writes the choice back to
// shell.json.
readonly property int weekStart: Model.normalizedWeekStart(setting("weekStartDay", null), Qt.locale().firstDayOfWeek)
// The interface is English throughout, so day names are not taken from the
// system locale. Where the week starts still is: that is a regional
// convention rather than a translation, and it stays overridable above.
readonly property var labelLocale: Qt.locale("en_US")
// Day and month names follow the same "locale" setting the bar label uses,
// so the calendar and the clock above it never disagree about what today
// is called; unset means the interface's English. Where the week starts is
// read separately, above: that is a regional convention rather than a
// translation, and it stays overridable.
readonly property var labelLocale: {
var name = Model.localeName(setting("locale", ""))
return name ? Qt.locale(name) : Qt.locale("en_US")
}
readonly property string nextWeekStartLabel: labelLocale.dayName(Model.toggledWeekStart(weekStart), Locale.LongFormat)
readonly property var weekdays: Model.weekdayOrder(weekStart)
readonly property var weeks: Model.monthGrid(viewYear, viewMonth, weekStart, todayKey)
Expand Down Expand Up @@ -217,9 +222,11 @@ Panel {
setWeekStart(Model.toggledWeekStart(root.weekStart))
}

// English short day names, matching the rest of the interface.
// Short day names in the configured language. Several locales abbreviate
// with a trailing period ("man.", "tir.") which reads as clutter once these
// are column headings set in small caps, so the period comes off.
function weekdayLabel(weekday) {
return String(labelLocale.dayName(weekday, Locale.ShortFormat)).toUpperCase()
return String(labelLocale.dayName(weekday, Locale.ShortFormat)).replace(/\.$/, "").toUpperCase()
}

SystemClock {
Expand Down Expand Up @@ -313,7 +320,9 @@ Panel {
id: heroDate
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: Qt.formatDate(root.today, "MMMM d")
// "August 25" in English; day-first locales get "25 August"
// or "25. august", following their own short date order.
text: root.today.toLocaleDateString(root.labelLocale, Model.heroDateFormat(root.labelLocale.dateFormat(Locale.ShortFormat)))
color: heroMouse.containsMouse
? Style.hoverStateColor(root.contentForeground, Color.accent)
: root.contentForeground
Expand Down Expand Up @@ -721,7 +730,7 @@ Panel {
// "MAY 2026" and a "SEPTEMBER 2026".
width: Style.space(130)
horizontalAlignment: Text.AlignHCenter
text: Qt.formatDate(root.viewDate, "MMMM yyyy").toUpperCase()
text: root.viewDate.toLocaleDateString(root.labelLocale, "MMMM yyyy").toUpperCase()
color: Qt.darker(root.contentForeground, 1.4)
font.family: root.contentFontFamily
font.pixelSize: Style.font.body
Expand Down
14 changes: 13 additions & 1 deletion shell/plugins/panels/clock/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@
"displayName": "Clock",
"description": "Date/time label with a calendar popup",
"category": "Time",
"allowMultiple": false
"allowMultiple": false,
"defaults": {
"locale": ""
},
"schema": [
{
"key": "locale",
"type": "string",
"label": "Locale for day and month names",
"defaultValue": "",
"description": "Empty keeps English. A locale name such as nb_NO or de_DE formats the bar label and the calendar in that language."
}
]
}
}