Merge branch 'master' into powersync

# Conflicts:
#	Gemfile.lock
#	android/settings.gradle
#	fastlane/report.xml
#	integration_test/1_dashboard.dart
#	integration_test/2_workout.dart
#	integration_test/3_gym_mode.dart
#	integration_test/5_nutritional_plan.dart
#	integration_test/6_weight.dart
#	lib/helpers/i18n.dart
#	lib/helpers/ui.dart
#	lib/main.dart
#	lib/models/nutrition/meal.g.dart
#	lib/models/nutrition/nutritional_plan.dart
#	lib/providers/auth.dart
#	lib/providers/nutrition.dart
#	lib/screens/add_exercise_screen.dart
#	lib/screens/auth_screen.dart
#	lib/screens/dashboard.dart
#	lib/screens/exercises_screen.dart
#	lib/screens/home_tabs_screen.dart
#	lib/screens/log_meal_screen.dart
#	lib/screens/log_meals_screen.dart
#	lib/screens/nutritional_plan_screen.dart
#	lib/screens/routine_list_screen.dart
#	lib/screens/workout_plan_screen.dart
#	lib/widgets/add_exercise/steps/step1basics.dart
#	lib/widgets/add_exercise/steps/step5images.dart
#	lib/widgets/add_exercise/steps/step_2_variations.dart
#	lib/widgets/add_exercise/steps/step_3_description.dart
#	lib/widgets/add_exercise/steps/step_4_translations.dart
#	lib/widgets/core/about.dart
#	lib/widgets/core/settings.dart
#	lib/widgets/dashboard/calendar.dart
#	lib/widgets/dashboard/widgets.dart
#	lib/widgets/exercises/exercises.dart
#	lib/widgets/gallery/overview.dart
#	lib/widgets/measurements/categories_card.dart
#	lib/widgets/measurements/charts.dart
#	lib/widgets/measurements/entries.dart
#	lib/widgets/measurements/forms.dart
#	lib/widgets/measurements/helpers.dart
#	lib/widgets/nutrition/charts.dart
#	lib/widgets/nutrition/forms.dart
#	lib/widgets/nutrition/helpers.dart
#	lib/widgets/nutrition/meal.dart
#	lib/widgets/nutrition/nutritional_diary_table.dart
#	lib/widgets/nutrition/nutritional_plan_detail.dart
#	lib/widgets/nutrition/nutritional_plans_list.dart
#	lib/widgets/nutrition/widgets.dart
#	lib/widgets/routines/charts.dart
#	lib/widgets/routines/workout_logs.dart
#	lib/widgets/weight/forms.dart
#	lib/widgets/weight/weight_overview.dart
#	lib/widgets/workouts/app_bar.dart
#	lib/widgets/workouts/day.dart
#	lib/widgets/workouts/forms.dart
#	lib/widgets/workouts/gym_mode.dart
#	lib/widgets/workouts/workout_plan_detail.dart
#	lib/widgets/workouts/workout_plans_list.dart
#	linux/flutter/generated_plugin_registrant.cc
#	linux/flutter/generated_plugins.cmake
#	pubspec.lock
#	pubspec.yaml
#	test/auth/auth_screen_test.dart
#	test/core/settings_test.dart
#	test/core/settings_test.mocks.dart
#	test/nutrition/nutritional_meal_form_test.mocks.dart
#	test/nutrition/nutritional_meal_item_form_test.dart
#	test/nutrition/nutritional_plan_form_test.mocks.dart
#	test/nutrition/nutritional_plan_screen_test.dart
#	test/nutrition/nutritional_plans_screen_test.dart
#	test/routine/repetition_unit_form_widget_test.dart
#	test/routine/routine_screen_test.dart
#	test/routine/routines_screen_test.dart
#	test/routine/weight_unit_form_widget_test.dart
#	test/workout/gym_mode_screen_test.dart
#	test/workout/workout_day_form_test.dart
#	test/workout/workout_form_test.dart
#	test/workout/workout_set_form_test.dart
This commit is contained in:
Roland Geider
2025-10-19 14:48:15 +02:00
1019 changed files with 62076 additions and 34475 deletions

29
lib/core/validators.dart Normal file
View File

@@ -0,0 +1,29 @@
import 'package:wger/l10n/generated/app_localizations.dart';
String? validateUrl(String? value, AppLocalizations i18n, {bool required = true}) {
// Required?
if (required && (value == null || value.trim().isEmpty)) {
return i18n.enterValue;
}
if (!required && (value == null || value.trim().isEmpty)) {
return null;
}
value = value!.trim();
if (!value.startsWith('http://') && !value.startsWith('https://')) {
return i18n.invalidUrl;
}
// Try to parse as URI
try {
final uri = Uri.parse(value);
if (!uri.hasScheme || !uri.hasAuthority) {
return i18n.invalidUrl;
}
} catch (e) {
return i18n.invalidUrl;
}
return null;
}

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:wger/database/exercises/type_converters.dart';
@@ -68,6 +69,8 @@ class Equipments extends Table {
@DriftDatabase(tables: [Exercises, Muscles, Equipments, Categories, Languages])
class ExerciseDatabase extends _$ExerciseDatabase {
final _logger = Logger('ExerciseDatabase');
ExerciseDatabase() : super(_openConnection());
// Named constructor for creating in-memory database
@@ -75,31 +78,32 @@ class ExerciseDatabase extends _$ExerciseDatabase {
/// Note that this needs to be bumped if the JSON response from the server changes
@override
int get schemaVersion => 1;
int get schemaVersion => 3;
/// There is not really a migration strategy. If we bump the version
/// number, delete everything and recreate the new tables. The provider
/// will fetch everything as needed from the server
@override
MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (m, from, to) async {
// no-op, but needs to be defined
return;
},
beforeOpen: (openingDetails) async {
if (openingDetails.hadUpgrade) {
final m = createMigrator();
for (final table in allTables) {
await m.deleteTable(table.actualTableName);
await m.createTable(table);
}
}
},
);
onUpgrade: (m, from, to) async {
// no-op, but needs to be defined
return;
},
beforeOpen: (openingDetails) async {
if (openingDetails.hadUpgrade) {
final m = createMigrator();
for (final table in allTables) {
await m.deleteTable(table.actualTableName);
await m.createTable(table);
}
}
},
);
Future<void> deleteEverything() {
return transaction(() async {
for (final table in allTables) {
_logger.info('Deleting db cache table ${table.actualTableName}');
await delete(table).go();
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +1,11 @@
import 'dart:convert';
import 'package:drift/drift.dart';
import 'package:wger/models/exercises/alias.dart';
import 'package:wger/models/exercises/category.dart';
import 'package:wger/models/exercises/comment.dart';
import 'package:wger/models/exercises/equipment.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/image.dart';
import 'package:wger/models/exercises/language.dart';
import 'package:wger/models/exercises/muscle.dart';
import 'package:wger/models/exercises/translation.dart';
import 'package:wger/models/exercises/variation.dart';
import 'package:wger/models/exercises/video.dart';
class ExerciseBaseConverter extends TypeConverter<Exercise, String> {
const ExerciseBaseConverter();
@override
Exercise fromSql(String fromDb) {
final Map<String, dynamic> baseData = json.decode(fromDb);
final category = ExerciseCategory.fromJson(baseData['categories']);
final musclesPrimary = baseData['muscless'].map((e) => Muscle.fromJson(e)).toList();
final musclesSecondary = baseData['musclesSecondary'].map((e) => Muscle.fromJson(e)).toList();
final equipment = baseData['equipments'].map((e) => Equipment.fromJson(e)).toList();
final images = baseData['images'].map((e) => ExerciseImage.fromJson(e)).toList();
final videos = baseData['videos'].map((e) => Video.fromJson(e)).toList();
final List<Translation> translations = [];
for (final exerciseData in baseData['translations']) {
final translation = Translation(
id: exerciseData['id'],
name: exerciseData['name'],
description: exerciseData['description'],
exerciseId: baseData['id'],
);
translation.aliases = exerciseData['aliases'].map((e) => Alias.fromJson(e)).toList();
translation.notes = exerciseData['notes'].map((e) => Comment.fromJson(e)).toList();
translation.language = Language.fromJson(exerciseData['languageObj']);
translations.add(translation);
}
final exerciseBase = Exercise(
id: baseData['id'],
uuid: baseData['uuid'],
created: null,
//creationDate: toDate(baseData['creation_date']),
musclesSecondary: musclesSecondary.cast<Muscle>(),
muscles: musclesPrimary.cast<Muscle>(),
equipment: equipment.cast<Equipment>(),
category: category,
images: images.cast<ExerciseImage>(),
translations: translations,
videos: videos.cast<Video>(),
);
return exerciseBase;
}
@override
String toSql(Exercise value) {
return json.encode(value.toJson());
}
}
class MuscleConverter extends TypeConverter<Muscle, String> {
const MuscleConverter();

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
@@ -21,6 +22,8 @@ class Ingredients extends Table {
@DriftDatabase(tables: [Ingredients])
class IngredientDatabase extends _$IngredientDatabase {
final _logger = Logger('IngredientDatabase');
IngredientDatabase() : super(_openConnection());
// Named constructor for creating in-memory database
@@ -35,24 +38,25 @@ class IngredientDatabase extends _$IngredientDatabase {
/// will fetch everything as needed from the server
@override
MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (m, from, to) async {
// no-op, but needs to be defined
return;
},
beforeOpen: (openingDetails) async {
if (openingDetails.hadUpgrade) {
final m = createMigrator();
for (final table in allTables) {
await m.deleteTable(table.actualTableName);
await m.createTable(table);
}
}
},
);
onUpgrade: (m, from, to) async {
// no-op, but needs to be defined
return;
},
beforeOpen: (openingDetails) async {
if (openingDetails.hadUpgrade) {
final m = createMigrator();
for (final table in allTables) {
await m.deleteTable(table.actualTableName);
await m.createTable(table);
}
}
},
);
Future<void> deleteEverything() {
return transaction(() async {
for (final table in allTables) {
_logger.info('Deleting db cache table ${table.actualTableName}');
await delete(table).go();
}
});

View File

@@ -3,38 +3,56 @@
part of 'ingredients_database.dart';
// ignore_for_file: type=lint
class $IngredientsTable extends Ingredients
with TableInfo<$IngredientsTable, IngredientTable> {
class $IngredientsTable extends Ingredients with TableInfo<$IngredientsTable, IngredientTable> {
@override
final GeneratedDatabase attachedDatabase;
final String? _alias;
$IngredientsTable(this.attachedDatabase, [this._alias]);
static const VerificationMeta _idMeta = const VerificationMeta('id');
@override
late final GeneratedColumn<int> id = GeneratedColumn<int>(
'id', aliasedName, false,
type: DriftSqlType.int, requiredDuringInsert: true);
'id',
aliasedName,
false,
type: DriftSqlType.int,
requiredDuringInsert: true,
);
static const VerificationMeta _dataMeta = const VerificationMeta('data');
@override
late final GeneratedColumn<String> data = GeneratedColumn<String>(
'data', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _lastFetchedMeta =
const VerificationMeta('lastFetched');
'data',
aliasedName,
false,
type: DriftSqlType.string,
requiredDuringInsert: true,
);
static const VerificationMeta _lastFetchedMeta = const VerificationMeta('lastFetched');
@override
late final GeneratedColumn<DateTime> lastFetched = GeneratedColumn<DateTime>(
'last_fetched', aliasedName, false,
type: DriftSqlType.dateTime, requiredDuringInsert: true);
'last_fetched',
aliasedName,
false,
type: DriftSqlType.dateTime,
requiredDuringInsert: true,
);
@override
List<GeneratedColumn> get $columns => [id, data, lastFetched];
@override
String get aliasedName => _alias ?? actualTableName;
@override
String get actualTableName => $name;
static const String $name = 'ingredients';
@override
VerificationContext validateIntegrity(Insertable<IngredientTable> instance,
{bool isInserting = false}) {
VerificationContext validateIntegrity(
Insertable<IngredientTable> instance, {
bool isInserting = false,
}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
@@ -43,16 +61,15 @@ class $IngredientsTable extends Ingredients
context.missing(_idMeta);
}
if (data.containsKey('data')) {
context.handle(
_dataMeta, this.data.isAcceptableOrUnknown(data['data']!, _dataMeta));
context.handle(_dataMeta, this.data.isAcceptableOrUnknown(data['data']!, _dataMeta));
} else if (isInserting) {
context.missing(_dataMeta);
}
if (data.containsKey('last_fetched')) {
context.handle(
_lastFetchedMeta,
lastFetched.isAcceptableOrUnknown(
data['last_fetched']!, _lastFetchedMeta));
_lastFetchedMeta,
lastFetched.isAcceptableOrUnknown(data['last_fetched']!, _lastFetchedMeta),
);
} else if (isInserting) {
context.missing(_lastFetchedMeta);
}
@@ -61,16 +78,17 @@ class $IngredientsTable extends Ingredients
@override
Set<GeneratedColumn> get $primaryKey => const {};
@override
IngredientTable map(Map<String, dynamic> data, {String? tablePrefix}) {
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
return IngredientTable(
id: attachedDatabase.typeMapping
.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
data: attachedDatabase.typeMapping
.read(DriftSqlType.string, data['${effectivePrefix}data'])!,
lastFetched: attachedDatabase.typeMapping
.read(DriftSqlType.dateTime, data['${effectivePrefix}last_fetched'])!,
id: attachedDatabase.typeMapping.read(DriftSqlType.int, data['${effectivePrefix}id'])!,
data: attachedDatabase.typeMapping.read(DriftSqlType.string, data['${effectivePrefix}data'])!,
lastFetched: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}last_fetched'],
)!,
);
}
@@ -86,8 +104,9 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
/// The date when the ingredient was last fetched from the server
final DateTime lastFetched;
const IngredientTable(
{required this.id, required this.data, required this.lastFetched});
const IngredientTable({required this.id, required this.data, required this.lastFetched});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
@@ -98,15 +117,10 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
}
IngredientsCompanion toCompanion(bool nullToAbsent) {
return IngredientsCompanion(
id: Value(id),
data: Value(data),
lastFetched: Value(lastFetched),
);
return IngredientsCompanion(id: Value(id), data: Value(data), lastFetched: Value(lastFetched));
}
factory IngredientTable.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
factory IngredientTable.fromJson(Map<String, dynamic> json, {ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return IngredientTable(
id: serializer.fromJson<int>(json['id']),
@@ -114,6 +128,7 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
lastFetched: serializer.fromJson<DateTime>(json['lastFetched']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
@@ -124,18 +139,17 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
};
}
IngredientTable copyWith({int? id, String? data, DateTime? lastFetched}) =>
IngredientTable(
id: id ?? this.id,
data: data ?? this.data,
lastFetched: lastFetched ?? this.lastFetched,
);
IngredientTable copyWith({int? id, String? data, DateTime? lastFetched}) => IngredientTable(
id: id ?? this.id,
data: data ?? this.data,
lastFetched: lastFetched ?? this.lastFetched,
);
IngredientTable copyWithCompanion(IngredientsCompanion data) {
return IngredientTable(
id: data.id.present ? data.id.value : this.id,
data: data.data.present ? data.data.value : this.data,
lastFetched:
data.lastFetched.present ? data.lastFetched.value : this.lastFetched,
lastFetched: data.lastFetched.present ? data.lastFetched.value : this.lastFetched,
);
}
@@ -151,6 +165,7 @@ class IngredientTable extends DataClass implements Insertable<IngredientTable> {
@override
int get hashCode => Object.hash(id, data, lastFetched);
@override
bool operator ==(Object other) =>
identical(this, other) ||
@@ -165,20 +180,23 @@ class IngredientsCompanion extends UpdateCompanion<IngredientTable> {
final Value<String> data;
final Value<DateTime> lastFetched;
final Value<int> rowid;
const IngredientsCompanion({
this.id = const Value.absent(),
this.data = const Value.absent(),
this.lastFetched = const Value.absent(),
this.rowid = const Value.absent(),
});
IngredientsCompanion.insert({
required int id,
required String data,
required DateTime lastFetched,
this.rowid = const Value.absent(),
}) : id = Value(id),
data = Value(data),
lastFetched = Value(lastFetched);
}) : id = Value(id),
data = Value(data),
lastFetched = Value(lastFetched);
static Insertable<IngredientTable> custom({
Expression<int>? id,
Expression<String>? data,
@@ -193,11 +211,12 @@ class IngredientsCompanion extends UpdateCompanion<IngredientTable> {
});
}
IngredientsCompanion copyWith(
{Value<int>? id,
Value<String>? data,
Value<DateTime>? lastFetched,
Value<int>? rowid}) {
IngredientsCompanion copyWith({
Value<int>? id,
Value<String>? data,
Value<DateTime>? lastFetched,
Value<int>? rowid,
}) {
return IngredientsCompanion(
id: id ?? this.id,
data: data ?? this.data,
@@ -238,140 +257,164 @@ class IngredientsCompanion extends UpdateCompanion<IngredientTable> {
abstract class _$IngredientDatabase extends GeneratedDatabase {
_$IngredientDatabase(QueryExecutor e) : super(e);
$IngredientDatabaseManager get managers => $IngredientDatabaseManager(this);
late final $IngredientsTable ingredients = $IngredientsTable(this);
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [ingredients];
}
typedef $$IngredientsTableCreateCompanionBuilder = IngredientsCompanion
Function({
required int id,
required String data,
required DateTime lastFetched,
Value<int> rowid,
});
typedef $$IngredientsTableUpdateCompanionBuilder = IngredientsCompanion
Function({
Value<int> id,
Value<String> data,
Value<DateTime> lastFetched,
Value<int> rowid,
});
typedef $$IngredientsTableCreateCompanionBuilder =
IngredientsCompanion Function({
required int id,
required String data,
required DateTime lastFetched,
Value<int> rowid,
});
typedef $$IngredientsTableUpdateCompanionBuilder =
IngredientsCompanion Function({
Value<int> id,
Value<String> data,
Value<DateTime> lastFetched,
Value<int> rowid,
});
class $$IngredientsTableFilterComposer
extends FilterComposer<_$IngredientDatabase, $IngredientsTable> {
$$IngredientsTableFilterComposer(super.$state);
ColumnFilters<int> get id => $state.composableBuilder(
column: $state.table.id,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
class $$IngredientsTableFilterComposer extends Composer<_$IngredientDatabase, $IngredientsTable> {
$$IngredientsTableFilterComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnFilters<String> get data => $state.composableBuilder(
column: $state.table.data,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get lastFetched => $state.composableBuilder(
column: $state.table.lastFetched,
builder: (column, joinBuilders) =>
ColumnFilters(column, joinBuilders: joinBuilders));
ColumnFilters<String> get data =>
$composableBuilder(column: $table.data, builder: (column) => ColumnFilters(column));
ColumnFilters<DateTime> get lastFetched =>
$composableBuilder(column: $table.lastFetched, builder: (column) => ColumnFilters(column));
}
class $$IngredientsTableOrderingComposer
extends OrderingComposer<_$IngredientDatabase, $IngredientsTable> {
$$IngredientsTableOrderingComposer(super.$state);
ColumnOrderings<int> get id => $state.composableBuilder(
column: $state.table.id,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
class $$IngredientsTableOrderingComposer extends Composer<_$IngredientDatabase, $IngredientsTable> {
$$IngredientsTableOrderingComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
ColumnOrderings<String> get data => $state.composableBuilder(
column: $state.table.data,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<int> get id =>
$composableBuilder(column: $table.id, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get lastFetched => $state.composableBuilder(
column: $state.table.lastFetched,
builder: (column, joinBuilders) =>
ColumnOrderings(column, joinBuilders: joinBuilders));
ColumnOrderings<String> get data =>
$composableBuilder(column: $table.data, builder: (column) => ColumnOrderings(column));
ColumnOrderings<DateTime> get lastFetched =>
$composableBuilder(column: $table.lastFetched, builder: (column) => ColumnOrderings(column));
}
class $$IngredientsTableTableManager extends RootTableManager<
_$IngredientDatabase,
$IngredientsTable,
IngredientTable,
$$IngredientsTableFilterComposer,
$$IngredientsTableOrderingComposer,
$$IngredientsTableCreateCompanionBuilder,
$$IngredientsTableUpdateCompanionBuilder,
(
IngredientTable,
BaseReferences<_$IngredientDatabase, $IngredientsTable, IngredientTable>
),
IngredientTable,
PrefetchHooks Function()> {
$$IngredientsTableTableManager(
_$IngredientDatabase db, $IngredientsTable table)
: super(TableManagerState(
class $$IngredientsTableAnnotationComposer
extends Composer<_$IngredientDatabase, $IngredientsTable> {
$$IngredientsTableAnnotationComposer({
required super.$db,
required super.$table,
super.joinBuilder,
super.$addJoinBuilderToRootComposer,
super.$removeJoinBuilderFromRootComposer,
});
GeneratedColumn<int> get id => $composableBuilder(column: $table.id, builder: (column) => column);
GeneratedColumn<String> get data =>
$composableBuilder(column: $table.data, builder: (column) => column);
GeneratedColumn<DateTime> get lastFetched =>
$composableBuilder(column: $table.lastFetched, builder: (column) => column);
}
class $$IngredientsTableTableManager
extends
RootTableManager<
_$IngredientDatabase,
$IngredientsTable,
IngredientTable,
$$IngredientsTableFilterComposer,
$$IngredientsTableOrderingComposer,
$$IngredientsTableAnnotationComposer,
$$IngredientsTableCreateCompanionBuilder,
$$IngredientsTableUpdateCompanionBuilder,
(
IngredientTable,
BaseReferences<_$IngredientDatabase, $IngredientsTable, IngredientTable>,
),
IngredientTable,
PrefetchHooks Function()
> {
$$IngredientsTableTableManager(_$IngredientDatabase db, $IngredientsTable table)
: super(
TableManagerState(
db: db,
table: table,
filteringComposer:
$$IngredientsTableFilterComposer(ComposerState(db, table)),
orderingComposer:
$$IngredientsTableOrderingComposer(ComposerState(db, table)),
updateCompanionCallback: ({
Value<int> id = const Value.absent(),
Value<String> data = const Value.absent(),
Value<DateTime> lastFetched = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
IngredientsCompanion(
id: id,
data: data,
lastFetched: lastFetched,
rowid: rowid,
),
createCompanionCallback: ({
required int id,
required String data,
required DateTime lastFetched,
Value<int> rowid = const Value.absent(),
}) =>
IngredientsCompanion.insert(
id: id,
data: data,
lastFetched: lastFetched,
rowid: rowid,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
.toList(),
createFilteringComposer: () => $$IngredientsTableFilterComposer($db: db, $table: table),
createOrderingComposer: () => $$IngredientsTableOrderingComposer($db: db, $table: table),
createComputedFieldComposer: () =>
$$IngredientsTableAnnotationComposer($db: db, $table: table),
updateCompanionCallback:
({
Value<int> id = const Value.absent(),
Value<String> data = const Value.absent(),
Value<DateTime> lastFetched = const Value.absent(),
Value<int> rowid = const Value.absent(),
}) =>
IngredientsCompanion(id: id, data: data, lastFetched: lastFetched, rowid: rowid),
createCompanionCallback:
({
required int id,
required String data,
required DateTime lastFetched,
Value<int> rowid = const Value.absent(),
}) => IngredientsCompanion.insert(
id: id,
data: data,
lastFetched: lastFetched,
rowid: rowid,
),
withReferenceMapper: (p0) =>
p0.map((e) => (e.readTable(table), BaseReferences(db, table, e))).toList(),
prefetchHooksCallback: null,
));
),
);
}
typedef $$IngredientsTableProcessedTableManager = ProcessedTableManager<
_$IngredientDatabase,
$IngredientsTable,
IngredientTable,
$$IngredientsTableFilterComposer,
$$IngredientsTableOrderingComposer,
$$IngredientsTableCreateCompanionBuilder,
$$IngredientsTableUpdateCompanionBuilder,
(
typedef $$IngredientsTableProcessedTableManager =
ProcessedTableManager<
_$IngredientDatabase,
$IngredientsTable,
IngredientTable,
BaseReferences<_$IngredientDatabase, $IngredientsTable, IngredientTable>
),
IngredientTable,
PrefetchHooks Function()>;
$$IngredientsTableFilterComposer,
$$IngredientsTableOrderingComposer,
$$IngredientsTableAnnotationComposer,
$$IngredientsTableCreateCompanionBuilder,
$$IngredientsTableUpdateCompanionBuilder,
(IngredientTable, BaseReferences<_$IngredientDatabase, $IngredientsTable, IngredientTable>),
IngredientTable,
PrefetchHooks Function()
>;
class $IngredientDatabaseManager {
final _$IngredientDatabase _db;
$IngredientDatabaseManager(this._db);
$$IngredientsTableTableManager get ingredients =>
$$IngredientsTableTableManager(_db, _db.ingredients);
}

View File

@@ -19,7 +19,7 @@
import 'dart:convert';
class WgerHttpException implements Exception {
Map<String, dynamic>? errors;
Map<String, dynamic> errors = {};
/// Custom http exception.
/// Expects the response body of the REST call and will try to parse it to
@@ -37,8 +37,12 @@ class WgerHttpException implements Exception {
}
}
WgerHttpException.fromMap(Map<String, dynamic> map) {
errors = map;
}
@override
String toString() {
return errors!.values.toList().join(', ');
return errors.values.toList().join(', ');
}
}

View File

@@ -1,3 +1,8 @@
class NoSuchEntryException implements Exception {
const NoSuchEntryException();
@override
String toString() {
return 'No such entry found';
}
}

View File

@@ -1,5 +1,9 @@
import 'consts.dart';
double chartGetInterval(DateTime first, DateTime last, {divider = 3}) {
final dayDiff = last.difference(first);
return dayDiff.inMilliseconds == 0 ? 1000 : dayDiff.inMilliseconds.abs() / divider;
return dayDiff.inMilliseconds == 0
? CHART_MILLISECOND_FACTOR
: dayDiff.inMilliseconds.abs() / divider;
}

View File

@@ -1,4 +1,4 @@
import 'dart:ui';
import 'package:flutter/material.dart';
const LIST_OF_COLORS8 = [
Color(0xFF2A4C7D),
@@ -41,4 +41,9 @@ Iterable<Color> generateChartColors(int nrOfItems) sync* {
for (final color in colors) {
yield color;
}
// Always return black after generating nrOfItems colors
while (true) {
yield Colors.black;
}
}

View File

@@ -31,19 +31,12 @@ const DEFAULT_SERVER_TEST = 'https://wger-master.rge.uber.space/';
const TESTSERVER_USER_NAME = 'user';
const TESTSERVER_PASSWORD = 'flutteruser';
/// Keys used in the android manifest
const MANIFEST_KEY_API = 'wger.api_key';
const MANIFEST_KEY_CHECK_UPDATE = 'wger.check_min_app_version';
/// Default weight unit is "kg"
const DEFAULT_WEIGHT_UNIT = 1;
/// Default impression for a workout session (neutral)
const DEFAULT_IMPRESSION = 2;
// Weight and repetition units for the workout logs
const REP_UNIT_REPETITIONS = 1;
const REP_UNIT_TILL_FAILURE = 2;
const REP_UNIT_REPETITIONS_ID = 1;
const REP_UNIT_TILL_FAILURE_ID = 2;
const WEIGHT_UNIT_KG = 1;
const WEIGHT_UNIT_LB = 2;
@@ -63,6 +56,7 @@ const PREFS_LAST_UPDATED_LANGUAGES = 'lastUpdatedLanguages';
const PREFS_INGREDIENTS = 'ingredientData';
const PREFS_WORKOUT_UNITS = 'workoutUnits';
const PREFS_USER = 'userData';
const PREFS_USER_DARK_THEME = 'userDarkMode';
const PREFS_LAST_SERVER = 'lastServer';
const DEFAULT_ANIMATION_DURATION = Duration(milliseconds: 200);
@@ -118,3 +112,37 @@ const COLOR_SECONDARY_MUSCLES = Colors.orange;
// Min account age to contribute exercises. Needs to be kept in sync with
// the value on the backend
const MIN_ACCOUNT_AGE = 14;
/// Different project URLs
const GITHUB_PROJECT_URL = 'https://github.com/wger-project';
const GITHUB_REPO_URL = '$GITHUB_PROJECT_URL/flutter';
const GITHUB_ISSUES_URL = '$GITHUB_REPO_URL/issues/new/choose';
const GITHUB_ISSUES_BUG_URL = '$GITHUB_REPO_URL/issues/new?template=1_bug.yml';
const GITHUB_SPONSORS_URL = 'https://github.com/sponsors/wger-project';
const DISCORD_URL = 'https://discord.gg/rPWFv6W';
const MASTODON_URL = 'https://fosstodon.org/@wger';
const WEBLATE_URL = 'https://hosted.weblate.org/engage/wger';
const BUY_ME_A_COFFEE_URL = 'https://buymeacoffee.com/wger';
const LIBERAPAY_URL = 'https://liberapay.com/wger';
/// Factor to multiply / divide in the charts when converting dates to milliseconds
/// from epoch since fl_charts does not support real time series charts and using
/// the milliseconds themselves can cause the application to crash since it runs
/// out of memory...
const double CHART_MILLISECOND_FACTOR = 100000.0;
enum WeightUnitEnum { kg, lb }
/// TextInputType for decimal numbers
const textInputTypeDecimal = TextInputType.numberWithOptions(decimal: true);
const String API_MAX_PAGE_SIZE = '999';
const String API_RESULTS_PAGE_SIZE = '100';
/// Marker used for identifying interpolated values in a list, e.g. for measurements
/// the milliseconds in the entry date are set to this value
const INTERPOLATION_MARKER = 123;
/// Creative Commons license IDs
const CC_BY_SA_4_ID = 2;

40
lib/helpers/date.dart Normal file
View File

@@ -0,0 +1,40 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* wger Workout Manager is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/// Returns a timezone aware DateTime object from a date and time string.
DateTime getDateTimeFromDateAndTime(String date, String time) {
return DateTime.parse('$date $time');
}
/// Returns a list of [DateTime] objects from [first] to [last], inclusive.
List<DateTime> daysInRange(DateTime first, DateTime last) {
final dayCount = last.difference(first).inDays + 1;
return List.generate(
dayCount,
(index) => DateTime.utc(first.year, first.month, first.day + index),
);
}
extension DateTimeExtension on DateTime {
bool isSameDayAs(DateTime other) {
final thisDay = DateTime(year, month, day);
final otherDay = DateTime(other.year, other.month, other.day);
return thisDay.isAtSameMomentAs(otherDay);
}
}

463
lib/helpers/errors.dart Normal file
View File

@@ -0,0 +1,463 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/main.dart';
import 'package:wger/models/workouts/log.dart';
import 'package:wger/providers/routines.dart';
import 'consts.dart';
import 'logs.dart';
void showHttpExceptionErrorDialog(WgerHttpException exception, {BuildContext? context}) {
final logger = Logger('showHttpExceptionErrorDialog');
// Attempt to get the BuildContext from our global navigatorKey.
// This allows us to show a dialog even if the error occurs outside
// of a widget's build method.
final BuildContext? dialogContext = context ?? navigatorKey.currentContext;
if (dialogContext == null) {
if (kDebugMode) {
logger.warning('Error: Could not error show http error dialog because the context is null.');
}
return;
}
final errorList = formatApiErrors(extractErrors(exception.errors));
showDialog(
context: dialogContext,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(ctx).anErrorOccurred),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [...errorList],
),
actions: [
TextButton(
child: Text(MaterialLocalizations.of(ctx).closeButtonLabel),
onPressed: () {
Navigator.of(ctx).pop();
},
),
],
),
);
}
void showGeneralErrorDialog(dynamic error, StackTrace? stackTrace, {BuildContext? context}) {
// Attempt to get the BuildContext from our global navigatorKey.
// This allows us to show a dialog even if the error occurs outside
// of a widget's build method.
final BuildContext? dialogContext = context ?? navigatorKey.currentContext;
final logger = Logger('showHttpExceptionErrorDialog');
if (dialogContext == null) {
if (kDebugMode) {
logger.warning('Error: Could not error show dialog because the context is null.');
}
return;
}
final i18n = AppLocalizations.of(dialogContext);
// If possible, determine the error title and message based on the error type.
// (Note that issue titles and error messages are not localized)
bool allowReportIssue = true;
String issueTitle = 'An error occurred';
String issueErrorMessage = error.toString();
String errorTitle = i18n.anErrorOccurred;
String errorDescription = i18n.errorInfoDescription;
var icon = Icons.error;
if (error is TimeoutException) {
issueTitle = 'Network Timeout';
issueErrorMessage =
'The connection to the server timed out. Please check your '
'internet connection and try again.';
} else if (error is FlutterErrorDetails) {
issueTitle = 'Application Error';
issueErrorMessage = error.exceptionAsString();
} else if (error is MissingRequiredKeysException) {
issueTitle = 'Missing Required Key';
} else if (error is SocketException) {
allowReportIssue = false;
icon = Icons.signal_wifi_connected_no_internet_4_outlined;
errorTitle = i18n.errorCouldNotConnectToServer;
errorDescription = i18n.errorCouldNotConnectToServerDetails;
}
final String fullStackTrace = stackTrace?.toString() ?? 'No stack trace available.';
final applicationLogs = InMemoryLogStore().getFormattedLogs();
showDialog(
context: dialogContext,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
title: Row(
spacing: 8,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon, color: Theme.of(context).colorScheme.error),
Expanded(
child: Text(errorTitle, style: TextStyle(color: Theme.of(context).colorScheme.error)),
),
],
),
content: SingleChildScrollView(
child: ListBody(
children: [
Text(errorDescription),
const SizedBox(height: 8),
Text(i18n.errorInfoDescription2),
const SizedBox(height: 10),
ExpansionTile(
tilePadding: EdgeInsets.zero,
title: Text(i18n.errorViewDetails),
children: [
Text(issueErrorMessage, style: const TextStyle(fontWeight: FontWeight.bold)),
Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.symmetric(vertical: 8.0),
constraints: const BoxConstraints(maxHeight: 250),
child: SingleChildScrollView(
child: Text(
fullStackTrace,
style: TextStyle(fontSize: 12.0, color: Colors.grey[700]),
),
),
),
CopyToClipboardButton(
text:
'Error Title: $issueTitle\n'
'Error Message: $issueErrorMessage\n\n'
'Stack Trace:\n$fullStackTrace',
),
const SizedBox(height: 8),
Text(i18n.applicationLogs, style: const TextStyle(fontWeight: FontWeight.bold)),
Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.symmetric(vertical: 8.0),
constraints: const BoxConstraints(maxHeight: 250),
child: SingleChildScrollView(
child: Column(
children: [
...applicationLogs.map(
(entry) => Text(
entry,
style: TextStyle(fontSize: 12.0, color: Colors.grey[700]),
),
),
],
),
),
),
CopyToClipboardButton(text: applicationLogs.join('\n')),
],
),
],
),
),
actions: [
if (allowReportIssue)
TextButton(
child: const Text('Report issue'),
onPressed: () async {
final logText = applicationLogs.isEmpty
? '-- No logs available --'
: applicationLogs.join('\n');
final description = Uri.encodeComponent(
'## Description\n\n'
'[Please describe what you were doing when the error occurred.]\n\n'
'## Error details\n\n'
'Error title: $issueTitle\n'
'Error message: $issueErrorMessage\n'
'Stack trace:\n'
'```\n$stackTrace\n```\n\n'
'App logs (last ${applicationLogs.length} entries):\n'
'```\n$logText\n```',
);
final githubIssueUrl =
'$GITHUB_ISSUES_BUG_URL'
'&title=$issueTitle'
'&description=$description';
final Uri reportUri = Uri.parse(githubIssueUrl);
try {
await launchUrl(reportUri, mode: LaunchMode.externalApplication);
} catch (e) {
if (kDebugMode) {
logger.warning('Error launching URL: $e');
}
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error opening issue tracker: $e')));
}
},
),
FilledButton(
child: Text(MaterialLocalizations.of(context).okButtonLabel),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
class CopyToClipboardButton extends StatelessWidget {
final logger = Logger('CopyToClipboardButton');
final String text;
CopyToClipboardButton({required this.text, super.key});
@override
Widget build(BuildContext context) {
final i18n = AppLocalizations.of(context);
return TextButton.icon(
icon: const Icon(Icons.copy_all_outlined, size: 18),
label: Text(i18n.copyToClipboard),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
Clipboard.setData(ClipboardData(text: text))
.then((_) {
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Details copied to clipboard!')));
}
})
.catchError((copyError) {
logger.warning('Error copying to clipboard: $copyError');
if (context.mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Could not copy details.')));
}
});
},
);
}
}
void showDeleteDialog(BuildContext context, String confirmDeleteName, Log log) async {
final res = await showDialog(
context: context,
builder: (BuildContext contextDialog) {
return AlertDialog(
content: Text(AppLocalizations.of(context).confirmDelete(confirmDeleteName)),
actions: [
TextButton(
key: const ValueKey('cancel-button'),
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
onPressed: () => Navigator.of(contextDialog).pop(),
),
TextButton(
key: const ValueKey('delete-button'),
child: Text(
AppLocalizations.of(context).delete,
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
onPressed: () {
context.read<RoutinesProvider>().deleteLog(log.id!, log.routineId);
Navigator.of(contextDialog).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).successfullyDeleted,
textAlign: TextAlign.center,
),
),
);
},
),
],
);
},
);
return res;
}
class ApiError {
final String key;
late List<String> errorMessages = [];
ApiError({required this.key, this.errorMessages = const []});
@override
String toString() {
return 'ApiError(key: $key, errorMessage: $errorMessages)';
}
}
/// Extracts error messages from the server response,
/// including nested error structures.
List<ApiError> extractErrors(Map<String, dynamic> errors) {
final List<ApiError> errorList = [];
_extractErrorsRecursive(errors, errorList);
return errorList;
}
void _extractErrorsRecursive(dynamic errors, List<ApiError> errorList, [String? parentKey]) {
if (errors is Map<String, dynamic>) {
for (final key in errors.keys) {
final value = errors[key];
final fullKey = parentKey != null ? '$parentKey | ${_formatHeader(key)}' : key;
_extractErrorsRecursive(value, errorList, fullKey);
}
} else if (errors is List) {
// List of Maps (nested errors)
if (errors.isNotEmpty && errors.first is Map<String, dynamic>) {
for (final item in errors) {
_extractErrorsRecursive(item, errorList, parentKey);
}
} else {
// List of Strings
final header = _formatHeader(parentKey ?? '');
final error = ApiError(key: header, errorMessages: errors.cast<String>());
errorList.add(error);
}
} else if (errors is String) {
final header = _formatHeader(parentKey ?? '');
final error = ApiError(key: header, errorMessages: [errors]);
errorList.add(error);
}
}
String _formatHeader(String key) {
var header = key[0].toUpperCase() + key.substring(1, key.length);
header = header.replaceAll('_', ' ');
return header.replaceAll('.', ' ');
}
/// Processes the error messages from the server and returns a list of widgets
List<Widget> formatApiErrors(List<ApiError> errors, {Color? color}) {
final textColor = color ?? Colors.black;
final List<Widget> errorList = [];
for (final error in errors) {
errorList.add(
Text(
error.key,
style: TextStyle(fontWeight: FontWeight.bold, color: textColor),
),
);
print(error.errorMessages);
for (final message in error.errorMessages) {
errorList.add(Text(message, style: TextStyle(color: textColor)));
}
errorList.add(const SizedBox(height: 8));
}
return errorList;
}
/// Processes the error messages from the server and returns a list of widgets
List<Widget> formatTextErrors(List<String> errors, {String? title, Color? color}) {
final textColor = color ?? Colors.black;
final List<Widget> errorList = [];
if (title != null) {
errorList.add(
Text(
title,
style: TextStyle(fontWeight: FontWeight.bold, color: textColor),
),
);
}
for (final message in errors) {
errorList.add(Text(message, style: TextStyle(color: textColor)));
}
errorList.add(const SizedBox(height: 8));
return errorList;
}
class FormHttpErrorsWidget extends StatelessWidget {
final WgerHttpException exception;
const FormHttpErrorsWidget(this.exception, {super.key});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxHeight: 250),
child: SingleChildScrollView(
child: Column(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
...formatApiErrors(
extractErrors(exception.errors),
color: Theme.of(context).colorScheme.error,
),
],
),
),
);
}
}
class GeneralErrorsWidget extends StatelessWidget {
final String? title;
final List<String> widgets;
const GeneralErrorsWidget(this.widgets, {this.title, super.key});
@override
Widget build(BuildContext context) {
return Container(
constraints: const BoxConstraints(maxHeight: 250),
child: SingleChildScrollView(
child: Column(
children: [
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
...formatTextErrors(widgets, title: title, color: Theme.of(context).colorScheme.error),
],
),
),
);
}
}

View File

@@ -14,19 +14,29 @@ String? validateName(String? name, BuildContext context) {
}
if (name.length < MIN_CHARS_NAME || name.length > MAX_CHARS_NAME) {
return AppLocalizations.of(context).enterCharacters(MIN_CHARS_NAME, MAX_CHARS_NAME);
return AppLocalizations.of(
context,
).enterCharacters(MIN_CHARS_NAME.toString(), MAX_CHARS_NAME.toString());
}
return null;
}
String? validateDescription(String? name, BuildContext context) {
String? validateAuthorName(String? name, BuildContext context) {
if (name!.isEmpty) {
return AppLocalizations.of(context).enterValue;
}
return null;
}
String? validateExerciseDescription(String? name, BuildContext context) {
if (name!.isEmpty) {
return AppLocalizations.of(context).enterValue;
}
if (name.length < MIN_CHARS_DESCRIPTION) {
return AppLocalizations.of(context).enterMinCharacters(MIN_CHARS_DESCRIPTION);
return AppLocalizations.of(context).enterMinCharacters(MIN_CHARS_DESCRIPTION.toString());
}
return null;

View File

@@ -18,30 +18,35 @@
/// Calculates the number of plates needed to reach a specific weight
List<num> plateCalculator(num totalWeight, num barWeight, List<num> plates) {
final List<num> ans = [];
final List<num> result = [];
final sortedPlates = List.of(plates)..sort();
// Weight is less than the bar
if (totalWeight < barWeight) {
return [];
}
if (sortedPlates.isEmpty) {
return [];
}
// Remove the bar and divide by two to get weight on each side
totalWeight = (totalWeight - barWeight) / 2;
// Weight can't be divided with the smallest plate
if (totalWeight % plates.first > 0) {
if (totalWeight % sortedPlates.first > 0) {
return [];
}
// Iterate through the plates, beginning with the biggest ones
for (final plate in plates.reversed) {
for (final plate in sortedPlates.reversed) {
while (totalWeight >= plate) {
totalWeight -= plate;
ans.add(plate);
result.add(plate);
}
}
return ans;
return result;
}
/// Groups a list of plates as calculated by [plateCalculator]

View File

@@ -1,5 +1,4 @@
/// This code is autogenerated in the backend repo in extract-i18n.py do not edit!
library;
/// Translate dynamic strings that are returned from the server
/// These strings such as categories or equipment are returned by the server
@@ -7,9 +6,12 @@ library;
/// probably better ways to do this, but that's the way it is right now).
import 'package:flutter/widgets.dart';
import 'package:logging/logging.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
String getTranslation(String value, BuildContext context) {
final logger = Logger('getTranslation');
switch (value) {
case 'Abs':
return AppLocalizations.of(context).abs;
@@ -98,6 +100,9 @@ String getTranslation(String value, BuildContext context) {
case 'Repetitions':
return AppLocalizations.of(context).repetitions;
case 'Resistance band':
return AppLocalizations.of(context).resistance_band;
case 'SZ-Bar':
return AppLocalizations.of(context).sz_bar;
@@ -126,6 +131,7 @@ String getTranslation(String value, BuildContext context) {
return AppLocalizations.of(context).none__bodyweight_exercise_;
default:
throw FormatException('Could not translate the server string $value');
logger.warning('Could not translate the server string $value');
return value;
}
}

View File

@@ -23,6 +23,17 @@ num stringToNum(String? e) {
return e == null ? 0 : num.parse(e);
}
num stringOrIntToNum(dynamic e) {
if (e is int) {
return e.toDouble(); // Convert int to double (a type of num)
}
return num.tryParse(e) ?? 0;
}
num? stringToNumNull(String? e) {
return e == null ? null : num.parse(e);
}
String? numToString(num? e) {
if (e == null) {
return null;
@@ -35,20 +46,37 @@ String? numToString(num? e) {
* Converts a datetime to ISO8601 date format, but only the date.
* Needed e.g. when the wger api only expects a date and no time information.
*/
String? toDate(DateTime? dateTime) {
String? dateToYYYYMMDD(DateTime? dateTime) {
if (dateTime == null) {
return null;
}
return DateFormat('yyyy-MM-dd').format(dateTime);
}
/// Convert a date to UTC and then to an ISO8601 string.
///
/// This makes sure that the serialized data has correct timezone information.
/// Otherwise the django backend will possibly treat the date as local time,
/// which will not be correct in most cases.
String dateToUtcIso8601(DateTime dateTime) {
return dateTime.toUtc().toIso8601String();
}
/*
* Converts a time to a date object.
* Needed e.g. when the wger api only sends a time but no date information.
*/
TimeOfDay stringToTime(String? time) {
final String out = time ?? '00:00';
return TimeOfDay.fromDateTime(DateTime.parse('2020-01-01 $out'));
time ??= '00:00';
return TimeOfDay.fromDateTime(DateTime.parse('2020-01-01 $time'));
}
TimeOfDay? stringToTimeNull(String? time) {
if (time == null) {
return null;
}
return TimeOfDay.fromDateTime(DateTime.parse('2020-01-01 $time'));
}
/*

56
lib/helpers/logs.dart Normal file
View File

@@ -0,0 +1,56 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:logging/logging.dart';
/// Stores log entries in memory.
///
/// This means nothing is stored permanently anywhere and we loose everything
/// when the application closes, but that's ok for our use case and can be
/// changed in the future if the need arises.
class InMemoryLogStore {
static final InMemoryLogStore _instance = InMemoryLogStore._internal();
final List<LogRecord> _logs = [];
factory InMemoryLogStore() => _instance;
InMemoryLogStore._internal();
// Adds a new log entry, but keeps the total number of entries limited
void add(LogRecord record) {
if (_logs.length >= 500) {
_logs.removeAt(0);
}
_logs.add(record);
}
List<LogRecord> get logs => List.unmodifiable(_logs);
List<String> getFormattedLogs({Level? minLevel}) {
final level = minLevel ?? Logger.root.level;
return _logs
.where((log) => log.level >= level)
.map(
(log) =>
'${log.time.toIso8601String()} ${log.level.name} [${log.loggerName}] ${log.message}',
)
.toList();
}
void clear() => _logs.clear();
}

View File

@@ -0,0 +1,116 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:wger/helpers/consts.dart';
import 'package:wger/helpers/date.dart';
import 'package:wger/widgets/measurements/charts.dart';
extension MeasurementChartEntryListExtensions on List<MeasurementChartEntry> {
List<MeasurementChartEntry> whereDate(DateTime start, DateTime? end) {
return where((e) => e.date.isAfter(start) && (end == null || e.date.isBefore(end))).toList();
}
// assures values on the start (and optionally end) dates exist, by interpolating if needed
// this is used for when you are looking at a specific time frame (e.g. for a nutrition plan)
// while gaps in the middle of a chart can be "visually interpolated", it's good to have a clearer
// explicit interpolation for the start and end dates (if needed)
// this also helps with computing delta's across the entire window
List<MeasurementChartEntry> whereDateWithInterpolation(DateTime start, DateTime? end) {
// Make sure our list is sorted by date
sort((a, b) => a.date.compareTo(b.date));
// Initialize result list
final List<MeasurementChartEntry> result = [];
// Check if we have any entries on the same day as start/end
bool hasEntryOnStartDay = false;
bool hasEntryOnEndDay = false;
// Track entries for potential interpolation
MeasurementChartEntry? lastBeforeStart;
MeasurementChartEntry? lastBeforeEnd;
// Single pass through the data
for (final entry in this) {
if (entry.date.isSameDayAs(start)) {
hasEntryOnStartDay = true;
}
if (end != null && entry.date.isSameDayAs(end)) {
hasEntryOnEndDay = true;
}
if (end != null && entry.date.isBefore(end)) {
lastBeforeEnd = entry;
}
if (entry.date.isBefore(start)) {
lastBeforeStart = entry;
} else {
// insert interpolated start value if needed
if (!hasEntryOnStartDay && lastBeforeStart != null) {
result.insert(0, interpolateBetween(lastBeforeStart, entry, start));
hasEntryOnStartDay = true;
}
if (end == null || entry.date.isBefore(end)) {
result.add(entry);
}
if (end != null && entry.date.isAfter(end)) {
// insert interpolated end value if needed
// note: we only interpolate end if we have data going beyond end
// if let's say your plan ends in a week from now, we wouldn't want to fake data until next week.
if (!hasEntryOnEndDay && lastBeforeEnd != null) {
result.add(interpolateBetween(lastBeforeEnd, entry, end));
hasEntryOnEndDay = true;
}
// we added all our values and did all interpolations
// surely all input values from here on are irrelevant.
return result;
}
}
}
return result;
}
}
// caller needs to make sure that before.date < date < after.date
MeasurementChartEntry interpolateBetween(
MeasurementChartEntry before,
MeasurementChartEntry after,
DateTime date,
) {
final totalDuration = after.date.difference(before.date).inMilliseconds;
final startDuration = date.difference(before.date).inMilliseconds;
// Create a special DateTime with milliseconds ending in 123 to mark it as interpolated
// which we leverage in the UI
final markedDate = DateTime(
date.year,
date.month,
date.day,
date.hour,
date.minute,
date.second,
INTERPOLATION_MARKER,
);
return MeasurementChartEntry(
before.value + (after.value - before.value) * (startDuration / totalDuration),
markedDate,
);
}

View File

@@ -24,33 +24,35 @@ import 'package:wger/models/workouts/weight_unit.dart';
/// Returns the text representation for a single setting, used in the gym mode
String repText(
int? reps,
RepetitionUnit repetitionUnitObj,
num? repetitions,
RepetitionUnit? repetitionUnitObj,
num? weight,
WeightUnit weightUnitObj,
String? rir,
WeightUnit? weightUnitObj,
num? rir,
) {
// TODO(x): how to (easily?) translate strings like the units or 'RiR'
final List<String> out = [];
if (reps != null) {
out.add(reps.toString());
if (repetitions != null) {
out.add(formatNum(repetitions).toString());
// The default repetition unit is 'reps', which we don't show unless there
// is no weight defined so that we don't just output something like "8" but
// rather "8 repetitions". If there is weight we want to output "8 x 50kg",
// since the repetitions are implied. If other units are used, we always
// print them
if (repetitionUnitObj.id != REP_UNIT_REPETITIONS || weight == 0 || weight == null) {
out.add(repetitionUnitObj.name);
if (repetitionUnitObj != null && repetitionUnitObj.id != REP_UNIT_REPETITIONS_ID ||
weight == 0 ||
weight == null) {
out.add(repetitionUnitObj!.name);
}
}
if (weight != null && weight != 0) {
out.add('×');
out.add(weight.toString());
out.add(weightUnitObj.name);
out.add(formatNum(weight).toString());
out.add(weightUnitObj!.name);
}
if (rir != null && rir != '') {
@@ -61,46 +63,6 @@ String repText(
return out.join(' ');
}
/// Returns a list of [DateTime] objects from [first] to [last], inclusive.
List<DateTime> daysInRange(DateTime first, DateTime last) {
final dayCount = last.difference(first).inDays + 1;
return List.generate(
dayCount,
(index) => DateTime.utc(first.year, first.month, first.day + index),
);
}
extension TimeOfDayExtension on TimeOfDay {
bool isAfter(TimeOfDay other) {
if (toMinutes() > other.toMinutes()) {
return true;
} else {
return false;
}
}
bool isBefore(TimeOfDay other) {
if (toMinutes() < other.toMinutes()) {
return true;
} else {
return false;
}
}
int toMinutes() {
return (hour * 60) + minute;
}
}
extension DateTimeExtension on DateTime {
bool isSameDayAs(DateTime other) {
final thisDay = DateTime(year, month, day);
final otherDay = DateTime(other.year, other.month, other.day);
return thisDay.isAtSameMomentAs(otherDay);
}
}
void launchURL(String url, BuildContext context) async {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final launched = await launchUrl(Uri.parse(url));
@@ -110,3 +72,11 @@ void launchURL(String url, BuildContext context) async {
);
}
}
/// Formats a number to an integer if it's a whole number
num formatNum(num value) {
if (value is double && value == value.toInt()) {
return value.toInt();
}
return value;
}

View File

@@ -0,0 +1,34 @@
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_preferences/util/legacy_to_async_migration_util.dart';
/// A helper class that manages preferences using SharedPreferencesAsync
/// and handles migration from the legacy SharedPreferences to
/// SharedPreferencesAsync.
class PreferenceHelper {
SharedPreferencesAsync _asyncPref = SharedPreferencesAsync();
PreferenceHelper._instantiate();
static final PreferenceHelper _instance = PreferenceHelper._instantiate();
static SharedPreferencesAsync get asyncPref => _instance._asyncPref;
static PreferenceHelper get instance => _instance;
/// Migration function that ensures any legacy data stored in
/// SharedPreferences is migrated to SharedPreferencesAsync. This migration
/// only happens once, as checked by the migrationCompletedKey.
///
/// [migrationCompletedKey] is used to track if the migration has been
/// completed.
Future<void> migrationSupportFunctionForSharedPreferences() async {
const SharedPreferencesOptions sharedPreferencesOptions = SharedPreferencesOptions();
final SharedPreferences prefs = await SharedPreferences.getInstance();
await migrateLegacySharedPreferencesToSharedPreferencesAsyncIfNecessary(
legacySharedPreferencesInstance: prefs,
sharedPreferencesAsyncOptions: sharedPreferencesOptions,
migrationCompletedKey: 'migrationCompleted',
);
_asyncPref = SharedPreferencesAsync();
}
}

View File

@@ -1,151 +0,0 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/models/exercises/exercise.dart';
import 'package:wger/models/exercises/translation.dart';
import 'package:wger/models/workouts/log.dart';
import 'package:wger/providers/workout_plans.dart';
void showErrorDialog(dynamic exception, BuildContext context) {
// log('showErrorDialog: ');
// log(exception.toString());
// log('=====================');
showDialog(
context: context,
builder: (ctx) => AlertDialog(
scrollable: true,
title: Text(AppLocalizations.of(context).anErrorOccurred),
content: SelectableText(exception.toString()),
actions: [
TextButton(
child: Text(MaterialLocalizations.of(context).closeButtonLabel),
onPressed: () {
Navigator.of(ctx).pop();
},
),
],
),
);
}
void showHttpExceptionErrorDialog(WgerHttpException exception, BuildContext context) {
log('showHttpExceptionErrorDialog: ');
log(exception.toString());
log('-------------------');
final List<Widget> errorList = [];
for (final key in exception.errors!.keys) {
// Error headers
// Ensure that the error heading first letter is capitalized.
final String errorHeaderMsg = key[0].toUpperCase() + key.substring(1, key.length);
errorList.add(
Text(
errorHeaderMsg.replaceAll('_', ' '),
style: const TextStyle(fontWeight: FontWeight.bold),
),
);
// Error messages
if (exception.errors![key] is String) {
errorList.add(Text(exception.errors![key]));
} else {
for (final value in exception.errors![key]) {
errorList.add(Text(value));
}
}
errorList.add(const SizedBox(height: 8));
}
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text(AppLocalizations.of(ctx).anErrorOccurred),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [...errorList],
),
actions: [
TextButton(
child: Text(MaterialLocalizations.of(ctx).closeButtonLabel),
onPressed: () {
Navigator.of(ctx).pop();
},
),
],
),
);
// This call serves no purpose The dialog above doesn't seem to show
// unless this dummy call is present
showDialog(context: context, builder: (context) => Container());
}
dynamic showDeleteDialog(
BuildContext context,
String confirmDeleteName,
Log log,
Translation exercise,
Map<Exercise, List<Log>> exerciseData,
) async {
final res = await showDialog(
context: context,
builder: (BuildContext contextDialog) {
return AlertDialog(
content: Text(AppLocalizations.of(context).confirmDelete(confirmDeleteName)),
actions: [
TextButton(
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
onPressed: () => Navigator.of(contextDialog).pop(),
),
TextButton(
child: Text(
AppLocalizations.of(context).delete,
style: TextStyle(color: Theme.of(context).colorScheme.error),
),
onPressed: () {
exerciseData[exercise]!.removeWhere((el) => el.id == log.id);
Provider.of<WorkoutPlansProvider>(context, listen: false).deleteLog(log);
Navigator.of(contextDialog).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
AppLocalizations.of(context).successfullyDeleted,
textAlign: TextAlign.center,
),
),
);
},
),
],
);
},
);
return res;
}

21
lib/l10n/README.md Normal file
View File

@@ -0,0 +1,21 @@
# Translations for the app
These files are usually edited on weblate, and not directly in the repo:
<https://hosted.weblate.org/engage/wger/>
## Maintenance
Find keys that are not used in the code anymore:
```bash
flutter pub add --dev translations_cleaner
flutter pub run translations_cleaner list-unused-terms
```
Remove unused keys from the arb files (this removes both `key` as well as `@key`):
```bash
cd lib/l10n
dart remove_keys.dart arbKey1 anotherArbKey
```

View File

@@ -15,10 +15,6 @@
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
},
"labelWorkoutPlans": "የአካል ብቃት እንቅስቃሴ እቅዶች",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"loginInstead": "በምትኩ ይግቡ",
"@loginInstead": {},
"registerInstead": "በምትኩ ይመዝገቡ",
@@ -79,4 +75,4 @@
"@login": {
"description": "Text for login button"
}
}
}

View File

@@ -48,7 +48,9 @@
"alsoKnownAs": "يُعرف أيضًا باسم: {aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {}
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
@@ -78,10 +80,6 @@
"@goToToday": {
"description": "Label on button to jump back to 'today' in the calendar widget"
},
"enterRepetitionsOrWeight": "يرجى ملء التكرارات أو الوزن لواحدة على الأقل من المجموعات",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"baseData": "الأساسيات بالإنجليزية",
"@baseData": {
"description": "The base data for an exercise such as category, trained muscles, etc."
@@ -145,7 +143,9 @@
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"setNr": "مجموعة {nr}",
@@ -153,7 +153,9 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"next": "التالي",
@@ -172,14 +174,6 @@
"@timeStartAhead": {},
"carbohydrates": "كربوهيدرات",
"@carbohydrates": {},
"noWorkoutPlans": "لا يوجد خطة تمارين",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"aboutTranslationTitle": "ترجمة",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"date": "التاريخ",
"@date": {
"description": "The date of a workout log or body weight entry"
@@ -238,10 +232,6 @@
},
"edit": "تعديل",
"@edit": {},
"aboutBugsText": "في حال مواجهتك لأي خلل أو الحاجة لإضافة ميزة جديدة للتطبيق، تواصل معنا",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"setUnitsAndRir": "تعيين الوحدات و RiR",
"@setUnitsAndRir": {
"description": "Label shown on the slider where the user can toggle showing units and RiR",
@@ -253,10 +243,6 @@
},
"anErrorOccurred": "حصل خطأ!",
"@anErrorOccurred": {},
"aboutContactUsTitle": "قل اهلاً!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"selectExercise": "اختر تمرين",
"@selectExercise": {
"description": "Error message when the user hasn't selected an exercise in the form"
@@ -284,8 +270,12 @@
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"recentlyUsedIngredients": "المكونات المضافة مؤخرًا",
@@ -298,10 +288,6 @@
"@addSet": {
"description": "Label for the button that adds a set (to a workout day)"
},
"newWorkout": "خطة تمارين جديدة",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"energyShort": "ط",
"@energyShort": {
"description": "The first letter or short name of the word 'Energy', used in overviews"
@@ -351,7 +337,9 @@
"verifiedEmailInfo": "أُرسلت رسالة للتحقق إلى {email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {}
"email": {
"type": "String"
}
}
},
"fatShort": "د",
@@ -427,9 +415,7 @@
"registerInstead": "ليس لديك حساب ؟ سجّل الان",
"@registerInstead": {},
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {
"description": "Title for mastodon section in the about dialog"
},
"@aboutMastodonTitle": {},
"repetitions": "التكرار",
"@repetitions": {
"description": "Generated entry for translation for server strings"
@@ -456,10 +442,6 @@
"@nutritionalDiary": {},
"protein": "بروتين",
"@protein": {},
"labelWorkoutPlans": "خطط التمارين",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"proteinShort": "ب",
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
@@ -496,10 +478,6 @@
"@labelWorkoutLogs": {
"description": "(Workout) logs"
},
"aboutTranslationText": "يُترجم التطبيق على weblate إذا كنت تريد المساعدة اضغط على الرابط وابدأ الترجمة",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"ingredient": "المكونات",
"@ingredient": {},
"measurementCategoriesHelpText": "فئة القياس، مثل \"العضلة ذات الرأسين\" أو \"دهون الجسم\"",
@@ -514,10 +492,6 @@
},
"todaysWorkout": "تمرينك اليوم",
"@todaysWorkout": {},
"aboutSourceText": "اطلع على الكود المصدري للتطبيق والسيرفر في القت هب",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"translateExercise": "ترجم التدريب الآن",
"@translateExercise": {},
"kJ": "كيلو جول",
@@ -532,10 +506,6 @@
},
"gallery": "الألبوم",
"@gallery": {},
"aboutMastodonText": "تابعنا على Mastodon لأحدث الأخبار حول المشروع",
"@aboutMastodonText": {
"description": "Text for the mastodon section in the about dialog"
},
"translation": "الترجمة",
"@translation": {},
"successfullySaved": "تم الحفظ",
@@ -570,10 +540,6 @@
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
},
"aboutContactUsText": "إذا كنت تريد مراسلتنا فنحن موجودين في Discord، اهلاً بك",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"minutes": "دقائق",
"@minutes": {
"description": "Generated entry for translation for server strings"
@@ -644,20 +610,12 @@
},
"measurementEntriesHelpText": "الوحدة المستخدمة لقياس الفئة مثل \"سم\" أو \"%\"",
"@measurementEntriesHelpText": {},
"supersetWith": "مجموعة شاملة مع",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"sugars": "سكريات",
"@sugars": {},
"customServerUrl": "رابط عنصر wger",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"aboutBugsTitle": "لديك مشكلة أو فكرة ؟",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"carbohydratesShort": "ك",
"@carbohydratesShort": {
"description": "The first letter or short name of the word 'Carbohydrates', used in overviews"
@@ -699,7 +657,9 @@
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"passwordsDontMatch": "كلمات المرور غير متطابقة",
@@ -731,7 +691,9 @@
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {}
"min": {
"type": "String"
}
}
},
"weekAverage": "متوسط سبع أيام",
@@ -759,7 +721,9 @@
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"none__bodyweight_exercise_": "لا شيء (تمرين وزن الجسم)",
@@ -779,7 +743,9 @@
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"exercises": "التمارين",
@@ -816,12 +782,28 @@
"@hamstrings": {
"description": "Generated entry for translation for server strings"
},
"aboutSourceTitle": "كود المصدر",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"textPromptSubheading": "اضغط على الزر للبدء",
"@textPromptSubheading": {},
"measurement": "القياس",
"@measurement": {}
"@measurement": {},
"useApiToken": "استخدم رمز API",
"@useApiToken": {},
"apiToken": "رمز API",
"@apiToken": {},
"invalidApiToken": "رجاءً أدخل مفتاح API صالح",
"@invalidApiToken": {
"description": "Error message when the user enters an invalid API key"
},
"useUsernameAndPassword": "استخدم اسم المستخدم وكلمة المرور",
"@useUsernameAndPassword": {},
"selectAvailablePlates": "اختر من الاطباق المتوفرة",
"@selectAvailablePlates": {},
"barWeight": "وزن القضيب الحديدي",
"@barWeight": {},
"useColors": "استخدم الالوان",
"@useColors": {},
"apiTokenValidChars": "إن مفتاح ال API لا يمكن أن يحتوي غير الأحرف من a-f، أرقام من 0-9، ويحب أن تكون 40 محرف تماما",
"@apiTokenValidChars": {
"description": "Error message when the user tries to input a API key with forbidden characters"
}
}

View File

@@ -73,10 +73,6 @@
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"supersetWith": "en conjunt amb",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"equipment": "Equipament",
"@equipment": {
"description": "Equipment needed to perform an exercise"
@@ -93,14 +89,6 @@
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"newWorkout": "Nou pla d'entrenament",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"noWorkoutPlans": "No tens cap pla d'entrenament",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"repetitions": "Repeticions",
"@repetitions": {
"description": "Repetitions for an exercise set"
@@ -291,34 +279,6 @@
},
"goToDetailPage": "Vés a la pàgina dels detalls",
"@goToDetailPage": {},
"aboutSourceTitle": "Codi font",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"aboutBugsTitle": "Teniu cap problema o idea?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"aboutBugsText": "Poseu-vos en contacte si alguna cosa no funciona com s'esperava o si hi ha alguna funció que penseu que manca.",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"aboutContactUsTitle": "Saludeu!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutContactUsText": "Si voleu xatejar amb nosaltres, entreu al servidor de Discord i poseu-vos en contacte",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"aboutTranslationTitle": "Traducció",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"aboutTranslationText": "Aquesta aplicació es tradueix a weblate. Si voleu ajudar-hi també, feu clic al vincle i comenceu a traduir",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"calendar": "Calendari",
"@calendar": {},
"enterValue": "Introduïu un valor",
@@ -330,7 +290,9 @@
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"setUnitsAndRir": "Unitats de la sèrie i RER",
@@ -386,10 +348,6 @@
},
"loginInstead": "Ja tens un compte? Entra",
"@loginInstead": {},
"labelWorkoutPlans": "Plans d'entrenament",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"labelBottomNavNutrition": "Nutrició",
"@labelBottomNavNutrition": {
"description": "Label used in bottom navigation, use a short word"
@@ -401,7 +359,9 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"dayDescriptionHelp": "Descripció de què es fa aquest dia (p. e. «dia de tracció») o quines parts del cos d'exerciten (p. e. «pit i espatlles»)",
@@ -458,10 +418,6 @@
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"enterRepetitionsOrWeight": "Ompliu les repeticions o bé el pes per a una de les sèries com a mínim",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"fatShort": "G",
"@fatShort": {
"description": "The first letter or short name of the word 'Fat', used in overviews"
@@ -478,16 +434,14 @@
"@sodium": {},
"delete": "Esborra",
"@delete": {},
"aboutSourceText": "Obtingueu el codi font d'aquesta aplicació i el seu servidor a Github",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"confirmDelete": "Esteu segur de voler esborrar '{toDelete}'?",
"@confirmDelete": {
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"goToToday": "Vés a avui",
@@ -503,8 +457,12 @@
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"recentlyUsedIngredients": "Ingredients afegits recentment",
@@ -513,6 +471,26 @@
},
"gallery": "Galeria",
"@gallery": {},
"imageFormatNotSupported": "{imageFormat} no és compatible",
"@imageFormatNotSupported": {
"description": "Label shown on the error container when image format is not supported",
"type": "text",
"placeholders": {
"imageFormat": {
"type": "String"
}
}
},
"imageFormatNotSupportedDetail": "Les imatges {imageFormat} encara no són compatibles.",
"@imageFormatNotSupportedDetail": {
"description": "Label shown on the image preview container when image format is not supported",
"type": "text",
"placeholders": {
"imageFormat": {
"type": "String"
}
}
},
"addImage": "Afegeix imatge",
"@addImage": {},
"dataCopied": "Dades copiades a una nova entrada",
@@ -542,7 +520,9 @@
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"productNotFound": "No s'ha trobat el producte",
@@ -554,7 +534,9 @@
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"alternativeNames": "Noms alternatius",
@@ -664,7 +646,9 @@
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"gValue": "{value} g",
@@ -672,7 +656,9 @@
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"percentValue": "{value} %",
@@ -680,17 +666,13 @@
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {
"description": "Title for mastodon section in the about dialog"
},
"aboutMastodonText": "Segueix-nos a Mastodon per a actualitzacions i notícies sobre el projecte",
"@aboutMastodonText": {
"description": "Text for the mastodon section in the about dialog"
},
"@aboutMastodonTitle": {},
"selectEntry": "Si us plau, selecciona una entrada",
"@selectEntry": {},
"baseNameEnglish": "Tots els exercicis necessiten un nom base en anglès",
@@ -792,7 +774,9 @@
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {}
"min": {
"type": "String"
}
}
},
"variations": "Variacions",
@@ -802,14 +786,18 @@
"alsoKnownAs": "També conegut com: {aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {}
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
"verifiedEmailInfo": "S'ha enviat un correu electrònic de verificació a {email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {}
"email": {
"type": "String"
}
}
},
"previous": "Anterior",

View File

@@ -21,10 +21,6 @@
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"labelWorkoutPlans": "Plány cvičení",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"labelBottomNavWorkout": "Trénink",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
@@ -59,10 +55,6 @@
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"noWorkoutPlans": "Nemáte žádné cvičební plány",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"rir": "OvR",
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
@@ -233,16 +225,8 @@
},
"goToDetailPage": "Přejít na stránku s podrobnostmi",
"@goToDetailPage": {},
"aboutSourceTitle": "Zdrojový kód",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"calendar": "Kalendář",
"@calendar": {},
"enterRepetitionsOrWeight": "Prosím, vyplňte buď počet opakování, nebo hmotnost u alespoň jedné sady",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"enterValue": "Prosím zadejte hodnotu",
"@enterValue": {
"description": "Error message when the user hasn't entered a value on a required field"
@@ -258,8 +242,12 @@
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"enterMinCharacters": "Prosím, zadejte alespoň {min} znaků",
@@ -267,7 +255,9 @@
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {}
"min": {
"type": "String"
}
}
},
"baseNameEnglish": "Všechny cviky potřebují mít základní název v angličtině",
@@ -372,10 +362,6 @@
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"supersetWith": "superset s",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"logMeal": "Zaznamenat do nutričního diáře",
"@logMeal": {},
"muscles": "Svaly",
@@ -386,10 +372,6 @@
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
},
"newWorkout": "Nový plán cvičení",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"reps": "Opakování",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
@@ -419,7 +401,9 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"newDay": "Nový den",
@@ -493,41 +477,15 @@
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"aboutDescription": "Děkujeme Vám za použití wger! wger je komunitní open source projekt, který je tvořen fitness nadšenci z celého světa.",
"@aboutDescription": {
"description": "Text in the about dialog"
},
"aboutSourceText": "Získejte zdrojový kód této aplikace a jejího serveru na GitHubu",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"aboutBugsTitle": "Máte problém nebo nápad?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"aboutBugsText": "Kontaktujte nás, pokud se něco nechovalo podle očekávání nebo pokud existuje funkce, o které se domníváte, že chybí.",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"aboutContactUsTitle": "Řekněte ahoj!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutContactUsText": "Pokud si s námi chcete popovídat, přejděte na Discord server a kontaktujte nás",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"aboutTranslationTitle": "Překlad",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"aboutTranslationText": "Tato aplikace je přeložena na weblate. Chcete-li také pomoci, klikněte na odkaz a začněte překládat",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"goToToday": "Přejít na dnešek",
"@goToToday": {
"description": "Label on button to jump back to 'today' in the calendar widget"
@@ -537,7 +495,9 @@
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"recentlyUsedIngredients": "Naposledy přidané přísady",
@@ -548,6 +508,26 @@
"@takePicture": {},
"gallery": "Galerie",
"@gallery": {},
"imageFormatNotSupported": "{imageFormat} není podporován",
"@imageFormatNotSupported": {
"description": "Label shown on the error container when image format is not supported",
"type": "text",
"placeholders": {
"imageFormat": {
"type": "String"
}
}
},
"imageFormatNotSupportedDetail": "Obrazy {imageFormat} zatím nejsou podporovány.",
"@imageFormatNotSupportedDetail": {
"description": "Label shown on the image preview container when image format is not supported",
"type": "text",
"placeholders": {
"imageFormat": {
"type": "String"
}
}
},
"productFound": "Produkt nalezen",
"@productFound": {
"description": "Header label for dialog when product is found with barcode"
@@ -581,7 +561,9 @@
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"scanBarcode": "Naskenovat čárový kód",
@@ -614,7 +596,9 @@
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"useMetric": "Použít metrické jednotky pro tělesnou váhu",
@@ -634,19 +618,15 @@
"description": "Message returned if no exercises match the searched string"
},
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {
"description": "Title for mastodon section in the about dialog"
},
"aboutMastodonText": "Sledujte nás na Mastodonu pro aktualizace a novinky o projektu",
"@aboutMastodonText": {
"description": "Text for the mastodon section in the about dialog"
},
"@aboutMastodonTitle": {},
"unVerifiedEmail": "Neověřený e-mail",
"@unVerifiedEmail": {},
"alsoKnownAs": "Také známé jako: {aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {}
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
@@ -663,7 +643,9 @@
"verifiedEmailInfo": "Ověřovací e-mail byl odeslán na adresu {email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {}
"email": {
"type": "String"
}
}
},
"translateExercise": "Přeložte toto cvičení",
@@ -848,10 +830,6 @@
"@onlyLogging": {},
"onlyLoggingHelpText": "Zaškrtněte, pokud chcete pouze zaznamenávat své kalorie a nechcete nastavovat podrobný výživový plán s přesnými jídly",
"@onlyLoggingHelpText": {},
"addGoalsToPlanHelpText": "Tato možnost vám umožňuje nastavit cíle pro energii, bílkoviny, karbohydráty a tuk ve vašem plánu. Upozornění: pokud si nastavíte podrobný jídelní plán, tyto hodnoty budou mít přednost.",
"@addGoalsToPlanHelpText": {},
"addGoalsToPlan": "Přidat cíle do tohoto plánu",
"@addGoalsToPlan": {},
"goalEnergy": "Cíl energie",
"@goalEnergy": {},
"goalProtein": "Cíl bílkovin",
@@ -869,7 +847,9 @@
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"gValue": "{value} g",
@@ -877,7 +857,9 @@
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"percentValue": "{value} %",
@@ -885,7 +867,9 @@
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"surplus": "přebytek",

File diff suppressed because it is too large Load Diff

View File

@@ -71,10 +71,6 @@
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"labelWorkoutPlans": "Προγράμματα προπόνησης",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"previous": "Προηγούμενο",
"@previous": {},
"userProfile": "Το προφίλ σας",
@@ -208,7 +204,9 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"newDay": "Νέα ημέρα",
@@ -259,20 +257,12 @@
"@successfullyDeleted": {
"description": "Message when an item was successfully deleted"
},
"noWorkoutPlans": "Δεν έχετε πλάνα προπόνησης",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"searchNamesInEnglish": "Επίσης, αναζητήστε ονόματα στα Αγγλικά",
"@searchNamesInEnglish": {},
"noMatchingExerciseFound": "Δεν βρέθηκαν αντίστοιχες ασκήσεις",
"@noMatchingExerciseFound": {
"description": "Message returned if no exercises match the searched string"
},
"supersetWith": "υπερσύνολο με",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"equipment": "Εξοπλισμός",
"@equipment": {
"description": "Equipment needed to perform an exercise"
@@ -281,10 +271,6 @@
"@muscles": {
"description": "(main) muscles trained by an exercise"
},
"newWorkout": "Νέο πλάνο προπόνησης",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"reps": "Επαναλήψεις",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
@@ -305,4 +291,4 @@
"@gymMode": {
"description": "Label when starting the gym mode"
}
}
}

View File

@@ -37,6 +37,9 @@
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"selectAvailablePlates": "Select available plates",
"barWeight": "Bar weight",
"useColors": "Use colors",
"password": "Password",
"@password": {},
"confirmPassword": "Confirm password",
@@ -53,6 +56,18 @@
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"useApiToken": "Use API Token",
"useUsernameAndPassword": "Use username and password",
"apiToken": "API Token",
"@apiToken": {},
"invalidApiToken": "Please enter a valid API key",
"@invalidApiToken": {
"description": "Error message when the user enters an invalid API key"
},
"apiTokenValidChars": "An API key may only contain the letters a-f, numbers 0-9 and be exactly 40 characters long",
"@apiTokenValidChars": {
"description": "Error message when the user tries to input a API key with forbidden characters"
},
"customServerUrl": "URL of the wger instance",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
@@ -69,10 +84,6 @@
"@registerInstead": {},
"loginInstead": "Already have an account? Login",
"@loginInstead": {},
"labelWorkoutPlans": "Workout plans",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"labelBottomNavWorkout": "Workout",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
@@ -128,10 +139,6 @@
"description": "Message returned if no exercises match the searched string"
},
"searchNamesInEnglish": "Also search for names in English",
"supersetWith": "superset with",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"equipment": "Equipment",
"@equipment": {
"description": "Equipment needed to perform an exercise"
@@ -148,18 +155,38 @@
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"newWorkout": "New workout plan",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"noWorkoutPlans": "You have no workout plans",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
"startDate": "Start date",
"@startDate": {
"description": "The start date of a nutritional plan or routine"
},
"dayTypeCustom": "Custom",
"dayTypeEnom": "Every minute on the minute",
"dayTypeAmrap": "As many rounds as possible",
"dayTypeHiit": "High intensity interval training",
"dayTypeTabata": "Tabata",
"dayTypeEdt": "Escalating density training",
"dayTypeRft": "Rounds for time",
"dayTypeAfap": "As fast as possible",
"slotEntryTypeNormal": "Normal",
"slotEntryTypeDropset": "Dropset",
"slotEntryTypeMyo": "Myo",
"slotEntryTypePartial": "Partial",
"slotEntryTypeForced": "Forced",
"slotEntryTypeTut": "Time under Tension",
"slotEntryTypeIso": "Isometric hold",
"slotEntryTypeJump": "Jump",
"routines": "Routines",
"newRoutine": "New routine",
"noRoutines": "You have no routines",
"reps": "Reps",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
},
"restTime": "Rest time",
"sets": "Sets",
"@sets": {
"description": "The number of sets to be done for one exercise"
},
"rir": "RiR",
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
@@ -179,12 +206,24 @@
},
"dayDescriptionHelp": "A description of what is done on this day (e.g. 'pull day') or what body parts are trained (e.g. 'chest and shoulders')",
"@dayDescriptionHelp": {},
"setNr": "Set {nr}",
"@setNr": {
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"exerciseNr": "Exercise {nr}",
"@exerciseNr": {
"description": "Header in form indicating the number of the current exercise. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"supersetNr": "Superset {nr}",
"@supersetNr": {
"description": "Header in form indicating the number of the current exercise. Can also be translated as something like 'Superset Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {
"type": "String"
}
}
},
"sameRepetitions": "If you do the same repetitions and weight for all sets you can just fill in one row. For example for 4 sets just enter 10 for the repetitions, this automatically becomes \"4 x 10\".",
@@ -205,6 +244,13 @@
"@workoutSession": {
"description": "A (logged) workout session"
},
"restDay": "Rest day",
"isRestDay": "Is rest day",
"isRestDayHelp": "Please note that all sets and exercises will be removed when you mark a day as a rest day.",
"needsLogsToAdvance": "Needs logs to advance",
"needsLogsToAdvanceHelp": "Select if you want the routine to progress to the next scheduled day only if you've logged a workout for the day",
"routineDays": "Days in routine",
"resultingRoutine": "Resulting routine",
"newDay": "New day",
"@newDay": {},
"newSet": "New set",
@@ -285,14 +331,30 @@
"description": "The goal for macronutrients"
},
"selectMealToLog": "Select a meal to log to diary",
"yourCurrentNutritionPlanHasNoMealsDefinedYet": "Your current nutrition plan has no meals defined",
"@yourCurrentNutritionPlanHasNoMealsDefinedYet": {
"description": "Message shown when a nutrition plan doesn't have any meals"
},
"toAddMealsToThePlanGoToNutritionalPlanDetails": "To add meals to the plan, go to the nutritional plan details",
"@toAddMealsToThePlanGoToNutritionalPlanDetails": {
"description": "Message shown to guide users to the nutritional plan details page to add meals"
},
"goalEnergy": "Energy goal",
"goalProtein": "Protein goal",
"goalCarbohydrates": "Carbohydrates goal",
"goalFat": "Fat goal",
"goalFiber": "Fiber goal",
"anErrorOccurred": "An Error Occurred!",
"@anErrorOccurred": {},
"errorInfoDescription": "We're sorry, but something went wrong. You can help us fix this by reporting the issue on GitHub.",
"errorInfoDescription2": "You can continue using the app, but some features may not work.",
"errorViewDetails": "Technical details",
"applicationLogs": "Application logs",
"errorCouldNotConnectToServer": "Couldn't connect to server",
"errorCouldNotConnectToServerDetails": "The application could not connect to the server. Please check your internet connection or the server URL and try again. If the problem persists, contact the server administrator.",
"copyToClipboard": "Copy to clipboard",
"weight": "Weight",
"min": "Min",
"max": "Max",
"@weight": {
"description": "The weight of a workout log or body weight entry"
},
@@ -301,7 +363,9 @@
"description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {}
"name": {
"type": "String"
}
}
},
"chart30DaysTitle": "{name} last 30 days",
@@ -309,7 +373,9 @@
"description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {}
"name": {
"type": "String"
}
}
},
"chartDuringPlanTitle": "{chartName} during nutritional plan {planName}",
@@ -317,8 +383,12 @@
"description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan",
"type": "text",
"placeholders": {
"chartName": {},
"planName": {}
"chartName": {
"type": "String"
},
"planName": {
"type": "String"
}
}
},
"measurement": "Measurement",
@@ -335,6 +405,14 @@
"@date": {
"description": "The date of a workout log or body weight entry"
},
"endDate": "End date",
"@endDate": {
"description": "The End date of a nutritional plan or routine"
},
"openEnded": "Open ended",
"@openEnded": {
"description": "When a nutrition plan has no pre-defined end date"
},
"value": "Value",
"@value": {
"description": "The value of a measurement entry"
@@ -412,7 +490,9 @@
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"kJ": "kJ",
@@ -428,7 +508,9 @@
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"percentValue": "{value} %",
@@ -436,7 +518,9 @@
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"protein": "Protein",
@@ -462,7 +546,6 @@
"saturatedFat": "Saturated fat",
"@saturatedFat": {},
"fiber": "Fibers",
"@fibres": {},
"sodium": "Sodium",
"@sodium": {},
"amount": "Amount",
@@ -502,73 +585,42 @@
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"newNutritionalPlan": "New nutritional plan",
"@newNutritionalPlan": {},
"overview": "Overview",
"toggleDetails": "Toggle details",
"@toggleDetails": {
"description": "Switch to toggle detail / overview"
},
"goToDetailPage": "Go to detail page",
"@goToDetailPage": {},
"aboutWhySupportTitle": "Open Source & free to use ❤️",
"aboutDescription": "Thank you for using wger! wger is a collaborative open source project, made by fitness enthusiasts from around the world.",
"@aboutDescription": {
"description": "Text in the about dialog"
},
"aboutSourceTitle": "Source code",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"aboutSourceText": "Get the source code of this application and its server on github",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"aboutBugsTitle": "Have a problem or idea?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"aboutBugsText": "Get in touch if something didn't behave as expected or if there is a feature that you feel is missing.",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"aboutContactUsTitle": "Say hi!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutContactUsText": "If you want to chat with us, hop on the Discord server and get in touch",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"aboutDonateTitle": "Make a donation",
"aboutDonateText": "While the project is free and will always remain it, running the server isnt! Development also takes significant time and effort from volunteers. Your contribution directly supports these costs, helping to keep the service reliable.",
"aboutContributeTitle": "Contribute",
"aboutContributeText": "All types of contributions are encouraged. Whether you're a developer, a translator, or just passionate about fitness, every bit of support is appreciated!",
"aboutBugsListTitle": "Report a problem or suggest a feature",
"aboutTranslationListTitle": "Translate the application",
"aboutSourceListTitle": "View source code",
"aboutJoinCommunityTitle": "Join the community",
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {
"description": "Title for mastodon section in the about dialog"
},
"aboutMastodonText": "Follow us on mastodon for updates and news about the project",
"@aboutMastodonText": {
"description": "Text for the mastodon section in the about dialog"
},
"aboutTranslationTitle": "Translation",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"aboutTranslationText": "This application is translated on weblate. If you also want to help, click the link and start translating",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"aboutDonateTitle": "Donate",
"aboutDonateText": "Buy us a coffee to help the project, pay for server costs, and keep us fueled",
"aboutDiscordTitle": "Discord",
"others": "Others",
"calendar": "Calendar",
"@calendar": {},
"goToToday": "Go to today",
"@goToToday": {
"description": "Label on button to jump back to 'today' in the calendar widget"
},
"enterRepetitionsOrWeight": "Please fill in either the repetitions or the weight for at least one of the sets",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"enterValue": "Please enter a value",
"@enterValue": {
"description": "Error message when the user hasn't entered a value on a required field"
@@ -583,8 +635,12 @@
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"enterMinCharacters": "Please enter at least {min} characters",
@@ -592,7 +648,9 @@
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {}
"min": {
"type": "String"
}
}
},
"baseNameEnglish": "All exercises need a base name in English",
@@ -601,7 +659,9 @@
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"setUnitsAndRir": "Set units and RiR",
@@ -654,7 +714,9 @@
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"productNotFound": "Product not found",
@@ -666,7 +728,9 @@
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"scanBarcode": "Scan barcode",
@@ -677,7 +741,75 @@
"@close": {
"description": "Translation for close"
},
"identicalExercisePleaseDiscard": "If you notice an exercise that is identical to the one you're adding, please discard your draft and edit that exercise instead.",
"checkInformationBeforeSubmitting": "Please check that the information you entered is correct before submitting the exercise",
"add_exercise_image_license": "Images must be compatible with the CC BY SA license. If in doubt, upload only photos you've taken yourself.",
"imageDetailsTitle": "Image details",
"@imageDetailsTitle": {
"description": "Title for image details form"
},
"imageDetailsLicenseTitle": "Title",
"@imageDetailsLicenseTitle": {
"description": "Label for image title field"
},
"imageDetailsLicenseTitleHint": "Enter image title",
"@imageDetailsLicenseTitleHint": {
"description": "Hint text for image title field"
},
"imageDetailsSourceLink": "Link to the source website",
"@imageDetailsSourceLink": {
"description": "Label for source link field"
},
"author": "Author(s)",
"@Author": {
"description": "Label for author field"
},
"authorHint": "Enter author name",
"@authorHint": {
"description": "Hint text for author field"
},
"imageDetailsAuthorLink": "Link to author website or profile",
"@imageDetailsAuthorLink": {
"description": "Label for author link field"
},
"imageDetailsDerivativeSource": "Link to the original source, if this is a derivative work",
"@imageDetailsDerivativeSource": {
"description": "Label for derivative source field"
},
"imageDetailsDerivativeHelp": "A derivative work is based on a previous work but contains sufficient new, creative content to entitle it to its own copyright.",
"@imageDetailsDerivativeHelp": {
"description": "Helper text explaining derivative works"
},
"imageDetailsImageType": "Image Type",
"@imageDetailsImageType": {
"description": "Label for image type selector"
},
"imageDetailsLicenseNotice": "By submitting this image, you agree to release it under the CC-BY-SA-4. The image must be either your own work or the author must have released it under a license compatible with it.",
"imageDetailsLicenseNoticeLinkToLicense": "See license text.",
"imageFormatNotSupported": "{imageFormat} not supported",
"@imageFormatNotSupported": {
"description": "Label shown on the error container when image format is not supported",
"type": "text",
"placeholders": {
"imageFormat": {
"type": "String"
}
}
},
"imageFormatNotSupportedDetail": "{imageFormat} images are not supported yet.",
"@imageFormatNotSupportedDetail": {
"description": "Label shown on the image preview container when image format is not supported",
"type": "text",
"placeholders": {
"imageFormat": {
"type": "String"
}
}
},
"add": "add",
"@add": {
"description": "Add button text"
},
"variations": "Variations",
"@variations": {
"description": "Variations of one exercise (e.g. benchpress and benchpress narrow)"
@@ -685,7 +817,9 @@
"alsoKnownAs": "Also known as: {aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {}
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
@@ -695,7 +829,9 @@
"verifiedEmailInfo": "A verification email was sent to {email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {}
"email": {
"type": "String"
}
}
},
"alternativeNames": "Alternative names",
@@ -706,6 +842,12 @@
"images": "Images",
"language": "Language",
"addExercise": "Add exercise",
"fitInWeek": "Fit in week",
"fitInWeekHelp": "If enabled, the days will repeat in a weekly cycle, otherwise the days will follow sequentially without regards to the start of a new week.",
"addSuperset": "Add superset",
"setHasProgression": "Set has progression",
"setHasProgressionWarning": "Please note that at the moment it is not possible to edit all settings for a set on the mobile application or configure automatic progression. For now, please use the web application.",
"setHasNoExercises": "This set has no exercises yet!",
"contributeExercise": "Contribute an exercise",
"translation": "Translation",
"translateExercise": "Translate this exercise now",
@@ -713,12 +855,13 @@
"@baseData": {
"description": "The base data for an exercise such as category, trained muscles, etc."
},
"enterTextInLanguage": "Please enter the text in the correct language!",
"settingsTitle": "Settings",
"settingsCacheTitle": "Cache",
"settingsExerciseCacheDescription": "Exercise cache",
"settingsIngredientCacheDescription": "Ingredient cache",
"settingsCacheDeletedSnackbar": "Cache successfully cleared",
"aboutPageTitle": "About Wger",
"aboutPageTitle": "About us & Support",
"contributeExerciseWarning": "You can only contribute exercises if your account is older than {days} days and have verified your email",
"@contributeExerciseWarning": {
"description": "Number of days before which a person can add exercise",
@@ -729,6 +872,9 @@
}
}
},
"simpleMode": "Simple mode",
"simpleModeHelp": "Hide some of the more advanced settings when editing exercises",
"progressionRules": "This exercise has progression rules and can't be edited on the mobile app. Please use the web application to edit this exercise.",
"cacheWarning": "Due to caching it might take some time till the changes are visible throughout the application.",
"textPromptTitle": "Ready to start?",
"textPromptSubheading": "Press the action button to begin",
@@ -848,6 +994,10 @@
"@repetitions": {
"description": "Generated entry for translation for server strings"
},
"resistance_band": "Resistance band",
"@resistance_band": {
"description": "Generated entry for translation for server strings"
},
"sz_bar": "SZ-Bar",
"@sz_bar": {
"description": "Generated entry for translation for server strings"
@@ -888,5 +1038,34 @@
"@log": {
"description": "Log a specific meal (imperative form)"
},
"done": "Done"
"done": "Done",
"overallChangeWeight": "Overall change",
"@overallChangeWeight": {
"description": "Overall change in weight, added for localization"
},
"goalTypeMeals": "From meals",
"@goalTypeMeals": {
"description": "added for localization of Class GoalType's filed meals"
},
"goalTypeBasic": "Basic",
"@goalTypeBasic": {
"description": "added for localization of Class GoalType's filed basic"
},
"goalTypeAdvanced": "Advanced",
"@goalTypeAdvanced": {
"description": "added for localization of Class GoalType's filed advanced"
},
"indicatorRaw": "raw",
"@indicatorRaw": {
"description": "added for localization of Class Indicator's field text"
},
"indicatorAvg": "avg",
"@indicatorAvg": {
"description": "added for localization of Class Indicator's field text"
},
"endWorkout": "End Workout",
"themeMode": "Theme mode",
"darkMode": "Always dark mode",
"lightMode": "Always light mode",
"systemMode": "System settings"
}

File diff suppressed because it is too large Load Diff

1
lib/l10n/app_fa.arb Normal file
View File

@@ -0,0 +1 @@
{}

File diff suppressed because it is too large Load Diff

View File

@@ -45,19 +45,19 @@
"@customServerHint": {
"description": "Hint text for the form where the users can enter their own wger instance"
},
"labelWorkoutLogs": "יומני אימון",
"labelWorkoutLogs": "אימונים מתועדים",
"@labelWorkoutLogs": {
"description": "(Workout) logs"
},
"labelWorkoutPlan": "תוכנית כושר",
"labelWorkoutPlan": "תוכנית אימונים",
"@labelWorkoutPlan": {
"description": "Title for screen workout plan"
},
"labelDashboard": "לוח פעולות",
"labelDashboard": "לוח בקרה",
"@labelDashboard": {
"description": "Title for screen dashboard"
},
"successfullyDeleted": "נמחק",
"successfullyDeleted": "המחיקה בוצעה",
"@successfullyDeleted": {
"description": "Message when an item was successfully deleted"
},
@@ -65,18 +65,14 @@
"@successfullySaved": {
"description": "Message when an item was successfully saved"
},
"exercise": "תרגיל כושר",
"exercise": "תרגיל",
"@exercise": {
"description": "An exercise for a workout"
},
"searchExercise": "חיפוש תרגיל כושר להוספה",
"searchExercise": "חיפוש תרגילים להוספה",
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"supersetWith": "סט תרגילים ראשי עם",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"muscles": "שרירים",
"@muscles": {
"description": "(main) muscles trained by an exercise"
@@ -89,14 +85,6 @@
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"newWorkout": "תוכנית כושר חדשה",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"noWorkoutPlans": "אין לך תוכניות כושר",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"repetitions": "חזרות",
"@repetitions": {
"description": "Repetitions for an exercise set"
@@ -128,7 +116,9 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"impression": "התרשמות",
@@ -139,7 +129,7 @@
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
"workoutSession": "אימון",
"workoutSession": "סשן אימונים",
"@workoutSession": {
"description": "A (logged) workout session"
},
@@ -175,7 +165,7 @@
"@mealLogged": {},
"addIngredient": "הוספת מרכיב",
"@addIngredient": {},
"nutritionalPlan": "תוכנית תזונה",
"nutritionalPlan": "תכנון תזונתי",
"@nutritionalPlan": {},
"nutritionalDiary": "יומן תזונה",
"@nutritionalDiary": {},
@@ -209,7 +199,7 @@
"@value": {
"description": "The value of a measurement entry"
},
"start": "התחל",
"start": "התחלה",
"@start": {
"description": "Label on button to start the gym mode (i.e., an imperative)"
},
@@ -233,7 +223,7 @@
"@energy": {
"description": "Energy in a meal, ingredient etc. e.g. in kJ"
},
"logMeal": "רשום את הארוחה הזאת",
"logMeal": "תיעוד המנה ביומן התזונתי",
"@logMeal": {},
"logHelpEntriesUnits": "שים לב שרק רשומות עם יחידת משקל (ק\"ג או ליברות) וחזרות הן ממופות, ישנה התעלמות משילובים אחרים כגון זמן או עד כשלון.",
"@logHelpEntriesUnits": {},
@@ -253,7 +243,7 @@
"@difference": {},
"percentEnergy": "אחוז של אנרגיה",
"@percentEnergy": {},
"total": "סה\"כ",
"total": "סה״כ",
"@total": {
"description": "Label used for total sums of e.g. calories or similar"
},
@@ -283,7 +273,7 @@
"@amount": {
"description": "The amount (e.g. in grams) of an ingredient in a meal"
},
"unit": "יחידה",
"unit": "יחידת מידה",
"@unit": {
"description": "The unit used for a repetition (kg, time, etc.)"
},
@@ -304,7 +294,9 @@
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"newNutritionalPlan": "תוכנית תזונה חדשה",
@@ -315,18 +307,6 @@
},
"goToDetailPage": "עבור לדף פירוט",
"@goToDetailPage": {},
"aboutSourceTitle": "קוד מקור",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"aboutContactUsTitle": "אמור שלום!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutTranslationTitle": "תרגום",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"calendar": "לוח שנה",
"@calendar": {},
"goToToday": "עבור להיום",
@@ -337,24 +317,16 @@
"@enterValue": {
"description": "Error message when the user hasn't entered a value on a required field"
},
"aboutSourceText": "לקבלת קוד המקור של אפליקציה זאת והשרת שלה בגיטהאב",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"enterRepetitionsOrWeight": "אנא מלאו את החזרות או את המשקל בלפחות אחד מהסטים",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"setUnitsAndRir": "יחידות סט וחזרות בעתודה",
"@setUnitsAndRir": {
"description": "Label shown on the slider where the user can toggle showing units and RiR",
"type": "text"
},
"reset": "אתחול",
"reset": "איפוס",
"@reset": {
"description": "Button text allowing the user to reset the entered values to the default"
},
"loginInstead": "הכנס במקום",
"loginInstead": "כבר יש לך חשבון? ניתן להיכנס",
"@loginInstead": {},
"selectExercise": "אנא בחר/י אימון",
"@selectExercise": {
@@ -365,8 +337,12 @@
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"nrOfSets": "כמות סטים לאימון: {nrOfSets}",
@@ -374,7 +350,9 @@
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"enterValidNumber": "אנא הכנס מספר תקין",
@@ -401,7 +379,7 @@
"@gallery": {},
"addImage": "הוסף תמונה",
"@addImage": {},
"email": "כתובת מייל",
"email": "כתובת דואר אלקטרוני",
"@email": {},
"username": "שם משתמש",
"@username": {},
@@ -421,17 +399,13 @@
"@passwordsDontMatch": {
"description": "Error message when the user enters two different passwords during registration"
},
"invalidEmail": "אנא הכנס/י כתובת מייל תקינה",
"invalidEmail": "נא להקליד כתובת דואר אלקטרוני תקינה",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
"registerInstead": "אין לך חשבון? הירשם עכשיו",
"@registerInstead": {},
"labelWorkoutPlans": "תוכניות כושר",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"labelBottomNavWorkout": "כושר",
"labelBottomNavWorkout": "אימון",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
},
@@ -463,10 +437,6 @@
"@loadingText": {
"description": "Text to show when entries are being loaded in the background: Loading..."
},
"aboutBugsTitle": "נתקלת בבעיה או יש לך רעיון?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"aboutDescription": "תודה לך על השימוש בווגר! ווגר הוא פרויקט קוד פתוח שנעשה על ידי אוהבי כושר מרחבי העולם.",
"@aboutDescription": {
"description": "Text in the about dialog"
@@ -475,21 +445,9 @@
"@selectExercises": {},
"logHelpEntries": "אם ביום אחד ישנה יותר מרשומה אחת עם אותה כמות חזרות, אבל משקל שונה, רק הרשומה עם המשקל הגבוה יותר תופיע בדיאגרמה.",
"@logHelpEntries": {},
"aboutBugsText": "צרו קשר אם משהו לא מתנהג כצפוי או אם יש תכונה שאתם מרגישים בחוסרה.",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"aboutTranslationText": "האפליקציה הזאת מתורגמת ב-weblate. אם גם אתם רוצים לעזור, לחצו על הקישור ותתחילו לתרגם",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"aboutContactUsText": "אם אתם רוצים לדבר עמנו, אתם מוזמנים לגשת לשרת הדיסקורד וליצור עמנו קשר שם",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"searchNamesInEnglish": "חפש גם לפי שמות באנגלית",
"searchNamesInEnglish": "לחפש גם לפי השמות באנגלית",
"@searchNamesInEnglish": {},
"success": "מוצלח",
"success": "הפעולה צלחה",
"@success": {
"description": "Message when an action completed successfully, usually used as a heading"
},
@@ -508,5 +466,501 @@
"noMatchingExerciseFound": "לא נמצאו תרגילים תואמים",
"@noMatchingExerciseFound": {
"description": "Message returned if no exercises match the searched string"
},
"useColors": "שימוש בצבעים",
"@useColors": {},
"useApiToken": "שימוש באסימון API",
"@useApiToken": {},
"useUsernameAndPassword": "שימוש בשם משתמש וסיסמה",
"@useUsernameAndPassword": {},
"apiToken": "אסימון API",
"@apiToken": {},
"invalidApiToken": "נא להקליד אסימון API תקין",
"@invalidApiToken": {
"description": "Error message when the user enters an invalid API key"
},
"routines": "שגרות",
"@routines": {},
"newRoutine": "שגרה חדשה",
"@newRoutine": {},
"noRoutines": "אין לך שגרות",
"@noRoutines": {},
"restDay": "יום מנוחה",
"@restDay": {},
"isRestDay": "הוא יום מנוחה",
"@isRestDay": {},
"routineDays": "ימים בשגרה",
"@routineDays": {},
"verify": "אימות",
"@verify": {},
"searchIngredient": "חיפוש מרכיב",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"onlyLogging": "מעקב אחר קלוריות בלבד",
"@onlyLogging": {},
"goalMacro": "מטרות מזעריות",
"@goalMacro": {
"description": "The goal for macronutrients"
},
"goalEnergy": "יעד אנרגיה",
"@goalEnergy": {},
"goalProtein": "יעד חלבונים",
"@goalProtein": {},
"goalFat": "יעד שומנים",
"@goalFat": {},
"goalFiber": "יעד סיבים תזונתיים",
"@goalFiber": {},
"errorViewDetails": "פרטים טכניים",
"@errorViewDetails": {},
"errorCouldNotConnectToServer": "לא היה ניתן להתחבר לשרת",
"@errorCouldNotConnectToServer": {},
"min": "מינ׳",
"@min": {},
"max": "מקס׳",
"@max": {},
"today": "היום",
"@today": {},
"weekAverage": "ממוצע שבועי",
"@weekAverage": {
"description": "Header for the column of '7 day average' nutritional values, i.e. what was logged last week"
},
"kcalValue": "{value} ק׳ קלוריות",
"@kcalValue": {
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {
"type": "String"
}
}
},
"gValue": "{value} גר׳",
"@gValue": {
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {
"type": "String"
}
}
},
"percentValue": "{value}%",
"@percentValue": {
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {
"type": "String"
}
}
},
"aboutWhySupportTitle": "תוכנה בקוד פתוח, וחופשית לשימוש ❤️",
"@aboutWhySupportTitle": {},
"aboutDonateTitle": "תרומה כספית",
"@aboutDonateTitle": {},
"aboutContributeTitle": "התנדבות",
"@aboutContributeTitle": {},
"aboutTranslationListTitle": "עזרה בתרגום היישום",
"@aboutTranslationListTitle": {},
"aboutSourceListTitle": "צפייה בקוד המקור",
"@aboutSourceListTitle": {},
"aboutJoinCommunityTitle": "הצטרפות לקהילה",
"@aboutJoinCommunityTitle": {},
"others": "אחרים",
"@others": {},
"selectEntry": "נא לבחור ערך",
"@selectEntry": {},
"recentlyUsedIngredients": "מרכיבים שנוספו לאחרונה",
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
},
"appUpdateTitle": "צריך לעדכן",
"@appUpdateTitle": {},
"appUpdateContent": "גרסה זו של היישום אינה מסוגלת להתחבר לשרת, נא לעדכן את היישום.",
"@appUpdateContent": {},
"productFound": "המוצר נמצא",
"@productFound": {
"description": "Header label for dialog when product is found with barcode"
},
"productNotFound": "המוצר לא נמצא",
"@productNotFound": {
"description": "Header label for dialog when product is not found with barcode"
},
"scanBarcode": "סריקת ברקוד",
"@scanBarcode": {
"description": "Label for scan barcode button"
},
"close": "סגירה",
"@close": {
"description": "Translation for close"
},
"alsoKnownAs": "מוכר גם בתור: {aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
"alternativeNames": "שמות חלופיים",
"@alternativeNames": {},
"previous": "הקודם",
"@previous": {},
"next": "הבא",
"@next": {},
"images": "תמונות",
"@images": {},
"language": "שפה",
"@language": {},
"addExercise": "הוספת תרגיל",
"@addExercise": {},
"translation": "תרגום",
"@translation": {},
"translateExercise": "לתרגם את התרגיל הזה כעת",
"@translateExercise": {},
"settingsTitle": "הגדרות",
"@settingsTitle": {},
"settingsCacheTitle": "מטמון",
"@settingsCacheTitle": {},
"settingsExerciseCacheDescription": "מטמון תרגילים",
"@settingsExerciseCacheDescription": {},
"aboutPageTitle": "מידע כללי ותמיכה",
"@aboutPageTitle": {},
"simpleMode": "מצב פשוט",
"@simpleMode": {},
"arms": "זרועות",
"@arms": {
"description": "Generated entry for translation for server strings"
},
"back": "גב",
"@back": {
"description": "Generated entry for translation for server strings"
},
"biceps": "שרירי הקיבורת (דו־ראשיים)",
"@biceps": {
"description": "Generated entry for translation for server strings"
},
"body_weight": "משקל הגוף",
"@body_weight": {
"description": "Generated entry for translation for server strings"
},
"cardio": "סיבולת",
"@cardio": {
"description": "Generated entry for translation for server strings"
},
"chest": "חזה",
"@chest": {
"description": "Generated entry for translation for server strings"
},
"gym_mat": "שטיח אימונים",
"@gym_mat": {
"description": "Generated entry for translation for server strings"
},
"kilometers": "קילומטרים",
"@kilometers": {
"description": "Generated entry for translation for server strings"
},
"kilometers_per_hour": "קילומטרים לשעה",
"@kilometers_per_hour": {
"description": "Generated entry for translation for server strings"
},
"miles": "מילים",
"@miles": {
"description": "Generated entry for translation for server strings"
},
"miles_per_hour": "מילים לשעה",
"@miles_per_hour": {
"description": "Generated entry for translation for server strings"
},
"minutes": "דקות",
"@minutes": {
"description": "Generated entry for translation for server strings"
},
"resistance_band": "רצועת התנגדות",
"@resistance_band": {
"description": "Generated entry for translation for server strings"
},
"seconds": "שניות",
"@seconds": {
"description": "Generated entry for translation for server strings"
},
"shoulders": "כתפיים",
"@shoulders": {
"description": "Generated entry for translation for server strings"
},
"triceps": "השרירים התלת־ראשיים",
"@triceps": {
"description": "Generated entry for translation for server strings"
},
"kg": "ק״ג",
"@kg": {
"description": "Generated entry for translation for server strings"
},
"goalTypeBasic": "בסיסי",
"@goalTypeBasic": {
"description": "added for localization of Class GoalType's filed basic"
},
"goalTypeAdvanced": "מתקדם",
"@goalTypeAdvanced": {
"description": "added for localization of Class GoalType's filed advanced"
},
"indicatorAvg": "ממוצע",
"@indicatorAvg": {
"description": "added for localization of Class Indicator's field text"
},
"systemMode": "הגדרות מערכת",
"@systemMode": {},
"copyToClipboard": "העתקה ללוח",
"@copyToClipboard": {},
"noIngredientsDefined": "עדיין לא הוגדרו מרכיבים",
"@noIngredientsDefined": {},
"restTime": "זמן מנוחה",
"@restTime": {},
"sets": "סטים",
"@sets": {
"description": "The number of sets to be done for one exercise"
},
"ingredientLogged": "המרכיב תועד ביומן",
"@ingredientLogged": {},
"logIngredient": "תיעוד המרכיב ביומן התזונתי",
"@logIngredient": {},
"errorInfoDescription": "משהו השתבש, עמך הסליחה. ניתן לעזור לנו בתיקון התקלה בעזרת דיווח על הבעיה ב־GitHub.",
"@errorInfoDescription": {},
"errorInfoDescription2": "אפשר להמשיך להשתמש ביישום, אבל ייתכן כי תכונות מסוימות לא יעבדו.",
"@errorInfoDescription2": {},
"proteinShort": "חל׳",
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"fatShort": "שומנ׳",
"@fatShort": {
"description": "The first letter or short name of the word 'Fat', used in overviews"
},
"moreMeasurementEntries": "הוספת מדד חדש",
"@moreMeasurementEntries": {
"description": "Message shown when the user wants to add new measurement"
},
"aboutBugsListTitle": "דיווח על בעיה או הצעת תכונה חדשה",
"@aboutBugsListTitle": {},
"aboutDiscordTitle": "Discord",
"@aboutDiscordTitle": {},
"dataCopied": "הנתונים הועתקו לערך החדש",
"@dataCopied": {
"description": "Snackbar message to show on copying data to a new log entry"
},
"useMetric": "שימוש בשיטה המטרית למדידת משקל הגוף",
"@useMetric": {},
"selectMealToLog": "בחירת מנה לתיעוד ביומן",
"@selectMealToLog": {},
"energyShort": "אנרגיה",
"@energyShort": {
"description": "The first letter or short name of the word 'Energy', used in overviews"
},
"loggedToday": "תועדו היום",
"@loggedToday": {},
"enterMinCharacters": "נא להקליד לפחות {min} תווים",
"@enterMinCharacters": {
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {
"type": "String"
}
}
},
"productFoundDescription": "הברקוד תואם למוצר: {productName}. להמשיך?",
"@productFoundDescription": {
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {
"type": "String"
}
}
},
"productNotFoundDescription": "לא נמצא המוצר עם הברקוד שנסרק {barcode} במאגר של wger",
"@productNotFoundDescription": {
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {
"type": "String"
}
}
},
"verifiedEmail": "דוא״ל מאומת",
"@verifiedEmail": {},
"unVerifiedEmail": "דוא״ל לא מאומת",
"@unVerifiedEmail": {},
"verifiedEmailInfo": "נשלחה הודעת אימות לכתובת {email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"setHasNoExercises": "עדיין אין אימונים בסט זה!",
"@setHasNoExercises": {},
"settingsIngredientCacheDescription": "מטמון מרכיב",
"@settingsIngredientCacheDescription": {},
"settingsCacheDeletedSnackbar": "המטמון נוקה בהצלחה",
"@settingsCacheDeletedSnackbar": {},
"textPromptTitle": "מוכנים להתחיל?",
"@textPromptTitle": {},
"abs": "שרירי בטן",
"@abs": {
"description": "Generated entry for translation for server strings"
},
"lb": "פאונד",
"@lb": {
"description": "Generated entry for translation for server strings"
},
"log": "תיעוד",
"@log": {
"description": "Log a specific meal (imperative form)"
},
"done": "סיום",
"@done": {},
"themeMode": "ערכת עיצוב",
"@themeMode": {},
"darkMode": "עיצוב כהה תמיד",
"@darkMode": {},
"lightMode": "עיצוב בהיר תמיד",
"@lightMode": {},
"barWeight": "משקל Bar",
"@barWeight": {},
"apiTokenValidChars": "מפתח API יכול להכיל רק את האותיות a-f, את הספרות 0-9, ועליו להיות באורך 40 תווים בדיוק",
"@apiTokenValidChars": {
"description": "Error message when the user tries to input a API key with forbidden characters"
},
"exerciseNr": "תרגיל {nr}",
"@exerciseNr": {
"description": "Header in form indicating the number of the current exercise. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {
"type": "String"
}
}
},
"isRestDayHelp": "לתשומת לבך, כל הסטים והתרגילים יוסרו מימים שהגדרת בתור ימי מנוחה.",
"@isRestDayHelp": {},
"onlyLoggingHelpText": "נא לסמן תיבה זו אך ורק אם ברצונך לתעד את כמות הקלוריות מבלי להגדיר תוכנית תזונתית עם מנות ספציפיות",
"@onlyLoggingHelpText": {},
"yourCurrentNutritionPlanHasNoMealsDefinedYet": "לא הוגדרו מנות בתוכניות התזונתית הנוכחית שלך",
"@yourCurrentNutritionPlanHasNoMealsDefinedYet": {
"description": "Message shown when a nutrition plan doesn't have any meals"
},
"toAddMealsToThePlanGoToNutritionalPlanDetails": "לצורך הוספת מנות לתוכנית, יש לגשת לפרטי התוכנית התזונתית",
"@toAddMealsToThePlanGoToNutritionalPlanDetails": {
"description": "Message shown to guide users to the nutritional plan details page to add meals"
},
"errorCouldNotConnectToServerDetails": "אין באפשרות היישום להתחבר לשרת. נא לבדוק את החיבור לאינטרנט או לכתובת השרת ולנסות שוב. אם הבעיה ממשיכה להתקיים, נא לפנות למנהל השרת.",
"@errorCouldNotConnectToServerDetails": {},
"chartAllTimeTitle": "{name} בכל הזמנים",
"@chartAllTimeTitle": {
"description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chart30DaysTitle": "{name} ב־30 הימים האחרונים",
"@chart30DaysTitle": {
"description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chartDuringPlanTitle": "{chartName} במסגרת התוכנית התזונתית {planName}",
"@chartDuringPlanTitle": {
"description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan",
"type": "text",
"placeholders": {
"chartName": {
"type": "String"
},
"planName": {
"type": "String"
}
}
},
"aboutDonateText": "בעוד המיזם הזה חופשי ותמיד יישאר כך, תפעול השרת כן עולה לנו כסף! גם הפיתוח דורש השקעה משמעותית של זמן ומאמץ של מתנדבים. התרומה שלך תלך ישירות לכיסוי העלויות האלה, המאפשרות לספק את השירות שלנו נאמנה.",
"@aboutDonateText": {},
"aboutContributeText": "אנחנו מעודדים את כל סוגי התרומה. בין אם היא מתבטאת בפיתוח קוד, תרגום, או אפילו רק תשוקה לכושר, אנו מעריכים כל סוג של תמיכה!",
"@aboutContributeText": {},
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {},
"baseNameEnglish": "לכל התרגילים נחוץ שם מקור באנגלית",
"@baseNameEnglish": {},
"add_exercise_image_license": "על התמונות להיות תואמות לרישיון CC BY SA. אם יש ספק כלשהו, יש להעלות רק תמונות שצילמת בעצמך.",
"@add_exercise_image_license": {},
"variations": "וריאציות",
"@variations": {
"description": "Variations of one exercise (e.g. benchpress and benchpress narrow)"
},
"verifiedEmailReason": "עליך לאמת את הדואר האלקטרוני שלך כדי לתרום תרגילים",
"@verifiedEmailReason": {},
"oneNamePerLine": "שם אחד בכל שורה",
"@oneNamePerLine": {},
"whatVariationsExist": "מהן הווריאציות שיש לתרגיל זה, אם קיימות?",
"@whatVariationsExist": {},
"contributeExercise": "לתרום תרגיל",
"@contributeExercise": {},
"contributeExerciseWarning": "אפשר לתרום תרגילים אך ורק אם החשבון שלך בן יותר מ־{days} ימים וכתובת הדואר האלקטרוני שלך מאומתת",
"@contributeExerciseWarning": {
"description": "Number of days before which a person can add exercise",
"placeholders": {
"days": {
"type": "String",
"example": "14"
}
}
},
"textPromptSubheading": "נא ללחוץ על כפתור הפעולה כדי להתחיל",
"@textPromptSubheading": {},
"bench": "",
"@bench": {
"description": "Generated entry for translation for server strings"
},
"dumbbell": "משקולות",
"@dumbbell": {
"description": "Generated entry for translation for server strings"
},
"kettlebell": "משקולות קטלבל",
"@kettlebell": {
"description": "Generated entry for translation for server strings"
},
"legs": "רגליים",
"@legs": {
"description": "Generated entry for translation for server strings"
},
"lower_back": "גב תחתון",
"@lower_back": {
"description": "Generated entry for translation for server strings"
},
"max_reps": "מספר מרבי של חזרות",
"@max_reps": {
"description": "Generated entry for translation for server strings"
},
"swiss_ball": "כדור התעמלות",
"@swiss_ball": {
"description": "Generated entry for translation for server strings"
},
"none__bodyweight_exercise_": "ללא (התרגיל מסתמך על משקל גוף)",
"@none__bodyweight_exercise_": {
"description": "Generated entry for translation for server strings"
},
"overallChangeWeight": "שינוי כללי",
"@overallChangeWeight": {
"description": "Overall change in weight, added for localization"
}
}

View File

@@ -49,8 +49,6 @@
"@registerInstead": {},
"loginInstead": "इसके बजाय लॉग इन करें",
"@loginInstead": {},
"labelWorkoutPlans": "कसरत की योजनाएं",
"@labelWorkoutPlans": {},
"labelBottomNavWorkout": "कसरत",
"@labelBottomNavWorkout": {},
"labelBottomNavNutrition": "पोषण",
@@ -69,8 +67,6 @@
"@exercise": {},
"searchExercise": "जोड़ने के लिए व्यायाम खोजें",
"@searchExercise": {},
"supersetWith": "सुपरसेट के साथ",
"@supersetWith": {},
"equipment": "उपकरण",
"@equipment": {},
"muscles": "मांसपेशियों",
@@ -79,10 +75,6 @@
"@musclesSecondary": {},
"category": "श्रेणी",
"@category": {},
"newWorkout": "नई कसरत योजना",
"@newWorkout": {},
"noWorkoutPlans": "आपकी कोई कसरत योजना नहीं है",
"@noWorkoutPlans": {},
"repetitions": "दोहराव",
"@repetitions": {},
"reps": "रेप्स",
@@ -149,7 +141,7 @@
"@logMeal": {},
"addIngredient": "सामग्री जोड़ें",
"@addIngredient": {},
"logIngredient": "पोषण डायरी में सहेजें",
"logIngredient": "पोषण डायरी में सामग्री दर्ज करें",
"@logIngredient": {},
"searchIngredient": "सामग्री खोजें",
"@searchIngredient": {},
@@ -259,26 +251,8 @@
"@goToDetailPage": {},
"aboutDescription": "Wger का उपयोग करने के लिए धन्यवाद! Wger एक सहयोगी ओपन सोर्स प्रोजेक्ट है, जिसे दुनिया भर के फिटनेस उत्साही लोगों द्वारा बनाया गया है।",
"@aboutDescription": {},
"aboutSourceTitle": "सोर्स कोड",
"@aboutSourceTitle": {},
"aboutSourceText": "इस एप्लिकेशन का स्रोत कोड और इसके सर्वर को Github पर प्राप्त करें",
"@aboutSourceText": {},
"aboutBugsTitle": "कोई समस्या या विचार है?",
"@aboutBugsTitle": {},
"aboutBugsText": "अगर किसी चीज़ ने अपेक्षा के अनुरूप व्यवहार नहीं किया या कोई ऐसी विशेषता है जो आपको याद आ रही है तो संपर्क करें।",
"@aboutBugsText": {},
"aboutContactUsTitle": "संपर्क करें",
"@aboutContactUsTitle": {},
"aboutContactUsText": "यदि आप हमारे साथ चैट करना चाहते हैं, तो Discord सर्वर पर आएं और संपर्क करें",
"@aboutContactUsText": {},
"aboutTranslationTitle": "अनुवाद",
"@aboutTranslationTitle": {},
"aboutTranslationText": "इस एप्लिकेशन का वेबलेट पर अनुवाद किया गया है। अगर आप भी मदद करना चाहते हैं, तो लिंक पर क्लिक करें और अनुवाद करना शुरू करें।",
"@aboutTranslationText": {},
"calendar": "कैलेंडर",
"@calendar": {},
"enterRepetitionsOrWeight": "कृपया इनमें से कम से कम एक सेट के लिए दोहराव या वजन भरें",
"@enterRepetitionsOrWeight": {},
"enterValue": "कृपया मान दर्ज करें",
"@enterValue": {},
"selectExercise": "कृपया एक पाठ्यक्रम का चयन करें",
@@ -324,5 +298,15 @@
"scanBarcode": "बारकोड स्कैन करें",
"@scanBarcode": {},
"close": "बंद करें",
"@close": {}
}
"@close": {},
"userProfile": "आपकी प्रोफ़ाइल",
"@userProfile": {},
"invalidUsername": "कृपया सही यूजरनेम प्रविष्ट करे",
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"onlyLogging": "सिर्फ कैलोरी ट्रैक करें",
"@onlyLogging": {},
"onlyLoggingHelpText": "यदि आप केवल अपनी कैलोरी लॉग करना चाहते हैं और विशिष्ट भोजन के साथ विस्तृत पोषण योजना नहीं बनाना चाहते हैं तो बॉक्स को चेक करें",
"@onlyLoggingHelpText": {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,80 @@
{}
{
"login": "Bejelentkezés",
"@login": {
"description": "Text for login button"
},
"invalidEmail": "Nem megfelelő e-mail cím",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
"customServerHint": "Add meg a saját szervered címét, máskülönben az alapértelmezettet használjuk",
"@customServerHint": {
"description": "Hint text for the form where the users can enter their own wger instance"
},
"userProfile": "Profil",
"@userProfile": {},
"logout": "Kijelentkezés",
"@logout": {
"description": "Text for logout button"
},
"register": "Regisztráció",
"@register": {
"description": "Text for registration button"
},
"useDefaultServer": "Alapértelmezett szerver",
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"useCustomServer": "Egyedi szerver",
"@useCustomServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"invalidUrl": "Nem megfelelő URL",
"@invalidUrl": {
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
},
"password": "Jelszó",
"@password": {},
"passwordTooShort": "A jelszó túl rövid",
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"usernameValidChars": "A felhasználónév csak betűket, számokat és @, +, ., -, és _ karaktereket tartalmazhat",
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"passwordsDontMatch": "A jelszavak nem egyeznek",
"@passwordsDontMatch": {
"description": "Error message when the user enters two different passwords during registration"
},
"labelBottomNavNutrition": "Táplálkozás",
"@labelBottomNavNutrition": {
"description": "Label used in bottom navigation, use a short word"
},
"username": "Felhasználónév",
"@username": {},
"email": "E-mail cím",
"@email": {},
"confirmPassword": "Jelszó újra",
"@confirmPassword": {},
"invalidUsername": "Nem megfelelő felhasználónév",
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"customServerUrl": "A wger szerver URL-je",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"reset": "Visszaállítás",
"@reset": {
"description": "Button text allowing the user to reset the entered values to the default"
},
"registerInstead": "Nincs fiókod? Regisztrálj most",
"@registerInstead": {},
"loginInstead": "Már van fiókod? Bejelentkezés",
"@loginInstead": {},
"labelBottomNavWorkout": "Edzés",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
}
}

View File

@@ -36,9 +36,9 @@
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"password": "Password",
"password": "Kata sandi",
"@password": {},
"confirmPassword": "Konfirmasi Password",
"confirmPassword": "Konfirmasi Kata Sandi",
"@confirmPassword": {},
"invalidEmail": "Mohon masukkan E-mail yang valid",
"@invalidEmail": {
@@ -46,7 +46,7 @@
},
"email": "Alamat E-mail",
"@email": {},
"username": "Username",
"username": "Nama pengguna",
"@username": {},
"invalidUsername": "Mohon masukkan username yang valid",
"@invalidUsername": {
@@ -68,10 +68,6 @@
"@registerInstead": {},
"loginInstead": "Punya akun? Masuk",
"@loginInstead": {},
"labelWorkoutPlans": "Daftar rencana workout",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"labelBottomNavWorkout": "Workout",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
@@ -108,10 +104,6 @@
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"supersetWith": "tambahkan dengan",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"equipment": "Perlengkapan",
"@equipment": {
"description": "Equipment needed to perform an exercise"
@@ -128,23 +120,15 @@
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"newWorkout": "Rencana workout baru",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"noWorkoutPlans": "Kamu belum mempunyain rencana workout",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"repetitions": "Pengulangan",
"@repetitions": {
"description": "Repetitions for an exercise set"
},
"reps": "Reps",
"reps": "Repetisi",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
},
"rir": "RiR",
"rir": "Repetisi tersisa",
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
},
@@ -167,7 +151,9 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"sameRepetitions": "Jika Anda melakukan pengulangan dan bobot yang sama untuk semua set, Anda cukup mengisi satu baris. Misalnya untuk 4 set cukup masukkan 10 untuk pengulangan, ini secara otomatis menjadi \"4 x 10\".",
@@ -200,7 +186,7 @@
"@gymMode": {
"description": "Label when starting the gym mode"
},
"plateCalculator": "Plates",
"plateCalculator": "Lempeng Beban",
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
@@ -218,9 +204,9 @@
},
"todaysWorkout": "Latihanmu hari ini",
"@todaysWorkout": {},
"logHelpEntries": "If on a single day there is more than one entry with the same number of repetitions, but different weights, only the entry with the higher weight is shown in the diagram.",
"logHelpEntries": "Jika dalam satu hari terdapat lebih dari satu entri dengan jumlah repetisi yang sama namun beban berbeda, hanya entri dengan beban tertinggi yang akan ditampilkan di diagram.",
"@logHelpEntries": {},
"logHelpEntriesUnits": "Note that only entries with a weight unit (kg or lb) and repetitions are charted, other combinations such as time or until failure are ignored here.",
"logHelpEntriesUnits": "Perlu dicatat bahwa hanya entri dengan satuan beban (kg atau lb) dan repetisi yang akan ditampilkan di grafik. Kombinasi lain seperti waktu atau hingga gagal tidak diperhitungkan di sini.",
"@logHelpEntriesUnits": {},
"description": "Description",
"@description": {},
@@ -236,49 +222,49 @@
},
"addMeal": "Tambah makanan",
"@addMeal": {},
"mealLogged": "Meal logged to diary",
"mealLogged": "Makanan ditambahkan ke buku harian",
"@mealLogged": {},
"logMeal": "Catat makanan ini",
"logMeal": "Catat makanan ke buku harian nutrisi",
"@logMeal": {},
"addIngredient": "Tambah komposisi",
"@addIngredient": {},
"logIngredient": "Simpan di diari nutrisi",
"logIngredient": "Catat bahan ke buku harian nutrisi",
"@logIngredient": {},
"searchIngredient": "Cari komposisi",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"nutritionalPlan": "Nutritional plan",
"nutritionalPlan": "Rencana Nutrisi",
"@nutritionalPlan": {},
"nutritionalDiary": "Nutritional diary",
"nutritionalDiary": "Buku Harian Nutrisi",
"@nutritionalDiary": {},
"nutritionalPlans": "Nutritional plans",
"nutritionalPlans": "Rencana Nutrisi",
"@nutritionalPlans": {},
"noNutritionalPlans": "You have no nutritional plans",
"noNutritionalPlans": "Anda belum memiliki rencana nutrisi",
"@noNutritionalPlans": {
"description": "Message shown when the user has no nutritional plans"
},
"anErrorOccurred": "An Error Occurred!",
"anErrorOccurred": "Terjadi kesalahan!",
"@anErrorOccurred": {},
"weight": "Weight",
"weight": "Berat",
"@weight": {
"description": "The weight of a workout log or body weight entry"
},
"measurement": "Ukuran",
"@measurement": {},
"measurements": "Measurements",
"measurements": "Pengukuran",
"@measurements": {
"description": "Categories for the measurements such as biceps size, body fat, etc."
},
"measurementCategoriesHelpText": "Measurement category, such as 'biceps' or 'body fat'",
"measurementCategoriesHelpText": "Kategori pengukuran, seperti 'bisep' atau 'lemak tubuh'",
"@measurementCategoriesHelpText": {},
"measurementEntriesHelpText": "The unit used to measure the category such as 'cm' or '%'",
"measurementEntriesHelpText": "Satuan yang digunakan untuk mengukur kategori, seperti 'cm' atau '%'",
"@measurementEntriesHelpText": {},
"date": "Date",
"@date": {
"description": "The date of a workout log or body weight entry"
},
"value": "Value",
"value": "Nilai",
"@value": {
"description": "The value of a measurement entry"
},
@@ -286,23 +272,23 @@
"@start": {
"description": "Label on button to start the gym mode (i.e., an imperative)"
},
"time": "Time",
"time": "Waktu",
"@time": {
"description": "The time of a meal or workout"
},
"timeStart": "Start time",
"timeStart": "Waktu mulai",
"@timeStart": {
"description": "The starting time of a workout"
},
"timeEnd": "End time",
"timeEnd": "Waktu selesai",
"@timeEnd": {
"description": "The end time of a workout"
},
"timeStartAhead": "Start time cannot be ahead of end time",
"timeStartAhead": "Waktu mulai tidak boleh melebihi waktu selesai",
"@timeStartAhead": {},
"ingredient": "Ingredient",
"ingredient": "Komposisi",
"@ingredient": {},
"energy": "Energy",
"energy": "Energi",
"@energy": {
"description": "Energy in a meal, ingredient etc. e.g. in kJ"
},
@@ -310,29 +296,29 @@
"@energyShort": {
"description": "The first letter or short name of the word 'Energy', used in overviews"
},
"kcal": "kcal",
"kcal": "kkal",
"@kcal": {
"description": "Energy in a meal in kilocalories, kcal"
},
"macronutrients": "Macronutrients",
"macronutrients": "Makronutrien",
"@macronutrients": {},
"planned": "Planned",
"planned": "Direncanakan",
"@planned": {
"description": "Header for the column of 'planned' nutritional values, i.e. what should be eaten"
},
"logged": "Logged",
"logged": "Dicatat",
"@logged": {
"description": "Header for the column of 'logged' nutritional values, i.e. what was eaten"
},
"weekAverage": "7 day average",
"weekAverage": "Rata-rata 7 hari",
"@weekAverage": {
"description": "Header for the column of '7 day average' nutritional values, i.e. what was logged last week"
},
"difference": "Difference",
"difference": "Perbedaan",
"@difference": {},
"percentEnergy": "Persenan Energi",
"@percentEnergy": {},
"gPerBodyKg": "g per body kg",
"gPerBodyKg": "g per kg tubuh",
"@gPerBodyKg": {
"description": "Label used for total sums of e.g. calories or similar in grams per Kg of body weight"
},
@@ -354,27 +340,27 @@
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"carbohydrates": "Carbohydrates",
"carbohydrates": "Karbohidrat",
"@carbohydrates": {},
"carbohydratesShort": "C",
"@carbohydratesShort": {
"description": "The first letter or short name of the word 'Carbohydrates', used in overviews"
},
"sugars": "Sugars",
"sugars": "Gula",
"@sugars": {},
"fat": "Fat",
"fat": "Lemak",
"@fat": {},
"fatShort": "F",
"@fatShort": {
"description": "The first letter or short name of the word 'Fat', used in overviews"
},
"saturatedFat": "Saturated fat",
"saturatedFat": "Lemak jenuh",
"@saturatedFat": {},
"fiber": "Fibre",
"@fiber": {},
"sodium": "Sodium",
"@sodium": {},
"amount": "Amount",
"amount": "Jumlah",
"@amount": {
"description": "The amount (e.g. in grams) of an ingredient in a meal"
},
@@ -382,11 +368,11 @@
"@unit": {
"description": "The unit used for a repetition (kg, time, etc.)"
},
"newEntry": "New entry",
"newEntry": "Entri baru",
"@newEntry": {
"description": "Title when adding a new entry such as a weight or log entry"
},
"noWeightEntries": "You have no weight entries",
"noWeightEntries": "Anda belum memiliki entri berat badan",
"@noWeightEntries": {
"description": "Message shown when the user has no logged weight entries"
},
@@ -398,111 +384,83 @@
},
"delete": "Delete",
"@delete": {},
"confirmDelete": "Are you sure you want to delete '{toDelete}'?",
"confirmDelete": "Apakah anda yakin ingin menghapus '{toDelete}'?",
"@confirmDelete": {
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"newNutritionalPlan": "New nutritional plan",
"newNutritionalPlan": "Rencana nutrisi baru",
"@newNutritionalPlan": {},
"toggleDetails": "Toggle details",
"toggleDetails": "Toggle rincian",
"@toggleDetails": {
"description": "Switch to toggle detail / overview"
},
"goToDetailPage": "Go to detail page",
"@goToDetailPage": {},
"aboutDescription": "Thank you for using wger! wger is a collaborative open source project, made by fitness enthusiasts from around the world.",
"aboutDescription": "Terima kasih telah menggunakan wger! wger proyek open source kolaboratif, dibuat oleh para penggemar kebugaran dari seluruh dunia.",
"@aboutDescription": {
"description": "Text in the about dialog"
},
"aboutSourceTitle": "Source code",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"aboutSourceText": "Get the source code of this application and its server on github",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"aboutBugsTitle": "Have a problem or idea?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"aboutBugsText": "Get in touch if something didn't behave as expected or if there is a feature that you feel is missing.",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"aboutContactUsTitle": "Say hi!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutContactUsText": "If you want to chat with us, hop on the Discord server and get in touch",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"aboutTranslationTitle": "Translation",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"aboutTranslationText": "This application is translated on weblate. If you also want to help, click the link and start translating",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"calendar": "Calendar",
"calendar": "Kalender",
"@calendar": {},
"goToToday": "Go to today",
"goToToday": "Pergi ke hari ini",
"@goToToday": {
"description": "Label on button to jump back to 'today' in the calendar widget"
},
"enterRepetitionsOrWeight": "Please fill in either the repetitions or the weight for at least one of the sets",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"enterValue": "Please enter a value",
"enterValue": "Silakan masukan nilai",
"@enterValue": {
"description": "Error message when the user hasn't entered a value on a required field"
},
"selectExercise": "Please select an exercise",
"selectExercise": "Silakan pilih latihan",
"@selectExercise": {
"description": "Error message when the user hasn't selected an exercise in the form"
},
"enterCharacters": "Please enter between {min} and {max} characters",
"enterCharacters": "Silakan masukan antara {min} dan {max} karakter",
"@enterCharacters": {
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"nrOfSets": "Sets per exercise: {nrOfSets}",
"nrOfSets": "Sets per latihan: {nrOfSets}",
"@nrOfSets": {
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"setUnitsAndRir": "Set units and RiR",
"setUnitsAndRir": "Unit Set and RiR",
"@setUnitsAndRir": {
"description": "Label shown on the slider where the user can toggle showing units and RiR",
"type": "text"
},
"enterValidNumber": "Please enter a valid number",
"enterValidNumber": "Silakan masukan angka yang valid",
"@enterValidNumber": {
"description": "Error message when the user has submitted an invalid number (e.g. '3,.,.,.')"
},
"selectIngredient": "Please select an ingredient",
"selectIngredient": "Silakan pilih komposisi",
"@selectIngredient": {
"description": "Error message when the user hasn't selected an ingredient from the autocompleter"
},
"recentlyUsedIngredients": "Recently added ingredients",
"recentlyUsedIngredients": "Komposisi yang baru ditambahkan",
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
},
"selectImage": "Please select an image",
"selectImage": "Silakan pilih gambar",
"@selectImage": {
"description": "Label and error message when the user hasn't selected an image to save"
},
@@ -510,55 +468,59 @@
"@optionsLabel": {
"description": "Label for the popup with general app options"
},
"takePicture": "Take a picture",
"takePicture": "Ambil gambar",
"@takePicture": {},
"chooseFromLibrary": "Choose from photo library",
"chooseFromLibrary": "Pilih foto dari library",
"@chooseFromLibrary": {},
"gallery": "Gallery",
"gallery": "Galeri",
"@gallery": {},
"addImage": "Add image",
"@addImage": {},
"dataCopied": "Data copied to new entry",
"dataCopied": "Data disalin ke entri baru",
"@dataCopied": {
"description": "Snackbar message to show on copying data to a new log entry"
},
"appUpdateTitle": "Update needed",
"appUpdateTitle": "Pembaruan diperlukan",
"@appUpdateTitle": {},
"appUpdateContent": "This version of the app is not compatible with the server, please update your application.",
"appUpdateContent": "Versi aplikasi ini tidak kompatibel dengan server, silakan perbarui aplikasi Anda.",
"@appUpdateContent": {},
"productFound": "Product found",
"productFound": "Produk ditemukan",
"@productFound": {
"description": "Header label for dialog when product is found with barcode"
},
"productFoundDescription": "The barcode corresponds to this product: {productName}. Do you want to continue?",
"productFoundDescription": "Kode batang ini sesuai dengan produk: {productName}. Apakah Anda ingin melanjutkan?",
"@productFoundDescription": {
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"productNotFound": "Product not found",
"productNotFound": "Produk tidak ditemukan",
"@productNotFound": {
"description": "Header label for dialog when product is not found with barcode"
},
"productNotFoundDescription": "The product with the scanned barcode {barcode} was not found in the wger database",
"productNotFoundDescription": "Produk dengan kode batang {barcode} tidak ditemukan dalam database wger",
"@productNotFoundDescription": {
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"scanBarcode": "Scan barcode",
"scanBarcode": "Pindai kode batang",
"@scanBarcode": {
"description": "Label for scan barcode button"
},
"close": "Close",
"close": "Tutup",
"@close": {
"description": "Translation for close"
},
"userProfile": "Profilmu",
"userProfile": "Profil Kamu",
"@userProfile": {},
"verify": "Verifikasi",
"@verify": {},
@@ -596,10 +558,510 @@
"@searchNamesInEnglish": {},
"noIngredientsDefined": "Belum ada bahan yang ditetapkan",
"@noIngredientsDefined": {},
"ingredientLogged": "Bahan telah dicatat",
"ingredientLogged": "Komposisi telah dicatat",
"@ingredientLogged": {},
"triceps": "Trisep",
"@triceps": {
"description": "Generated entry for translation for server strings"
}
},
"goalFiber": "Target serat",
"@goalFiber": {},
"abs": "Otot perut",
"@abs": {
"description": "Generated entry for translation for server strings"
},
"selectAvailablePlates": "Pilih lempeng beban yang tersedia",
"@selectAvailablePlates": {},
"useColors": "Gunakan warna",
"@useColors": {},
"barWeight": "Berat batang",
"@barWeight": {},
"routineDays": "Hari dalam rutinitas",
"@routineDays": {},
"resultingRoutine": "Rutinitas yang dihasilkan",
"@resultingRoutine": {},
"goalCarbohydrates": "Target karbohidrat",
"@goalCarbohydrates": {},
"alternativeNames": "Nama lain",
"@alternativeNames": {},
"back": "Punggung",
"@back": {
"description": "Generated entry for translation for server strings"
},
"routines": "Rutinitas",
"@routines": {},
"newRoutine": "Rutinitas baru",
"@newRoutine": {},
"sets": "Set",
"@sets": {
"description": "The number of sets to be done for one exercise"
},
"isRestDayHelp": "Harap diperhatikan bahwa semua set dan latihan akan dihapus ketika Anda menandai suatu hari sebagai hari istirahat.",
"@isRestDayHelp": {},
"max": "Maksimal",
"@max": {},
"today": "Hari ini",
"@today": {},
"noMeasurementEntries": "Anda belum memiliki entri pengukuran apa pun",
"@noMeasurementEntries": {},
"log": "Catat",
"@log": {
"description": "Log a specific meal (imperative form)"
},
"settingsTitle": "Pengaturan",
"@settingsTitle": {},
"biceps": "Bisep",
"@biceps": {
"description": "Generated entry for translation for server strings"
},
"miles_per_hour": "Mil Per Jam",
"@miles_per_hour": {
"description": "Generated entry for translation for server strings"
},
"selectEntry": "Silakan pilih entri",
"@selectEntry": {},
"next": "Selanjutnya",
"@next": {},
"simpleMode": "Mode sederhana",
"@simpleMode": {},
"body_weight": "Berat Badan",
"@body_weight": {
"description": "Generated entry for translation for server strings"
},
"incline_bench": "Bangku Incline",
"@incline_bench": {
"description": "Generated entry for translation for server strings"
},
"legs": "Kaki",
"@legs": {
"description": "Generated entry for translation for server strings"
},
"aboutDonateTitle": "Beri donasi",
"@aboutDonateTitle": {},
"bench": "Bangku",
"@bench": {
"description": "Generated entry for translation for server strings"
},
"useUsernameAndPassword": "Gunakan nama pengguna dan kata sandi",
"@useUsernameAndPassword": {},
"useApiToken": "Gunakan Token API",
"@useApiToken": {},
"restTime": "Waktu istirahat",
"@restTime": {},
"restDay": "Hari istirahat",
"@restDay": {},
"exerciseNr": "Latihan ke-{nr}",
"@exerciseNr": {
"description": "Header in form indicating the number of the current exercise. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {
"type": "String"
}
}
},
"errorCouldNotConnectToServer": "Tidak dapat terhubung ke server",
"@errorCouldNotConnectToServer": {},
"errorInfoDescription": "Maaf, terjadi kesalahan. Anda dapat membantu kami memperbaikinya dengan melaporkan masalah ini di GitHub.",
"@errorInfoDescription": {},
"min": "Minimal",
"@min": {},
"chartAllTimeTitle": "{name}Sepanjang waktu",
"@chartAllTimeTitle": {
"description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chart30DaysTitle": "{name} 30 hari terakhir",
"@chart30DaysTitle": {
"description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chartDuringPlanTitle": "{chartName} selama rencana nutrisi {planName}",
"@chartDuringPlanTitle": {
"description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan",
"type": "text",
"placeholders": {
"chartName": {
"type": "String"
},
"planName": {
"type": "String"
}
}
},
"aboutWhySupportTitle": "Bersifat open source & gratis ❤️",
"@aboutWhySupportTitle": {},
"aboutSourceListTitle": "Lihat kode sumber",
"@aboutSourceListTitle": {},
"aboutJoinCommunityTitle": "Gabung komunitas",
"@aboutJoinCommunityTitle": {},
"previous": "Sebelumnya",
"@previous": {},
"setHasProgressionWarning": "Perlu diketahui, saat ini pengaturan lengkap untuk set dan konfigurasi progresi otomatis belum bisa dilakukan di aplikasi mobile. Silakan gunakan aplikasi web untuk fitur tersebut.",
"@setHasProgressionWarning": {},
"settingsExerciseCacheDescription": "Cache latihan",
"@settingsExerciseCacheDescription": {},
"settingsIngredientCacheDescription": "Cache komposisi",
"@settingsIngredientCacheDescription": {},
"settingsCacheDeletedSnackbar": "Cache berhasil dibersihkan",
"@settingsCacheDeletedSnackbar": {},
"aboutPageTitle": "Tentang kami dan dukungan",
"@aboutPageTitle": {},
"simpleModeHelp": "Sembunyikan beberapa pengaturan lanjutan saat mengedit latihan",
"@simpleModeHelp": {},
"kilometers": "Kilometer",
"@kilometers": {
"description": "Generated entry for translation for server strings"
},
"lats": "Otot Punggung Samping",
"@lats": {
"description": "Generated entry for translation for server strings"
},
"lower_back": "Punggung Bawah",
"@lower_back": {
"description": "Generated entry for translation for server strings"
},
"minutes": "Menit",
"@minutes": {
"description": "Generated entry for translation for server strings"
},
"until_failure": "Sampai tidak mampu lagi",
"@until_failure": {
"description": "Generated entry for translation for server strings"
},
"kg": "kg",
"@kg": {
"description": "Generated entry for translation for server strings"
},
"done": "Selesai",
"@done": {},
"overallChangeWeight": "Perubahan keseluruhan",
"@overallChangeWeight": {
"description": "Overall change in weight, added for localization"
},
"goalTypeMeals": "Dari makanan",
"@goalTypeMeals": {
"description": "added for localization of Class GoalType's filed meals"
},
"goalTypeBasic": "Dasar",
"@goalTypeBasic": {
"description": "added for localization of Class GoalType's filed basic"
},
"goalTypeAdvanced": "Lanjutan",
"@goalTypeAdvanced": {
"description": "added for localization of Class GoalType's filed advanced"
},
"indicatorRaw": "Mentah",
"@indicatorRaw": {
"description": "added for localization of Class Indicator's field text"
},
"indicatorAvg": "rata-rata",
"@indicatorAvg": {
"description": "added for localization of Class Indicator's field text"
},
"themeMode": "mode Tema",
"@themeMode": {},
"systemMode": "Pengaturan sitem",
"@systemMode": {},
"pull_up_bar": "Palang Pull-up",
"@pull_up_bar": {
"description": "Generated entry for translation for server strings"
},
"onlyLoggingHelpText": "Centang kotak ini jika Anda hanya ingin mencatat kalori dan tidak ingin menyusun rencana nutrisi secara rinci dengan menu khusus",
"@onlyLoggingHelpText": {},
"goalEnergy": "Target energi",
"@goalEnergy": {},
"moreMeasurementEntries": "Tambah pengukuran baru",
"@moreMeasurementEntries": {
"description": "Message shown when the user wants to add new measurement"
},
"baseNameEnglish": "Semua latihan memerlukan nama dasar dalam Bahasa Inggris",
"@baseNameEnglish": {},
"add_exercise_image_license": "Gambar harus kompatibel dengan lisensi CC BY SA. Jika ragu, unggah hanya foto yang anda ambil sendiri.",
"@add_exercise_image_license": {},
"verifiedEmail": "Email terverifikasi",
"@verifiedEmail": {},
"max_reps": "Repetisi Maksimal",
"@max_reps": {
"description": "Generated entry for translation for server strings"
},
"miles": "Mil",
"@miles": {
"description": "Generated entry for translation for server strings"
},
"lb": "lb",
"@lb": {
"description": "Generated entry for translation for server strings"
},
"verifiedEmailReason": "Anda perlu memverifikasi email untuk dapat menambahkan latihan",
"@verifiedEmailReason": {},
"baseData": "Dasar dalam Bahasa Inggris",
"@baseData": {
"description": "The base data for an exercise such as category, trained muscles, etc."
},
"textPromptSubheading": "Tekan tombol untuk mulai",
"@textPromptSubheading": {},
"cacheWarning": "Karena sistem cache, perubahan mungkin membutuhkan waktu sebelum terlihat di seluruh aplikasi.",
"@cacheWarning": {},
"textPromptTitle": "Siap memulai?",
"@textPromptTitle": {},
"yourCurrentNutritionPlanHasNoMealsDefinedYet": "Rencana nutrisi Anda saat ini belum memiliki menu yang ditentukan",
"@yourCurrentNutritionPlanHasNoMealsDefinedYet": {
"description": "Message shown when a nutrition plan doesn't have any meals"
},
"toAddMealsToThePlanGoToNutritionalPlanDetails": "Untuk menambahkan menu ke dalam rencana, buka detail rencana nutrisi",
"@toAddMealsToThePlanGoToNutritionalPlanDetails": {
"description": "Message shown to guide users to the nutritional plan details page to add meals"
},
"goalFat": "Target lemak",
"@goalFat": {},
"errorInfoDescription2": "Anda masih dapat menggunakan aplikasi, namun beberapa fitur mungkin tidak berfungsi.",
"@errorInfoDescription2": {},
"errorViewDetails": "Rincian teknis",
"@errorViewDetails": {},
"copyToClipboard": "Salin ke papan klip",
"@copyToClipboard": {},
"loggedToday": "Dicatat hari ini",
"@loggedToday": {},
"aboutDonateText": "Meskipun proyek ini gratis dan akan selalu begitu, menjalankan server tetap memerlukan biaya! Pengembangan juga membutuhkan waktu dan tenaga yang tidak sedikit dari para relawan. Kontribusi Anda secara langsung membantu menutup biaya-biaya ini dan menjaga layanan tetap andal.",
"@aboutDonateText": {},
"aboutContributeText": "Semua jenis kontribusi sangat kami hargai. Baik Anda seorang pengembang, penerjemah, atau sekadar memiliki semangat di bidang kebugaran, setiap bentuk dukungan sangat berarti!",
"@aboutContributeText": {},
"aboutTranslationListTitle": "Terjemahkan aplikasi",
"@aboutTranslationListTitle": {},
"others": "Lainnya",
"@others": {},
"verifiedEmailInfo": "Email verifikasi telah dikirim ke {email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"oneNamePerLine": "Satu nama per baris",
"@oneNamePerLine": {},
"whatVariationsExist": "Apakah ada variasi dari latihan ini? Jika ada, apa saja?",
"@whatVariationsExist": {},
"images": "Gambar",
"@images": {},
"fitInWeek": "Bugar dalam seminggu",
"@fitInWeek": {},
"fitInWeekHelp": "Jika diaktifkan, hari-hari akan berulang dalam siklus mingguan. Jika tidak, hari-hari akan berjalan secara berurutan tanpa memperhatikan awal minggu baru.",
"@fitInWeekHelp": {},
"calves": "Betis",
"@calves": {
"description": "Generated entry for translation for server strings"
},
"cardio": "Kardio",
"@cardio": {
"description": "Generated entry for translation for server strings"
},
"dumbbell": "Dumbel",
"@dumbbell": {
"description": "Generated entry for translation for server strings"
},
"gym_mat": "Matras Gym",
"@gym_mat": {
"description": "Generated entry for translation for server strings"
},
"hamstrings": "Paha Belakang",
"@hamstrings": {
"description": "Generated entry for translation for server strings"
},
"kettlebell": "Kettlebell",
"@kettlebell": {
"description": "Generated entry for translation for server strings"
},
"kilometers_per_hour": "Kilometer Per Jam",
"@kilometers_per_hour": {
"description": "Generated entry for translation for server strings"
},
"darkMode": "Selalu mode gelap",
"@darkMode": {},
"goalMacro": "Target makronutrien",
"@goalMacro": {
"description": "The goal for macronutrients"
},
"selectMealToLog": "Pilih makanan untuk dicatat ke dalam buku harian",
"@selectMealToLog": {},
"surplus": "Surplus",
"@surplus": {
"description": "Caloric surplus (either planned or unplanned)"
},
"kcalValue": "{value} kkal",
"@kcalValue": {
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {
"type": "String"
}
}
},
"gValue": "{value} g",
"@gValue": {
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {
"type": "String"
}
}
},
"percentValue": "{value} %",
"@percentValue": {
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {
"type": "String"
}
}
},
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {},
"variations": "Variasi",
"@variations": {
"description": "Variations of one exercise (e.g. benchpress and benchpress narrow)"
},
"alsoKnownAs": "Juga dikenal sebagai: {aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
"language": "Bahasa",
"@language": {},
"contributeExercise": "Tambahkan latihan",
"@contributeExercise": {},
"addExercise": "Tambah latihan",
"@addExercise": {},
"translation": "Terjemahan",
"@translation": {},
"settingsCacheTitle": "Cache",
"@settingsCacheTitle": {},
"contributeExerciseWarning": "Anda hanya dapat menambahkan latihan jika akun Anda berusia lebih dari {days} hari dan email Anda telah terverifikasi",
"@contributeExerciseWarning": {
"description": "Number of days before which a person can add exercise",
"placeholders": {
"days": {
"type": "String",
"example": "14"
}
}
},
"arms": "Lengan",
"@arms": {
"description": "Generated entry for translation for server strings"
},
"chest": "Dada",
"@chest": {
"description": "Generated entry for translation for server strings"
},
"plates": "Lempeng Beban",
"@plates": {
"description": "Generated entry for translation for server strings"
},
"quads": "Paha Depan",
"@quads": {
"description": "Generated entry for translation for server strings"
},
"none__bodyweight_exercise_": "Tanpa alat (Latihan Berat Badan Sendiri)",
"@none__bodyweight_exercise_": {
"description": "Generated entry for translation for server strings"
},
"deficit": "defisit",
"@deficit": {
"description": "Caloric deficit (either planned or unplanned)"
},
"addSuperset": "Tambah superset",
"@addSuperset": {},
"isRestDay": "Hari ini adalah hari istirahat",
"@isRestDay": {},
"apiTokenValidChars": "Kunci API hanya boleh berisi huruf a-f, angka 0-9 dan harus berjumlah 40 karakter",
"@apiTokenValidChars": {
"description": "Error message when the user tries to input a API key with forbidden characters"
},
"needsLogsToAdvance": "Perlu log untuk melanjutkan",
"@needsLogsToAdvance": {},
"invalidApiToken": "Mohon masukan kunci API yang valid",
"@invalidApiToken": {
"description": "Error message when the user enters an invalid API key"
},
"goalProtein": "Target protein",
"@goalProtein": {},
"noRoutines": "Kamu tidak memiliki rutinitas",
"@noRoutines": {},
"apiToken": "Token API",
"@apiToken": {},
"supersetNr": "Superset ke-{nr}",
"@supersetNr": {
"description": "Header in form indicating the number of the current exercise. Can also be translated as something like 'Superset Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {
"type": "String"
}
}
},
"aboutContributeTitle": "Kontribusi",
"@aboutContributeTitle": {},
"translateExercise": "Terjemahkan latihan ini sekarang",
"@translateExercise": {},
"enterMinCharacters": "Silakan masukan minimal {min} karakter",
"@enterMinCharacters": {
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {
"type": "String"
}
}
},
"swiss_ball": "Bola Gym",
"@swiss_ball": {
"description": "Generated entry for translation for server strings"
},
"needsLogsToAdvanceHelp": "Pilih opsi ini jika Anda ingin rutinitas berlanjut ke hari berikutnya hanya setelah Anda mencatat latihan pada hari tersebut",
"@needsLogsToAdvanceHelp": {},
"setHasNoExercises": "Set ini belum memiliki latihan apa pun!",
"@setHasNoExercises": {},
"aboutBugsListTitle": "Laporkan masalah atau usulkan fitur",
"@aboutBugsListTitle": {},
"errorCouldNotConnectToServerDetails": "Aplikasi tidak dapat terhubung ke server. Silakan periksa koneksi internet atau URL server anda, lalu coba lagi. Jika masalah terus berlanjut, hubungi administrator server.",
"@errorCouldNotConnectToServerDetails": {},
"unVerifiedEmail": "Email belum diverifikasi",
"@unVerifiedEmail": {},
"progressionRules": "Latihan ini memiliki aturan peningkatan dan tidak dapat diedit melalui aplikasi mobile. Silakan gunakan aplikasi web untuk mengedit latihan ini.",
"@progressionRules": {},
"setHasProgression": "Set memiliki peningkatan",
"@setHasProgression": {},
"barbell": "Barbel",
"@barbell": {
"description": "Generated entry for translation for server strings"
},
"glutes": "Bokong",
"@glutes": {
"description": "Generated entry for translation for server strings"
},
"resistance_band": "Karet Latihan",
"@resistance_band": {
"description": "Generated entry for translation for server strings"
},
"lightMode": "Selalu mode terang",
"@lightMode": {}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1054
lib/l10n/app_ko.arb Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,6 @@
{
"aboutText": "wger arbeidsøkthåndterer er gemenhetslig fri programvare, lisensiert AGPLv3+ og tilgjengelig på GitHub:",
"@aboutText": {
"description": "Text in the about dialog"
},
"fiber": "Fiber",
"@fibres": {},
"@fiber": {},
"mealLogged": "Måltid lagt til i dagbok",
"@mealLogged": {},
"successfullyDeleted": "Slettet",
@@ -35,7 +31,7 @@
"@newNutritionalPlan": {},
"delete": "Slett",
"@delete": {},
"loadingText": "Laster …",
"loadingText": "Laster…",
"@loadingText": {
"description": "Text to show when entries are being loaded in the background: Loading..."
},
@@ -91,8 +87,6 @@
"@weight": {
"description": "The weight of a workout log or body weight entry"
},
"dismiss": "Forkast",
"@dismiss": {},
"anErrorOccurred": "En feil inntraff.",
"@anErrorOccurred": {},
"nutritionalPlans": "Ernæringsplaner",
@@ -127,7 +121,7 @@
"@workoutSession": {
"description": "A (logged) workout session"
},
"notes": "Notiser",
"notes": "Notater",
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
@@ -139,10 +133,6 @@
"@repetitions": {
"description": "Repetitions for an exercise set"
},
"newWorkout": "Ny treningsøktplan",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"exercise": "Øvelse",
"@exercise": {
"description": "An exercise for a workout"
@@ -155,10 +145,6 @@
"@labelWorkoutPlan": {
"description": "Title for screen workout plan"
},
"labelWorkoutPlans": "Treningsøktplaner",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"logHelpEntriesUnits": "Kun oppføringer med vekt vises, ingen andre kombinasjoner.",
"@logHelpEntriesUnits": {},
"logHelpEntries": "Kun tyngre vekt vises hvis én dag har forskjellige vekter med samme antall repetisjoner.",
@@ -270,5 +256,11 @@
"description": "Text for logout button"
},
"difference": "Forskjell",
"@difference": {}
"@difference": {},
"copyToClipboard": "Kopier til utklippstavlen",
"@copyToClipboard": {},
"value": "Verdi",
"@value": {
"description": "The value of a measurement entry"
}
}

94
lib/l10n/app_nl.arb Normal file
View File

@@ -0,0 +1,94 @@
{
"passwordTooShort": "Het wachtwoord is te kort",
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"register": "Registreren",
"@register": {
"description": "Text for registration button"
},
"logout": "Uitloggen",
"@logout": {
"description": "Text for logout button"
},
"usernameValidChars": "Een gebruikersnaam mag alleen letters, nummers en de tekens @, +, ., -, en _ bevatten",
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"passwordsDontMatch": "De wachtwoorden komen niet overeen",
"@passwordsDontMatch": {
"description": "Error message when the user enters two different passwords during registration"
},
"login": "Inloggen",
"@login": {
"description": "Text for login button"
},
"invalidEmail": "Vul een geldig e-mailadres in",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
"confirmPassword": "Bevestig wachtwoord",
"@confirmPassword": {},
"userProfile": "Jouw profiel",
"@userProfile": {},
"useDefaultServer": "Gebruik standaard server",
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"useCustomServer": "Gebruik aangepaste server",
"@useCustomServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"password": "Wachtwoord",
"@password": {},
"invalidUrl": "Vul een geldige URL in",
"@invalidUrl": {
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
},
"email": "E-mailadres",
"@email": {},
"username": "Gebruikersnaam",
"@username": {},
"invalidUsername": "Vul een geldige gebruikersnaam in",
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"customServerUrl": "URL van de wger instantie",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"successfullySaved": "Opgeslagen",
"@successfullySaved": {
"description": "Message when an item was successfully saved"
},
"exerciseList": "Oefeningen lijst",
"@exerciseList": {},
"exercise": "Oefening",
"@exercise": {
"description": "An exercise for a workout"
},
"exercises": "Oefeningen",
"@exercises": {
"description": "Multiple exercises for a workout"
},
"exerciseName": "Oefening naam",
"@exerciseName": {
"description": "Label for the name of a workout exercise"
},
"success": "Succes",
"@success": {
"description": "Message when an action completed successfully, usually used as a heading"
},
"category": "Categorie",
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"reps": "Herhalingen",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
},
"muscles": "Spieren",
"@muscles": {
"description": "(main) muscles trained by an exercise"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

1062
lib/l10n/app_pt_PT.arb Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,328 +1,330 @@
{
"comment": "Comentariu",
"@comment": {
"description": "Comment, additional information"
},
"equipment": "Echipament",
"@equipment": {
"description": "Equipment needed to perform an exercise"
},
"useDefaultServer": "Folosește serverul implicit",
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"successfullyDeleted": "S-a șters",
"@successfullyDeleted": {
"description": "Message when an item was successfully deleted"
},
"set": "Serie",
"@set": {
"description": "A set in a workout plan"
},
"labelBottomNavWorkout": "Antrenament",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
},
"reset": "Resetare",
"@reset": {
"description": "Button text allowing the user to reset the entered values to the default"
},
"password": "Parolă",
"@password": {},
"invalidUsername": "Introdu, te rugăm, un nume de utilizator valid",
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"impression": "Impresie",
"@impression": {
"description": "General impression (e.g. for a workout session) such as good, bad, etc."
},
"setNr": "Seria {nr}",
"@setNr": {
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
}
},
"noWorkoutPlans": "Nu ai niciun de antrenament",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"muscles": "Mușchi",
"@muscles": {
"description": "(main) muscles trained by an exercise"
},
"dayDescriptionHelp": "O descriere a ceea ce se face în această zi (de exemplu, „zi de tragere”) sau ce părți ale corpului sunt antrenate (de ex.: „piept și umeri”)",
"@dayDescriptionHelp": {},
"loginInstead": "Ai deja un cont? Log in",
"@loginInstead": {},
"success": "Succes",
"@success": {
"description": "Message when an action completed successfully, usually used as a heading"
},
"passwordTooShort": "Parola e prea scurtă",
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"repetitionUnit": "Unitatea de repetiții",
"@repetitionUnit": {},
"weightUnit": "Unitate de greutate",
"@weightUnit": {},
"email": "Adresă de e-mail",
"@email": {},
"newWorkout": "Plan de antrenament nou",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"searchNamesInEnglish": "Caută nume și în engleză",
"@searchNamesInEnglish": {},
"username": "Nume de utilizator",
"@username": {},
"exercise": "Exercițiu",
"@exercise": {
"description": "An exercise for a workout"
},
"customServerHint": "Introdu adresa propriului server, altfel va fi folosită cea implicită",
"@customServerHint": {
"description": "Hint text for the form where the users can enter their own wger instance"
},
"useCustomServer": "Folosește un server personalizat",
"@useCustomServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"notes": "Notițe",
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
"register": "Înregistrează-te",
"@register": {
"description": "Text for registration button"
},
"searchExercise": "Caută un exercițiu pentru a-l adăuga",
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"registerInstead": "Nu ai un cont? Înregistrează-te acum",
"@registerInstead": {},
"usernameValidChars": "Un nume de utilizator poate conține numai litere, cifre și caracterele @, +, ., - și _",
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"labelWorkoutPlans": "Planuri de antrenament",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"proteinShort": "P",
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"userProfile": "Profilul tău",
"@userProfile": {},
"labelWorkoutLogs": "Jurnale de antrenament",
"@labelWorkoutLogs": {
"description": "(Workout) logs"
},
"rirNotUsed": "RîR nu e folosit",
"@rirNotUsed": {
"description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log"
},
"successfullySaved": "S-a salvat",
"@successfullySaved": {
"description": "Message when an item was successfully saved"
},
"exerciseList": "Lista exercițiilor",
"@exerciseList": {},
"musclesSecondary": "Mușchi ajutători",
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
},
"labelDashboard": "Tablou de bord",
"@labelDashboard": {
"description": "Title for screen dashboard"
},
"exerciseName": "Numele exercițiului",
"@exerciseName": {
"description": "Label for the name of a workout exercise"
},
"labelBottomNavNutrition": "Nutriție",
"@labelBottomNavNutrition": {
"description": "Label used in bottom navigation, use a short word"
},
"invalidUrl": "Te rugăm să introduci o adresă URL validă",
"@invalidUrl": {
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
},
"sameRepetitions": "Dacă faci n.r de repetări și greutatea la fel pentru toate seriile, poți completa doar un rând. De exemplu, pentru 4 serii, introdu 10 la repetări și devine automat „4 x 10”.",
"@sameRepetitions": {},
"logout": "Deconectează-te",
"@logout": {
"description": "Text for logout button"
},
"supersetWith": "superset cu",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"customServerUrl": "URL-ul instanței wger",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"login": "Log in",
"@login": {
"description": "Text for login button"
},
"reps": "Repetiții",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
},
"passwordsDontMatch": "Parolele nu se potrivesc",
"@passwordsDontMatch": {
"description": "Error message when the user enters two different passwords during registration"
},
"invalidEmail": "Te rugăm să introduci o adresă de email validă",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
"labelWorkoutPlan": "Plan de antrenament",
"@labelWorkoutPlan": {
"description": "Title for screen workout plan"
},
"category": "Catogorie",
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"rir": "RîR",
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
},
"exercises": "Exerciții",
"@exercises": {
"description": "Multiple exercises for a workout"
},
"workoutSession": "Sesiune de antrenament",
"@workoutSession": {
"description": "A (logged) workout session"
},
"confirmPassword": "Confirmă parola",
"@confirmPassword": {},
"newDay": "Zi nouă",
"@newDay": {},
"selectExercises": "Dacă vrei să faci un superset poți căuta mai multe exerciții, acestea vor fi grupate împreună",
"@selectExercises": {},
"logHelpEntries": "Dacă într-o singură zi există mai multe înregistrări cu același număr de repetări, dar greutăți diferite, în diagramă este prezentată doar intrarea cu greutatea mai mare.",
"@logHelpEntries": {},
"description": "Descriere",
"@description": {},
"name": "Nume",
"@name": {
"description": "Name for a workout or nutritional plan"
},
"measurements": "Măsurători",
"@measurements": {
"description": "Categories for the measurements such as biceps size, body fat, etc."
},
"measurementEntriesHelpText": "Unitatea utilizată pentru măsurarea categoriei, cum ar fi „cm” sau „%”",
"@measurementEntriesHelpText": {},
"useMetric": "Utilizați unități metrice pentru greutatea corporală",
"@useMetric": {},
"plateCalculator": "Discuri",
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
"jumpTo": "Sari la",
"@jumpTo": {
"description": "Imperative. Label used in popup allowing the user to jump to a specific exercise while in the gym mode"
},
"logHelpEntriesUnits": "Rețineți că numai intrările cu o unitate de greutate (kg sau lb) și repetări sunt grafice, alte combinații precum timpul sau până la eșec sunt ignorate aici.",
"@logHelpEntriesUnits": {},
"save": "Salvează",
"@save": {},
"verify": "Verifică",
"@verify": {},
"addIngredient": "Adaugă ingredient",
"@addIngredient": {},
"logIngredient": "Salvează în jurnalul de nutriție",
"@logIngredient": {},
"nutritionalDiary": "Jurnal de nutriție",
"@nutritionalDiary": {},
"nutritionalPlans": "Planuri nutriționale",
"@nutritionalPlans": {},
"weight": "Greutate",
"@weight": {
"description": "The weight of a workout log or body weight entry"
},
"timeStart": "Timpul de începere",
"@timeStart": {
"description": "The starting time of a workout"
},
"timeEnd": "Sfârșitul timpului",
"@timeEnd": {
"description": "The end time of a workout"
},
"noMatchingExerciseFound": "Nu s-au găsit exerciții potrivite",
"@noMatchingExerciseFound": {
"description": "Message returned if no exercises match the searched string"
},
"gymMode": "Modul sală",
"@gymMode": {
"description": "Label when starting the gym mode"
},
"plateCalculatorNotDivisible": "Nu se poate atinge greutatea cu plăcile disponibile",
"@plateCalculatorNotDivisible": {
"description": "Error message when the current weight is not reachable with plates (e.g. 33.1 kg)"
},
"pause": "Pauză",
"@pause": {
"description": "Noun, not an imperative! Label used for the pause when using the gym mode"
},
"todaysWorkout": "Antrenamentul tău de astăzi",
"@todaysWorkout": {},
"addSet": "Adaugă set",
"@addSet": {
"description": "Label for the button that adds a set (to a workout day)"
},
"addMeal": "Adaugă masa",
"@addMeal": {},
"mealLogged": "Masa înregistrată în jurnal",
"@mealLogged": {},
"logMeal": "Înregistrează această masă",
"@logMeal": {},
"searchIngredient": "Caută ingredient",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"nutritionalPlan": "Planul nutrițional",
"@nutritionalPlan": {},
"noNutritionalPlans": "Nu ai planuri nutriționale",
"@noNutritionalPlans": {
"description": "Message shown when the user has no nutritional plans"
},
"anErrorOccurred": "A aparut o eroare!",
"@anErrorOccurred": {},
"timeStartAhead": "Ora de început nu poate fi înaintea orei de încheiere",
"@timeStartAhead": {},
"newSet": "Set nou",
"@newSet": {
"description": "Header when adding a new set to a workout day"
},
"measurement": "Măsurare",
"@measurement": {},
"measurementCategoriesHelpText": "Categoria de măsurare, cum ar fi „biceps” sau „grăsimi corporale”",
"@measurementCategoriesHelpText": {},
"date": "Data",
"@date": {
"description": "The date of a workout log or body weight entry"
},
"value": "Valoare",
"@value": {
"description": "The value of a measurement entry"
},
"start": "Start",
"@start": {
"description": "Label on button to start the gym mode (i.e., an imperative)"
},
"time": "Timp",
"@time": {
"description": "The time of a meal or workout"
}
"comment": "Comentariu",
"@comment": {
"description": "Comment, additional information"
},
"equipment": "Echipament",
"@equipment": {
"description": "Equipment needed to perform an exercise"
},
"useDefaultServer": "Folosește serverul implicit",
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"successfullyDeleted": "S-a șters",
"@successfullyDeleted": {
"description": "Message when an item was successfully deleted"
},
"set": "Serie",
"@set": {
"description": "A set in a workout plan"
},
"labelBottomNavWorkout": "Antrenament",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
},
"reset": "Resetare",
"@reset": {
"description": "Button text allowing the user to reset the entered values to the default"
},
"password": "Parolă",
"@password": {},
"invalidUsername": "Introdu, te rugăm, un nume de utilizator valid",
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"impression": "Impresie",
"@impression": {
"description": "General impression (e.g. for a workout session) such as good, bad, etc."
},
"setNr": "Seria {nr}",
"@setNr": {
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {
"type": "String"
}
}
},
"muscles": "Mușchi",
"@muscles": {
"description": "(main) muscles trained by an exercise"
},
"dayDescriptionHelp": "O descriere a ceea ce se face în această zi (de exemplu, „zi de tragere”) sau ce părți ale corpului sunt antrenate (de ex.: „piept și umeri”)",
"@dayDescriptionHelp": {},
"loginInstead": "Ai deja un cont? Log in",
"@loginInstead": {},
"success": "Succes",
"@success": {
"description": "Message when an action completed successfully, usually used as a heading"
},
"passwordTooShort": "Parola e prea scurtă",
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"repetitionUnit": "Unitatea de repetiții",
"@repetitionUnit": {},
"weightUnit": "Unitate de greutate",
"@weightUnit": {},
"email": "Adresă de e-mail",
"@email": {},
"searchNamesInEnglish": "Caută nume și în engleză",
"@searchNamesInEnglish": {},
"username": "Nume de utilizator",
"@username": {},
"exercise": "Exercițiu",
"@exercise": {
"description": "An exercise for a workout"
},
"customServerHint": "Introdu adresa propriului server, altfel va fi folosită cea implicită",
"@customServerHint": {
"description": "Hint text for the form where the users can enter their own wger instance"
},
"useCustomServer": "Folosește un server personalizat",
"@useCustomServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"notes": "Notițe",
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
"register": "Înregistrează-te",
"@register": {
"description": "Text for registration button"
},
"searchExercise": "Caută un exercițiu pentru a-l adăuga",
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"registerInstead": "Nu ai un cont? Înregistrează-te acum",
"@registerInstead": {},
"usernameValidChars": "Un nume de utilizator poate conține numai litere, cifre și caracterele @, +, ., - și _",
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"proteinShort": "P",
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"userProfile": "Profilul tău",
"@userProfile": {},
"labelWorkoutLogs": "Jurnale de antrenament",
"@labelWorkoutLogs": {
"description": "(Workout) logs"
},
"rirNotUsed": "RîR nu e folosit",
"@rirNotUsed": {
"description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log"
},
"successfullySaved": "S-a salvat",
"@successfullySaved": {
"description": "Message when an item was successfully saved"
},
"exerciseList": "Lista exercițiilor",
"@exerciseList": {},
"musclesSecondary": "Mușchi ajutători",
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
},
"labelDashboard": "Tablou de bord",
"@labelDashboard": {
"description": "Title for screen dashboard"
},
"exerciseName": "Numele exercițiului",
"@exerciseName": {
"description": "Label for the name of a workout exercise"
},
"labelBottomNavNutrition": "Nutriție",
"@labelBottomNavNutrition": {
"description": "Label used in bottom navigation, use a short word"
},
"invalidUrl": "Te rugăm să introduci o adresă URL validă",
"@invalidUrl": {
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
},
"sameRepetitions": "Dacă faci n.r de repetări și greutatea la fel pentru toate seriile, poți completa doar un rând. De exemplu, pentru 4 serii, introdu 10 la repetări și devine automat „4 x 10”.",
"@sameRepetitions": {},
"logout": "Deconectează-te",
"@logout": {
"description": "Text for logout button"
},
"customServerUrl": "URL-ul instanței wger",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"login": "Log in",
"@login": {
"description": "Text for login button"
},
"reps": "Repetiții",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
},
"passwordsDontMatch": "Parolele nu se potrivesc",
"@passwordsDontMatch": {
"description": "Error message when the user enters two different passwords during registration"
},
"invalidEmail": "Te rugăm să introduci o adresă de email validă",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
"labelWorkoutPlan": "Plan de antrenament",
"@labelWorkoutPlan": {
"description": "Title for screen workout plan"
},
"category": "Catogorie",
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"rir": "RîR",
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
},
"exercises": "Exerciții",
"@exercises": {
"description": "Multiple exercises for a workout"
},
"workoutSession": "Sesiune de antrenament",
"@workoutSession": {
"description": "A (logged) workout session"
},
"confirmPassword": "Confirmă parola",
"@confirmPassword": {},
"newDay": "Zi nouă",
"@newDay": {},
"selectExercises": "Dacă vrei să faci un superset poți căuta mai multe exerciții, acestea vor fi grupate împreună",
"@selectExercises": {},
"logHelpEntries": "Dacă într-o singură zi există mai multe înregistrări cu același număr de repetări, dar greutăți diferite, în diagramă este prezentată doar intrarea cu greutatea mai mare.",
"@logHelpEntries": {},
"description": "Descriere",
"@description": {},
"name": "Nume",
"@name": {
"description": "Name for a workout or nutritional plan"
},
"measurements": "Măsurători",
"@measurements": {
"description": "Categories for the measurements such as biceps size, body fat, etc."
},
"measurementEntriesHelpText": "Unitatea utilizată pentru măsurarea categoriei, cum ar fi „cm” sau „%”",
"@measurementEntriesHelpText": {},
"useMetric": "Utilizați unități metrice pentru greutatea corporală",
"@useMetric": {},
"plateCalculator": "Discuri",
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
"jumpTo": "Sari la",
"@jumpTo": {
"description": "Imperative. Label used in popup allowing the user to jump to a specific exercise while in the gym mode"
},
"logHelpEntriesUnits": "Rețineți că numai intrările cu o unitate de greutate (kg sau lb) și repetări sunt grafice, alte combinații precum timpul sau până la eșec sunt ignorate aici.",
"@logHelpEntriesUnits": {},
"save": "Salvează",
"@save": {},
"verify": "Verifică",
"@verify": {},
"addIngredient": "Adaugă ingredient",
"@addIngredient": {},
"logIngredient": "Salvează în jurnalul de nutriție",
"@logIngredient": {},
"nutritionalDiary": "Jurnal de nutriție",
"@nutritionalDiary": {},
"nutritionalPlans": "Planuri nutriționale",
"@nutritionalPlans": {},
"weight": "Greutate",
"@weight": {
"description": "The weight of a workout log or body weight entry"
},
"timeStart": "Timpul de începere",
"@timeStart": {
"description": "The starting time of a workout"
},
"timeEnd": "Sfârșitul timpului",
"@timeEnd": {
"description": "The end time of a workout"
},
"noMatchingExerciseFound": "Nu s-au găsit exerciții potrivite",
"@noMatchingExerciseFound": {
"description": "Message returned if no exercises match the searched string"
},
"gymMode": "Modul sală",
"@gymMode": {
"description": "Label when starting the gym mode"
},
"plateCalculatorNotDivisible": "Nu se poate atinge greutatea cu plăcile disponibile",
"@plateCalculatorNotDivisible": {
"description": "Error message when the current weight is not reachable with plates (e.g. 33.1 kg)"
},
"pause": "Pauză",
"@pause": {
"description": "Noun, not an imperative! Label used for the pause when using the gym mode"
},
"todaysWorkout": "Antrenamentul tău de astăzi",
"@todaysWorkout": {},
"addSet": "Adaugă set",
"@addSet": {
"description": "Label for the button that adds a set (to a workout day)"
},
"addMeal": "Adaugă masa",
"@addMeal": {},
"mealLogged": "Masa înregistrată în jurnal",
"@mealLogged": {},
"logMeal": "Înregistrează această masă",
"@logMeal": {},
"searchIngredient": "Caută ingredient",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"nutritionalPlan": "Planul nutrițional",
"@nutritionalPlan": {},
"noNutritionalPlans": "Nu ai planuri nutriționale",
"@noNutritionalPlans": {
"description": "Message shown when the user has no nutritional plans"
},
"anErrorOccurred": "A aparut o eroare!",
"@anErrorOccurred": {},
"timeStartAhead": "Ora de început nu poate fi înaintea orei de încheiere",
"@timeStartAhead": {},
"newSet": "Set nou",
"@newSet": {
"description": "Header when adding a new set to a workout day"
},
"measurement": "Măsurare",
"@measurement": {},
"measurementCategoriesHelpText": "Categoria de măsurare, cum ar fi „biceps” sau „grăsimi corporale",
"@measurementCategoriesHelpText": {},
"date": "Data",
"@date": {
"description": "The date of a workout log or body weight entry"
},
"value": "Valoare",
"@value": {
"description": "The value of a measurement entry"
},
"start": "Start",
"@start": {
"description": "Label on button to start the gym mode (i.e., an imperative)"
},
"time": "Timp",
"@time": {
"description": "The time of a meal or workout"
},
"useApiToken": "Folosește Token-ul API",
"@useApiToken": {},
"useUsernameAndPassword": "Folosește numele de utilizator și parola",
"@useUsernameAndPassword": {},
"apiToken": "Token API",
"@apiToken": {},
"invalidApiToken": "Te rugăm să introduci o cheie API validă",
"@invalidApiToken": {
"description": "Error message when the user enters an invalid API key"
},
"apiTokenValidChars": "Cheia API poate conține doar litere a-f, numere 0-9 și trebuie să aibă exact 40 de caractere",
"@apiTokenValidChars": {
"description": "Error message when the user tries to input a API key with forbidden characters"
},
"noIngredientsDefined": "Nici un ingredient definit",
"@noIngredientsDefined": {}
}

File diff suppressed because it is too large Load Diff

322
lib/l10n/app_sk.arb Normal file
View File

@@ -0,0 +1,322 @@
{
"userProfile": "Váš profil",
"@userProfile": {},
"login": "Prihlásiť sa",
"@login": {
"description": "Text for login button"
},
"logout": "Odhlásiť sa",
"@logout": {
"description": "Text for logout button"
},
"register": "Registrovať sa",
"@register": {
"description": "Text for registration button"
},
"useDefaultServer": "Použiť predvolený server",
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"useCustomServer": "Použiť vlastný server",
"@useCustomServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"invalidUrl": "Zadajte platnú URL adresu",
"@invalidUrl": {
"description": "Error message when the user enters an invalid URL, e.g. in the login form"
},
"usernameValidChars": "Používateľské meno môže obsahovať iba písmená, číslice a znaky @, +, ., -, a _",
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"passwordsDontMatch": "Heslá sa nezhodujú",
"@passwordsDontMatch": {
"description": "Error message when the user enters two different passwords during registration"
},
"passwordTooShort": "Heslo je príliš krátke",
"@passwordTooShort": {
"description": "Error message when the user a password that is too short"
},
"selectAvailablePlates": "Vyberte dostupné platne",
"@selectAvailablePlates": {},
"barWeight": "Hmotnosť tyče",
"@barWeight": {},
"useColors": "Používajte farby",
"@useColors": {},
"password": "Heslo",
"@password": {},
"confirmPassword": "Potvrďte heslo",
"@confirmPassword": {},
"invalidEmail": "Zadajte platnú e-mailovú adresu",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
"email": "E-mailová adresa",
"@email": {},
"username": "Používateľské meno",
"@username": {},
"invalidUsername": "Zadajte platné používateľské meno",
"@invalidUsername": {
"description": "Error message when the user enters an invalid username"
},
"useApiToken": "Použite token API",
"@useApiToken": {},
"useUsernameAndPassword": "Použite používateľské meno a heslo",
"@useUsernameAndPassword": {},
"apiToken": "API token",
"@apiToken": {},
"invalidApiToken": "Zadajte platný kľúč API",
"@invalidApiToken": {
"description": "Error message when the user enters an invalid API key"
},
"apiTokenValidChars": "Kľúč API môže obsahovať iba písmená a-f, číslice 0-9 a musí mať presne 40 znakov",
"@apiTokenValidChars": {
"description": "Error message when the user tries to input a API key with forbidden characters"
},
"customServerUrl": "URL inštancie wger",
"@customServerUrl": {
"description": "Label in the form where the users can enter their own wger instance"
},
"customServerHint": "Zadajte adresu svojho vlastného servera, inak sa použije predvolená adresa",
"@customServerHint": {
"description": "Hint text for the form where the users can enter their own wger instance"
},
"reset": "Reset",
"@reset": {
"description": "Button text allowing the user to reset the entered values to the default"
},
"registerInstead": "Nemáte účet? Zaregistrujte sa teraz",
"@registerInstead": {},
"loginInstead": "Už máte účet? Prihláste sa",
"@loginInstead": {},
"labelBottomNavWorkout": "Tréning",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
},
"labelBottomNavNutrition": "Výživa",
"@labelBottomNavNutrition": {
"description": "Label used in bottom navigation, use a short word"
},
"labelWorkoutLogs": "Tréninkové denníky",
"@labelWorkoutLogs": {
"description": "(Workout) logs"
},
"labelWorkoutPlan": "Plán tréningu",
"@labelWorkoutPlan": {
"description": "Title for screen workout plan"
},
"labelDashboard": "Ovládací panel",
"@labelDashboard": {
"description": "Title for screen dashboard"
},
"success": "Úspech",
"@success": {
"description": "Message when an action completed successfully, usually used as a heading"
},
"successfullyDeleted": "Odstránené",
"@successfullyDeleted": {
"description": "Message when an item was successfully deleted"
},
"successfullySaved": "Uložené",
"@successfullySaved": {
"description": "Message when an item was successfully saved"
},
"exerciseList": "Zoznam cvičení",
"@exerciseList": {},
"exercise": "Cvičenie",
"@exercise": {
"description": "An exercise for a workout"
},
"exercises": "Cvičenia",
"@exercises": {
"description": "Multiple exercises for a workout"
},
"exerciseName": "Názov cvičenia",
"@exerciseName": {
"description": "Label for the name of a workout exercise"
},
"searchExercise": "Vyhľadávacie cvičenie na pridanie",
"@searchExercise": {
"description": "Label on set form. Selected exercises are added to the set"
},
"noIngredientsDefined": "Žiadne ingrediencie zatiaľ neboli definované",
"@noIngredientsDefined": {},
"noMatchingExerciseFound": "Nenašli sa žiadne zodpovedajúce cvičenia",
"@noMatchingExerciseFound": {
"description": "Message returned if no exercises match the searched string"
},
"searchNamesInEnglish": "Hľadať aj mená v angličtine",
"@searchNamesInEnglish": {},
"equipment": "Vybavenie",
"@equipment": {
"description": "Equipment needed to perform an exercise"
},
"muscles": "Svaly",
"@muscles": {
"description": "(main) muscles trained by an exercise"
},
"musclesSecondary": "Sekundárne svaly",
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
},
"category": "Kategória",
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"endDate": "Dátum ukončenia",
"@endDate": {},
"startDate": "Dátum začatia",
"@startDate": {},
"routines": "Rutiny",
"@routines": {},
"newRoutine": "Nová rutina",
"@newRoutine": {},
"noRoutines": "Nemáte žiadne rutiny",
"@noRoutines": {},
"restTime": "Čas odpočinku",
"@restTime": {},
"sets": "Sety",
"@sets": {
"description": "The number of sets to be done for one exercise"
},
"rir": "RiR",
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
},
"rirNotUsed": "RiR sa nepoužíva",
"@rirNotUsed": {
"description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log"
},
"useMetric": "Používať metrické jednotky pre telesnú hmotnosť",
"@useMetric": {},
"weightUnit": "Jednotka hmotnosti",
"@weightUnit": {},
"repetitionUnit": "Opakovacie jednotky",
"@repetitionUnit": {},
"dayDescriptionHelp": "Popis toho, čo sa v tento deň robí (napr. „ťahací deň“) alebo ktoré časti tela sa trénujú (napr. „hrudník a ramená“)",
"@dayDescriptionHelp": {},
"sameRepetitions": "",
"@sameRepetitions": {},
"comment": "Komentár",
"@comment": {
"description": "Comment, additional information"
},
"impression": "Dojem",
"@impression": {
"description": "General impression (e.g. for a workout session) such as good, bad, etc."
},
"notes": "Poznámky",
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
"restDay": "Deň odpočinku",
"@restDay": {},
"isRestDay": "Je deň odpočinku",
"@isRestDay": {},
"isRestDayHelp": "Upozorňujeme, že všetky sady a cvičenia budú odstránené, keď označíte deň ako deň odpočinku.",
"@isRestDayHelp": {},
"needsLogsToAdvance": "Na postup sú potrebné protokoly",
"@needsLogsToAdvance": {},
"needsLogsToAdvanceHelp": "Vyberte, či chcete, aby rutina pokračovala do nasledujúceho naplánovaného dňa len v prípade, že ste zaznamenali tréning za daný deň",
"@needsLogsToAdvanceHelp": {},
"routineDays": "Dni v rutine",
"@routineDays": {},
"resultingRoutine": "Výsledná rutina",
"@resultingRoutine": {},
"newDay": "Nový deň",
"@newDay": {},
"newSet": "Nová séria",
"@newSet": {
"description": "Header when adding a new set to a workout day"
},
"selectExercises": "Ak chcete urobiť superset, môžete vyhľadať niekoľko cvikov, ktoré budú zoskupené dohromady",
"@selectExercises": {},
"gymMode": "Režim posilňovňa",
"@gymMode": {
"description": "Label when starting the gym mode"
},
"plateCalculator": "Kotúče",
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
"plateCalculatorNotDivisible": "Nie je možné dosiahnuť hmotnosť s dostupnými platňami",
"@plateCalculatorNotDivisible": {
"description": "Error message when the current weight is not reachable with plates (e.g. 33.1 kg)"
},
"pause": "Pauza",
"@pause": {
"description": "Noun, not an imperative! Label used for the pause when using the gym mode"
},
"jumpTo": "Preskočiť na",
"@jumpTo": {
"description": "Imperative. Label used in popup allowing the user to jump to a specific exercise while in the gym mode"
},
"todaysWorkout": "Tvoj dnešný tréning",
"@todaysWorkout": {},
"logHelpEntries": "Ak je v jeden deň viac ako jeden záznam s rovnakým počtom opakovaní, ale s rôznymi váhami, v grafe sa zobrazí len záznam s vyššou váhou.",
"@logHelpEntries": {},
"logHelpEntriesUnits": "Upozorňujeme, že zaznamenávajú sa iba záznamy s jednotkou hmotnosti (kg alebo lb) a opakovaním, ostatné kombinácie, ako napríklad čas alebo do zlyhania, sa tu ignorujú.",
"@logHelpEntriesUnits": {},
"description": "Popis",
"@description": {},
"name": "Názov",
"@name": {
"description": "Name for a workout or nutritional plan"
},
"save": "Uložiť",
"@save": {},
"verify": "Overiť",
"@verify": {},
"addSet": "Pridať sériu",
"@addSet": {
"description": "Label for the button that adds a set (to a workout day)"
},
"addMeal": "Pridať jedlo",
"@addMeal": {},
"mealLogged": "Jedlo zaznamenané do denníka",
"@mealLogged": {},
"ingredientLogged": "Ingrediencia zaznamenaná do denníka",
"@ingredientLogged": {},
"logMeal": "Zapíšte si jedlo do denníka výživy",
"@logMeal": {},
"addIngredient": "Pridať ingredienciu",
"@addIngredient": {},
"logIngredient": "Zapíšte zložku do výživového denníka",
"@logIngredient": {},
"searchIngredient": "Vyhľadávanie ingrediencie",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"nutritionalPlan": "Výživový plán",
"@nutritionalPlan": {},
"nutritionalDiary": "Deník stravovania",
"@nutritionalDiary": {},
"nutritionalPlans": "Výživové plány",
"@nutritionalPlans": {},
"noNutritionalPlans": "Nemáte žiadne výživové plány",
"@noNutritionalPlans": {
"description": "Message shown when the user has no nutritional plans"
},
"onlyLogging": "Sledujte iba kalórie",
"@onlyLogging": {},
"onlyLoggingHelpText": "Zaškrtnite políčko, ak chcete zaznamenávať len kalórie a nechcete vytvárať podrobný výživový plán s konkrétnymi jedlami",
"@onlyLoggingHelpText": {},
"goalMacro": "Makro ciele",
"@goalMacro": {
"description": "The goal for macronutrients"
},
"selectMealToLog": "Vyberte jedlo, ktoré chcete zaznamenať do denníka",
"@selectMealToLog": {},
"yourCurrentNutritionPlanHasNoMealsDefinedYet": "Váš aktuálny výživový plán neobsahuje žiadne definované jedlá",
"@yourCurrentNutritionPlanHasNoMealsDefinedYet": {
"description": "Message shown when a nutrition plan doesn't have any meals"
},
"toAddMealsToThePlanGoToNutritionalPlanDetails": "Ak chcete pridať jedlá do plánu, prejdite do podrobností výživového plánu",
"@toAddMealsToThePlanGoToNutritionalPlanDetails": {
"description": "Message shown to guide users to the nutritional plan details page to add meals"
},
"goalEnergy": "Energetický cieľ",
"@goalEnergy": {},
"goalProtein": "Cieľ bielkovín",
"@goalProtein": {}
}

1010
lib/l10n/app_ta.arb Normal file

File diff suppressed because it is too large Load Diff

1
lib/l10n/app_th.arb Normal file
View File

@@ -0,0 +1 @@
{}

View File

@@ -47,10 +47,6 @@
"@labelBottomNavNutrition": {
"description": "Label used in bottom navigation, use a short word"
},
"labelWorkoutPlans": "Antrenman planları",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"labelBottomNavWorkout": "Antrenman",
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
@@ -123,10 +119,6 @@
"@login": {
"description": "Text for login button"
},
"noWorkoutPlans": "Antrenman planınız yok",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"repetitions": "Tekrarlar",
"@repetitions": {
"description": "Repetitions for an exercise set"
@@ -141,14 +133,6 @@
"@set": {
"description": "A set in a workout plan"
},
"supersetWith": "süper set ile",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"newWorkout": "Yeni antrenman planı",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"rirNotUsed": "RiR kullanılmadı",
"@rirNotUsed": {
"description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log"
@@ -162,7 +146,9 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"sameRepetitions": "Eğer tüm setler için aynıırlıklarla aynı setleri yapıyorsanız, sadece bir satır doldurabilirsiniz. Örneğin, 4 set için sadece 10 girin, bu otomatik olarak \"4 x 10\"a dönüşüyor.",
@@ -367,14 +353,6 @@
"@aboutDescription": {
"description": "Text in the about dialog"
},
"aboutSourceText": "Bu uygulamanın ve sunucusunun kaynak kodlarını github'dan edinin",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"aboutBugsTitle": "Bir sorununuz veya fikriniz mi var?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"enterValidNumber": "Lütfen geçerli bir sayı girin",
"@enterValidNumber": {
"description": "Error message when the user has submitted an invalid number (e.g. '3,.,.,.')"
@@ -392,17 +370,11 @@
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"aboutContactUsTitle": "Merhaba deyin!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutContactUsText": "Bizimle sohbet etmek istiyorsanız, Discord sunucusuna gelin ve iletişime geçin",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"goToToday": "Bugüne git",
"@goToToday": {
"description": "Label on button to jump back to 'today' in the calendar widget"
@@ -418,7 +390,9 @@
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"rir": "RiR",
@@ -448,33 +422,15 @@
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"toggleDetails": "Ayrıntıları aç/kapat",
"@toggleDetails": {
"description": "Switch to toggle detail / overview"
},
"aboutSourceTitle": "Kaynak kodları",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"aboutBugsText": "Bir şey beklendiği gibi davranmazsa veya eksik olduğunu düşündüğünüz bir özellik varsa iletişime geçin.",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"aboutTranslationTitle": "Çeviri",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"aboutTranslationText": "Bu uygulama Weblate üzerinde çevrilmektedir. Yardım etmek istiyorsanız bağlantıya tıklayın ve çevirmeye başlayın",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"enterRepetitionsOrWeight": "Lütfen setlerden en az birinin tekrarlarını veya ağırlığını doldurun",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"selectExercise": "Lütfen bir egzersiz seçin",
"@selectExercise": {
"description": "Error message when the user hasn't selected an exercise in the form"
@@ -484,8 +440,12 @@
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"setUnitsAndRir": "RiR ve birimleri ayarla",
@@ -522,7 +482,9 @@
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"close": "Kapat",
@@ -560,7 +522,9 @@
"alsoKnownAs": "{aliases} olarak da bilinir",
"@alsoKnownAs": {
"placeholders": {
"aliases": {}
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
@@ -577,7 +541,9 @@
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {}
"min": {
"type": "String"
}
}
},
"add_exercise_image_license": "Görseller CC BY SA lisansı ile uyumlu olmalıdır. Emin değilseniz, yalnızca kendi çektiğiniz fotoğrafları yükleyin.",
@@ -593,7 +559,9 @@
"verifiedEmailInfo": "{email} adresine bir doğrulama e-postası gönderildi",
"@verifiedEmailInfo": {
"placeholders": {
"email": {}
"email": {
"type": "String"
}
}
},
"previous": "Önceki",
@@ -753,13 +721,7 @@
"description": "Generated entry for translation for server strings"
},
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {
"description": "Title for mastodon section in the about dialog"
},
"aboutMastodonText": "Projeyle ilgili güncellemeler ve haberler için bizi Mastodon'da takip edin",
"@aboutMastodonText": {
"description": "Text for the mastodon section in the about dialog"
},
"@aboutMastodonTitle": {},
"settingsTitle": "Ayarlar",
"@settingsTitle": {},
"settingsCacheTitle": "Önbellek",
@@ -789,11 +751,11 @@
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"addGoalsToPlan": "Bu plana hedefler ekle",
"@addGoalsToPlan": {},
"goalEnergy": "Enerji hedefi",
"@goalEnergy": {},
"goalProtein": "Protein hedefi",
@@ -802,8 +764,6 @@
"@onlyLogging": {},
"onlyLoggingHelpText": "Yalnızca kalorilerinizi kaydetmek istiyorsanız ve belirli öğünler için ayrıntılı bir beslenme planı oluşturmak istemiyorsanız kutuyu işaretleyin",
"@onlyLoggingHelpText": {},
"addGoalsToPlanHelpText": "Bu, plan için enerji, protein, karbonhidrat veya yağ için genel hedefler belirlemenize olanak tanır. Ayrıntılı bir yemek planı ayarlarsanız, bu değerlerin öncelikli olacağını unutmayın.",
"@addGoalsToPlanHelpText": {},
"loggedToday": "Bugün kaydedildi",
"@loggedToday": {},
"kcalValue": "{value} kcal",
@@ -811,7 +771,9 @@
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"goalCarbohydrates": "Karbonhidrat hedefi",
@@ -825,7 +787,9 @@
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"goalMacro": "Makro hedefleri",
@@ -855,7 +819,9 @@
"description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {}
"name": {
"type": "String"
}
}
},
"chart30DaysTitle": "son 30 gün {name}",
@@ -863,7 +829,9 @@
"description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {}
"name": {
"type": "String"
}
}
},
"chartDuringPlanTitle": "{planName} beslenme planı boyunca {chartName}",
@@ -871,8 +839,12 @@
"description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan",
"type": "text",
"placeholders": {
"chartName": {},
"planName": {}
"chartName": {
"type": "String"
},
"planName": {
"type": "String"
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -91,7 +91,7 @@
"@notes": {
"description": "Personal notes, e.g. for a workout session"
},
"comment": "备注",
"comment": "评论",
"@comment": {
"description": "Comment, additional information"
},
@@ -105,14 +105,6 @@
"@repetitionUnit": {},
"weightUnit": "重量单位",
"@weightUnit": {},
"noWorkoutPlans": "您还没有设定锻炼计划",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"newWorkout": "新增锻炼计划",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"category": "类别",
"@category": {
"description": "Category for an exercise, ingredient, etc."
@@ -153,10 +145,6 @@
"@labelBottomNavWorkout": {
"description": "Label used in bottom navigation, use a short word"
},
"labelWorkoutPlans": "锻炼计划",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"loginInstead": "已经有帐号了吗?立即登入",
"@loginInstead": {},
"registerInstead": "没有帐户?点击注册",
@@ -194,8 +182,12 @@
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"selectExercise": "请选择一个锻炼项目",
@@ -212,22 +204,6 @@
},
"calendar": "日历",
"@calendar": {},
"aboutContactUsTitle": "Hello!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"aboutBugsTitle": "有疑问或者建议?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"aboutSourceText": "获取code and server",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"aboutSourceTitle": "源代码",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"aboutDescription": "感谢您使用wger wger 是一个协作开源项目,由来自世界各地的健身爱好者创建。",
"@aboutDescription": {
"description": "Text in the about dialog"
@@ -245,14 +221,16 @@
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"delete": "删除",
"@delete": {},
"loadingText": "加载...",
"loadingText": "加载...",
"@loadingText": {
"description": "Text to show when entries are being loaded in the background: Loading..."
"description": "Text to show when entries are being loaded in the background: Loading..., changed from \"加载\" to \"加载中\""
},
"edit": "编辑",
"@edit": {},
@@ -326,7 +304,7 @@
},
"weight": "重量",
"@weight": {
"description": "The weight of a workout log or body weight entry"
"description": "The weight of a workout log or body weight entry, changed from \"重量\" to \"体重\""
},
"anErrorOccurred": "出错了!",
"@anErrorOccurred": {},
@@ -348,9 +326,9 @@
"@addMeal": {},
"save": "保存",
"@save": {},
"name": "名",
"name": "名",
"@name": {
"description": "Name for a workout or nutritional plan"
"description": "Name for a workout or nutritional plan, changed from \"名\" to \"名称\""
},
"description": "描述",
"@description": {},
@@ -370,13 +348,11 @@
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"enterRepetitionsOrWeight": "请至少填写其中一项的重复次数或重量",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"timeStartAhead": "开始时间不能早于结束时间",
"@timeStartAhead": {},
"addIngredient": "添加营养成分",
@@ -412,21 +388,15 @@
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"muscles": "主要部位",
"@muscles": {
"description": "(main) muscles trained by an exercise"
},
"aboutTranslationText": "可以在weblate网站上参与翻译工作",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"aboutTranslationTitle": "翻译",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"musclesSecondary": "次要部位",
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
@@ -435,18 +405,6 @@
"@newSet": {
"description": "Header when adding a new set to a workout day"
},
"supersetWith": "组合",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"aboutContactUsText": "聊一聊",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"aboutBugsText": "如果某些情况未如预期运作或您认为缺少某个功能,请与我们联络。",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"plateCalculatorNotDivisible": "达不到预定重量",
"@plateCalculatorNotDivisible": {
"description": "Error message when the current weight is not reachable with plates (e.g. 33.1 kg)"
@@ -460,7 +418,9 @@
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"weekAverage": "7天平均",
@@ -472,7 +432,9 @@
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"measurement": "测量",
@@ -527,9 +489,9 @@
"@close": {
"description": "Translation for close"
},
"logIngredient": "保存至营养日志",
"logIngredient": "记录食材至营养日志",
"@logIngredient": {},
"searchIngredient": "搜索营养成分",
"searchIngredient": "搜索营养成分",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
@@ -557,21 +519,21 @@
"@logged": {
"description": "Header for the column of 'logged' nutritional values, i.e. what was eaten"
},
"userProfile": "账户",
"userProfile": "个人资料",
"@userProfile": {},
"exerciseName": "锻炼名",
"@exerciseName": {
"description": "Label for the name of a workout exercise"
},
"previous": "前一个",
"previous": "前",
"@previous": {},
"kg": "公斤",
"kg": "千克",
"@kg": {
"description": "Generated entry for translation for server strings"
},
"verify": "确认",
"@verify": {},
"next": "下一个",
"next": "",
"@next": {},
"success": "成功",
"@success": {
@@ -593,7 +555,7 @@
"@minutes": {
"description": "Generated entry for translation for server strings"
},
"pull_up_bar": "上杆",
"pull_up_bar": "引体向上杆",
"@pull_up_bar": {
"description": "Generated entry for translation for server strings"
},
@@ -650,7 +612,9 @@
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"gValue": "{value} g",
@@ -658,7 +622,9 @@
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"percentValue": "{value} %",
@@ -666,9 +632,435 @@
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"noMeasurementEntries": "您没有测量条目",
"@noMeasurementEntries": {}
"@noMeasurementEntries": {},
"overallChangeWeight": "总体变化",
"@overallChangeWeight": {
"description": "Overall change in weight"
},
"goalTypeMeals": "从饮食出发",
"@goalTypeMeals": {},
"goalTypeBasic": "基础",
"@goalTypeBasic": {},
"goalTypeAdvanced": "进阶",
"@goalTypeAdvanced": {},
"chartAllTimeTitle": "{name} 历史记录曲线",
"@chartAllTimeTitle": {
"description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chartDuringPlanTitle": "{chartName} 在计划 \"{planName}\" 期间的改变曲线",
"@chartDuringPlanTitle": {
"description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan",
"type": "text",
"placeholders": {
"chartName": {
"type": "String"
},
"planName": {
"type": "String"
}
}
},
"indicatorRaw": "原始值",
"@indicatorRaw": {},
"indicatorAvg": "平均值",
"@indicatorAvg": {},
"textPromptTitle": "准备就绪?",
"@textPromptTitle": {
"description": "Title for the text prompt"
},
"textPromptSubheading": "点击右下角按钮开始",
"@textPromptSubheading": {
"description": "Subheading for the text prompt"
},
"enterMinCharacters": "请输入最少{min}个字符",
"@enterMinCharacters": {
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {
"type": "String"
}
}
},
"baseNameEnglish": "所有运动需要一个英文代号",
"@baseNameEnglish": {},
"lower_back": "下背",
"@lower_back": {
"description": "Generated entry for translation for server strings"
},
"images": "图像",
"@images": {},
"biceps": "肱二头肌",
"@biceps": {
"description": "Generated entry for translation for server strings"
},
"aboutPageTitle": "关于我们&支持",
"@aboutPageTitle": {},
"selectEntry": "请选择一项",
"@selectEntry": {},
"cardio": "有氧",
"@cardio": {
"description": "Generated entry for translation for server strings"
},
"abs": "腹肌",
"@abs": {
"description": "Generated entry for translation for server strings"
},
"plates": "盘",
"@plates": {
"description": "Generated entry for translation for server strings"
},
"quads": "大腿前侧",
"@quads": {
"description": "Generated entry for translation for server strings"
},
"bench": "凳",
"@bench": {
"description": "Generated entry for translation for server strings"
},
"sz_bar": "W型曲杆",
"@sz_bar": {
"description": "Generated entry for translation for server strings"
},
"aboutDonateTitle": "进行捐赠",
"@aboutDonateTitle": {},
"aboutDonateText": "尽管该项目是免费的,且将始终保持免费,但服务器的运营并非如此!开发工作也需要志愿者投入大量的时间和精力。您的捐赠将直接用于支付这些成本,助力维持服务的稳定可靠。",
"@aboutDonateText": {},
"none__bodyweight_exercise_": "无(自重动作)",
"@none__bodyweight_exercise_": {
"description": "Generated entry for translation for server strings"
},
"chart30DaysTitle": "{name} 过去三十天",
"@chart30DaysTitle": {
"description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"oneNamePerLine": "每行一个名字",
"@oneNamePerLine": {},
"settingsExerciseCacheDescription": "动作缓存",
"@settingsExerciseCacheDescription": {},
"settingsIngredientCacheDescription": "营养成分缓存",
"@settingsIngredientCacheDescription": {},
"contributeExerciseWarning": "账号注册{days}天且邮箱验证通过后,你方可贡献动作",
"@contributeExerciseWarning": {
"description": "Number of days before which a person can add exercise",
"placeholders": {
"days": {
"type": "String",
"example": "14"
}
}
},
"body_weight": "体重",
"@body_weight": {
"description": "Generated entry for translation for server strings"
},
"chest": "胸",
"@chest": {
"description": "Generated entry for translation for server strings"
},
"dumbbell": "哑铃",
"@dumbbell": {
"description": "Generated entry for translation for server strings"
},
"hamstrings": "大腿后侧",
"@hamstrings": {
"description": "Generated entry for translation for server strings"
},
"legs": "腿",
"@legs": {
"description": "Generated entry for translation for server strings"
},
"max_reps": "最大次数",
"@max_reps": {
"description": "Generated entry for translation for server strings"
},
"miles": "英里",
"@miles": {
"description": "Generated entry for translation for server strings"
},
"miles_per_hour": "迈",
"@miles_per_hour": {
"description": "Generated entry for translation for server strings"
},
"seconds": "秒",
"@seconds": {
"description": "Generated entry for translation for server strings"
},
"shoulders": "肩",
"@shoulders": {
"description": "Generated entry for translation for server strings"
},
"swiss_ball": "瑜伽球",
"@swiss_ball": {
"description": "Generated entry for translation for server strings"
},
"triceps": "肱三头肌",
"@triceps": {
"description": "Generated entry for translation for server strings"
},
"until_failure": "直至力竭",
"@until_failure": {
"description": "Generated entry for translation for server strings"
},
"variations": "变式",
"@variations": {
"description": "Variations of one exercise (e.g. benchpress and benchpress narrow)"
},
"verifiedEmail": "已验证的邮箱",
"@verifiedEmail": {},
"unVerifiedEmail": "未验证的邮箱",
"@unVerifiedEmail": {},
"verifiedEmailInfo": "验证邮件已经发往{email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {
"type": "String"
}
}
},
"whatVariationsExist": "这个动作有什么变体吗?",
"@whatVariationsExist": {},
"language": "语言",
"@language": {},
"addExercise": "添加动作",
"@addExercise": {},
"contributeExercise": "贡献一个动作",
"@contributeExercise": {},
"translateExercise": "翻译该动作",
"@translateExercise": {},
"baseData": "英文基础动作",
"@baseData": {
"description": "The base data for an exercise such as category, trained muscles, etc."
},
"settingsTitle": "设置",
"@settingsTitle": {},
"settingsCacheTitle": "缓存",
"@settingsCacheTitle": {},
"settingsCacheDeletedSnackbar": "成功清除缓存",
"@settingsCacheDeletedSnackbar": {},
"barbell": "杠铃",
"@barbell": {
"description": "Generated entry for translation for server strings"
},
"gym_mat": "健身垫",
"@gym_mat": {
"description": "Generated entry for translation for server strings"
},
"incline_bench": "上斜凳",
"@incline_bench": {
"description": "Generated entry for translation for server strings"
},
"kettlebell": "壶铃",
"@kettlebell": {
"description": "Generated entry for translation for server strings"
},
"kilometers_per_hour": "千米每小时",
"@kilometers_per_hour": {
"description": "Generated entry for translation for server strings"
},
"lats": "背阔肌",
"@lats": {
"description": "Generated entry for translation for server strings"
},
"log": "记录",
"@log": {
"description": "Log a specific meal (imperative form)"
},
"done": "完成",
"@done": {},
"moreMeasurementEntries": "添加新围度",
"@moreMeasurementEntries": {
"description": "Message shown when the user wants to add new measurement"
},
"add_exercise_image_license": "图像必须符合 CC BY-SA 知识共享许可。如果你不太确定,那请仅上传你自己拍摄的照片。",
"@add_exercise_image_license": {},
"cacheWarning": "由于缓存,申请中的变动或需一段时间方可呈现。",
"@cacheWarning": {},
"alsoKnownAs": "又名:{aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
"verifiedEmailReason": "你需要验证邮箱来参与贡献",
"@verifiedEmailReason": {},
"arms": "手臂",
"@arms": {
"description": "Generated entry for translation for server strings"
},
"translation": "翻译",
"@translation": {},
"back": "背",
"@back": {
"description": "Generated entry for translation for server strings"
},
"calves": "小腿",
"@calves": {
"description": "Generated entry for translation for server strings"
},
"glutes": "臀",
"@glutes": {
"description": "Generated entry for translation for server strings"
},
"aboutMastodonTitle": "长毛象",
"@aboutMastodonTitle": {},
"routines": "训练计划",
"@routines": {},
"newRoutine": "新计划",
"@newRoutine": {},
"noRoutines": "你没有训练计划",
"@noRoutines": {},
"restTime": "休息时间",
"@restTime": {},
"sets": "组",
"@sets": {
"description": "The number of sets to be done for one exercise"
},
"exerciseNr": "练习{nr}",
"@exerciseNr": {
"description": "Header in form indicating the number of the current exercise. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {
"type": "String"
}
}
},
"supersetNr": "特别组{nr}",
"@supersetNr": {
"description": "Header in form indicating the number of the current exercise. Can also be translated as something like 'Superset Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {
"type": "String"
}
}
},
"restDay": "休息日",
"@restDay": {},
"isRestDay": "是休息日",
"@isRestDay": {},
"isRestDayHelp": "请注意,当您将某天标记为休息日时,所有的组数和练习都将被删除。",
"@isRestDayHelp": {},
"needsLogsToAdvance": "需要日志以前进",
"@needsLogsToAdvance": {},
"needsLogsToAdvanceHelp": "选择您是否希望只有在当天记录了锻炼后,训练计划才会进入下一个计划日",
"@needsLogsToAdvanceHelp": {},
"routineDays": "计划中的天数",
"@routineDays": {},
"min": "分钟",
"@min": {},
"max": "最大",
"@max": {},
"fitInWeekHelp": "如果启用,天数将以周为周期重复,否则天数将依次重复,而不考虑新一周的开始。",
"@fitInWeekHelp": {},
"addSuperset": "增加超级组",
"@addSuperset": {},
"setHasProgression": "此组已经有训练进度",
"@setHasProgression": {},
"setHasProgressionWarning": "请注意目前还无法在移动应用程序上编辑一个设置的所有设置也无法配置自动升级。目前请使用Web应用程序。",
"@setHasProgressionWarning": {},
"setHasNoExercises": "此训练组还没有任何练习!",
"@setHasNoExercises": {},
"simpleMode": "简易模式",
"@simpleMode": {},
"simpleModeHelp": "在编辑练习时隐藏更多高级设置",
"@simpleModeHelp": {},
"progressionRules": "此练习有进度规则无法在移动应用程序上编辑。请使用Web应用程序编辑此练习。",
"@progressionRules": {},
"themeMode": "主题模式",
"@themeMode": {},
"darkMode": "总是暗色模式",
"@darkMode": {},
"lightMode": "总是亮色模式",
"@lightMode": {},
"systemMode": "系统设置",
"@systemMode": {},
"barWeight": "杠铃重量",
"@barWeight": {},
"useColors": "使用颜色",
"@useColors": {},
"useApiToken": "使用API密钥",
"@useApiToken": {},
"useUsernameAndPassword": "使用用户名与密码",
"@useUsernameAndPassword": {},
"apiToken": "API密钥",
"@apiToken": {},
"invalidApiToken": "请输入有效API值",
"@invalidApiToken": {
"description": "Error message when the user enters an invalid API key"
},
"apiTokenValidChars": "API 密钥只能包含字母 a-f、数字 0-9长度为 40 个字符",
"@apiTokenValidChars": {
"description": "Error message when the user tries to input a API key with forbidden characters"
},
"selectAvailablePlates": "选择可用的杠片",
"@selectAvailablePlates": {},
"yourCurrentNutritionPlanHasNoMealsDefinedYet": "你当前的营养计划中没有设定任何餐食",
"@yourCurrentNutritionPlanHasNoMealsDefinedYet": {
"description": "Message shown when a nutrition plan doesn't have any meals"
},
"toAddMealsToThePlanGoToNutritionalPlanDetails": "若要为营养计划添加餐食,请前往营养计划详情页面",
"@toAddMealsToThePlanGoToNutritionalPlanDetails": {
"description": "Message shown to guide users to the nutritional plan details page to add meals"
},
"errorInfoDescription": "很抱歉,出现了一些问题。您可以通过在 GitHub 上报告此问题来帮助我们修复它。",
"@errorInfoDescription": {},
"errorInfoDescription2": "您可以继续使用这款应用,但部分功能可能无法正常运行。",
"@errorInfoDescription2": {},
"errorViewDetails": "错误技术详情",
"@errorViewDetails": {},
"errorCouldNotConnectToServer": "无法连接到服务器",
"@errorCouldNotConnectToServer": {},
"errorCouldNotConnectToServerDetails": "应用程序无法连接到服务器。请检查您的网络连接或服务器网址,然后重试。如果问题持续存在,请联系服务器管理员。",
"@errorCouldNotConnectToServerDetails": {},
"copyToClipboard": "复制",
"@copyToClipboard": {},
"aboutWhySupportTitle": "开源 & 免费使用",
"@aboutWhySupportTitle": {},
"aboutContributeTitle": "贡献",
"@aboutContributeTitle": {},
"aboutContributeText": "我们鼓励各种形式的贡献。无论您是开发者、翻译人员,还是单纯热爱健身的人士,每一份支持都值得我们由衷感谢!",
"@aboutContributeText": {},
"aboutBugsListTitle": "报告问题或提出功能建议",
"@aboutBugsListTitle": {},
"aboutTranslationListTitle": "翻译此软件",
"@aboutTranslationListTitle": {},
"aboutSourceListTitle": "查看源代码",
"@aboutSourceListTitle": {},
"aboutJoinCommunityTitle": "加入社区",
"@aboutJoinCommunityTitle": {},
"aboutDiscordTitle": "Discord",
"@aboutDiscordTitle": {},
"others": "其他",
"@others": {},
"fitInWeek": "一周健身计划",
"@fitInWeek": {},
"resistance_band": "弹力带",
"@resistance_band": {
"description": "Generated entry for translation for server strings"
},
"resultingRoutine": "最终生成的训练计划",
"@resultingRoutine": {}
}

View File

@@ -15,10 +15,6 @@
"@registerInstead": {},
"loginInstead": "已經有帳號了嗎?立即登入",
"@loginInstead": {},
"labelWorkoutPlans": "健身計劃",
"@labelWorkoutPlans": {
"description": "Title for screen workout plans"
},
"userProfile": "你的檔案",
"@userProfile": {},
"register": "註冊",
@@ -41,7 +37,7 @@
"@password": {},
"confirmPassword": "確認密碼",
"@confirmPassword": {},
"invalidEmail": "請輸入有效的電子郵件信箱",
"invalidEmail": "請輸入有效的電子郵件地址",
"@invalidEmail": {
"description": "Error message when the user enters an invalid email"
},
@@ -77,13 +73,13 @@
"@successfullySaved": {
"description": "Message when an item was successfully saved"
},
"exerciseList": "運動列表",
"exerciseList": "動作清單",
"@exerciseList": {},
"exercise": "動作",
"@exercise": {
"description": "An exercise for a workout"
},
"exercises": "訓練",
"exercises": "動作",
"@exercises": {
"description": "Multiple exercises for a workout"
},
@@ -103,11 +99,11 @@
"@useDefaultServer": {
"description": "Toggle button allowing users to switch between the default and a custom wger server"
},
"usernameValidChars": "使用者名稱只能包含字母、數字,以及字元 (@, +, ., -, 和_)",
"usernameValidChars": "使用者名稱只能包含字母、數字,以及 @、+、.、-、_ 等符號",
"@usernameValidChars": {
"description": "Error message when the user tries to register a username with forbidden characters"
},
"customServerHint": "輸入自架伺服器的地址,否則將使用預設地址",
"customServerHint": "輸入自架伺服器址,否則將使用預設伺服器",
"@customServerHint": {
"description": "Hint text for the form where the users can enter their own wger instance"
},
@@ -123,11 +119,11 @@
"@successfullyDeleted": {
"description": "Message when an item was successfully deleted"
},
"noMatchingExerciseFound": "沒有找到符合的動項目",
"noMatchingExerciseFound": "沒有找到符合的動項目",
"@noMatchingExerciseFound": {
"description": "Message returned if no exercises match the searched string"
},
"muscles": "肌肉",
"muscles": "主要肌群",
"@muscles": {
"description": "(main) muscles trained by an exercise"
},
@@ -135,13 +131,13 @@
"@equipment": {
"description": "Equipment needed to perform an exercise"
},
"searchNamesInEnglish": "也用英文字詞搜尋",
"searchNamesInEnglish": "也搜尋英文名稱",
"@searchNamesInEnglish": {},
"logged": "紀錄",
"logged": "實際",
"@logged": {
"description": "Header for the column of 'logged' nutritional values, i.e. what was eaten"
},
"scanBarcode": "掃描條碼",
"scanBarcode": "掃描條碼",
"@scanBarcode": {
"description": "Label for scan barcode button"
},
@@ -157,33 +153,33 @@
},
"repetitionUnit": "重複次數單位",
"@repetitionUnit": {},
"dayDescriptionHelp": "描述這一天做了什麼(例如:拉力日)或訓練了哪些身體部位(例如:胸部和肩膀)",
"dayDescriptionHelp": "描述這一天做了什麼(例如拉力日)或訓練了哪些部位(例如胸部和肩膀)",
"@dayDescriptionHelp": {},
"comment": "註",
"comment": "註",
"@comment": {
"description": "Comment, additional information"
},
"impression": "感",
"impression": "感",
"@impression": {
"description": "General impression (e.g. for a workout session) such as good, bad, etc."
},
"sameRepetitions": "如果您對所有組別進行相同的重複次數和重量,則只需填寫一行即可。例如,對於 4 組,只輸入 10 次重複,會自動變成「4 x 10」。",
"sameRepetitions": "如果您對所有組別進行相同的重複次數和重量則只需填寫一行即可。例如4 組每組 10 下,只輸入 10 次重複,系統會自動變成「4 x 10」。",
"@sameRepetitions": {},
"plateCalculator": "槓片",
"@plateCalculator": {
"description": "Label used for the plate calculator in the gym mode"
},
"gymMode": "健身模式",
"gymMode": "健身模式",
"@gymMode": {
"description": "Label when starting the gym mode"
},
"selectExercises": "如果你想做一個超級組,你可以搜尋幾個訓練,它們會在一起",
"selectExercises": "如果您想進行超級組訓練,您可以搜尋多個動作項目,它們會被組合在一起",
"@selectExercises": {},
"workoutSession": "訓練(節)",
"workoutSession": "健身(節)",
"@workoutSession": {
"description": "A (logged) workout session"
},
"pause": "暫停",
"pause": "休息",
"@pause": {
"description": "Noun, not an imperative! Label used for the pause when using the gym mode"
},
@@ -191,21 +187,21 @@
"@jumpTo": {
"description": "Imperative. Label used in popup allowing the user to jump to a specific exercise while in the gym mode"
},
"todaysWorkout": "今天的訓練",
"todaysWorkout": "今天的訓練",
"@todaysWorkout": {},
"logHelpEntries": "如果一天有多相同重複次數但重量不同的條目,圖表只會顯示重量較高的條目。",
"logHelpEntries": "如果一天有多相同重複次數但不同重量的紀錄,圖表只會顯示重量較重的那筆紀錄。",
"@logHelpEntries": {},
"description": "描述",
"@description": {},
"addIngredient": "增加材料",
"addIngredient": "新增食材",
"@addIngredient": {},
"verify": "確認",
"verify": "驗證",
"@verify": {},
"logMeal": "紀錄餐",
"logMeal": "紀錄餐點到營養日記",
"@logMeal": {},
"logHelpEntriesUnits": "請注意,系統只會繪製帶重量單位(公斤或磅)和重複次數的紀錄,其他組合(例如時間或直到力竭)將被忽略。",
"logHelpEntriesUnits": "請注意,圖表只會顯示包含重量單位(公斤或磅)和重複次數的紀錄,其他組合(例如時間或直到力竭)不會被顯示。",
"@logHelpEntriesUnits": {},
"name": "名",
"name": "名",
"@name": {
"description": "Name for a workout or nutritional plan"
},
@@ -221,15 +217,15 @@
"@time": {
"description": "The time of a meal or workout"
},
"searchIngredient": "搜尋材",
"searchIngredient": "搜尋材",
"@searchIngredient": {
"description": "Label on ingredient search form"
},
"logIngredient": "儲存到營養日記",
"logIngredient": "紀錄食材到營養日記",
"@logIngredient": {},
"anErrorOccurred": "發生錯誤!",
"@anErrorOccurred": {},
"noNutritionalPlans": "沒有營養計劃",
"noNutritionalPlans": "沒有營養計劃",
"@noNutritionalPlans": {
"description": "Message shown when the user has no nutritional plans"
},
@@ -237,7 +233,7 @@
"@timeStart": {
"description": "The starting time of a workout"
},
"timeEnd": "結時間",
"timeEnd": "結時間",
"@timeEnd": {
"description": "The end time of a workout"
},
@@ -249,35 +245,27 @@
"@fatShort": {
"description": "The first letter or short name of the word 'Fat', used in overviews"
},
"fibres": "纖維",
"@fibres": {},
"sodium": "鈉",
"@sodium": {},
"edit": "編輯",
"@edit": {},
"moreMeasurementEntries": "增加新的測量",
"moreMeasurementEntries": "新增測量紀錄",
"@moreMeasurementEntries": {
"description": "Message shown when the user wants to add new measurement"
},
"confirmDelete": "您確定要刪除'{toDelete}'嗎?",
"confirmDelete": "您確定要刪除{toDelete}嗎?",
"@confirmDelete": {
"description": "Confirmation text before the user deletes an object",
"type": "text",
"placeholders": {
"toDelete": {}
"toDelete": {
"type": "String"
}
}
},
"aboutBugsTitle": "有問題或想法嗎?",
"@aboutBugsTitle": {
"description": "Title for bugs section in the about dialog"
},
"newNutritionalPlan": "新的營養計劃",
"@newNutritionalPlan": {},
"aboutSourceText": "在 Github 上取得這應用程式的原始程式碼及伺服器",
"@aboutSourceText": {
"description": "Text for source code section in the about dialog"
},
"aboutDescription": "感謝您使用wger wger 是一個協作開源項目,由來自世界各地的健身愛好者創建。",
"aboutDescription": "感謝您使用 wgerwger 是一個協作式開源專案,由來自世界各地的健身愛好者共同打造。",
"@aboutDescription": {
"description": "Text in the about dialog"
},
@@ -285,21 +273,13 @@
"@enterValue": {
"description": "Error message when the user hasn't entered a value on a required field"
},
"enterRepetitionsOrWeight": "請為至少一組輸入重複次數或重量",
"@enterRepetitionsOrWeight": {
"description": "Error message when the user hasn't filled in the forms for exercise sets"
},
"aboutBugsText": "如果某些情況未如預期運作或您認為缺少某個功能,請與我們聯絡。",
"@aboutBugsText": {
"description": "Text for bugs section in the about dialog"
},
"previous": "前一個",
"previous": "上一個",
"@previous": {},
"add_exercise_image_license": "影像必須與 CC BY SA 授權相容。如有疑,請僅上傳自己拍攝的照片。",
"add_exercise_image_license": "影像必須與 CC BY SA 授權相容。如有疑,請僅上傳自己拍攝的照片。",
"@add_exercise_image_license": {},
"verifiedEmail": "已認證電郵",
"verifiedEmail": "已驗證的電子郵件",
"@verifiedEmail": {},
"selectIngredient": "請選擇材",
"selectIngredient": "請選擇一個食材",
"@selectIngredient": {
"description": "Error message when the user hasn't selected an ingredient from the autocompleter"
},
@@ -311,7 +291,7 @@
"@arms": {
"description": "Generated entry for translation for server strings"
},
"contributeExerciseWarning": "只有當您的帳已超過 {days} 天且已驗證您的電子郵件時,您才可以貢獻訓練",
"contributeExerciseWarning": "只有當您的帳已超過 {days} 天且已驗證您的電子郵件時,您才能貢獻動作項目",
"@contributeExerciseWarning": {
"description": "Number of days before which a person can add exercise",
"placeholders": {
@@ -333,7 +313,7 @@
"@calves": {
"description": "Generated entry for translation for server strings"
},
"bench": "平板凳",
"bench": "重訓椅",
"@bench": {
"description": "Generated entry for translation for server strings"
},
@@ -345,7 +325,7 @@
"@until_failure": {
"description": "Generated entry for translation for server strings"
},
"none__bodyweight_exercise_": "無(自重訓練",
"none__bodyweight_exercise_": "無(徒手運動",
"@none__bodyweight_exercise_": {
"description": "Generated entry for translation for server strings"
},
@@ -355,9 +335,9 @@
"@log": {
"description": "Log a specific meal (imperative form)"
},
"addMeal": "増加餐單",
"addMeal": "新增餐點",
"@addMeal": {},
"sugars": "糖",
"sugars": "糖",
"@sugars": {},
"fat": "脂肪",
"@fat": {},
@@ -369,9 +349,9 @@
},
"weightUnit": "重量單位",
"@weightUnit": {},
"newDay": "新一天",
"newDay": "新一天",
"@newDay": {},
"newSet": "新組",
"newSet": "新增一組",
"@newSet": {
"description": "Header when adding a new set to a workout day"
},
@@ -379,11 +359,11 @@
"@start": {
"description": "Label on button to start the gym mode (i.e., an imperative)"
},
"selectExercise": "請選擇一個訓練",
"selectExercise": "請選擇一個動作",
"@selectExercise": {
"description": "Error message when the user hasn't selected an exercise in the form"
},
"whatVariationsExist": "此練習有哪些變(如果有)?",
"whatVariationsExist": "這個動作有哪些變(如果有的話",
"@whatVariationsExist": {},
"back": "背部",
"@back": {
@@ -393,24 +373,22 @@
"@category": {
"description": "Category for an exercise, ingredient, etc."
},
"newWorkout": "新健身計畫",
"@newWorkout": {
"description": "Header when adding a new workout"
},
"musclesSecondary": "輔助肌",
"musclesSecondary": "輔助肌群",
"@musclesSecondary": {
"description": "secondary muscles trained by an exercise"
},
"rirNotUsed": "保留次數未儲存",
"rirNotUsed": "未使用保留次數",
"@rirNotUsed": {
"description": "Label used in RiR slider when the RiR value is not used/saved for the current setting or log"
},
"setNr": " {nr}",
"setNr": " {nr}",
"@setNr": {
"description": "Header in form indicating the number of the current set. Can also be translated as something like 'Set Nr. xy'.",
"type": "text",
"placeholders": {
"nr": {}
"nr": {
"type": "String"
}
}
},
"notes": "筆記",
@@ -423,11 +401,11 @@
},
"save": "儲存",
"@save": {},
"addSet": "組",
"addSet": "新增一組",
"@addSet": {
"description": "Label for the button that adds a set (to a workout day)"
},
"mealLogged": "膳食已記錄到日記",
"mealLogged": "餐點已紀錄到日記",
"@mealLogged": {},
"measurement": "測量",
"@measurement": {},
@@ -441,9 +419,9 @@
"@value": {
"description": "The value of a measurement entry"
},
"measurementEntriesHelpText": "用於測量類別的單位,例如「cm」或「%」",
"measurementEntriesHelpText": "用於測量類別的單位,例如「公分」或「%」",
"@measurementEntriesHelpText": {},
"measurements": "測量",
"measurements": "測量項目",
"@measurements": {
"description": "Categories for the measurements such as biceps size, body fat, etc."
},
@@ -459,7 +437,7 @@
"@kJ": {
"description": "Energy in a meal in kilo joules, kJ"
},
"carbohydratesShort": "碳水化合物",
"carbohydratesShort": "碳水",
"@carbohydratesShort": {
"description": "The first letter or short name of the word 'Carbohydrates', used in overviews"
},
@@ -467,11 +445,11 @@
"@unit": {
"description": "The unit used for a repetition (kg, time, etc.)"
},
"newEntry": "新條目",
"newEntry": "新增紀錄",
"@newEntry": {
"description": "Title when adding a new entry such as a weight or log entry"
},
"amount": "量",
"amount": "量",
"@amount": {
"description": "The amount (e.g. in grams) of an ingredient in a meal"
},
@@ -481,27 +459,15 @@
},
"delete": "刪除",
"@delete": {},
"aboutSourceTitle": "原始碼",
"@aboutSourceTitle": {
"description": "Title for source code section in the about dialog"
},
"goToDetailPage": "進入詳情頁面",
"goToDetailPage": "前往詳細資訊頁面",
"@goToDetailPage": {},
"aboutContactUsText": "如果您想與我們聊天,請到 Discord 伺服器並取得聯繫",
"@aboutContactUsText": {
"description": "Text for contact us section in the about dialog"
},
"aboutMastodonText": "在 Mastodon 上關注我們,以了解有關這項目的更新和新聞",
"@aboutMastodonText": {
"description": "Text for the mastodon section in the about dialog"
},
"appUpdateTitle": "需要更新",
"@appUpdateTitle": {},
"variations": "變",
"variations": "變",
"@variations": {
"description": "Variations of one exercise (e.g. benchpress and benchpress narrow)"
},
"legs": "腿",
"legs": "腿",
"@legs": {
"description": "Generated entry for translation for server strings"
},
@@ -517,75 +483,73 @@
"@quads": {
"description": "Generated entry for translation for server strings"
},
"textPromptTitle": "準備開始?",
"textPromptTitle": "準備開始了嗎",
"@textPromptTitle": {},
"body_weight": "體重",
"@body_weight": {
"description": "Generated entry for translation for server strings"
},
"textPromptSubheading": "按作按鈕開始",
"textPromptSubheading": "按下動作按鈕開始",
"@textPromptSubheading": {},
"ingredient": "材",
"ingredient": "材",
"@ingredient": {},
"energy": "量",
"energy": "量",
"@energy": {
"description": "Energy in a meal, ingredient etc. e.g. in kJ"
},
"goalEnergy": "量目標",
"goalEnergy": "量目標",
"@goalEnergy": {},
"goalProtein": "蛋白質目標",
"@goalProtein": {},
"goalFat": "脂肪目標",
"@goalFat": {},
"onlyLogging": "追蹤卡路里",
"onlyLogging": "追蹤卡路里",
"@onlyLogging": {},
"addGoalsToPlan": "新增目標至此計劃",
"@addGoalsToPlan": {},
"measurementCategoriesHelpText": "測量類別,例如“二頭肌”或“體脂”",
"measurementCategoriesHelpText": "測量類別,例如「二頭肌」或「體脂率」",
"@measurementCategoriesHelpText": {},
"today": "今天",
"@today": {},
"percentEnergy": "量百分比",
"percentEnergy": "量百分比",
"@percentEnergy": {},
"onlyLoggingHelpText": "如果只想錄卡路里但不想設計有特定膳食的詳細營養計劃,請選中此框",
"onlyLoggingHelpText": "如果只想錄卡路里但不想設詳細營養計劃和特定餐點,請勾選此項",
"@onlyLoggingHelpText": {},
"addGoalsToPlanHelpText": "這使你可以為計劃設定能量、蛋白質、碳水化合物或脂肪的總體目標。請注意,如果你設定了詳細的膳食計劃,這些數值將取得優先權。",
"@addGoalsToPlanHelpText": {},
"goalCarbohydrates": "碳水化合物目標",
"@goalCarbohydrates": {},
"loggedToday": "今天紀錄",
"loggedToday": "今天紀錄",
"@loggedToday": {},
"verifiedEmailReason": "您需要驗證您的電子郵件才能貢獻訓練項目",
"verifiedEmailReason": "您需要驗證您的電子郵件才能貢獻動作項目",
"@verifiedEmailReason": {},
"alternativeNames": "稱",
"alternativeNames": "替代名稱",
"@alternativeNames": {},
"productNotFound": "找不到產品",
"@productNotFound": {
"description": "Header label for dialog when product is not found with barcode"
},
"productNotFoundDescription": "在 wger 資料庫中找不到帶有掃描條碼 {barcode} 的產品",
"productNotFoundDescription": "在 wger 資料庫中找不到條碼 {barcode} 的產品",
"@productNotFoundDescription": {
"description": "Dialog info when product is not found with barcode",
"type": "text",
"placeholders": {
"barcode": {}
"barcode": {
"type": "String"
}
}
},
"images": "圖片",
"@images": {},
"language": "語言",
"@language": {},
"translateExercise": "立即翻譯此訓練",
"translateExercise": "立即翻譯此動作",
"@translateExercise": {},
"translation": "翻譯",
"@translation": {},
"contributeExercise": "貢獻一個訓練",
"contributeExercise": "貢獻一個動作",
"@contributeExercise": {},
"baseData": "基礎數據(英文)",
"baseData": "英文基本資料",
"@baseData": {
"description": "The base data for an exercise such as category, trained muscles, etc."
},
"cacheWarning": "由於緩存中,改變可能需要一些時間才能在應用程式顯示。",
"cacheWarning": "由於快取機制,變更可能需要一些時間才會在整個應用程式顯示。",
"@cacheWarning": {},
"miles": "英里",
"@miles": {
@@ -619,13 +583,13 @@
"@shoulders": {
"description": "Generated entry for translation for server strings"
},
"unVerifiedEmail": "未認證電郵",
"unVerifiedEmail": "未驗證的電子郵件",
"@unVerifiedEmail": {},
"glutes": "臀大肌",
"glutes": "臀大肌",
"@glutes": {
"description": "Generated entry for translation for server strings"
},
"cardio": "氧運動",
"cardio": "氧運動",
"@cardio": {
"description": "Generated entry for translation for server strings"
},
@@ -633,10 +597,6 @@
"@miles_per_hour": {
"description": "Generated entry for translation for server strings"
},
"supersetWith": "超級組",
"@supersetWith": {
"description": "Text used between exercise cards when adding a new set. Translate as something like 'in a superset with'"
},
"weekAverage": "七天平均值",
"@weekAverage": {
"description": "Header for the column of '7 day average' nutritional values, i.e. what was logged last week"
@@ -657,12 +617,14 @@
},
"oneNamePerLine": "每行一個名稱",
"@oneNamePerLine": {},
"aboutPageTitle": "關於Wger",
"aboutPageTitle": "關於 Wger",
"@aboutPageTitle": {},
"verifiedEmailInfo": "驗證電郵已發送至 {email}",
"verifiedEmailInfo": "驗證郵件已寄送至 {email}",
"@verifiedEmailInfo": {
"placeholders": {
"email": {}
"email": {
"type": "String"
}
}
},
"gym_mat": "健身墊",
@@ -673,16 +635,18 @@
"@settingsCacheDeletedSnackbar": {},
"settingsTitle": "設定",
"@settingsTitle": {},
"settingsCacheTitle": "緩存",
"settingsCacheTitle": "快取",
"@settingsCacheTitle": {},
"settingsExerciseCacheDescription": "訓練緩存",
"settingsExerciseCacheDescription": "動作快取",
"@settingsExerciseCacheDescription": {},
"gValue": "{value} 克",
"@gValue": {
"description": "A value in grams, e.g. 5 g",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"percentValue": "{value} %",
@@ -690,13 +654,11 @@
"description": "A value in percent, e.g. 10 %",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"noWorkoutPlans": "你沒有健身計畫",
"@noWorkoutPlans": {
"description": "Message shown when the user has no workout plans"
},
"reps": "次",
"@reps": {
"description": "Shorthand for repetitions, used when space constraints are tighter"
@@ -705,20 +667,24 @@
"@rir": {
"description": "Shorthand for Repetitions In Reserve"
},
"selectEntry": "請選擇一個目",
"selectEntry": "請選擇一個目",
"@selectEntry": {},
"nrOfSets": "組 / 訓練: {nrOfSets}",
"nrOfSets": "每個動作的組數:{nrOfSets}",
"@nrOfSets": {
"description": "Label shown on the slider where the user selects the nr of sets",
"type": "text",
"placeholders": {
"nrOfSets": {}
"nrOfSets": {
"type": "String"
}
}
},
"alsoKnownAs": "又稱: {aliases}",
"alsoKnownAs": "也稱為:{aliases}",
"@alsoKnownAs": {
"placeholders": {
"aliases": {}
"aliases": {
"type": "String"
}
},
"description": "List of alternative names for an exercise"
},
@@ -726,53 +692,51 @@
"@abs": {
"description": "Generated entry for translation for server strings"
},
"energyShort": "量",
"energyShort": "量",
"@energyShort": {
"description": "The first letter or short name of the word 'Energy', used in overviews"
},
"kcal": "卡",
"kcal": "卡",
"@kcal": {
"description": "Energy in a meal in kilocalories, kcal"
},
"macronutrients": "量營養素",
"macronutrients": "量營養素",
"@macronutrients": {},
"proteinShort": "蛋白質",
"@proteinShort": {
"description": "The first letter or short name of the word 'Protein', used in overviews"
},
"toggleDetails": "切換詳",
"toggleDetails": "切換詳細資訊",
"@toggleDetails": {
"description": "Switch to toggle detail / overview"
},
"aboutContactUsTitle": "您好!",
"@aboutContactUsTitle": {
"description": "Title for contact us section in the about dialog"
},
"addImage": "增加圖片",
"addImage": "新增圖片",
"@addImage": {},
"addExercise": "增加鍛鍊項目",
"addExercise": "新增動作",
"@addExercise": {},
"productFoundDescription": "此條碼對應產品:{productName}。您繼續嗎?",
"productFoundDescription": "此條碼對應到這個產品:{productName}。您繼續嗎?",
"@productFoundDescription": {
"description": "Dialog info when product is found with barcode",
"type": "text",
"placeholders": {
"productName": {}
"productName": {
"type": "String"
}
}
},
"appUpdateContent": "此版本的應用程式與伺服器不相容,請更新您的應用程式。",
"@appUpdateContent": {},
"aboutTranslationText": "這應用程式是在 weblate 上翻譯的。如果您也想提供協助,請點擊連結並開始翻譯",
"@aboutTranslationText": {
"description": "Text for translation section in the about dialog"
},
"enterCharacters": "請輸入 {min} 到 {max} 個字符",
"enterCharacters": "請輸入 {min} 到 {max} 個字元",
"@enterCharacters": {
"description": "Error message when the user hasn't entered the correct number of characters in a form",
"type": "text",
"placeholders": {
"min": {},
"max": {}
"min": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"g": "克",
@@ -787,55 +751,53 @@
"@surplus": {
"description": "Caloric surplus (either planned or unplanned)"
},
"deficit": "赤字",
"deficit": "不足",
"@deficit": {
"description": "Caloric deficit (either planned or unplanned)"
},
"kcalValue": "{value} 卡",
"kcalValue": "{value} 卡",
"@kcalValue": {
"description": "A value in kcal, e.g. 500 kcal",
"type": "text",
"placeholders": {
"value": {}
"value": {
"type": "String"
}
}
},
"noWeightEntries": "您沒有體重條目",
"noWeightEntries": "您沒有體重紀錄",
"@noWeightEntries": {
"description": "Message shown when the user has no logged weight entries"
},
"noMeasurementEntries": "您沒有測量條目",
"noMeasurementEntries": "您沒有測量紀錄",
"@noMeasurementEntries": {},
"enterMinCharacters": "請輸入至少 {min} 個字",
"enterMinCharacters": "請輸入至少 {min} 個字",
"@enterMinCharacters": {
"description": "Error message when the user hasn't entered the minimum amount characters in a form",
"type": "text",
"placeholders": {
"min": {}
"min": {
"type": "String"
}
}
},
"baseNameEnglish": "所有訓練都需要一個英文基本名稱",
"baseNameEnglish": "所有動作都需要一個英文基本名稱",
"@baseNameEnglish": {},
"aboutDonateText": "請我們喝杯咖啡來幫助項目,支付服務器費用,讓我們保持經費",
"aboutDonateText": "請我們喝杯咖啡來支持專案、支付伺服器費用,讓我們持續前進",
"@aboutDonateText": {},
"aboutMastodonTitle": "Mastodon",
"@aboutMastodonTitle": {
"description": "Title for mastodon section in the about dialog"
},
"aboutTranslationTitle": "翻譯",
"@aboutTranslationTitle": {
"description": "Title for translation section in the about dialog"
},
"aboutDonateTitle": "捐贈",
"@aboutMastodonTitle": {},
"aboutDonateTitle": "贊助",
"@aboutDonateTitle": {},
"goToToday": "到今天",
"goToToday": "到今天",
"@goToToday": {
"description": "Label on button to jump back to 'today' in the calendar widget"
},
"total": "總",
"total": "總",
"@total": {
"description": "Label used for total sums of e.g. calories or similar"
},
"swiss_ball": "抗力球",
"swiss_ball": "瑜伽球",
"@swiss_ball": {
"description": "Generated entry for translation for server strings"
},
@@ -843,7 +805,7 @@
"@sz_bar": {
"description": "Generated entry for translation for server strings"
},
"pull_up_bar": "引體上桿",
"pull_up_bar": "引體上桿",
"@pull_up_bar": {
"description": "Generated entry for translation for server strings"
},
@@ -855,11 +817,11 @@
"@minutes": {
"description": "Generated entry for translation for server strings"
},
"incline_bench": "上斜",
"incline_bench": "上斜",
"@incline_bench": {
"description": "Generated entry for translation for server strings"
},
"hamstrings": "腿後肌",
"hamstrings": "腿後肌",
"@hamstrings": {
"description": "Generated entry for translation for server strings"
},
@@ -867,28 +829,28 @@
"@selectImage": {
"description": "Label and error message when the user hasn't selected an image to save"
},
"chooseFromLibrary": "從照片庫中選擇",
"chooseFromLibrary": "從相簿中選擇",
"@chooseFromLibrary": {},
"enterValidNumber": "請輸入有效數",
"enterValidNumber": "請輸入有效數",
"@enterValidNumber": {
"description": "Error message when the user has submitted an invalid number (e.g. '3,.,.,.')"
},
"dataCopied": "資料已複製到新條目",
"dataCopied": "資料已複製到新紀錄",
"@dataCopied": {
"description": "Snackbar message to show on copying data to a new log entry"
},
"setUnitsAndRir": "設定單位和次數",
"setUnitsAndRir": "設定單位和保留次數",
"@setUnitsAndRir": {
"description": "Label shown on the slider where the user can toggle showing units and RiR",
"type": "text"
},
"recentlyUsedIngredients": "最近加入的材料",
"recentlyUsedIngredients": "最近新增的食材",
"@recentlyUsedIngredients": {
"description": "A message when a user adds a new ingredient to a meal."
},
"takePicture": "拍張照片",
"takePicture": "拍",
"@takePicture": {},
"gallery": "照片庫",
"gallery": "相簿",
"@gallery": {},
"next": "下一個",
"@next": {},
@@ -896,12 +858,91 @@
"@repetitions": {
"description": "Generated entry for translation for server strings"
},
"goalMacro": "量營養素目標",
"goalMacro": "量營養素目標",
"@goalMacro": {
"description": "The goal for macronutrients"
},
"fiber": "纖維",
"@fiber": {},
"goalFiber": "纖維目標",
"@goalFiber": {}
"@goalFiber": {},
"settingsIngredientCacheDescription": "食材快取",
"@settingsIngredientCacheDescription": {},
"overallChangeWeight": "整體變化",
"@overallChangeWeight": {
"description": "Overall change in weight, added for localization"
},
"goalTypeMeals": "來自餐點",
"@goalTypeMeals": {
"description": "added for localization of Class GoalType's filed meals"
},
"goalTypeBasic": "基本",
"@goalTypeBasic": {
"description": "added for localization of Class GoalType's filed basic"
},
"goalTypeAdvanced": "進階",
"@goalTypeAdvanced": {
"description": "added for localization of Class GoalType's filed advanced"
},
"indicatorRaw": "原始",
"@indicatorRaw": {
"description": "added for localization of Class Indicator's field text"
},
"indicatorAvg": "平均",
"@indicatorAvg": {
"description": "added for localization of Class Indicator's field text"
},
"themeMode": "主題模式",
"@themeMode": {},
"darkMode": "保持深色模式",
"@darkMode": {},
"lightMode": "保持淺色模式",
"@lightMode": {},
"systemMode": "系統設定",
"@systemMode": {},
"chartAllTimeTitle": "{name} 歷史紀錄",
"@chartAllTimeTitle": {
"description": "All-time chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chart30DaysTitle": "{name} 最近 30 天",
"@chart30DaysTitle": {
"description": "last 30 days chart of 'name' (e.g. 'weight', 'body fat' etc.)",
"type": "text",
"placeholders": {
"name": {
"type": "String"
}
}
},
"chartDuringPlanTitle": "營養計劃 {planName} 期間的 {chartName}",
"@chartDuringPlanTitle": {
"description": "chart of 'chartName' (e.g. 'weight', 'body fat' etc.) logged during plan",
"type": "text",
"placeholders": {
"chartName": {
"type": "String"
},
"planName": {
"type": "String"
}
}
},
"noIngredientsDefined": "尚未定義食材",
"@noIngredientsDefined": {},
"ingredientLogged": "食材已紀錄到日記",
"@ingredientLogged": {},
"selectMealToLog": "選擇要紀錄到日記的餐點",
"@selectMealToLog": {},
"errorCouldNotConnectToServerDetails": "無法連接到伺服器",
"@errorCouldNotConnectToServerDetails": {},
"copyToClipboard": "複製到剪貼簿",
"@copyToClipboard": {},
"aboutWhySupportTitle": "開源且免費使用❤️",
"@aboutWhySupportTitle": {}
}

39
lib/l10n/remove_keys.dart Normal file
View File

@@ -0,0 +1,39 @@
import 'dart:convert';
import 'dart:io';
/// Remove unused keys from .arb files
///
/// Usage: dart remove_keys.dart aboutBugsTitle aboutBugsText
void main(List<String> arguments) {
if (arguments.isEmpty) {
print('Please provide at least one key as a parameter.');
return;
}
final directory = Directory('.');
final keysToRemove = arguments;
// Iterate through all files in the directory that end with .arb
for (final file in directory.listSync().where((f) => f.path.endsWith('.arb'))) {
final content = File(file.path).readAsStringSync();
final Map<String, dynamic> jsonContent = json.decode(content);
var modified = false;
for (final key in keysToRemove) {
if (jsonContent.containsKey(key)) {
// Remove the key
jsonContent.remove(key);
// Remove the metadata key (e.g., '@$key')
jsonContent.remove('@$key');
modified = true;
print('Key "$key" removed from ${file.path}.');
}
}
if (modified) {
File(file.path).writeAsStringSync(const JsonEncoder.withIndent(' ').convert(jsonContent));
}
}
}

View File

@@ -18,11 +18,15 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart' as riverpod;
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:wger/core/locator.dart';
import 'package:wger/powersync.dart';
import 'package:wger/exceptions/http_exception.dart';
import 'package:wger/helpers/errors.dart';
import 'package:wger/helpers/shared_preferences.dart';
import 'package:wger/l10n/generated/app_localizations.dart';
import 'package:wger/providers/add_exercise.dart';
import 'package:wger/providers/base_provider.dart';
import 'package:wger/providers/body_weight.dart';
@@ -30,10 +34,11 @@ import 'package:wger/providers/exercises.dart';
import 'package:wger/providers/gallery.dart';
import 'package:wger/providers/measurement.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/providers/routines.dart';
import 'package:wger/providers/user.dart';
import 'package:wger/providers/workout_plans.dart';
import 'package:wger/screens/add_exercise_screen.dart';
import 'package:wger/screens/auth_screen.dart';
import 'package:wger/screens/configure_plates_screen.dart';
import 'package:wger/screens/dashboard.dart';
import 'package:wger/screens/exercise_screen.dart';
import 'package:wger/screens/exercises_screen.dart';
@@ -48,44 +53,106 @@ import 'package:wger/screens/measurement_entries_screen.dart';
import 'package:wger/screens/nutritional_diary_screen.dart';
import 'package:wger/screens/nutritional_plan_screen.dart';
import 'package:wger/screens/nutritional_plans_screen.dart';
import 'package:wger/screens/routine_edit_screen.dart';
import 'package:wger/screens/routine_list_screen.dart';
import 'package:wger/screens/routine_logs_screen.dart';
import 'package:wger/screens/routine_screen.dart';
import 'package:wger/screens/splash_screen.dart';
import 'package:wger/screens/update_app_screen.dart';
import 'package:wger/screens/weight_screen.dart';
import 'package:wger/screens/workout_plan_screen.dart';
import 'package:wger/screens/workout_plans_screen.dart';
import 'package:wger/theme/theme.dart';
import 'package:wger/widgets/core/about.dart';
import 'package:wger/widgets/core/settingsdart';
import 'package:wger/widgets/core/log_overview.dart';
import 'package:wger/widgets/core/settings.dart';
import 'helpers/logs.dart';
import 'providers/auth.dart';
void main() async {
//zx.setLogEnabled(kDebugMode);
Logger.root.level = Level.INFO;
void _setupLogging() {
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
Logger.root.onRecord.listen((record) {
if (kDebugMode) {
print('[${record.loggerName}] ${record.level.name}: ${record.time}: ${record.message}');
if (record.error != null) {
print(record.error);
}
if (record.stackTrace != null) {
print(record.stackTrace);
}
}
print('${record.level.name}: ${record.time} [${record.loggerName}] ${record.message}');
InMemoryLogStore().add(record);
});
}
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
// Needs to be called before runApp
WidgetsFlutterBinding.ensureInitialized();
// Logger
_setupLogging();
final logger = Logger('main');
// Locator to initialize exerciseDB
await ServiceLocator().configure();
print('running myapp');
// SharedPreferences to SharedPreferencesAsync migration function
await PreferenceHelper.instance.migrationSupportFunctionForSharedPreferences();
// Catch errors from Flutter itself (widget build, layout, paint, etc.)
//
// NOTE: it seems this sometimes makes problems and even freezes the flutter
// process when widgets overflow, so it is disabled in dev mode.
if (!kDebugMode) {
FlutterError.onError = (FlutterErrorDetails details) {
final stack = details.stack ?? StackTrace.empty;
logger.severe('Error caught by FlutterError.onError: ${details.exception}');
FlutterError.dumpErrorToConsole(details);
// Don't show the full error dialog for network image loading errors.
if (details.exception is NetworkImageLoadException) {
return;
}
showGeneralErrorDialog(details.exception, stack);
// throw details.exception;
};
}
// Catch errors that happen outside of the Flutter framework (e.g., in async operations)
PlatformDispatcher.instance.onError = (error, stack) {
logger.severe('Error caught by PlatformDispatcher.instance.onError: $error');
logger.severe('Stack trace: $stack');
if (error is WgerHttpException) {
showHttpExceptionErrorDialog(error);
} else {
showGeneralErrorDialog(error, stack);
}
// Return true to indicate that the error has been handled.
return true;
};
// Application
runApp(const MyApp());
runApp(const riverpod.ProviderScope(child: MainApp()));
}
class MyApp extends StatelessWidget {
const MyApp(); // This widget is the root of your application.
class MainApp extends StatelessWidget {
const MainApp();
Widget _getHomeScreen(AuthProvider auth) {
switch (auth.state) {
case AuthState.loggedIn:
return HomeTabsScreen();
case AuthState.updateRequired:
return const UpdateAppScreen();
default:
return FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState == ConnectionState.waiting
? const SplashScreen()
: const AuthScreen(),
);
}
}
@override
Widget build(BuildContext context) {
return MultiProvider(
@@ -97,14 +164,14 @@ class MyApp extends StatelessWidget {
update: (context, base, previous) =>
previous ?? ExercisesProvider(WgerBaseProvider(base)),
),
ChangeNotifierProxyProvider2<AuthProvider, ExercisesProvider, WorkoutPlansProvider>(
create: (context) => WorkoutPlansProvider(
ChangeNotifierProxyProvider2<AuthProvider, ExercisesProvider, RoutinesProvider>(
create: (context) => RoutinesProvider(
WgerBaseProvider(Provider.of(context, listen: false)),
Provider.of(context, listen: false),
[],
),
update: (context, auth, exercises, previous) =>
previous ?? WorkoutPlansProvider(WgerBaseProvider(auth), exercises, []),
previous ?? RoutinesProvider(WgerBaseProvider(auth), exercises, []),
),
ChangeNotifierProxyProvider<AuthProvider, NutritionPlansProvider>(
create: (context) =>
@@ -140,46 +207,45 @@ class MyApp extends StatelessWidget {
),
],
child: Consumer<AuthProvider>(
builder: (ctx, auth, _) => MaterialApp(
title: 'wger',
theme: wgerLightTheme,
darkTheme: wgerDarkTheme,
highContrastTheme: wgerLightThemeHc,
highContrastDarkTheme: wgerDarkThemeHc,
themeMode: ThemeMode.system,
home: auth.isAuth
? const HomeTabsScreen()
: FutureBuilder(
future: auth.tryAutoLogin(),
builder: (ctx, authResultSnapshot) =>
authResultSnapshot.connectionState == ConnectionState.waiting
? const SplashScreen()
: const AuthScreen(),
),
routes: {
DashboardScreen.routeName: (ctx) => const DashboardScreen(),
FormScreen.routeName: (ctx) => const FormScreen(),
GalleryScreen.routeName: (ctx) => const GalleryScreen(),
GymModeScreen.routeName: (ctx) => const GymModeScreen(),
HomeTabsScreen.routeName: (ctx) => const HomeTabsScreen(),
MeasurementCategoriesScreen.routeName: (ctx) => const MeasurementCategoriesScreen(),
MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(),
NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(),
NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(),
NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(),
LogMealsScreen.routeName: (ctx) => const LogMealsScreen(),
LogMealScreen.routeName: (ctx) => const LogMealScreen(),
WeightScreen.routeName: (ctx) => const WeightScreen(),
WorkoutPlanScreen.routeName: (ctx) => const WorkoutPlanScreen(),
WorkoutPlansScreen.routeName: (ctx) => const WorkoutPlansScreen(),
ExercisesScreen.routeName: (ctx) => const ExercisesScreen(),
ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(),
AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(),
AboutPage.routeName: (ctx) => const AboutPage(),
SettingsPage.routeName: (ctx) => const SettingsPage(),
},
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
builder: (ctx, auth, _) => Consumer<UserProvider>(
builder: (ctx, user, _) => MaterialApp(
title: 'wger',
navigatorKey: navigatorKey,
theme: wgerLightTheme,
darkTheme: wgerDarkTheme,
highContrastTheme: wgerLightThemeHc,
highContrastDarkTheme: wgerDarkThemeHc,
themeMode: user.themeMode,
home: _getHomeScreen(auth),
routes: {
DashboardScreen.routeName: (ctx) => const DashboardScreen(),
FormScreen.routeName: (ctx) => const FormScreen(),
GalleryScreen.routeName: (ctx) => const GalleryScreen(),
GymModeScreen.routeName: (ctx) => const GymModeScreen(),
HomeTabsScreen.routeName: (ctx) => HomeTabsScreen(),
MeasurementCategoriesScreen.routeName: (ctx) => const MeasurementCategoriesScreen(),
MeasurementEntriesScreen.routeName: (ctx) => const MeasurementEntriesScreen(),
NutritionalPlansScreen.routeName: (ctx) => const NutritionalPlansScreen(),
NutritionalDiaryScreen.routeName: (ctx) => const NutritionalDiaryScreen(),
NutritionalPlanScreen.routeName: (ctx) => const NutritionalPlanScreen(),
LogMealsScreen.routeName: (ctx) => const LogMealsScreen(),
LogMealScreen.routeName: (ctx) => const LogMealScreen(),
WeightScreen.routeName: (ctx) => const WeightScreen(),
RoutineScreen.routeName: (ctx) => const RoutineScreen(),
RoutineEditScreen.routeName: (ctx) => const RoutineEditScreen(),
WorkoutLogsScreen.routeName: (ctx) => const WorkoutLogsScreen(),
RoutineListScreen.routeName: (ctx) => const RoutineListScreen(),
ExercisesScreen.routeName: (ctx) => const ExercisesScreen(),
ExerciseDetailScreen.routeName: (ctx) => const ExerciseDetailScreen(),
AddExerciseScreen.routeName: (ctx) => const AddExerciseScreen(),
AboutPage.routeName: (ctx) => const AboutPage(),
SettingsPage.routeName: (ctx) => const SettingsPage(),
LogOverviewPage.routeName: (ctx) => const LogOverviewPage(),
ConfigurePlatesScreen.routeName: (ctx) => const ConfigurePlatesScreen(),
},
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
),
),
),
);

View File

@@ -29,7 +29,7 @@ class WeightEntry {
@JsonKey(required: true, fromJson: stringToNum, toJson: numToString)
late num weight = 0;
@JsonKey(required: true, toJson: toDate)
@JsonKey(required: true)
late DateTime date;
WeightEntry({this.id, weight, DateTime? date}) {
@@ -41,10 +41,10 @@ class WeightEntry {
}
WeightEntry copyWith({int? id, int? weight, DateTime? date}) => WeightEntry(
id: id,
weight: weight ?? this.weight,
date: date ?? this.date,
);
id: id,
weight: weight ?? this.weight,
date: date ?? this.date,
);
// Boilerplate
factory WeightEntry.fromJson(Map<String, dynamic> json) => _$WeightEntryFromJson(json);

View File

@@ -7,10 +7,7 @@ part of 'weight_entry.dart';
// **************************************************************************
WeightEntry _$WeightEntryFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'weight', 'date'],
);
$checkKeys(json, requiredKeys: const ['id', 'weight', 'date']);
return WeightEntry(
id: (json['id'] as num?)?.toInt(),
weight: stringToNum(json['weight'] as String?),
@@ -18,9 +15,8 @@ WeightEntry _$WeightEntryFromJson(Map<String, dynamic> json) {
);
}
Map<String, dynamic> _$WeightEntryToJson(WeightEntry instance) =>
<String, dynamic>{
'id': instance.id,
'weight': numToString(instance.weight),
'date': toDate(instance.date),
};
Map<String, dynamic> _$WeightEntryToJson(WeightEntry instance) => <String, dynamic>{
'id': instance.id,
'weight': numToString(instance.weight),
'date': instance.date.toIso8601String(),
};

View File

@@ -25,15 +25,16 @@ class Alias {
@JsonKey(required: true)
final int? id;
@JsonKey(name: 'exercise')
final int? exerciseId;
@JsonKey(name: 'translation')
final int? translationId;
@JsonKey(required: true)
final String alias;
const Alias({this.id, required this.exerciseId, required this.alias});
const Alias({this.id, required this.translationId, required this.alias});
// Boilerplate
factory Alias.fromJson(Map<String, dynamic> json) => _$AliasFromJson(json);
Map<String, dynamic> toJson() => _$AliasToJson(this);
}

View File

@@ -7,19 +7,16 @@ part of 'alias.dart';
// **************************************************************************
Alias _$AliasFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'alias'],
);
$checkKeys(json, requiredKeys: const ['id', 'alias']);
return Alias(
id: (json['id'] as num?)?.toInt(),
exerciseId: (json['exercise'] as num?)?.toInt(),
translationId: (json['translation'] as num?)?.toInt(),
alias: json['alias'] as String,
);
}
Map<String, dynamic> _$AliasToJson(Alias instance) => <String, dynamic>{
'id': instance.id,
'exercise': instance.exerciseId,
'alias': instance.alias,
};
'id': instance.id,
'translation': instance.translationId,
'alias': instance.alias,
};

View File

@@ -7,18 +7,11 @@ part of 'category.dart';
// **************************************************************************
ExerciseCategory _$ExerciseCategoryFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'name'],
);
return ExerciseCategory(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
);
$checkKeys(json, requiredKeys: const ['id', 'name']);
return ExerciseCategory(id: (json['id'] as num).toInt(), name: json['name'] as String);
}
Map<String, dynamic> _$ExerciseCategoryToJson(ExerciseCategory instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
};
Map<String, dynamic> _$ExerciseCategoryToJson(ExerciseCategory instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
};

View File

@@ -25,19 +25,20 @@ class Comment {
@JsonKey(required: true)
final int id;
@JsonKey(name: 'exercise')
final int exerciseId;
@JsonKey(name: 'translation')
final int translationId;
@JsonKey(required: true)
final String comment;
const Comment({
required this.id,
required this.exerciseId,
required this.translationId,
required this.comment,
});
// Boilerplate
factory Comment.fromJson(Map<String, dynamic> json) => _$CommentFromJson(json);
Map<String, dynamic> toJson() => _$CommentToJson(this);
}

View File

@@ -7,19 +7,16 @@ part of 'comment.dart';
// **************************************************************************
Comment _$CommentFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'comment'],
);
$checkKeys(json, requiredKeys: const ['id', 'comment']);
return Comment(
id: (json['id'] as num).toInt(),
exerciseId: (json['exercise'] as num).toInt(),
translationId: (json['translation'] as num).toInt(),
comment: json['comment'] as String,
);
}
Map<String, dynamic> _$CommentToJson(Comment instance) => <String, dynamic>{
'id': instance.id,
'exercise': instance.exerciseId,
'comment': instance.comment,
};
'id': instance.id,
'translation': instance.translationId,
'comment': instance.comment,
};

View File

@@ -7,10 +7,7 @@ part of 'equipment.dart';
// **************************************************************************
Equipment _$EquipmentFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'name'],
);
$checkKeys(json, requiredKeys: const ['id', 'name']);
return Equipment(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
@@ -18,6 +15,6 @@ Equipment _$EquipmentFromJson(Map<String, dynamic> json) {
}
Map<String, dynamic> _$EquipmentToJson(Equipment instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
};
'id': instance.id,
'name': instance.name,
};

View File

@@ -15,9 +15,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:developer';
import 'package:collection/collection.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:logging/logging.dart';
import 'package:wger/helpers/consts.dart';
import 'package:wger/models/exercises/category.dart';
import 'package:wger/models/exercises/equipment.dart';
@@ -32,6 +35,8 @@ part 'exercise.g.dart';
@JsonSerializable(explicitToJson: true)
class Exercise extends Equatable {
final _logger = Logger('ExerciseModel');
@JsonKey(required: true)
late final int? id;
@@ -139,40 +144,50 @@ class Exercise extends Equatable {
this.authorsGlobal = authorsGlobal ?? [];
}
bool get showPlateCalculator => equipment.map((e) => e.id).contains(ID_EQUIPMENT_BARBELL);
Exercise.fromApiDataString(String baseData, List<Language> languages)
: this.fromApiData(ExerciseApiData.fromString(baseData), languages);
: this.fromApiData(ExerciseApiData.fromString(baseData), languages);
Exercise.fromApiDataJson(Map<String, dynamic> baseData, List<Language> languages)
: this.fromApiData(ExerciseApiData.fromJson(baseData), languages);
: this.fromApiData(ExerciseApiData.fromJson(baseData), languages);
Exercise.fromApiData(ExerciseApiData baseData, List<Language> languages) {
id = baseData.id;
uuid = baseData.uuid;
categoryId = baseData.category.id;
category = baseData.category;
Exercise.fromApiData(ExerciseApiData exerciseData, List<Language> languages) {
id = exerciseData.id;
uuid = exerciseData.uuid;
categoryId = exerciseData.category.id;
category = exerciseData.category;
created = baseData.created;
lastUpdate = baseData.lastUpdate;
lastUpdateGlobal = baseData.lastUpdateGlobal;
created = exerciseData.created;
lastUpdate = exerciseData.lastUpdate;
lastUpdateGlobal = exerciseData.lastUpdateGlobal;
musclesSecondary = baseData.muscles;
muscles = baseData.muscles;
equipment = baseData.equipment;
category = baseData.category;
translations = baseData.translations.map((e) {
e.language = languages.firstWhere((l) => l.id == e.languageId);
musclesSecondary = exerciseData.muscles;
muscles = exerciseData.muscles;
equipment = exerciseData.equipment;
category = exerciseData.category;
translations = exerciseData.translations.map((e) {
e.language = languages.firstWhere(
(l) => l.id == e.languageId,
// workaround for https://github.com/wger-project/flutter/issues/722
orElse: () {
log('Could not find language for translation ${e.languageId}');
return Language(id: e.languageId, shortName: 'unknown', fullName: 'unknown');
},
);
return e;
}).toList();
videos = baseData.videos;
images = baseData.images;
videos = exerciseData.videos;
images = exerciseData.images;
authors = baseData.authors;
authorsGlobal = baseData.authorsGlobal;
authors = exerciseData.authors;
authorsGlobal = exerciseData.authorsGlobal;
variationId = baseData.variationId;
variationId = exerciseData.variationId;
}
/// Returns exercises for the given language
/// Returns translation for the given language
///
/// If no translation is found, English will be returned
///
@@ -180,7 +195,7 @@ class Exercise extends Equatable {
/// translation in English. This is something that should never happen,
/// but we can't make sure that no local installation hasn't deleted
/// the entry in English.
Translation getExercise(String language) {
Translation getTranslation(String language) {
// If the language is in the form en-US, take the language code only
final languageCode = language.split('-')[0];
@@ -188,17 +203,19 @@ class Exercise extends Equatable {
(e) => e.languageObj.shortName == languageCode,
orElse: () => translations.firstWhere(
(e) => e.languageObj.shortName == LANGUAGE_SHORT_ENGLISH,
orElse: () => translations.first,
orElse: () {
_logger.info(
'Could not find fallback english translation for exercise-ID ${id}, returning '
'first language (${translations.first.languageObj.shortName}) instead.',
);
return translations.first;
},
),
);
}
ExerciseImage? get getMainImage {
try {
return images.firstWhere((image) => image.isMain);
} on StateError {
return null;
}
return images.firstWhereOrNull((image) => image.isMain);
}
set setCategory(ExerciseCategory category) {
@@ -213,13 +230,13 @@ class Exercise extends Equatable {
@override
List<Object?> get props => [
id,
uuid,
created,
lastUpdate,
category,
equipment,
muscles,
musclesSecondary,
];
id,
uuid,
created,
lastUpdate,
category,
equipment,
muscles,
musclesSecondary,
];
}

View File

@@ -19,53 +19,46 @@ Exercise _$ExerciseFromJson(Map<String, dynamic> json) {
'category',
'muscles',
'muscles_secondary',
'equipment'
'equipment',
],
);
return Exercise(
id: (json['id'] as num?)?.toInt(),
uuid: json['uuid'] as String?,
created: json['created'] == null
? null
: DateTime.parse(json['created'] as String),
lastUpdate: json['last_update'] == null
? null
: DateTime.parse(json['last_update'] as String),
lastUpdateGlobal: json['last_update_global'] == null
? null
: DateTime.parse(json['last_update_global'] as String),
variationId: (json['variations'] as num?)?.toInt(),
translations: (json['translations'] as List<dynamic>?)
?.map((e) => Translation.fromJson(e as Map<String, dynamic>))
.toList(),
category: json['categories'] == null
? null
: ExerciseCategory.fromJson(json['categories'] as Map<String, dynamic>),
)
id: (json['id'] as num?)?.toInt(),
uuid: json['uuid'] as String?,
created: json['created'] == null ? null : DateTime.parse(json['created'] as String),
lastUpdate: json['last_update'] == null
? null
: DateTime.parse(json['last_update'] as String),
lastUpdateGlobal: json['last_update_global'] == null
? null
: DateTime.parse(json['last_update_global'] as String),
variationId: (json['variations'] as num?)?.toInt(),
translations: (json['translations'] as List<dynamic>?)
?.map((e) => Translation.fromJson(e as Map<String, dynamic>))
.toList(),
category: json['categories'] == null
? null
: ExerciseCategory.fromJson(json['categories'] as Map<String, dynamic>),
)
..categoryId = (json['category'] as num).toInt()
..musclesIds = (json['muscles'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList()
..musclesIds = (json['muscles'] as List<dynamic>).map((e) => (e as num).toInt()).toList()
..musclesSecondaryIds = (json['muscles_secondary'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList()
..equipmentIds = (json['equipment'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList();
..equipmentIds = (json['equipment'] as List<dynamic>).map((e) => (e as num).toInt()).toList();
}
Map<String, dynamic> _$ExerciseToJson(Exercise instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'variations': instance.variationId,
'created': instance.created?.toIso8601String(),
'last_update': instance.lastUpdate?.toIso8601String(),
'last_update_global': instance.lastUpdateGlobal?.toIso8601String(),
'category': instance.categoryId,
'categories': instance.category?.toJson(),
'muscles': instance.musclesIds,
'muscles_secondary': instance.musclesSecondaryIds,
'musclesSecondary':
instance.musclesSecondary.map((e) => e.toJson()).toList(),
'equipment': instance.equipmentIds,
};
'id': instance.id,
'uuid': instance.uuid,
'variations': instance.variationId,
'created': instance.created?.toIso8601String(),
'last_update': instance.lastUpdate?.toIso8601String(),
'last_update_global': instance.lastUpdateGlobal?.toIso8601String(),
'category': instance.categoryId,
'categories': instance.category?.toJson(),
'muscles': instance.musclesIds,
'muscles_secondary': instance.musclesSecondaryIds,
'musclesSecondary': instance.musclesSecondary.map((e) => e.toJson()).toList(),
'equipment': instance.equipmentIds,
};

View File

@@ -11,12 +11,12 @@ import 'package:wger/models/exercises/video.dart';
part 'exercise_api.freezed.dart';
part 'exercise_api.g.dart';
/// Model for an exercise as returned from the exercisebaseinfo endpoint
/// Model for an exercise as returned from the exerciseinfo endpoint
///
/// Basically this is just used as a convenience to create "real" exercise
/// objects and nothing more
@freezed
class ExerciseApiData with _$ExerciseApiData {
sealed class ExerciseApiData with _$ExerciseApiData {
factory ExerciseApiData({
required int id,
required String uuid,
@@ -35,7 +35,7 @@ class ExerciseApiData with _$ExerciseApiData {
// ignore: invalid_annotation_target
required List<Equipment> equipment,
// ignore: invalid_annotation_target
@JsonKey(name: 'exercises') required List<Translation> translations,
@JsonKey(name: 'translations', defaultValue: []) required List<Translation> translations,
required List<ExerciseImage> images,
required List<Video> videos,
// ignore: invalid_annotation_target
@@ -52,7 +52,7 @@ class ExerciseApiData with _$ExerciseApiData {
/// Model for the search results returned from the /api/v2/exercise/search endpoint
///
@freezed
class ExerciseSearchDetails with _$ExerciseSearchDetails {
sealed class ExerciseSearchDetails with _$ExerciseSearchDetails {
factory ExerciseSearchDetails({
// ignore: invalid_annotation_target
@JsonKey(name: 'id') required int translationId,
@@ -70,7 +70,7 @@ class ExerciseSearchDetails with _$ExerciseSearchDetails {
}
@freezed
class ExerciseSearchEntry with _$ExerciseSearchEntry {
sealed class ExerciseSearchEntry with _$ExerciseSearchEntry {
factory ExerciseSearchEntry({
required String value,
required ExerciseSearchDetails data,
@@ -81,7 +81,7 @@ class ExerciseSearchEntry with _$ExerciseSearchEntry {
}
@freezed
class ExerciseApiSearch with _$ExerciseApiSearch {
sealed class ExerciseApiSearch with _$ExerciseApiSearch {
factory ExerciseApiSearch({
required List<ExerciseSearchEntry> suggestions,
}) = _ExerciseApiSearch;

File diff suppressed because it is too large Load Diff

View File

@@ -6,66 +6,58 @@ part of 'exercise_api.dart';
// JsonSerializableGenerator
// **************************************************************************
_$ExerciseBaseDataImpl _$$ExerciseBaseDataImplFromJson(
Map<String, dynamic> json) =>
_$ExerciseBaseDataImpl(
id: (json['id'] as num).toInt(),
uuid: json['uuid'] as String,
variationId: (json['variations'] as num?)?.toInt() ?? null,
created: DateTime.parse(json['created'] as String),
lastUpdate: DateTime.parse(json['last_update'] as String),
lastUpdateGlobal: DateTime.parse(json['last_update_global'] as String),
category:
ExerciseCategory.fromJson(json['category'] as Map<String, dynamic>),
muscles: (json['muscles'] as List<dynamic>)
.map((e) => Muscle.fromJson(e as Map<String, dynamic>))
.toList(),
musclesSecondary: (json['muscles_secondary'] as List<dynamic>)
.map((e) => Muscle.fromJson(e as Map<String, dynamic>))
.toList(),
equipment: (json['equipment'] as List<dynamic>)
.map((e) => Equipment.fromJson(e as Map<String, dynamic>))
.toList(),
translations: (json['exercises'] as List<dynamic>)
.map((e) => Translation.fromJson(e as Map<String, dynamic>))
.toList(),
images: (json['images'] as List<dynamic>)
.map((e) => ExerciseImage.fromJson(e as Map<String, dynamic>))
.toList(),
videos: (json['videos'] as List<dynamic>)
.map((e) => Video.fromJson(e as Map<String, dynamic>))
.toList(),
authors: (json['author_history'] as List<dynamic>)
.map((e) => e as String)
.toList(),
authorsGlobal: (json['total_authors_history'] as List<dynamic>)
.map((e) => e as String)
.toList(),
);
_ExerciseBaseData _$ExerciseBaseDataFromJson(Map<String, dynamic> json) => _ExerciseBaseData(
id: (json['id'] as num).toInt(),
uuid: json['uuid'] as String,
variationId: (json['variations'] as num?)?.toInt() ?? null,
created: DateTime.parse(json['created'] as String),
lastUpdate: DateTime.parse(json['last_update'] as String),
lastUpdateGlobal: DateTime.parse(json['last_update_global'] as String),
category: ExerciseCategory.fromJson(json['category'] as Map<String, dynamic>),
muscles: (json['muscles'] as List<dynamic>)
.map((e) => Muscle.fromJson(e as Map<String, dynamic>))
.toList(),
musclesSecondary: (json['muscles_secondary'] as List<dynamic>)
.map((e) => Muscle.fromJson(e as Map<String, dynamic>))
.toList(),
equipment: (json['equipment'] as List<dynamic>)
.map((e) => Equipment.fromJson(e as Map<String, dynamic>))
.toList(),
translations:
(json['translations'] as List<dynamic>?)
?.map((e) => Translation.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
images: (json['images'] as List<dynamic>)
.map((e) => ExerciseImage.fromJson(e as Map<String, dynamic>))
.toList(),
videos: (json['videos'] as List<dynamic>)
.map((e) => Video.fromJson(e as Map<String, dynamic>))
.toList(),
authors: (json['author_history'] as List<dynamic>).map((e) => e as String).toList(),
authorsGlobal: (json['total_authors_history'] as List<dynamic>).map((e) => e as String).toList(),
);
Map<String, dynamic> _$$ExerciseBaseDataImplToJson(
_$ExerciseBaseDataImpl instance) =>
<String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'variations': instance.variationId,
'created': instance.created.toIso8601String(),
'last_update': instance.lastUpdate.toIso8601String(),
'last_update_global': instance.lastUpdateGlobal.toIso8601String(),
'category': instance.category,
'muscles': instance.muscles,
'muscles_secondary': instance.musclesSecondary,
'equipment': instance.equipment,
'exercises': instance.translations,
'images': instance.images,
'videos': instance.videos,
'author_history': instance.authors,
'total_authors_history': instance.authorsGlobal,
};
Map<String, dynamic> _$ExerciseBaseDataToJson(_ExerciseBaseData instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'variations': instance.variationId,
'created': instance.created.toIso8601String(),
'last_update': instance.lastUpdate.toIso8601String(),
'last_update_global': instance.lastUpdateGlobal.toIso8601String(),
'category': instance.category,
'muscles': instance.muscles,
'muscles_secondary': instance.musclesSecondary,
'equipment': instance.equipment,
'translations': instance.translations,
'images': instance.images,
'videos': instance.videos,
'author_history': instance.authors,
'total_authors_history': instance.authorsGlobal,
};
_$ExerciseSearchDetailsImpl _$$ExerciseSearchDetailsImplFromJson(
Map<String, dynamic> json) =>
_$ExerciseSearchDetailsImpl(
_ExerciseSearchDetails _$ExerciseSearchDetailsFromJson(Map<String, dynamic> json) =>
_ExerciseSearchDetails(
translationId: (json['id'] as num).toInt(),
exerciseId: (json['base_id'] as num).toInt(),
name: json['name'] as String,
@@ -74,8 +66,7 @@ _$ExerciseSearchDetailsImpl _$$ExerciseSearchDetailsImplFromJson(
imageThumbnail: json['image_thumbnail'] as String?,
);
Map<String, dynamic> _$$ExerciseSearchDetailsImplToJson(
_$ExerciseSearchDetailsImpl instance) =>
Map<String, dynamic> _$ExerciseSearchDetailsToJson(_ExerciseSearchDetails instance) =>
<String, dynamic>{
'id': instance.translationId,
'base_id': instance.exerciseId,
@@ -85,31 +76,21 @@ Map<String, dynamic> _$$ExerciseSearchDetailsImplToJson(
'image_thumbnail': instance.imageThumbnail,
};
_$ExerciseSearchEntryImpl _$$ExerciseSearchEntryImplFromJson(
Map<String, dynamic> json) =>
_$ExerciseSearchEntryImpl(
_ExerciseSearchEntry _$ExerciseSearchEntryFromJson(Map<String, dynamic> json) =>
_ExerciseSearchEntry(
value: json['value'] as String,
data:
ExerciseSearchDetails.fromJson(json['data'] as Map<String, dynamic>),
data: ExerciseSearchDetails.fromJson(json['data'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$ExerciseSearchEntryImplToJson(
_$ExerciseSearchEntryImpl instance) =>
<String, dynamic>{
'value': instance.value,
'data': instance.data,
};
Map<String, dynamic> _$ExerciseSearchEntryToJson(_ExerciseSearchEntry instance) =>
<String, dynamic>{'value': instance.value, 'data': instance.data};
_$ExerciseApiSearchImpl _$$ExerciseApiSearchImplFromJson(
Map<String, dynamic> json) =>
_$ExerciseApiSearchImpl(
suggestions: (json['suggestions'] as List<dynamic>)
.map((e) => ExerciseSearchEntry.fromJson(e as Map<String, dynamic>))
.toList(),
);
_ExerciseApiSearch _$ExerciseApiSearchFromJson(Map<String, dynamic> json) => _ExerciseApiSearch(
suggestions: (json['suggestions'] as List<dynamic>)
.map((e) => ExerciseSearchEntry.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$ExerciseApiSearchImplToJson(
_$ExerciseApiSearchImpl instance) =>
<String, dynamic>{
'suggestions': instance.suggestions,
};
Map<String, dynamic> _$ExerciseApiSearchToJson(_ExerciseApiSearch instance) => <String, dynamic>{
'suggestions': instance.suggestions,
};

View File

@@ -0,0 +1,81 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:freezed_annotation/freezed_annotation.dart';
part 'exercise_submission.freezed.dart';
part 'exercise_submission.g.dart';
// ignore_for_file: invalid_annotation_target
///
/// Models for the data model used when submitting an exercise to the API
///
/// Alias part of an exercise submission
@freezed
sealed class ExerciseAliasSubmissionApi with _$ExerciseAliasSubmissionApi {
const factory ExerciseAliasSubmissionApi({required String alias}) = _ExerciseAliasSubmissionApi;
factory ExerciseAliasSubmissionApi.fromJson(Map<String, dynamic> json) =>
_$ExerciseAliasSubmissionApiFromJson(json);
}
/// Comment part of an exercise submission
@freezed
sealed class ExerciseCommentSubmissionApi with _$ExerciseCommentSubmissionApi {
const factory ExerciseCommentSubmissionApi({required String alias}) =
_ExerciseCommentSubmissionApi;
factory ExerciseCommentSubmissionApi.fromJson(Map<String, dynamic> json) =>
_$ExerciseCommentSubmissionApiFromJson(json);
}
/// Translation part of an exercise submission
@freezed
sealed class ExerciseTranslationSubmissionApi with _$ExerciseTranslationSubmissionApi {
const factory ExerciseTranslationSubmissionApi({
required String name,
required String description,
required int language,
@JsonKey(name: 'license_author') required String author,
@Default([]) List<ExerciseAliasSubmissionApi> aliases,
@Default([]) List<ExerciseCommentSubmissionApi> comments,
}) = _ExerciseTranslationSubmissionApi;
factory ExerciseTranslationSubmissionApi.fromJson(Map<String, dynamic> json) =>
_$ExerciseTranslationSubmissionApiFromJson(json);
}
/// "Main" part of an exercise submission
@freezed
sealed class ExerciseSubmissionApi with _$ExerciseSubmissionApi {
const factory ExerciseSubmissionApi({
required int category,
required List<int> muscles,
@JsonKey(name: 'muscles_secondary') required List<int> musclesSecondary,
required List<int> equipment,
@JsonKey(name: 'license_author') required String author,
@JsonKey(includeToJson: true) int? variation,
@JsonKey(includeToJson: true, name: 'variations_connect_to') int? variationConnectTo,
required List<ExerciseTranslationSubmissionApi> translations,
}) = _ExerciseSubmissionApi;
factory ExerciseSubmissionApi.fromJson(Map<String, dynamic> json) =>
_$ExerciseSubmissionApiFromJson(json);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'exercise_submission.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_ExerciseAliasSubmissionApi _$ExerciseAliasSubmissionApiFromJson(Map<String, dynamic> json) =>
_ExerciseAliasSubmissionApi(alias: json['alias'] as String);
Map<String, dynamic> _$ExerciseAliasSubmissionApiToJson(_ExerciseAliasSubmissionApi instance) =>
<String, dynamic>{'alias': instance.alias};
_ExerciseCommentSubmissionApi _$ExerciseCommentSubmissionApiFromJson(Map<String, dynamic> json) =>
_ExerciseCommentSubmissionApi(alias: json['alias'] as String);
Map<String, dynamic> _$ExerciseCommentSubmissionApiToJson(_ExerciseCommentSubmissionApi instance) =>
<String, dynamic>{'alias': instance.alias};
_ExerciseTranslationSubmissionApi _$ExerciseTranslationSubmissionApiFromJson(
Map<String, dynamic> json,
) => _ExerciseTranslationSubmissionApi(
name: json['name'] as String,
description: json['description'] as String,
language: (json['language'] as num).toInt(),
author: json['license_author'] as String,
aliases:
(json['aliases'] as List<dynamic>?)
?.map((e) => ExerciseAliasSubmissionApi.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
comments:
(json['comments'] as List<dynamic>?)
?.map((e) => ExerciseCommentSubmissionApi.fromJson(e as Map<String, dynamic>))
.toList() ??
const [],
);
Map<String, dynamic> _$ExerciseTranslationSubmissionApiToJson(
_ExerciseTranslationSubmissionApi instance,
) => <String, dynamic>{
'name': instance.name,
'description': instance.description,
'language': instance.language,
'license_author': instance.author,
'aliases': instance.aliases,
'comments': instance.comments,
};
_ExerciseSubmissionApi _$ExerciseSubmissionApiFromJson(Map<String, dynamic> json) =>
_ExerciseSubmissionApi(
category: (json['category'] as num).toInt(),
muscles: (json['muscles'] as List<dynamic>).map((e) => (e as num).toInt()).toList(),
musclesSecondary: (json['muscles_secondary'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
equipment: (json['equipment'] as List<dynamic>).map((e) => (e as num).toInt()).toList(),
author: json['license_author'] as String,
variation: (json['variation'] as num?)?.toInt(),
variationConnectTo: (json['variations_connect_to'] as num?)?.toInt(),
translations: (json['translations'] as List<dynamic>)
.map((e) => ExerciseTranslationSubmissionApi.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$ExerciseSubmissionApiToJson(_ExerciseSubmissionApi instance) =>
<String, dynamic>{
'category': instance.category,
'muscles': instance.muscles,
'muscles_secondary': instance.musclesSecondary,
'equipment': instance.equipment,
'license_author': instance.author,
'variation': instance.variation,
'variations_connect_to': instance.variationConnectTo,
'translations': instance.translations,
};

View File

@@ -0,0 +1,67 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:io';
import 'package:flutter/material.dart';
enum ImageType {
photo(id: 1, label: 'Photo', icon: Icons.photo_camera),
threeD(id: 2, label: '3D', icon: Icons.view_in_ar),
line(id: 3, label: 'Line', icon: Icons.show_chart),
lowPoly(id: 4, label: 'Low-Poly', icon: Icons.filter_vintage),
other(id: 5, label: 'Other', icon: Icons.more_horiz);
const ImageType({required this.id, required this.label, required this.icon});
final int id;
final String label;
final IconData icon;
}
class ExerciseSubmissionImage {
final File imageFile;
String? title;
String? author;
String? authorUrl;
String? sourceUrl;
String? derivativeSourceUrl;
ImageType type = ImageType.photo;
ExerciseSubmissionImage({
this.title,
this.author,
this.authorUrl,
this.sourceUrl,
this.derivativeSourceUrl,
this.type = ImageType.photo,
required this.imageFile,
});
Map<String, String> toJson() {
return {
'license_title': title ?? '',
'license_author': author ?? '',
'license_author_url': authorUrl ?? '',
'license_object_url': sourceUrl ?? '',
'license_derivative_source_url': derivativeSourceUrl ?? '',
'style': type.id.toString(),
};
}
}

View File

@@ -28,8 +28,8 @@ class ExerciseImage {
@JsonKey(required: true)
final String uuid;
@JsonKey(required: true, name: 'exercise_base')
final int exerciseBaseId;
@JsonKey(required: true, name: 'exercise')
final int exerciseId;
@JsonKey(required: true, name: 'image')
final String url;
@@ -40,13 +40,14 @@ class ExerciseImage {
const ExerciseImage({
required this.id,
required this.uuid,
required this.exerciseBaseId,
required this.exerciseId,
required this.url,
required this.isMain,
});
// Boilerplate
factory ExerciseImage.fromJson(Map<String, dynamic> json) => _$ExerciseImageFromJson(json);
Map<String, dynamic> toJson() => _$ExerciseImageToJson(this);
@override

View File

@@ -7,24 +7,20 @@ part of 'image.dart';
// **************************************************************************
ExerciseImage _$ExerciseImageFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'uuid', 'exercise_base', 'image'],
);
$checkKeys(json, requiredKeys: const ['id', 'uuid', 'exercise', 'image']);
return ExerciseImage(
id: (json['id'] as num).toInt(),
uuid: json['uuid'] as String,
exerciseBaseId: (json['exercise_base'] as num).toInt(),
exerciseId: (json['exercise'] as num).toInt(),
url: json['image'] as String,
isMain: json['is_main'] as bool? ?? false,
);
}
Map<String, dynamic> _$ExerciseImageToJson(ExerciseImage instance) =>
<String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'exercise_base': instance.exerciseBaseId,
'image': instance.url,
'is_main': instance.isMain,
};
Map<String, dynamic> _$ExerciseImageToJson(ExerciseImage instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'exercise': instance.exerciseId,
'image': instance.url,
'is_main': instance.isMain,
};

View File

@@ -1,40 +0,0 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'ingredient_api.freezed.dart';
part 'ingredient_api.g.dart';
/// Model for the search results returned from the /api/v2/ingredient/search endpoint
@freezed
class IngredientApiSearchDetails with _$IngredientApiSearchDetails {
factory IngredientApiSearchDetails({
required int id,
required String name,
required String? image,
// ignore: invalid_annotation_target
@JsonKey(name: 'image_thumbnail') required String? imageThumbnail,
}) = _IngredientApiSearchDetails;
factory IngredientApiSearchDetails.fromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchDetailsFromJson(json);
}
@freezed
class IngredientApiSearchEntry with _$IngredientApiSearchEntry {
factory IngredientApiSearchEntry({
required String value,
required IngredientApiSearchDetails data,
}) = _IngredientApiSearchEntry;
factory IngredientApiSearchEntry.fromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchEntryFromJson(json);
}
@freezed
class IngredientApiSearch with _$IngredientApiSearch {
factory IngredientApiSearch({
required List<IngredientApiSearchEntry> suggestions,
}) = _IngredientApiSearch;
factory IngredientApiSearch.fromJson(Map<String, dynamic> json) =>
_$IngredientApiSearchFromJson(json);
}

View File

@@ -1,555 +0,0 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// 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 'ingredient_api.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
IngredientApiSearchDetails _$IngredientApiSearchDetailsFromJson(
Map<String, dynamic> json) {
return _IngredientApiSearchDetails.fromJson(json);
}
/// @nodoc
mixin _$IngredientApiSearchDetails {
int get id => throw _privateConstructorUsedError;
String get name => throw _privateConstructorUsedError;
String? get image =>
throw _privateConstructorUsedError; // ignore: invalid_annotation_target
@JsonKey(name: 'image_thumbnail')
String? get imageThumbnail => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$IngredientApiSearchDetailsCopyWith<IngredientApiSearchDetails>
get copyWith => throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IngredientApiSearchDetailsCopyWith<$Res> {
factory $IngredientApiSearchDetailsCopyWith(IngredientApiSearchDetails value,
$Res Function(IngredientApiSearchDetails) then) =
_$IngredientApiSearchDetailsCopyWithImpl<$Res,
IngredientApiSearchDetails>;
@useResult
$Res call(
{int id,
String name,
String? image,
@JsonKey(name: 'image_thumbnail') String? imageThumbnail});
}
/// @nodoc
class _$IngredientApiSearchDetailsCopyWithImpl<$Res,
$Val extends IngredientApiSearchDetails>
implements $IngredientApiSearchDetailsCopyWith<$Res> {
_$IngredientApiSearchDetailsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = null,
Object? image = freezed,
Object? imageThumbnail = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
image: freezed == image
? _value.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
imageThumbnail: freezed == imageThumbnail
? _value.imageThumbnail
: imageThumbnail // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$IngredientApiSearchDetailsImplCopyWith<$Res>
implements $IngredientApiSearchDetailsCopyWith<$Res> {
factory _$$IngredientApiSearchDetailsImplCopyWith(
_$IngredientApiSearchDetailsImpl value,
$Res Function(_$IngredientApiSearchDetailsImpl) then) =
__$$IngredientApiSearchDetailsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
String name,
String? image,
@JsonKey(name: 'image_thumbnail') String? imageThumbnail});
}
/// @nodoc
class __$$IngredientApiSearchDetailsImplCopyWithImpl<$Res>
extends _$IngredientApiSearchDetailsCopyWithImpl<$Res,
_$IngredientApiSearchDetailsImpl>
implements _$$IngredientApiSearchDetailsImplCopyWith<$Res> {
__$$IngredientApiSearchDetailsImplCopyWithImpl(
_$IngredientApiSearchDetailsImpl _value,
$Res Function(_$IngredientApiSearchDetailsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? name = null,
Object? image = freezed,
Object? imageThumbnail = freezed,
}) {
return _then(_$IngredientApiSearchDetailsImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
image: freezed == image
? _value.image
: image // ignore: cast_nullable_to_non_nullable
as String?,
imageThumbnail: freezed == imageThumbnail
? _value.imageThumbnail
: imageThumbnail // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$IngredientApiSearchDetailsImpl implements _IngredientApiSearchDetails {
_$IngredientApiSearchDetailsImpl(
{required this.id,
required this.name,
required this.image,
@JsonKey(name: 'image_thumbnail') required this.imageThumbnail});
factory _$IngredientApiSearchDetailsImpl.fromJson(
Map<String, dynamic> json) =>
_$$IngredientApiSearchDetailsImplFromJson(json);
@override
final int id;
@override
final String name;
@override
final String? image;
// ignore: invalid_annotation_target
@override
@JsonKey(name: 'image_thumbnail')
final String? imageThumbnail;
@override
String toString() {
return 'IngredientApiSearchDetails(id: $id, name: $name, image: $image, imageThumbnail: $imageThumbnail)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IngredientApiSearchDetailsImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.image, image) || other.image == image) &&
(identical(other.imageThumbnail, imageThumbnail) ||
other.imageThumbnail == imageThumbnail));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, id, name, image, imageThumbnail);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$IngredientApiSearchDetailsImplCopyWith<_$IngredientApiSearchDetailsImpl>
get copyWith => __$$IngredientApiSearchDetailsImplCopyWithImpl<
_$IngredientApiSearchDetailsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IngredientApiSearchDetailsImplToJson(
this,
);
}
}
abstract class _IngredientApiSearchDetails
implements IngredientApiSearchDetails {
factory _IngredientApiSearchDetails(
{required final int id,
required final String name,
required final String? image,
@JsonKey(name: 'image_thumbnail')
required final String? imageThumbnail}) =
_$IngredientApiSearchDetailsImpl;
factory _IngredientApiSearchDetails.fromJson(Map<String, dynamic> json) =
_$IngredientApiSearchDetailsImpl.fromJson;
@override
int get id;
@override
String get name;
@override
String? get image;
@override // ignore: invalid_annotation_target
@JsonKey(name: 'image_thumbnail')
String? get imageThumbnail;
@override
@JsonKey(ignore: true)
_$$IngredientApiSearchDetailsImplCopyWith<_$IngredientApiSearchDetailsImpl>
get copyWith => throw _privateConstructorUsedError;
}
IngredientApiSearchEntry _$IngredientApiSearchEntryFromJson(
Map<String, dynamic> json) {
return _IngredientApiSearchEntry.fromJson(json);
}
/// @nodoc
mixin _$IngredientApiSearchEntry {
String get value => throw _privateConstructorUsedError;
IngredientApiSearchDetails get data => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$IngredientApiSearchEntryCopyWith<IngredientApiSearchEntry> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IngredientApiSearchEntryCopyWith<$Res> {
factory $IngredientApiSearchEntryCopyWith(IngredientApiSearchEntry value,
$Res Function(IngredientApiSearchEntry) then) =
_$IngredientApiSearchEntryCopyWithImpl<$Res, IngredientApiSearchEntry>;
@useResult
$Res call({String value, IngredientApiSearchDetails data});
$IngredientApiSearchDetailsCopyWith<$Res> get data;
}
/// @nodoc
class _$IngredientApiSearchEntryCopyWithImpl<$Res,
$Val extends IngredientApiSearchEntry>
implements $IngredientApiSearchEntryCopyWith<$Res> {
_$IngredientApiSearchEntryCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? value = null,
Object? data = null,
}) {
return _then(_value.copyWith(
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as IngredientApiSearchDetails,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$IngredientApiSearchDetailsCopyWith<$Res> get data {
return $IngredientApiSearchDetailsCopyWith<$Res>(_value.data, (value) {
return _then(_value.copyWith(data: value) as $Val);
});
}
}
/// @nodoc
abstract class _$$IngredientApiSearchEntryImplCopyWith<$Res>
implements $IngredientApiSearchEntryCopyWith<$Res> {
factory _$$IngredientApiSearchEntryImplCopyWith(
_$IngredientApiSearchEntryImpl value,
$Res Function(_$IngredientApiSearchEntryImpl) then) =
__$$IngredientApiSearchEntryImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({String value, IngredientApiSearchDetails data});
@override
$IngredientApiSearchDetailsCopyWith<$Res> get data;
}
/// @nodoc
class __$$IngredientApiSearchEntryImplCopyWithImpl<$Res>
extends _$IngredientApiSearchEntryCopyWithImpl<$Res,
_$IngredientApiSearchEntryImpl>
implements _$$IngredientApiSearchEntryImplCopyWith<$Res> {
__$$IngredientApiSearchEntryImplCopyWithImpl(
_$IngredientApiSearchEntryImpl _value,
$Res Function(_$IngredientApiSearchEntryImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? value = null,
Object? data = null,
}) {
return _then(_$IngredientApiSearchEntryImpl(
value: null == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String,
data: null == data
? _value.data
: data // ignore: cast_nullable_to_non_nullable
as IngredientApiSearchDetails,
));
}
}
/// @nodoc
@JsonSerializable()
class _$IngredientApiSearchEntryImpl implements _IngredientApiSearchEntry {
_$IngredientApiSearchEntryImpl({required this.value, required this.data});
factory _$IngredientApiSearchEntryImpl.fromJson(Map<String, dynamic> json) =>
_$$IngredientApiSearchEntryImplFromJson(json);
@override
final String value;
@override
final IngredientApiSearchDetails data;
@override
String toString() {
return 'IngredientApiSearchEntry(value: $value, data: $data)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IngredientApiSearchEntryImpl &&
(identical(other.value, value) || other.value == value) &&
(identical(other.data, data) || other.data == data));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, value, data);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$IngredientApiSearchEntryImplCopyWith<_$IngredientApiSearchEntryImpl>
get copyWith => __$$IngredientApiSearchEntryImplCopyWithImpl<
_$IngredientApiSearchEntryImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IngredientApiSearchEntryImplToJson(
this,
);
}
}
abstract class _IngredientApiSearchEntry implements IngredientApiSearchEntry {
factory _IngredientApiSearchEntry(
{required final String value,
required final IngredientApiSearchDetails data}) =
_$IngredientApiSearchEntryImpl;
factory _IngredientApiSearchEntry.fromJson(Map<String, dynamic> json) =
_$IngredientApiSearchEntryImpl.fromJson;
@override
String get value;
@override
IngredientApiSearchDetails get data;
@override
@JsonKey(ignore: true)
_$$IngredientApiSearchEntryImplCopyWith<_$IngredientApiSearchEntryImpl>
get copyWith => throw _privateConstructorUsedError;
}
IngredientApiSearch _$IngredientApiSearchFromJson(Map<String, dynamic> json) {
return _IngredientApiSearch.fromJson(json);
}
/// @nodoc
mixin _$IngredientApiSearch {
List<IngredientApiSearchEntry> get suggestions =>
throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$IngredientApiSearchCopyWith<IngredientApiSearch> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $IngredientApiSearchCopyWith<$Res> {
factory $IngredientApiSearchCopyWith(
IngredientApiSearch value, $Res Function(IngredientApiSearch) then) =
_$IngredientApiSearchCopyWithImpl<$Res, IngredientApiSearch>;
@useResult
$Res call({List<IngredientApiSearchEntry> suggestions});
}
/// @nodoc
class _$IngredientApiSearchCopyWithImpl<$Res, $Val extends IngredientApiSearch>
implements $IngredientApiSearchCopyWith<$Res> {
_$IngredientApiSearchCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? suggestions = null,
}) {
return _then(_value.copyWith(
suggestions: null == suggestions
? _value.suggestions
: suggestions // ignore: cast_nullable_to_non_nullable
as List<IngredientApiSearchEntry>,
) as $Val);
}
}
/// @nodoc
abstract class _$$IngredientApiSearchImplCopyWith<$Res>
implements $IngredientApiSearchCopyWith<$Res> {
factory _$$IngredientApiSearchImplCopyWith(_$IngredientApiSearchImpl value,
$Res Function(_$IngredientApiSearchImpl) then) =
__$$IngredientApiSearchImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({List<IngredientApiSearchEntry> suggestions});
}
/// @nodoc
class __$$IngredientApiSearchImplCopyWithImpl<$Res>
extends _$IngredientApiSearchCopyWithImpl<$Res, _$IngredientApiSearchImpl>
implements _$$IngredientApiSearchImplCopyWith<$Res> {
__$$IngredientApiSearchImplCopyWithImpl(_$IngredientApiSearchImpl _value,
$Res Function(_$IngredientApiSearchImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? suggestions = null,
}) {
return _then(_$IngredientApiSearchImpl(
suggestions: null == suggestions
? _value._suggestions
: suggestions // ignore: cast_nullable_to_non_nullable
as List<IngredientApiSearchEntry>,
));
}
}
/// @nodoc
@JsonSerializable()
class _$IngredientApiSearchImpl implements _IngredientApiSearch {
_$IngredientApiSearchImpl(
{required final List<IngredientApiSearchEntry> suggestions})
: _suggestions = suggestions;
factory _$IngredientApiSearchImpl.fromJson(Map<String, dynamic> json) =>
_$$IngredientApiSearchImplFromJson(json);
final List<IngredientApiSearchEntry> _suggestions;
@override
List<IngredientApiSearchEntry> get suggestions {
if (_suggestions is EqualUnmodifiableListView) return _suggestions;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_suggestions);
}
@override
String toString() {
return 'IngredientApiSearch(suggestions: $suggestions)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$IngredientApiSearchImpl &&
const DeepCollectionEquality()
.equals(other._suggestions, _suggestions));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_suggestions));
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$IngredientApiSearchImplCopyWith<_$IngredientApiSearchImpl> get copyWith =>
__$$IngredientApiSearchImplCopyWithImpl<_$IngredientApiSearchImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$IngredientApiSearchImplToJson(
this,
);
}
}
abstract class _IngredientApiSearch implements IngredientApiSearch {
factory _IngredientApiSearch(
{required final List<IngredientApiSearchEntry> suggestions}) =
_$IngredientApiSearchImpl;
factory _IngredientApiSearch.fromJson(Map<String, dynamic> json) =
_$IngredientApiSearchImpl.fromJson;
@override
List<IngredientApiSearchEntry> get suggestions;
@override
@JsonKey(ignore: true)
_$$IngredientApiSearchImplCopyWith<_$IngredientApiSearchImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@@ -1,55 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ingredient_api.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$IngredientApiSearchDetailsImpl _$$IngredientApiSearchDetailsImplFromJson(
Map<String, dynamic> json) =>
_$IngredientApiSearchDetailsImpl(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
image: json['image'] as String?,
imageThumbnail: json['image_thumbnail'] as String?,
);
Map<String, dynamic> _$$IngredientApiSearchDetailsImplToJson(
_$IngredientApiSearchDetailsImpl instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'image': instance.image,
'image_thumbnail': instance.imageThumbnail,
};
_$IngredientApiSearchEntryImpl _$$IngredientApiSearchEntryImplFromJson(
Map<String, dynamic> json) =>
_$IngredientApiSearchEntryImpl(
value: json['value'] as String,
data: IngredientApiSearchDetails.fromJson(
json['data'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$IngredientApiSearchEntryImplToJson(
_$IngredientApiSearchEntryImpl instance) =>
<String, dynamic>{
'value': instance.value,
'data': instance.data,
};
_$IngredientApiSearchImpl _$$IngredientApiSearchImplFromJson(
Map<String, dynamic> json) =>
_$IngredientApiSearchImpl(
suggestions: (json['suggestions'] as List<dynamic>)
.map((e) =>
IngredientApiSearchEntry.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$IngredientApiSearchImplToJson(
_$IngredientApiSearchImpl instance) =>
<String, dynamic>{
'suggestions': instance.suggestions,
};

View File

@@ -7,10 +7,7 @@ part of 'language.dart';
// **************************************************************************
Language _$LanguageFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'short_name', 'full_name'],
);
$checkKeys(json, requiredKeys: const ['id', 'short_name', 'full_name']);
return Language(
id: (json['id'] as num).toInt(),
shortName: json['short_name'] as String,
@@ -19,7 +16,7 @@ Language _$LanguageFromJson(Map<String, dynamic> json) {
}
Map<String, dynamic> _$LanguageToJson(Language instance) => <String, dynamic>{
'id': instance.id,
'short_name': instance.shortName,
'full_name': instance.fullName,
};
'id': instance.id,
'short_name': instance.shortName,
'full_name': instance.fullName,
};

View File

@@ -7,10 +7,7 @@ part of 'muscle.dart';
// **************************************************************************
Muscle _$MuscleFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'name', 'name_en', 'is_front'],
);
$checkKeys(json, requiredKeys: const ['id', 'name', 'name_en', 'is_front']);
return Muscle(
id: (json['id'] as num).toInt(),
name: json['name'] as String,
@@ -20,8 +17,8 @@ Muscle _$MuscleFromJson(Map<String, dynamic> json) {
}
Map<String, dynamic> _$MuscleToJson(Muscle instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'name_en': instance.nameEn,
'is_front': instance.isFront,
};
'id': instance.id,
'name': instance.name,
'name_en': instance.nameEn,
'is_front': instance.isFront,
};

View File

@@ -42,7 +42,7 @@ class Translation extends Equatable {
@JsonKey(required: true, name: 'created')
final DateTime? created;
@JsonKey(required: true, name: 'exercise_base')
@JsonKey(required: true, name: 'exercise')
late int? exerciseId;
@JsonKey(required: true)
@@ -92,12 +92,12 @@ class Translation extends Equatable {
@override
List<Object?> get props => [
id,
exerciseId,
uuid,
languageId,
created,
name,
description,
];
id,
exerciseId,
uuid,
languageId,
created,
name,
description,
];
}

View File

@@ -9,26 +9,16 @@ part of 'translation.dart';
Translation _$TranslationFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const [
'id',
'uuid',
'language',
'created',
'exercise_base',
'name',
'description'
],
requiredKeys: const ['id', 'uuid', 'language', 'created', 'exercise', 'name', 'description'],
);
return Translation(
id: (json['id'] as num?)?.toInt(),
uuid: json['uuid'] as String?,
created: json['created'] == null
? null
: DateTime.parse(json['created'] as String),
name: json['name'] as String,
description: json['description'] as String,
exerciseId: (json['exercise_base'] as num?)?.toInt(),
)
id: (json['id'] as num?)?.toInt(),
uuid: json['uuid'] as String?,
created: json['created'] == null ? null : DateTime.parse(json['created'] as String),
name: json['name'] as String,
description: json['description'] as String,
exerciseId: (json['exercise'] as num?)?.toInt(),
)
..languageId = (json['language'] as num).toInt()
..notes = (json['notes'] as List<dynamic>)
.map((e) => Comment.fromJson(e as Map<String, dynamic>))
@@ -38,13 +28,12 @@ Translation _$TranslationFromJson(Map<String, dynamic> json) {
.toList();
}
Map<String, dynamic> _$TranslationToJson(Translation instance) =>
<String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'language': instance.languageId,
'created': instance.created?.toIso8601String(),
'exercise_base': instance.exerciseId,
'name': instance.name,
'description': instance.description,
};
Map<String, dynamic> _$TranslationToJson(Translation instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'language': instance.languageId,
'created': instance.created?.toIso8601String(),
'exercise': instance.exerciseId,
'name': instance.name,
'description': instance.description,
};

View File

@@ -7,15 +7,10 @@ part of 'variation.dart';
// **************************************************************************
Variation _$VariationFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id'],
);
return Variation(
id: (json['id'] as num).toInt(),
);
$checkKeys(json, requiredKeys: const ['id']);
return Variation(id: (json['id'] as num).toInt());
}
Map<String, dynamic> _$VariationToJson(Variation instance) => <String, dynamic>{
'id': instance.id,
};
'id': instance.id,
};

View File

@@ -32,8 +32,10 @@ class Video {
@JsonKey(name: 'video', required: true)
final String url;
@JsonKey(name: 'exercise_base', required: true)
final int base;
Uri get uri => Uri.parse(url);
@JsonKey(name: 'exercise', required: true)
final int exerciseId;
@JsonKey(required: true)
final int size;
@@ -62,7 +64,7 @@ class Video {
const Video({
required this.id,
required this.uuid,
required this.base,
required this.exerciseId,
required this.size,
required this.url,
required this.duration,
@@ -76,5 +78,6 @@ class Video {
// Boilerplate
factory Video.fromJson(Map<String, dynamic> json) => _$VideoFromJson(json);
Map<String, dynamic> toJson() => _$VideoToJson(this);
}

View File

@@ -13,7 +13,7 @@ Video _$VideoFromJson(Map<String, dynamic> json) {
'id',
'uuid',
'video',
'exercise_base',
'exercise',
'size',
'duration',
'width',
@@ -21,13 +21,13 @@ Video _$VideoFromJson(Map<String, dynamic> json) {
'codec',
'codec_long',
'license',
'license_author'
'license_author',
],
);
return Video(
id: (json['id'] as num).toInt(),
uuid: json['uuid'] as String,
base: (json['exercise_base'] as num).toInt(),
exerciseId: (json['exercise'] as num).toInt(),
size: (json['size'] as num).toInt(),
url: json['video'] as String,
duration: stringToNum(json['duration'] as String?),
@@ -41,16 +41,16 @@ Video _$VideoFromJson(Map<String, dynamic> json) {
}
Map<String, dynamic> _$VideoToJson(Video instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'video': instance.url,
'exercise_base': instance.base,
'size': instance.size,
'duration': numToString(instance.duration),
'width': instance.width,
'height': instance.height,
'codec': instance.codec,
'codec_long': instance.codecLong,
'license': instance.license,
'license_author': instance.licenseAuthor,
};
'id': instance.id,
'uuid': instance.uuid,
'video': instance.url,
'exercise': instance.exerciseId,
'size': instance.size,
'duration': numToString(instance.duration),
'width': instance.width,
'height': instance.height,
'codec': instance.codec,
'codec_long': instance.codecLong,
'license': instance.license,
'license_author': instance.licenseAuthor,
};

View File

@@ -26,7 +26,7 @@ class Image {
@JsonKey(required: true)
int? id;
@JsonKey(required: true, toJson: toDate)
@JsonKey(required: true, toJson: dateToYYYYMMDD)
late DateTime date;
@JsonKey(required: true, name: 'image')
@@ -49,5 +49,6 @@ class Image {
// Boilerplate
factory Image.fromJson(Map<String, dynamic> json) => _$ImageFromJson(json);
Map<String, dynamic> toJson() => _$ImageToJson(this);
}

View File

@@ -7,10 +7,7 @@ part of 'image.dart';
// **************************************************************************
Image _$ImageFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'date', 'image'],
);
$checkKeys(json, requiredKeys: const ['id', 'date', 'image']);
return Image(
id: (json['id'] as num?)?.toInt(),
date: DateTime.parse(json['date'] as String),
@@ -20,8 +17,8 @@ Image _$ImageFromJson(Map<String, dynamic> json) {
}
Map<String, dynamic> _$ImageToJson(Image instance) => <String, dynamic>{
'id': instance.id,
'date': toDate(instance.date),
'image': instance.url,
'description': instance.description,
};
'id': instance.id,
'date': dateToYYYYMMDD(instance.date),
'image': instance.url,
'description': instance.description,
};

View File

@@ -7,26 +7,22 @@ part of 'measurement_category.dart';
// **************************************************************************
MeasurementCategory _$MeasurementCategoryFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'name', 'unit'],
);
$checkKeys(json, requiredKeys: const ['id', 'name', 'unit']);
return MeasurementCategory(
id: (json['id'] as num?)?.toInt(),
name: json['name'] as String,
unit: json['unit'] as String,
entries: (json['entries'] as List<dynamic>?)
entries:
(json['entries'] as List<dynamic>?)
?.map((e) => MeasurementEntry.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
);
}
Map<String, dynamic> _$MeasurementCategoryToJson(
MeasurementCategory instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'unit': instance.unit,
'entries': MeasurementCategory._nullValue(instance.entries),
};
Map<String, dynamic> _$MeasurementCategoryToJson(MeasurementCategory instance) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'unit': instance.unit,
'entries': MeasurementCategory._nullValue(instance.entries),
};

View File

@@ -12,7 +12,7 @@ class MeasurementEntry extends Equatable {
@JsonKey(required: true)
final int category;
@JsonKey(required: true, toJson: toDate)
@JsonKey(required: true, toJson: dateToYYYYMMDD)
final DateTime date;
@JsonKey(required: true)

View File

@@ -20,11 +20,10 @@ MeasurementEntry _$MeasurementEntryFromJson(Map<String, dynamic> json) {
);
}
Map<String, dynamic> _$MeasurementEntryToJson(MeasurementEntry instance) =>
<String, dynamic>{
'id': instance.id,
'category': instance.category,
'date': toDate(instance.date),
'value': instance.value,
'notes': instance.notes,
};
Map<String, dynamic> _$MeasurementEntryToJson(MeasurementEntry instance) => <String, dynamic>{
'id': instance.id,
'category': instance.category,
'date': dateToYYYYMMDD(instance.date),
'value': instance.value,
'notes': instance.notes,
};

View File

@@ -18,6 +18,7 @@
import 'package:json_annotation/json_annotation.dart';
import 'package:wger/helpers/json.dart';
import 'package:wger/models/nutrition/ingredient_image.dart';
import 'package:wger/models/nutrition/ingredient_image_thumbnails.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';
part 'ingredient.g.dart';
@@ -90,6 +91,8 @@ class Ingredient {
IngredientImage? image;
IngredientImageThumbnails? thumbnails;
Ingredient({
required this.remoteId,
required this.sourceName,
@@ -108,6 +111,7 @@ class Ingredient {
required this.fiber,
required this.sodium,
this.image,
this.thumbnails,
});
// Boilerplate

View File

@@ -25,7 +25,7 @@ Ingredient _$IngredientFromJson(Map<String, dynamic> json) {
'fat',
'fat_saturated',
'fiber',
'sodium'
'sodium',
],
);
return Ingredient(
@@ -48,26 +48,29 @@ Ingredient _$IngredientFromJson(Map<String, dynamic> json) {
image: json['image'] == null
? null
: IngredientImage.fromJson(json['image'] as Map<String, dynamic>),
thumbnails: json['thumbnails'] == null
? null
: IngredientImageThumbnails.fromJson(json['thumbnails'] as Map<String, dynamic>),
);
}
Map<String, dynamic> _$IngredientToJson(Ingredient instance) =>
<String, dynamic>{
'id': instance.id,
'remote_id': instance.remoteId,
'source_name': instance.sourceName,
'source_url': instance.sourceUrl,
'license_object_url': instance.licenseObjectURl,
'code': instance.code,
'name': instance.name,
'created': instance.created.toIso8601String(),
'energy': instance.energy,
'carbohydrates': numToString(instance.carbohydrates),
'carbohydrates_sugar': numToString(instance.carbohydratesSugar),
'protein': numToString(instance.protein),
'fat': numToString(instance.fat),
'fat_saturated': numToString(instance.fatSaturated),
'fiber': numToString(instance.fiber),
'sodium': numToString(instance.sodium),
'image': instance.image,
};
Map<String, dynamic> _$IngredientToJson(Ingredient instance) => <String, dynamic>{
'id': instance.id,
'remote_id': instance.remoteId,
'source_name': instance.sourceName,
'source_url': instance.sourceUrl,
'license_object_url': instance.licenseObjectURl,
'code': instance.code,
'name': instance.name,
'created': instance.created.toIso8601String(),
'energy': instance.energy,
'carbohydrates': numToString(instance.carbohydrates),
'carbohydrates_sugar': numToString(instance.carbohydratesSugar),
'protein': numToString(instance.protein),
'fat': numToString(instance.fat),
'fat_saturated': numToString(instance.fatSaturated),
'fiber': numToString(instance.fiber),
'sodium': numToString(instance.sodium),
'image': instance.image,
'thumbnails': instance.thumbnails,
};

View File

@@ -33,8 +33,8 @@ class IngredientImage {
final int ingredientId;
/// URL of the image on the server
@JsonKey(required: true)
final String image;
@JsonKey(required: true, name: 'image')
final String url;
/// Size in bytes
@JsonKey(required: true)
@@ -70,7 +70,7 @@ class IngredientImage {
required this.id,
required this.uuid,
required this.ingredientId,
required this.image,
required this.url,
required this.size,
required this.licenseId,
required this.author,

View File

@@ -20,14 +20,14 @@ IngredientImage _$IngredientImageFromJson(Map<String, dynamic> json) {
'license_author_url',
'license_title',
'license_object_url',
'license_derivative_source_url'
'license_derivative_source_url',
],
);
return IngredientImage(
id: (json['id'] as num).toInt(),
uuid: json['uuid'] as String,
ingredientId: (json['ingredient_id'] as num).toInt(),
image: json['image'] as String,
url: json['image'] as String,
size: (json['size'] as num).toInt(),
licenseId: (json['license'] as num).toInt(),
author: json['license_author'] as String,
@@ -38,17 +38,16 @@ IngredientImage _$IngredientImageFromJson(Map<String, dynamic> json) {
);
}
Map<String, dynamic> _$IngredientImageToJson(IngredientImage instance) =>
<String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'ingredient_id': instance.ingredientId,
'image': instance.image,
'size': instance.size,
'license': instance.licenseId,
'license_author': instance.author,
'license_author_url': instance.authorUrl,
'license_title': instance.title,
'license_object_url': instance.objectUrl,
'license_derivative_source_url': instance.derivativeSourceUrl,
};
Map<String, dynamic> _$IngredientImageToJson(IngredientImage instance) => <String, dynamic>{
'id': instance.id,
'uuid': instance.uuid,
'ingredient_id': instance.ingredientId,
'image': instance.url,
'size': instance.size,
'license': instance.licenseId,
'license_author': instance.author,
'license_author_url': instance.authorUrl,
'license_title': instance.title,
'license_object_url': instance.objectUrl,
'license_derivative_source_url': instance.derivativeSourceUrl,
};

View File

@@ -0,0 +1,56 @@
/*
* This file is part of wger Workout Manager <https://github.com/wger-project>.
* Copyright (C) 2020, 2021 wger Team
*
* wger Workout Manager is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'package:json_annotation/json_annotation.dart';
part 'ingredient_image_thumbnails.g.dart';
@JsonSerializable()
class IngredientImageThumbnails {
@JsonKey(required: true)
final String small;
@JsonKey(required: true, name: 'small_cropped')
final String smallCropped;
@JsonKey(required: true)
final String medium;
@JsonKey(required: true, name: 'medium_cropped')
final String mediumCropped;
@JsonKey(required: true)
final String large;
@JsonKey(required: true, name: 'large_cropped')
final String largeCropped;
const IngredientImageThumbnails({
required this.small,
required this.smallCropped,
required this.medium,
required this.mediumCropped,
required this.large,
required this.largeCropped,
});
// Boilerplate
factory IngredientImageThumbnails.fromJson(Map<String, dynamic> json) =>
_$IngredientImageThumbnailsFromJson(json);
Map<String, dynamic> toJson() => _$IngredientImageThumbnailsToJson(this);
}

View File

@@ -0,0 +1,39 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'ingredient_image_thumbnails.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
IngredientImageThumbnails _$IngredientImageThumbnailsFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const [
'small',
'small_cropped',
'medium',
'medium_cropped',
'large',
'large_cropped',
],
);
return IngredientImageThumbnails(
small: json['small'] as String,
smallCropped: json['small_cropped'] as String,
medium: json['medium'] as String,
mediumCropped: json['medium_cropped'] as String,
large: json['large'] as String,
largeCropped: json['large_cropped'] as String,
);
}
Map<String, dynamic> _$IngredientImageThumbnailsToJson(IngredientImageThumbnails instance) =>
<String, dynamic>{
'small': instance.small,
'small_cropped': instance.smallCropped,
'medium': instance.medium,
'medium_cropped': instance.mediumCropped,
'large': instance.large,
'large_cropped': instance.largeCropped,
};

View File

@@ -7,22 +7,17 @@ part of 'ingredient_weight_unit.dart';
// **************************************************************************
IngredientWeightUnit _$IngredientWeightUnitFromJson(Map<String, dynamic> json) {
$checkKeys(
json,
requiredKeys: const ['id', 'weight_unit', 'ingredient', 'grams', 'amount'],
);
$checkKeys(json, requiredKeys: const ['id', 'weight_unit', 'ingredient', 'grams', 'amount']);
return IngredientWeightUnit(
id: (json['id'] as num).toInt(),
weightUnit:
WeightUnit.fromJson(json['weight_unit'] as Map<String, dynamic>),
weightUnit: WeightUnit.fromJson(json['weight_unit'] as Map<String, dynamic>),
ingredient: Ingredient.fromJson(json['ingredient'] as Map<String, dynamic>),
grams: (json['grams'] as num).toInt(),
amount: (json['amount'] as num).toDouble(),
);
}
Map<String, dynamic> _$IngredientWeightUnitToJson(
IngredientWeightUnit instance) =>
Map<String, dynamic> _$IngredientWeightUnitToJson(IngredientWeightUnit instance) =>
<String, dynamic>{
'id': instance.id,
'weight_unit': instance.weightUnit,

View File

@@ -40,7 +40,7 @@ class Log {
@JsonKey(required: true, name: 'plan')
String planId;
@JsonKey(required: true)
@JsonKey(required: true, toJson: dateToUtcIso8601)
late DateTime datetime;
String? comment;
@@ -101,8 +101,9 @@ class Log {
NutritionalValues get nutritionalValues {
// This is already done on the server. It might be better to read it from there.
final weight =
weightUnitObj == null ? amount : amount * weightUnitObj!.amount * weightUnitObj!.grams;
final weight = weightUnitObj == null
? amount
: amount * weightUnitObj!.amount * weightUnitObj!.grams;
return ingredient.nutritionalValues / (100 / weight);
}

Some files were not shown because too many files have changed in this diff Show More