Commit graph

1383 commits

Author SHA1 Message Date
f53c838a85 Renumber display-size proto fields to fill 16-18 gap 2026-05-28 19:19:33 +02:00
6cef3fbce7 Switch display size to five symmetric levels
The discrete tick-slider UI lands Default at the slider's mid-point,
which matches how every other size control on a TV (volume, brightness,
font-size sliders) works. The previous four-level asymmetric layout
(two shrink steps, one growth step) was a discrete-picker decision
that didn't translate well once the UI became a slider: at four slots
Default sat at slot 2 of 4, off-centre.

DisplaySizeLevel proto enum gains EXTRA_LARGE = 4. The Kotlin enum
ordering already iterates UNRECOGNIZED last so existing UNRECOGNIZED
clamps still work. The string-array and individual level string
resources gain the Extra large entry.

Percentages are re-tuned to be symmetric around Default with growth
headroom slightly larger than shrink headroom (matches the demand
signal from the upstream discussion thread):

- Text:    XS 82  S 92  Default 100  L 110  XL 120
- Card:    XS 78  S 90  Default 100  L 115  XL 130
- Spacing: XS 75  S 88  Default 100  L 118  XL 135

The scaledSp lookup is regenerated for the new percentages and gains
an XL column. The formula fallback (rounded integer math) handles any
base sp not in the table.

Tests assert the new Card LARGE (115%) and Spacing LARGE (118%)
percentages against the dp math.
2026-05-28 14:23:20 +02:00
8ff97daf44 Display size page polish: tick-slider control, typography lock, richer preview
Settings panel layout flipped: preview now occupies the left two-thirds
of the page (nav rail at the natural app-left position), settings panel
sits on the right at 300dp wide. Mirrors the real home-screen layout
so the preview reads as a true reflection of what the user will see.

Replaces the popup ChoicePreference selector with an inline tick-slider
per control. Each slider is a chunky 24dp rounded track using
surfaceVariant for the inactive background and primary for the thumb;
the thumb is a rounded pill that snaps to one of four slot centers and
fills 88% of the track height. D-pad LEFT/RIGHT cycles slots within the
focused control; UP/DOWN moves between controls. Selection commits on
focus change so the preview updates without an extra Enter press.

Wraps the settings panel in a nested MaterialTheme using the unscaled
AppTypography (both tv.material3 and compose.material3 flavors) so the
panel's own labels stay at base size regardless of the Text size level
the user picks. Without this nesting the panel chrome reflowed under
the user's fingers as they cycled levels.

Preview now uses SlimItemFields when fetching, so the Overview text
populates on the hero. Episodes row is rendered first (matching the
real home-screen ordering) so the hero defaults to an episode item,
which exercises the full hero surface (title + episode name + overview
+ quick details). TimeDisplay is drawn over the preview area when the
user's showClock preference is enabled.

Text-size jumps are tuned wider: ExtraSmall 84% (was 88), Small 92%
(was 94), Large 116% (was 108). Default stays at 100%. The hand-mapped
sp lookup is regenerated for these percentages.

scaledSp falls back to integer-rounded formula math instead of throwing
on unmapped base sp values, so the helper is safe to call from any
fontSize site without first extending the table.
2026-05-28 13:41:54 +02:00
ca8ed52cf0 Scale hardcoded fontSize sites by Text size level
Adds a scaledSp(baseSp: Int) composable helper that returns a TextUnit
scaled by the active text-size level. The lookup is a hand-mapped
DisplaySizeLevel.scaledSp(Int) table for the known base sp values
(integer outputs at every cell, no fractional sp). Unknown base sp
values fall back to integer-rounded percent math instead of throwing,
so the helper is safe to call from anywhere.

scaledSp reads from LocalInterfaceCustomization.current.textSizeLevel
so the same composition local that already carries the user's interface
preferences also drives text scaling.

