Lot B côté app, partie NON bloquée par le lot plugin (voir §Limites).
§1 Locale (indépendant) — JSONRPC.Hello portait {} : l'app était en en_US de
fait alors que nymea gère la locale PAR CONNEXION (o:locale). Envoi de la
locale à chaque Hello, donc aussi après reconnexion subie (m_clientLocales
repart au défaut de la box à chaque socket). Locale demandée ET retenue
journalisées : un .qm introuvable rend exactement comme une absence de
traduction, sans cette trace un catalogue mal packagé passe inaperçu.
§2 i18n — flutter_localizations + intl + ARB (français seul). Aucune chaîne
en dur dans le nouvel écran. Les codes reçus de la box (domaine, mécanisme)
sont des clés ARB avec repli explicite sur code brut si l'app ne les connaît
pas : une box en avance sur l'app est un cas normal en parc déployé.
§3 Modèle — LoadConfigEntry est une VUE en lecture au-dessus de la map brute ;
l'écriture repart de la map d'origine (patched()). Pas de couche de filtrage
préventive : l'aller-retour neutre passe contre .75, donc l'écho verbatim est
sûr par construction (§7-1).
§4 Priorité à deux niveaux — groupByDomain / flattenToPayload. Le rang de
domaine se déduit de l'ordre d'apparition à plat ; la box ne sait rien des
groupes. Config non contiguë : signalée, ordre réel affiché, normalisation
seulement sur confirmation explicite. Miroir client de validateSet().
§5 Écritures asynchrones — LoadConfigProvider ne conclut jamais sur
l'acquittement du RPC : pending → LoadConfigChanged → confirmed|diverged.
NymeaEnergy.GetLoadConfig / SetLoadConfig deviennent RÉELS (ils étaient des
stubs journalisés). Conséquence traitée : EnergySetupProvider.persist()
n'émet plus setLoadConfig(buildLoadDescriptors()) — SetLoadConfig remplace
TOUT l'ensemble, et _commit() l'appelait à chaque changement d'UI : une
reconstruction depuis le modèle typé aurait écrasé la config réelle de la
box sans geste explicite.
Écran /energy/loads, gaté installateur (drawer + garde de routeur).
Limites — le lot plugin n'est PAS déployé sur .75 : GetLoadTelemetry,
LoadTelemetryChanged et o:domain sont absents (19 méthodes / 13 notifications
inchangées). Toute la moitié télémétrie du brief est donc hors d'atteinte, et
o:domain n'étant pas au schéma, la box rejetterait la clé (jsonvalidator.cpp:143).
Fixtures multi-domaines synthétiques : .75 n'a qu'une charge.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
413 lines
13 KiB
Dart
413 lines
13 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:flutter/widgets.dart';
|
||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||
import 'package:intl/intl.dart' as intl;
|
||
|
||
import 'app_localizations_fr.dart';
|
||
|
||
// ignore_for_file: type=lint
|
||
|
||
/// Callers can lookup localized strings with an instance of L10n
|
||
/// returned by `L10n.of(context)`.
|
||
///
|
||
/// Applications need to include `L10n.delegate()` in their app's
|
||
/// `localizationDelegates` list, and the locales they support in the app's
|
||
/// `supportedLocales` list. For example:
|
||
///
|
||
/// ```dart
|
||
/// import 'l10n/app_localizations.dart';
|
||
///
|
||
/// return MaterialApp(
|
||
/// localizationsDelegates: L10n.localizationsDelegates,
|
||
/// supportedLocales: L10n.supportedLocales,
|
||
/// home: MyApplicationHome(),
|
||
/// );
|
||
/// ```
|
||
///
|
||
/// ## Update pubspec.yaml
|
||
///
|
||
/// Please make sure to update your pubspec.yaml to include the following
|
||
/// packages:
|
||
///
|
||
/// ```yaml
|
||
/// dependencies:
|
||
/// # Internationalization support.
|
||
/// flutter_localizations:
|
||
/// sdk: flutter
|
||
/// intl: any # Use the pinned version from flutter_localizations
|
||
///
|
||
/// # Rest of dependencies
|
||
/// ```
|
||
///
|
||
/// ## iOS Applications
|
||
///
|
||
/// iOS applications define key application metadata, including supported
|
||
/// locales, in an Info.plist file that is built into the application bundle.
|
||
/// To configure the locales supported by your app, you’ll need to edit this
|
||
/// file.
|
||
///
|
||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||
/// project’s Runner folder.
|
||
///
|
||
/// Next, select the Information Property List item, select Add Item from the
|
||
/// Editor menu, then select Localizations from the pop-up menu.
|
||
///
|
||
/// Select and expand the newly-created Localizations item then, for each
|
||
/// locale your application supports, add a new item and select the locale
|
||
/// you wish to add from the pop-up menu in the Value field. This list should
|
||
/// be consistent with the languages listed in the L10n.supportedLocales
|
||
/// property.
|
||
abstract class L10n {
|
||
L10n(String locale)
|
||
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||
|
||
final String localeName;
|
||
|
||
static L10n? of(BuildContext context) {
|
||
return Localizations.of<L10n>(context, L10n);
|
||
}
|
||
|
||
static const LocalizationsDelegate<L10n> delegate = _L10nDelegate();
|
||
|
||
/// A list of this localizations delegate along with the default localizations
|
||
/// delegates.
|
||
///
|
||
/// Returns a list of localizations delegates containing this delegate along with
|
||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||
/// and GlobalWidgetsLocalizations.delegate.
|
||
///
|
||
/// Additional delegates can be added by appending to this list in
|
||
/// MaterialApp. This list does not have to be used at all if a custom list
|
||
/// of delegates is preferred or required.
|
||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||
<LocalizationsDelegate<dynamic>>[
|
||
delegate,
|
||
GlobalMaterialLocalizations.delegate,
|
||
GlobalCupertinoLocalizations.delegate,
|
||
GlobalWidgetsLocalizations.delegate,
|
||
];
|
||
|
||
/// A list of this localizations delegate's supported locales.
|
||
static const List<Locale> supportedLocales = <Locale>[Locale('fr')];
|
||
|
||
/// Titre de l'écran installateur de configuration des charges pilotables
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Rôles & appareils'**
|
||
String get installerLoadsTitle;
|
||
|
||
/// No description provided for @installerLoadsSubtitle.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Ordre de service des charges pilotables'**
|
||
String get installerLoadsSubtitle;
|
||
|
||
/// No description provided for @loadsEmpty.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Aucune charge pilotable déclarée sur cette installation.'**
|
||
String get loadsEmpty;
|
||
|
||
/// No description provided for @loadsLoadError.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Lecture de la configuration impossible : {error}'**
|
||
String loadsLoadError(String error);
|
||
|
||
/// Bucket des charges sans o:domain — jamais rangées d'office dans un domaine
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Non classé'**
|
||
String get sectionUnclassified;
|
||
|
||
/// No description provided for @domainEv.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Véhicule électrique'**
|
||
String get domainEv;
|
||
|
||
/// No description provided for @domainEcs.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Eau chaude sanitaire'**
|
||
String get domainEcs;
|
||
|
||
/// No description provided for @domainHvac.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Climatisation'**
|
||
String get domainHvac;
|
||
|
||
/// No description provided for @domainHeating.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Chauffage'**
|
||
String get domainHeating;
|
||
|
||
/// Repli quand la box renvoie un domaine que cette version d'app ne connaît pas
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Domaine « {code} »'**
|
||
String domainUnknown(String code);
|
||
|
||
/// No description provided for @adapterRelayRouter.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Routeur de relais'**
|
||
String get adapterRelayRouter;
|
||
|
||
/// No description provided for @adapterEtmVariableLoad.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Charge modulable'**
|
||
String get adapterEtmVariableLoad;
|
||
|
||
/// No description provided for @adapterSgReady.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'SG-Ready'**
|
||
String get adapterSgReady;
|
||
|
||
/// No description provided for @adapterEvCharger.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Borne de recharge'**
|
||
String get adapterEvCharger;
|
||
|
||
/// No description provided for @adapterUnknown.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Mécanisme « {code} »'**
|
||
String adapterUnknown(String code);
|
||
|
||
/// No description provided for @loadEnabled.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Active'**
|
||
String get loadEnabled;
|
||
|
||
/// No description provided for @loadDisabled.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Exclue de l\'arbitrage'**
|
||
String get loadDisabled;
|
||
|
||
/// No description provided for @loadPriority.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Rang {rank}'**
|
||
String loadPriority(int rank);
|
||
|
||
/// No description provided for @loadMinOn.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Maintien ON minimal : {seconds} s'**
|
||
String loadMinOn(int seconds);
|
||
|
||
/// No description provided for @loadMinOff.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Maintien OFF minimal : {seconds} s'**
|
||
String loadMinOff(int seconds);
|
||
|
||
/// No description provided for @loadRelayCount.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'{count, plural, =1{1 relais} other{{count} relais}}'**
|
||
String loadRelayCount(int count);
|
||
|
||
/// Emplacement visuel réservé — LoadConfig n'a aucun champ pour rattacher un compteur à une charge
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Compteur dédié : non rattachable'**
|
||
String get loadMeterSlot;
|
||
|
||
/// No description provided for @orderNotGroupedTitle.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Ordre non groupé'**
|
||
String get orderNotGroupedTitle;
|
||
|
||
/// No description provided for @orderNotGroupedBody.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'L\'ordre en place sur la box n\'est pas groupé par domaine : les charges d\'un même domaine ne se suivent pas. L\'ordre réel est affiché ci-dessous, tel que la box l\'applique.'**
|
||
String get orderNotGroupedBody;
|
||
|
||
/// No description provided for @orderNotGroupedAction.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Normaliser l\'ordre'**
|
||
String get orderNotGroupedAction;
|
||
|
||
/// No description provided for @orderNotGroupedWarning.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Normaliser modifiera l\'ordre d\'arbitrage réel de l\'installation. Aucune écriture ne part tant que vous n\'avez pas confirmé.'**
|
||
String get orderNotGroupedWarning;
|
||
|
||
/// No description provided for @orderNormalizeConfirm.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Normaliser et enregistrer'**
|
||
String get orderNormalizeConfirm;
|
||
|
||
/// No description provided for @cancel.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Annuler'**
|
||
String get cancel;
|
||
|
||
/// No description provided for @saveOrder.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Enregistrer l\'ordre'**
|
||
String get saveOrder;
|
||
|
||
/// No description provided for @savePending.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Envoi en cours…'**
|
||
String get savePending;
|
||
|
||
/// No description provided for @saveWaitingBox.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'En attente de confirmation de la box…'**
|
||
String get saveWaitingBox;
|
||
|
||
/// No description provided for @saveConfirmed.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Configuration confirmée par la box.'**
|
||
String get saveConfirmed;
|
||
|
||
/// No description provided for @saveRejected.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'La box a refusé : {reason}'**
|
||
String saveRejected(String reason);
|
||
|
||
/// No description provided for @saveDiverged.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'La box a enregistré un état différent de celui demandé. L\'affichage montre ce que la box applique réellement.'**
|
||
String get saveDiverged;
|
||
|
||
/// No description provided for @validationDuplicateId.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'L\'identifiant de charge « {id} » apparaît deux fois.'**
|
||
String validationDuplicateId(String id);
|
||
|
||
/// No description provided for @validationThingClaimedTwice.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'L\'appareil {thingId} est commandé par deux charges actives, « {first} » et « {second} » : deux commandes contradictoires et une puissance comptée deux fois.'**
|
||
String validationThingClaimedTwice(
|
||
String thingId,
|
||
String first,
|
||
String second,
|
||
);
|
||
|
||
/// No description provided for @telemetryUnavailableTitle.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Télémétrie indisponible'**
|
||
String get telemetryUnavailableTitle;
|
||
|
||
/// No description provided for @telemetryUnavailableBody.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Cette box n\'expose pas GetLoadTelemetry. Puissance allouée, verrous, disponibilité et motifs de décision ne sont pas lisibles depuis l\'app.'**
|
||
String get telemetryUnavailableBody;
|
||
|
||
/// No description provided for @telemetryStale.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Données anciennes — dernier point il y a {seconds} s'**
|
||
String telemetryStale(int seconds);
|
||
|
||
/// No description provided for @telemetryDegradedMode.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Mode dégradé L2 — compteur muet, planification suspendue'**
|
||
String get telemetryDegradedMode;
|
||
|
||
/// No description provided for @faultTitle.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'En défaut'**
|
||
String get faultTitle;
|
||
|
||
/// No description provided for @faultClearAction.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Lever le défaut'**
|
||
String get faultClearAction;
|
||
|
||
/// No description provided for @faultCleared.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Défaut levé — confirmé par la télémétrie relue.'**
|
||
String get faultCleared;
|
||
|
||
/// No description provided for @faultClearNoop.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Aucun défaut à lever sur cette charge.'**
|
||
String get faultClearNoop;
|
||
|
||
/// No description provided for @faultUnknown.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Défaut « {code} »'**
|
||
String faultUnknown(String code);
|
||
|
||
/// Repli obligatoire quand la box renvoie un decision.code absent de cet ARB — box plus récente que l'app. Affiche le code brut et ses paramètres, jamais une phrase vide.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Décision « {code} »{params}'**
|
||
String decisionUnknown(String code, String params);
|
||
|
||
/// No description provided for @localeRequested.
|
||
///
|
||
/// In fr, this message translates to:
|
||
/// **'Locale demandée : {requested} · retenue par la box : {effective}'**
|
||
String localeRequested(String requested, String effective);
|
||
}
|
||
|
||
class _L10nDelegate extends LocalizationsDelegate<L10n> {
|
||
const _L10nDelegate();
|
||
|
||
@override
|
||
Future<L10n> load(Locale locale) {
|
||
return SynchronousFuture<L10n>(lookupL10n(locale));
|
||
}
|
||
|
||
@override
|
||
bool isSupported(Locale locale) =>
|
||
<String>['fr'].contains(locale.languageCode);
|
||
|
||
@override
|
||
bool shouldReload(_L10nDelegate old) => false;
|
||
}
|
||
|
||
L10n lookupL10n(Locale locale) {
|
||
// Lookup logic when only language code is specified.
|
||
switch (locale.languageCode) {
|
||
case 'fr':
|
||
return L10nFr();
|
||
}
|
||
|
||
throw FlutterError(
|
||
'L10n.delegate failed to load unsupported locale "$locale". This is likely '
|
||
'an issue with the localizations generation tool. Please file an issue '
|
||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||
'that was used.',
|
||
);
|
||
}
|