Mediensteuerung hochkant, Symbole wieder sichtbar, „Auto" als Zustand

Die Mediensteuerung stand nebeneinander: Cover links, alles andere in den
verbleibenden gut hundert Punkten. Daher „Bernhard H…" statt eines Titels.
Jetzt übereinander, wie es zu einer hochkanten Kachel passt.

Das Cover ragte zusätzlich über die Kachel hinaus, weil `clipShape` vor dem
Rahmen stand — beschnitten wurde damit auf die Eigengröße des Bildes, und
die ist bei einem Podcast-Cover ein Vielfaches. Erst Rahmen, dann
beschneiden.

Menüleistensymbole waren kaum zu sehen. `color.set()` vor `image.draw(in:)`
färbt ein Template-Bild nicht: es wird schwarz gezeichnet, auf einer dunklen
Menüleiste also unsichtbar. AppKit färbt Template-Bilder nur ein, wenn sie
als `image` eines Buttons gesetzt sind — in einer selbst gezeichneten Ansicht
muss die Farbe in die Symbolkonfiguration. Betraf alle fünf Zeichenstellen,
nicht nur die Lüfter.

„fans.auto" stand als roher Schlüssel da: Xcode hatte ihn extrahiert, gefüllt
wurde er nie. Beim Nachsehen waren es 35 solche Einträge über alle Kataloge
hinweg — die meisten reine Formatschlüssel, die als Durchreicher trotzdem
einen Wert brauchen.

Der Automatik-Knopf ist jetzt eine gefüllte Pille, wenn sie greift. Blauer
Text sieht aus wie ein Link und nicht wie ein Zustand.

Und ein Absturz, der noch niemandem passiert ist: meldet der Helfer für einen
stehenden Lüfter keine Grenzen, hätte `Slider(in: 0...0)` die App beendet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-08-11 11:12:33 +02:00
parent 93fe95827f
commit b22a44ba70
14 changed files with 1400 additions and 1027 deletions

View File

