blob: 46c57eba8f091b72ddc4be748a7ce7a56a315263 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// A library for general helper code associated with the intl library
/// rather than confined to specific parts of it.
library intl_helpers;
import 'dart:async';
import 'global_state.dart' as global_state;
/// Type for the callback action when a message translation is not found.
typedef MessageIfAbsent = String Function(
String messageText, List<Object> args);
/// This is used as a marker for a locale data map that hasn't been initialized,
/// and will throw an exception on any usage that isn't the fallback
/// patterns/symbols provided.
class UninitializedLocaleData<F> implements MessageLookup {
final String message;
final F fallbackData;
UninitializedLocaleData(this.message, this.fallbackData);
bool _isFallback(String key) => canonicalizedLocale(key) == 'en_US';
F operator [](String key) =>
_isFallback(key) ? fallbackData : _throwException();
/// If a message is looked up before any locale initialization, record it,
/// and throw an exception with that information once the locale is
/// initialized.
///
/// Set this during development to find issues with race conditions between
/// message caching and locale initialization. If the results of Intl.message
/// calls aren't being cached, then this won't help.
///
/// There's nothing that actually sets this, so checking this requires
/// patching the code here.
static final bool throwOnFallback = false;
/// The messages that were called before the locale was initialized.
final List<String> _badMessages = [];
void _reportErrors() {
if (throwOnFallback && _badMessages.isNotEmpty) {
throw StateError(
'The following messages were called before locale initialization:'
' $_uninitializedMessages');
}
}
String get _uninitializedMessages =>
(_badMessages.toSet().toList()..sort()).join('\n ');
String? lookupMessage(String? messageText, String? locale, String? name,
List<Object>? args, String? meaning,
{MessageIfAbsent? ifAbsent}) {
if (throwOnFallback) {
_badMessages.add((name ?? messageText)!);
}
return messageText;
}
/// Given an initial locale or null, returns the locale that will be used
/// for messages.
String findLocale(String? locale) =>
locale ?? global_state.getCurrentLocale();
List<String> get keys => _throwException() as List<String>;
bool containsKey(String key) {
if (!_isFallback(key)) {
_throwException();
}
return true;
}
F _throwException() {
throw LocaleDataException('Locale data has not been initialized'
', call $message.');
}
void addLocale(String localeName, Function findLocale) => _throwException();
}
abstract class MessageLookup {
String? lookupMessage(String? messageText, String? locale, String? name,
List<Object>? args, String? meaning,
{MessageIfAbsent? ifAbsent});
void addLocale(String localeName, Function findLocale);
}
class LocaleDataException implements Exception {
final String message;
LocaleDataException(this.message);
String toString() => 'LocaleDataException: $message';
}
/// An abstract superclass for data readers to keep the type system happy.
abstract class LocaleDataReader {
Future<String> read(String locale);
}
/// The internal mechanism for looking up messages. We expect this to be set
/// by the implementing package so that we're not dependent on its
/// implementation.
MessageLookup messageLookup =
UninitializedLocaleData('initializeMessages(<locale>)', null);
/// Initialize the message lookup mechanism. This is for internal use only.
/// User applications should import `message_lookup_by_library.dart` and call
/// `initializeMessages`
void initializeInternalMessageLookup(Function lookupFunction) {
if (messageLookup is UninitializedLocaleData<dynamic>) {
// This line has to be precisely this way to work around an analyzer crash.
(messageLookup as UninitializedLocaleData<dynamic>)._reportErrors();
messageLookup = lookupFunction();
}
}
/// If a message is a string literal without interpolation, compute
/// a name based on that and the meaning, if present.
// NOTE: THIS LOGIC IS DUPLICATED IN intl_translation AND THE TWO MUST MATCH.
String computeMessageName(String? name, String text, String? meaning) {
if (name != null && name != '') return name;
return meaning == null ? text : '${text}_$meaning';
}
String canonicalizedLocale(String? aLocale) {
// Locales of length < 5 are presumably two-letter forms, or else malformed.
// We return them unmodified and if correct they will be found.
// Locales longer than 6 might be malformed, but also do occur. Do as
// little as possible to them, but make the '-' be an '_' if it's there.
// We treat C as a special case, and assume it wants en_ISO for formatting.
// TODO(alanknight): en_ISO is probably not quite right for the C/Posix
// locale for formatting. Consider adding C to the formats database.
if (aLocale == null) return global_state.getCurrentLocale();
if (aLocale == 'C') return 'en_ISO';
if (aLocale.length < 5) return aLocale;
if (aLocale[2] != '-' && (aLocale[2] != '_')) return aLocale;
var region = aLocale.substring(3);
// If it's longer than three it's something odd, so don't touch it.
if (region.length <= 3) region = region.toUpperCase();
return '${aLocale[0]}${aLocale[1]}_$region';
}