refactor: extract timezone data, remove duplicate i18n keys

This commit is contained in:
2026-03-25 05:45:05 +01:00
parent 6d30a59651
commit c140daa7ae
15 changed files with 668 additions and 8 deletions

View File

@@ -1457,6 +1457,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.11" version: "0.6.11"
timezone:
dependency: transitive
description:
name: timezone
sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
timing: timing:
dependency: transitive dependency: transitive
description: description:

View File

@@ -0,0 +1,64 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart';
import 'timezone_view_state.dart';
final timezoneViewModelProvider =
NotifierProvider.autoDispose<TimezoneViewModel, TimezoneViewState>(
TimezoneViewModel.new,
);
class TimezoneViewModel extends Notifier<TimezoneViewState> {
late final DeviceSettingsUpdateDatasource _datasource;
@override
TimezoneViewState build() {
_datasource = ref.read(deviceSettingsUpdateProvider);
Future.microtask(_load);
return const TimezoneViewState();
}
void _load() {
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
state = state.copyWith(
timezone: device.settings.timezone,
isLoading: false,
);
}
void selectTimezone(int value) {
state = state.copyWith(timezone: value);
}
Future<void> save() async {
final device = ref.read(selectedDeviceProvider);
if (device == null) return;
if (state.timezone == device.settings.timezone) {
state = state.copyWith(saveSuccess: true);
return;
}
state = state.copyWith(isSaving: true, errorEvent: null, saveSuccess: false);
try {
final updatedSettings = device.settings.copyWith(
timezone: state.timezone,
);
await _datasource.updateDeviceSettings(
device: device,
updatedSettings: updatedSettings,
);
if (!ref.mounted) return;
state = state.copyWith(isSaving: false, saveSuccess: true);
} catch (e) {
if (!ref.mounted) return;
state = state.copyWith(
isSaving: false,
errorEvent: TimezoneErrorEvent.update,
);
}
}
}

View File

@@ -0,0 +1,16 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'timezone_view_state.freezed.dart';
enum TimezoneErrorEvent { update }
@freezed
abstract class TimezoneViewState with _$TimezoneViewState {
const factory TimezoneViewState({
@Default(true) bool isLoading,
@Default(false) bool isSaving,
@Default(0) int timezone,
TimezoneErrorEvent? errorEvent,
@Default(false) bool saveSuccess,
}) = _TimezoneViewState;
}

View File

