Line data Source code
1 : // Copyright 2026 The Authors. See the AUTHORS file for details.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // https://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : import 'package:collection/collection.dart';
16 : import 'package:intl/intl.dart' show Intl;
17 : import 'package:intl/locale.dart' show Locale;
18 :
19 : import '../../collections.dart' show MappedObject;
20 :
21 : /// Returns the current locale.
22 : ///
23 : /// Used as the default in the [IntlString] constructor.
24 3 : Locale getCurrentLocale() => Locale.parse(Intl.getCurrentLocale());
25 :
26 : /// Aligns a String with its locale
27 : class IntlString implements MappedObject<String> {
28 : /// The value of the string
29 : final String value;
30 :
31 : /// The language tag backing the string
32 : final String? _languageTag;
33 :
34 : /// The locale of the string
35 1 : Locale get locale =>
36 4 : _languageTag != null ? Locale.parse(_languageTag) : getCurrentLocale();
37 :
38 : /// Constructor
39 : ///
40 : /// Notes:
41 : ///
42 : /// - If [locale] is not set or is null, [currentLocaleFunction] will be used.
43 : /// - If [currentLocaleFunction] is not null, it will be called to get the
44 : /// current locale.
45 : /// - If [Locale] is set, it will be used and [currentLocaleFunction] will
46 : /// be ignored.
47 1 : IntlString(
48 : this.value, {
49 : Locale? locale,
50 : Locale Function()? currentLocaleFunction,
51 : }) : _languageTag =
52 : (locale ??
53 : (currentLocaleFunction != null
54 0 : ? currentLocaleFunction()
55 1 : : getCurrentLocale()))
56 1 : .toLanguageTag();
57 :
58 : /// A strict const constructor for static code-generation.
59 1 : const IntlString.constant(this.value, {this._languageTag});
60 :
61 : /// Return `true` if the [localeIdentifier] can be parsed, null otherwise.
62 1 : static Locale? tryParseLocale(String localeIdentifier) =>
63 1 : Locale.tryParse(localeIdentifier);
64 :
65 1 : @override
66 : bool operator ==(Object other) =>
67 1 : other is IntlString &&
68 3 : other.runtimeType == runtimeType &&
69 3 : other.value == value &&
70 3 : other.locale == locale;
71 :
72 1 : @override
73 4 : int get hashCode => Object.hashAll([value, locale]);
74 :
75 1 : @override
76 2 : String toString() => toMap().toString();
77 :
78 1 : @override
79 1 : Map<String, String> toMap() => {
80 1 : 'value': value,
81 2 : 'locale': locale.toLanguageTag(),
82 : };
83 : }
84 :
85 : /// A map of locales to IntlStrings
86 : class IntlStrings implements MappedObject<Map<String, String>> {
87 : /// A map of locales to IntlStrings
88 : final Map<Locale, IntlString> _strings;
89 :
90 : /// The default locale to use when a locale isn't requested
91 : final Locale defaultLocale;
92 :
93 : /// The locale to use when no match is found - 'en' by default
94 : final Locale fallbackLocale;
95 :
96 : /// Constructor
97 : ///
98 : /// Notes:
99 : ///
100 : /// - If [locale] is not set or is null, [currentLocaleFunction] will be used.
101 : /// - If [currentLocaleFunction] is not null, it will be called to get the
102 : /// current locale.
103 : /// - If [Locale] is set, it will be used and [currentLocaleFunction] will
104 : /// be ignored.
105 1 : IntlStrings(
106 : List<IntlString> values, {
107 : Locale? defaultLocale,
108 : Locale Function()? currentLocaleFunction,
109 : Locale? fallbackLocale,
110 5 : }) : _strings = Map.fromEntries(values.map((s) => MapEntry(s.locale, s))),
111 : defaultLocale =
112 : defaultLocale ??
113 : (currentLocaleFunction != null
114 0 : ? currentLocaleFunction()
115 1 : : getCurrentLocale()),
116 : fallbackLocale =
117 1 : fallbackLocale ?? Locale.fromSubtags(languageCode: 'en');
118 :
119 : /// Given a locale, return the corresponding IntlString if it exists.
120 : ///
121 : /// If not, it will return the first IntlString that matches the locale's
122 : /// language (but not region).
123 : ///
124 : /// As a final try, it will return the IntlString that matches the
125 : /// [fallbackLocale].
126 : ///
127 : /// If no match can be found, a [Failure] [Result] will be returned.
128 : ///
129 : /// If [locale] is not set or is null, the [defaultLocale] will be used
130 1 : IntlString? getString({Locale? locale}) {
131 1 : locale ??= defaultLocale;
132 :
133 2 : var v = _strings[locale];
134 :
135 : if (v != null) {
136 : return v;
137 : }
138 :
139 3 : Locale ll = Locale.parse(Intl.shortLocale(locale.toLanguageTag()));
140 :
141 2 : v = _strings[ll];
142 :
143 : if (v != null) {
144 : return v;
145 : }
146 :
147 3 : v = _strings[fallbackLocale];
148 : if (v != null) {
149 : return v;
150 : }
151 :
152 : return null;
153 : }
154 :
155 : /// Convenience property to call [call] with no locale
156 2 : String? get string => call();
157 :
158 : /// Convenience method to call [getString]
159 1 : String? call({Locale? locale}) {
160 1 : final result = getString(locale: locale);
161 : if (result != null) {
162 1 : return result.value;
163 : }
164 : return null;
165 : }
166 :
167 : /// The list of all mapped locales
168 5 : Set<Locale?> get locales => UnmodifiableSetView(_strings.keys.toSet());
169 :
170 1 : @override
171 : Map<String, Map<String, String>> toMap() {
172 2 : return _strings.map(
173 4 : (locale, value) => MapEntry(locale.toLanguageTag(), value.toMap()),
174 : );
175 : }
176 : }
|