Quitter icon indicating copy to clipboard operation
Quitter copied to clipboard

feat: Add Japanese localization support

Open Gonbei774 opened this issue 4 weeks ago • 1 comments

Summary

This PR adds Japanese localization support to the Quitter app, enabling Japanese users to use the app in their native language.

Changes Made

  • Added flutter_localizations dependency to support multiple languages
  • Created l10n configuration (l10n.yaml)
  • Added 247 localized strings in ARB format:
    • lib/l10n/app_en.arb (English)
    • lib/l10n/app_ja.arb (Japanese)
  • Updated MaterialApp with localization delegates and supported locales
  • Replaced all hardcoded UI strings with AppLocalizations calls across 20 files
  • Updated l10n output directory to lib/l10n/generated/ for Flutter 3.38+ compatibility (synthetic-package was deprecated)
  • The app automatically displays in Japanese when the device language is set to Japanese

Files Changed

  • Configuration: pubspec.yaml, l10n.yaml
  • Localization files: lib/l10n/app_en.arb, lib/l10n/app_ja.arb
  • Generated files: lib/l10n/generated/
  • Main app: lib/main.dart
  • UI pages (20 files): All user-facing pages updated to use localized strings

Screenshots

Home Screen (Japanese)

Addiction Detail Page (Japanese)

Journal Page (Japanese)

Settings Page (Japanese)

Testing

  • ✅ flutter analyze passes with no issues
  • ✅ App builds successfully (Flutter 3.38.3)
  • ✅ All UI strings display correctly in Japanese
  • ✅ English localization works as before
  • ✅ Tested on Android device

Notes

  • The app automatically detects the device language and displays the appropriate localization
  • All existing functionality remains unchanged
  • Future language support can be easily added by creating new ARB files
  • Milestone descriptions remain in English (localization would require architectural changes)

Gonbei774 avatar Dec 02 '25 15:12 Gonbei774

home_japanese detail_japanese s/c7cfe9f8-e9bc-4354-84da-476156adc77b" /> journal_japanese settings_japanese

Gonbei774 avatar Dec 02 '25 15:12 Gonbei774

Great work King thank you for your contribution. How would we need to change the milestones to allow them to be localized as well?

brandonp2412 avatar Dec 02 '25 21:12 brandonp2412

Thanks for the kind words!

I investigated the milestone localization, and it turned out to be simpler than I initially expected. In my original PR description, I mentioned "architectural changes would be required" - but after actually implementing it, the existing l10n system handles it well.

What I did: Changed the milestone list from a const field to a method that takes AppLocalizations:

// Before: const list with hardcoded strings
final List<QuitMilestone> miles = const [
  QuitMilestone(day: 1, title: "Sleep Quality Begins to Improve", ...),
];

// After: dynamic list using l10n
List<QuitMilestone> _getMilestones(AppLocalizations l10n) {
  return [
    QuitMilestone(day: 1, title: l10n.alcoholMilestone1Title, ...),
  ];
}

I've implemented this for the Alcohol page as a proof of concept and verified it works on a real device.

The reference and link fields remain in English since they're source citations (URLs and publication names).

I can extend this to all addiction types if you'd like. Let me know how you'd like to proceed!

Gonbei774 avatar Dec 03 '25 00:12 Gonbei774

alcohol_milestone_japanese

Gonbei774 avatar Dec 03 '25 00:12 Gonbei774

Thanks for the kind words!

I investigated the milestone localization, and it turned out to be simpler than I initially expected. In my original PR description, I mentioned "architectural changes would be required" - but after actually implementing it, the existing l10n system handles it well.

What I did: Changed the milestone list from a const field to a method that takes AppLocalizations:

// Before: const list with hardcoded strings
final List<QuitMilestone> miles = const [
  QuitMilestone(day: 1, title: "Sleep Quality Begins to Improve", ...),
];

// After: dynamic list using l10n
List<QuitMilestone> _getMilestones(AppLocalizations l10n) {
  return [
    QuitMilestone(day: 1, title: l10n.alcoholMilestone1Title, ...),
  ];
}

I've implemented this for the Alcohol page as a proof of concept and verified it works on a real device.

The reference and link fields remain in English since they're source citations (URLs and publication names).

I can extend this to all addiction types if you'd like. Let me know how you'd like to proceed!

Yes please. Thanks for the hard work i'll review and accept this soon

brandonp2412 avatar Dec 03 '25 03:12 brandonp2412

I'm still testing on a real device and will push the milestone localization for all addiction types tonight (JST).


By the way, I found a critical bug while testing (this exists in the current production version too):

PIN Lock Issue When setting a PIN using a Japanese keyboard, users can input full-width numbers (e.g., "1234"). However, the PIN entry screen only allows half-width numbers ("1234") via the numeric keypad. This causes a permanent lockout since the PINs never match.

I'll investigate the root cause tonight and share my findings. However, since this involves user data security, I'd prefer to leave the actual fix to you.

Gonbei774 avatar Dec 03 '25 03:12 Gonbei774

Done! Milestone localization for all 9 addiction types:

  • Alcohol, Smoking, Vaping, Marijuana, Opioid, Social Media, Nicotine Pouches, Pornography, and Custom
  • 81 milestones total (9 per type) × 2 languages = 162 entries
  • References/links kept in English (source citations)

All tested on real device.

Regarding the PIN bug - I'll share my investigation findings separately.

Gonbei774 avatar Dec 03 '25 15:12 Gonbei774

Note: Test setup requires update

After adding localization to the milestone pages, the existing tests will fail because they don't set up the localization delegates.

Cause: Tests create MaterialApp without localizationsDelegates, so AppLocalizations.of(context) returns null.

Fix: Add localization setup to test widgets:

import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:quitter/l10n/generated/app_localizations.dart';

Widget createTestWidget() {
  return MaterialApp(
    localizationsDelegates: const [
      AppLocalizations.delegate,
      GlobalMaterialLocalizations.delegate,
      GlobalWidgetsLocalizations.delegate,
      GlobalCupertinoLocalizations.delegate,
    ],
    supportedLocales: AppLocalizations.supportedLocales,
    home: SettingsPage(),
  );
}

This affects:

  • test/settings_page_test.dart
  • test/home_page_test.dart
  • test/journal_page_test.dart

I can submit a fix for this if you'd like, or leave it to you.

Gonbei774 avatar Dec 03 '25 15:12 Gonbei774

Thank you i'll work through fixing the unit tests right now. The PIN thing doesn't sound like a big issue security wise, i'll probably reject non-ascii characters 😎

brandonp2412 avatar Dec 03 '25 20:12 brandonp2412

Going to just add 1 thing - manually picking language in the settings. Will be useful for testing, not sure anyone who picks japanese / other languages in android settings will want each app to specify the language, however might as well

brandonp2412 avatar Dec 03 '25 21:12 brandonp2412

Thanks for merging!

Gonbei774 avatar Dec 04 '25 01:12 Gonbei774