@@ -0,0 +1,283 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'timezone_view_state.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$TimezoneViewState {
bool get isLoading; bool get isSaving; int get timezone; TimezoneErrorEvent? get errorEvent; bool get saveSuccess;
/// Create a copy of TimezoneViewState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$TimezoneViewStateCopyWith<TimezoneViewState> get copyWith => _$TimezoneViewStateCopyWithImpl<TimezoneViewState>(this as TimezoneViewState, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is TimezoneViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.timezone, timezone) || other.timezone == timezone)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.saveSuccess, saveSuccess) || other.saveSuccess == saveSuccess));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,isSaving,timezone,errorEvent,saveSuccess);
@override
String toString() {
return 'TimezoneViewState(isLoading: $isLoading, isSaving: $isSaving, timezone: $timezone, errorEvent: $errorEvent, saveSuccess: $saveSuccess)';
}
}
/// @nodoc
abstract mixin class $TimezoneViewStateCopyWith<$Res> {
factory $TimezoneViewStateCopyWith(TimezoneViewState value, $Res Function(TimezoneViewState) _then) = _$TimezoneViewStateCopyWithImpl;
@useResult
$Res call({
bool isLoading, bool isSaving, int timezone, TimezoneErrorEvent? errorEvent, bool saveSuccess
});
}
/// @nodoc
class _$TimezoneViewStateCopyWithImpl<$Res>
implements $TimezoneViewStateCopyWith<$Res> {
_$TimezoneViewStateCopyWithImpl(this._self, this._then);
final TimezoneViewState _self;
final $Res Function(TimezoneViewState) _then;
/// Create a copy of TimezoneViewState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isLoading = null,Object? isSaving = null,Object? timezone = null,Object? errorEvent = freezed,Object? saveSuccess = null,}) {
return _then(_self.copyWith(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
as bool,timezone: null == timezone ? _self.timezone : timezone // ignore: cast_nullable_to_non_nullable
as int,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as TimezoneErrorEvent?,saveSuccess: null == saveSuccess ? _self.saveSuccess : saveSuccess // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// Adds pattern-matching-related methods to [TimezoneViewState].
extension TimezoneViewStatePatterns on TimezoneViewState {
/// A variant of `map` that fallback to returning `orElse`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _TimezoneViewState value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _TimezoneViewState() when $default != null:
return $default(_that);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// Callbacks receives the raw object, upcasted.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case final Subclass2 value:
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _TimezoneViewState value) $default,){
final _that = this;
switch (_that) {
case _TimezoneViewState():
return $default(_that);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `map` that fallback to returning `null`.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case final Subclass value:
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _TimezoneViewState value)? $default,){
final _that = this;
switch (_that) {
case _TimezoneViewState() when $default != null:
return $default(_that);case _:
return null;
}
}
/// A variant of `when` that fallback to an `orElse` callback.
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return orElse();
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isLoading, bool isSaving, int timezone, TimezoneErrorEvent? errorEvent, bool saveSuccess)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _TimezoneViewState() when $default != null:
return $default(_that.isLoading,_that.isSaving,_that.timezone,_that.errorEvent,_that.saveSuccess);case _:
return orElse();
}
}
/// A `switch`-like method, using callbacks.
///
/// As opposed to `map`, this offers destructuring.
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case Subclass2(:final field2):
/// return ...;
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isLoading, bool isSaving, int timezone, TimezoneErrorEvent? errorEvent, bool saveSuccess) $default,) {final _that = this;
switch (_that) {
case _TimezoneViewState():
return $default(_that.isLoading,_that.isSaving,_that.timezone,_that.errorEvent,_that.saveSuccess);case _:
throw StateError('Unexpected subclass');
}
}
/// A variant of `when` that fallback to returning `null`
///
/// It is equivalent to doing:
/// ```dart
/// switch (sealedClass) {
/// case Subclass(:final field):
/// return ...;
/// case _:
/// return null;
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isLoading, bool isSaving, int timezone, TimezoneErrorEvent? errorEvent, bool saveSuccess)? $default,) {final _that = this;
switch (_that) {
case _TimezoneViewState() when $default != null:
return $default(_that.isLoading,_that.isSaving,_that.timezone,_that.errorEvent,_that.saveSuccess);case _:
return null;
}
}
}
/// @nodoc
class _TimezoneViewState implements TimezoneViewState {
const _TimezoneViewState({this.isLoading = true, this.isSaving = false, this.timezone = 0, this.errorEvent, this.saveSuccess = false});
@override@JsonKey() final bool isLoading;
@override@JsonKey() final bool isSaving;
@override@JsonKey() final int timezone;
@override final TimezoneErrorEvent? errorEvent;
@override@JsonKey() final bool saveSuccess;
/// Create a copy of TimezoneViewState
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$TimezoneViewStateCopyWith<_TimezoneViewState> get copyWith => __$TimezoneViewStateCopyWithImpl<_TimezoneViewState>(this, _$identity);
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TimezoneViewState&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.isSaving, isSaving) || other.isSaving == isSaving)&&(identical(other.timezone, timezone) || other.timezone == timezone)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.saveSuccess, saveSuccess) || other.saveSuccess == saveSuccess));
}
@override
int get hashCode => Object.hash(runtimeType,isLoading,isSaving,timezone,errorEvent,saveSuccess);
@override
String toString() {
return 'TimezoneViewState(isLoading: $isLoading, isSaving: $isSaving, timezone: $timezone, errorEvent: $errorEvent, saveSuccess: $saveSuccess)';
}
}
/// @nodoc
abstract mixin class _$TimezoneViewStateCopyWith<$Res> implements $TimezoneViewStateCopyWith<$Res> {
factory _$TimezoneViewStateCopyWith(_TimezoneViewState value, $Res Function(_TimezoneViewState) _then) = __$TimezoneViewStateCopyWithImpl;
@override @useResult
$Res call({
bool isLoading, bool isSaving, int timezone, TimezoneErrorEvent? errorEvent, bool saveSuccess
});
}
/// @nodoc
class __$TimezoneViewStateCopyWithImpl<$Res>
implements _$TimezoneViewStateCopyWith<$Res> {
__$TimezoneViewStateCopyWithImpl(this._self, this._then);
final _TimezoneViewState _self;
final $Res Function(_TimezoneViewState) _then;
/// Create a copy of TimezoneViewState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isLoading = null,Object? isSaving = null,Object? timezone = null,Object? errorEvent = freezed,Object? saveSuccess = null,}) {
return _then(_TimezoneViewState(
isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable
as bool,isSaving: null == isSaving ? _self.isSaving : isSaving // ignore: cast_nullable_to_non_nullable
as bool,timezone: null == timezone ? _self.timezone : timezone // ignore: cast_nullable_to_non_nullable
as int,errorEvent: freezed == errorEvent ? _self.errorEvent : errorEvent // ignore: cast_nullable_to_non_nullable
as TimezoneErrorEvent?,saveSuccess: null == saveSuccess ? _self.saveSuccess : saveSuccess // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
// dart format on

View File

@@ -4,18 +4,233 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:legacy_shared/legacy_shared.dart'; import 'package:legacy_shared/legacy_shared.dart';
import 'package:sf_localizations/sf_localizations.dart'; import 'package:sf_localizations/sf_localizations.dart';
import '../timezone_data.dart';
import 'state/timezone_view_model.dart';
class TimezoneScreen extends ConsumerWidget { class TimezoneScreen extends ConsumerWidget {
const TimezoneScreen({super.key}); const TimezoneScreen({super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themePortProvider); final theme = ref.watch(themePortProvider);
final primaryColor = theme.getColorFor(ThemeCode.legacyPrimary);
final state = ref.watch(timezoneViewModelProvider);
final vm = ref.read(timezoneViewModelProvider.notifier);
ref.listen(
timezoneViewModelProvider.select((s) => s.errorEvent),
(previous, next) {
if (next != null) {
showTopSnackbar(
context,
message: context.translate(I18n.errorTimezone),
type: MessageType.error,
);
}
},
);
ref.listen(
timezoneViewModelProvider.select((s) => s.saveSuccess),
(previous, next) {
if (next && !(previous ?? false)) {
showTopSnackbar(
context,
message: context.translate(I18n.timezoneUpdated),
type: MessageType.success,
);
}
},
);
final selected = timezoneEntries.where((t) => t.$1 == state.timezone).firstOrNull;
final others = timezoneEntries.where((t) => t.$1 != state.timezone).toList();
return LegacyPageLayout( return LegacyPageLayout(
theme: theme, theme: theme,
title: context.translate(I18n.timezone), title: context.translate(I18n.timezone),
body: const Center( body: state.isLoading
child: Text('Coming soon'), ? const Center(child: CircularProgressIndicator())
: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
children: [
if (selected != null) ...[
_SelectedTimezoneCard(
offset: selected.$1,
city: selected.$2,
continent: selected.$3,
label: formatUtcOffset(selected.$1),
primaryColor: primaryColor,
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.only(left: 4, bottom: 8),
child: Text(
context.translate(I18n.timezoneOther),
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.grey.shade500,
),
),
),
],
...others.map(
(tz) => _TimezoneItem(
offset: tz.$1,
city: tz.$2,
continent: tz.$3,
label: formatUtcOffset(tz.$1),
primaryColor: primaryColor,
onTap: () => vm.selectTimezone(tz.$1),
),
),
],
),
footer: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
child: PrimaryButton(
onPressed: state.isSaving ? null : () => vm.save(),
text: state.isSaving ? '...' : context.translate(I18n.save),
color: primaryColor,
),
),
);
}
}
class _SelectedTimezoneCard extends StatelessWidget {
final int offset;
final String city;
final String continent;
final String label;
final Color primaryColor;
const _SelectedTimezoneCard({
required this.offset,
required this.city,
required this.continent,
required this.label,
required this.primaryColor,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: primaryColor.withValues(alpha: 0.08),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: primaryColor.withValues(alpha: 0.3)),
),
child: Row(
children: [
Container(
width: 52,
height: 52,
decoration: BoxDecoration(
color: primaryColor,
shape: BoxShape.circle,
),
child: const Center(
child: Icon(Icons.access_time, color: Colors.white, size: 28),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: primaryColor,
),
),
const SizedBox(height: 4),
Text(
'$city · $continent',
style: TextStyle(
fontSize: 14,
color: primaryColor.withValues(alpha: 0.7),
),
),
],
),
),
Icon(Icons.check_circle, color: primaryColor, size: 28),
],
),
);
}
}
class _TimezoneItem extends StatelessWidget {
final int offset;
final String city;
final String continent;
final String label;
final Color primaryColor;
final VoidCallback onTap;
const _TimezoneItem({
required this.offset,
required this.city,
required this.continent,
required this.label,
required this.primaryColor,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
margin: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: Colors.grey.shade100,
shape: BoxShape.circle,
),
child: Center(
child: Icon(
Icons.public,
color: Colors.grey.shade500,
size: 20,
),
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'$label · $city',
style: const TextStyle(fontSize: 15),
),
const SizedBox(height: 2),
Text(
continent,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
),
),
],
),
), ),
); );
} }