@@ -98,22 +98,46 @@ private struct FanRow: View {
.foregroundStyle(Onyx.Color.textPrimary)
}
HStack(spacing: 6) {
HStack(spacing: 8) {
// Automatik ist der Ruhezustand und bleibt einen Klick entfernt:
// wer manuell geregelt hat, muss ohne Nachdenken zurückkönnen.
// Als gefüllte Pille, wenn sie greift ein blauer Text sieht
// aus wie ein Link und nicht wie ein Zustand.
Button { control.setAutomatic(index: index) } label: {
Text("fans.auto")
.font(Onyx.Font.caption)
.font(Onyx.Font.metricSmall)
.foregroundStyle(fan.isManual ? Onyx.Color.textTertiary
: Onyx.Color.accent)
: Onyx.Color.surface)
.padding(.horizontal, 7)
.padding(.vertical, 2)
.background {
Capsule()
.fill(fan.isManual ? Color.clear : Onyx.Color.accent)
.overlay {
Capsule().strokeBorder(
fan.isManual ? Onyx.Color.hairline : .clear,
lineWidth: 1)
}
}
}
.buttonStyle(.plain)
.disabled(!fan.isManual)
// Ein Regler über einen leeren Bereich stürzt ab. Meldet der
// Helfer keine brauchbaren Grenzen etwa weil der Lüfter steht
// und der SMC nichts hergibt , gibt es nichts zu ziehen.
if fan.limits.maximum > fan.limits.minimum {
Slider(value: Binding(
get: { fan.targetRPM },
get: { min(max(fan.targetRPM, fan.limits.minimum), fan.limits.maximum) },
set: { control.setTarget(index: index, rpm: $0) }),
in: fan.limits.minimum...fan.limits.maximum)
.controlSize(.mini)
} else {
Text("fans.noRange")
.font(Onyx.Font.metricSmall)
.foregroundStyle(Onyx.Color.textTertiary)
Spacer(minLength: 0)
}
}
}
}
@@ -219,19 +243,16 @@ private final class FanStatusView: NSView, WidthReporting {
override func draw(_ dirtyRect: NSRect) {
let color = NSColor.labelColor
guard let image = NSImage(systemSymbolName: "fan", accessibilityDescription: nil)
else { return }
image.isTemplate = true
if presentation == .symbol {
color.set()
image.draw(in: NSRect(x: bounds.midX - 7.5, y: bounds.midY - 7.5,
MenuBarText.drawSymbol("fan", color: color,
in: NSRect(x: bounds.midX - 7.5, y: bounds.midY - 7.5,
width: 15, height: 15))
return
}
color.set()
image.draw(in: NSRect(x: 0, y: bounds.midY - 6.5, width: 13, height: 13))
MenuBarText.drawSymbol("fan", color: color,
in: NSRect(x: 0, y: bounds.midY - 6.5, width: 13, height: 13))
MenuBarText.draw(rpm.map { String(Int($0.rounded())) } ?? "",
color: color,
in: NSRect(x: 15, y: 0, width: bounds.width - 15, height: bounds.height))

View File

@@ -3,15 +3,75 @@
"strings": {
"%@": {
"comment": "A label showing the minimum RPM of a fan.",
"isCommentAutoGenerated": true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%@"
}
}
}
},
"%@ U/min": {
"comment": "A label that shows the current speed of a fan. The argument is the current speed of the fan.",
"isCommentAutoGenerated": true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ U/min"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ U/min"
}
}
}
},
"%@ °C": {
"comment": "A label showing the temperature of the hottest fan in the report, in degrees Celsius. The argument is the temperature in degrees Celsius.",
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ °C"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ °C"
}
}
}
},
"%@°": {
"comment": "A temperature in °C.",
"isCommentAutoGenerated": true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%@°"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%@°"
}
}
}
},
"fans.approval.needed": {
"extractionState": "stale",
@@ -64,6 +124,24 @@
}
}
},
"fans.auto": {
"comment": "A button to set a fan to automatic mode.",
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Auto"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Auto"
}
}
}
},
"fans.connecting": {
"localizations": {
"de": {
@@ -80,7 +158,26 @@
}
}
},
"fans.fan %@": {
"comment": "A label showing the index of a fan. The argument is the index of the fan.",
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Lüfter %@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Fan %@"
}
}
}
},
"fans.fan %lld": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -162,6 +259,22 @@
}
}
},
"fans.manual": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Manuell"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Manual"
}
}
}
},
"fans.mode.auto": {
"localizations": {
"de": {
@@ -195,6 +308,7 @@
}
},
"fans.noData": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -210,7 +324,24 @@
}
}
},
"fans.noRange": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Kein Regelbereich"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "No adjustable range"
}
}
}
},
"fans.notInstalled": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -563,6 +694,7 @@
}
},
"mixer.menubar.name": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -596,6 +728,7 @@
}
},
"onboarding.allow": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -612,6 +745,7 @@
}
},
"onboarding.audio": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -628,6 +762,7 @@
}
},
"onboarding.audio.why": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -644,6 +779,7 @@
}
},
"onboarding.calendar": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -660,6 +796,7 @@
}
},
"onboarding.calendar.why": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -676,6 +813,7 @@
}
},
"onboarding.done": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -692,6 +830,7 @@
}
},
"onboarding.helper": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -708,6 +847,7 @@
}
},
"onboarding.helper.why": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -740,6 +880,7 @@
}
},
"onboarding.location": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -756,6 +897,7 @@
}
},
"onboarding.location.why": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -772,6 +914,7 @@
}
},
"onboarding.openSettings": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -951,6 +1094,7 @@
}
},
"settings.general": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -1100,6 +1244,7 @@
}
},
"settings.onboarding.show": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -1252,7 +1397,21 @@
},
"settings.shelf.count %@": {
"comment": "A label showing the number of items in the shelf. The argument is the number of items in the shelf.",
"isCommentAutoGenerated": true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ Dateien abgelegt"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ files parked"
}
}
}
},
"settings.shelf.count %lld": {
"extractionState": "stale",
@@ -1273,7 +1432,21 @@
},
"settings.shelf.days %@": {
"comment": "A label that lets the user select how many days to wait before automatically removing items from the shelf. The argument is the number of days.",
"isCommentAutoGenerated": true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Nach %@ Tagen"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "After %@ days"
}
}
}
},
"settings.shelf.days %lld": {
"extractionState": "stale",
@@ -1579,6 +1752,7 @@
}
},
"settings.widgets.hint.order": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {
@@ -1629,6 +1803,7 @@
}
},
"widget.fans.name": {
"extractionState": "stale",
"localizations": {
"de": {
"stringUnit": {

View File

@@ -92,17 +92,14 @@ private final class MixerStatusView: NSView {
}
override func draw(_ dirtyRect: NSRect) {
guard let image = NSImage(systemSymbolName: "slider.horizontal.3",
accessibilityDescription: nil) else { return }
image.isTemplate = true
// Ausgeschaltet bleibt es sichtbar, aber zurückgenommen: das Element
// verschwinden zu lassen wäre die schlechtere Antwort dann fände man
// den Weg zum Einschalten nicht mehr.
let color = (isEnabled && managed > 0)
? NSColor.labelColor
: NSColor.labelColor.withAlphaComponent(0.55)
color.set()
image.draw(in: NSRect(x: bounds.midX - 7.5, y: bounds.midY - 7.5,
MenuBarText.drawSymbol("slider.horizontal.3", color: color,
in: NSRect(x: bounds.midX - 7.5, y: bounds.midY - 7.5,
width: 15, height: 15))
}
}

View File

@@ -4,7 +4,21 @@
"": {},
"%lld %%": {
"comment": "A row of controls for adjusting the volume and muting a process.",
"isCommentAutoGenerated": true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%lld %%"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%lld %%"
}
}
}
},
"mixer.name": {
"localizations": {

View File

@@ -1,12 +1,24 @@
{
"sourceLanguage": "en",
"strings": {
"" : {
},
"": {},
"%lld": {
"comment": "A day of the month. The argument is the day of the month.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%lld"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%lld"
}
}
}
},
"calendar.source.apple": {
"localizations": {
@@ -91,7 +103,21 @@
},
"calendar.source.status.coverage %@": {
"comment": "A footnote in the Calendarr settings, indicating the end of the coverage window.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Bekannt bis %@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Known through %@"
}
}
}
},
"calendar.source.status.incompatible": {
"localizations": {
@@ -167,6 +193,12 @@
"state": "new",
"value": "calendar.source.status.ready %1$@ %2$@"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ Termine, Stand %@"
}
}
}
},
@@ -189,7 +221,21 @@
},
"calendar.source.status.unreadable %@": {
"comment": "A warning message explaining that a Calendarr source is not readable. The argument is a human-readable description of the problem.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Schnappschuss unlesbar: %@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Snapshot unreadable: %@"
}
}
}
},
"calendar.source.title": {
"localizations": {

View File

@@ -3,21 +3,51 @@
"strings": {
"widget.media.name": {
"localizations": {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Medien" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Media" } }
"de": {
"stringUnit": {
"state": "translated",
"value": "Medien"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Media"
}
}
}
},
"widget.media.nothing": {
"localizations": {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Nichts läuft gerade" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Nothing playing" } }
"de": {
"stringUnit": {
"state": "translated",
"value": "Nichts läuft gerade"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Nothing playing"
}
}
}
},
"widget.media.viaAppleScript": {
"comment": "Hinweis, dass die schwächere Quelle greift",
"localizations": {
"de" : { "stringUnit" : { "state" : "translated", "value" : "Über AppleScript — ohne Cover, nur Spotify und Musik" } },
"en" : { "stringUnit" : { "state" : "translated", "value" : "Via AppleScript — no artwork, Spotify and Music only" } }
"de": {
"stringUnit": {
"state": "translated",
"value": "Über AppleScript — ohne Cover, nur Spotify und Musik"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Via AppleScript — no artwork, Spotify and Music only"
}
}
}
}
},

