forked from baron/baron-sso
Merge pull request 'feature/i18n-enhancement' (#521) from feature/i18n-enhancement into dev
Reviewed-on: baron/baron-sso#521
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
const { spawnSync } = require("child_process");
|
||||||
|
|
||||||
const ROOT = process.cwd();
|
const ROOT = process.cwd();
|
||||||
const LOCALES_DIR = path.join(ROOT, "locales");
|
const LOCALES_DIR = path.join(ROOT, "locales");
|
||||||
@@ -84,3 +85,12 @@ const output = [
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
fs.writeFileSync(OUT_PATH, output, "utf8");
|
fs.writeFileSync(OUT_PATH, output, "utf8");
|
||||||
|
|
||||||
|
const formatResult = spawnSync("dart", ["format", OUT_PATH], {
|
||||||
|
cwd: ROOT,
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (formatResult.status !== 0) {
|
||||||
|
process.exit(formatResult.status ?? 1);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,59 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:toml/toml.dart';
|
import '../../i18n_data.dart';
|
||||||
|
|
||||||
class TomlAssetLoader extends AssetLoader {
|
class TomlAssetLoader extends AssetLoader {
|
||||||
const TomlAssetLoader();
|
const TomlAssetLoader();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, dynamic>> load(String path, Locale locale) async {
|
Future<Map<String, dynamic>> load(String path, Locale locale) async {
|
||||||
final assetPath = '$path/${locale.languageCode}.toml';
|
final languageCode = locale.languageCode.toLowerCase();
|
||||||
try {
|
final source = switch (languageCode) {
|
||||||
final content = await rootBundle.loadString(assetPath);
|
'ko' => koStrings,
|
||||||
final document = TomlDocument.parse(content);
|
'en' => enStrings,
|
||||||
return document.toMap();
|
_ => enStrings,
|
||||||
} catch (e) {
|
};
|
||||||
// 로딩 실패 시 빈 맵을 반환해 렌더링을 지속합니다.
|
return _expandFlatTranslations(source);
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _expandFlatTranslations(Map<String, String> flatMap) {
|
||||||
|
final nested = <String, dynamic>{};
|
||||||
|
for (final entry in flatMap.entries) {
|
||||||
|
final key = entry.key;
|
||||||
|
if (key.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final segments = key.split('.');
|
||||||
|
Map<String, dynamic> cursor = nested;
|
||||||
|
for (var index = 0; index < segments.length; index++) {
|
||||||
|
final segment = segments[index];
|
||||||
|
if (segment.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final isLeaf = index == segments.length - 1;
|
||||||
|
if (isLeaf) {
|
||||||
|
cursor[segment] = _normalizeLocalizationValue(entry.value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final next = cursor.putIfAbsent(segment, () => <String, dynamic>{});
|
||||||
|
if (next is Map<String, dynamic>) {
|
||||||
|
cursor = next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final replacement = <String, dynamic>{};
|
||||||
|
cursor[segment] = replacement;
|
||||||
|
cursor = replacement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nested;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _normalizeLocalizationValue(String value) {
|
||||||
|
return value.replaceAllMapped(
|
||||||
|
RegExp(r'\{\{[[:space:]]*([a-zA-Z0-9_]+)[[:space:]]*\}\}'),
|
||||||
|
(match) => '{${match.group(1)}}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,6 +57,40 @@ class _ConsentScreenState extends State<ConsentScreen> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _renderConsentText(String key, {String? fallback}) {
|
||||||
|
return tr(
|
||||||
|
key,
|
||||||
|
fallback: fallback,
|
||||||
|
).replaceAll(r'\\n', '\n').replaceAll(r'\n', '\n').replaceAll('\\\n', '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _renderScopeCountLabel(int count) {
|
||||||
|
return tr(
|
||||||
|
'msg.userfront.consent.scope_count',
|
||||||
|
fallback: 'Total {{count}}',
|
||||||
|
params: {'count': '$count'},
|
||||||
|
).replaceAll('{$count}', '$count');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _scopeDisplayLabel(String scope) {
|
||||||
|
if (scope == 'offline_access') {
|
||||||
|
return 'offline access';
|
||||||
|
}
|
||||||
|
return scope.replaceAll('_', ' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _renderClientIdLabel(String clientId) {
|
||||||
|
final raw = tr(
|
||||||
|
'msg.userfront.consent.client_id',
|
||||||
|
fallback: 'Client ID: {{id}}',
|
||||||
|
);
|
||||||
|
final normalized = raw
|
||||||
|
.replaceAll('{{id}}', '')
|
||||||
|
.replaceAll('{id}', '')
|
||||||
|
.trimRight();
|
||||||
|
return '$normalized $clientId';
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchConsentInfo() async {
|
Future<void> _fetchConsentInfo() async {
|
||||||
try {
|
try {
|
||||||
final info = await AuthProxyService.getConsentInfo(
|
final info = await AuthProxyService.getConsentInfo(
|
||||||
@@ -271,7 +305,7 @@ class _ConsentScreenState extends State<ConsentScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
tr('msg.userfront.consent.description'),
|
_renderConsentText('msg.userfront.consent.description'),
|
||||||
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
@@ -318,11 +352,7 @@ class _ConsentScreenState extends State<ConsentScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
tr(
|
_renderClientIdLabel(clientId),
|
||||||
'msg.userfront.consent.client_id',
|
|
||||||
fallback: 'Client ID: {{id}}',
|
|
||||||
params: {'id': clientId},
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.grey[500],
|
color: Colors.grey[500],
|
||||||
@@ -349,11 +379,7 @@ class _ConsentScreenState extends State<ConsentScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
tr(
|
_renderScopeCountLabel(requestedScopes.length),
|
||||||
'msg.userfront.consent.scope_count',
|
|
||||||
fallback: 'Total {{count}}',
|
|
||||||
params: {'count': '${requestedScopes.length}'},
|
|
||||||
),
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
@@ -371,7 +397,7 @@ class _ConsentScreenState extends State<ConsentScreen> {
|
|||||||
|
|
||||||
return CheckboxListTile(
|
return CheckboxListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
scope, // 스코프 키 (예: openid)
|
_scopeDisplayLabel(scope),
|
||||||
style: const TextStyle(fontWeight: FontWeight.w500),
|
style: const TextStyle(fontWeight: FontWeight.w500),
|
||||||
),
|
),
|
||||||
subtitle: Text(description),
|
subtitle: Text(description),
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user