View File

@@ -0,0 +1,32 @@
const timezoneEntries = [
(-12, 'Baker Island', 'Pacific'),
(-11, 'Pago Pago', 'Pacific'),
(-10, 'Honolulu', 'Pacific'),
(-9, 'Anchorage', 'America'),
(-8, 'Los Angeles', 'America'),
(-7, 'Denver', 'America'),
(-6, 'Mexico City', 'America'),
(-5, 'New York', 'America'),
(-4, 'Santiago', 'America'),
(-3, 'Buenos Aires', 'America'),
(-2, 'South Georgia', 'Atlantic'),
(-1, 'Azores', 'Atlantic'),
(0, 'London', 'Europe'),
(1, 'Madrid', 'Europe'),
(2, 'Cairo', 'Africa'),
(3, 'Moscow', 'Europe'),
(4, 'Dubai', 'Asia'),
(5, 'Karachi', 'Asia'),
(6, 'Dhaka', 'Asia'),
(7, 'Bangkok', 'Asia'),
(8, 'Shanghai', 'Asia'),
(9, 'Tokyo', 'Asia'),
(10, 'Sydney', 'Australia'),
(11, 'Noumea', 'Pacific'),
(12, 'Auckland', 'Pacific'),
];
String formatUtcOffset(int offset) {
final sign = offset >= 0 ? '+' : '';
return 'UTC$sign$offset';
}

