feat: AdvancedLocalization Init

This commit is contained in:
2024-05-03 22:35:31 +08:00
parent 4e506a11a0
commit 9a875fc5a5
14 changed files with 1175 additions and 38 deletions

View File

@ -0,0 +1,102 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/ui/home/localization/advanced_localization_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:super_sliver_list/super_sliver_list.dart';
class AdvancedLocalizationUI extends HookConsumerWidget {
const AdvancedLocalizationUI({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(advancedLocalizationUIModelProvider);
final homeUIState = ref.watch(homeUIModelProvider);
return makeDefaultPage(
title: "高级汉化 -> ${homeUIState.scInstalledPath}",
context,
content: state.workingText.isNotEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const ProgressRing(),
const SizedBox(height: 12),
Text(state.workingText),
],
),
)
: _makeBody(context, homeUIState, state, ref));
}
Widget _makeBody(BuildContext context, HomeUIModelState homeUIState,
AdvancedLocalizationUIState state, WidgetRef ref) {
return AlignedGridView.count(
crossAxisCount: 3,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
padding: const EdgeInsets.all(12),
itemBuilder: (BuildContext context, int index) {
final item = state.classMap!.values.elementAt(index);
return Container(
padding: const EdgeInsets.only(top: 12, bottom: 12),
decoration: BoxDecoration(
color: Colors.white.withOpacity(.05),
borderRadius: BorderRadius.circular(4),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
child: Row(
children: [
Expanded(
child: Text(
"${item.className}",
style: const TextStyle(
fontSize: 16, fontWeight: FontWeight.bold),
)),
Text(
"${item.valuesMap.length}",
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(.6),
),
),
],
),
),
Container(
margin: const EdgeInsets.only(top: 6, bottom: 12),
width: MediaQuery.of(context).size.width,
height: 1,
color: Colors.white.withOpacity(.1),
),
SizedBox(
height: 160,
child: SuperListView.builder(
itemCount: item.valuesMap.length,
padding: const EdgeInsets.only(left: 12, right: 12),
itemBuilder: (BuildContext context, int index) {
final itemKey = item.valuesMap.keys.elementAt(index);
return Text(
"${item.valuesMap[itemKey]}",
maxLines: 1,
style: const TextStyle(
fontSize: 12,
overflow: TextOverflow.ellipsis,
),
);
},
),
),
],
),
);
},
itemCount: state.classMap?.length ?? 0,
);
}
}

View File

