Merge pull request #3597 from cake-tech/CW-1621-Brazillian-Locale-Fix

fix: brazillian localle bug that makes app unresponsive
This commit is contained in:
David Adegoke
2026-09-10 14:36:49 +01:00
committed by GitHub
9 changed files with 84 additions and 35 deletions
+2 -5
View File
@@ -1,5 +1,5 @@
import "package:cw_core/amount/money.dart";
import "package:intl/intl.dart";
import "package:cw_core/crypto_amount_format.dart";
extension WithLocalSeparator on Money {
String toLocalStringWithSymbol({
@@ -37,10 +37,7 @@ extension WithLocalSeparator on Money {
String _withLocalSeparator(String amount, {String? locale}) {
final isNegative = amount.startsWith("-");
final formater = NumberFormat("#,###", locale);
final parts = (isNegative ? amount.substring(1) : amount).split(".");
final formatted = [formater.format(int.tryParse(parts.first) ?? 0), ...parts.sublist(1)]
.join(formater.symbols.DECIMAL_SEP);
final formatted = (isNegative ? amount.substring(1) : amount).withLocalSeperator(locale);
return isNegative ? "-$formatted" : formatted;
}
+4 -1
View File
@@ -67,7 +67,10 @@ extension MaxDecimals on String {
return [parts.first.withLocalSeperator(locale), ...parts.sublist(1)].join(" ");
}
final formater = NumberFormat("#,###", locale);
final formater = NumberFormat(
"#,###",
Intl.verifiedLocale(locale, NumberFormat.localeExists, onFailure: (_) => "en"),
);
final parts = replaceAll(",", "").split(".");
if (parts.first.contains("< 0")) parts.first = "0";
@@ -23,6 +23,10 @@ void main() {
expect(_btcGrouping.toLocalStringWithPrecision(locale: "de_DE"), "1.234,5");
});
test("formats as English when intl has no number data for the locale (yo)", () {
expect(_btcGrouping.toLocalStringWithPrecision(locale: "yo"), "1,234.5");
});
test("base unit renders integer sats with grouping", () {
expect(
_btc012.toLocalStringWithPrecision(useBaseUnit: true, locale: "en_US"),
+9
View File
@@ -108,6 +108,15 @@ void main() {
});
});
group("Locale without intl number data", () {
test("should format as English instead of throwing (ha)", () {
const input = "1123.4567";
final result = input.withLocalSeperator("ha");
expect(result, equals("1,123.4567"));
});
});
group('With XMR Suffix', () {
const locale = 'de_CH';
+15 -3
View File
@@ -1,7 +1,15 @@
import 'package:cake_wallet/generated/locales.dart';
import 'package:devicelocale/devicelocale.dart';
import "package:flutter/widgets.dart";
import 'package:intl/intl.dart';
// Flutter's Material delegates match on the language subtag, so "pt_BR" must be split into two
Locale localeFromLanguageCode(String code) {
final parts = code.split("_");
return parts.length == 2 ? Locale(parts[0], parts[1]) : Locale(code);
}
class LanguageService {
static const Map<String, String> supportedLocales = {
'en': 'English',
@@ -85,12 +93,16 @@ class LanguageService {
static Future<String> localeDetection() async {
try {
var locale = await Devicelocale.currentLocale ?? '';
locale = Intl.shortLocale(locale);
final locale = Intl.canonicalizedLocale(await Devicelocale.currentLocale ?? "");
if (list.keys.contains(locale)) {
return locale;
}
final language = Intl.shortLocale(locale);
if (list.keys.contains(language)) {
return language;
}
return LanguageService.defaultLocale;
} catch (_) {
return LanguageService.defaultLocale;
+3 -1
View File
@@ -65,6 +65,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_daemon/flutter_daemon.dart';
import "package:flutter_localizations/flutter_localizations.dart";
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:hive/hive.dart';
import 'package:quick_actions/quick_actions.dart';
@@ -329,6 +330,7 @@ Future<void> initialSetup({
required int initialMigrationVersion,
}) async {
LanguageService.loadLocaleList();
await GlobalMaterialLocalizations.delegate.load(const Locale("en"));
await defaultSettingsMigration(
secureStorage: secureStorage,
version: initialMigrationVersion,
@@ -426,7 +428,7 @@ class AppState extends State<App> with SingleTickerProviderStateMixin {
themeMode: themeMode,
localizationsDelegates: localizationDelegates,
supportedLocales: S.delegate.supportedLocales,
locale: Locale(appStore.settingsStore.languageCode),
locale: localeFromLanguageCode(appStore.settingsStore.languageCode),
onGenerateRoute: (settings) => Router.createRoute(settings),
initialRoute: initialRoute,
scrollBehavior: AppScrollBehavior(),
@@ -32,30 +32,35 @@ class ListItemSelectorWidget extends StatelessWidget {
onTap: onTap,
isFirstInSection: isFirstInSection,
isLastInSection: isLastInSection,
builder: (context, textStyle, labelStyle) {
return Row(
builder: (context, textStyle, labelStyle) => Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(label, style: textStyle)),
Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
spacing: 8,
children: [
Text(
options[selectedIndex],
style: labelStyle,
),
CakeImageWidget(
imageUrl: "assets/new-ui/chooser.svg",
colorFilter:
ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn),
),
],
Flexible(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 8,
children: [
Flexible(
child: Text(
options[selectedIndex],
style: labelStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
CakeImageWidget(
imageUrl: "assets/new-ui/chooser.svg",
colorFilter:
ColorFilter.mode(theme.colorScheme.onSurfaceVariant, BlendMode.srcIn),
),
],
),
),
),
],
);
});
));
}
}
+14 -3
View File
@@ -1378,8 +1378,8 @@ abstract class SettingsStoreBase with Store {
pinLength = defaultPinLength;
}
final savedLanguageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ??
await LanguageService.localeDetection();
final savedLanguageCode =
_offeredLanguageCode(sharedPreferences) ?? await LanguageService.localeDetection();
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
@@ -1938,7 +1938,12 @@ abstract class SettingsStoreBase with Store {
}
pinCodeLength = pinLength;
languageCode = sharedPreferences.getString(PreferencesKey.currentLanguageCode) ?? languageCode;
final restoredLanguageCode = _offeredLanguageCode(sharedPreferences);
if (restoredLanguageCode == null) {
await sharedPreferences.setString(PreferencesKey.currentLanguageCode, languageCode);
} else {
languageCode = restoredLanguageCode;
}
shouldShowYatPopup =
sharedPreferences.getBool(PreferencesKey.shouldShowYatPopup) ?? shouldShowYatPopup;
shouldShowDEuroDisclaimer =
@@ -2315,6 +2320,12 @@ abstract class SettingsStoreBase with Store {
await _sharedPreferences.setString(key, serializedData);
}
static String? _offeredLanguageCode(SharedPreferences sharedPreferences) {
final code = sharedPreferences.getString(PreferencesKey.currentLanguageCode);
return LanguageService.list.containsKey(code) ? code : null;
}
static Future<String?> _getDeviceName() async {
String? deviceName = '';
final deviceInfoPlugin = DeviceInfoPlugin();
+9 -3
View File
@@ -1,6 +1,6 @@
import "dart:collection";
import 'dart:io';
import 'dart:convert';
import './print_verbose_dummy.dart';
import 'localization/localization_constants.dart';
import 'utils/utils.dart';
@@ -49,7 +49,7 @@ Future<void> main(List<String> args) async {
return;
}
final localePath = <String, dynamic>{};
final localePath = SplayTreeMap<String, dynamic>();
await dir.list(recursive: false).forEach((element) {
// Parse the locale from the file name (e.g. strings_pt_br.arb -> pt_BR),
// normalizing the case so keys match LanguageService.supportedLocales.
@@ -103,8 +103,14 @@ Future<void> main(List<String> args) async {
output += classDeclaration;
// Flutter matches on the language subtag, so pt_BR is emitted as Locale("pt", "BR")
localePath.keys.forEach((key) {
output += ' Locale("$key", ""),' + '\n';
final parts = key.split("_");
if (parts.length == 2) {
output += ' Locale("${parts[0]}", "${parts[1]}"),\n';
} else {
output += ' Locale("$key", ""),\n';
}
});
output += part2;