View File

@@ -1119,6 +1119,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.11" version: "0.6.11"
timezone:
dependency: "direct main"
description:
name: timezone
sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d"
url: "https://pub.dev"
source: hosted
version: "0.9.4"
timing: timing:
dependency: transitive dependency: transitive
description: description:

View File

@@ -54,6 +54,7 @@ dependencies:
json_serializable: ^6.11.2 json_serializable: ^6.11.2
url_launcher: ^6.3.2 url_launcher: ^6.3.2
flutter_contacts: ^1.1.9+2 flutter_contacts: ^1.1.9+2
timezone: ^0.9.4
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.

View File

@@ -629,6 +629,8 @@
"measure": "Messen", "measure": "Messen",
"callHistory": "Anrufverlauf", "callHistory": "Anrufverlauf",
"callHistoryEmpty": "Keine Anrufe aufgezeichnet", "callHistoryEmpty": "Keine Anrufe aufgezeichnet",
"timezoneSearch": "Stadt suchen...",
"timezoneNoResults": "Keine Städte gefunden",
"callIncoming": "Eingehend", "callIncoming": "Eingehend",
"callOutgoing": "Ausgehend", "callOutgoing": "Ausgehend",
"callMissed": "Verpasst", "callMissed": "Verpasst",
@@ -697,5 +699,8 @@
"batteryNightModeTitle": "Nächtlicher Energiesparmodus", "batteryNightModeTitle": "Nächtlicher Energiesparmodus",
"batteryNightModeDescription": "Die Uhr spart Batterie und trennt sich zwischen 22:00 und 06:00 Uhr von allen Netzwerken.", "batteryNightModeDescription": "Die Uhr spart Batterie und trennt sich zwischen 22:00 und 06:00 Uhr von allen Netzwerken.",
"batteryNightModeUpdated": "Nachtmodus aktualisiert", "batteryNightModeUpdated": "Nachtmodus aktualisiert",
"errorBatteryNightMode": "Der Nachtmodus konnte nicht aktualisiert werden" "errorBatteryNightMode": "Der Nachtmodus konnte nicht aktualisiert werden",
"timezoneUpdated": "Zeitzone aktualisiert",
"errorTimezone": "Die Zeitzone konnte nicht aktualisiert werden",
"timezoneOther": "Andere Zeitzonen"
} }