View File

@@ -56,27 +56,36 @@ private struct Playing: View {
let state: NowPlaying
var body: some View {
HStack(spacing: 10) {
// Übereinander, nicht nebeneinander. Die Kachel ist hochkant: neben
// einem Cover blieben für den Titel noch gut hundert Punkte, und dann
// steht dort Bernhard H" statt eines Titels.
VStack(alignment: .leading, spacing: 7) {
Artwork(data: state.artwork)
.frame(width: artworkSize, height: artworkSize)
.frame(maxWidth: .infinity)
.frame(height: 88)
VStack(alignment: .leading, spacing: 2) {
VStack(alignment: .leading, spacing: 1) {
Text(state.title)
.font(Onyx.Font.title)
.font(Onyx.Font.caption.weight(.medium))
.foregroundStyle(Onyx.Color.textPrimary)
.lineLimit(1)
.lineLimit(2)
Text(state.artist)
.font(Onyx.Font.caption)
.foregroundStyle(Onyx.Color.textSecondary)
.lineLimit(1)
}
.frame(maxWidth: .infinity, alignment: .leading)
Spacer(minLength: 2)
Spacer(minLength: 0)
Scrubber(model: model, state: state)
HStack {
HStack(spacing: 0) {
Spacer(minLength: 0)
TransportControls(model: model, size: 15)
Spacer(minLength: 0)
}
.overlay(alignment: .trailing) {
if state.origin == .appleScript {
// Sichtbar machen, dass gerade die schwächere Quelle
// greift: dann fehlen Cover und Browser-Wiedergaben,
@@ -88,13 +97,10 @@ private struct Playing: View {
}
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.contentShape(Rectangle())
.onTapGesture(count: 2) { model.activateSource() }
}
private let artworkSize: CGFloat = 56
}
private struct Artwork: View {
@@ -103,6 +109,9 @@ private struct Artwork: View {
var body: some View {
let shape = RoundedRectangle(cornerRadius: 8, style: .continuous)
// Reihenfolge zählt: erst der Rahmen, dann das Beschneiden. Andersherum
// beschneidet man das Bild auf seine eigene, viel größere Größe und es
// ragt anschließend über die Kachel hinaus, mitten in den Nachbarn.
Group {
if let data, let image = NSImage(data: data) {
Image(nsImage: image).resizable().aspectRatio(contentMode: .fill)
@@ -118,6 +127,7 @@ private struct Artwork: View {
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipShape(shape)
.overlay(shape.strokeBorder(Onyx.Color.hairline, lineWidth: 1))
}

View File

@@ -1,20 +1,60 @@
{
"sourceLanguage": "en",
"strings": {
"" : {
},
"": {},
"%lld": {
"comment": "A label displaying the speed of a fan. The argument is the speed of the fan, in revolutions per minute.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%lld"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%lld"
}
}
}
},
"%lld U/min": {
"comment": "A label that shows the fan speed in revolutions per minute. The value is the fan speed in revolutions per minute.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "%lld U/min"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "%lld U/min"
}
}
}
},
"Lüfter %lld": {
"comment": "A label that shows the name of a fan. The argument is the index of the fan.",
"isCommentAutoGenerated" : true
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Lüfter %lld"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Fan %lld"
}
}
}
},
"metric.battery": {
"localizations": {

View File

@@ -173,14 +173,11 @@ final class MetricStatusView: NSView, WidthReporting {
}
private func drawSymbol(color: NSColor) {
guard let image = NSImage(systemSymbolName: metric.symbolName,
accessibilityDescription: nil) else { return }
image.isTemplate = true
let side: CGFloat = 15
let rect = NSRect(x: bounds.midX - side / 2, y: bounds.midY - side / 2,
width: side, height: side)
color.set()
image.draw(in: rect)
MenuBarText.drawSymbol(metric.symbolName, color: color,
in: NSRect(x: bounds.midX - side / 2,
y: bounds.midY - side / 2,
width: side, height: side))
}
private func drawGraph(color: NSColor, in rect: NSRect) {

View File

@@ -110,11 +110,8 @@ final class NetworkStatusView: NSView {
switch presentation {
case .symbol:
guard let image = NSImage(systemSymbolName: snapshot.interfaceKind.symbolName,
accessibilityDescription: nil) else { return }
image.isTemplate = true
color.set()
image.draw(in: NSRect(x: bounds.midX - 7.5, y: bounds.midY - 7.5,
MenuBarText.drawSymbol(snapshot.interfaceKind.symbolName, color: color,
in: NSRect(x: bounds.midX - 7.5, y: bounds.midY - 7.5,
width: 15, height: 15))
case .graph, .bars:

View File

@@ -1,19 +1,18 @@
{
"sourceLanguage": "en",
"strings": {
"singleInstance.blocked.message" : {
"comment" : "Titel des Dialogs, wenn eine zweite Instanz zurücktritt",
"common.ok": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value" : "Onyx läuft bereits"
"value": "OK"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value" : "Onyx is already running"
"value": "OK"
}
}
}
@@ -35,18 +34,19 @@
}
}
},
"common.ok" : {
"singleInstance.blocked.message": {
"comment": "Titel des Dialogs, wenn eine zweite Instanz zurücktritt",
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value" : "OK"
"value": "Onyx läuft bereits"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value" : "OK"
"value": "Onyx is already running"
}
}
}

View File

@@ -47,6 +47,24 @@ public enum MenuBarText {
itemWidth(textWidth: width(of: reference), includesSymbol: includesSymbol)
}
/// Zeichnet ein SF-Symbol in der gewünschten Farbe.
///
/// `color.set()` vor `image.draw(in:)` reicht **nicht**. Ein Template-Bild
/// trägt seine Farbe nicht aus dem Grafikzustand, sondern wird schwarz
/// gezeichnet auf einer dunklen Menüleiste also unsichtbar. AppKit färbt
/// Template-Bilder nur ein, wenn sie als `image` eines Buttons gesetzt sind;
/// in einer selbst gezeichneten Ansicht muss die Farbe in die
/// Symbolkonfiguration.
public static func drawSymbol(_ name: String, color: NSColor, in rect: NSRect) {
guard let image = NSImage(systemSymbolName: name, accessibilityDescription: nil)
else { return }
let configuration = NSImage.SymbolConfiguration(pointSize: rect.height,
weight: .regular)
.applying(NSImage.SymbolConfiguration(paletteColors: [color]))
let tinted = image.withSymbolConfiguration(configuration) ?? image
tinted.draw(in: rect)
}
public static func draw(_ text: String, color: NSColor, in rect: NSRect) {
let string = NSAttributedString(string: text,
attributes: [.font: font, .foregroundColor: color])

View File

@@ -1,14 +1,6 @@
{
"sourceLanguage": "en",
"strings": {
"↑ %@": {
"comment": "A label that shows the high temperature. The argument is the high temperature.",
"isCommentAutoGenerated": true
},
"↓ %@": {
"comment": "A label that reads \"low\" in the weather app.",
"isCommentAutoGenerated": true
},
"widget.weather.asOf": {
"comment": "%@ ist die Uhrzeit des Messwerts",
"localizations": {
@@ -42,6 +34,22 @@
}
}
},
"widget.weather.locationUndetermined": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Standort noch nicht erlaubt.\nIn den Einstellungen freigeben."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Location not allowed yet.\nGrant it in settings."
}
}
}
},
"widget.weather.name": {
"localizations": {
"de": {
@@ -74,18 +82,38 @@
}
}
},
"widget.weather.locationUndetermined": {
"↑ %@": {
"comment": "A label that shows the high temperature. The argument is the high temperature.",
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Standort noch nicht erlaubt.\nIn den Einstellungen freigeben."
"value": "↑ %@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Location not allowed yet.\nGrant it in settings."
"value": "↑ %@"
}
}
}
},
"↓ %@": {
"comment": "A label that reads \"low\" in the weather app.",
"isCommentAutoGenerated": true,
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "↓ %@"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "↓ %@"
}
}
}