Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Terbilang Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with composer require riskihajar/terbilang. Laravel's package auto-discovery registers the service provider and the Terbilang facade alias, so no manual wiring is needed on 5.5+.
  • Publish the config first: php artisan vendor:publish --provider="Riskihajar\Terbilang\TerbilangServiceProvider" (or --tag=terbilang). The generated config/terbilang.php is where locale, distance.show.*, and output formatting live.
  • Set the locale explicitly. Supported locales are en, id, and pt; the default is Indonesian, which is rarely what an English-language app wants. Put it in the config file rather than relying on app()->getLocale().
  • Reach for the facade or the helper in your first use case: an invoice total in words.
use Riskihajar\Terbilang\Facades\Terbilang;

Terbilang::make(1250000);        // "satu juta dua ratus lima puluh ribu" (id)
Terbilang::make(1250000)->toRoman();
number_to_words(42);             // if the helper is available in your version
  • First thing to verify in a REPL (php artisan tinker) is Terbilang::make(0), Terbilang::make(-5), and Terbilang::make(1.5). As of 2.1.1 these no longer crash, but the exact output shape is worth pinning before you write assertions against it.

Implementation Patterns

Currency amounts in documents. The dominant use case is "say the amount" on invoices, receipts, and contracts. Wrap it in a small presenter so you control the suffix and case:

class AmountInWords
{
    public static function id(int $amount, string $currency = 'Rupiah'): string
    {
        return ucfirst(Terbilang::make($amount)) . ' ' . $currency;
    }
}

Model accessors and API resources. Expose the spelled-out form alongside the numeric field instead of calling the facade in Blade. This keeps the locale decision in one place and makes it testable:

protected $appends = ['total_in_words'];

public function getTotalInWordsAttribute(): string
{
    return Terbilang::make($this->total);
}

Per-request locale switching. The package reads its own config value, not the app locale, so switching languages for a single request means overriding config at runtime — ideally in middleware, and ideally restored afterwards:

config(['terbilang.locale' => $request->user()->locale]);

Wrap this in a middleware rather than scattering config([...]) calls through controllers; the global config mutation is easy to leak between queued jobs and Octane workers.

Roman numerals for numbering. toRoman() is useful for chapter/section numbering in generated PDFs and appendices. Since 2.1.1 it throws Riskihajar\Terbilang\Exceptions\InvalidNumber outside 1–3999, so guard at the call site:

if ($n >= 1 && $n <= 3999) {
    $label = Terbilang::make($n)->toRoman();
}

Distance / relative time. The distance feature renders a human-readable gap (e.g. "2 years, 3 months ago") from a date or interval. Use it for "member since" and "last updated" labels, but treat it as display-only — it is not a substitute for Carbon's diff API when you need exact values.

Testing. With 271 tests across all three locales in 2.1.1, the upstream behaviour is now well pinned — but your own snapshot tests will break when translation typos are fixed (see below). Assert on exact strings for the languages you ship, and use assertMatchesRegularExpression on a looser pattern for the rest.

Gotchas and Tips

  • toRoman() now throws instead of returning ''. This is the only behavioural break in 2.1.1. Any if ($roman === '') fallback you wrote for out-of-range values is now dead code, and unguarded calls for values <= 0 or >= 4000 will throw. Audit for toRoman() before upgrading.
  • Distance config key was renamed. terbilang.period.show.* → terbilang.distance.show.*. If you published config/terbilang.php on 2.1.0, your copy still has the old key and your show toggles are silently doing nothing. Republish and diff, or rename the block by hand. After any config edit, run php artisan config:clear — config caching is the usual reason a key change "doesn't work".
  • Translation strings changed in 2.1.1. English fourty → forty, Indonesian Nopember → November, and the Portuguese abbreviation key short → large-number. Any test asserting on those exact strings, or an override file you copied from the package, needs updating. If you have published translation files under lang/vendor/terbilang, they will still contain the old spellings until you re-copy them.
  • Distance formatting changed. Hours and minutes are now accumulated correctly (previously dropped), and type() no longer emits a leading separator. Both are bug fixes but both change output — expect diff churn in PDF fixtures.
  • Negative numbers. A double space in negative output was removed, and LargeNumber no longer crashes on zero or negative input. If you were previously catching exceptions or trimming whitespace as a workaround, remove those shims.
  • Floats are the sharpest edge. Round before calling. Terbilang::make(1234.567) is unlikely to produce what you want; do Terbilang::make((int) round($amount)) for money, and decide explicitly how you handle the cents remainder.
  • Locale is config, not App::getLocale(). Setting app()->setLocale('en') will not change the package's output. This is the single most common source of "the translation isn't working" reports.
  • Extension point: rather than post-processing the returned string with str_replace, override the translation source (publish and edit the vendor language files) so every call site benefits. String surgery on the output breaks silently the next time a package translation is fixed.
  • Debugging: if output looks stale after an upgrade, check bootstrap/cache/config.php and composer dump-autoload before assuming the release is broken — several of the 2.1.1 fixes are config-driven and invisible under a cached config.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource