Skip to content

V2: Configurability and formatting options#197

Draft
jacobras wants to merge 45 commits into
mainfrom
feature/2.0-configurability-and-formatting
Draft

V2: Configurability and formatting options#197
jacobras wants to merge 45 commits into
mainfrom
feature/2.0-configurability-and-formatting

Conversation

@jacobras

@jacobras jacobras commented Jul 2, 2026

Copy link
Copy Markdown
Owner

V2

The biggest update to the library yet. All changes are in the time section. Backwards compatible with v1, but with a slight behavioural fix: default rounding HalfUp is now consistent.

See updated README in this branch for the exact parameter names: https://github.com/jacobras/Human-Readable/blob/feature/2.0-configurability-and-formatting/README.md (or take a look at the tests)

DEMO

A live demo of 2.0.0 is available at https://www.jacobras.nl/humanreadable2/index.html (⚠️ not latest state)

TODO

  • Update translations

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

No large file size changes found

Generated by jacobras/size-diff-action

@rocketraman

Copy link
Copy Markdown

This test fails by falsely outputting an approximation word:

        assertThat(
            duration(
                duration = 1.days + 1.hours + 4.minutes,
                parts = PartsConfig(max = 5),
                formatting = FormatStyle(indicateApproximation = true),
            )
        ).isEqualTo("1 day, 1 hour, 4 minutes")

outputs:

Expected :1 day, 1 hour, 4 minutes
Actual   :about 1 day, 1 hour, 4 minutes

@jacobras jacobras mentioned this pull request Jul 6, 2026
@jacobras

jacobras commented Jul 6, 2026

Copy link
Copy Markdown
Owner Author

This test fails by falsely outputting an approximation word:

        assertThat(
            duration(
                duration = 1.days + 1.hours + 4.minutes,
                parts = PartsConfig(max = 5),
                formatting = FormatStyle(indicateApproximation = true),
            )
        ).isEqualTo("1 day, 1 hour, 4 minutes")

outputs:

Expected :1 day, 1 hour, 4 minutes
Actual   :about 1 day, 1 hour, 4 minutes

Thanks! Fixed in 49cbe51

@jacobras jacobras force-pushed the feature/2.0-configurability-and-formatting branch from 068285d to 790e5b8 Compare July 7, 2026 17:09
@jacobras jacobras requested a review from Copilot July 7, 2026 17:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces the v2 relative-time/duration formatting engine with extensive configurability (format styles, multi-part output, cutoffs, unit-limiting, rounding strategies, and date-based “today/tomorrow/yesterday”), and replaces the previous Libres-based localisation with an internal i18n implementation across supported platforms (including new tvOS/watchOS targets).

Changes:

  • Added configurable time formatting primitives (FormatStyle, PartsConfig, Rounding, new TimeUnit) and rewired HumanReadable.timeAgo() / duration() to use them (including a LocalDate overload).
  • Migrated localisation from Libres XML resources to internal Kotlin translation objects + platform systemLanguageTag() implementations.
  • Updated README, demo app UI, and build tooling/targets (tvOS/watchOS, additional native targets, Gradle/Compose updates).

Reviewed changes

