An International Tip Calculator — Because Tipping Customs Are Wildly Different
In the US, 18% is the minimum you can leave without feeling guilty. In Japan, handing a waiter cash is confusing at best and insulting at worst. In Germany, you round up to the nearest euro. In China, it's technically illegal in some hotels. A tip calculator needs to know which country you're in to give useful advice — not just a number.
Tipping is one of those travel quirks that sneaks up on you. This calculator has presets for 14 countries with culturally-appropriate tip ranges and explanations in both Japanese and English.
🔗 Live demo: https://sen.ltd/portfolio/tip-calc/
📦 GitHub: https://github.com/sen-ltd/tip-calc
Features:
- 14 country presets (US, JP, DE, FR, GB, IT, ES, AU, CA, CN, IN, AE, KR, BR)
- Currency-aware formatting via Intl.NumberFormat
- Country-specific tip ranges and cultural notes
- Round up to nearest currency unit
- Even split among N people
- Japanese / English UI
- Dark / light theme
- Zero dependencies, 33 tests
Country presets
export const COUNTRIES = [
{
code: 'US',
name: { en: 'United States', ja: 'アメリカ' },
currency: 'USD',
locale: 'en-US',
typicalTip: [15, 20],
defaultTip: 18,
note: {
en: '15-20% is standard. Even bad service gets 10%+. Bars: $1-2 per drink.',
ja: '15-20% が標準。悪いサービスでも 10% 以上が慣例。バーは $1-2/ドリンク',
},
},
{
code: 'JP',
name: { en: 'Japan', ja: '日本' },
currency: 'JPY',
locale: 'ja-JP',
typicalTip: [0, 0],
defaultTip: 0,
note: {
en: 'No tipping. Attempting to tip is often refused and may be seen as rude.',
ja: 'チップ文化なし。渡そうとすると困惑される',
},
},
// ... 12 more
];
Each country has its own locale for number formatting, its own default tip (zero for Japan), and a note explaining the local custom. The note is the most important field — a traveler who sees "US: 18%" and "Japan: 0%" learns something that actually affects their next meal.
Intl.NumberFormat for currency
Different currencies have different conventions. JPY has no decimal places. USD uses comma thousands separators. EUR uses period as thousands separator in most locales. Intl.NumberFormat handles all of this:
export function formatCurrency(amount, currency, locale) {
const isZeroDecimal = currency === 'JPY' || currency === 'KRW';
return new Intl.NumberFormat(locale, {
style: 'currency',
currency,
minimumFractionDigits: isZeroDecimal ? 0 : 2,
maximumFractionDigits: isZeroDecimal ? 0 : 2,
}).format(amount);
}
The zero-decimal check is necessary because Intl defaults to 2 decimal places even for JPY, producing wrong-looking output like ¥1,500.00. Japanese currency has no sub-yen units in practice.
Round up to nearest
Rounding a tip up to a convenient amount is a common request — pay ¥4500 for a ¥4321 bill:
export function roundUp(amount, to) {
if (to === 0) return amount;
return Math.ceil(amount / to) * to;
}
Divide, ceil, multiply. roundUp(4321, 100) → 4400. roundUp(4321, 500) → 4500. Users choose the rounding base (1, 5, 10, 100, 500, 1000) from a dropdown.
Even split with precision
export function splitEven(total, people) {
if (people <= 0) return 0;
// Floor to 2 decimal places so each person pays a "clean" amount
return Math.floor((total / people) * 100) / 100;
}
Note: Math.floor, not round. If 3 people split $100, each pays $33.33 (total: $99.99). The 1¢ remainder is absorbed by whoever initiates the transfer. Better than $33.34 × 3 = $100.02 where the group collectively overpays.
Tipping notes in context
The educational aspect: what makes this tool different from the hundreds of generic tip calculators is that it TEACHES. Japanese people visiting the US benefit from seeing "You should leave 15-20%". Americans visiting Japan benefit from seeing "Do not tip".
Each country's note renders prominently below the calculator. Travelers can pick their destination and skim the customs before their first meal there.
Series
This is entry #97 in my 100+ public portfolio series.
- 📦 Repo: https://github.com/sen-ltd/tip-calc
- 🌐 Live: https://sen.ltd/portfolio/tip-calc/
- 🏢 Company: https://sen.ltd/

Top comments (0)