@ -0,0 +1,170 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/app_advanced_localization_data.dart';
import 'package:starcitizen_doctor/provider/unp4kc.dart';
import '../home_ui_model.dart';
import 'localization_ui_model.dart';
part 'advanced_localization_ui_model.g.dart';
part 'advanced_localization_ui_model.freezed.dart';
@freezed
class AdvancedLocalizationUIState with _$AdvancedLocalizationUIState {
factory AdvancedLocalizationUIState({
@Default("") String workingText,
Map<String, AppAdvancedLocalizationClassKeysData>? classMap,
String? p4kGlobalIni,
String? serverGlobalIni,
}) = _AdvancedLocalizationUIState;
}
@riverpod
class AdvancedLocalizationUIModel extends _$AdvancedLocalizationUIModel {
@override
AdvancedLocalizationUIState build() {
final localizationUIState = ref.read(localizationUIModelProvider);
final localizationUIModel = ref.read(localizationUIModelProvider.notifier);
state = AdvancedLocalizationUIState(classMap: {});
_init(localizationUIState, localizationUIModel);
return state;
}
Future<void> _init(LocalizationUIState localizationUIState,
LocalizationUIModel localizationUIModel) async {
final (p4kGlobalIni, serverGlobalIni) =
await _readIni(localizationUIState, localizationUIModel);
final ald = await _readClassJson();
if (ald.classKeys == null) return;
state = state.copyWith(workingText: "正在分类 ...");
final m = await compute(_doClassIni, (ald, p4kGlobalIni, serverGlobalIni));
state = state.copyWith(
workingText: "",
p4kGlobalIni: p4kGlobalIni,
serverGlobalIni: serverGlobalIni,
classMap: m);
}
static Map<String, AppAdvancedLocalizationClassKeysData> _doClassIni(
(
AppAdvancedLocalizationData ald,
String p4kGlobalIni,
String serverGlobalIni
) v,
) {
final (
AppAdvancedLocalizationData ald,
String p4kGlobalIni,
String serverGlobalIni,
) = v;
final unLocalization = AppAdvancedLocalizationClassKeysData(
id: "un_localization",
className: "未汉化",
keys: [],
);
final unClass = AppAdvancedLocalizationClassKeysData(
id: "un_class",
className: "未分类",
keys: [],
);
final classMap = <String, AppAdvancedLocalizationClassKeysData>{
for (final keys in ald.classKeys!) keys.id ?? "": keys,
};
final p4kIniMap = readIniAsMap(p4kGlobalIni);
final serverIniMap = readIniAsMap(serverGlobalIni);
var regexList = classMap.values
.expand((c) => c.keys!.map((k) => MapEntry(c, RegExp(k))))
.toList();
iniKeysLoop:
for (var p4kIniKey in p4kIniMap.keys) {
final serverValue = serverIniMap[p4kIniKey];
if (serverValue == null) {
unLocalization.valuesMap[p4kIniKey] = p4kIniMap[p4kIniKey] ?? "";
continue iniKeysLoop;
} else {
for (var item in regexList) {
if (item.value.hasMatch(p4kIniKey)) {
item.key.valuesMap[p4kIniKey] = serverValue;
serverIniMap.remove(p4kIniKey);
continue iniKeysLoop;
}
}
}
}
if (unLocalization.valuesMap.isNotEmpty) {
classMap[unLocalization.id!] = unLocalization;
}
if (serverIniMap.isNotEmpty) {
for (var element in serverIniMap.keys) {
unClass.valuesMap[element] = serverIniMap[element] ?? "";
}
classMap[unClass.id!] = unClass;
}
return classMap;
}
static Map<String, String> readIniAsMap(String iniString) {
final iniMap = <String, String>{};
for (final line in iniString.split("\n")) {
final index = line.indexOf("=");
if (index == -1) continue;
final key = line.substring(0, index).trim();
final value = line.substring(index + 1).trim();
iniMap[key] = value;
}
return iniMap;
}
Future<AppAdvancedLocalizationData> _readClassJson() async {
final s = await rootBundle.loadString("assets/advanced_localization.json");
return AppAdvancedLocalizationData.fromJson(json.decode(s));
}
Future<(String, String)> _readIni(LocalizationUIState localizationUIState,
LocalizationUIModel localizationUIModel) async {
final homeUIState = ref.read(homeUIModelProvider);
final gameDir = homeUIState.scInstalledPath;
if (gameDir == null) return ("", "");
state = state.copyWith(workingText: "读取 p4k 文件 ...");
final p4kGlobalIni = await readEnglishInI(gameDir);
dPrint("read p4kGlobalIni => ${p4kGlobalIni.length}");
state = state.copyWith(workingText: "获取汉化文本 ...");
final apiLocalizationData =
localizationUIState.apiLocalizationData?.values.firstOrNull;
if (apiLocalizationData == null) return ("", "");
final file = File(
"${localizationUIModel.getDownloadDir().absolute.path}\\${apiLocalizationData.versionName}.sclang");
if (!await file.exists()) {
await localizationUIModel.downloadLocalizationFile(
file, apiLocalizationData);
}
final serverGlobalIni =
(await compute(LocalizationUIModel.readArchive, file.absolute.path))
.toString();
dPrint("read serverGlobalIni => ${serverGlobalIni.length}");
return (p4kGlobalIni, serverGlobalIni);
}
Future<String> readEnglishInI(String gameDir) async {
final data = await Unp4kCModel.unp4kTools(
appGlobalState.applicationBinaryModuleDir!, [
"extract_memory",
"$gameDir\\Data.p4k",
"Data\\Localization\\english\\global.ini"
]);
final iniData = String.fromCharCodes(data);
return iniData;
}
}

View File