View File

@@ -761,6 +761,8 @@
"measure": "Measure", "measure": "Measure",
"callHistory": "Call history", "callHistory": "Call history",
"callHistoryEmpty": "No calls recorded", "callHistoryEmpty": "No calls recorded",
"timezoneSearch": "Search city...",
"timezoneNoResults": "No cities found",
"callIncoming": "Incoming", "callIncoming": "Incoming",
"callOutgoing": "Outgoing", "callOutgoing": "Outgoing",
"callMissed": "Missed", "callMissed": "Missed",
@@ -828,5 +830,8 @@
"batteryNightModeTitle": "Night energy saving mode", "batteryNightModeTitle": "Night energy saving mode",
"batteryNightModeDescription": "The watch will save battery and disconnect from all networks between 22:00 and 06:00.", "batteryNightModeDescription": "The watch will save battery and disconnect from all networks between 22:00 and 06:00.",
"batteryNightModeUpdated": "Night mode updated", "batteryNightModeUpdated": "Night mode updated",
"errorBatteryNightMode": "Could not update night mode" "errorBatteryNightMode": "Could not update night mode",
"timezoneUpdated": "Timezone updated",
"errorTimezone": "Could not update timezone",
"timezoneOther": "Other timezones"
} }

View File

@@ -759,6 +759,8 @@
"measure": "Medir", "measure": "Medir",
"callHistory": "Historial de llamadas", "callHistory": "Historial de llamadas",
"callHistoryEmpty": "No hay llamadas registradas", "callHistoryEmpty": "No hay llamadas registradas",
"timezoneSearch": "Buscar ciudad...",
"timezoneNoResults": "No se encontraron ciudades",
"callIncoming": "Entrantes", "callIncoming": "Entrantes",
"callOutgoing": "Salientes", "callOutgoing": "Salientes",
"callMissed": "Perdidas", "callMissed": "Perdidas",
@@ -826,5 +828,8 @@
"batteryNightModeTitle": "Modo de ahorro de energía nocturno", "batteryNightModeTitle": "Modo de ahorro de energía nocturno",
"batteryNightModeDescription": "El reloj ahorrará batería y se desconectará de todas las redes entre las 22:00 y las 06:00.", "batteryNightModeDescription": "El reloj ahorrará batería y se desconectará de todas las redes entre las 22:00 y las 06:00.",
"batteryNightModeUpdated": "Modo nocturno actualizado", "batteryNightModeUpdated": "Modo nocturno actualizado",
"errorBatteryNightMode": "No se pudo actualizar el modo nocturno" "errorBatteryNightMode": "No se pudo actualizar el modo nocturno",
"timezoneUpdated": "Zona horaria actualizada",
"errorTimezone": "No se pudo actualizar la zona horaria",
"timezoneOther": "Otras zonas horarias"
} }

View File

@@ -629,6 +629,8 @@
"measure": "Mesurer", "measure": "Mesurer",
"callHistory": "Historique des appels", "callHistory": "Historique des appels",
"callHistoryEmpty": "Aucun appel enregistré", "callHistoryEmpty": "Aucun appel enregistré",
"timezoneSearch": "Rechercher une ville...",
"timezoneNoResults": "Aucune ville trouvée",
"callIncoming": "Entrants", "callIncoming": "Entrants",
"callOutgoing": "Sortants", "callOutgoing": "Sortants",
"callMissed": "Manqués", "callMissed": "Manqués",
@@ -697,5 +699,8 @@
"batteryNightModeTitle": "Mode d'économie d'énergie nocturne", "batteryNightModeTitle": "Mode d'économie d'énergie nocturne",
"batteryNightModeDescription": "La montre économisera la batterie et se déconnectera de tous les réseaux entre 22h00 et 06h00.", "batteryNightModeDescription": "La montre économisera la batterie et se déconnectera de tous les réseaux entre 22h00 et 06h00.",
"batteryNightModeUpdated": "Mode nuit mis à jour", "batteryNightModeUpdated": "Mode nuit mis à jour",
"errorBatteryNightMode": "Impossible de mettre à jour le mode nuit" "errorBatteryNightMode": "Impossible de mettre à jour le mode nuit",
"timezoneUpdated": "Fuseau horaire mis à jour",
"errorTimezone": "Impossible de mettre à jour le fuseau horaire",
"timezoneOther": "Autres fuseaux horaires"
} }