Copilot reviewed 127 out of 128 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/webMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.web.kt Web implementation of systemLanguageTag() via browser navigator language.
src/mingwMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.mingw.kt Windows (mingw) implementation of systemLanguageTag() via Win32 locale APIs.
src/linuxMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.linux.kt Linux implementation of systemLanguageTag() via environment variables.
src/jvmTest/kotlin/nl/jacobras/humanreadable/i18n/LocalisationTest.kt JVM tests for localisation selection, fallback, and normalization.
src/jvmMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.jvm.kt JVM implementation of systemLanguageTag() from Locale.
src/jvmMain/kotlin/nl/jacobras/humanreadable/extendLibresPlurals.kt Removes JVM Libres plural workaround (Libres migration).
src/commonTest/kotlin/nl/jacobras/humanreadable/time/HumanReadableRelativeTimeTests.kt New common tests for v2 relative time behaviors (incl. today/tomorrow/yesterday).
src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableRelativeTimeTests.kt Removes old relative time tests that depended on Libres.
src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableNumberEdgeCaseTests.kt Updates tests to use new HumanReadable.config.languageTag.
src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableFileSizeTests.kt Updates tests to use new HumanReadable.config.languageTag.
src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableDurationTests.kt Removes old duration tests (v1 formatting logic).
src/commonTest/kotlin/nl/jacobras/humanreadable/HumanReadableAbbreviationTests.kt Updates tests to use new HumanReadable.config.languageTag.
src/commonMain/libres/strings/time_units_ar.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_cs.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_de.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_el.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_en.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_es.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_fi.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_fr.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_id.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_it.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_ja.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_kk.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_ko.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_nl.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_pl.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_pt.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_ru.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_tr.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_uk.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_uz.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_vi.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/time_units_zh.xml Removes Libres XML time strings (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_cs.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_de.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_el.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_en.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_es.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_fi.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_fr.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_id.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_it.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_nl.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_pl.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_pt.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_ru.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_tr.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_uk.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_uz.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/number_units_vi.xml Removes Libres XML number symbols (migration to Kotlin translations).
src/commonMain/libres/strings/distance_units_en.xml Removes Libres XML distance unit abbreviations (migration).
src/commonMain/libres/strings/distance_units_ar.xml Removes Libres XML distance unit abbreviations (migration).
src/commonMain/libres/strings/data_units_en.xml Removes Libres XML file-size unit symbols (migration).
src/commonMain/libres/strings/data_units_ar.xml Removes Libres XML file-size unit symbols (migration).
src/commonMain/libres/strings/data_units_fi.xml Removes Libres XML file-size unit symbols (migration).
src/commonMain/libres/strings/data_units_fr.xml Removes Libres XML file-size unit symbols (migration).
src/commonMain/kotlin/nl/jacobras/humanreadable/TimeUnit.kt Removes old Libres-backed TimeUnit (moved/refactored).
src/commonMain/kotlin/nl/jacobras/humanreadable/time/TimeUnit.kt New public TimeUnit with style-dependent tense/plural forms and rounding hooks.
src/commonMain/kotlin/nl/jacobras/humanreadable/time/Rounding.kt Adds rounding strategies (Floor, HalfUp, IfClose) for time formatting.
src/commonMain/kotlin/nl/jacobras/humanreadable/time/RelativeTime.kt Moves RelativeTime into the time package.
src/commonMain/kotlin/nl/jacobras/humanreadable/time/PartsConfig.kt Adds multi-part duration formatting configuration (max parts, cutoffs, etc.).
src/commonMain/kotlin/nl/jacobras/humanreadable/time/HumanReadableRelativeTime.kt New relative-time formatter for Instant and LocalDate including special day words.
src/commonMain/kotlin/nl/jacobras/humanreadable/time/HumanReadableDuration.kt New duration formatter with parts, unit-limiting, approximation/“less than”, and digital time.
src/commonMain/kotlin/nl/jacobras/humanreadable/time/FormatStyle.kt Adds date/time format styles and approximation indication options.
src/commonMain/kotlin/nl/jacobras/humanreadable/SafelyTranslate.kt Removes Libres-era translation crash guard helpers.
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ArStrings.kt Arabic Kotlin translations (time + file size + distance).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/CsStrings.kt Czech Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/DeStrings.kt German Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ElStrings.kt Greek Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/EnStrings.kt English Kotlin translations (default baseline).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/EsStrings.kt Spanish Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/FiStrings.kt Finnish Kotlin translations (time + number + file size).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/FrStrings.kt French Kotlin translations (time + number + file size).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/IdStrings.kt Indonesian Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ItStrings.kt Italian Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/JaStrings.kt Japanese Kotlin translations (time).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/KkStrings.kt Kazakh Kotlin translations (time).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/KoStrings.kt Korean Kotlin translations (time).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/NlStrings.kt Dutch Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/PlStrings.kt Polish Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/PtStrings.kt Portuguese Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/RuStrings.kt Russian Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/TrStrings.kt Turkish Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/UkStrings.kt Ukrainian Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/UzStrings.kt Uzbek Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ViStrings.kt Vietnamese Kotlin translations (time + number).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations/ZhStrings.kt Chinese Kotlin translations (time).
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/translations.kt Registers supported languages in a translation map.
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/TenseForms.kt Adds plural/tense forms model + DSL for translation authoring.
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.kt Declares expected systemLanguageTag() for platform implementations.
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/Plural.kt Defines CLDR plural category enum.
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/Localisation.kt Implements language selection, normalization, caching, and fallback behavior.
src/commonMain/kotlin/nl/jacobras/humanreadable/i18n/HumanReadableStrings.kt Defines the structured i18n string model (time/number/file size/distance).
src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableRelativeTime.kt Removes old Libres-based relative time formatter.
src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableNumber.kt Rewires number formatting to internal i18n symbols.
src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableFileSize.kt Rewires file size symbols to internal i18n strings.
src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableDuration.kt Removes old duration formatter (replaced by v2 time module).
src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadableDistance.kt Rewires distance unit abbreviations to internal i18n strings.
src/commonMain/kotlin/nl/jacobras/humanreadable/HumanReadable.kt Public API updates: new config, new overloads, v2 time formatting parameters.
src/commonMain/kotlin/nl/jacobras/humanreadable/extendLibresPlurals.kt Removes Libres plural workaround expect declaration.
src/commonMain/kotlin/nl/jacobras/humanreadable/Config.kt Adds global configuration container (HumanReadable.config) for language/time defaults.
src/appleMain/kotlin/nl/jacobras/humanreadable/i18n/systemLanguageTag.apple.kt Apple implementation of systemLanguageTag().
src/appleAndJsMain/kotlin/nl/jacobras/humanreadable/extendLibresPlurals.kt Removes Apple/JS Libres plural workaround implementation.
README.md Updates documentation for v2 formatting options and new i18n API.
karma.config.d/index.js Removes old Karma workaround script file.
gradle/wrapper/gradle-wrapper.properties Updates Gradle wrapper settings/version and wrapper retry config.
gradle/libs.versions.toml Updates Compose/KMP dependencies; adds adaptive + browser libraries and buildConfig plugin.
gradle.properties Adds project version property and keeps jscanvas flag.
demo/src/webMain/kotlin/ui/TimeDemo.kt Removes old demo time screen implementation.
demo/src/webMain/kotlin/ui/highlightKotlinCode.kt Adds Kotlin code highlighting utility for demo output.
demo/src/webMain/kotlin/ui/FlexibleLayout.kt Removes old flexible multi-demo layout.
demo/src/webMain/kotlin/ui/FeaturesList.kt Adds feature navigation list for demo scaffold.
demo/src/webMain/kotlin/ui/DistanceDemo.kt Removes old distance demo implementation (migrated to feature screens).
demo/src/webMain/kotlin/ui/DecimalsInput.kt Adds reusable decimals input control for demo screens.
demo/src/webMain/kotlin/ui/CodeExample.kt Adds reusable code+result display component (selectable) for demo.
demo/src/webMain/kotlin/ui/AbbreviationDemo.kt Removes old abbreviation demo implementation.
demo/src/webMain/kotlin/feature/TimeDemo.kt Adds new time demo feature screen showcasing configuration options.
demo/src/webMain/kotlin/feature/NumberFormatDemo.kt Moves/updates number demo to feature module + shared UI components.
demo/src/webMain/kotlin/feature/FileSizeDemo.kt Moves/updates file size demo to feature module + shared UI components.
demo/src/webMain/kotlin/feature/DistanceDemo.kt Adds new distance demo feature screen using shared UI components.
demo/src/webMain/kotlin/Feature.kt Adds demo feature enum for navigation.
demo/src/webMain/kotlin/App.kt Updates demo app scaffold, language switching, adaptive navigation, and version display.
demo/build.gradle.kts Adds BuildConfig generation and adaptive Compose dependencies for demo.
build.gradle.kts Removes Libres plugin config, adds new targets, adds web deps, and reads version from property.
.github/workflows/build.yml Minor workflow cleanup (comment removal).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +68 to +70
today = "اليوم",
tomorrow = "أمس",
yesterday = "غداً",
Comment on lines +16 to +20
var requestedLanguageTag = systemLanguageTag()
set(value) {
field = value.lowercase().substringBefore("-")
currentTagAndStrings = null
}
baseDate -> return strings.dateTime.today
baseDate.plus(1, DateTimeUnit.DAY) -> return strings.dateTime.tomorrow
}
val secondsAgo = date.daysUntil(baseDate) * 86_400
Comment on lines +77 to +83
if (format.time == FormatStyle.Time.Digital) {
if (!isEmpty()) append(", ")

val hours = (duration.inWholeHours % 24).toString().padStart(2, '0')
val minutes = (duration.inWholeMinutes % 60).toString().padStart(2, '0')
val seconds = (duration.inWholeSeconds % 60).toString().padStart(2, '0')
append("$hours:$minutes:$seconds")
Comment thread README.md
parts = PartsConfig(
max = 2, // "1 hour, 50 minutes"
smallestDuration = 10.minutes, // anything smaller will return "less than 10 minutes"
subpartCutOffs = mapOf(TimeUnit.Hours to 12.hours), // drops subparts, e.g. "11 hours, 40 minutes" and then "12 hours"
Comment thread README.md
subpartCutOffs = mapOf(TimeUnit.Hours to 12.hours), // drops subparts, e.g. "11 hours, 40 minutes" and then "12 hours"
onlyConsecutiveParts = true // whether "1 hour, 5 seconds" can be returned (as seconds are not the next unit after hours)
),
units = setOf(TimeUnit.Hours), // limits the output to these units, e.g. "391 days"
Comment on lines +202 to +211
CodeExample(
code = "rounding = Rounding.UpIfClose",
res = remember(selectedLanguageCode, now, myInstant) {
HumanReadable.timeAgo(
myInstant, now,
rounding = Rounding.IfClose()
)
},
inline = true
)
Comment on lines +39 to +42
* - `1.minutes + 5.seconds` remains "1 minute, 5 seconds";
* - `1.minutes + 4.seconds` gets rounded to "1 minute";
* - `1.minutes + 54 seconds` remains "1 minute, 54 seconds";
* - `1.minutes + 55 seconds` gets rounded to "2 minutes".
Comment thread README.md
onlyConsecutiveParts = true // whether "1 hour, 5 seconds" can be returned (as seconds are not the next unit after hours)
),
units = setOf(TimeUnit.Hours), // limits the output to these units, e.g. "391 days"
rounding = Rounding.HalfUp // or Floor to round down, or UpIfClose to round up on 55 seconds/55 minutes/23 hours/13 days
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate away from Libres

3 participants