Skip to content

feat: Implement internationalization (i18n) with English and Indonesian language#24

Open
zakyyudha wants to merge 1 commit intoahmadawais:mainfrom
zakyyudha:feature/internationalization
Open

feat: Implement internationalization (i18n) with English and Indonesian language#24
zakyyudha wants to merge 1 commit intoahmadawais:mainfrom
zakyyudha:feature/internationalization

Conversation

@zakyyudha
Copy link
Copy Markdown

Summary

Adds internationalization (i18n) support to ramadan-cli using a lightweight typed dictionary pattern — no external i18n dependencies added.

Languages

  • 🇬🇧 English (default)
  • 🇮🇩 Bahasa Indonesia

Changes

New files

  • src/i18n/index.ts — Core i18n module with setLocale(), getLocale(), t() (translation with template interpolation)
  • src/i18n/locales/en.ts — English locale (~120 translatable string keys)
  • src/i18n/locales/id.ts — Indonesian locale with natural Bahasa Indonesia translations
  • src/tests/i18n.test.ts — 10 unit tests for locale switching, interpolation, and fallback

Modified files

  • cli.ts — Added -l, --lang flag, early locale detection before program.parse()
  • setup.ts — Language selection as first prompt in interactive setup
  • commands/ramadan.ts — All user-facing strings use t() calls; fixed Hijri day alignment with roza number
  • commands/config.ts — --lang support for persistent language preference, language shown in --show
  • ui/banner.ts — Translated banner label and tagline
  • ramadan-config.ts — Added language field with getStoredLanguage() / setStoredLanguage()
  • index.ts — Export i18n module
  • readme.md — Documented language feature, flags, and usage

Usage

# One-off language switch
ramadan-cli --lang id

# Save preferred language
ramadan-cli config --lang id

# First-run setup prompts for language selection
ramadan-cli

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 i18n module (setLocale/getLocale/t) plus en/id locale dictionaries.
  • Updates CLI/setup/commands/banner to use translated strings and adds --lang support + 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

  • --lang is defined, but the parsed opts.lang is never applied (locale selection relies on the manual process.argv scan). This can ignore common forms like --lang=id, and makes runtime behavior depend on argv formatting. Suggest applying setLocale(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.

Comment thread src/commands/ramadan.ts
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}`,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

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).

Suggested change
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}`,

Copilot uses AI. Check for mistakes.
Comment thread src/commands/ramadan.ts
Comment on lines +827 to +829
throw new Error(
t('errorCouldNotFetchPrayer', { details: errors.join(' | ') })
);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

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).

Copilot uses AI. Check for mistakes.
Comment thread src/commands/config.ts
const langValue = options.lang.trim().toLowerCase();
if (isSupportedLang(langValue)) {
setStoredLanguage(langValue);
setLocale(langValue);
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
setLocale(langValue);
setLocale(langValue);
} else {
console.error(pc.red(`Unsupported language: "${options.lang}". Language setting was not updated.`));

Copilot uses AI. Check for mistakes.
Comment thread src/ui/banner.ts
@@ -1,4 +1,5 @@
import pc from 'picocolors';
import { t } from '../i18n/index.js';
import { MOON_EMOJI, ramadanGreen } from './theme.js';
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

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.

Suggested change
import { MOON_EMOJI, ramadanGreen } from './theme.js';
import { ramadanGreen } from './theme.js';

Copilot uses AI. Check for mistakes.
Comment thread src/ramadan-config.ts
Comment on lines 41 to 45
method: z.number().optional(),
school: z.number().optional(),
timezone: z.string().optional(),
language: z.string().optional(),
firstRozaDate: z
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

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.

Copilot uses AI. Check for mistakes.
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.

2 participants