import 'dart:ui' show Locale; class RuntimeLocaleInfo { const RuntimeLocaleInfo({ required this.requested, required this.resolved, required this.defaultLocale, required this.supportedLocales, required this.languageCode, this.scriptCode, this.countryCode, }); final String requested; final String resolved; final String defaultLocale; final List supportedLocales; final String languageCode; final String? scriptCode; final String? countryCode; Map toMap() { return { 'requested': requested, 'resolved': resolved, 'default': defaultLocale, 'supported': supportedLocales, 'languageCode': languageCode, if (scriptCode != null) 'scriptCode': scriptCode, if (countryCode != null) 'countryCode': countryCode, }; } } class RuntimeLocaleResolver { const RuntimeLocaleResolver._(); static RuntimeLocaleInfo resolve({ required Locale requested, required String defaultLocale, required List supportedLocales, }) { final requestedTag = normalizeTag(tagOf(requested)); final fallback = normalizeTag(defaultLocale); final supported = supportedLocales.isEmpty ? [fallback] : supportedLocales.map(normalizeTag).toList(growable: false); final resolved = _resolveTag( requestedTag: requestedTag, fallback: fallback, supported: supported, ); return RuntimeLocaleInfo( requested: requestedTag, resolved: resolved, defaultLocale: fallback, supportedLocales: supported, languageCode: requested.languageCode, scriptCode: requested.scriptCode, countryCode: requested.countryCode, ); } static Locale localeFromTag(String tag) { final parts = normalizeTag(tag).split('-'); if (parts.isEmpty || parts.first.isEmpty) { throw const FormatException('Locale tag must not be empty'); } String? scriptCode; String? countryCode; for (final part in parts.skip(1)) { if (part.length == 4 && scriptCode == null) { scriptCode = part; } else { countryCode ??= part; } } return Locale.fromSubtags( languageCode: parts.first, scriptCode: scriptCode, countryCode: countryCode, ); } static String tagOf(Locale locale) { final parts = [locale.languageCode]; final scriptCode = locale.scriptCode; final countryCode = locale.countryCode; if (scriptCode != null && scriptCode.isNotEmpty) { parts.add(scriptCode); } if (countryCode != null && countryCode.isNotEmpty) { parts.add(countryCode); } return normalizeTag(parts.join('-')); } static String normalizeTag(String tag) { final normalized = tag.trim().replaceAll('_', '-'); if (normalized.isEmpty) { throw const FormatException('Locale tag must not be empty'); } final parts = normalized .split('-') .where((part) => part.isNotEmpty) .toList(growable: false); if (parts.isEmpty) { throw const FormatException('Locale tag must not be empty'); } if (!_isLocalePart(parts.first)) { throw FormatException('Locale language code is invalid: ${parts.first}'); } final result = [parts.first.toLowerCase()]; for (final part in parts.skip(1)) { if (!_isLocalePart(part)) { throw FormatException('Locale tag part is invalid: $part'); } if (part.length == 4) { result.add( '${part[0].toUpperCase()}${part.substring(1).toLowerCase()}', ); } else if (part.length == 2 || part.length == 3) { result.add(part.toUpperCase()); } else { result.add(part.toLowerCase()); } } return result.join('-'); } static String _resolveTag({ required String requestedTag, required String fallback, required List supported, }) { final supportedSet = supported.toSet(); if (supportedSet.contains(requestedTag)) { return requestedTag; } final requestedLanguage = requestedTag.split('-').first; for (final candidate in supported) { if (candidate.split('-').first == requestedLanguage) { return candidate; } } if (supportedSet.contains(fallback)) { return fallback; } return supported.first; } static bool _isLocalePart(String value) { return RegExp(r'^[A-Za-z0-9]{2,8}$').hasMatch(value); } }