33 hardcoded fontSize = N.sp call sites across cards, components,
playback overlay, nav, setup, and livetv go through scaledSp. This
covers both plain text and FontAwesome icon-glyph sites: at the Default
level the lookup returns the exact base sp, so today's rendered sizes
are byte-identical. PlaybackController's file-level titleTextSize and
subtitleTextSize constants are inlined as scaledSp(28) and scaledSp(18),
keeping the rendering and PlaybackOverlay's height-offset math in lockstep
at every level.

The v1 scaledDensity helper in InterfaceHelpers.kt and its tests are
deleted - they had no callers after MainContent's density override was
removed. The InterfaceCustomization derived-getter doc comment is
retuned to v2-valid percent values.
2026-05-28 12:49:55 +02:00
f039be0495 Scale TV typography by Text size level; mirror to Material theme
WholphinTheme takes a new textSizeLevel parameter (defaults to DEFAULT).
AppTypography stays the TV Material3 default; scaledBy(level) returns a
TvTypography with each role's fontSize and lineHeight multiplied by the
level's textPercent (identity at DEFAULT). toMaterialTypography() copies
each role's TextStyle into a compose.material3.Typography so the inner
compose.material3.MaterialTheme inside WholphinTheme is fed from the same
source. Both wraps now scale uniformly, and any compose.material3
component reading MaterialTheme.typography.X now matches the TV M3 size
for X.

MainActivity and WholphinDreamService pass the level from prefs. Preview
call sites in cards/playback fall through to DEFAULT and render identically
to before.
2026-05-28 11:46:02 +02:00
c9433fbf57 Swap percent fields for DisplaySizeLevel enum end-to-end
The three v1 free sliders (UI scale, Card size, Spacing) become two
discrete enum-based controls (Card size, Spacing) and a new Text size
control, each selecting one of four DisplaySizeLevel values
(Extra small / Small / Default / Large). UI scale is dropped entirely;
the density override at the MainContent root goes with it.

Proto: replaces ui_scale_percent / card_size_percent / spacing_percent
with text_size_level / card_size_level / spacing_level on InterfacePreferences.
DisplaySizeLevel enum is ordered DEFAULT = 0 so proto-zero-default lands
new installs and any unset upgrader on DEFAULT for free; no upgrade-reset
block needed, no serializer default-setting needed.

AppPreference UiScale / CardSize / Spacing slider objects become three
AppChoicePreference<AppPreferences, DisplaySizeLevel> entries
(TextSize, CardSize, Spacing). DisplaySizeLevels.kt holds the user-facing
display order and the three per-control percent-mapping extensions
(textPercent / cardPercent / spacingPercent).

InterfaceCustomization stores the three level fields and exposes
cardSizePercent / spacingPercent as derived getters off the levels, so
every consumer of cardHeightDp / episodeCardHeightDp / spacingDp stays
unchanged.

DisplaySizePage keeps the preview infrastructure (sample-item viewmodel,
HomePageContent-based canvas, fake nav rail, two-column layout) and
swaps the slider column for three ChoicePreference selectors bound to
the new AppPreferences. The density-override wrap on the preview pane
is removed.

InterfaceHelpersTest withinBoundsOrDefault now exercises SkipForward
since CardSize / Spacing are no longer AppSliderPreference. The two
InterfaceCustomization scaling tests assert against cardSizeLevel = LARGE
(116%) and spacingLevel = LARGE (125%).
2026-05-28 11:26:27 +02:00
b6c4f0dd4f Apply the global Card slider to preset-set rows too
resolveCardHeight now stacks the preset base, the global Card percent,
and the per-row multiplier: effective = base * globalCardSizePercent
/ 100 * cardSizeMultiplier / 100. Before this commit, a row with an
absolute preset heightDp (Favorite-Episode at 148, live-TV at 96, etc.)
was insulated from the global slider - the user had to drag the
per-row Card slider on that specific row to scale it, which made the
global slider feel inconsistent. Now the global reaches every row;
preset rows still get to set their own anchor and the per-row
multiplier still adjusts them independently.

