첫 커밋: 로컬 프로젝트 업로드
This commit is contained in:
75
baron-sso/userfront/lib/core/i18n/locale_gate.dart
Normal file
75
baron-sso/userfront/lib/core/i18n/locale_gate.dart
Normal file
@@ -0,0 +1,75 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart' hide tr;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:userfront/i18n.dart';
|
||||
import '../services/web_window.dart';
|
||||
import 'locale_storage.dart';
|
||||
import 'locale_utils.dart';
|
||||
|
||||
class LocaleGate extends StatefulWidget {
|
||||
const LocaleGate({super.key, required this.localeCode, required this.child});
|
||||
|
||||
final String localeCode;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<LocaleGate> createState() => _LocaleGateState();
|
||||
}
|
||||
|
||||
class _LocaleGateState extends State<LocaleGate> {
|
||||
bool _syncScheduled = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_scheduleLocaleSync();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(LocaleGate oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.localeCode != widget.localeCode) {
|
||||
_scheduleLocaleSync();
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleLocaleSync() {
|
||||
if (_syncScheduled) {
|
||||
return;
|
||||
}
|
||||
_syncScheduled = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_syncScheduled = false;
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
unawaited(_applyLocale());
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _applyLocale() async {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final normalized = normalizeLocaleCode(widget.localeCode);
|
||||
LocaleStorage.write(normalized);
|
||||
final localization = EasyLocalization.of(context);
|
||||
if (localization == null) {
|
||||
return;
|
||||
}
|
||||
if (localization.currentLocale?.languageCode == normalized) {
|
||||
webWindow.setTitle(tr('ui.userfront.app_title'));
|
||||
return;
|
||||
}
|
||||
await localization.setLocale(Locale(normalized));
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
webWindow.setTitle(tr('ui.userfront.app_title'));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
115
baron-sso/userfront/lib/core/i18n/locale_registry.dart
Normal file
115
baron-sso/userfront/lib/core/i18n/locale_registry.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
const _translationAssetPrefix = 'assets/translations/';
|
||||
const _templateFileName = 'template.toml';
|
||||
const _safeFallbackLocaleCode = 'en';
|
||||
|
||||
List<String> extractSupportedLocaleCodesFromAssets(Iterable<String> assets) {
|
||||
final localeCodes = <String>{};
|
||||
for (final asset in assets) {
|
||||
if (!asset.startsWith(_translationAssetPrefix) ||
|
||||
!asset.endsWith('.toml')) {
|
||||
continue;
|
||||
}
|
||||
final fileName = asset.substring(_translationAssetPrefix.length);
|
||||
if (fileName.contains('/') || fileName == _templateFileName) {
|
||||
continue;
|
||||
}
|
||||
final rawCode = fileName.substring(0, fileName.length - '.toml'.length);
|
||||
final normalized = rawCode.toLowerCase().replaceAll('_', '-');
|
||||
if (_isValidLocaleCode(normalized)) {
|
||||
localeCodes.add(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
final sorted = localeCodes.toList()..sort();
|
||||
return sorted;
|
||||
}
|
||||
|
||||
class LocaleRegistry {
|
||||
static final Set<String> _localeCodes = <String>{};
|
||||
static bool _initialized = false;
|
||||
|
||||
static void primeWithDefaults({
|
||||
Iterable<String> localeCodes = const ['en', 'ko'],
|
||||
}) {
|
||||
if (_localeCodes.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
_localeCodes.addAll(
|
||||
localeCodes
|
||||
.map((code) => code.toLowerCase().replaceAll('_', '-'))
|
||||
.where(_isValidLocaleCode),
|
||||
);
|
||||
if (_localeCodes.isEmpty) {
|
||||
_localeCodes.add(_safeFallbackLocaleCode);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> initialize({AssetBundle? assetBundle}) async {
|
||||
if (_initialized) {
|
||||
return;
|
||||
}
|
||||
final bundle = assetBundle ?? rootBundle;
|
||||
try {
|
||||
final manifest = await AssetManifest.loadFromAssetBundle(bundle);
|
||||
final extracted = extractSupportedLocaleCodesFromAssets(
|
||||
manifest.listAssets(),
|
||||
);
|
||||
_localeCodes.addAll(extracted);
|
||||
} catch (_) {
|
||||
// manifest 로딩 실패 시 안전 fallback으로 계속 진행합니다.
|
||||
}
|
||||
|
||||
if (_localeCodes.isEmpty) {
|
||||
_localeCodes.add(_safeFallbackLocaleCode);
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
static List<String> get supportedLocaleCodes {
|
||||
final sorted = _localeCodes.toList()..sort();
|
||||
return List.unmodifiable(sorted);
|
||||
}
|
||||
|
||||
static String get fallbackLocaleCode {
|
||||
final supported = supportedLocaleCodes;
|
||||
if (supported.isEmpty) {
|
||||
return _safeFallbackLocaleCode;
|
||||
}
|
||||
if (supported.contains('en')) {
|
||||
return 'en';
|
||||
}
|
||||
return supported.first;
|
||||
}
|
||||
|
||||
static bool contains(String code) {
|
||||
return _localeCodes.contains(code.toLowerCase());
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static void setSupportedLocaleCodesForTest(Iterable<String> localeCodes) {
|
||||
_localeCodes
|
||||
..clear()
|
||||
..addAll(
|
||||
localeCodes
|
||||
.map((code) => code.toLowerCase().replaceAll('_', '-'))
|
||||
.where(_isValidLocaleCode),
|
||||
);
|
||||
if (_localeCodes.isEmpty) {
|
||||
_localeCodes.add(_safeFallbackLocaleCode);
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static void resetForTest() {
|
||||
_localeCodes.clear();
|
||||
_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isValidLocaleCode(String value) {
|
||||
return RegExp(r'^[a-z]{2,3}$').hasMatch(value);
|
||||
}
|
||||
59
baron-sso/userfront/lib/core/i18n/locale_storage.dart
Normal file
59
baron-sso/userfront/lib/core/i18n/locale_storage.dart
Normal file
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'locale_storage_backend.dart';
|
||||
import 'locale_storage_stub.dart'
|
||||
if (dart.library.js_interop) 'locale_storage_web.dart';
|
||||
|
||||
abstract class LocaleStorage {
|
||||
static bool _forceMemory = false;
|
||||
static bool _forceSession = false;
|
||||
|
||||
static void _syncTestMode() {
|
||||
if (_forceMemory) {
|
||||
localeStorage.setTestMode(LocaleStorageTestMode.memoryOnly);
|
||||
return;
|
||||
}
|
||||
if (_forceSession) {
|
||||
localeStorage.setTestMode(LocaleStorageTestMode.sessionOnly);
|
||||
return;
|
||||
}
|
||||
localeStorage.setTestMode(LocaleStorageTestMode.normal);
|
||||
}
|
||||
|
||||
static String? read() => localeStorage.read();
|
||||
static void write(String locale) => localeStorage.write(locale);
|
||||
|
||||
@visibleForTesting
|
||||
static void setTestModeForTests(LocaleStorageTestMode mode) {
|
||||
_forceMemory = mode == LocaleStorageTestMode.memoryOnly;
|
||||
_forceSession = mode == LocaleStorageTestMode.sessionOnly;
|
||||
_syncTestMode();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static void clearForTests() {
|
||||
localeStorage.clearForTests();
|
||||
_forceMemory = false;
|
||||
_forceSession = false;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static void seedLegacyForTests(String locale) {
|
||||
localeStorage.seedLegacyForTests(locale);
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
static LocaleStorageDebugState debugStateForTests() {
|
||||
return localeStorage.debugStateForTests();
|
||||
}
|
||||
|
||||
static void forceMemoryStorageForTests(bool value) {
|
||||
_forceMemory = value;
|
||||
_syncTestMode();
|
||||
}
|
||||
|
||||
static void forceSessionStorageForTests(bool value) {
|
||||
_forceSession = value;
|
||||
_syncTestMode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
enum LocaleStorageTestMode { normal, sessionOnly, memoryOnly }
|
||||
|
||||
@immutable
|
||||
class LocaleStorageDebugState {
|
||||
const LocaleStorageDebugState({
|
||||
required this.mode,
|
||||
this.localCurrent,
|
||||
this.localLegacy,
|
||||
this.sessionCurrent,
|
||||
this.sessionLegacy,
|
||||
this.memoryCurrent,
|
||||
this.memoryLegacy,
|
||||
});
|
||||
|
||||
final LocaleStorageTestMode mode;
|
||||
final String? localCurrent;
|
||||
final String? localLegacy;
|
||||
final String? sessionCurrent;
|
||||
final String? sessionLegacy;
|
||||
final String? memoryCurrent;
|
||||
final String? memoryLegacy;
|
||||
}
|
||||
|
||||
abstract interface class LocaleStorageBackend {
|
||||
String? read();
|
||||
|
||||
void write(String locale);
|
||||
|
||||
void setTestMode(LocaleStorageTestMode mode);
|
||||
|
||||
void clearForTests();
|
||||
|
||||
void seedLegacyForTests(String locale);
|
||||
|
||||
LocaleStorageDebugState debugStateForTests();
|
||||
}
|
||||
245
baron-sso/userfront/lib/core/i18n/locale_storage_engine.dart
Normal file
245
baron-sso/userfront/lib/core/i18n/locale_storage_engine.dart
Normal file
@@ -0,0 +1,245 @@
|
||||
import 'locale_storage_backend.dart';
|
||||
import 'locale_storage_policy.dart';
|
||||
|
||||
enum _StorageTarget { local, session, memory }
|
||||
|
||||
abstract interface class LocaleStorageTarget {
|
||||
String? read(String key);
|
||||
|
||||
bool write(String key, String value);
|
||||
|
||||
bool remove(String key);
|
||||
|
||||
void clear();
|
||||
}
|
||||
|
||||
class LocaleStorageNoopTarget implements LocaleStorageTarget {
|
||||
const LocaleStorageNoopTarget();
|
||||
|
||||
@override
|
||||
String? read(String key) => null;
|
||||
|
||||
@override
|
||||
bool write(String key, String value) => false;
|
||||
|
||||
@override
|
||||
bool remove(String key) => false;
|
||||
|
||||
@override
|
||||
void clear() {}
|
||||
}
|
||||
|
||||
class LocaleStorageCallbackTarget implements LocaleStorageTarget {
|
||||
LocaleStorageCallbackTarget({
|
||||
required this.readCallback,
|
||||
required this.writeCallback,
|
||||
required this.removeCallback,
|
||||
required this.clearCallback,
|
||||
});
|
||||
|
||||
final String? Function(String key) readCallback;
|
||||
final void Function(String key, String value) writeCallback;
|
||||
final void Function(String key) removeCallback;
|
||||
final void Function() clearCallback;
|
||||
|
||||
@override
|
||||
String? read(String key) => readCallback(key);
|
||||
|
||||
@override
|
||||
bool write(String key, String value) {
|
||||
writeCallback(key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
bool remove(String key) {
|
||||
removeCallback(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void clear() => clearCallback();
|
||||
}
|
||||
|
||||
class LocaleStorageEngine implements LocaleStorageBackend {
|
||||
LocaleStorageEngine({
|
||||
required LocaleStorageTarget localTarget,
|
||||
required LocaleStorageTarget sessionTarget,
|
||||
}) : _localTarget = localTarget,
|
||||
_sessionTarget = sessionTarget;
|
||||
|
||||
final LocaleStorageTarget _localTarget;
|
||||
final LocaleStorageTarget _sessionTarget;
|
||||
final Map<String, String> _memory = {};
|
||||
LocaleStorageTestMode _mode = LocaleStorageTestMode.normal;
|
||||
|
||||
List<_StorageTarget> _fallbackTargets() {
|
||||
switch (_mode) {
|
||||
case LocaleStorageTestMode.normal:
|
||||
return [
|
||||
_StorageTarget.local,
|
||||
_StorageTarget.session,
|
||||
_StorageTarget.memory,
|
||||
];
|
||||
case LocaleStorageTestMode.sessionOnly:
|
||||
return [_StorageTarget.session, _StorageTarget.memory];
|
||||
case LocaleStorageTestMode.memoryOnly:
|
||||
return [_StorageTarget.memory];
|
||||
}
|
||||
}
|
||||
|
||||
String? _safeReadTarget(_StorageTarget target, String key) {
|
||||
try {
|
||||
switch (target) {
|
||||
case _StorageTarget.local:
|
||||
return _localTarget.read(key);
|
||||
case _StorageTarget.session:
|
||||
return _sessionTarget.read(key);
|
||||
case _StorageTarget.memory:
|
||||
return _memory[key];
|
||||
}
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
bool _safeWriteTarget(_StorageTarget target, String key, String value) {
|
||||
try {
|
||||
switch (target) {
|
||||
case _StorageTarget.local:
|
||||
return _localTarget.write(key, value);
|
||||
case _StorageTarget.session:
|
||||
return _sessionTarget.write(key, value);
|
||||
case _StorageTarget.memory:
|
||||
_memory[key] = value;
|
||||
return true;
|
||||
}
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool _safeRemoveTarget(_StorageTarget target, String key) {
|
||||
try {
|
||||
switch (target) {
|
||||
case _StorageTarget.local:
|
||||
return _localTarget.remove(key);
|
||||
case _StorageTarget.session:
|
||||
return _sessionTarget.remove(key);
|
||||
case _StorageTarget.memory:
|
||||
_memory.remove(key);
|
||||
return true;
|
||||
}
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void _safeClearTarget(_StorageTarget target) {
|
||||
try {
|
||||
switch (target) {
|
||||
case _StorageTarget.local:
|
||||
_localTarget.clear();
|
||||
case _StorageTarget.session:
|
||||
_sessionTarget.clear();
|
||||
case _StorageTarget.memory:
|
||||
_memory.clear();
|
||||
}
|
||||
} catch (_) {
|
||||
// 테스트 정리 단계에서는 clear 예외를 무시합니다.
|
||||
}
|
||||
}
|
||||
|
||||
String? _readByKey(String key) {
|
||||
for (final target in _fallbackTargets()) {
|
||||
final value = _safeReadTarget(target, key);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _writeByKey(String key, String value) {
|
||||
for (final target in _fallbackTargets()) {
|
||||
if (_safeWriteTarget(target, key, value)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _removeEverywhere(String key) {
|
||||
_safeRemoveTarget(_StorageTarget.local, key);
|
||||
_safeRemoveTarget(_StorageTarget.session, key);
|
||||
_memory.remove(key);
|
||||
}
|
||||
|
||||
@override
|
||||
String? read() {
|
||||
final current = _readByKey(LocaleStoragePolicy.currentKey);
|
||||
if (LocaleStoragePolicy.hasValue(current)) {
|
||||
return current;
|
||||
}
|
||||
|
||||
final legacy = _readByKey(LocaleStoragePolicy.legacyKey);
|
||||
if (LocaleStoragePolicy.shouldMigrateLegacy(
|
||||
current: current,
|
||||
legacy: legacy,
|
||||
) &&
|
||||
legacy != null) {
|
||||
_writeByKey(LocaleStoragePolicy.currentKey, legacy);
|
||||
_removeEverywhere(LocaleStoragePolicy.legacyKey);
|
||||
return legacy;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@override
|
||||
void write(String locale) {
|
||||
_writeByKey(LocaleStoragePolicy.currentKey, locale);
|
||||
}
|
||||
|
||||
@override
|
||||
void setTestMode(LocaleStorageTestMode mode) {
|
||||
_mode = mode;
|
||||
}
|
||||
|
||||
@override
|
||||
void clearForTests() {
|
||||
_safeClearTarget(_StorageTarget.local);
|
||||
_safeClearTarget(_StorageTarget.session);
|
||||
_memory.clear();
|
||||
_mode = LocaleStorageTestMode.normal;
|
||||
}
|
||||
|
||||
@override
|
||||
void seedLegacyForTests(String locale) {
|
||||
_writeByKey(LocaleStoragePolicy.legacyKey, locale);
|
||||
}
|
||||
|
||||
@override
|
||||
LocaleStorageDebugState debugStateForTests() {
|
||||
return LocaleStorageDebugState(
|
||||
mode: _mode,
|
||||
localCurrent: _safeReadTarget(
|
||||
_StorageTarget.local,
|
||||
LocaleStoragePolicy.currentKey,
|
||||
),
|
||||
localLegacy: _safeReadTarget(
|
||||
_StorageTarget.local,
|
||||
LocaleStoragePolicy.legacyKey,
|
||||
),
|
||||
sessionCurrent: _safeReadTarget(
|
||||
_StorageTarget.session,
|
||||
LocaleStoragePolicy.currentKey,
|
||||
),
|
||||
sessionLegacy: _safeReadTarget(
|
||||
_StorageTarget.session,
|
||||
LocaleStoragePolicy.legacyKey,
|
||||
),
|
||||
memoryCurrent: _memory[LocaleStoragePolicy.currentKey],
|
||||
memoryLegacy: _memory[LocaleStoragePolicy.legacyKey],
|
||||
);
|
||||
}
|
||||
}
|
||||
13
baron-sso/userfront/lib/core/i18n/locale_storage_policy.dart
Normal file
13
baron-sso/userfront/lib/core/i18n/locale_storage_policy.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
class LocaleStoragePolicy {
|
||||
static const currentKey = 'locale';
|
||||
static const legacyKey = 'baron_locale';
|
||||
|
||||
static bool hasValue(String? value) => value != null && value.isNotEmpty;
|
||||
|
||||
static bool shouldMigrateLegacy({
|
||||
required String? current,
|
||||
required String? legacy,
|
||||
}) {
|
||||
return !hasValue(current) && hasValue(legacy);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'locale_storage_backend.dart';
|
||||
import 'locale_storage_engine.dart';
|
||||
|
||||
final LocaleStorageBackend localeStorage = LocaleStorageEngine(
|
||||
localTarget: const LocaleStorageNoopTarget(),
|
||||
sessionTarget: const LocaleStorageNoopTarget(),
|
||||
);
|
||||
22
baron-sso/userfront/lib/core/i18n/locale_storage_web.dart
Normal file
22
baron-sso/userfront/lib/core/i18n/locale_storage_web.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
// ignore_for_file: avoid_web_libraries_in_flutter
|
||||
|
||||
import 'package:web/web.dart' as web;
|
||||
|
||||
import 'locale_storage_backend.dart';
|
||||
import 'locale_storage_engine.dart';
|
||||
|
||||
final LocaleStorageBackend localeStorage = LocaleStorageEngine(
|
||||
localTarget: LocaleStorageCallbackTarget(
|
||||
readCallback: (key) => web.window.localStorage.getItem(key),
|
||||
writeCallback: (key, value) => web.window.localStorage.setItem(key, value),
|
||||
removeCallback: (key) => web.window.localStorage.removeItem(key),
|
||||
clearCallback: () => web.window.localStorage.clear(),
|
||||
),
|
||||
sessionTarget: LocaleStorageCallbackTarget(
|
||||
readCallback: (key) => web.window.sessionStorage.getItem(key),
|
||||
writeCallback: (key, value) =>
|
||||
web.window.sessionStorage.setItem(key, value),
|
||||
removeCallback: (key) => web.window.sessionStorage.removeItem(key),
|
||||
clearCallback: () => web.window.sessionStorage.clear(),
|
||||
),
|
||||
);
|
||||
117
baron-sso/userfront/lib/core/i18n/locale_utils.dart
Normal file
117
baron-sso/userfront/lib/core/i18n/locale_utils.dart
Normal file
@@ -0,0 +1,117 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'locale_storage.dart';
|
||||
import 'locale_registry.dart';
|
||||
|
||||
String get defaultLocaleCode => LocaleRegistry.fallbackLocaleCode;
|
||||
|
||||
String normalizeLocaleCode(String? code) {
|
||||
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
|
||||
final fallbackLocaleCode = LocaleRegistry.fallbackLocaleCode;
|
||||
|
||||
if (code == null || code.isEmpty) {
|
||||
return fallbackLocaleCode;
|
||||
}
|
||||
final normalized = code.toLowerCase().replaceAll('_', '-');
|
||||
if (supportedLocaleCodes.contains(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
final languageCode = normalized.split('-').first;
|
||||
if (supportedLocaleCodes.contains(languageCode)) {
|
||||
return languageCode;
|
||||
}
|
||||
return fallbackLocaleCode;
|
||||
}
|
||||
|
||||
String resolvePreferredLocaleCode() {
|
||||
final stored = LocaleStorage.read();
|
||||
if (stored != null && stored.isNotEmpty) {
|
||||
final normalizedStored = normalizeLocaleCode(stored);
|
||||
if (LocaleRegistry.contains(normalizedStored)) {
|
||||
return normalizedStored;
|
||||
}
|
||||
}
|
||||
final deviceLocale = PlatformDispatcher.instance.locale;
|
||||
final countryCode = deviceLocale.countryCode;
|
||||
final languageTag = countryCode == null || countryCode.isEmpty
|
||||
? deviceLocale.languageCode
|
||||
: '${deviceLocale.languageCode}-$countryCode';
|
||||
return normalizeLocaleCode(languageTag);
|
||||
}
|
||||
|
||||
String? extractLocaleFromPath(Uri uri) {
|
||||
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
|
||||
if (uri.pathSegments.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final code = uri.pathSegments.first.toLowerCase();
|
||||
if (supportedLocaleCodes.contains(code)) {
|
||||
return code;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String stripLocalePath(Uri uri) {
|
||||
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
|
||||
final segments = uri.pathSegments;
|
||||
if (segments.isNotEmpty &&
|
||||
supportedLocaleCodes.contains(segments.first.toLowerCase())) {
|
||||
final rest = segments.skip(1).join('/');
|
||||
if (rest.isEmpty) {
|
||||
return '/';
|
||||
}
|
||||
return '/$rest';
|
||||
}
|
||||
return uri.path;
|
||||
}
|
||||
|
||||
String buildLocalizedPath(String localeCode, Uri uri) {
|
||||
final supportedLocaleCodes = LocaleRegistry.supportedLocaleCodes;
|
||||
final segments = uri.pathSegments;
|
||||
Iterable<String> restSegments = segments;
|
||||
if (segments.isNotEmpty) {
|
||||
final head = segments.first.toLowerCase();
|
||||
if (supportedLocaleCodes.contains(head)) {
|
||||
restSegments = segments.skip(1);
|
||||
}
|
||||
}
|
||||
final newPath = '/${[localeCode, ...restSegments].join('/')}';
|
||||
|
||||
// Return only the path and query part to avoid GoRouter confusion with full URLs
|
||||
final newUri = uri.replace(path: newPath);
|
||||
String result = newUri.path;
|
||||
if (newUri.hasQuery) {
|
||||
result += '?${newUri.query}';
|
||||
}
|
||||
if (newUri.hasFragment) {
|
||||
result += '#${newUri.fragment}';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String buildSigninRedirectPath(String localeCode, Uri uri) {
|
||||
final newPath = '/$localeCode/signin';
|
||||
final newUri = uri.replace(path: newPath);
|
||||
String result = newUri.path;
|
||||
if (newUri.hasQuery) {
|
||||
result += '?${newUri.query}';
|
||||
}
|
||||
if (newUri.hasFragment) {
|
||||
result += '#${newUri.fragment}';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String buildLocalizedHomePath(Uri uri, {String? preferredLocaleCode}) {
|
||||
final resolvedLocale =
|
||||
extractLocaleFromPath(uri) ??
|
||||
normalizeLocaleCode(preferredLocaleCode ?? resolvePreferredLocaleCode());
|
||||
return '/$resolvedLocale/dashboard';
|
||||
}
|
||||
|
||||
String buildLocalizedSigninPath(Uri uri, {String? preferredLocaleCode}) {
|
||||
final resolvedLocale =
|
||||
extractLocaleFromPath(uri) ??
|
||||
normalizeLocaleCode(preferredLocaleCode ?? resolvePreferredLocaleCode());
|
||||
return '/$resolvedLocale/signin';
|
||||
}
|
||||
54
baron-sso/userfront/lib/core/i18n/toml_asset_loader.dart
Normal file
54
baron-sso/userfront/lib/core/i18n/toml_asset_loader.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import '../../i18n_data.dart';
|
||||
|
||||
class TomlAssetLoader extends AssetLoader {
|
||||
const TomlAssetLoader();
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> load(String path, Locale locale) async {
|
||||
final languageCode = locale.languageCode.toLowerCase();
|
||||
return switch (languageCode) {
|
||||
'ko' => _normalizedKoStrings,
|
||||
'en' => _normalizedEnStrings,
|
||||
_ => _normalizedEnStrings,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
final Map<String, dynamic> _normalizedKoStrings = _normalizeFlatTranslations(
|
||||
koStrings,
|
||||
);
|
||||
final Map<String, dynamic> _normalizedEnStrings = _normalizeFlatTranslations(
|
||||
enStrings,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _normalizeFlatTranslations(Map<String, String> flatMap) =>
|
||||
Map.fromEntries(
|
||||
flatMap.entries
|
||||
.where((entry) => _isUserfrontTranslationKey(entry.key))
|
||||
.map(
|
||||
(entry) =>
|
||||
MapEntry(entry.key, _normalizeLocalizationValue(entry.value)),
|
||||
),
|
||||
);
|
||||
|
||||
bool _isUserfrontTranslationKey(String key) {
|
||||
return key.startsWith('domain.') ||
|
||||
key.startsWith('err.userfront.') ||
|
||||
key.startsWith('msg.userfront.') ||
|
||||
key.startsWith('ui.userfront.') ||
|
||||
key.startsWith('ui.common.');
|
||||
}
|
||||
|
||||
String _normalizeLocalizationValue(String value) {
|
||||
return value
|
||||
.replaceAllMapped(
|
||||
RegExp(r'\{\{\s*([a-zA-Z0-9_]+)\s*\}\}'),
|
||||
(match) => '{${match.group(1)}}',
|
||||
)
|
||||
.replaceAll(r'\\n', '\n')
|
||||
.replaceAll(r'\n', '\n');
|
||||
}
|
||||
Reference in New Issue
Block a user