halkyon_app: data locations as precise as a country or a state
An operator's DATA_LOCATION and BACKUP_LOCATION may name a subdivision
(ISO 3166-2, US-CA, DE-BY) besides the EU or a country. The app adds
"(EU)" to a member country and the country to a subdivision ("Germany
(EU)", "California, United States", "Bavaria, Germany (EU)"), in
English and German, and shows the most specific flag there is: a
subdivision's own where the flag package has one, else its country's.
Subdivision names come from Unicode CLDR 48.2 (tool/subdivision_names.py),
bundled with their licence, which the app registers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
e23cc77f7c
commit
96f2d14618
33 changed files with 616 additions and 49 deletions
|
|
@ -24,6 +24,9 @@ tests in its folder (`dart test`, or `flutter test` for `halkyon_ui`, its
|
|||
example and `halkyon_app`). `halkyon_base_server`'s tests start Serverpod's embedded PostgreSQL by
|
||||
themselves and need a git-ignored `config/passwords.yaml` (see its README).
|
||||
|
||||
`tool/subdivision_names.py` writes `halkyon_app`'s names of subdivisions from a
|
||||
release of Unicode CLDR (`python3 -I tool/subdivision_names.py release-48-2`).
|
||||
|
||||
Licences: the server package `halkyon_base_server` is under the Server Side
|
||||
Public License 1.0, as the Halkyon servers are; every other package under the
|
||||
GNU Affero General Public License 3.0 or later.
|
||||
|
|
|
|||
|
|
@ -21,8 +21,14 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
|
|||
another, as tiles, `InstanceTile`), `InstanceStep` (an address, checked),
|
||||
`showAccountSwitcher` and `AddAccountScreen`, and where an instance keeps
|
||||
people's data, as a flag and words (`InstanceLocationBuilder`,
|
||||
`InstanceFlag`, `instanceLocationWords`, countries named by the app's
|
||||
`CountryNames`).
|
||||
`InstanceFlag`, `instanceLocationWords`): `EU`, a country or a subdivision
|
||||
(ISO 3166-2, `US-CA`) as its operator says, "Germany (EU)" for a member of
|
||||
the European Union (`europeanUnionMembers`) and "California, United States"
|
||||
for a subdivision, with the most specific flag there is (a few subdivisions
|
||||
have their own, such as Scotland; else the country's). Countries are named
|
||||
by the app's `CountryNames`, subdivisions by Unicode CLDR's names in English
|
||||
and German, which come with the package (`AssetSubdivisionNames`; the
|
||||
workspace's `tool/subdivision_names.py` writes them).
|
||||
- **Gates** (`lib/src/app/`): `InstanceGate` opens the app's dependencies on
|
||||
the instance in use (`AppDependencies`, handed down by `AppScope`), anew on
|
||||
a switch, the previous ones closed first; `SessionGate` keeps a person
|
||||
|
|
@ -99,11 +105,14 @@ Written as the apps are: Dart 3.13's primary constructors, Material from
|
|||
A person's widgets find their dependencies with
|
||||
`PersonScope.of<PersonDependencies>(context)` and the app's with
|
||||
`AppScope.of<Dependencies>(context)`.
|
||||
7. **Licences**: call `registerFlagIconsLicense()` once at startup, beside
|
||||
halkyon_ui's `registerFontLicenses()`.
|
||||
7. **Licences**: call `registerHalkyonAppLicenses()` once at startup (the
|
||||
flags' and Unicode CLDR's notices), beside halkyon_ui's
|
||||
`registerFontLicenses()`.
|
||||
|
||||
## Licence
|
||||
|
||||
GNU Affero General Public License 3.0 or later (`LICENSE`). The flags are
|
||||
lipis' flag-icons, drawn by the `flag` package, under the MIT licence
|
||||
(`assets/flags/LICENSE-flag-icons.txt`).
|
||||
(`assets/flags/LICENSE-flag-icons.txt`). The subdivisions' names are Unicode
|
||||
CLDR's (release 48.2), under the Unicode License v3
|
||||
(`assets/subdivisions/LICENSE-CLDR.txt`, `assets/subdivisions/ATTRIBUTION.md`).
|
||||
|
|
|
|||
12
packages/halkyon_app/assets/subdivisions/ATTRIBUTION.md
Normal file
12
packages/halkyon_app/assets/subdivisions/ATTRIBUTION.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Subdivisions' names
|
||||
|
||||
`subdivisions.json` holds the names of states, regions and provinces (ISO 3166-2
|
||||
codes, `US-CA`) in English and German, from the Unicode Common Locale Data
|
||||
Repository (CLDR, <https://cldr.unicode.org/>), release 48.2 (tag `release-48-2`,
|
||||
`common/subdivisions/en.xml` and `de.xml`), converted by the workspace's
|
||||
`tool/subdivision_names.py`: English whole, German where it differs, variants left
|
||||
out.
|
||||
|
||||
CLDR is under the Unicode License v3, whose notice is in `LICENSE-CLDR.txt`,
|
||||
verbatim from that release, and in the app's licence list
|
||||
(`registerHalkyonAppLicenses`).
|
||||
41
packages/halkyon_app/assets/subdivisions/LICENSE-CLDR.txt
Normal file
41
packages/halkyon_app/assets/subdivisions/LICENSE-CLDR.txt
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
UNICODE LICENSE V3
|
||||
|
||||
COPYRIGHT AND PERMISSION NOTICE
|
||||
|
||||
Copyright © 2004-2026 Unicode, Inc.
|
||||
|
||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
||||
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
||||
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
||||
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of data files and any associated documentation (the "Data Files") or
|
||||
software and any associated documentation (the "Software") to deal in the
|
||||
Data Files or Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
||||
copies of the Data Files or Software, and to permit persons to whom the
|
||||
Data Files or Software are furnished to do so, provided that either (a)
|
||||
this copyright and permission notice appear with all copies of the Data
|
||||
Files or Software, or (b) this copyright and permission notice appear in
|
||||
associated Documentation.
|
||||
|
||||
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
THIRD PARTY RIGHTS.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
||||
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
||||
FILES OR SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of a copyright holder shall
|
||||
not be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in these Data Files or Software without prior written
|
||||
authorization of the copyright holder.
|
||||
|
||||
SPDX-License-Identifier: Unicode-3.0
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -43,7 +43,9 @@ export 'src/auth/widget/email_sign_in_sheet.dart';
|
|||
export 'src/auth/widget/session_gate.dart';
|
||||
export 'src/auth/widget/start_screen.dart';
|
||||
export 'src/base/base_client.dart';
|
||||
export 'src/instance/cldr_license.dart';
|
||||
export 'src/instance/country_names.dart';
|
||||
export 'src/instance/european_union.dart';
|
||||
export 'src/instance/flag_icons_license.dart';
|
||||
export 'src/instance/instance.dart';
|
||||
export 'src/instance/instance_checker.dart';
|
||||
|
|
@ -52,6 +54,7 @@ export 'src/instance/instance_locations.dart';
|
|||
export 'src/instance/instance_scope.dart';
|
||||
export 'src/instance/instance_storage.dart';
|
||||
export 'src/instance/instance_store.dart';
|
||||
export 'src/instance/subdivision_names.dart';
|
||||
export 'src/instance/widget/account_switcher_sheet.dart';
|
||||
export 'src/instance/widget/first_start_screen.dart';
|
||||
export 'src/instance/widget/instance_location.dart';
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
"instanceBackupsIn": "Sicherungen: {place}",
|
||||
"instanceNoBackups": "Keine Sicherungen",
|
||||
"instanceEuropeanUnion": "Europäische Union",
|
||||
"instanceCountryInEu": "{country} (EU)",
|
||||
"instanceSubdivisionOf": "{subdivision}, {country}",
|
||||
"accountsTitle": "Konten",
|
||||
"accountAdd": "Konto hinzufügen",
|
||||
"accountAddSubtitle": "Wähle die Instanz für das Konto, das du hinzufügst.",
|
||||
|
|
|
|||
|
|
@ -70,6 +70,27 @@
|
|||
},
|
||||
"instanceNoBackups": "No backups",
|
||||
"instanceEuropeanUnion": "European Union",
|
||||
"instanceCountryInEu": "{country} (EU)",
|
||||
"@instanceCountryInEu": {
|
||||
"description": "A country of the European Union, where an instance keeps data: Germany (EU).",
|
||||
"placeholders": {
|
||||
"country": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"instanceSubdivisionOf": "{subdivision}, {country}",
|
||||
"@instanceSubdivisionOf": {
|
||||
"description": "A state or region with its country, where an instance keeps data: California, United States.",
|
||||
"placeholders": {
|
||||
"subdivision": {
|
||||
"type": "String"
|
||||
},
|
||||
"country": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"accountsTitle": "Accounts",
|
||||
"accountAdd": "Add account",
|
||||
"accountAddSubtitle": "Choose the instance for the account to add.",
|
||||
|
|
|
|||
|
|
@ -205,6 +205,18 @@ abstract class HalkyonAppLocalizations {
|
|||
/// **'European Union'**
|
||||
String get instanceEuropeanUnion;
|
||||
|
||||
/// A country of the European Union, where an instance keeps data: Germany (EU).
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{country} (EU)'**
|
||||
String instanceCountryInEu(String country);
|
||||
|
||||
/// A state or region with its country, where an instance keeps data: California, United States.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{subdivision}, {country}'**
|
||||
String instanceSubdivisionOf(String subdivision, String country);
|
||||
|
||||
/// No description provided for @accountsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -74,6 +74,16 @@ class HalkyonAppLocalizationsDe extends HalkyonAppLocalizations {
|
|||
@override
|
||||
String get instanceEuropeanUnion => 'Europäische Union';
|
||||
|
||||
@override
|
||||
String instanceCountryInEu(String country) {
|
||||
return '$country (EU)';
|
||||
}
|
||||
|
||||
@override
|
||||
String instanceSubdivisionOf(String subdivision, String country) {
|
||||
return '$subdivision, $country';
|
||||
}
|
||||
|
||||
@override
|
||||
String get accountsTitle => 'Konten';
|
||||
|
||||
|
|
|
|||
|
|
@ -74,6 +74,16 @@ class HalkyonAppLocalizationsEn extends HalkyonAppLocalizations {
|
|||
@override
|
||||
String get instanceEuropeanUnion => 'European Union';
|
||||
|
||||
@override
|
||||
String instanceCountryInEu(String country) {
|
||||
return '$country (EU)';
|
||||
}
|
||||
|
||||
@override
|
||||
String instanceSubdivisionOf(String subdivision, String country) {
|
||||
return '$subdivision, $country';
|
||||
}
|
||||
|
||||
@override
|
||||
String get accountsTitle => 'Accounts';
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import 'package:halkyon_app/src/instance/instance.dart';
|
|||
import 'package:halkyon_app/src/instance/instance_checker.dart';
|
||||
import 'package:halkyon_app/src/instance/instance_choice.dart';
|
||||
import 'package:halkyon_app/src/instance/instance_scope.dart';
|
||||
import 'package:halkyon_app/src/instance/subdivision_names.dart';
|
||||
import 'package:halkyon_app/src/instance/widget/first_start_screen.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
|
|
@ -32,6 +33,10 @@ class const InstanceGate({
|
|||
/// Names the countries instances keep their data in.
|
||||
required final CountryNames countryNames,
|
||||
|
||||
/// Names the subdivisions (states, regions) they keep it in: Unicode
|
||||
/// CLDR's, which come with the package, unless a test gives others.
|
||||
final SubdivisionNames subdivisionNames = const AssetSubdivisionNames(),
|
||||
|
||||
/// The dependencies of the app on an instance.
|
||||
required final Future<AppDependencies<Disposable>> Function(
|
||||
Instance instance,
|
||||
|
|
@ -136,6 +141,7 @@ class _InstanceGateState() extends State<InstanceGate> {
|
|||
notifier: widget.choice,
|
||||
checker: widget.checker,
|
||||
countryNames: widget.countryNames,
|
||||
subdivisionNames: widget.subdivisionNames,
|
||||
child: switch (current) {
|
||||
null => PlainApp(look: _look, home: const FirstStartScreen()),
|
||||
final instance when dependencies != null && _opened == instance =>
|
||||
|
|
|
|||
27
packages/halkyon_app/lib/src/instance/cldr_license.dart
Normal file
27
packages/halkyon_app/lib/src/instance/cldr_license.dart
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:halkyon_app/src/instance/flag_icons_license.dart';
|
||||
|
||||
/// Adds Unicode CLDR's notice to the app's licence list, beside the
|
||||
/// packages' own: the subdivisions' names come from it
|
||||
/// (`AssetSubdivisionNames`), and its licence asks that its notice go with
|
||||
/// them.
|
||||
void registerCldrLicense() => LicenseRegistry.addLicense(() async* {
|
||||
yield LicenseEntryWithLineBreaks(
|
||||
const ['Unicode CLDR'],
|
||||
await rootBundle.loadString(cldrLicenseAsset),
|
||||
);
|
||||
});
|
||||
|
||||
/// Where the notice is, among this package's assets: verbatim from the
|
||||
/// release the names come from.
|
||||
const cldrLicenseAsset =
|
||||
'packages/halkyon_app/assets/subdivisions/LICENSE-CLDR.txt';
|
||||
|
||||
/// Adds every notice the package's own assets ask for to the app's licence
|
||||
/// list: the flags' ([registerFlagIconsLicense]) and Unicode CLDR's
|
||||
/// ([registerCldrLicense]). An app calls it once at startup.
|
||||
void registerHalkyonAppLicenses() {
|
||||
registerFlagIconsLicense();
|
||||
registerCldrLicense();
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/// The European Union's member states, by the codes ISO 3166-1 gives them
|
||||
/// (Greece as `GR`; the Union's own lists say `EL`), as of 2026: a place in
|
||||
/// one of them is said to be in the EU too ("Germany (EU)"), as its law
|
||||
/// applies there beside the country's. Change it when the Union does.
|
||||
const europeanUnionMembers = {
|
||||
'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', //
|
||||
'GR', 'HR', 'HU', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', //
|
||||
'PT', 'RO', 'SE', 'SI', 'SK',
|
||||
};
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import 'package:equatable/equatable.dart';
|
||||
|
||||
/// Where an instance keeps people's data and its backups, as its operator
|
||||
/// says (`instance.basics()`): each a country's code (ISO 3166-1 alpha-2,
|
||||
/// `CH`) or `EU`, the backups also [noBackups]; null where the instance
|
||||
/// says nothing. Where the disks are, not which law
|
||||
/// governs.
|
||||
/// says (`instance.basics()`): each `EU`, a country's code (ISO 3166-1
|
||||
/// alpha-2, `DE`) or a subdivision's (ISO 3166-2, `US-CA`), in capitals, the
|
||||
/// backups also [noBackups]; null where the instance says nothing. Where
|
||||
/// the disks are, not which law governs.
|
||||
final class const InstanceLocations({
|
||||
/// Where people's data is.
|
||||
final String? data,
|
||||
|
|
@ -18,6 +18,14 @@ final class const InstanceLocations({
|
|||
/// The European Union's code, ISO 3166's reserved one.
|
||||
static const europeanUnion = 'EU';
|
||||
|
||||
/// The country [place] is in: a subdivision's first two letters (`US` of
|
||||
/// `US-CA`), a country itself.
|
||||
static String countryOf(String place) => place.substring(0, 2);
|
||||
|
||||
/// Whether [place] is a subdivision's code (ISO 3166-2, `US-CA`) rather
|
||||
/// than a country's or the European Union's.
|
||||
static bool isSubdivision(String place) => place.contains('-');
|
||||
|
||||
@override
|
||||
List<Object?> get props => [data, backups];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:halkyon_app/src/instance/country_names.dart';
|
||||
import 'package:halkyon_app/src/instance/instance_checker.dart';
|
||||
import 'package:halkyon_app/src/instance/instance_choice.dart';
|
||||
import 'package:halkyon_app/src/instance/subdivision_names.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// Hands the instances the device keeps accounts on ([InstanceChoice]) to
|
||||
|
|
@ -13,6 +14,10 @@ class const InstanceScope({
|
|||
|
||||
/// Names the countries instances keep their data in.
|
||||
required final CountryNames countryNames,
|
||||
|
||||
/// Names the subdivisions (states, regions) they keep it in: Unicode
|
||||
/// CLDR's, which come with the package, unless a test gives others.
|
||||
final SubdivisionNames subdivisionNames = const AssetSubdivisionNames(),
|
||||
required super.child,
|
||||
super.key,
|
||||
}) extends InheritedNotifier<InstanceChoice> {
|
||||
|
|
|
|||
55
packages/halkyon_app/lib/src/instance/subdivision_names.dart
Normal file
55
packages/halkyon_app/lib/src/instance/subdivision_names.dart
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// Subdivisions' names (states, regions, provinces) by their code (ISO
|
||||
/// 3166-2, `US-CA`), in a language. [AssetSubdivisionNames] reads Unicode
|
||||
/// CLDR's, which come with the package; tests give them with
|
||||
/// [MemorySubdivisionNames].
|
||||
abstract interface class SubdivisionNames() {
|
||||
/// [code]'s name in [language], else in English; null for a code without
|
||||
/// one.
|
||||
Future<String?> nameOf(String code, String language);
|
||||
}
|
||||
|
||||
/// Unicode CLDR's names of subdivisions in English and German, from the
|
||||
/// package's own asset ([subdivisionNamesAsset], written by the workspace's
|
||||
/// `tool/subdivision_names.py`), read when first asked for and kept.
|
||||
class const AssetSubdivisionNames() implements SubdivisionNames {
|
||||
/// The names read, by language and then code; kept as values, never as a
|
||||
/// future (a widget test's fake clock never completes one made in its
|
||||
/// real zone).
|
||||
static Map<String, Map<String, String>>? _names;
|
||||
|
||||
@override
|
||||
Future<String?> nameOf(String code, String language) async {
|
||||
final names = _names ??= _read(
|
||||
await rootBundle.loadString(subdivisionNamesAsset),
|
||||
);
|
||||
final key = code.toUpperCase();
|
||||
return names[language]?[key] ?? names['en']?[key];
|
||||
}
|
||||
|
||||
static Map<String, Map<String, String>> _read(String json) => {
|
||||
for (final MapEntry(:key, :value)
|
||||
in (jsonDecode(json) as Map<String, Object?>).entries)
|
||||
if (value is Map<String, Object?>) key: value.cast<String, String>(),
|
||||
};
|
||||
}
|
||||
|
||||
/// Where the names are, among this package's assets: an object of
|
||||
/// languages, each of codes and names; German holds only the names that
|
||||
/// differ from English.
|
||||
const subdivisionNamesAsset =
|
||||
'packages/halkyon_app/assets/subdivisions/subdivisions.json';
|
||||
|
||||
/// The names given, by code and then language, as a test gives them.
|
||||
class const MemorySubdivisionNames(
|
||||
final Map<String, Map<String, String>> _names,
|
||||
) implements SubdivisionNames {
|
||||
@override
|
||||
Future<String?> nameOf(String code, String language) async {
|
||||
final of = _names[code.toUpperCase()];
|
||||
return of?[language] ?? of?['en'];
|
||||
}
|
||||
}
|
||||
|
|
@ -9,10 +9,10 @@ import 'package:halkyon_app/src/instance/widget/instance_location_words.dart';
|
|||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
/// Where [instance] says it keeps people's data, asked of it as this is
|
||||
/// first built: [builder] gets the code of the flag to show (where the data
|
||||
/// is) and the words ([instanceLocationWords], the countries named in the
|
||||
/// app's language) once it answers, nulls until then and where it says
|
||||
/// nothing.
|
||||
/// first built: [builder] gets the place whose flag to show (where the data
|
||||
/// is, for [InstanceFlag]) and the words ([instanceLocationWords], the
|
||||
/// countries and subdivisions named in the app's language) once it
|
||||
/// answers, nulls until then and where it says nothing.
|
||||
class const InstanceLocationBuilder({
|
||||
required final Instance instance,
|
||||
required final Widget Function(
|
||||
|
|
@ -51,9 +51,20 @@ class _InstanceLocationBuilderState() extends State<InstanceLocationBuilder> {
|
|||
return;
|
||||
}
|
||||
final names = <String, String>{};
|
||||
for (final code in {locations.data, locations.backups}.nonNulls) {
|
||||
if (await scope.countryNames.nameOf(code, language) case final name?) {
|
||||
names[code] = name;
|
||||
for (final place in {locations.data, locations.backups}.nonNulls) {
|
||||
if (place == InstanceLocations.noBackups ||
|
||||
place == InstanceLocations.europeanUnion) {
|
||||
continue;
|
||||
}
|
||||
final country = InstanceLocations.countryOf(place);
|
||||
if (await scope.countryNames.nameOf(country, language) case final name?) {
|
||||
names[country] = name;
|
||||
}
|
||||
if (InstanceLocations.isSubdivision(place)) {
|
||||
if (await scope.subdivisionNames.nameOf(place, language)
|
||||
case final name?) {
|
||||
names[place] = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mounted && language == _language) {
|
||||
|
|
@ -81,11 +92,12 @@ class _InstanceLocationBuilderState() extends State<InstanceLocationBuilder> {
|
|||
}
|
||||
}
|
||||
|
||||
/// The flag of [code] (a country's code, or `EU`) in a white ring, [size]
|
||||
/// across; nothing for a code without a flag. Screen readers hear the
|
||||
/// place in the words beside it instead.
|
||||
/// The flag of [place] ([flagCodeOf]: `EU`, a country, or a subdivision,
|
||||
/// by its own flag or its country's) in a white ring, [size] across;
|
||||
/// nothing for a place without a flag. Screen readers hear the place in
|
||||
/// the words beside it instead.
|
||||
class const InstanceFlag(
|
||||
final String code, {
|
||||
final String place, {
|
||||
final double size = 26,
|
||||
super.key,
|
||||
}) extends StatelessWidget {
|
||||
|
|
@ -94,7 +106,8 @@ class const InstanceFlag(
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!Flag.flagsCode.contains(code.toLowerCase())) {
|
||||
final code = flagCodeOf(place);
|
||||
if (code == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
const inner = 2 * ring;
|
||||
|
|
@ -126,3 +139,17 @@ class const InstanceFlag(
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The flag package's code of the most specific flag [place] has (`EU`, a
|
||||
/// country's code or a subdivision's, ISO 3166-2): a subdivision's own
|
||||
/// where there is one (the package draws a few: Scotland's `gb_sct`,
|
||||
/// Catalonia's `es_ct`), else its country's, the European Union's for
|
||||
/// `EU`; null where there is none.
|
||||
String? flagCodeOf(String place) {
|
||||
final own = place.toLowerCase().replaceAll('-', '_');
|
||||
if (Flag.flagsCode.contains(own)) {
|
||||
return own;
|
||||
}
|
||||
final country = InstanceLocations.countryOf(place).toLowerCase();
|
||||
return Flag.flagsCode.contains(country) ? country : null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,35 @@
|
|||
import 'package:halkyon_app/l10n/halkyon_app_localizations.dart';
|
||||
import 'package:halkyon_app/src/instance/european_union.dart';
|
||||
import 'package:halkyon_app/src/instance/instance_locations.dart';
|
||||
|
||||
/// Where [locations] says an instance keeps people's data and its backups,
|
||||
/// in words, the countries by their [names] (by code; one without goes by
|
||||
/// its code): "Data and backups: European Union", else a line each, "Data:
|
||||
/// European Union" over "Backups: Switzerland" or "No backups"; null where
|
||||
/// it says nothing. Labels rather than sentences, since "in" would need the
|
||||
/// article some countries take (in the Netherlands, in der Schweiz).
|
||||
/// in words, the countries and subdivisions by their [names] (by code; one
|
||||
/// without goes by its code): "Data and backups: European Union", else a
|
||||
/// line each, "Data: Bavaria, Germany (EU)" over "Backups: Switzerland" or
|
||||
/// "No backups"; null where it says nothing. A subdivision is named with
|
||||
/// its country, and a member of the European Union says so, as the Union's
|
||||
/// law applies there too (decided 2026-10-19). Labels rather than sentences,
|
||||
/// since "in" would need the article some countries take (in the
|
||||
/// Netherlands, in der Schweiz).
|
||||
String? instanceLocationWords(
|
||||
HalkyonAppLocalizations l10n,
|
||||
InstanceLocations locations,
|
||||
Map<String, String> names,
|
||||
) {
|
||||
String place(String code) => code == InstanceLocations.europeanUnion
|
||||
? l10n.instanceEuropeanUnion
|
||||
: names[code] ?? code;
|
||||
String place(String code) {
|
||||
if (code == InstanceLocations.europeanUnion) {
|
||||
return l10n.instanceEuropeanUnion;
|
||||
}
|
||||
final country = InstanceLocations.countryOf(code);
|
||||
final countryName = names[country] ?? country;
|
||||
final inCountry = europeanUnionMembers.contains(country)
|
||||
? l10n.instanceCountryInEu(countryName)
|
||||
: countryName;
|
||||
return code == country
|
||||
? inCountry
|
||||
: l10n.instanceSubdivisionOf(names[code] ?? code, inCountry);
|
||||
}
|
||||
|
||||
final InstanceLocations(:data, :backups) = locations;
|
||||
if (data != null && data == backups) {
|
||||
return l10n.instanceDataAndBackupsIn(place(data));
|
||||
|
|
|
|||
|
|
@ -60,3 +60,8 @@ flutter:
|
|||
assets:
|
||||
# flag-icons' MIT notice, for the licence list (registerFlagIconsLicense).
|
||||
- assets/flags/LICENSE-flag-icons.txt
|
||||
# Unicode CLDR's names of subdivisions (states, regions) in English and
|
||||
# German (AssetSubdivisionNames; tool/subdivision_names.py writes them),
|
||||
# and its licence's notice (registerCldrLicense).
|
||||
- assets/subdivisions/subdivisions.json
|
||||
- assets/subdivisions/LICENSE-CLDR.txt
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue