dissolve app/ into repo root (D-056)
Single Flutter package at the repo root. All code, tests, assets, and platform directories moved from app/ to root. Package renamed from clide_app to clide — all imports rewritten. Merged pubspec combines core (ffi) and app (flutter, yaml, xterm) dependencies. Makefile simplified: no APP_PRESENT conditionals, no cd, no daemon lifecycle. 317 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:clide/kernel/src/i18n/fallback_chain.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Loads catalog JSON for a given `(namespace, locale)` pair.
|
||||
///
|
||||
/// The file format (mirrors fframe verbatim):
|
||||
/// `{namespace}_{lang}_{country}.json` — or `{namespace}_{lang}.json`
|
||||
/// Content: `{ "key": { "translation": "...", ...extras }, ... }`.
|
||||
///
|
||||
/// Two reader shapes:
|
||||
/// * Asset bundle (built-in catalogs shipped under `lib/kernel/src/i18n/catalog/`).
|
||||
/// * Filesystem (third-party extensions under `~/.clide/extensions/<id>/`).
|
||||
///
|
||||
/// Missing files return an empty map — not an error. The fallback chain
|
||||
/// walker handles "nothing for this locale" by trying the next one.
|
||||
abstract class CatalogLoader {
|
||||
Future<Map<String, Object?>> load(String namespace, Locale locale);
|
||||
}
|
||||
|
||||
class AssetCatalogLoader implements CatalogLoader {
|
||||
AssetCatalogLoader({required this.bundle, this.rootDir = _defaultRoot});
|
||||
|
||||
final AssetBundle bundle;
|
||||
final String rootDir;
|
||||
|
||||
static const String _defaultRoot = 'lib/kernel/src/i18n/catalog';
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>> load(String namespace, Locale locale) async {
|
||||
final suffix = FallbackChain.filenameSuffix(locale);
|
||||
final path = '$rootDir/${namespace}_$suffix.json';
|
||||
try {
|
||||
final text = await bundle.loadString(path);
|
||||
if (text.trim().isEmpty) return const {};
|
||||
final obj = jsonDecode(text);
|
||||
if (obj is Map) return obj.cast<String, Object?>();
|
||||
return const {};
|
||||
} on FlutterError {
|
||||
// Asset missing. Return empty map; fallback chain handles the miss.
|
||||
return const {};
|
||||
} on FormatException {
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FileCatalogLoader implements CatalogLoader {
|
||||
const FileCatalogLoader({required this.rootDir});
|
||||
|
||||
final Directory rootDir;
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>> load(String namespace, Locale locale) async {
|
||||
final suffix = FallbackChain.filenameSuffix(locale);
|
||||
final f = File('${rootDir.path}/${namespace}_$suffix.json');
|
||||
if (!await f.exists()) return const {};
|
||||
try {
|
||||
final text = await f.readAsString();
|
||||
if (text.trim().isEmpty) return const {};
|
||||
final obj = jsonDecode(text);
|
||||
if (obj is Map) return obj.cast<String, Object?>();
|
||||
} on FormatException {
|
||||
// malformed — return empty; caller will fall back.
|
||||
}
|
||||
return const {};
|
||||
}
|
||||
}
|
||||
|
||||
/// Preloaded-in-memory loader for tests and synthesized catalogs.
|
||||
class InMemoryCatalogLoader implements CatalogLoader {
|
||||
InMemoryCatalogLoader(this._map);
|
||||
|
||||
final Map<String, Map<Locale, Map<String, Object?>>> _map;
|
||||
|
||||
@override
|
||||
Future<Map<String, Object?>> load(String namespace, Locale locale) async {
|
||||
final byNs = _map[namespace];
|
||||
if (byNs == null) return const {};
|
||||
// match by canonical comparison so Locale("en") == registered Locale("en")
|
||||
for (final entry in byNs.entries) {
|
||||
if (_eq(entry.key, locale)) return entry.value;
|
||||
}
|
||||
return const {};
|
||||
}
|
||||
|
||||
static bool _eq(Locale a, Locale b) =>
|
||||
a.languageCode == b.languageCode && a.countryCode == b.countryCode;
|
||||
}
|
||||
Reference in New Issue
Block a user