Signature change: globalCardHeightDp (a pre-scaled dp value) is
replaced by globalCardSizePercent (raw percent) so the resolver can
apply the global multiplication itself, and baseCardHeightDp is
renamed to naturalCardHeightDp since it now serves both as the
fallback base and as the no-override sentinel. The composable caller
(resolvedCardHeight) drops its branch to pick between cardHeightDp and
episodeCardHeightDp - it just passes cardSizePercent through.

Tests cover preset compounding (148 * 150% * 100% = 222; 148 * 50% =
74; 96 * 125% = 120) plus the existing natural-base, sentinel, and
per-row override cases retargeted at the new signature.
2026-05-28 10:05:21 +02:00
0b61007b3f Extract naturalCardBaseDp and scaledDensity helpers so tests bind to real code
resolvedCardHeight previously routed through a two-branch if(isWide)
hidden inside the composable extension, so the regression tests for
the global-slider reach bug only exercised the inner resolveCardHeight
(which was never broken). Lift the aspect -> natural base mapping
into a pure naturalCardBaseDp(AspectRatio) function with an exhaustive
when, and test that directly. SQUARE and FOUR_THREE rows piggyback on
the TALL global by design; the override branch still handles their
absolute heightDp values (Music presets at 100dp, etc.).

MainContent and DisplaySizePage's preview now both call a single
scaledDensity(base, percent) helper instead of inlining Density(...)
constructions. The double-fontScale-multiply regression has a real
guard now: tests assert scaledDensity(2.0, 1.0, 150) returns density
3.0 and fontScale 1.0 (not 1.5), and a third test confirms an
accessibility fontScale survives a UI scale change.

Pure refactor + test sharpening; no behaviour change.
2026-05-28 10:05:21 +02:00
71e74949b7 Fix global slider reach + UI-scale font math + per-type grid spacing
resolvedCardHeight now picks the row's natural base (128 for WIDE /
episode aspect, 172 for TALL / poster aspect) via HomeRowViewOptions
.aspectRatio and selects the matching scaled global (cardHeightDp or
episodeCardHeightDp) from InterfaceCustomization. Previously the
function always passed HEIGHT_2X3_DP as the natural base, so every
row storing heightDp = HEIGHT_EPISODE = 128 hit the override branch
and the global Card slider silently skipped episode-aspect home rows
(Favorite-Episode preset, genreDefault rows, etc.).

MainContent and DisplaySizePage's preview Density no longer multiply
fontScale by the UI-scale factor. sp rendering pipes through
density * fontScale, so scaling both produced factor^2 text size -
slider 150% rendered fonts at 2.25x while layouts rendered at 1.5x.
Scaling density alone makes both linear; the user's system fontScale
stays applied on top.

ItemGrid, CollectionFolderGrid, CollectionMixedGrid, and HomeRowGrid
now honor the per-type viewOptions.spacing base (16/24/16 for the
three view-options presets) the same way CollectionFolderList already
did - scaled by the global Spacing percent at the consumer site.
Before this fix only the list-mode consumer honored the per-type base
and grids fell back to a fixed 16dp baseline regardless of preset.

Also wires ViewMoreCard's default size into cardHeightDp and
QueueRowOverlay's queue-thumbnail height into episodeCardHeightDp so
those two surfaces scale too. Adds a Why: comment on the
migrateLegacyRowSpacing clamp (50..150 per-row range, intentionally
narrower than the global 25..175). Drops a couple of restating //
comments in DisplaySizePage and imports java.util.EnumSet at the top
of InterfaceHelpersTest. New unit tests cover resolveCardHeight's
episode-natural-base path, per-row override precedence over the
global, and the linear-density invariant guarding against the
fontScale bug regressing.
2026-05-28 10:05:21 +02:00
9f90885b4c Wire Search episode-row into the Card size slider
The actual EPISODE_ROW result row on SearchPage was missing the
LocalInterfaceCustomization.current.episodeCardHeightDp.dp read that the
adjacent ALBUM/ARTIST/SONG rows already had, so the Card size slider
did not affect Search episode results. Drop the extra
padding(horizontal = 8.dp) at the same call site so the row's
leading-edge alignment matches the other ItemRow consumers after the
horizontalPadding/cardSpacing split in 66b58ada.

