riskihajar/terbilang
A Laravel/PHP package for converting numbers into Indonesian words (terbilang). Useful for invoices, receipts, and documents that need amount-in-words output, with helpers and facade support for quick integration.
Scope of the release. 2.1.1 is nominally a patch release: no new public API, no new locales, no deprecations announced. Content is nine bug fixes plus a test suite expansion from 34 to 271 tests. The functional surface is unchanged, which keeps the integration blast radius small — but two of the fixes are behavior-changing rather than purely cosmetic, and one of them ships in a patch version. That semver hygiene point is the main thing to manage with stakeholders.
Architecture fit. This is a leaf, presentation-layer dependency: number → localized words conversion (Indonesian terbilang), plus distance/date phrasing, Roman numerals, and large-number abbreviation. It is config-driven, translation-file-driven, and has no database, queue, cache, or network surface. That is close to the ideal shape for a formatting utility — it should never appear in a domain model or a persistence path, only at rendering boundaries (Blade views, API resources, PDF/invoice document generation, notification templates). Confirm in review that no consumer has wrapped it in a domain service that is now load-bearing; if it has, treat the upgrade as higher risk than the release notes suggest.
Behavioral changes to treat as breaking for consumers:
| Change | Impact | Consumer action |
|---|---|---|
Roman now throws InvalidNumber outside 1–3999 (was: empty string) |
Any caller that relied on falsy/empty-string handling for out-of-range input will now get an uncaught exception at runtime — i.e. a 500 in a report or document-generation path | Grep for Roman/roman usage, wrap in try/catch or pre-validate range |
Config key terbilang.period.show.* → terbilang.distance.show.* |
Previously published config files in consumer apps retain the old key; whether this silently falls back to defaults or produces wrong output depends on the config access path | Republish/diff the config file; clear config:cache |
Portuguese translation key short → large-number |
Only bites apps that override or reference the pt translation key directly | Update overrides |
LargeNumber zero/negative crash fixed; DistanceDate accumulation fixed |
Strictly improvements, but outputs change on edge inputs — snapshot tests may need rebaselining | Rebaseline golden-file tests |
Technical risk. Low-to-moderate. Execution risk of the upgrade itself is low (small, well-tested surface). The real risks are (a) the silent-config-key class of failure, which produces plausible-but-wrong output rather than an error, and (b) the Roman exception, which fails loudly in exactly the batch/document paths this package tends to sit in. The test expansion from 34 to 271 tests — including previously uncovered DistanceDate and full en/id/pt locale coverage — is a material maturity improvement and substantially raises confidence in the fixes themselves. It does not raise confidence in consumer-side compatibility, which only a call-site audit can establish.
Key questions for the maintainer / to answer in the upgrade review:
Roman throw considered a patch-level change? Is there any deprecation window or opt-in flag, or is the 1–3999 contract now permanent?period.show key, or is the old key simply dead? Does a stale published config fail silently or loudly?Stack fit. Conventional Laravel package: service provider auto-discovery, facade, publishable config, translation namespaces, and (typically) Blade-friendly helpers. Fits any Laravel app that emits Indonesian- or Portuguese-language financial or legal documents — Indonesian invoices and receipts conventionally require the amount spelled out, which is the primary use case. Also fits non-Laravel PHP via Composer if the service provider is bypassed, though that is not the supported path. Suitability is high; the package is doing narrow, deterministic work with no infrastructural demands.
Migration path (2.1.0 → 2.1.1).
composer require riskihajar/terbilang:2.1.1.php artisan vendor:publish --tag=... --force into a temp directory and diff against the app's published terbilang.php — reconcile the distance.show rename and any other drift explicitly rather than blanket-overwriting.php artisan config:clear && php artisan config:cache and php artisan view:clear; translation caches too if the app caches them.Roman, terbilang.period, terbilang.distance, and any pt locale overrides.DistanceDate and LargeNumber edge cases.Compatibility notes. No database migrations, no env var changes, no interface changes. The only compatibility surface is the config array and the Roman exception contract. Non-Laravel or heavily customized installs that vendored the translation files will not pick up the fourty/Nopember/November and pt-key fixes automatically — they must re-copy.
Sequencing. Recommend: audit + upgrade in a low-traffic window (this is a patch, no feature work to coordinate), then a QA pass focused on document generation with edge inputs (0, negative, 3999, 4000, decimals, Indonesian meridiem/time-of-day labels), then production. Because the output changes are localized to number/date phrasing, a single release train is sufficient — no need for a staged roll-out or feature flag, provided the Roman call-site audit is clean. If the audit finds Roman in a synchronous document-generation path with unvalidated user input, gate that call site behind validation before deploying rather than delaying the whole upgrade.
Rough effort. Half a day to a day for upgrade plus call-site audit for a typical app; one to two days of QA if document-generation paths are in scope. Effort is dominated by verification, not by the change itself.
Maintenance. The 8x test-suite expansion and the addition of coverage for a previously untested subsystem are the strongest signals here: the maintainer is investing in correctness rather than accruing features, and the fixes target real field bugs (zero/negative crashes, date accumulation, broken locale strings) that suggest active production use. Counterweight: this is a single-maintainer community package with no stated LTS, security policy, or release cadence. Recommend pinning to an exact version, tracking upstream releases, and recording the current version in the dependency inventory so drift is visible.
Support. Upstream support is GitHub issues only — no vendor SLA. For a formatting utility that is acceptable, but it means the team owns workarounds. Practical mitigation: keep a thin internal adapter/wrapper around the package's public entry points so a future swap (or a maintained fork) is a one-file change rather than a codebase-wide refactor. This is worth doing regardless of this release.
Scaling. Negligible runtime cost — CPU-bound string assembly with no I/O. Only scaling consideration is volume: high-throughput document generation (thousands of PDFs per batch) should format once and cache the output string alongside the immutable amount, rather than reconverting per render. No memory, connection-pool, or concurrency concerns.
Failure modes to monitor:
InvalidNumber from Roman on out-of-range input — surfaces as a 500 in document/report generation. Add input validation or a try/catch at the boundary and alert on it in the first week post-deploy.period.show config key or stale vendored translation files producing correct-looking but wrong localized output (fourty, Nopember, missing pt abbreviation). This is the more dangerous class — it won't page anyone. Mitigate with a handful of assertion tests on known outputs per locale, run in CI.DistanceDate separator and accumulation fixes change output strings; any downstream string matching or snapshot tests will flag them.Ramp-up. Low for any Laravel developer — facade plus config, learnable in under an hour. The non-obvious cost is domain verification: correct Indonesian spelled-out numbers have rules (e.g. sebelas vs satu belas, seratus vs satu ratus) that a non-speaker cannot validate by inspection. Route the QA sign-off through a native Indonesian (and Portuguese) speaker, or at minimum a fixed table of expected outputs reviewed by one, and keep that table as the regression suite. Budget roughly half a day of onboarding plus that review.
Recommendation. Adopt 2.1.1. The test-coverage jump and the crash fixes outweigh the two behavior changes, and the changes are cheap to audit in a codebase of normal size. Treat it as an upgrade-with-audit rather than a routine patch: verify the Roman call sites, reconcile the published config, and re-copy translations if vendored.
How can I help you explore Laravel packages today?