@ -0,0 +1,236 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'advanced_localization_ui_model.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
/// @nodoc
mixin _$AdvancedLocalizationUIState {
String get workingText => throw _privateConstructorUsedError;
Map<String, AppAdvancedLocalizationClassKeysData>? get classMap =>
throw _privateConstructorUsedError;
String? get p4kGlobalIni => throw _privateConstructorUsedError;
String? get serverGlobalIni => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$AdvancedLocalizationUIStateCopyWith<AdvancedLocalizationUIState>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $AdvancedLocalizationUIStateCopyWith<$Res> {
factory $AdvancedLocalizationUIStateCopyWith(
AdvancedLocalizationUIState value,
$Res Function(AdvancedLocalizationUIState) then) =
_$AdvancedLocalizationUIStateCopyWithImpl<$Res,
AdvancedLocalizationUIState>;
@useResult
$Res call(
{String workingText,
Map<String, AppAdvancedLocalizationClassKeysData>? classMap,
String? p4kGlobalIni,
String? serverGlobalIni});
}
/// @nodoc
class _$AdvancedLocalizationUIStateCopyWithImpl<$Res,
$Val extends AdvancedLocalizationUIState>
implements $AdvancedLocalizationUIStateCopyWith<$Res> {
_$AdvancedLocalizationUIStateCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? workingText = null,
Object? classMap = freezed,
Object? p4kGlobalIni = freezed,
Object? serverGlobalIni = freezed,
}) {
return _then(_value.copyWith(
workingText: null == workingText
? _value.workingText
: workingText // ignore: cast_nullable_to_non_nullable
as String,
classMap: freezed == classMap
? _value.classMap
: classMap // ignore: cast_nullable_to_non_nullable
as Map<String, AppAdvancedLocalizationClassKeysData>?,
p4kGlobalIni: freezed == p4kGlobalIni
? _value.p4kGlobalIni
: p4kGlobalIni // ignore: cast_nullable_to_non_nullable
as String?,
serverGlobalIni: freezed == serverGlobalIni
? _value.serverGlobalIni
: serverGlobalIni // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$AdvancedLocalizationUIStateImplCopyWith<$Res>
implements $AdvancedLocalizationUIStateCopyWith<$Res> {
factory _$$AdvancedLocalizationUIStateImplCopyWith(
_$AdvancedLocalizationUIStateImpl value,
$Res Function(_$AdvancedLocalizationUIStateImpl) then) =
__$$AdvancedLocalizationUIStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String workingText,
Map<String, AppAdvancedLocalizationClassKeysData>? classMap,
String? p4kGlobalIni,
String? serverGlobalIni});
}
/// @nodoc
class __$$AdvancedLocalizationUIStateImplCopyWithImpl<$Res>
extends _$AdvancedLocalizationUIStateCopyWithImpl<$Res,
_$AdvancedLocalizationUIStateImpl>
implements _$$AdvancedLocalizationUIStateImplCopyWith<$Res> {
__$$AdvancedLocalizationUIStateImplCopyWithImpl(
_$AdvancedLocalizationUIStateImpl _value,
$Res Function(_$AdvancedLocalizationUIStateImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? workingText = null,
Object? classMap = freezed,
Object? p4kGlobalIni = freezed,
Object? serverGlobalIni = freezed,
}) {
return _then(_$AdvancedLocalizationUIStateImpl(
workingText: null == workingText
? _value.workingText
: workingText // ignore: cast_nullable_to_non_nullable
as String,
classMap: freezed == classMap
? _value._classMap
: classMap // ignore: cast_nullable_to_non_nullable
as Map<String, AppAdvancedLocalizationClassKeysData>?,
p4kGlobalIni: freezed == p4kGlobalIni
? _value.p4kGlobalIni
: p4kGlobalIni // ignore: cast_nullable_to_non_nullable
as String?,
serverGlobalIni: freezed == serverGlobalIni
? _value.serverGlobalIni
: serverGlobalIni // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
class _$AdvancedLocalizationUIStateImpl
with DiagnosticableTreeMixin
implements _AdvancedLocalizationUIState {
_$AdvancedLocalizationUIStateImpl(
{this.workingText = "",
final Map<String, AppAdvancedLocalizationClassKeysData>? classMap,
this.p4kGlobalIni,
this.serverGlobalIni})
: _classMap = classMap;
@override
@JsonKey()
final String workingText;
final Map<String, AppAdvancedLocalizationClassKeysData>? _classMap;
@override
Map<String, AppAdvancedLocalizationClassKeysData>? get classMap {
final value = _classMap;
if (value == null) return null;
if (_classMap is EqualUnmodifiableMapView) return _classMap;
// ignore: implicit_dynamic_type
return EqualUnmodifiableMapView(value);
}
@override
final String? p4kGlobalIni;
@override
final String? serverGlobalIni;
@override
String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
return 'AdvancedLocalizationUIState(workingText: $workingText, classMap: $classMap, p4kGlobalIni: $p4kGlobalIni, serverGlobalIni: $serverGlobalIni)';
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty('type', 'AdvancedLocalizationUIState'))
..add(DiagnosticsProperty('workingText', workingText))
..add(DiagnosticsProperty('classMap', classMap))
..add(DiagnosticsProperty('p4kGlobalIni', p4kGlobalIni))
..add(DiagnosticsProperty('serverGlobalIni', serverGlobalIni));
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$AdvancedLocalizationUIStateImpl &&
(identical(other.workingText, workingText) ||
other.workingText == workingText) &&
const DeepCollectionEquality().equals(other._classMap, _classMap) &&
(identical(other.p4kGlobalIni, p4kGlobalIni) ||
other.p4kGlobalIni == p4kGlobalIni) &&
(identical(other.serverGlobalIni, serverGlobalIni) ||
other.serverGlobalIni == serverGlobalIni));
}
@override
int get hashCode => Object.hash(
runtimeType,
workingText,
const DeepCollectionEquality().hash(_classMap),
p4kGlobalIni,
serverGlobalIni);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$AdvancedLocalizationUIStateImplCopyWith<_$AdvancedLocalizationUIStateImpl>
get copyWith => __$$AdvancedLocalizationUIStateImplCopyWithImpl<
_$AdvancedLocalizationUIStateImpl>(this, _$identity);
}
abstract class _AdvancedLocalizationUIState
implements AdvancedLocalizationUIState {
factory _AdvancedLocalizationUIState(
{final String workingText,
final Map<String, AppAdvancedLocalizationClassKeysData>? classMap,
final String? p4kGlobalIni,
final String? serverGlobalIni}) = _$AdvancedLocalizationUIStateImpl;
@override
String get workingText;
@override
Map<String, AppAdvancedLocalizationClassKeysData>? get classMap;
@override
String? get p4kGlobalIni;
@override
String? get serverGlobalIni;
@override
@JsonKey(ignore: true)
_$$AdvancedLocalizationUIStateImplCopyWith<_$AdvancedLocalizationUIStateImpl>
get copyWith => throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,28 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'advanced_localization_ui_model.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$advancedLocalizationUIModelHash() =>
r'2bb7dfc3cd8d45ecf5056083627136ad5cb7a285';
/// See also [AdvancedLocalizationUIModel].
@ProviderFor(AdvancedLocalizationUIModel)
final advancedLocalizationUIModelProvider = AutoDisposeNotifierProvider<
AdvancedLocalizationUIModel, AdvancedLocalizationUIState>.internal(
AdvancedLocalizationUIModel.new,
name: r'advancedLocalizationUIModelProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$advancedLocalizationUIModelHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$AdvancedLocalizationUIModel
= AutoDisposeNotifier<AdvancedLocalizationUIState>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View File

@ -2,6 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import 'package:flutter_tilt/flutter_tilt.dart';
import 'package:go_router/go_router.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
import 'package:starcitizen_doctor/ui/tools/tools_ui_model.dart';
@ -145,7 +146,7 @@ class LocalizationDialogUI extends HookConsumerWidget {
],
context,
gridViewMode: true),
makeToolsListContainer(context, model),
makeToolsListContainer(context, model, state),
],
),
),
@ -445,8 +446,8 @@ class LocalizationDialogUI extends HookConsumerWidget {
}
}
Widget makeToolsListContainer(
BuildContext context, LocalizationUIModel model) {
Widget makeToolsListContainer(BuildContext context, LocalizationUIModel model,
LocalizationUIState state) {
final toolsMenu = {
"launcher_mod": (
const Icon(FluentIcons.c_plus_plus, size: 24),
@ -462,21 +463,29 @@ class LocalizationDialogUI extends HookConsumerWidget {
),
};
final enableTap = state.workingVersion.isEmpty;
return makeListContainer(
"汉化工具",
[
for (final item in toolsMenu.entries)
Tilt(
disable: !enableTap,
shadowConfig: const ShadowConfig(maxIntensity: .3),
borderRadius: BorderRadius.circular(7),
child: GestureDetector(
onTap: () async {
switch (item.key) {
case "launcher_mod":
ToolsUIModel.rsiEnhance(context);
break;
}
},
onTap: enableTap
? () async {
switch (item.key) {
case "launcher_mod":
ToolsUIModel.rsiEnhance(context);
break;
case "advanced":
context.push("/index/advanced_localization");
break;
}
}
: null,
child: Container(
decoration: BoxDecoration(
color: FluentTheme.of(context).cardColor,

View File

@ -17,7 +17,6 @@ import 'package:starcitizen_doctor/common/utils/log.dart';
import 'package:starcitizen_doctor/common/utils/provider.dart';
import 'package:starcitizen_doctor/data/sc_localization_data.dart';
import 'package:starcitizen_doctor/generated/no_l10n_strings.dart';
import 'package:starcitizen_doctor/provider/unp4kc.dart';
import 'package:starcitizen_doctor/ui/home/home_ui_model.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -48,6 +47,8 @@ class LocalizationUIModel extends _$LocalizationUIModel {
Directory get _downloadDir =>
Directory("${appGlobalState.applicationSupportDir}\\Localizations");
Directory getDownloadDir() => _downloadDir;
Directory get _scDataDir =>
Directory("${ref.read(homeUIModelProvider).scInstalledPath}\\data");
@ -79,17 +80,6 @@ class LocalizationUIModel extends _$LocalizationUIModel {
await _loadData();
}
readEnglishInI() async {
final data = await Unp4kCModel.unp4kTools(
appGlobalState.applicationBinaryModuleDir!, [
"extract_memory",
"$_scInstallPath\\Data.p4k",
"Data\\Localization\\english\\global.ini"
]);
final iniData = String.fromCharCodes(data);
dPrint("read english ini => ${iniData.length}");
}
final Map<String, Map<String, ScLocalizationData>>
_allVersionLocalizationData = {};
@ -268,27 +258,20 @@ class LocalizationUIModel extends _$LocalizationUIModel {
BuildContext context, ScLocalizationData value) {
return () async {
AnalyticsApi.touch("install_localization");
final downloadUrl =
"${URLConf.gitlabLocalizationUrl}/archive/${value.versionName}.tar.gz";
final savePath =
File("${_downloadDir.absolute.path}\\${value.versionName}.sclang");
try {
state = state.copyWith(workingVersion: value.versionName!);
if (!await savePath.exists()) {
// download
dPrint("downloading file to $savePath");
final r = await RSHttp.get(downloadUrl);
if (r.statusCode == 200 && r.data != null) {
await savePath.writeAsBytes(r.data!);
} else {
throw "statusCode Error : ${r.statusCode}";
}
await downloadLocalizationFile(savePath, value);
} else {
dPrint("use cache $savePath");
}
await Future.delayed(const Duration(milliseconds: 300));
// check file
final globalIni = await compute(_readArchive, savePath.absolute.path);
final globalIni = await compute(readArchive, savePath.absolute.path);
if (globalIni.isEmpty) {
throw S.current.localization_info_corrupted_file;
}
@ -303,7 +286,20 @@ class LocalizationUIModel extends _$LocalizationUIModel {
};
}
static StringBuffer _readArchive(String savePath) {
Future<void> downloadLocalizationFile(
File savePath, ScLocalizationData value) async {
dPrint("downloading file to $savePath");
final downloadUrl =
"${URLConf.gitlabLocalizationUrl}/archive/${value.versionName}.tar.gz";
final r = await RSHttp.get(downloadUrl);
if (r.statusCode == 200 && r.data != null) {
await savePath.writeAsBytes(r.data!);
} else {
throw "statusCode Error : ${r.statusCode}";
}
}
static StringBuffer readArchive(String savePath) {
final inputStream = InputFileStream(savePath);
final archive =
TarDecoder().decodeBytes(GZipDecoder().decodeBuffer(inputStream));

View File

@ -7,7 +7,7 @@ part of 'localization_ui_model.dart';
// **************************************************************************
String _$localizationUIModelHash() =>
r'a4e33b337db587ad3e766450d9bd8ac4aa7b4c49';
r'ed47da78fdc6adac904a17909f111640ac84563e';
/// See also [LocalizationUIModel].
@ProviderFor(LocalizationUIModel)