Also fix EpisodeCard so the requested image size matches the rendered
card: previously imageWidth = Dp.Unspecified was being roundToPx()'d
into a junk value and passed as fillWidth, and fillHeight was never
plumbed through at all. Mirror the SeasonCard pattern - null when the
input dp is Unspecified, otherwise the pixel value - so the Jellyfin
server scales the image to the real card dimensions. Source images
that are themselves low-resolution (auto-extracted episode PRIMARY)
will still look soft when the slider is at 150%, but well-curated
episode thumbnails now render sharply.
2026-05-28 10:05:21 +02:00
f9c0da2e08 Add unit tests and migrate legacy per-row spacing on decode
Restores TestHomeRowSamples.kt (sample-data + sanity tests that
survived an earlier soft-reset on disk but never got staged). Updates
the RecentlyAdded sample to use the new cardSizeMultiplier and
spacingMultiplier fields.

Adds InterfaceHelpersTest.kt covering:
- Int.withinBoundsOrDefault (in-range, below-min, above-max, boundary
  equality) for both CardSize and Spacing preferences.
- resolveCardHeight (newly extracted pure helper underneath the
  composable resolvedCardHeight): sentinel 0, legacy 172, explicit
  per-row override, multiplier path.
- InterfaceCustomization.cardHeightDp / spacingDp math.
- migrateLegacyRowSpacing: absent / default / non-default / snapping /
  clamping / does-not-overwrite-existing-multiplier.

Adds migrateLegacyRowSpacing in HomeSettingsService and wires it into
decode(): when a row's raw JSON still carries the legacy "spacing"
field (from before this PR deleted the absolute spacing data field),
translate the value into the new spacingMultiplier so users keep the
same visual gap after upgrade.

Extracts resolveCardHeight as a pure helper from the @Composable
resolvedCardHeight so the resolution math is testable without the
Compose runtime.
2026-05-28 10:05:21 +02:00
1f9188abc0 Restore per-row Card size and Spacing as multipliers; drop legacy spacing field
Per-row card size and spacing return to HomeRowSettings, reframed as
percent multipliers that compound with the global Display Size sliders
instead of the old absolute heightDp / spacing values. Display Presets
continue to set absolute heightDp for special cases (Favorite Episode,
Compact, etc.); the multiplier applies on top of whatever base is in
effect for that row.

HomeRowViewOptions:
- Drops the unused absolute `spacing: Int = 16` field.
- Adds `cardSizeMultiplier: Int = 100` and `spacingMultiplier: Int = 100`.
- `heightDp` stays as the per-row absolute set by Display Presets.

resolvedCardHeight() in InterfaceHelpers now layers the multiplier on
top of the existing per-row vs global resolution.

HomeRowSettings exposes Card size and Spacing sliders for each row
(range 50-150, step 5, default 100) under "General". Genre rows keep
their stripped-down options list and skip both. On HomePage, the
per-row spacingMultiplier compounds with the global Spacing slider for
that row's inter-card gap.
2026-05-28 10:05:21 +02:00
3060887756 Fix Card size slider for users with legacy stored heightDp
HomeRowViewOptions is serialized to the user's Jellyfin server.
Existing users have rows with heightDp == 172 (the pre-PR default)
persisted upstream. Without this guard, resolvedCardHeight() would
treat 172 as a per-row override and the Card size slider would do
nothing on the home page until the user explicitly reset their rows.

