feat: Implement internationalization (i18n) with English and Indonesian language#24
feat: Implement internationalization (i18n) with English and Indonesian language#24zakyyudha wants to merge 1 commit intoahmadawais:mainfrom
Conversation
…an language support.
There was a problem hiding this comment.
Pull request overview
Adds a lightweight, typed-dictionary internationalization layer to ramadan-cli, enabling English (default) and Indonesian UI strings across the CLI, setup flow, and commands without introducing an external i18n dependency.
Changes:
- Introduces a core
i18nmodule (setLocale/getLocale/t) plusen/idlocale dictionaries. - Updates CLI/setup/commands/banner to use translated strings and adds
--langsupport + persisted language preference. - Adds unit tests for i18n behavior (locale switching, interpolation, fallback).
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/i18n/index.ts | Core locale state + translation helper with template interpolation |
| src/i18n/locales/en.ts | English strings dictionary (baseline locale keys) |
| src/i18n/locales/id.ts | Indonesian translations implementing the same key set |
| src/tests/i18n.test.ts | Unit tests for locale switching and interpolation |
| src/cli.ts | Adds --lang/-l, initializes locale early for translated help/validation messages |
| src/setup.ts | Prompts for language first in interactive setup and persists it |
| src/commands/ramadan.ts | Replaces user-facing strings with t(...) and adjusts output formatting |
| src/commands/config.ts | Adds language persistence via config --lang and displays language in --show |
| src/ui/banner.ts | Localizes banner label/tagline |
| src/ramadan-config.ts | Persists language in config store (+ getters/setters) |
| src/index.ts | Re-exports i18n module |
| readme.md | Documents language feature and CLI/config flags |
Comments suppressed due to low confidence (1)
src/cli.ts:82
--langis defined, but the parsedopts.langis never applied (locale selection relies on the manualprocess.argvscan). This can ignore common forms like--lang=id, and makes runtime behavior depend on argv formatting. Suggest applyingsetLocale(opts.lang)in the action/preAction hook (and keep the early scan only for help/description rendering).
.option('-l, --lang <lang>', t('cliLangOption'))
.action(async (cityArg: string | undefined, opts: RootOptions) => {
await ramadanCommand({
city: cityArg || opts.city,
all: opts.all,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| iftar: to12HourTime(day.timings.Maghrib), | ||
| date: day.date.readable, | ||
| hijri: `${day.date.hijri.day} ${day.date.hijri.month.en} ${day.date.hijri.year}`, | ||
| hijri: `${roza} ${day.date.hijri.month.en} ${day.date.hijri.year}`, |
There was a problem hiding this comment.
The hijri column is now built from the roza number (${roza} ...) instead of the actual Hijri day (day.date.hijri.day). This makes the "Hijri" column incorrect/duplicative (it repeats the roza day). Consider restoring day.date.hijri.day (or explicitly renaming the column if the intent is to show roza-aligned day-of-Ramadan rather than the Hijri date).
| hijri: `${roza} ${day.date.hijri.month.en} ${day.date.hijri.year}`, | |
| hijri: `${day.date.hijri.day} ${day.date.hijri.month.en} ${day.date.hijri.year}`, |
| throw new Error( | ||
| t('errorCouldNotFetchPrayer', { details: errors.join(' | ') }) | ||
| ); |
There was a problem hiding this comment.
These errors are now localized via t(...), but getJsonErrorCode() still derives stable error codes by matching English message prefixes (e.g., "Could not fetch prayer times"). In non-English locales the JSON error code will degrade to RAMADAN_CLI_ERROR. Recommend emitting structured errors (e.g., custom Error class with a code, or throwing with an internal non-localized code/key and localizing only the displayed message).
| const langValue = options.lang.trim().toLowerCase(); | ||
| if (isSupportedLang(langValue)) { | ||
| setStoredLanguage(langValue); | ||
| setLocale(langValue); |
There was a problem hiding this comment.
If options.lang is provided but unsupported, the command silently skips updating the stored language and still prints the translated "config updated" message. This can mislead users into thinking the language was saved. Consider validating lang and throwing/printing an explicit error on invalid values, or only printing the updated message when something actually changed.
| setLocale(langValue); | |
| setLocale(langValue); | |
| } else { | |
| console.error(pc.red(`Unsupported language: "${options.lang}". Language setting was not updated.`)); |
| @@ -1,4 +1,5 @@ | |||
| import pc from 'picocolors'; | |||
| import { t } from '../i18n/index.js'; | |||
| import { MOON_EMOJI, ramadanGreen } from './theme.js'; | |||
There was a problem hiding this comment.
MOON_EMOJI appears to be unused after switching the banner label to t('bannerLabel'). Consider removing it from the import to avoid unused-import lint/build failures, or reusing MOON_EMOJI and keeping the translation string emoji-free.
| import { MOON_EMOJI, ramadanGreen } from './theme.js'; | |
| import { ramadanGreen } from './theme.js'; |
| method: z.number().optional(), | ||
| school: z.number().optional(), | ||
| timezone: z.string().optional(), | ||
| language: z.string().optional(), | ||
| firstRozaDate: z |
There was a problem hiding this comment.
Config validation currently allows any language string (z.string().optional()), but the rest of the code only supports 'en' | 'id'. Tightening the Zod schema to an enum (or refining the string) would prevent invalid persisted values and reduce the need for fallback logic elsewhere.
Summary
Adds internationalization (i18n) support to ramadan-cli using a lightweight typed dictionary pattern — no external i18n dependencies added.
Languages
Changes
New files
Modified files
-l, --langflag, early locale detection beforeprogram.parse()--langsupport for persistent language preference, language shown in--showlanguagefield with getStoredLanguage() / setStoredLanguage()Usage