View File

@@ -629,6 +629,8 @@
"measure": "Misurare", "measure": "Misurare",
"callHistory": "Cronologia chiamate", "callHistory": "Cronologia chiamate",
"callHistoryEmpty": "Nessuna chiamata registrata", "callHistoryEmpty": "Nessuna chiamata registrata",
"timezoneSearch": "Cerca città...",
"timezoneNoResults": "Nessuna città trovata",
"callIncoming": "In arrivo", "callIncoming": "In arrivo",
"callOutgoing": "In uscita", "callOutgoing": "In uscita",
"callMissed": "Perse", "callMissed": "Perse",
@@ -697,5 +699,8 @@
"batteryNightModeTitle": "Modalità risparmio energetico notturno", "batteryNightModeTitle": "Modalità risparmio energetico notturno",
"batteryNightModeDescription": "L'orologio risparmierà batteria e si disconnetterà da tutte le reti tra le 22:00 e le 06:00.", "batteryNightModeDescription": "L'orologio risparmierà batteria e si disconnetterà da tutte le reti tra le 22:00 e le 06:00.",
"batteryNightModeUpdated": "Modalità notturna aggiornata", "batteryNightModeUpdated": "Modalità notturna aggiornata",
"errorBatteryNightMode": "Impossibile aggiornare la modalità notturna" "errorBatteryNightMode": "Impossibile aggiornare la modalità notturna",
"timezoneUpdated": "Fuso orario aggiornato",
"errorTimezone": "Impossibile aggiornare il fuso orario",
"timezoneOther": "Altri fusi orari"
} }

View File

@@ -629,6 +629,8 @@
"measure": "Medir", "measure": "Medir",
"callHistory": "Histórico de chamadas", "callHistory": "Histórico de chamadas",
"callHistoryEmpty": "Nenhuma chamada registrada", "callHistoryEmpty": "Nenhuma chamada registrada",
"timezoneSearch": "Buscar cidade...",
"timezoneNoResults": "Nenhuma cidade encontrada",
"callIncoming": "Recebidas", "callIncoming": "Recebidas",
"callOutgoing": "Efetuadas", "callOutgoing": "Efetuadas",
"callMissed": "Perdidas", "callMissed": "Perdidas",
@@ -697,5 +699,8 @@
"batteryNightModeTitle": "Modo de economia de energia noturno", "batteryNightModeTitle": "Modo de economia de energia noturno",
"batteryNightModeDescription": "O relógio poupará bateria e desconectar-se-á de todas as redes entre as 22:00 e as 06:00.", "batteryNightModeDescription": "O relógio poupará bateria e desconectar-se-á de todas as redes entre as 22:00 e as 06:00.",
"batteryNightModeUpdated": "Modo noturno atualizado", "batteryNightModeUpdated": "Modo noturno atualizado",
"errorBatteryNightMode": "Não foi possível atualizar o modo noturno" "errorBatteryNightMode": "Não foi possível atualizar o modo noturno",
"timezoneUpdated": "Fuso horário atualizado",
"errorTimezone": "Não foi possível atualizar o fuso horário",
"timezoneOther": "Outros fusos horários"
} }

View File

@@ -833,4 +833,7 @@ class I18n {
static const String batteryNightModeDescription = 'batteryNightModeDescription'; static const String batteryNightModeDescription = 'batteryNightModeDescription';
static const String batteryNightModeUpdated = 'batteryNightModeUpdated'; static const String batteryNightModeUpdated = 'batteryNightModeUpdated';
static const String errorBatteryNightMode = 'errorBatteryNightMode'; static const String errorBatteryNightMode = 'errorBatteryNightMode';
static const String timezoneUpdated = 'timezoneUpdated';
static const String errorTimezone = 'errorTimezone';
static const String timezoneOther = 'timezoneOther';
} }