Treat the legacy 172 value as "no override" so users coming from older
builds see the slider take effect immediately on home rows.
HEIGHT_2X3_DP is now the base value the global multiplier scales
from; no preset sets heightDp to exactly 172. HEIGHT_EPISODE (128)
and other preset values remain valid per-row overrides because they
are not equal to 172.
2026-05-28 10:05:21 +02:00
9fc46a80b4 Route card height and spacing through global preferences
Replaces hardcoded card heights and the per-library Spacing slider
with reads from LocalInterfaceCustomization. Home rows split the
previous single horizontalPadding arg into a fixed leading-edge
contentPadding (BASE_SPACING_DP.dp) and a slider-driven cardSpacing.
CardGrid grows a contentPadding so its leading edge stays flush
regardless of the spacing slider.

The ViewOptionsSpacing slider in ViewOptionsDialog (grid + list) and
CollectionViewOptionsDialog is removed - the global Spacing slider
covers all browse-density tuning. ViewOptions.spacing remains as a
type-driven base (16/4/2 dp for GRID/LIST/DENSE_LIST, set by the type
setter) and is multiplied by the global Spacing percent at the
list-view consumer site. The Increase / Decrease all card-size
shortcuts in HomeSettingsGlobal are also removed (superseded by the
global Card size slider).

Episode-card heights also scale with the global Card size slider via
a new InterfaceCustomization.episodeCardHeightDp helper. Direct
consumer sites (ExtrasRow, MovieDetails similar-items, PersonPage
episode row, SearchPage EPISODE_ROW results) read it. Preset-set
absolute heights (Favorite-Episode preset, etc.) stay as overrides.

Adds HomeRowViewOptions.resolvedCardHeight() to InterfaceHelpers.kt -
the composable resolver bridging per-row heightDp overrides and the
global slider. HomeRowViewOptions.heightDp default flips from
Cards.HEIGHT_2X3_DP (172) to 0, the new sentinel meaning "use the
global value".
2026-05-28 10:05:21 +02:00
8fb301c2bb Add Display size customization page
Consolidates UI sizing into three percentage sliders surfaced through a
new Settings -> Interface -> Display size page: UI scale (50-150%),
Card size (50-150%), Spacing (25-175%). Each persists immediately via
DataStore; the rest of the app reflects them through
LocalInterfaceCustomization and a root-level LocalDensity override in
MainContent.

The page mirrors HomeSettingsPage's two-column shell: a fixed-width
settings panel on the left, HomePageContent rendering live preview rows
on the right. The page snapshots its own density on entry so the slider
rail stays fixed while UI scale changes the rest of the app.

