Working with multilingual asset files in Flutter can become surprisingly painful when filenames contain Unicode characters.
Files like:
Malmö_2026.json
Ålesund_2026.json
İstanbul_2026.json
may physically exist inside your assets folder, yet Flutter fails to load them correctly using:
rootBundle.loadString(path)The reason is Unicode normalization.
Different operating systems store Unicode filenames differently:
NFC (composed form)
NFD (decomposed form)
macOS commonly stores filenames in decomposed form, while Flutter often expects exact matching. This creates silent asset-loading failures that are extremely difficult to debug — especially inside FlutterFlow custom actions.
After dealing with hundreds of international JSON files for a prayer time application, I built a generic Unicode Asset Resolver that automatically handles these filename inconsistencies.
You simply pass the filename.
The resolver handles the rest.
Features
✅ Unicode-safe asset loading
✅ NFC ↔ NFD normalization support
✅ FlutterFlow compatible
✅ Automatic AssetManifest scanning
✅ Smart fallback matching
✅ Cached asset lookup for performance
✅ Works with JSON, TXT, CSV, and other assets
✅ Cross-platform compatibility
Unicode Asset Resolver
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
class UnicodeAssetResolver {
UnicodeAssetResolver._();
static List<String>? _cachedAssets;
/// Convert common Unicode characters to decomposed form (NFD)
static String toNfd(String input) {
const decomposed = <String, String>{
'å': 'a\u030A',
'ä': 'a\u0308',
'ö': 'o\u0308',
'é': 'e\u0301',
'è': 'e\u0300',
'ü': 'u\u0308',
'Å': 'A\u030A',
'Ä': 'A\u0308',
'Ö': 'O\u0308',
'É': 'E\u0301',
'İ': 'I\u0307',
};
var result = input;
decomposed.forEach((composed, decomposed) {
result = result.replaceAll(composed, decomposed);
});
return result;
}
static bool _fileNamesMatch(String a, String b) {
final left = a.toLowerCase();
final right = b.toLowerCase();
if (left == right) return true;
if (toNfd(left) == right) return true;
if (left == toNfd(right)) return true;
if (toNfd(left) == toNfd(right)) return true;
return false;
}
static Future<List<String>> _allAssets() async {
if (_cachedAssets != null) {
return _cachedAssets!;
}
final manifest =
await AssetManifest.loadFromAssetBundle(rootBundle);
_cachedAssets = manifest.listAssets();
return _cachedAssets!;
}
/// Resolve any asset path using only the filename
static Future<String?> resolvePath({
required String fileName,
String assetRoot = 'assets',
}) async {
final normalizedFile = fileName.trim();
final normalizedFileNfd = toNfd(normalizedFile);
final assets = await _allAssets();
/// Direct path attempts
final directPaths = <String>[
'$assetRoot/$normalizedFile',
'$assetRoot/$normalizedFileNfd',
];
for (final path in directPaths) {
if (assets.contains(path)) {
return path;
}
}
/// Deep fallback search
for (final asset in assets) {
if (!asset.startsWith(assetRoot)) continue;
final base = asset.split('/').last;
if (_fileNamesMatch(base, normalizedFile)) {
return asset;
}
}
return null;
}
/// Load any asset as string
static Future<String> loadString({
required String fileName,
String assetRoot = 'assets',
}) async {
final path = await resolvePath(
fileName: fileName,
assetRoot: assetRoot,
);
if (path == null) {
throw FlutterError(
'Unicode asset not found: $fileName',
);
}
return rootBundle.loadString(path);
}
/// Clear cached asset manifest
static void clearCache() {
_cachedAssets = null;
}
}Usage
Basic Example
final json = await UnicodeAssetResolver.loadString(
fileName: 'Malmö_2026.json',
assetRoot: 'assets/jsons',
);FlutterFlow Custom Action Example
final data = await UnicodeAssetResolver.loadString(
fileName: '${cityName}_2026.json',
assetRoot: 'assets/jsons/2026',
);Why This Utility Matters
This utility was built from a real-world production problem involving thousands of multilingual JSON files.
Instead of:
renaming files manually
removing Unicode characters
creating duplicate datasets
hardcoding paths
this resolver allows Flutter applications to work naturally with international filenames.
Perfect for:
Prayer time applications
Localization systems
Offline datasets
Multilingual apps
International city databases
Translation packages