Includes a one-shot AppUpgradeHandler migration block for installs at or
before 0.6.4-31-g0 that resets the three new percent fields to their
defaults.
2026-05-28 10:02:08 +02:00
Damontecres
81e5f31401
Workaround for resource shrinking w/ split ABIs 2026-05-27 13:56:18 -04:00
Damontecres
30837d0a9a
Release v1.0.0 2026-05-27 13:05:32 -04:00
Damontecres
1da72c5df2
Translations as of 2026-05-27 (#1471)
Updates translations as of 2026-05-27

Thank you to all of the contributors!
2026-05-27 13:05:18 -04:00
Damontecres
967295fd5e
Fix linting 2026-05-27 12:56:46 -04:00
Outbreak2096
428f58b1e3
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (575 of 575 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/
2026-05-27 12:53:18 -04:00
idezentas
64d6579cae
Translated using Weblate (Turkish)
Currently translated at 100.0% (575 of 575 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/
2026-05-27 12:53:18 -04:00
Priit Jõerüüt
40b080ce32
Translated using Weblate (Estonian)
Currently translated at 100.0% (575 of 575 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/
2026-05-27 12:53:18 -04:00
danpergal84
696af7b39d
Translated using Weblate (Spanish)
Currently translated at 100.0% (575 of 575 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/
2026-05-27 12:53:18 -04:00
thotranhuu99
0dbf172742
Added translation using Weblate (Vietnamese) 2026-05-27 12:53:18 -04:00
TheMatrixan
c5fc26bbff
Translated using Weblate (Polish)
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pl/
2026-05-27 12:53:17 -04:00
opakholis
445ed91275
Translated using Weblate (Indonesian)
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/id/
2026-05-27 12:53:17 -04:00
danpergal84
fb8b516239
Translated using Weblate (Spanish)
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/
2026-05-27 12:53:17 -04:00
JoschiGrey
caa0e769da
Translated using Weblate (German)
Currently translated at 99.8% (572 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/
2026-05-27 12:53:17 -04:00
idezentas
a29dcd2ea0
Translated using Weblate (Turkish)
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/
2026-05-27 12:53:17 -04:00
Outbreak2096
8271d7a486
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/
2026-05-27 12:53:17 -04:00
TheMatrixan
4ac2024318
Translated using Weblate (Polish)
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pl/
2026-05-27 12:53:17 -04:00
SimonHung
08a0f3f247
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/
2026-05-27 12:53:17 -04:00
ymir
e6e5502cdc
Translated using Weblate (French)
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/fr/
2026-05-27 12:53:17 -04:00
Priit Jõerüüt
f2b87c9e36
Translated using Weblate (Estonian)
Currently translated at 100.0% (573 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/
2026-05-27 12:53:16 -04:00
nofkclue
8e124846a3
Translated using Weblate (German)
Currently translated at 99.6% (571 of 573 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/
2026-05-27 12:53:16 -04:00
Priit Jõerüüt
99560439ad
Translated using Weblate (Estonian)
Currently translated at 100.0% (570 of 570 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/
2026-05-27 12:53:16 -04:00
danpergal84
8618fbc6e6
Translated using Weblate (Spanish)
Currently translated at 99.8% (569 of 570 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/
2026-05-27 12:53:16 -04:00
idezentas
9d6197671e
Translated using Weblate (Turkish)
Currently translated at 100.0% (570 of 570 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/
2026-05-27 12:53:16 -04:00
SimonHung
dcb71a48fd
Translated using Weblate (Chinese (Traditional Han script))
Currently translated at 100.0% (569 of 569 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/
2026-05-27 12:53:16 -04:00
TheMatrixan
3f5ffdb865
Translated using Weblate (Polish)
Currently translated at 100.0% (569 of 569 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pl/
2026-05-27 12:53:16 -04:00
idezentas
96e670cc17
Translated using Weblate (Turkish)
Currently translated at 100.0% (569 of 569 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/
2026-05-27 12:53:16 -04:00
Outbreak2096
5543a78421
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (569 of 569 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/
2026-05-27 12:53:16 -04:00
Priit Jõerüüt
4580b2598a
Translated using Weblate (Estonian)
Currently translated at 100.0% (569 of 569 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/
2026-05-27 12:53:16 -04:00
danpergal84
8f6ce83cd3
Translated using Weblate (Spanish)
Currently translated at 99.8% (568 of 569 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/
2026-05-27 12:53:15 -04:00
Timer
25bf0ee574
Translated using Weblate (German)
Currently translated at 98.2% (559 of 569 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/
2026-05-27 12:53:15 -04:00
mashu
da685142a8
Translated using Weblate (Serbian)
Currently translated at 1.2% (7 of 566 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sr/
2026-05-27 12:53:15 -04:00
idezentas
e64ab11a2e
Translated using Weblate (Turkish)
Currently translated at 100.0% (566 of 566 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/
2026-05-27 12:53:15 -04:00
Outbreak2096
8c20c57595
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (566 of 566 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/
2026-05-27 12:53:15 -04:00
Geekice
debc810cd3
Translated using Weblate (Italian)
Currently translated at 99.4% (563 of 566 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/
2026-05-27 12:53:15 -04:00
Priit Jõerüüt
19912b9e0c
Translated using Weblate (Estonian)
Currently translated at 100.0% (566 of 566 strings)

Translation: Wholphin/Wholphin
Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/
2026-05-27 12:53:15 -04:00