diff --git a/.gitignore b/.gitignore index 0a2369f3..6c2ee171 100644 --- a/.gitignore +++ b/.gitignore @@ -33,13 +33,13 @@ build/ pubspec.lock # Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java +example/android_backup/**/gradle-wrapper.jar +example/android_backup/.gradle +example/android_backup/captures/ +example/android_backup/gradlew +example/android_backup/gradlew.bat +example/android_backup/local.properties +example/android_backup/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 diff --git a/README.md b/README.md index 584d48f3..82a22a88 100644 --- a/README.md +++ b/README.md @@ -418,3 +418,37 @@ Take a look at [our fantastic ecosystem](https://github.com/flutter-form-builder ## Thanks to [All contributors](https://github.com/flutter-form-builder-ecosystem/form_builder_validators/graphs/contributors) + + +# API changes draft +During the process of exploration of new possibilities for the new API, I realized that there are +basically three layers of validators: required layer, type layer and the specialized layer. Instead of +repeating the computations for required and type layer for each validator composition, it is possible +to decouple them, avoiding this redundancy and taking benefits from the Dart compiler. + +During the exploration, I implemented some elementary validators that would make it possible, by +composition, to create more sophisticated validators. The recipe is simple, start with a (not)required +validator, add a type validator, and then chain as many specialized validators as you want. + +```dart +// In this example, we build a validator composing a required, with a numeric and then a max. +// The logic result is: required && numeric && max(70) + +final validator = ValidatorBuilder.required(and: >[ + ValidatorBuilder.numeric( + errorText: 'La edad debe ser numérica.', + and: >[ + ValidatorBuilder.max(70), + ]) + ]).validate; +``` + +I needed to change a little bit the approach. Instead of composing directly the validators as +FormFieldValidator's, one level of indirection was necessary, using a ValidatorBuilder instead. +Thus, we first build the validator and then create the validation method calling validate. + +I implemented some examples that are related to some examples from example/main.dart. The new +API examples are implemented in example/api_refactoring_main.dart. I recorded a video showing the +execution of the examples and explaining the new api ideas. + +Please, give me the necessary feedback for me to continue the work. diff --git a/example/.gitignore b/example/.gitignore index a1345d01..f5ca3e76 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -40,6 +40,6 @@ app.*.symbols app.*.map.json # Android Studio will place build artifacts here -/android/app/debug -/android/app/profile -/android/app/release +/android_backup/app/debug +/android_backup/app/profile +/android_backup/app/release diff --git a/example/.metadata b/example/.metadata index 88c82f9a..2d1be89a 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" + revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" channel: "stable" project_type: app @@ -13,23 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - platform: android - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - platform: ios - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: linux + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - platform: macos - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - platform: web - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 - platform: windows - create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 # User provided section diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index bd814c76..7bb2df6b 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip diff --git a/example/lib/basic_examples.dart b/example/lib/basic_examples.dart new file mode 100644 index 00000000..f746a3b0 --- /dev/null +++ b/example/lib/basic_examples.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:form_builder_validators/form_builder_validators.dart' + show Validators; + +/// Basic examples involving only one validator per example +class BasicExamplesPage extends StatelessWidget { + /// Constructs a new instance of the [BasicExamplesPage] class. + const BasicExamplesPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Basic Examples (one validator per example)')), + body: Padding( + padding: const EdgeInsets.all(8), + child: SingleChildScrollView( + child: Column( + children: [ + // Core validators + Text( + 'Core Validators', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), + ), + Align( + alignment: Alignment.centerLeft, + child: Text( + 'Equality Validators', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + ), + ), + TextFormField( + decoration: const InputDecoration( + labelText: + 'Type "I want to delete" to confirm the action.'), + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: Validators.isEqual('I want to delete'), + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Username (should not be "RESERVED")'), + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: Validators.isNotEqual('RESERVED'), + ), + Align( + alignment: Alignment.centerLeft, + child: Text( + 'Required Error Message', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), + ), + ), + TextFormField( + decoration: + const InputDecoration(labelText: 'Input must not be null'), + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (String? input) { + final String? isRequiredMsg = Validators.isRequired()(input); + return isRequiredMsg + ?.toUpperCase() + .replaceFirst('OVERRIDE: ', ''); + }, + ), + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/forms_with_validate_granularlly.dart b/example/lib/forms_with_validate_granularlly.dart new file mode 100644 index 00000000..dad155fe --- /dev/null +++ b/example/lib/forms_with_validate_granularlly.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; + +import 'package:form_builder_validators/form_builder_validators.dart' + show Validators; + +/// Alias for [Validators]. +typedef V = Validators; + +/// {@template forms_with_validate_granularly} +/// Forms which you can use validate granularly to redirect the +/// focus to the first invalid field after submitting. +/// {@endtemplate} +class FormsWithValidateGranularly extends StatelessWidget { + ///{@macro forms_with_validate_granularly} + const FormsWithValidateGranularly({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Validate Granularly')), + body: const _Body(), + ); + } +} + +class _Body extends StatefulWidget { + const _Body(); + + @override + State<_Body> createState() => _BodyState(); +} + +class _BodyState extends State<_Body> { + final GlobalKey _formKey = GlobalKey(); + String? selectedBloodType; + late TextEditingController dateOfBirthController; + + static const List validBloodTypeOptions = [ + 'A+', + 'A-', + 'B+', + 'B-', + 'AB+', + 'AB-', + 'O+', + 'O-', + ]; + @override + void initState() { + super.initState(); + dateOfBirthController = TextEditingController(); + } + + @override + void dispose() { + dateOfBirthController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Form( + key: _formKey, + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 512), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + TextFormField( + key: Key('full name'), + decoration: const InputDecoration( + labelText: 'Full Name', + hintText: 'Enter your full name', + prefixIcon: Icon(Icons.person), + ), + validator: V.isRequired(V.match(RegExp('[A-Z].*'), + matchMsg: (_) => 'The name must start with uppercase')), + textInputAction: TextInputAction.next, + ), + const SizedBox(height: 16), + TextFormField( + key: Key('email'), + decoration: const InputDecoration( + labelText: 'Email', + hintText: 'Enter your email', + prefixIcon: Icon(Icons.email), + ), + validator: V.isRequired(V.email()), + keyboardType: TextInputType.emailAddress, + textInputAction: TextInputAction.next, + ), + const SizedBox(height: 16), + TextFormField( + key: Key('date of birth'), + controller: dateOfBirthController, + decoration: const InputDecoration( + labelText: 'Date of Birth', + hintText: 'YYYY-MM-DD', + prefixIcon: Icon(Icons.calendar_today), + ), + validator: V.isRequired(V.isDateTime(V.isBefore( + DateTime.now(), + isBeforeMsg: (_, __) => 'Date must be in the past.'))), + keyboardType: TextInputType.datetime, + textInputAction: TextInputAction.next, + onTap: () async { + // Add date picker functionality + final DateTime? picked = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now().add(Duration(days: 30)), + ); + if (picked != null) { + dateOfBirthController.text = picked.toString(); + } + }, + ), + const SizedBox(height: 16), + TextFormField( + key: Key('height'), + decoration: const InputDecoration( + labelText: 'Height (m)', + hintText: 'Enter your height in meters', + prefixIcon: Icon(Icons.height), + suffixText: 'm', + ), + validator: V.isRequired(V.isNum(V.between(0.5, 2.5, + betweenMsg: (_, num min, num max, __, ___) => + 'Please enter a realistic height [$min-${max}m]'))), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + ), + const SizedBox(height: 16), + TextFormField( + key: Key('weight'), + decoration: const InputDecoration( + labelText: 'Weight (kg)', + hintText: 'Enter your weight in kilograms', + prefixIcon: Icon(Icons.monitor_weight), + suffixText: 'kg', + ), + validator: V.isOptional(V.isNum(V.between(20, 300, + betweenMsg: (_, num min, num max, ____, _____) => + 'weight must be in [$min, ${max}kg]'))), + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + ), + const SizedBox(height: 16), + TextFormField( + key: Key('phone number'), + decoration: const InputDecoration( + labelText: 'Phone Number', + hintText: 'Enter your phone number', + prefixIcon: Icon(Icons.phone), + ), + validator: V.isRequired(), + keyboardType: TextInputType.phone, + textInputAction: TextInputAction.next, + ), + const SizedBox(height: 16), + DropdownButtonFormField( + key: Key('blood type'), + value: selectedBloodType, + decoration: const InputDecoration( + labelText: 'Blood Type', + prefixIcon: Icon(Icons.bloodtype), + ), + items: validBloodTypeOptions + .map((String e) => + DropdownMenuItem(value: e, child: Text(e))) + .followedBy(>[ + DropdownMenuItem( + value: 'invalid option 1', + child: Text('Invalid option 1'), + ), + DropdownMenuItem( + value: 'invalid option 2', + child: Text('Invalid option 2'), + ), + ]).toList(), + validator: V.isRequired(V.containsElement( + validBloodTypeOptions, + containsElementMsg: (_, List v) => + 'The option must be one of: ${v.join(', ')}.')), + onChanged: (String? value) { + setState(() { + selectedBloodType = value; + }); + }, + ), + const SizedBox(height: 24), + ElevatedButton( + onPressed: () { + final Iterable invalidFields = _formKey + .currentState! + .validateGranularly() + .map((FormFieldState e) => e.widget.key + .toString() + .replaceAll('[<\'', '') + .replaceAll('\'>]', '')); + if (invalidFields.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text('Form submitted successfully!'), + duration: Duration(seconds: 2), + elevation: 5, + behavior: SnackBarBehavior.floating, + width: 350, + backgroundColor: Colors.green, + )); + } else { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: + Text('Invalid fields: ${invalidFields.join(', ')}'), + duration: Duration(seconds: 2), + elevation: 5, + behavior: SnackBarBehavior.floating, + width: 350, + backgroundColor: Colors.green, + )); + } + }, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 32, vertical: 12), + child: Text('Submit'), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/example/lib/home_page.dart b/example/lib/generic_examples.dart similarity index 82% rename from example/lib/home_page.dart rename to example/lib/generic_examples.dart index 2b741701..f527db19 100644 --- a/example/lib/home_page.dart +++ b/example/lib/generic_examples.dart @@ -1,15 +1,19 @@ import 'package:flutter/material.dart'; -import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/form_builder_validators.dart' + show Validators, Validator; + +/// alias for Validators class. +typedef V = Validators; /// Represents the home page of the application. -class HomePage extends StatelessWidget { - /// Constructs a new instance of the [HomePage] class. - const HomePage({super.key}); +class GenericExamplesPage extends StatelessWidget { + /// Constructs a new instance of the [GenericExamplesPage] class. + const GenericExamplesPage({super.key}); @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: const Text('Form Builder Validators')), + appBar: AppBar(title: const Text('Generic Examples')), body: Padding( padding: const EdgeInsets.all(8), child: SingleChildScrollView( @@ -20,30 +24,22 @@ class HomePage extends StatelessWidget { decoration: const InputDecoration(labelText: 'Age'), keyboardType: TextInputType.number, autovalidateMode: AutovalidateMode.always, - validator: - FormBuilderValidators.compose(>[ - /// Makes this field required - FormBuilderValidators.required(), - - /// Ensures the value entered is numeric - with custom error message - FormBuilderValidators.numeric( - errorText: 'La edad debe ser numérica.', - ), - - /// Sets a maximum value of 70 - FormBuilderValidators.max(70), + validator: V.isRequired(V.and(>[ + V.isNum(V.lessThan(70), (_) => 'La edad debe ser numérica.'), /// Include your own custom `FormFieldValidator` function, if you want /// Ensures positive values only. We could also have used `FormBuilderValidators.min( 0)` instead (String? val) { if (val != null) { final int? number = int.tryParse(val); + // todo bug here: if it is not int, it accepts negative + // numbers if (number == null) return null; if (number < 0) return 'We cannot have a negative age'; } return null; } - ]), + ])), ), // Required Validator TextFormField( @@ -51,7 +47,7 @@ class HomePage extends StatelessWidget { labelText: 'Required Field', prefixIcon: Icon(Icons.star), ), - validator: FormBuilderValidators.required(), + validator: V.isRequired(), autofillHints: const [AutofillHints.name], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, @@ -63,11 +59,12 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.numbers), ), keyboardType: TextInputType.number, - validator: FormBuilderValidators.numeric(), + validator: V.isRequired(V.isNum()), autofillHints: const [AutofillHints.oneTimeCode], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + /* TODO implement the email and url validator // Email Validator TextFormField( decoration: const InputDecoration( @@ -75,7 +72,7 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.email), ), keyboardType: TextInputType.emailAddress, - validator: FormBuilderValidators.email(), + validator: v.email(), autofillHints: const [AutofillHints.email], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, @@ -87,18 +84,19 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.link), ), keyboardType: TextInputType.url, - validator: FormBuilderValidators.url(), + validator: v.url(), autofillHints: const [AutofillHints.url], textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + */ // Min Length Validator TextFormField( decoration: const InputDecoration( labelText: 'Min Length Field', prefixIcon: Icon(Icons.text_fields), ), - validator: FormBuilderValidators.minLength(5), + validator: V.isRequired(V.minLength(5)), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -108,7 +106,7 @@ class HomePage extends StatelessWidget { labelText: 'Max Length Field', prefixIcon: Icon(Icons.text_fields), ), - validator: FormBuilderValidators.maxLength(10), + validator: V.isRequired(V.maxLength(10)), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -119,7 +117,7 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_neg_1), ), keyboardType: TextInputType.number, - validator: FormBuilderValidators.min(10), + validator: V.isRequired(V.isNum(V.greaterThan(10))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -130,7 +128,7 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.exposure_plus_1), ), keyboardType: TextInputType.number, - validator: FormBuilderValidators.max(100), + validator: V.isRequired(V.isNum(V.lessThan(100))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -140,10 +138,11 @@ class HomePage extends StatelessWidget { labelText: 'Equal Field', prefixIcon: Icon(Icons.check), ), - validator: FormBuilderValidators.equal('test'), + validator: V.isEqual('test'), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + /* TODO implement contains substring // Contains Validator TextFormField( decoration: const InputDecoration( @@ -154,17 +153,19 @@ class HomePage extends StatelessWidget { textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + + */ // Match Validator TextFormField( decoration: const InputDecoration( labelText: 'Match Pattern', prefixIcon: Icon(Icons.pattern), ), - validator: - FormBuilderValidators.match(RegExp(r'^[a-zA-Z0-9]+$')), + validator: V.isRequired(V.match(RegExp(r'^[a-zA-Z0-9]+$'))), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + /* TODO implement id, uuid, credit cart, and phone number validators // IP Validator TextFormField( decoration: const InputDecoration( @@ -172,17 +173,18 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.computer), ), keyboardType: TextInputType.number, - validator: FormBuilderValidators.ip(), + validator: v.ip(), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + // UUID Validator TextFormField( decoration: const InputDecoration( labelText: 'UUID Field', prefixIcon: Icon(Icons.code), ), - validator: FormBuilderValidators.uuid(), + validator: v.uuid(), textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), @@ -210,6 +212,7 @@ class HomePage extends StatelessWidget { textInputAction: TextInputAction.next, autovalidateMode: AutovalidateMode.always, ), + */ // Password Validator TextFormField( decoration: const InputDecoration( @@ -217,7 +220,7 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.lock), ), obscureText: true, - validator: FormBuilderValidators.password(), + validator: V.isRequired(V.password()), autofillHints: const [AutofillHints.password], textInputAction: TextInputAction.done, autovalidateMode: AutovalidateMode.always, @@ -229,13 +232,8 @@ class HomePage extends StatelessWidget { prefixIcon: Icon(Icons.calendar_today), ), keyboardType: TextInputType.number, - validator: - FormBuilderValidators.compose(>[ - FormBuilderValidators.required(), - FormBuilderValidators.numeric(), - FormBuilderValidators.min(0), - FormBuilderValidators.max(120), - ]), + validator: V.isRequired( + V.isNum(V.and(>[V.between(0, 120)]))), textInputAction: TextInputAction.done, autovalidateMode: AutovalidateMode.always, ), diff --git a/example/lib/main.dart b/example/lib/main.dart index 7fe3f799..d1b7fc35 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; -import 'home_page.dart'; +import 'basic_examples.dart'; +import 'forms_with_validate_granularlly.dart'; +import 'generic_examples.dart'; import 'localization/intl/app_localizations.dart'; import 'override_form_builder_localizations_en.dart'; @@ -20,7 +22,7 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'Form Builder Validators Demo', theme: ThemeData(primarySwatch: Colors.blue), - home: const HomePage(), + home: const _HomePage(), supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: const >[ ...GlobalMaterialLocalizations.delegates, @@ -32,3 +34,49 @@ class MyApp extends StatelessWidget { ); } } + +class _HomePage extends StatelessWidget { + const _HomePage(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => BasicExamplesPage(), + ), + ), + child: Text('Basic Examples')), + SizedBox( + height: 15, + ), + ElevatedButton( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => + GenericExamplesPage(), + ), + ), + child: Text('Generic Examples')), + SizedBox( + height: 15, + ), + ElevatedButton( + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => + FormsWithValidateGranularly(), + ), + ), + child: Text('Validate Granularly')) + ], + ), + ), + ); + } +} diff --git a/example/linux/.gitignore b/example/linux/.gitignore new file mode 100644 index 00000000..d3896c98 --- /dev/null +++ b/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt new file mode 100644 index 00000000..9cb0d1dd --- /dev/null +++ b/example/linux/CMakeLists.txt @@ -0,0 +1,145 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/example/linux/main.cc b/example/linux/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc new file mode 100644 index 00000000..c0530d42 --- /dev/null +++ b/example/linux/my_application.cc @@ -0,0 +1,124 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/example/linux/my_application.h b/example/linux/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/lib/form_builder_validators.dart b/lib/form_builder_validators.dart index 225a2743..1e64121d 100644 --- a/lib/form_builder_validators.dart +++ b/lib/form_builder_validators.dart @@ -31,6 +31,7 @@ export 'localization/intl/messages_tr.dart'; export 'localization/intl/messages_uk.dart'; export 'localization/intl/messages_zh.dart'; export 'localization/l10n.dart'; +// DEPRECATED START export 'src/base_validator.dart'; export 'src/bool/bool.dart'; export 'src/collection/collection.dart'; @@ -38,7 +39,10 @@ export 'src/core/core.dart'; export 'src/datetime/datetime.dart'; export 'src/file/file.dart'; export 'src/finance/finance.dart'; +// DEPRECATED END export 'src/form_builder_validators.dart'; +export 'src/validators/constants.dart'; +// DEPRECATED START export 'src/form_field_validator_extensions.dart'; export 'src/identity/identity.dart'; export 'src/network/network.dart'; @@ -46,3 +50,4 @@ export 'src/numeric/numeric.dart'; export 'src/string/string.dart'; export 'src/translated_validator.dart'; export 'src/usecase/usecase.dart'; +// DEPRECATED END diff --git a/lib/l10n/intl_ar.arb b/lib/l10n/intl_ar.arb index 0db04f6e..01462f20 100644 --- a/lib/l10n/intl_ar.arb +++ b/lib/l10n/intl_ar.arb @@ -1,5 +1,19 @@ { "@@locale": "ar", + "andSeparator": " و ", + "betweenLengthErrorText": "يجب أن يكون طول القيمة بين {min} و {max}، شاملة.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "القيمة المدخلة لا تصلح كرقم بطاقة إئتمانية.", "dateStringErrorText": "هذا الحقل يتطلب تاريخا صالحا.", "emailErrorText": "هذا الحقل يتطلب عنوان بريد إلكتروني صالح.", @@ -86,5 +100,126 @@ "vinErrorText": "يجب أن تكون القيمة رقم VIN صالح.", "languageCodeErrorText": "يجب أن تكون القيمة رمز لغة صالح.", "floatErrorText": "يجب أن تكون القيمة رقم عشري صالح.", - "hexadecimalErrorText": "يجب أن تكون القيمة رقم سداسي عشري صالح." + "hexadecimalErrorText": "يجب أن تكون القيمة رقم سداسي عشري صالح.", + + "betweenNumErrorText": "يجب أن تكون القيمة {minInclusive, select, true{أكبر من أو تساوي} other{أكبر من}} {min} و{maxInclusive, select, true{أقل من أو تساوي} other{أقل من}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "يتطلب هذا الحقل قيمة منطقية صحيحة (صح أو خطأ).", + "dateMustBeAfterErrorText": "يجب أن يكون التاريخ بعد {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "يجب أن يكون التاريخ قبل {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "يجب أن يكون التاريخ بين {minReference} و {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "يتطلب هذا الحقل تاريخًا وتوقيتًا صحيحًا.", + "greaterThanErrorText": "يجب أن تكون القيمة أكبر من {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "يجب أن تكون القيمة أكبر من أو تساوي {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "هذا الحقل اختياري، وإلا، {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "يتطلب هذا الحقل نصًا صحيحًا.", + "lessThanErrorText": "يجب أن تكون القيمة أقل من {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "يجب أن تكون القيمة أقل من أو تساوي {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " أو ", + "transformAndValidateErrorTextV1": "لا يمكن تحويل القيمة.", + "transformAndValidateErrorTextV2": "يجب أن تكون القيمة {transformedResultTypeDescription} صحيحة.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_bg.arb b/lib/l10n/intl_bg.arb index dfa0b669..a368d290 100644 --- a/lib/l10n/intl_bg.arb +++ b/lib/l10n/intl_bg.arb @@ -1,5 +1,19 @@ { "@@locale": "bg", + "andSeparator": " и ", + "betweenLengthErrorText": "Стойността трябва да има дължина между {min} и {max}, включително.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Това поле изисква валиден номер на кредитна карта.", "dateStringErrorText": "Това поле изисква валиден низ за дата.", "emailErrorText": "Това поле изисква валиден имейл адрес.", @@ -86,5 +100,125 @@ "vinErrorText": "Стойността трябва да бъде валиден VIN.", "languageCodeErrorText": "Стойността трябва да бъде валиден езиков код.", "floatErrorText": "Стойността трябва да бъде валидно число с плаваща запетая.", - "hexadecimalErrorText": "Стойността трябва да бъде валиден шестнадесетичен номер." + "hexadecimalErrorText": "Стойността трябва да бъде валиден шестнадесетичен номер.", + "betweenNumErrorText": "Стойността трябва да бъде {minInclusive, select, true{по-голяма или равна на} other{по-голяма от}} {min} и {maxInclusive, select, true{по-малка или равна на} other{по-малка от}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Това поле изисква валидна булева стойност (вярно или грешно).", + "dateMustBeAfterErrorText": "Датата трябва да бъде след {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Датата трябва да бъде преди {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Датата трябва да бъде между {minReference} и {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Това поле изисква валидна дата и час.", + "greaterThanErrorText": "Стойността трябва да бъде по-голяма от {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Стойността трябва да бъде по-голяма или равна на {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Полето е незадължително, в противен случай {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Това поле изисква валиден текстов низ.", + "lessThanErrorText": "Стойността трябва да бъде по-малка от {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Стойността трябва да бъде по-малка или равна на {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " или ", + "transformAndValidateErrorTextV1": "Стойността не може да бъде трансформирана.", + "transformAndValidateErrorTextV2": "Стойността трябва да бъде валиден {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_bn.arb b/lib/l10n/intl_bn.arb index 617db002..3ac53c17 100644 --- a/lib/l10n/intl_bn.arb +++ b/lib/l10n/intl_bn.arb @@ -1,5 +1,19 @@ { "@@locale": "bn", + "andSeparator": " এবং ", + "betweenLengthErrorText": "মানটির দৈর্ঘ্য {min} এবং {max} এর মধ্যে হতে হবে, সহ.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "বৈধ ক্রেডিট কার্ড নম্বর প্রয়োজন।", "dateStringErrorText": "একটি বৈধ তারিখ প্রয়োজন।", "emailErrorText": "একটি বৈধ ইমেল আইডি প্রয়োজন।", @@ -86,5 +100,125 @@ "vinErrorText": "মানটি একটি বৈধ VIN হতে হবে।", "languageCodeErrorText": "মানটি একটি বৈধ ভাষা কোড হতে হবে।", "floatErrorText": "মান একটি বৈধ ভাসমান বিন্দু সংখ্যা হতে হবে।", - "hexadecimalErrorText": "মান একটি বৈধ হেক্সাডেসিমাল সংখ্যা হতে হবে।" + "hexadecimalErrorText": "মান একটি বৈধ হেক্সাডেসিমাল সংখ্যা হতে হবে।", + "betweenNumErrorText": "মান অবশ্যই {minInclusive, select, true{বড় বা সমান হতে হবে} other{বড় হতে হবে}} {min} এবং {maxInclusive, select, true{ছোট বা সমান হতে হবে} other{ছোট হতে হবে}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "এই ক্ষেত্রে একটি বৈধ বুলিয়ান মান (সত্য বা মিথ্যা) প্রয়োজন।", + "dateMustBeAfterErrorText": "তারিখ অবশ্যই {reference} এর পরে হতে হবে।", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "তারিখ অবশ্যই {reference} এর আগে হতে হবে।", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "তারিখ অবশ্যই {minReference} এবং {maxReference} এর মধ্যে হতে হবে।", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "এই ক্ষেত্রে একটি বৈধ তারিখ ও সময় প্রয়োজন।", + "greaterThanErrorText": "মান অবশ্যই {min} এর চেয়ে বড় হতে হবে।", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "মান অবশ্যই {min} এর চেয়ে বড় বা সমান হতে হবে।", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "এই ক্ষেত্রটি ঐচ্ছিক, অন্যথায় {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "এই ক্ষেত্রে একটি বৈধ স্ট্রিং প্রয়োজন।", + "lessThanErrorText": "মান অবশ্যই {max} এর চেয়ে ছোট হতে হবে।", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "মান অবশ্যই {max} এর চেয়ে ছোট বা সমান হতে হবে।", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " বা ", + "transformAndValidateErrorTextV1": "মানটি রূপান্তর করা যাবে না।", + "transformAndValidateErrorTextV2": "মানটি অবশ্যই একটি বৈধ {transformedResultTypeDescription} হতে হবে।", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_bs.arb b/lib/l10n/intl_bs.arb index 4ca3a8ce..56dc721d 100644 --- a/lib/l10n/intl_bs.arb +++ b/lib/l10n/intl_bs.arb @@ -1,5 +1,19 @@ { "@@locale": "bs", + "andSeparator": " i ", + "betweenLengthErrorText": "Vrijednost mora imati dužinu između {min} i {max}, uključivo.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Unesite validan broj kreditne kartice.", "dateStringErrorText": "Unesite validan datum.", "emailErrorText": "Unesite validnu e-mail adresu.", @@ -86,5 +100,125 @@ "vinErrorText": "Vrijednost mora biti ispravan VIN.", "languageCodeErrorText": "Vrijednost mora biti ispravan kod jezika.", "floatErrorText": "Vrijednost mora biti ispravan broj s pomičnim zarezom.", - "hexadecimalErrorText": "Vrijednost mora biti ispravan heksadecimalni broj." + "hexadecimalErrorText": "Vrijednost mora biti ispravan heksadecimalni broj.", + "betweenNumErrorText": "Vrijednost mora biti {minInclusive, select, true{veća ili jednaka} other{veća od}} {min} i {maxInclusive, select, true{manja ili jednaka} other{manja od}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Ovo polje zahtijeva validnu boolean vrijednost (true ili false).", + "dateMustBeAfterErrorText": "Datum mora biti nakon {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datum mora biti prije {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datum mora biti između {minReference} i {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Ovo polje zahtijeva validan datum i vrijeme.", + "greaterThanErrorText": "Vrijednost mora biti veća od {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Vrijednost mora biti veća ili jednaka {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Polje je opcionalno, u suprotnom, {nextErrorMessage}'", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Ovo polje zahtijeva validan tekst.", + "lessThanErrorText": "Vrijednost mora biti manja od {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Vrijednost mora biti manja ili jednaka {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ili ", + "transformAndValidateErrorTextV1": "Vrijednost se ne može transformisati.", + "transformAndValidateErrorTextV2": "Vrijednost mora biti validan {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index a173dba7..e6c8c1cc 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1,5 +1,19 @@ { "@@locale": "ca", + "andSeparator": " i ", + "betweenLengthErrorText": "El valor ha de tenir una longitud entre {min} i {max}, inclosos.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Aquest camp requereix un número de targeta de crèdit vàlid.", "dateStringErrorText": "Aquest camp requereix una cadena de data vàlida.", "emailErrorText": "Aquest camp requereix una adreça de correu electrònic vàlida.", @@ -86,5 +100,125 @@ "vinErrorText": "El valor ha de ser un VIN vàlid.", "languageCodeErrorText": "El valor ha de ser un codi de llengua vàlid.", "floatErrorText": "El valor ha de ser un nombre de coma flotant vàlid.", - "hexadecimalErrorText": "El valor ha de ser un nombre hexadecimal vàlid." + "hexadecimalErrorText": "El valor ha de ser un nombre hexadecimal vàlid.", + "betweenNumErrorText": "El valor ha de ser {minInclusive, select, true{més gran o igual que} other{més gran que}} {min} i {maxInclusive, select, true{més petit o igual que} other{més petit que}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Aquest camp requereix un valor booleà vàlid (true o false).", + "dateMustBeAfterErrorText": "La data ha de ser posterior a {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "La data ha de ser anterior a {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "La data ha d'estar entre {minReference} i {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Aquest camp requereix una data i hora vàlides.", + "greaterThanErrorText": "El valor ha de ser més gran que {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "El valor ha de ser més gran o igual que {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "El camp és opcional, en cas contrari, {nextErrorMessage}'", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Aquest camp requereix una cadena de text vàlida.", + "lessThanErrorText": "El valor ha de ser més petit que {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "El valor ha de ser més petit o igual que {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " o ", + "transformAndValidateErrorTextV1": "El valor no es pot transformar.", + "transformAndValidateErrorTextV2": "El valor ha de ser un {transformedResultTypeDescription} vàlid.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index d748d5d0..a0782cc1 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1,5 +1,19 @@ { "@@locale": "cs", + "andSeparator": " a ", + "betweenLengthErrorText": "Hodnota musí mít délku mezi {min} a {max} včetně.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Pole vyžaduje platné číslo kreditní karty.", "dateStringErrorText": "Pole vyžaduje platný zápis data.", "emailErrorText": "Pole vyžaduje platnou e-mailovou adresu.", @@ -86,5 +100,125 @@ "vinErrorText": "Hodnota musí být platný VIN.", "languageCodeErrorText": "Hodnota musí být platný kód jazyka.", "floatErrorText": "Hodnota musí být platné desetinné číslo.", - "hexadecimalErrorText": "Hodnota musí být platné šestnáctkové číslo." + "hexadecimalErrorText": "Hodnota musí být platné šestnáctkové číslo.", + "betweenNumErrorText": "Hodnota musí být {minInclusive, select, true{větší nebo rovna} other{větší než}} {min} a {maxInclusive, select, true{menší nebo rovna} other{menší než}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Toto pole vyžaduje platnou logickou hodnotu (true nebo false).", + "dateMustBeAfterErrorText": "Datum musí být po {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datum musí být před {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datum musí být mezi {minReference} a {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Toto pole vyžaduje platné datum a čas.", + "greaterThanErrorText": "Hodnota musí být větší než {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Hodnota musí být větší nebo rovna {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Pole je volitelné, jinak {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Toto pole vyžaduje platný řetězec.", + "lessThanErrorText": "Hodnota musí být menší než {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Hodnota musí být menší nebo rovna {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " nebo ", + "transformAndValidateErrorTextV1": "Hodnotu nelze transformovat.", + "transformAndValidateErrorTextV2": "Hodnota musí být platný {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_da.arb b/lib/l10n/intl_da.arb index 7749fdd7..81b4738e 100644 --- a/lib/l10n/intl_da.arb +++ b/lib/l10n/intl_da.arb @@ -1,5 +1,19 @@ { "@@locale": "da", + "andSeparator": " og ", + "betweenLengthErrorText": "Værdien skal have en længde mellem {min} og {max}, inklusive.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Dette felt kræver et gyldigt kreditkort nummer.", "dateStringErrorText": "Dette felt kræver en gyldig dato.", "emailErrorText": "Dette felt kræver en gyldig e-mail adresse.", @@ -86,5 +100,125 @@ "vinErrorText": "Værdien skal være en gyldig VIN.", "languageCodeErrorText": "Værdien skal være en gyldig sprogkode.", "floatErrorText": "Værdien skal være et gyldigt flydende punkt nummer.", - "hexadecimalErrorText": "Værdien skal være et gyldigt hexadecimalt nummer." + "hexadecimalErrorText": "Værdien skal være et gyldigt hexadecimalt nummer.", + "betweenNumErrorText": "Værdien skal være {minInclusive, select, true{større end eller lig med} other{større end}} {min} og {maxInclusive, select, true{mindre end eller lig med} other{mindre end}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Dette felt kræver en gyldig boolean (sand eller falsk).", + "dateMustBeAfterErrorText": "Datoen skal være efter {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datoen skal være før {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datoen skal være mellem {minReference} og {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Dette felt kræver en gyldig dato og tidspunkt.", + "greaterThanErrorText": "Værdien skal være større end {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Værdien skal være større end eller lig med {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Feltet er valgfrit, ellers {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Dette felt kræver en gyldig tekststreng.", + "lessThanErrorText": "Værdien skal være mindre end {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Værdien skal være mindre end eller lig med {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " eller ", + "transformAndValidateErrorTextV1": "Værdien kan ikke transformeres.", + "transformAndValidateErrorTextV2": "Værdien skal være en gyldig {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 76b4b4dd..af1fbd34 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1,5 +1,19 @@ { "@@locale": "de", + "andSeparator": " und ", + "betweenLengthErrorText": "Der Wert muss eine Länge zwischen {min} und {max} haben, einschließlich.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Für dieses Feld ist eine gültige Kreditkartennummer erforderlich.", "dateStringErrorText": "Dieses Feld erfordert ein gültiges Datum.", "emailErrorText": "Für dieses Feld ist eine gültige E-Mail-Adresse erforderlich.", @@ -86,5 +100,125 @@ "vinErrorText": "Der Wert muss eine gültige Fahrzeug-Identifizierungsnummer (VIN) sein.", "languageCodeErrorText": "Der Wert muss ein gültiger Sprachcode sein.", "floatErrorText": "Der Wert muss eine gültige Fließkommazahl sein.", - "hexadecimalErrorText": "Der Wert muss eine gültige hexadezimale Zahl sein." + "hexadecimalErrorText": "Der Wert muss eine gültige hexadezimale Zahl sein.", + "betweenNumErrorText": "Der Wert muss {minInclusive, select, true{größer oder gleich} other{größer als}} {min} und {maxInclusive, select, true{kleiner oder gleich} other{kleiner als}} {max} sein", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Dieses Feld erfordert einen gültigen booleschen Wert (true oder false).", + "dateMustBeAfterErrorText": "Das Datum muss nach {reference} liegen.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Das Datum muss vor {reference} liegen.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Das Datum muss zwischen {minReference} und {maxReference} liegen.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Dieses Feld erfordert eine gültige Datums- und Zeitangabe.", + "greaterThanErrorText": "Der Wert muss größer als {min} sein.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Der Wert muss größer oder gleich {min} sein.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Das Feld ist optional, andernfalls {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Dieses Feld erfordert eine gültige Zeichenkette.", + "lessThanErrorText": "Der Wert muss kleiner als {max} sein.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Der Wert muss kleiner oder gleich {max} sein.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " oder ", + "transformAndValidateErrorTextV1": "Der Wert kann nicht transformiert werden.", + "transformAndValidateErrorTextV2": "Der Wert muss ein gültiger {transformedResultTypeDescription} sein.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_el.arb b/lib/l10n/intl_el.arb index 6d134dca..1b05ead2 100644 --- a/lib/l10n/intl_el.arb +++ b/lib/l10n/intl_el.arb @@ -1,5 +1,19 @@ { "@@locale": "el", + "andSeparator": " και ", + "betweenLengthErrorText": "Η τιμή πρέπει να έχει μήκος μεταξύ {min} και {max}, συμπεριλαμβανομένων.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Η τιμή πρέπει να είναι έγκυρη πιστωτική κάρτα.", "dateStringErrorText": "Η τιμή πρέπει να είναι έγκυρη ημερομηνία.", "emailErrorText": "Το πεδίο πρέπει να έχει μία έγκυρη διεύθυνση email.", @@ -86,5 +100,125 @@ "vinErrorText": "Η τιμή πρέπει να είναι ένας έγκυρος αριθμός VIN.", "languageCodeErrorText": "Η τιμή πρέπει να είναι ένας έγκυρος κωδικός γλώσσας.", "floatErrorText": "Η τιμή πρέπει να είναι έγκυρος δεκαδικός αριθμός κινητής υποδιαστολής.", - "hexadecimalErrorText": "Η τιμή πρέπει να είναι έγκυρος δεκαεξαδικός αριθμός." + "hexadecimalErrorText": "Η τιμή πρέπει να είναι έγκυρος δεκαεξαδικός αριθμός.", + "betweenNumErrorText": "Η τιμή πρέπει να είναι {minInclusive, select, true{μεγαλύτερη ή ίση με} other{μεγαλύτερη από}} {min} και {maxInclusive, select, true{μικρότερη ή ίση με} other{μικρότερη από}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Αυτό το πεδίο απαιτεί μια έγκυρη λογική τιμή (true ή false).", + "dateMustBeAfterErrorText": "Η ημερομηνία πρέπει να είναι μετά τις {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Η ημερομηνία πρέπει να είναι πριν τις {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Η ημερομηνία πρέπει να είναι μεταξύ {minReference} και {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Αυτό το πεδίο απαιτεί μια έγκυρη ημερομηνία και ώρα.", + "greaterThanErrorText": "Η τιμή πρέπει να είναι μεγαλύτερη από {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Η τιμή πρέπει να είναι μεγαλύτερη ή ίση με {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Το πεδίο είναι προαιρετικό, διαφορετικά, {nextErrorMessage}'", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Αυτό το πεδίο απαιτεί μια έγκυρη συμβολοσειρά.", + "lessThanErrorText": "Η τιμή πρέπει να είναι μικρότερη από {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Η τιμή πρέπει να είναι μικρότερη ή ίση με {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ή ", + "transformAndValidateErrorTextV1": "Η τιμή δεν μπορεί να μετασχηματιστεί.", + "transformAndValidateErrorTextV2": "Η τιμή πρέπει να είναι έγκυρη {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index af5e7a0f..b77fd412 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1,7 +1,84 @@ { "@@locale": "en", + "andSeparator": " and ", + "betweenLengthErrorText": "Value must have a length that is between {min} and {max}, inclusive.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, + "betweenNumErrorText": "Value must be {minInclusive, select, true{greater than or equal to} other{greater than}} {min} and {maxInclusive, select, true{less than or equal to} other{less than}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "This field requires a valid boolean (true or false).", "creditCardErrorText": "This field requires a valid credit card number.", + "dateMustBeAfterErrorText": "Date must be after {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Date must be before {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Date must be between {minReference} and {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, "dateStringErrorText": "This field requires a valid date string.", + "dateTimeErrorText": "This field requires a valid datetime.", "emailErrorText": "This field requires a valid email address.", "equalErrorText": "This field value must be equal to {value}.", "@equalErrorText": { @@ -21,8 +98,54 @@ } } }, + "greaterThanErrorText": "Value must be greater than {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Value must be greater than or equal to {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, "integerErrorText": "This field requires a valid integer.", "ipErrorText": "This field requires a valid IP.", + "isOptionalErrorText": "The field is optional, otherwise, {nextErrorMessage}'", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "This field requires a valid string.", + "lessThanErrorText": "Value must be less than {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Value must be less than or equal to {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, "matchErrorText": "Value does not match pattern.", "maxErrorText": "Value must be less than or equal to {max}.", "@maxErrorText": { @@ -88,7 +211,18 @@ } }, "numericErrorText": "Value must be numeric.", + "orSeparator": " or ", "requiredErrorText": "This field cannot be empty.", + "transformAndValidateErrorTextV1": "Value is not able to be transformed.", + "transformAndValidateErrorTextV2": "Value must be a valid {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + }, "urlErrorText": "This field requires a valid URL address.", "phoneErrorText": "This field requires a valid phone number.", "creditCardExpirationDateErrorText": "This field requires a valid expiration date.", diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index 42abb12a..a62ea60c 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1,5 +1,19 @@ { "@@locale": "es", + "andSeparator": " y ", + "betweenLengthErrorText": "El valor debe tener una longitud entre {min} y {max}, inclusive.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Este campo requiere un número de tarjeta de crédito válido.", "dateStringErrorText": "Este campo requiere una cadena de fecha válida.", "emailErrorText": "Este campo requiere una dirección de correo electrónico válida.", @@ -86,5 +100,125 @@ "vinErrorText": "El valor debe ser un VIN válido.", "languageCodeErrorText": "El valor debe ser un código de idioma válido.", "floatErrorText": "El valor debe ser un número de punto flotante válido.", - "hexadecimalErrorText": "El valor debe ser un número hexadecimal válido." + "hexadecimalErrorText": "El valor debe ser un número hexadecimal válido.", + "betweenNumErrorText": "El valor debe ser {minInclusive, select, true{mayor o igual que} other{mayor que}} {min} y {maxInclusive, select, true{menor o igual que} other{menor que}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Este campo requiere un valor booleano válido (verdadero o falso).", + "dateMustBeAfterErrorText": "La fecha debe ser posterior a {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "La fecha debe ser anterior a {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "La fecha debe estar entre {minReference} y {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Este campo requiere una fecha y hora válidas.", + "greaterThanErrorText": "El valor debe ser mayor que {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "El valor debe ser mayor o igual que {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "El campo es opcional, de lo contrario, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Este campo requiere una cadena de texto válida.", + "lessThanErrorText": "El valor debe ser menor que {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "El valor debe ser menor o igual que {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " o ", + "transformAndValidateErrorTextV1": "El valor no puede ser transformado.", + "transformAndValidateErrorTextV2": "El valor debe ser un {transformedResultTypeDescription} válido.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_et.arb b/lib/l10n/intl_et.arb index a59b5345..173e1b38 100644 --- a/lib/l10n/intl_et.arb +++ b/lib/l10n/intl_et.arb @@ -1,5 +1,19 @@ { "@@locale": "et", + "andSeparator": " ja ", + "betweenLengthErrorText": "Väärtuse pikkus peab olema vahemikus {min} kuni {max}, kaasa arvatud.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Sellele väljale tuleb sisestada korrektne krediitkaardi number.", "dateStringErrorText": "Sellele väljale tuleb sisestada korrektne kuupäev.", "emailErrorText": "See väli nõuab kehtivat e-posti aadressi.", @@ -86,5 +100,125 @@ "vinErrorText": "Väärtus peab olema kehtiv VIN.", "languageCodeErrorText": "Väärtus peab olema kehtiv keelekood.", "floatErrorText": "Väärtus peab olema kehtiv ujukomaarv.", - "hexadecimalErrorText": "Väärtus peab olema kehtiv kuueteistkümnendkohtade süsteemi arv." + "hexadecimalErrorText": "Väärtus peab olema kehtiv kuueteistkümnendkohtade süsteemi arv.", + "betweenNumErrorText": "Väärtus peab olema {minInclusive, select, true{suurem või võrdne kui} other{suurem kui}} {min} ja {maxInclusive, select, true{väiksem või võrdne kui} other{väiksem kui}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "See väli nõuab kehtivat tõeväärtust (tõene või väär).", + "dateMustBeAfterErrorText": "Kuupäev peab olema pärast {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Kuupäev peab olema enne {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Kuupäev peab olema vahemikus {minReference} kuni {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "See väli nõuab kehtivat kuupäeva ja kellaaega.", + "greaterThanErrorText": "Väärtus peab olema suurem kui {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Väärtus peab olema suurem või võrdne kui {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Väli on valikuline, vastasel juhul {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "See väli nõuab kehtivat teksti.", + "lessThanErrorText": "Väärtus peab olema väiksem kui {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Väärtus peab olema väiksem või võrdne kui {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " või ", + "transformAndValidateErrorTextV1": "Väärtust ei ole võimalik teisendada.", + "transformAndValidateErrorTextV2": "Väärtus peab olema kehtiv {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_fa.arb b/lib/l10n/intl_fa.arb index b18340ef..11d1081e 100644 --- a/lib/l10n/intl_fa.arb +++ b/lib/l10n/intl_fa.arb @@ -1,5 +1,19 @@ { "@@locale": "fa", + "andSeparator": " و ", + "betweenLengthErrorText": "مقدار باید طولی بین {min} و {max}، به صورت شامل داشته باشد.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "این ورودی به شماره کارت اعتباری معتبر نیاز دارد.", "dateStringErrorText": "این ورودی به یک تاریخ معتبر نیاز دارد.", "emailErrorText": "این ورودی به یک آدرس ایمیل معتبر نیاز دارد.", @@ -86,5 +100,125 @@ "vinErrorText": "مقدار باید یک شماره VIN معتبر باشد.", "languageCodeErrorText": "مقدار باید یک کد زبان معتبر باشد.", "floatErrorText": "مقدار باید یک عدد اعشاری معتبر باشد.", - "hexadecimalErrorText": "مقدار باید یک عدد هگزادسیمال معتبر باشد." + "hexadecimalErrorText": "مقدار باید یک عدد هگزادسیمال معتبر باشد.", + "betweenNumErrorText": "مقدار باید {minInclusive, select, true{بزرگتر یا مساوی با} other{بزرگتر از}} {min} و {maxInclusive, select, true{کوچکتر یا مساوی با} other{کوچکتر از}} {max} باشد", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "این فیلد نیاز به یک مقدار منطقی معتبر دارد (درست یا نادرست)", + "dateMustBeAfterErrorText": "تاریخ باید بعد از {reference} باشد", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "تاریخ باید قبل از {reference} باشد", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "تاریخ باید بین {minReference} و {maxReference} باشد", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "این فیلد نیاز به یک تاریخ و زمان معتبر دارد", + "greaterThanErrorText": "مقدار باید بزرگتر از {min} باشد", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "مقدار باید بزرگتر یا مساوی با {min} باشد", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "این فیلد اختیاری است، در غیر این صورت، {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "این فیلد نیاز به یک رشته معتبر دارد", + "lessThanErrorText": "مقدار باید کمتر از {max} باشد", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "مقدار باید کمتر یا مساوی با {max} باشد", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " یا ", + "transformAndValidateErrorTextV1": "مقدار قابل تبدیل نیست", + "transformAndValidateErrorTextV2": "مقدار باید یک {transformedResultTypeDescription} معتبر باشد", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_fi.arb b/lib/l10n/intl_fi.arb index 21597268..b96245ef 100644 --- a/lib/l10n/intl_fi.arb +++ b/lib/l10n/intl_fi.arb @@ -1,5 +1,19 @@ { "@@locale": "fi", + "andSeparator": " ja ", + "betweenLengthErrorText": "Arvon pituuden on oltava välillä {min} ja {max}, mukaan lukien.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Luottokortin numero on oltava oikeassa muodossa.", "dateStringErrorText": "Päivämäärä ei ole oikeassa muodossa.", "emailErrorText": "Sähköpostiosoitteen muoto ei ole oikea.", @@ -86,5 +100,125 @@ "vinErrorText": "Arvon on oltava kelvollinen VIN.", "languageCodeErrorText": "Arvon on oltava kelvollinen kielikoodi.", "floatErrorText": "Arvon on oltava kelvollinen liukuluku.", - "hexadecimalErrorText": "Arvon on oltava kelvollinen heksadesimaaliluku." + "hexadecimalErrorText": "Arvon on oltava kelvollinen heksadesimaaliluku.", + "betweenNumErrorText": "Arvon täytyy olla {minInclusive, select, true{suurempi tai yhtä suuri kuin} other{suurempi kuin}} {min} ja {maxInclusive, select, true{pienempi tai yhtä suuri kuin} other{pienempi kuin}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Tämä kenttä vaatii kelvollisen totuusarvon (tosi tai epätosi)", + "dateMustBeAfterErrorText": "Päivämäärän täytyy olla {reference} jälkeen", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Päivämäärän täytyy olla ennen {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Päivämäärän täytyy olla välillä {minReference} ja {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Tämä kenttä vaatii kelvollisen päivämäärän ja ajan", + "greaterThanErrorText": "Arvon täytyy olla suurempi kuin {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Arvon täytyy olla suurempi tai yhtä suuri kuin {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Kenttä on valinnainen, muuten {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Tämä kenttä vaatii kelvollisen merkkijonon", + "lessThanErrorText": "Arvon täytyy olla pienempi kuin {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Arvon täytyy olla pienempi tai yhtä suuri kuin {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " tai ", + "transformAndValidateErrorTextV1": "Arvoa ei voida muuntaa", + "transformAndValidateErrorTextV2": "Arvon täytyy olla kelvollinen {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 5d7b99c1..5dc1fc09 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1,5 +1,19 @@ { "@@locale": "fr", + "andSeparator": " et ", + "betweenLengthErrorText": "La valeur doit avoir une longueur comprise entre {min} et {max}, inclus.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Ce champ nécessite un numéro de carte de crédit valide.", "dateStringErrorText": "Ce champ nécessite une chaîne de date valide.", "emailErrorText": "Ce champ nécessite une adresse e-mail valide.", @@ -86,5 +100,139 @@ "vinErrorText": "La valeur doit être un numéro VIN valide.", "languageCodeErrorText": "La valeur doit être un code de langue valide.", "floatErrorText": "La valeur doit être un nombre à virgule flottante valide.", - "hexadecimalErrorText": "La valeur doit être un nombre hexadécimal valide." + "hexadecimalErrorText": "La valeur doit être un nombre hexadécimal valide.", +"betweenNumErrorText": "La valeur doit être {minInclusive, select, true{supérieure ou égale à} other{supérieure à}} {min} et {maxInclusive, select, true{inférieure ou égale à} other{inférieure à}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + + "booleanErrorText": "Ce champ nécessite une valeur booléenne valide (vrai ou faux).", + + "dateMustBeAfterErrorText": "La date doit être après {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + + "dateMustBeBeforeErrorText": "La date doit être avant {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + + "dateMustBeBetweenErrorText": "La date doit être comprise entre {minReference} et {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + + "dateTimeErrorText": "Ce champ nécessite une date et une heure valides.", + + "greaterThanErrorText": "La valeur doit être supérieure à {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + + "greaterThanOrEqualToErrorText": "La valeur doit être supérieure ou égale à {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + + "isOptionalErrorText": "Le champ est optionnel, sinon, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + + "isStringErrorText": "Ce champ nécessite une chaîne de caractères valide.", + + "lessThanErrorText": "La valeur doit être inférieure à {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + + "lessThanOrEqualToErrorText": "La valeur doit être inférieure ou égale à {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + + "orSeparator": " ou ", + + "transformAndValidateErrorTextV1": "La valeur ne peut pas être transformée.", + + "transformAndValidateErrorTextV2": "La valeur doit être un(e) {transformedResultTypeDescription} valide.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_he.arb b/lib/l10n/intl_he.arb index 1e5b3cab..6834b6af 100644 --- a/lib/l10n/intl_he.arb +++ b/lib/l10n/intl_he.arb @@ -1,5 +1,19 @@ { "@@locale": "he", + "andSeparator": " ו ", + "betweenLengthErrorText": "הערך חייב להיות באורך שבין {min} ל-{max}, כולל.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "שדה זה דורש מספר כרטיס אשראי תקין.", "dateStringErrorText": "שדה זה דורש מחרוזת תאריך תקינה.", "emailErrorText": "שדה זה דורש כתובת דוא\"ל תקינה.", @@ -86,5 +100,125 @@ "vinErrorText": "הערך חייב להיות מספר VIN חוקי.", "languageCodeErrorText": "הערך חייב להיות קוד שפה חוקי.", "floatErrorText": "הערך חייב להיות מספר נקודה צפה חוקי.", - "hexadecimalErrorText": "הערך חייב להיות מספר הקסדצימלי חוקי." + "hexadecimalErrorText": "הערך חייב להיות מספר הקסדצימלי חוקי.", + "betweenNumErrorText": "הערך חייב להיות {minInclusive, select, true{גדול או שווה ל} other{גדול מ}} {min} ו{maxInclusive, select, true{קטן או שווה ל} other{קטן מ}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "שדה זה דורש ערך בוליאני תקין (אמת או שקר)", + "dateMustBeAfterErrorText": "התאריך חייב להיות אחרי {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "התאריך חייב להיות לפני {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "התאריך חייב להיות בין {minReference} לבין {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "שדה זה דורש תאריך ושעה תקינים", + "greaterThanErrorText": "הערך חייב להיות גדול מ-{min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "הערך חייב להיות גדול או שווה ל-{min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "השדה הוא אופציונלי, אחרת, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "שדה זה דורש מחרוזת תקינה", + "lessThanErrorText": "הערך חייב להיות קטן מ-{max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "הערך חייב להיות קטן או שווה ל-{max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " או ", + "transformAndValidateErrorTextV1": "לא ניתן להמיר את הערך", + "transformAndValidateErrorTextV2": "הערך חייב להיות {transformedResultTypeDescription} תקין", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_hi.arb b/lib/l10n/intl_hi.arb index a370361d..a8117b7b 100644 --- a/lib/l10n/intl_hi.arb +++ b/lib/l10n/intl_hi.arb @@ -1,5 +1,19 @@ { "@@locale": "hi", + "andSeparator": " और ", + "betweenLengthErrorText": "मान की लंबाई {min} और {max} के बीच होनी चाहिए, सहित.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "इस फ़ील्ड में एक मान्य क्रेडिट कार्ड नंबर की आवश्यकता है।", "dateStringErrorText": "इस फ़ील्ड में एक मान्य तिथि स्ट्रिंग की आवश्यकता है।", "emailErrorText": "इस फ़ील्ड में एक मान्य ईमेल पता की आवश्यकता है।", @@ -86,5 +100,125 @@ "vinErrorText": "मान मान्य VIN होना चाहिए।", "languageCodeErrorText": "मान मान्य भाषा कोड होना चाहिए।", "floatErrorText": "मान्य फ़्लोटिंग पॉइंट नंबर होना चाहिए।", - "hexadecimalErrorText": "मान्य हेक्साडेसिमल नंबर होना चाहिए।" + "hexadecimalErrorText": "मान्य हेक्साडेसिमल नंबर होना चाहिए।", + "betweenNumErrorText": "मान {minInclusive, select, true{से बड़ा या बराबर} other{से बड़ा}} {min} और {maxInclusive, select, true{से छोटा या बराबर} other{से छोटा}} {max} होना चाहिए", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "इस फ़ील्ड में एक मान्य बूलियन (सत्य या असत्य) की आवश्यकता है", + "dateMustBeAfterErrorText": "दिनांक {reference} के बाद का होना चाहिए", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "दिनांक {reference} से पहले का होना चाहिए", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "दिनांक {minReference} और {maxReference} के बीच होना चाहिए", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "इस फ़ील्ड में एक मान्य दिनांक और समय की आवश्यकता है", + "greaterThanErrorText": "मान {min} से बड़ा होना चाहिए", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "मान {min} से बड़ा या बराबर होना चाहिए", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "यह फ़ील्ड वैकल्पिक है, अन्यथा {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "इस फ़ील्ड में एक मान्य स्ट्रिंग की आवश्यकता है", + "lessThanErrorText": "मान {max} से छोटा होना चाहिए", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "मान {max} से छोटा या बराबर होना चाहिए", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " या ", + "transformAndValidateErrorTextV1": "मान को परिवर्तित नहीं किया जा सकता", + "transformAndValidateErrorTextV2": "मान एक मान्य {transformedResultTypeDescription} होना चाहिए", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_hr.arb b/lib/l10n/intl_hr.arb index 73d88f0a..52083af8 100644 --- a/lib/l10n/intl_hr.arb +++ b/lib/l10n/intl_hr.arb @@ -1,5 +1,19 @@ { "@@locale": "hr", + "andSeparator": " i ", + "betweenLengthErrorText": "Vrijednost mora imati duljinu između {min} i {max}, uključivo.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Unesite validan broj kreditne kartice.", "dateStringErrorText": "Unesite validan datum.", "emailErrorText": "Unesite validnu e-mail adresu.", @@ -86,5 +100,125 @@ "vinErrorText": "Vrijednost mora biti važeći VIN.", "languageCodeErrorText": "Vrijednost mora biti važeći jezični kod.", "floatErrorText": "Vrijednost mora biti valjani broj s pomičnim zarezom.", - "hexadecimalErrorText": "Vrijednost mora biti valjani heksadecimalni broj." + "hexadecimalErrorText": "Vrijednost mora biti valjani heksadecimalni broj.", + "betweenNumErrorText": "Vrijednost mora biti {minInclusive, select, true{veća ili jednaka} other{veća od}} {min} i {maxInclusive, select, true{manja ili jednaka} other{manja od}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Ovo polje zahtijeva valjanu boolean vrijednost (true ili false).", + "dateMustBeAfterErrorText": "Datum mora biti nakon {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datum mora biti prije {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datum mora biti između {minReference} i {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Ovo polje zahtijeva valjani datum i vrijeme.", + "greaterThanErrorText": "Vrijednost mora biti veća od {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Vrijednost mora biti veća ili jednaka {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Polje je neobavezno, u suprotnom, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Ovo polje zahtijeva valjani tekst.", + "lessThanErrorText": "Vrijednost mora biti manja od {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Vrijednost mora biti manja ili jednaka {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ili ", + "transformAndValidateErrorTextV1": "Vrijednost nije moguće transformirati.", + "transformAndValidateErrorTextV2": "Vrijednost mora biti valjani {transformedResultTypeDescription}.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_hu.arb b/lib/l10n/intl_hu.arb index 1f658910..ed565531 100644 --- a/lib/l10n/intl_hu.arb +++ b/lib/l10n/intl_hu.arb @@ -1,5 +1,19 @@ { "@@locale": "hu", + "andSeparator": " és ", + "betweenLengthErrorText": "Az értéknek {min} és {max} közötti hosszúságúnak kell lennie, beleértve.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "A megadott érték nem egy érvényes bankkártya szám.", "dateStringErrorText": "Ennek a mezőnek dátumnak kell lennie.", "emailErrorText": "A megadott érték nem egy érvényes email cím.", @@ -86,5 +100,125 @@ "vinErrorText": "Az értéknek egy érvényes járműazonosító számnak kell lennie.", "languageCodeErrorText": "Az értéknek egy érvényes nyelvkódnak kell lennie.", "floatErrorText": "Az értéknek érvényes lebegőpontos számnak kell lennie.", - "hexadecimalErrorText": "Az értéknek érvényes hexadecimális számnak kell lennie." + "hexadecimalErrorText": "Az értéknek érvényes hexadecimális számnak kell lennie.", + "betweenNumErrorText": "Az értéknek {minInclusive, select, true{nagyobbnak vagy egyenlőnek kell lennie mint} other{nagyobbnak kell lennie mint}} {min} és {maxInclusive, select, true{kisebbnek vagy egyenlőnek kell lennie mint} other{kisebbnek kell lennie mint}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Ehhez a mezőhöz érvényes logikai érték szükséges (igaz vagy hamis).", + "dateMustBeAfterErrorText": "A dátumnak {reference} utáninak kell lennie.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "A dátumnak {reference} előttinek kell lennie.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "A dátumnak {minReference} és {maxReference} között kell lennie.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Ehhez a mezőhöz érvényes dátum és időpont szükséges.", + "greaterThanErrorText": "Az értéknek nagyobbnak kell lennie mint {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Az értéknek nagyobbnak vagy egyenlőnek kell lennie mint {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "A mező opcionális, különben {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Ehhez a mezőhöz érvényes szöveg szükséges.", + "lessThanErrorText": "Az értéknek kisebbnek kell lennie mint {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Az értéknek kisebbnek vagy egyenlőnek kell lennie mint {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " vagy ", + "transformAndValidateErrorTextV1": "Az érték nem alakítható át.", + "transformAndValidateErrorTextV2": "Az értéknek érvényes {transformedResultTypeDescription}-nak/-nek kell lennie.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_id.arb b/lib/l10n/intl_id.arb index 3b28acc5..0082f56e 100644 --- a/lib/l10n/intl_id.arb +++ b/lib/l10n/intl_id.arb @@ -1,5 +1,19 @@ { "@@locale": "id", + "andSeparator": " dan ", + "betweenLengthErrorText": "Nilai harus memiliki panjang antara {min} dan {max}, inklusif.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Nomor kartu kredit tidak valid.", "dateStringErrorText": "Tanggal tidak valid.", "emailErrorText": "Alamat email tidak valid.", @@ -86,5 +100,125 @@ "vinErrorText": "Nilai harus berupa VIN yang valid.", "languageCodeErrorText": "Nilai harus berupa kode bahasa yang valid.", "floatErrorText": "Nilai harus berupa angka floating point yang valid.", - "hexadecimalErrorText": "Nilai harus berupa angka heksadesimal yang valid." + "hexadecimalErrorText": "Nilai harus berupa angka heksadesimal yang valid.", + "betweenNumErrorText": "Nilai harus {minInclusive, select, true{lebih besar dari atau sama dengan} other{lebih besar dari}} {min} dan {maxInclusive, select, true{kurang dari atau sama dengan} other{kurang dari}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Bidang ini memerlukan nilai boolean yang valid (true atau false).", + "dateMustBeAfterErrorText": "Tanggal harus setelah {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Tanggal harus sebelum {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Tanggal harus di antara {minReference} dan {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Bidang ini memerlukan datetime yang valid.", + "greaterThanErrorText": "Nilai harus lebih besar dari {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Nilai harus lebih besar dari atau sama dengan {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Bidang ini bersifat opsional, jika tidak, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Bidang ini memerlukan string yang valid.", + "lessThanErrorText": "Nilai harus kurang dari {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Nilai harus kurang dari atau sama dengan {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " atau ", + "transformAndValidateErrorTextV1": "Nilai tidak dapat ditransformasikan.", + "transformAndValidateErrorTextV2": "Nilai harus berupa {transformedResultTypeDescription} yang valid.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_it.arb b/lib/l10n/intl_it.arb index 028696f8..6d2a256c 100644 --- a/lib/l10n/intl_it.arb +++ b/lib/l10n/intl_it.arb @@ -1,5 +1,19 @@ { "@@locale": "it", + "andSeparator": " e ", + "betweenLengthErrorText": "Il valore deve avere una lunghezza compresa tra {min} e {max}, inclusi.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Questo campo richiede un numero di carta di credito valido.", "dateStringErrorText": "Questo campo richiede una data valida.", "emailErrorText": "Questo campo richiede un indirizzo email valido.", @@ -86,5 +100,125 @@ "vinErrorText": "Il valore deve essere un VIN valido.", "languageCodeErrorText": "Il valore deve essere un codice lingua valido.", "floatErrorText": "Il valore deve essere un numero in virgola mobile valido.", - "hexadecimalErrorText": "Il valore deve essere un numero esadecimale valido." + "hexadecimalErrorText": "Il valore deve essere un numero esadecimale valido.", + "betweenNumErrorText": "Il valore deve essere {minInclusive, select, true{maggiore o uguale a} other{maggiore di}} {min} e {maxInclusive, select, true{minore o uguale a} other{minore di}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Questo campo richiede un valore booleano valido (vero o falso).", + "dateMustBeAfterErrorText": "La data deve essere successiva a {reference}.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "La data deve essere precedente a {reference}.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "La data deve essere compresa tra {minReference} e {maxReference}.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Questo campo richiede una data e ora valida.", + "greaterThanErrorText": "Il valore deve essere maggiore di {min}.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Il valore deve essere maggiore o uguale a {min}.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Il campo è opzionale, altrimenti, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Questo campo richiede una stringa valida.", + "lessThanErrorText": "Il valore deve essere minore di {max}.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Il valore deve essere minore o uguale a {max}.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " o ", + "transformAndValidateErrorTextV1": "Il valore non può essere trasformato.", + "transformAndValidateErrorTextV2": "Il valore deve essere un {transformedResultTypeDescription} valido.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index ac200051..87a37a0d 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1,5 +1,19 @@ { "@@locale": "ja", + "andSeparator": " と ", + "betweenLengthErrorText": "値の長さは{min}から{max}の間(両端を含む)である必要があります。", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "有効なクレジットカード番号を入力してください。", "dateStringErrorText": "正しい日付を入力してください。", "emailErrorText": "有効なメールアドレスを入力してください。", @@ -86,5 +100,125 @@ "vinErrorText": "値は有効なVINでなければなりません。", "languageCodeErrorText": "値は有効な言語コードでなければなりません。", "floatErrorText": "値は有効な浮動小数点数でなければなりません。", - "hexadecimalErrorText": "値は有効な16進数でなければなりません。" + "hexadecimalErrorText": "値は有効な16進数でなければなりません。", + "betweenNumErrorText": "値は{minInclusive, select, true{以上} other{より大きい}} {min} かつ {maxInclusive, select, true{以下} other{未満}} {max} である必要があります", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "このフィールドには有効な真偽値(true または false)が必要です", + "dateMustBeAfterErrorText": "日付は{reference}より後である必要があります", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "日付は{reference}より前である必要があります", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "日付は{minReference}から{maxReference}の間である必要があります", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "このフィールドには有効な日時が必要です", + "greaterThanErrorText": "値は{min}より大きい必要があります", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "値は{min}以上である必要があります", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "このフィールドは任意です。入力する場合は{nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "このフィールドには有効な文字列が必要です", + "lessThanErrorText": "値は{max}未満である必要があります", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "値は{max}以下である必要があります", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": "または", + "transformAndValidateErrorTextV1": "値を変換できません", + "transformAndValidateErrorTextV2": "値は有効な{transformedResultTypeDescription}である必要があります", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_km.arb b/lib/l10n/intl_km.arb index 3175ec8c..879d1fb2 100644 --- a/lib/l10n/intl_km.arb +++ b/lib/l10n/intl_km.arb @@ -1,5 +1,19 @@ { "@@locale": "km", + "andSeparator": " និង ", + "betweenLengthErrorText": "តម្លៃត្រូវតែមានប្រវែងរវាង {min} និង {max} រួមបញ្ចូលទាំងពីរ។", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "ទិន្នន័យនេះត្រូវតែជាលេខកាតឥណទានតែប៉ុណ្ណោះ។", "dateStringErrorText": "ទិន្នន័យ​នេះ​ត្រូវ​តែ​ជា​កាលបរិច្ឆេទតែប៉ុណ្ណោះ។", "emailErrorText": "ទិន្នន័យ​នេះ​ត្រូវ​តែ​ជាអ៊ីមែលតែប៉ុណ្ណោះ។", @@ -86,5 +100,125 @@ "vinErrorText": "តម្លៃត្រូវតែជាលេខ VIN ដែលត្រឹមត្រូវ។", "languageCodeErrorText": "តម្លៃត្រូវតែជាកូដភាសាដែលត្រឹមត្រូវ។", "floatErrorText": "តម្លៃត្រូវតែជាចំនួនទសភាគត្រឹមត្រូវ។", - "hexadecimalErrorText": "តម្លៃត្រូវតែជាលេខសិប្បកម្មត្រឹមត្រូវ។" + "hexadecimalErrorText": "តម្លៃត្រូវតែជាលេខសិប្បកម្មត្រឹមត្រូវ។", + "betweenNumErrorText": "តម្លៃត្រូវតែ {minInclusive, select, true{ធំជាងឬស្មើ} other{ធំជាង}} {min} និង {maxInclusive, select, true{តូចជាងឬស្មើ} other{តូចជាង}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "វាលនេះត្រូវការតម្លៃប៊ូលីនត្រឹមត្រូវ (ពិត ឬ មិនពិត)", + "dateMustBeAfterErrorText": "កាលបរិច្ឆេទត្រូវតែនៅក្រោយ {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "កាលបរិច្ឆេទត្រូវតែនៅមុន {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "កាលបរិច្ឆេទត្រូវតែនៅចន្លោះ {minReference} និង {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "វាលនេះត្រូវការកាលបរិច្ឆេទនិងពេលវេលាត្រឹមត្រូវ", + "greaterThanErrorText": "តម្លៃត្រូវតែធំជាង {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "តម្លៃត្រូវតែធំជាងឬស្មើ {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "វាលនេះជាជម្រើស បើមិនដូច្នោះទេ {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "វាលនេះត្រូវការអក្សរត្រឹមត្រូវ", + "lessThanErrorText": "តម្លៃត្រូវតែតូចជាង {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "តម្លៃត្រូវតែតូចជាងឬស្មើ {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ឬ ", + "transformAndValidateErrorTextV1": "តម្លៃមិនអាចបំលែងបានទេ", + "transformAndValidateErrorTextV2": "តម្លៃត្រូវតែជា {transformedResultTypeDescription} ត្រឹមត្រូវ", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ko.arb b/lib/l10n/intl_ko.arb index 1efefd4e..7645894c 100644 --- a/lib/l10n/intl_ko.arb +++ b/lib/l10n/intl_ko.arb @@ -1,5 +1,19 @@ { "@@locale": "ko", + "andSeparator": " 그리고 ", + "betweenLengthErrorText": "값의 길이는 {min}에서 {max} 사이여야 합니다(포함).", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "유효한 카드 번호를 입력해 주세요.", "dateStringErrorText": "날짜 형식이 올바르지 않습니다.", "emailErrorText": "이메일 주소 형식이 올바르지 않습니다.", @@ -86,5 +100,125 @@ "vinErrorText": "값은 유효한 VIN이어야 합니다.", "languageCodeErrorText": "값은 유효한 언어 코드이어야 합니다.", "floatErrorText": "값은 유효한 부동 소수점 수여야 합니다.", - "hexadecimalErrorText": "값은 유효한 16진수여야 합니다." + "hexadecimalErrorText": "값은 유효한 16진수여야 합니다.", + "betweenNumErrorText": "값은 {min}보다 {minInclusive, select, true{크거나 같고} other{크고}} {max}보다 {maxInclusive, select, true{작거나 같아야} other{작아야}} 합니다", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "이 필드는 유효한 불리언 값(true 또는 false)이 필요합니다", + "dateMustBeAfterErrorText": "날짜는 {reference} 이후여야 합니다", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "날짜는 {reference} 이전이어야 합니다", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "날짜는 {minReference}와 {maxReference} 사이여야 합니다", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "이 필드는 유효한 날짜/시간이 필요합니다", + "greaterThanErrorText": "값은 {min}보다 커야 합니다", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "값은 {min}보다 크거나 같아야 합니다", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "이 필드는 선택사항이며, 입력 시 {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "이 필드는 유효한 문자열이 필요합니다", + "lessThanErrorText": "값은 {max}보다 작아야 합니다", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "값은 {max}보다 작거나 같아야 합니다", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " 또는 ", + "transformAndValidateErrorTextV1": "값을 변환할 수 없습니다", + "transformAndValidateErrorTextV2": "값은 유효한 {transformedResultTypeDescription}이어야 합니다", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ku.arb b/lib/l10n/intl_ku.arb index caf8f557..c40a13b7 100644 --- a/lib/l10n/intl_ku.arb +++ b/lib/l10n/intl_ku.arb @@ -1,5 +1,19 @@ { "@@locale": "ku", + "andSeparator": " û ", + "betweenLengthErrorText": "Nirx divê dirêjahiya wê di navbera {min} û {max} de be, tevlî wan.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "ئەم خانە پێویستە ژمارەی کارتی کرێدیتێکی دروست هەبێت.", "dateStringErrorText": "ئەم خانە پێویستە دەستپێکی بەروارێکی دروست هەبێت.", "emailErrorText": "ئەم خانە پێویستە ناونیشانی ئیمەیڵێکی دروست هەبێت.", @@ -86,5 +100,125 @@ "vinErrorText": "Nirxî divê yekem hejmarê VIN derbasdar be.", "languageCodeErrorText": "Nirxî divê yekem kodê zimanê derbasdar be.", "floatErrorText": "Gerrdê hewce ye ku zêdeya rast bibe.", - "hexadecimalErrorText": "Gerrdê hewce ye ku hexa de rast bibe." + "hexadecimalErrorText": "Gerrdê hewce ye ku hexa de rast bibe.", + "betweenNumErrorText": "Nirx divê {minInclusive, select, true{mezintir an wekhev be ji} other{mezintir be ji}} {min} û {maxInclusive, select, true{kêmtir an wekhev be ji} other{kêmtir be ji}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Ev qad pêdivî bi nirxeke rast a boolean e (rast an şaş)", + "dateMustBeAfterErrorText": "Dîrok divê piştî {reference} be", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Dîrok divê beriya {reference} be", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Dîrok divê di navbera {minReference} û {maxReference} de be", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Ev qad pêdivî bi dîrokeke derbasdar e", + "greaterThanErrorText": "Nirx divê ji {min} mezintir be", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Nirx divê ji {min} mezintir an wekhev be", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Ev qad bijarte ye, wekî din, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Ev qad pêdivî bi rêzekeke derbasdar e", + "lessThanErrorText": "Nirx divê ji {max} kêmtir be", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Nirx divê ji {max} kêmtir an wekhev be", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " an ", + "transformAndValidateErrorTextV1": "Nirx nikare were veguhertin", + "transformAndValidateErrorTextV2": "Nirx divê {transformedResultTypeDescription} ya derbasdar be", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_lo.arb b/lib/l10n/intl_lo.arb index 049194ca..0c144540 100644 --- a/lib/l10n/intl_lo.arb +++ b/lib/l10n/intl_lo.arb @@ -1,5 +1,19 @@ { "@@locale": "lo", + "andSeparator": " ແລະ ", + "betweenLengthErrorText": "ຄ່າຕ້ອງມີຄວາມຍາວລະຫວ່າງ {min} ແລະ {max}, ລວມທັງສອງຄ່າ.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "ຄ່າໃນຟອມນີ້ຕ້ອງຢູ່ໃນຮູບແບບຂອງເລກບັດເຄຣດິດ.", "dateStringErrorText": "ຄ່າໃນຟອມນີ້ຕ້ອງຢູ່ໃນຮູບແບບຂອງວັນທີ.", "emailErrorText": "ຄ່າໃນຟອມນີ້ຕ້ອງຢູ່ໃນຮູບແບບຂອງອີເມວ.", @@ -86,5 +100,125 @@ "vinErrorText": "ຄ່າຕ້ອງເປັນ VIN ທີ່ຖືກຕ້ອງ.", "languageCodeErrorText": "ຄ່າຕ້ອງເປັນໄລຄໂລດພາສາທີ່ຖືກຕ້ອງ.", "floatErrorText": "ຄ່າຕ້ອງເປັນໂຕເລກ float ທີ່ຖືກຕ້ອງ.", - "hexadecimalErrorText": "ຄ່າຕ້ອງເປັນໂຕເລກເຮັກຊະເດສິມທີ່ຖືກຕ້ອງ." + "hexadecimalErrorText": "ຄ່າຕ້ອງເປັນໂຕເລກເຮັກຊະເດສິມທີ່ຖືກຕ້ອງ.", + "betweenNumErrorText": "ຄ່າຕ້ອງ {minInclusive, select, true{ໃຫຍ່ກວ່າ ຫຼື ເທົ່າກັບ} other{ໃຫຍ່ກວ່າ}} {min} ແລະ {maxInclusive, select, true{ນ້ອຍກວ່າ ຫຼື ເທົ່າກັບ} other{ນ້ອຍກວ່າ}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "ຊ່ອງນີ້ຕ້ອງການຄ່າ boolean ທີ່ຖືກຕ້ອງ (true ຫຼື false)", + "dateMustBeAfterErrorText": "ວັນທີຕ້ອງຢູ່ຫຼັງຈາກ {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "ວັນທີຕ້ອງຢູ່ກ່ອນ {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "ວັນທີຕ້ອງຢູ່ລະຫວ່າງ {minReference} ແລະ {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "ຊ່ອງນີ້ຕ້ອງການວັນທີແລະເວລາທີ່ຖືກຕ້ອງ", + "greaterThanErrorText": "ຄ່າຕ້ອງໃຫຍ່ກວ່າ {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "ຄ່າຕ້ອງໃຫຍ່ກວ່າ ຫຼື ເທົ່າກັບ {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "ຊ່ອງນີ້ເປັນທາງເລືອກ, ຖ້າບໍ່ດັ່ງນັ້ນ, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "ຊ່ອງນີ້ຕ້ອງການຂໍ້ຄວາມທີ່ຖືກຕ້ອງ", + "lessThanErrorText": "ຄ່າຕ້ອງນ້ອຍກວ່າ {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "ຄ່າຕ້ອງນ້ອຍກວ່າ ຫຼື ເທົ່າກັບ {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ຫຼື ", + "transformAndValidateErrorTextV1": "ບໍ່ສາມາດແປງຄ່າໄດ້", + "transformAndValidateErrorTextV2": "ຄ່າຕ້ອງເປັນ {transformedResultTypeDescription} ທີ່ຖືກຕ້ອງ", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_lv.arb b/lib/l10n/intl_lv.arb index 973f4121..117de1ac 100644 --- a/lib/l10n/intl_lv.arb +++ b/lib/l10n/intl_lv.arb @@ -1,5 +1,19 @@ { "@@locale": "lv", + "andSeparator": " un ", + "betweenLengthErrorText": "Vērtības garumam jābūt starp {min} un {max}, ieskaitot.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Šis lauks prasa derīgu kredītkartes numuru.", "dateStringErrorText": "Šis lauks prasa derīgu datuma virkni.", "emailErrorText": "Šis lauks prasa derīgu e-pasta adresi.", @@ -86,5 +100,125 @@ "vinErrorText": "Vērtībai jābūt derīgam VIN.", "languageCodeErrorText": "Vērtībai jābūt derīgam valodas kodam.", "floatErrorText": "Vērtībai jābūt derīgam decimālskaitlim.", - "hexadecimalErrorText": "Vērtībai jābūt derīgam heksadecimālam skaitlim." + "hexadecimalErrorText": "Vērtībai jābūt derīgam heksadecimālam skaitlim.", + "betweenNumErrorText": "Vērtībai jābūt {minInclusive, select, true{lielākai vai vienādai ar} other{lielākai nekā}} {min} un {maxInclusive, select, true{mazākai vai vienādai ar} other{mazākai nekā}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Šim laukam nepieciešama derīga būla vērtība (true vai false)", + "dateMustBeAfterErrorText": "Datumam jābūt pēc {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datumam jābūt pirms {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datumam jābūt starp {minReference} un {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Šim laukam nepieciešams derīgs datums un laiks", + "greaterThanErrorText": "Vērtībai jābūt lielākai par {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Vērtībai jābūt lielākai vai vienādai ar {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Lauks ir neobligāts, pretējā gadījumā {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Šim laukam nepieciešama derīga teksta virkne", + "lessThanErrorText": "Vērtībai jābūt mazākai par {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Vērtībai jābūt mazākai vai vienādai ar {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " vai ", + "transformAndValidateErrorTextV1": "Vērtību nav iespējams pārveidot", + "transformAndValidateErrorTextV2": "Vērtībai jābūt derīgai {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_mn.arb b/lib/l10n/intl_mn.arb index c9e3722a..f91f1646 100644 --- a/lib/l10n/intl_mn.arb +++ b/lib/l10n/intl_mn.arb @@ -1,5 +1,19 @@ { "@@locale": "mn", + "andSeparator": " ба ", + "betweenLengthErrorText": "Утгын урт нь {min}-с {max} хүртэл байх ёстой, оруулан тооцно.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Картын дугаар алдаатай байна.", "dateStringErrorText": "Огнооны загварт таарахгүй байна.", "emailErrorText": "И-мэйл хаяг алдаатай байна.", @@ -86,5 +100,125 @@ "vinErrorText": "Утга нь хүчин төгөлдөр VIN байх ёстой.", "languageCodeErrorText": "Утга нь хүчин төгөлдөр хэлний код байх ёстой.", "floatErrorText": "Утга нь буцаж ирдэг зөв хөвөгч цэгийн тоо байх ёстой.", - "hexadecimalErrorText": "Утга нь буцаж ирдэг зөв арван зургаатын тоо байх ёстой." + "hexadecimalErrorText": "Утга нь буцаж ирдэг зөв арван зургаатын тоо байх ёстой.", + "betweenNumErrorText": "Утга нь {minInclusive, select, true{-аас их буюу тэнцүү} other{-аас их}} {min} ба {maxInclusive, select, true{-аас бага буюу тэнцүү} other{-аас бага}} {max} байх ёстой", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Энэ талбарт зөвхөн үнэн эсвэл худал утга оруулах шаардлагатай", + "dateMustBeAfterErrorText": "Огноо {reference}-с хойш байх ёстой", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Огноо {reference}-с өмнө байх ёстой", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Огноо {minReference} ба {maxReference}-ны хооронд байх ёстой", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Энэ талбарт зөв огноо оруулах шаардлагатай", + "greaterThanErrorText": "Утга {min}-аас их байх ёстой", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Утга {min}-тай тэнцүү эсвэл түүнээс их байх ёстой", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Талбар нь заавал биш, эсвэл {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Энэ талбарт зөв текст оруулах шаардлагатай", + "lessThanErrorText": "Утга {max}-аас бага байх ёстой", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Утга {max}-тай тэнцүү эсвэл түүнээс бага байх ёстой", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " эсвэл ", + "transformAndValidateErrorTextV1": "Утгыг хөрвүүлэх боломжгүй байна", + "transformAndValidateErrorTextV2": "Утга нь хүчинтэй {transformedResultTypeDescription} байх ёстой", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ms.arb b/lib/l10n/intl_ms.arb index 3698711d..ee067002 100644 --- a/lib/l10n/intl_ms.arb +++ b/lib/l10n/intl_ms.arb @@ -1,5 +1,19 @@ { "@@locale": "ms", + "andSeparator": " dan ", + "betweenLengthErrorText": "Nilai mesti mempunyai panjang antara {min} dan {max}, termasuk.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Ruangan ini memerlukan nombor kad kredit yang sah.", "dateStringErrorText": "Ruangan ini memerlukan rentetan tarikh yang sah.", "emailErrorText": "Ruangan ini memerlukan alamat e-mel yang sah.", @@ -86,5 +100,125 @@ "vinErrorText": "Nilai mesti nombor VIN yang sah.", "languageCodeErrorText": "Nilai mesti kod bahasa yang sah.", "floatErrorText": "Nilai mesti nombor terapung sah.", - "hexadecimalErrorText": "Nilai mesti nombor heksadesimal sah." + "hexadecimalErrorText": "Nilai mesti nombor heksadesimal sah.", + "betweenNumErrorText": "Nilai mesti {minInclusive, select, true{lebih besar atau sama dengan} other{lebih besar daripada}} {min} dan {maxInclusive, select, true{kurang atau sama dengan} other{kurang daripada}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Medan ini memerlukan nilai boolean yang sah (benar atau palsu)", + "dateMustBeAfterErrorText": "Tarikh mesti selepas {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Tarikh mesti sebelum {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Tarikh mesti antara {minReference} dan {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Medan ini memerlukan tarikh dan masa yang sah", + "greaterThanErrorText": "Nilai mesti lebih besar daripada {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Nilai mesti lebih besar atau sama dengan {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Medan ini adalah pilihan, jika tidak, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Medan ini memerlukan rentetan yang sah", + "lessThanErrorText": "Nilai mesti kurang daripada {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Nilai mesti kurang atau sama dengan {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " atau ", + "transformAndValidateErrorTextV1": "Nilai tidak dapat diubah", + "transformAndValidateErrorTextV2": "Nilai mesti {transformedResultTypeDescription} yang sah", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ne.arb b/lib/l10n/intl_ne.arb index 7582a947..01728a92 100644 --- a/lib/l10n/intl_ne.arb +++ b/lib/l10n/intl_ne.arb @@ -1,5 +1,19 @@ { "@@locale": "ne", + "andSeparator": " र ", + "betweenLengthErrorText": "मान को लम्बाई {min} र {max} बीचमा हुनुपर्छ, समावेश गरेर।", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "यो क्षेत्रलाई मान्य क्रेडिट कार्ड नम्बर चाहिन्छ।", "dateStringErrorText": "यो क्षेत्रलाई मान्य मिति स्ट्रिंग चाहिन्छ।", "emailErrorText": "यो क्षेत्रलाई मान्य इमेल ठेगाना चाहिन्छ।", @@ -86,5 +100,125 @@ "vinErrorText": "मान मान्य VIN हुनुपर्छ।", "languageCodeErrorText": "मान मान्य भाषाको कोड हुनुपर्छ।", "floatErrorText": "मान्यता प्राप्त फ्लोटिङ पोइन्ट नम्बर हुनुपर्छ।", - "hexadecimalErrorText": "मान्यता प्राप्त हेक्साडेसिमल नम्बर हुनुपर्छ।" + "hexadecimalErrorText": "मान्यता प्राप्त हेक्साडेसिमल नम्बर हुनुपर्छ।", + "betweenNumErrorText": "मान {minInclusive, select, true{बराबर वा बढी} other{भन्दा बढी}} {min} र {maxInclusive, select, true{बराबर वा कम} other{भन्दा कम}} {max} हुनुपर्छ", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "यो फिल्डमा मान्य बुलियन (सत्य वा असत्य) आवश्यक छ", + "dateMustBeAfterErrorText": "मिति {reference} पछि हुनुपर्छ", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "मिति {reference} अघि हुनुपर्छ", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "मिति {minReference} र {maxReference} बीच हुनुपर्छ", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "यो फिल्डमा मान्य मिति र समय आवश्यक छ", + "greaterThanErrorText": "मान {min} भन्दा बढी हुनुपर्छ", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "मान {min} बराबर वा बढी हुनुपर्छ", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "यो फिल्ड वैकल्पिक छ, अन्यथा, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "यो फिल्डमा मान्य स्ट्रिङ आवश्यक छ", + "lessThanErrorText": "मान {max} भन्दा कम हुनुपर्छ", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "मान {max} बराबर वा कम हुनुपर्छ", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " वा ", + "transformAndValidateErrorTextV1": "मान रूपान्तरण गर्न सकिएन", + "transformAndValidateErrorTextV2": "मान मान्य {transformedResultTypeDescription} हुनुपर्छ", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 36a2cc8f..30418dbd 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1,5 +1,19 @@ { "@@locale": "nl", + "andSeparator": " en ", + "betweenLengthErrorText": "De waarde moet een lengte hebben tussen {min} en {max}, inclusief.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Een geldig creditcardnummer is vereist.", "dateStringErrorText": "Een geldige datum is vereist.", "emailErrorText": "Een geldig e-mailadres is vereist.", @@ -86,5 +100,125 @@ "vinErrorText": "Waarde moet een geldig VIN zijn.", "languageCodeErrorText": "Waarde moet een geldige taalcode zijn.", "floatErrorText": "Waarde moet een geldig drijvend-komma getal zijn.", - "hexadecimalErrorText": "Waarde moet een geldig hexadecimaal getal zijn." + "hexadecimalErrorText": "Waarde moet een geldig hexadecimaal getal zijn.", + "betweenNumErrorText": "Waarde moet {minInclusive, select, true{groter dan of gelijk aan} other{groter dan}} {min} en {maxInclusive, select, true{kleiner dan of gelijk aan} other{kleiner dan}} {max} zijn", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Dit veld vereist een geldige booleaanse waarde (true of false)", + "dateMustBeAfterErrorText": "Datum moet na {reference} zijn", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datum moet voor {reference} zijn", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datum moet tussen {minReference} en {maxReference} liggen", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Dit veld vereist een geldige datum en tijd", + "greaterThanErrorText": "Waarde moet groter zijn dan {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Waarde moet groter dan of gelijk zijn aan {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Het veld is optioneel, anders {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Dit veld vereist een geldige tekst", + "lessThanErrorText": "Waarde moet kleiner zijn dan {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Waarde moet kleiner dan of gelijk zijn aan {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " of ", + "transformAndValidateErrorTextV1": "Waarde kan niet worden getransformeerd", + "transformAndValidateErrorTextV2": "Waarde moet een geldige {transformedResultTypeDescription} zijn", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_no.arb b/lib/l10n/intl_no.arb index 5d7506bb..5688cc2a 100644 --- a/lib/l10n/intl_no.arb +++ b/lib/l10n/intl_no.arb @@ -1,5 +1,19 @@ { "@@locale": "no", + "andSeparator": " og ", + "betweenLengthErrorText": "Verdien må ha en lengde mellom {min} og {max}, inkludert.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Dette feltet krever et gyldig kredittkortnummer.", "dateStringErrorText": "Dette feltet krever en gyldig dato.", "emailErrorText": "Dette feltet krever en gyldig e-postadresse.", @@ -86,5 +100,125 @@ "vinErrorText": "Verdien må være et gyldig VIN.", "languageCodeErrorText": "Verdien må være en gyldig språkkode.", "floatErrorText": "Verdien må være et gyldig flyttall.", - "hexadecimalErrorText": "Verdien må være et gyldig heksadesimalt tall." + "hexadecimalErrorText": "Verdien må være et gyldig heksadesimalt tall.", + "betweenNumErrorText": "Verdien må være {minInclusive, select, true{større enn eller lik} other{større enn}} {min} og {maxInclusive, select, true{mindre enn eller lik} other{mindre enn}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Dette feltet krever en gyldig boolsk verdi (sann eller usann)", + "dateMustBeAfterErrorText": "Datoen må være etter {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datoen må være før {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datoen må være mellom {minReference} og {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Dette feltet krever en gyldig dato og tid", + "greaterThanErrorText": "Verdien må være større enn {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Verdien må være større enn eller lik {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Feltet er valgfritt, ellers {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Dette feltet krever en gyldig tekststreng", + "lessThanErrorText": "Verdien må være mindre enn {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Verdien må være mindre enn eller lik {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " eller ", + "transformAndValidateErrorTextV1": "Verdien kan ikke transformeres", + "transformAndValidateErrorTextV2": "Verdien må være en gyldig {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 8b5cc2d7..70df2e71 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1,5 +1,19 @@ { "@@locale": "pl", + "andSeparator": " i ", + "betweenLengthErrorText": "Wartość musi mieć długość pomiędzy {min} a {max}, włącznie.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "To pole wymaga podania ważnego numeru karty kredytowej.", "dateStringErrorText": "To pole wymaga prawidłowej daty.", "emailErrorText": "To pole wymaga prawidłowego adresu e-mail.", @@ -86,5 +100,125 @@ "vinErrorText": "Wartość musi być prawidłowym numerem VIN.", "languageCodeErrorText": "Wartość musi być prawidłowym kodem języka.", "floatErrorText": "Wartość musi być prawidłową liczbą zmiennoprzecinkową.", - "hexadecimalErrorText": "Wartość musi być prawidłową liczbą szesnastkową." + "hexadecimalErrorText": "Wartość musi być prawidłową liczbą szesnastkową.", + "betweenNumErrorText": "Wartość musi być {minInclusive, select, true{większa lub równa} other{większa niż}} {min} i {maxInclusive, select, true{mniejsza lub równa} other{mniejsza niż}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "To pole wymaga prawidłowej wartości logicznej (prawda lub fałsz)", + "dateMustBeAfterErrorText": "Data musi być późniejsza niż {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Data musi być wcześniejsza niż {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Data musi być pomiędzy {minReference} a {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "To pole wymaga prawidłowej daty i godziny", + "greaterThanErrorText": "Wartość musi być większa niż {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Wartość musi być większa lub równa {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Pole jest opcjonalne, w przeciwnym razie {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "To pole wymaga prawidłowego ciągu znaków", + "lessThanErrorText": "Wartość musi być mniejsza niż {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Wartość musi być mniejsza lub równa {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " lub ", + "transformAndValidateErrorTextV1": "Wartość nie może zostać przekształcona", + "transformAndValidateErrorTextV2": "Wartość musi być prawidłowym {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 11a1c6c2..32d01356 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -1,5 +1,19 @@ { "@@locale": "pt", + "andSeparator": " e ", + "betweenLengthErrorText": "O valor deve ter um comprimento entre {min} e {max}, inclusive.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Este campo requer um número de cartão de crédito válido.", "dateStringErrorText": "Este campo requer uma string de data válida.", "emailErrorText": "Este campo requer um endereço de e-mail válido.", @@ -86,5 +100,125 @@ "vinErrorText": "O valor deve ser um VIN válido.", "languageCodeErrorText": "O valor deve ser um código de idioma válido.", "floatErrorText": "O valor deve ser um número de ponto flutuante válido.", - "hexadecimalErrorText": "O valor deve ser um número hexadecimal válido." + "hexadecimalErrorText": "O valor deve ser um número hexadecimal válido.", + "betweenNumErrorText": "O valor deve ser {minInclusive, select, true{maior ou igual a} other{maior que}} {min} e {maxInclusive, select, true{menor ou igual a} other{menor que}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Este campo requer um valor booleano válido (verdadeiro ou falso)", + "dateMustBeAfterErrorText": "A data deve ser posterior a {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "A data deve ser anterior a {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "A data deve estar entre {minReference} e {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Este campo requer uma data e hora válidas", + "greaterThanErrorText": "O valor deve ser maior que {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "O valor deve ser maior ou igual a {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "O campo é opcional, caso contrário, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Este campo requer uma string válida", + "lessThanErrorText": "O valor deve ser menor que {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "O valor deve ser menor ou igual a {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ou ", + "transformAndValidateErrorTextV1": "O valor não pode ser transformado", + "transformAndValidateErrorTextV2": "O valor deve ser um {transformedResultTypeDescription} válido", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ro.arb b/lib/l10n/intl_ro.arb index 659d09eb..fe641608 100644 --- a/lib/l10n/intl_ro.arb +++ b/lib/l10n/intl_ro.arb @@ -1,5 +1,19 @@ { "@@locale": "ro", + "andSeparator": " și ", + "betweenLengthErrorText": "Valoarea trebuie să aibă o lungime între {min} și {max}, inclusiv.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Acest câmp necesită un număr valid de card de credit.", "dateStringErrorText": "Acest câmp necesită un șir de date valid.", "emailErrorText": "Acest câmp necesită o adresă de e-mail validă.", @@ -86,5 +100,125 @@ "vinErrorText": "Valoarea trebuie să fie un VIN valid.", "languageCodeErrorText": "Valoarea trebuie să fie un cod de limbă valid.", "floatErrorText": "Valoarea trebuie să fie un număr zecimal valid.", - "hexadecimalErrorText": "Valoarea trebuie să fie un număr hexadecimal valid." + "hexadecimalErrorText": "Valoarea trebuie să fie un număr hexadecimal valid.", + "betweenNumErrorText": "Valoarea trebuie să fie {minInclusive, select, true{mai mare sau egală cu} other{mai mare decât}} {min} și {maxInclusive, select, true{mai mică sau egală cu} other{mai mică decât}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Acest câmp necesită o valoare booleană validă (adevărat sau fals)", + "dateMustBeAfterErrorText": "Data trebuie să fie după {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Data trebuie să fie înainte de {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Data trebuie să fie între {minReference} și {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Acest câmp necesită o dată și oră validă", + "greaterThanErrorText": "Valoarea trebuie să fie mai mare decât {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Valoarea trebuie să fie mai mare sau egală cu {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Câmpul este opțional, în caz contrar, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Acest câmp necesită un șir de caractere valid", + "lessThanErrorText": "Valoarea trebuie să fie mai mică decât {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Valoarea trebuie să fie mai mică sau egală cu {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " sau ", + "transformAndValidateErrorTextV1": "Valoarea nu poate fi transformată", + "transformAndValidateErrorTextV2": "Valoarea trebuie să fie un {transformedResultTypeDescription} valid", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 015ce131..9778b859 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1,5 +1,19 @@ { "@@locale": "ru", + "andSeparator": " и ", + "betweenLengthErrorText": "Значение должно иметь длину от {min} до {max} включительно.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Значение поля должно быть номером кредитной карты.", "dateStringErrorText": "Поле должно быть датой.", "emailErrorText": "Поле должно быть email адресом.", @@ -86,5 +100,125 @@ "vinErrorText": "Значение должно быть действительным VIN.", "languageCodeErrorText": "Значение должно быть действительным кодом языка.", "floatErrorText": "Значение должно быть допустимым числом с плавающей запятой.", - "hexadecimalErrorText": "Значение должно быть допустимым шестнадцатеричным числом." + "hexadecimalErrorText": "Значение должно быть допустимым шестнадцатеричным числом.", + "betweenNumErrorText": "Значение должно быть {minInclusive, select, true{больше или равно} other{больше}} {min} и {maxInclusive, select, true{меньше или равно} other{меньше}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Это поле требует допустимое логическое значение (true или false)", + "dateMustBeAfterErrorText": "Дата должна быть после {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Дата должна быть до {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Дата должна быть между {minReference} и {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Это поле требует действительную дату и время", + "greaterThanErrorText": "Значение должно быть больше {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Значение должно быть больше или равно {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Поле является необязательным, в противном случае {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Это поле требует действительную строку", + "lessThanErrorText": "Значение должно быть меньше {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Значение должно быть меньше или равно {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " или ", + "transformAndValidateErrorTextV1": "Значение не может быть преобразовано", + "transformAndValidateErrorTextV2": "Значение должно быть допустимым {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_sk.arb b/lib/l10n/intl_sk.arb index cdb33c15..852e8362 100644 --- a/lib/l10n/intl_sk.arb +++ b/lib/l10n/intl_sk.arb @@ -1,5 +1,19 @@ { "@@locale": "sk", + "andSeparator": " a ", + "betweenLengthErrorText": "Hodnota musí mať dĺžku medzi {min} a {max}, vrátane.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Toto pole vyžaduje platné číslo platobnej karty.", "dateStringErrorText": "Toto pole vyžaduje platný dátum.", "emailErrorText": "Toto pole vyžaduje platnú emailovú adresu.", @@ -86,5 +100,125 @@ "vinErrorText": "Hodnota musí byť platné číslo VIN.", "languageCodeErrorText": "Hodnota musí byť platný kód jazyka.", "floatErrorText": "Hodnota musí byť platné desatinné číslo.", - "hexadecimalErrorText": "Hodnota musí byť platné hexadecimálne číslo." + "hexadecimalErrorText": "Hodnota musí byť platné hexadecimálne číslo.", + "betweenNumErrorText": "Hodnota musí byť {minInclusive, select, true{väčšia alebo rovná} other{väčšia ako}} {min} a {maxInclusive, select, true{menšia alebo rovná} other{menšia ako}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Toto pole vyžaduje platnú boolovskú hodnotu (true alebo false)", + "dateMustBeAfterErrorText": "Dátum musí byť po {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Dátum musí byť pred {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Dátum musí byť medzi {minReference} a {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Toto pole vyžaduje platný dátum a čas", + "greaterThanErrorText": "Hodnota musí byť väčšia ako {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Hodnota musí byť väčšia alebo rovná {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Pole je voliteľné, v opačnom prípade {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Toto pole vyžaduje platný reťazec", + "lessThanErrorText": "Hodnota musí byť menšia ako {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Hodnota musí byť menšia alebo rovná {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " alebo ", + "transformAndValidateErrorTextV1": "Hodnotu nie je možné transformovať", + "transformAndValidateErrorTextV2": "Hodnota musí byť platná {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_sl.arb b/lib/l10n/intl_sl.arb index 6e06a7d4..5a55c642 100644 --- a/lib/l10n/intl_sl.arb +++ b/lib/l10n/intl_sl.arb @@ -1,5 +1,19 @@ { "@@locale": "sl", + "andSeparator": " in ", + "betweenLengthErrorText": "Vrednost mora imeti dolžino med {min} in {max}, vključno.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Vnesite veljavno številko kreditne kartice.", "dateStringErrorText": "Vnesite veljaven datum.", "emailErrorText": "Vnesite veljaven e-mail naslov.", @@ -86,5 +100,125 @@ "vinErrorText": "Vrednost mora biti veljavna identifikacijska številka vozila (VIN).", "languageCodeErrorText": "Vrednost mora biti veljavna koda jezika.", "floatErrorText": "Vrednost mora biti veljavno število s plavajočo vejico.", - "hexadecimalErrorText": "Vrednost mora biti veljavno šestnajstiško število." + "hexadecimalErrorText": "Vrednost mora biti veljavno šestnajstiško število.", + "betweenNumErrorText": "Vrednost mora biti {minInclusive, select, true{večja ali enaka} other{večja od}} {min} in {maxInclusive, select, true{manjša ali enaka} other{manjša od}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "To polje zahteva veljavno logično vrednost (true ali false)", + "dateMustBeAfterErrorText": "Datum mora biti po {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datum mora biti pred {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datum mora biti med {minReference} in {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "To polje zahteva veljaven datum in čas", + "greaterThanErrorText": "Vrednost mora biti večja od {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Vrednost mora biti večja ali enaka {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Polje je neobvezno, sicer {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "To polje zahteva veljaven niz", + "lessThanErrorText": "Vrednost mora biti manjša od {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Vrednost mora biti manjša ali enaka {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ali ", + "transformAndValidateErrorTextV1": "Vrednosti ni mogoče pretvoriti", + "transformAndValidateErrorTextV2": "Vrednost mora biti veljaven {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_sq.arb b/lib/l10n/intl_sq.arb index b9d7a2cf..e4b7cc62 100644 --- a/lib/l10n/intl_sq.arb +++ b/lib/l10n/intl_sq.arb @@ -1,5 +1,19 @@ { "@@locale": "sq", + "andSeparator": " dhe ", + "betweenLengthErrorText": "Vlera duhet të ketë një gjatësi midis {min} dhe {max}, përfshirë.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Kjo fushë kërkon një numër të vlefshëm për kartën e kreditit.", "dateStringErrorText": "Kjo fushë kërkon një datë të vlefshme.", "emailErrorText": "Kjo fushë kërkon një adresë e E-mail-i të vlefshme.", @@ -86,5 +100,125 @@ "vinErrorText": "Vlera duhet të jetë një VIN i vlefshëm.", "languageCodeErrorText": "Vlera duhet të jetë një kod gjuhe i vlefshëm.", "floatErrorText": "Vlera duhet të jetë një numër i vlefshëm me pikë lundruese.", - "hexadecimalErrorText": "Vlera duhet të jetë një numër i vlefshëm heksadecimal." + "hexadecimalErrorText": "Vlera duhet të jetë një numër i vlefshëm heksadecimal.", + "betweenNumErrorText": "Vlera duhet të jetë {minInclusive, select, true{më e madhe ose e barabartë me} other{më e madhe se}} {min} dhe {maxInclusive, select, true{më e vogël ose e barabartë me} other{më e vogël se}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Kjo fushë kërkon një vlerë të vlefshme booleane (true ose false)", + "dateMustBeAfterErrorText": "Data duhet të jetë pas {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Data duhet të jetë para {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Data duhet të jetë midis {minReference} dhe {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Kjo fushë kërkon një datë të vlefshme", + "greaterThanErrorText": "Vlera duhet të jetë më e madhe se {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Vlera duhet të jetë më e madhe ose e barabartë me {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Fusha është opsionale, përndryshe, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Kjo fushë kërkon një varg të vlefshëm", + "lessThanErrorText": "Vlera duhet të jetë më e vogël se {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Vlera duhet të jetë më e vogël ose e barabartë me {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " ose ", + "transformAndValidateErrorTextV1": "Vlera nuk mund të transformohet", + "transformAndValidateErrorTextV2": "Vlera duhet të jetë një {transformedResultTypeDescription} i vlefshëm", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } diff --git a/lib/l10n/intl_sv.arb b/lib/l10n/intl_sv.arb index abfb0a3d..c0ee955b 100644 --- a/lib/l10n/intl_sv.arb +++ b/lib/l10n/intl_sv.arb @@ -1,5 +1,19 @@ { "@@locale": "sv", + "andSeparator": " och ", + "betweenLengthErrorText": "Värdet måste ha en längd mellan {min} och {max}, inklusive.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Detta fält kräver ett giltigt kreditkortsnummer.", "dateStringErrorText": "Detta fält kräver ett giltigt datum.", "emailErrorText": "Detta fält kräver en giltig e-postadress.", @@ -86,5 +100,125 @@ "vinErrorText": "Värdet måste vara ett giltigt VIN.", "languageCodeErrorText": "Värdet måste vara en giltig språkkod.", "floatErrorText": "Värdet måste vara ett giltigt flyttal.", - "hexadecimalErrorText": "Värdet måste vara ett giltigt hexadecimaltal." + "hexadecimalErrorText": "Värdet måste vara ett giltigt hexadecimaltal.", + "betweenNumErrorText": "Värdet måste vara {minInclusive, select, true{större än eller lika med} other{större än}} {min} och {maxInclusive, select, true{mindre än eller lika med} other{mindre än}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Detta fält kräver ett giltigt booleskt värde (sant eller falskt)", + "dateMustBeAfterErrorText": "Datumet måste vara efter {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Datumet måste vara före {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Datumet måste vara mellan {minReference} och {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Detta fält kräver ett giltigt datum och tid", + "greaterThanErrorText": "Värdet måste vara större än {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Värdet måste vara större än eller lika med {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Fältet är valfritt, annars {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Detta fält kräver en giltig sträng", + "lessThanErrorText": "Värdet måste vara mindre än {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Värdet måste vara mindre än eller lika med {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " eller ", + "transformAndValidateErrorTextV1": "Värdet kan inte omvandlas", + "transformAndValidateErrorTextV2": "Värdet måste vara en giltig {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_sw.arb b/lib/l10n/intl_sw.arb index 5dcf203e..93a4d170 100644 --- a/lib/l10n/intl_sw.arb +++ b/lib/l10n/intl_sw.arb @@ -1,5 +1,19 @@ { "@@locale": "sw", + "andSeparator": " na ", + "betweenLengthErrorText": "Thamani lazima iwe na urefu kati ya {min} na {max}, ikijumuisha.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Sehemu hii inahitaji nambari halali ya kadi ya mkopo.", "dateStringErrorText": "Sehemu hii inahitaji mfuatano halali wa tarehe.", "emailErrorText": "Sehemu hii inahitaji barua pepe halali.", @@ -86,5 +100,125 @@ "vinErrorText": "Thamani lazima iwe VIN halali.", "languageCodeErrorText": "Thamani lazima iwe msimbo halali wa lugha.", "floatErrorText": "Thamani lazima iwe nambari sahihi ya nukta mvutano.", - "hexadecimalErrorText": "Thamani lazima iwe nambari sahihi ya hekshadesimali." + "hexadecimalErrorText": "Thamani lazima iwe nambari sahihi ya hekshadesimali.", + "betweenNumErrorText": "Thamani lazima iwe {minInclusive, select, true{kubwa kuliko au sawa na} other{kubwa kuliko}} {min} na {maxInclusive, select, true{ndogo kuliko au sawa na} other{ndogo kuliko}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Sehemu hii inahitaji boolean halali (kweli au si kweli)", + "dateMustBeAfterErrorText": "Tarehe lazima iwe baada ya {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Tarehe lazima iwe kabla ya {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Tarehe lazima iwe kati ya {minReference} na {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Sehemu hii inahitaji tarehe na wakati halali", + "greaterThanErrorText": "Thamani lazima iwe kubwa kuliko {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Thamani lazima iwe kubwa kuliko au sawa na {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Sehemu hii ni hiari, vinginevyo, {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Sehemu hii inahitaji maandishi halali", + "lessThanErrorText": "Thamani lazima iwe ndogo kuliko {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Thamani lazima iwe ndogo kuliko au sawa na {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " au ", + "transformAndValidateErrorTextV1": "Thamani haiwezi kubadilishwa", + "transformAndValidateErrorTextV2": "Thamani lazima iwe {transformedResultTypeDescription} halali", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_ta.arb b/lib/l10n/intl_ta.arb index 71176758..11f35ad7 100644 --- a/lib/l10n/intl_ta.arb +++ b/lib/l10n/intl_ta.arb @@ -1,5 +1,19 @@ { "@@locale": "ta", + "andSeparator": " மற்றும் ", + "betweenLengthErrorText": "மதிப்பு {min} மற்றும் {max} இடையே நீளம் கொண்டிருக்க வேண்டும், உள்ளடங்கலாக.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "இந்த உள்ளீட்டுக்கு சரியான கிரெடிட் கார்டு எண் தேவை.", "dateStringErrorText": "இந்த உள்ளீட்டுக்கு சரியான தேதி தேவை.", "emailErrorText": "இந்த உள்ளீட்டுக்கு சரியான மின்னஞ்சல் முகவரி தேவை.", @@ -86,5 +100,125 @@ "vinErrorText": "மதிப்பு ஒரு செல்லுபடியான வாகன அடையாள எணாக (VIN) இருக்க வேண்டும்.", "languageCodeErrorText": "மதிப்பு ஒரு செல்லுபடியான மொழி குறியீடாக இருக்க வேண்டும்.", "floatErrorText": "மதிப்பு சரியான மிதக்கும் புள்ளி எண் ஆக இருக்க வேண்டும்.", - "hexadecimalErrorText": "மதிப்பு சரியான ஹெக்சாடெசிமல் எண் ஆக இருக்க வேண்டும்." + "hexadecimalErrorText": "மதிப்பு சரியான ஹெக்சாடெசிமல் எண் ஆக இருக்க வேண்டும்.", + "betweenNumErrorText": "மதிப்பு {minInclusive, select, true{இதற்கு சமமாகவோ அல்லது அதிகமாகவோ} other{இதற்கு மேல்}} {min} மற்றும் {maxInclusive, select, true{இதற்கு சமமாகவோ அல்லது குறைவாகவோ} other{இதற்கு குறைவாக}} {max} இருக்க வேண்டும்", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "இந்த புலம் சரியான பூலியன் மதிப்பை (உண்மை அல்லது பொய்) தேவைப்படுகிறது", + "dateMustBeAfterErrorText": "தேதி {reference}க்கு பிறகு இருக்க வேண்டும்", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "தேதி {reference}க்கு முன் இருக்க வேண்டும்", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "தேதி {minReference} மற்றும் {maxReference}க்கு இடையில் இருக்க வேண்டும்", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "இந்த புலம் சரியான தேதி மற்றும் நேரத்தை தேவைப்படுகிறது", + "greaterThanErrorText": "மதிப்பு {min}ஐ விட அதிகமாக இருக்க வேண்டும்", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "மதிப்பு {min}ஐ விட அதிகமாக அல்லது சமமாக இருக்க வேண்டும்", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "இந்த புலம் விருப்பத்திற்குரியது, இல்லையெனில், {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "இந்த புலம் சரியான சரத்தை தேவைப்படுகிறது", + "lessThanErrorText": "மதிப்பு {max}ஐ விட குறைவாக இருக்க வேண்டும்", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "மதிப்பு {max}ஐ விட குறைவாக அல்லது சமமாக இருக்க வேண்டும்", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " அல்லது ", + "transformAndValidateErrorTextV1": "மதிப்பை மாற்ற முடியவில்லை", + "transformAndValidateErrorTextV2": "மதிப்பு சரியான {transformedResultTypeDescription}ஆக இருக்க வேண்டும்", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_th.arb b/lib/l10n/intl_th.arb index 410eadfa..ebcd0e48 100644 --- a/lib/l10n/intl_th.arb +++ b/lib/l10n/intl_th.arb @@ -1,5 +1,19 @@ { "@@locale": "th", + "andSeparator": " และ ", + "betweenLengthErrorText": "ค่าต้องมีความยาวระหว่าง {min} และ {max} รวมทั้งสองค่า", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "ข้อมูลนี้ต้องเป็นเลขบัตรเครดิตเท่านั้น", "dateStringErrorText": "ข้อมูลนี้ต้องเป็นวันที่เท่านั้น", "emailErrorText": "กรุณาระบุ email ของคุณ", @@ -86,5 +100,125 @@ "vinErrorText": "ค่าต้องเป็นหมายเลข VIN ที่ถูกต้อง", "languageCodeErrorText": "ค่าต้องเป็นรหัสภาษาที่ถูกต้อง", "floatErrorText": "ค่าต้องเป็นตัวเลขทศนิยมที่ถูกต้อง", - "hexadecimalErrorText": "ค่าต้องเป็นเลขฐานสิบหกที่ถูกต้อง" + "hexadecimalErrorText": "ค่าต้องเป็นเลขฐานสิบหกที่ถูกต้อง", + "betweenNumErrorText": "ค่าต้องอยู่ {minInclusive, select, true{มากกว่าหรือเท่ากับ} other{มากกว่า}} {min} และ {maxInclusive, select, true{น้อยกว่าหรือเท่ากับ} other{น้อยกว่า}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "ฟิลด์นี้ต้องการค่าบูลีนที่ถูกต้อง (จริงหรือเท็จ)", + "dateMustBeAfterErrorText": "วันที่ต้องอยู่หลัง {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "วันที่ต้องอยู่ก่อน {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "วันที่ต้องอยู่ระหว่าง {minReference} และ {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "ฟิลด์นี้ต้องการวันที่และเวลาที่ถูกต้อง", + "greaterThanErrorText": "ค่าต้องมากกว่า {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "ค่าต้องมากกว่าหรือเท่ากับ {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "ฟิลด์นี้เป็นตัวเลือก มิฉะนั้น {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "ฟิลด์นี้ต้องการสตริงที่ถูกต้อง", + "lessThanErrorText": "ค่าต้องน้อยกว่า {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "ค่าต้องน้อยกว่าหรือเท่ากับ {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " หรือ ", + "transformAndValidateErrorTextV1": "ไม่สามารถแปลงค่าได้", + "transformAndValidateErrorTextV2": "ค่าต้องเป็น {transformedResultTypeDescription} ที่ถูกต้อง", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index d163ce6a..711ca988 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1,5 +1,19 @@ { "@@locale": "tr", + "andSeparator": " ve ", + "betweenLengthErrorText": "Değer, {min} ile {max} arasında bir uzunluğa sahip olmalıdır, dahil.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Bu alan geçerli bir kredi kartı numarası gerektirir.", "dateStringErrorText": "Bu alan geçerli bir tarih gerektirir.", "emailErrorText": "Bu alan geçerli bir e-posta adresi gerektirir.", @@ -86,5 +100,125 @@ "vinErrorText": "Değer geçerli bir VIN numarası olmalıdır.", "languageCodeErrorText": "Değer geçerli bir dil kodu olmalıdır.", "floatErrorText": "Değer geçerli bir noktalı sayı olmalıdır.", - "hexadecimalErrorText": "Değer geçerli bir onaltılık sayı olmalıdır." + "hexadecimalErrorText": "Değer geçerli bir onaltılık sayı olmalıdır.", + "betweenNumErrorText": "Değer {minInclusive, select, true{büyük veya eşit olmalı} other{büyük olmalı}} {min} ve {maxInclusive, select, true{küçük veya eşit olmalı} other{küçük olmalı}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Bu alan geçerli bir boolean değeri (true veya false) gerektirir.", + "dateMustBeAfterErrorText": "Tarih {reference} tarihinden sonra olmalıdır.", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Tarih {reference} tarihinden önce olmalıdır.", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Tarih {minReference} ve {maxReference} arasında olmalıdır.", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Bu alan geçerli bir tarih ve saat gerektirir.", + "greaterThanErrorText": "Değer {min} değerinden büyük olmalıdır.", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Değer {min} değerinden büyük veya eşit olmalıdır.", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Bu alan isteğe bağlıdır, aksi takdirde {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Bu alan geçerli bir metin gerektirir.", + "lessThanErrorText": "Değer {max} değerinden küçük olmalıdır.", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Değer {max} değerinden küçük veya eşit olmalıdır.", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " veya ", + "transformAndValidateErrorTextV1": "Değer dönüştürülemiyor.", + "transformAndValidateErrorTextV2": "Değer geçerli bir {transformedResultTypeDescription} olmalıdır.", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_uk.arb b/lib/l10n/intl_uk.arb index d7149043..e3fa6322 100644 --- a/lib/l10n/intl_uk.arb +++ b/lib/l10n/intl_uk.arb @@ -1,5 +1,19 @@ { "@@locale": "uk", + "andSeparator": " і ", + "betweenLengthErrorText": "Значення повинно мати довжину від {min} до {max} включно.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Значення поля має бути номером кредитної картки.", "dateStringErrorText": "Поле має бути датою.", "emailErrorText": "Поле має бути email адресою.", @@ -86,5 +100,125 @@ "vinErrorText": "Значення повинно бути дійсним VIN.", "languageCodeErrorText": "Значення повинно бути дійсним кодом мови.", "floatErrorText": "Значення повинно бути дійсним числом з плаваючою комою.", - "hexadecimalErrorText": "Значення повинно бути дійсним шістнадцятковим числом." + "hexadecimalErrorText": "Значення повинно бути дійсним шістнадцятковим числом.", + "betweenNumErrorText": "Значення має бути {minInclusive, select, true{більше або дорівнювати} other{більше ніж}} {min} та {maxInclusive, select, true{менше або дорівнювати} other{менше ніж}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Це поле вимагає дійсного логічного значення (true або false)", + "dateMustBeAfterErrorText": "Дата має бути після {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Дата має бути до {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Дата має бути між {minReference} та {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Це поле вимагає дійсної дати та часу", + "greaterThanErrorText": "Значення має бути більше ніж {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Значення має бути більше або дорівнювати {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Поле є необов'язковим, в іншому випадку {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Це поле вимагає дійсного текстового рядка", + "lessThanErrorText": "Значення має бути менше ніж {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Значення має бути менше або дорівнювати {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " або ", + "transformAndValidateErrorTextV1": "Значення неможливо перетворити", + "transformAndValidateErrorTextV2": "Значення має бути дійсним {transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index b82dd2f4..8028309f 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -1,5 +1,19 @@ { "@@locale": "vi", + "andSeparator": " và ", + "betweenLengthErrorText": "Giá trị phải có độ dài trong khoảng từ {min} đến {max}, bao gồm cả hai giá trị.", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "Yêu cầu nhập đúng số thẻ tín dụng.", "dateStringErrorText": "Yêu cầu nhập đúng định dạng ngày.", "emailErrorText": "Nhập đúng email.", @@ -86,5 +100,125 @@ "vinErrorText": "Giá trị phải là số VIN hợp lệ.", "languageCodeErrorText": "Giá trị phải là mã ngôn ngữ hợp lệ.", "floatErrorText": "Giá trị phải là một số dấu phẩy động hợp lệ.", - "hexadecimalErrorText": "Giá trị phải là một số thập lục phân hợp lệ." + "hexadecimalErrorText": "Giá trị phải là một số thập lục phân hợp lệ.", + "betweenNumErrorText": "Giá trị phải {minInclusive, select, true{lớn hơn hoặc bằng} other{lớn hơn}} {min} và {maxInclusive, select, true{nhỏ hơn hoặc bằng} other{nhỏ hơn}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "Trường này yêu cầu một giá trị boolean hợp lệ (true hoặc false)", + "dateMustBeAfterErrorText": "Ngày phải sau {reference}", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "Ngày phải trước {reference}", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "Ngày phải nằm giữa {minReference} và {maxReference}", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "Trường này yêu cầu một ngày giờ hợp lệ", + "greaterThanErrorText": "Giá trị phải lớn hơn {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "Giá trị phải lớn hơn hoặc bằng {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "Trường này là tùy chọn, nếu không thì {nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "Trường này yêu cầu một chuỗi hợp lệ", + "lessThanErrorText": "Giá trị phải nhỏ hơn {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "Giá trị phải nhỏ hơn hoặc bằng {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " hoặc ", + "transformAndValidateErrorTextV1": "Không thể chuyển đổi giá trị", + "transformAndValidateErrorTextV2": "Giá trị phải là một {transformedResultTypeDescription} hợp lệ", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 1f433690..91c2a9dd 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1,5 +1,19 @@ { "@@locale": "zh", + "andSeparator": " 和 ", + "betweenLengthErrorText": "值的长度必须在 {min} 和 {max} 之间(包含)。", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "该字段必须是有效的信用卡号。", "dateStringErrorText": "该字段必须是有效的时间日期。", "emailErrorText": "该字段必须是有效的电子邮件地址。", @@ -86,5 +100,125 @@ "vinErrorText": "值必须是有效的车辆识别码(VIN)。", "languageCodeErrorText": "值必须是有效的语言代码。", "floatErrorText": "值必须是有效的浮点数。", - "hexadecimalErrorText": "值必须是有效的十六进制数。" + "hexadecimalErrorText": "值必须是有效的十六进制数。", + "betweenNumErrorText": "数值必须{minInclusive, select, true{大于或等于} other{大于}}{min}且{maxInclusive, select, true{小于或等于} other{小于}}{max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "此字段需要有效的布尔值(true或false)", + "dateMustBeAfterErrorText": "日期必须在{reference}之后", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "日期必须在{reference}之前", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "日期必须在{minReference}和{maxReference}之间", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "此字段需要有效的日期时间", + "greaterThanErrorText": "数值必须大于{min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "数值必须大于或等于{min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "此字段是可选的,否则,{nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "此字段需要有效的字符串", + "lessThanErrorText": "数值必须小于{max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "数值必须小于或等于{max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": "或", + "transformAndValidateErrorTextV1": "无法转换该值", + "transformAndValidateErrorTextV2": "值必须是有效的{transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/l10n/intl_zh_Hant.arb b/lib/l10n/intl_zh_Hant.arb index 8f76cdf9..5f541606 100644 --- a/lib/l10n/intl_zh_Hant.arb +++ b/lib/l10n/intl_zh_Hant.arb @@ -1,5 +1,19 @@ { "@@locale": "zh_Hant", + "andSeparator": " 和 ", + "betweenLengthErrorText": "值的長度必須在 {min} 和 {max} 之間(包含)。", + "@betweenLengthErrorText": { + "placeholders": { + "min": { + "type": "int", + "description": "The minimum length allowed" + }, + "max": { + "type": "int", + "description": "The maximum length allowed" + } + } + }, "creditCardErrorText": "此欄位需要有效的信用卡號碼。", "dateStringErrorText": "此欄位需要有效的日期字符串。", "emailErrorText": "此欄位需要有效的電子郵件地址。", @@ -86,5 +100,125 @@ "vinErrorText": "值必須是一個有效的車輛識別號。", "languageCodeErrorText": "值必須是一個有效的語言代碼。", "floatErrorText": "數值必須為有效的浮點數。", - "hexadecimalErrorText": "數值必須為有效的十六進位數字。" + "hexadecimalErrorText": "數值必須為有效的十六進位數字。", + "betweenNumErrorText": "數值必須{minInclusive, select, true{大於或等於} other{大於}} {min} 且 {maxInclusive, select, true{小於或等於} other{小於}} {max}", + "@betweenNumErrorText": { + "description": "Error text when a value must be within a specific range", + "placeholders": { + "min": { + "type": "num", + "description": "The minimum allowed value" + }, + "max": { + "type": "num", + "description": "The maximum allowed value" + }, + "minInclusive": { + "type": "String", + "description": "Whether the minimum bound is inclusive" + }, + "maxInclusive": { + "type": "String", + "description": "Whether the maximum bound is inclusive" + } + } + }, + "booleanErrorText": "此欄位需要有效的布林值(true 或 false)", + "dateMustBeAfterErrorText": "日期必須在 {reference} 之後", + "@dateMustBeAfterErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + } + } + }, + "dateMustBeBeforeErrorText": "日期必須在 {reference} 之前", + "@dateMustBeBeforeErrorText": { + "placeholders": { + "reference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateMustBeBetweenErrorText": "日期必須在 {minReference} 和 {maxReference} 之間", + "@dateMustBeBetweenErrorText": { + "placeholders": { + "minReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The minimum date allowed" + }, + "maxReference": { + "type": "DateTime", + "format": "yMd", + "example": "11/10/2021", + "description": "The maximum date allowed" + } + } + }, + "dateTimeErrorText": "此欄位需要有效的日期時間", + "greaterThanErrorText": "數值必須大於 {min}", + "@greaterThanErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "greaterThanOrEqualToErrorText": "數值必須大於或等於 {min}", + "@greaterThanOrEqualToErrorText": { + "placeholders": { + "min": { + "type": "num", + "description": "The minimum value allowed" + } + } + }, + "isOptionalErrorText": "此欄位為選填,否則,{nextErrorMessage}", + "@isOptionalErrorText": { + "placeholders": { + "nextErrorMessage": { + "type": "String", + "description": "The error text from the next validator." + } + } + }, + "isStringErrorText": "此欄位需要有效的字串", + "lessThanErrorText": "數值必須小於 {max}", + "@lessThanErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "lessThanOrEqualToErrorText": "數值必須小於或等於 {max}", + "@lessThanOrEqualToErrorText": { + "placeholders": { + "max": { + "type": "num", + "description": "The maximum value allowed" + } + } + }, + "orSeparator": " 或 ", + "transformAndValidateErrorTextV1": "無法轉換此數值", + "transformAndValidateErrorTextV2": "數值必須是有效的{transformedResultTypeDescription}", + "@transformAndValidateErrorTextV2": { + "placeholders": { + "transformedResultTypeDescription": { + "type": "String", + "description": "A description for the expected transformed type." + } + } + } } \ No newline at end of file diff --git a/lib/src/form_builder_validators.dart b/lib/src/form_builder_validators.dart index 452d3b14..d24dcbba 100644 --- a/lib/src/form_builder_validators.dart +++ b/lib/src/form_builder_validators.dart @@ -2,6 +2,11 @@ import 'package:flutter/widgets.dart'; import '../form_builder_validators.dart'; +//import 'validators/constants.dart'; // Uncomment after removing the deprecated code. +import 'validators/network_validators.dart'; +import 'validators/validators.dart' as val; + +@Deprecated('use the base class Validators instead') /// Provides utility methods for creating various [FormFieldValidator]s. class FormBuilderValidators { @@ -1732,3 +1737,2442 @@ class FormBuilderValidators { checkNullOrEmpty: checkNullOrEmpty, ).validate; } + +/// A class that is used as an aggregator/namespace for all the available +/// validators in this package. +final class Validators { + // Composition validators + /// {@template validator_and} + /// Creates a composite validator that applies multiple validation rules from + /// `validators` using AND logic. + /// + /// The validator executes each validation rule in sequence and handles errors + /// in one of two modes: + /// + /// 1. Fast-fail mode (`printErrorAsSoonAsPossible = true`): + /// - Returns the first encountered error message + /// - Stops validation after first failure + /// + /// 2. Aggregate mode (`printErrorAsSoonAsPossible = false`): + /// - Collects all error messages + /// - Combines them as: `prefix + msg1 + separator + msg2 + ... + msgN + suffix` + /// + /// Returns `null` if all validations pass. + /// + /// ## Example + /// ```dart + /// final validator = and([ + /// isEmail, + /// fromGmail, + /// ], separator: ' * '); + /// ``` + /// + /// ## Parameters + /// - `validators`: List of validation functions to apply + /// - `prefix`: String to prepend to combined error message (default: '') + /// - `suffix`: String to append to combined error message (default: '') + /// - `separator`: String between error messages + /// (default: FormBuilderLocalizations.current.andSeparator) + /// - `printErrorAsSoonAsPossible`: Whether to return first error or combine all + /// (default: true) + /// + /// ## Returns + /// - `null` if all validation passes + /// - Validation failure message, otherwise + /// + /// ## Throws + /// - [ArgumentError] if `validators` is empty + /// {@endtemplate} + static Validator and( + List> validators, { + String prefix = '', + String suffix = '', + String? separator, + bool printErrorAsSoonAsPossible = true, + }) => + val.and(validators, + prefix: prefix, + suffix: suffix, + separator: separator, + printErrorAsSoonAsPossible: printErrorAsSoonAsPossible); + + /// {@template validator_or} + /// Creates a composite validator that applies multiple validation rules from + /// `validators` using OR logic. + /// + /// The validator executes each validation rule in sequence until one passes + /// (returns null) or all fail. If all validators fail, their error messages + /// are combined as: `prefix + msg1 + separator + msg2 + ... + msgN + suffix` + /// + /// ## Example + /// ```dart + /// final validator = or([ + /// isGmail, + /// isYahoo, + /// ], separator: ' + '); + /// ``` + /// + /// ## Parameters + /// - `validators`: List of validation functions to apply + /// - `prefix`: String to prepend to combined error message (default: '') + /// - `suffix`: String to append to combined error message (default: '') + /// - `separator`: String between error messages + /// (default: FormBuilderLocalizations.current.orSeparator) + /// + /// ## Returns + /// - `null` if any validation passes + /// - Combined error message if all validations fail + /// + /// ## Throws + /// - [ArgumentError] if `validators` is empty + /// {@endtemplate} + static Validator or( + List> validators, { + String prefix = '', + String suffix = '', + String? separator, + }) => + val.or( + validators, + prefix: prefix, + suffix: suffix, + separator: separator, + ); + + // Conditional validators + /// {@template validator_validate_if} + /// Creates a conditional validator that only applies validation when a specified + /// condition is met. + /// + /// The validator first evaluates the `condition` function with the input value. + /// If the condition returns true, it applies the validation rule `v`. + /// If the condition returns false, the validation automatically passes. + /// + /// ## Example + /// ```dart + /// final validator = validateIf( + /// (value) => value.startsWith('http'), + /// isValidUrl, + /// ); + /// ``` + /// + /// ## Parameters + /// - `condition`: Function that determines if validation should be applied + /// - `v`: Validation function to apply when condition is true + /// + /// ## Returns + /// - `null` if condition is false or validation passes + /// - Validation failure message from `v` if condition is true and validation fails + /// + /// ## Type Parameters + /// - `T`: Type of value being validated, may be a nullable Object + /// {@endtemplate} + static Validator validateIf( + bool Function(T value) condition, Validator v) => + val.validateIf(condition, v); + + /// {@template validator_skip_if} + /// Creates a validator that conditionally bypasses validation based on a + /// predicate function. + /// + /// First evaluates `condition` with the input value. If true, validation is + /// skipped and automatically passes. If false, applies validation rule `v`. + /// + /// ## Example + /// ```dart + /// final validator = skipIf( + /// (value) => value.isEmpty, + /// validateEmail, + /// ); + /// ``` + /// + /// ## Parameters + /// - `condition`: Predicate function determining if validation should be skipped + /// - `v`: Validation function to apply when condition is false + /// + /// ## Returns + /// - `null` if condition is true or validation passes + /// - Validation failure message from `v` if condition is false and validation fails + /// + /// ## Type Parameters + /// - `T`: Type of value being validated, may be a nullable Object + /// {@endtemplate} + static Validator skipIf( + bool Function(T value) condition, Validator v) => + val.skipIf(condition, v); + + // Debug print validator + /// {@template validator_debug_print_validator} + /// Creates a validator that logs input values to stdout before optionally applying + /// another validator. + /// + /// ## Example + /// ```dart + /// final validator = debugPrintValidator( + /// next: validateEmail, + /// logOnInput: (value) => 'Email input: $value', + /// ); + /// ``` + /// + /// ## Parameters + /// - `next`: Optional validator to apply after logging + /// - `logOnInput`: Optional function to customize log message format + /// + /// ## Returns + /// - `null` if no `next` validator or if validation passes + /// - Validation failure from `next` validator if validation fails + /// + /// ## Type Parameters + /// - `T`: Type of value being validated, may be a nullable Object + /// {@endtemplate} + static Validator debugPrintValidator( + {Validator? next, String Function(T input)? logOnInput}) => + val.debugPrintValidator(next: next, logOnInput: logOnInput); + + // Equality validators + /// {@template validator_is_equal} + /// Creates a validator that checks if a given input matches `referenceValue` + /// using the equality (`==`) operator. + /// + /// + /// ## Parameters + /// - `referenceValue` (`T`): The value to compare against the input. This serves as + /// the reference for equality checking. + /// - `isEqualMsg` (`String Function(T input, T referenceValue)?`): Optional + /// custom error message generator. Takes the `input` and the `referenceValue` + /// as parameters and returns a custom error message. + /// + /// ## Type Parameters + /// - `T`: The type of value being validated. Must extend `Object?` to allow for + /// nullable types. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input matches the target value + /// - Returns an error message if the values don't match, either from the custom + /// `isEqualMsg` function or the default localized error text. + /// + /// ## Examples + /// ```dart + /// // Basic usage for password confirmation + /// final confirmAction = isEqual('Type this to confirm the action'); + /// assert(confirmAction('Type this to confirm the action') == null); // null returned (validation passes) + /// assert(confirmAction(12345) != null); // Error message returned + /// + /// // Using custom error message + /// final specificValueValidator = isEqual( + /// 42, + /// isEqualMsg: (_, value) => 'Value must be exactly $value', + /// ); + /// ``` + /// + /// ## Caveats + /// - The comparison uses the `==` operator, which may not be suitable for complex + /// objects without proper equality implementation + /// - The error message uses the string representation of the value via + /// `toString()`, which might not be ideal for all types. + /// {@endtemplate} + static Validator isEqual( + T value, { + String Function(T input, T referenceValue)? isEqualMsg, + }) => + val.isEqual(value, isEqualMsg: isEqualMsg); + + /// {@template validator_is_not_equal} + /// Creates a validator that checks if a given input is not equal to + /// `referenceValue` using the not-equal (`!=`) operator. + /// + /// ## Parameters + /// - `referenceValue` (`T`): The reference value to compare against. Input must + /// not equal this value to pass validation. + /// - `isNotEqualMsg` (`String Function(T input, T referenceValue)?`): Optional + /// custom error message generator. Takes the `input` and the `referenceValue` + /// as parameters and returns a custom error message. + /// + /// ## Type Parameters + /// - `T`: The type of value being validated. Must extend `Object?` to allow for + /// null values and proper equality comparison. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input is not equal to the reference value + /// - Returns an error message string if the input equals the reference value + /// + /// ## Examples + /// ```dart + /// // Basic usage with strings + /// final validator = isNotEqual('reserved'); + /// assert(validator('not-reserved') == null); // null (validation passes) + /// assert(validator('reserved') != null); // "Value must not be equal to reserved" + /// + /// // Custom error message + /// final customValidator = isNotEqual( + /// 42, + /// isNotEqualMsg: (_, value) => 'Please choose a number other than $value', + /// ); + /// ``` + /// + /// ## Caveats + /// - The comparison uses the `!=` operator, which may not be suitable for complex + /// objects without proper equality implementation + /// - The error message uses the string representation of the value via + /// `toString()`, which might not be ideal for all types + /// {@endtemplate} + static Validator isNotEqual( + T value, { + String Function(T input, T referenceValue)? isNotEqualMsg, + }) => + val.isNotEqual(value, isNotEqualMsg: isNotEqualMsg); + + // Required validators + /// {@template validator_is_required} + /// Generates a validator function that enforces required field validation for + /// form inputs. This validator ensures that a field has a non-null, non-empty + /// value before any subsequent validation is performed. + /// + /// ## Type Parameters + /// - `T`: Represents the non-nullable version of the field's type that will be + /// passed to any subsequent validators. Once this validator passes, downstream + /// validators are guaranteed to receive a non-null value, eliminating the need + /// for additional null checks. + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator function that + /// will be applied after the required validation passes. This allows for + /// chaining multiple validation rules. + /// - `isRequiredMsg` (`String?`): An optional custom error message to display + /// when the field is empty or null. If not provided, defaults to the + /// localized required field error text. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns null if the value passes both required and subsequent `next` + /// validation + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic required field validation + /// final validator = isRequired(); + /// print(validator(null)); // Returns localized error message + /// print(validator('')); // Returns localized error message + /// print(validator('value')); // Returns null (validation passed) + /// + /// // Chaining with another validator + /// final complexValidator = isRequired( + /// (value) => value.length < 10 ? 'Too long' : null, + /// 'Custom required message' + /// ); + /// ``` + /// + /// ## Caveats + /// - The validator assumes empty strings/maps/iterables, white strings, and null + /// values are equivalent for validation purposes + /// {@endtemplate} + static Validator isRequired([ + Validator? next, + String? isRequiredMsg, + ]) => + val.isRequired(next, isRequiredMsg); + + /// {@template validator_validate_with_default} + /// Creates a validator function that applies a default value before validation, + /// making sure the `next` validator will always receive a non-null input. + /// + /// This function generates a new validator that first replaces null input + /// with a specified default value, then applies `next` validator. + /// + /// ## Type Parameters + /// - `T`: The non-nullable version of the type of the input being validated. + /// It must extend from `Object`. + /// + /// ## Parameters + /// - `defaultValue` (`T`): The fallback non-null value to use when input is null. + /// - `next` (`Validator`): The validation function to apply after the default + /// value has been potentially substituted. + /// + /// ## Returns + /// Returns a new `Validator` function that accepts nullable input and + /// produces validation results based on the combined default value substitution + /// and validation logic. The returned validator is a function that: + /// - Returns null if the value, potentially replaced with the default, passes + /// the `next` validation + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Create a validator that requires a minimum length of 3 + /// final minLength = (String value) => + /// value.length >= 3 ? null : 'Must be at least 3 characters'; + /// + /// // Wrap it with a default value of 'N/A' + /// final defaultValue = 'default value'; + /// final validator = validateWithDefault('N/A', minLength); + /// + /// print(validator(null)); // Returns null (valid) + /// print(validator('ab')); // Returns 'Must be at least 3 characters' + /// print(validator('abc')); // Returns null (valid) + /// // Equivalent to: + /// print(minLength(null ?? defaultValue)); // Returns null (valid) + /// print(minLength('ab' ?? defaultValue)); // Returns 'Must be at least 3 characters' + /// print(minLength('abc' ?? defaultValue)); // Returns null (valid) + /// ``` + /// {@endtemplate} + static Validator validateWithDefault( + T defaultValue, Validator next) => + val.validateWithDefault(defaultValue, next); + + /// {@template validator_is_optional} + /// Creates a validator function that makes a field optional while allowing additional validation + /// rules. This validator is particularly useful in form validation scenarios where certain + /// fields are not mandatory but still need to conform to specific rules when provided. + /// + /// The validator handles various input types including strings, iterables, and maps, + /// considering them as "not provided" when they are null, empty, or contain only whitespace + /// (for strings). + /// + /// ## Type Parameters + /// - `T`: Represents the non-nullable version of the field's type that will be + /// passed to any subsequent validators. Once a non-null value is passed, downstream + /// validators are guaranteed to receive a non-null value, eliminating the need + /// for additional null checks. + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator function that will be + /// applied only if the input value is provided (non-null and non-empty). This allows + /// for chaining validation rules. + /// - `isOptionalMsg` (`String Function(T input, String nextErrorMessage)?`): An + /// optional error message that takes the `input` and the `nextErrorMessage` as + /// parameters and returns the custom error message. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input is not provided (indicating valid optional field) + /// - Returns `null` if the non-null/non-empty input passes the `next` validation + /// rules. + /// - Returns a formatted error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic optional string validator + /// final validator = isOptional(); + /// + /// // Optional validator with additional email validation + /// final emailValidator = isOptional( + /// validateEmail, + /// (_, error) => 'Invalid email format: $error', + /// ); + /// + /// // Usage with different inputs + /// print(validator(null)); // Returns: null (valid) + /// print(validator('')); // Returns: null (valid) + /// print(emailValidator('invalid@email')); // Returns: error message + /// ``` + /// + /// ## Caveats + /// - The validator assumes empty strings/maps/iterables, white strings, and null values are + /// equivalent for validation purposes, all them are considered valid. + /// {@endtemplate} + static Validator isOptional([ + Validator? next, + String Function(T input, String nextErrorMsg)? isOptionalMsg, + ]) => + val.isOptional(next, isOptionalMsg); + + // Transform Validator + + /// {@template validator_transform_and_validate} + /// Creates a validator that transforms user input and optionally chains with the `next` validator. + /// This validator attempts to transform the input using the provided transformation function. + /// If the transformation succeeds, it either returns null or passes the transformed value + /// to the next validator in the chain. If the transformation fails, it returns an error message. + /// + /// The validator is particularly useful for type conversions and data transformations where + /// the transformation itself serves as a validation step. For example, converting string + /// input to numbers or dates where invalid formats should be treated as validation failures. + /// + /// ## Type Parameters + /// - `IN`: The input type to be transformed. Must be nullable or non-nullable Object. + /// - `OUT`: The output type after transformation. Must be nullable or non-nullable Object. + /// + /// ## Parameters + /// - `transformFunction` (`OUT Function(IN)`): The function that performs the actual + /// transformation from type `IN` to type `OUT`. This function should throw an exception + /// if the transformation cannot be performed. + /// - `next` (`Validator?`): Optional validator to process the transformed value. + /// If provided, its result will be returned when transformation succeeds. + /// - `transformAndValidateMsg` (`String Function(IN)?`): Optional function that generates + /// a custom error message when transformation fails. Receives the original input as + /// an argument. + /// - `transformedResultTypeDescription` (`String?`): Optional description of the expected + /// transformed type, used to generate more readable error messages. For example, + /// "positive integer" would result in messages like 'Value is not a valid positive integer'. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if transformation succeeds and no `next` validator is provided + /// - Returns the result of the `next` validator if transformation succeeds and a `next` + /// validator is provided + /// - Returns an error message string if transformation fails + /// + /// ## Examples + /// ```dart + /// // Creating a validator that converts strings to ArithmeticExpression: + /// final arithmeticExprValidator = transformAndValidate( + /// parseToArithmeticExpression, + /// transformedResultTypeDescription: 'arithmetic expression', + /// ); + /// + /// // Example usage: + /// final validator = arithmeticExprValidator; + /// print(validator('2+3')); // null (valid) + /// print(validator('2+Hello World+3')); // "Value is not a valid arithmetic expression" + /// print(validator('1+2+3+4+5+6+7+8+9+10')); // null (valid) + /// ``` + /// {@endtemplate} + static Validator + transformAndValidate( + OUT Function(IN) transformFunction, { + Validator? next, + String Function(IN)? transformAndValidateMsg, + String? transformedResultTypeDescription, + }) => + val.transformAndValidate( + transformFunction, + next: next, + transformAndValidateMsg: transformAndValidateMsg, + transformedResultTypeDescription: transformedResultTypeDescription, + ); + + // Type Validator + /// {@template validator_is_string} + /// Creates a validator that verifies if an input value is a [String]. If the + /// check succeeds, the transformed value will be passed to the `next` + /// validator. + /// + /// ## Type Parameters + /// - T: The type of the input value, must extend Object to ensure non-null values + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator that processes + /// the input after successful string validation. Receives the validated input + /// as a [String]. + /// - `isStringMsg` (`String Function(T input)?`): An optional custom error message + /// generator function that takes the input as parameter and returns a customized error + /// message. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns null if the input is valid and no `next` validator is provided + /// - If the input is a valid [String], returns the result of the `next` validator + /// - Returns an error message string if the input is not a [String] + /// + /// ## Examples + /// ```dart + /// // Basic string validation + /// final validator = isString(); + /// print(validator('valid string')); // null + /// print(validator(123)); // 'Must be a string' + /// + /// // With custom error message + /// final customValidator = isString( + /// isStringMsg: (input) => '${input.toString()} is not a valid String.', + /// ); + /// print(customValidator(42)); // '42 is not a valid string' + /// + /// // Chaining validators + /// final chainedValidator = isString( + /// (value) => value.isEmpty ? 'String cannot be empty' : null, + /// ); + /// print(chainedValidator('')); // 'String cannot be empty' + /// ``` + /// + /// + /// ## Caveats + /// - This validator does not automatically convert the input to [String]. For + /// example, if the input is a number, it will never transform it to the string + /// version by calling `toString` method. + /// {@endtemplate} + static Validator isString([ + Validator? next, + String Function(T input)? isStringMsg, + ]) => + val.isString(next, isStringMsg); + + /// {@template validator_is_int} + /// Creates a validator that verifies if an input value is an [int] or can be + /// parsed into an [int]. If the check succeeds, the transformed value will be + /// passed to the `next` validator. + /// + /// This validator performs two key checks: + /// 1. Direct validation of `int` types + /// 2. String parsing validation for string inputs that represent integers + /// + /// ## Type Parameters + /// - `T`: The type of the input value. Must extend `Object` to ensure non-null + /// values. + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator that receives + /// the converted integer value for additional validation + /// - `isIntMsg` (`String Function(T input)?`): Optional custom error message + /// generator function that receives the invalid input and returns an error + /// message + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation succeeds and no `next` validator is provided + /// - Returns the result of the `next` validator if provided and initial + /// validation succeeds + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic integer validation + /// final validator = isInt(); + /// print(validator(42)); // null (valid) + /// print(validator('123')); // null (valid) + /// print(validator('abc')); // 'This field requires a valid integer.' + /// + /// // With custom error message + /// final customValidator = isInt(null, (input) => 'Custom error for: $input'); + /// print(customValidator('abc')); // 'Custom error for: abc' + /// + /// // With chained validation + /// final rangeValidator = isInt((value) => + /// value > 100 ? 'Must be less than 100' : null); + /// print(rangeValidator('150')); // 'Must be less than 100' + /// ``` + /// + /// ## Caveats + /// - If the input is [String], it will be parsed by the [int.tryParse] method. + /// {@endtemplate} + static Validator isInt([ + Validator? next, + String Function(T input)? isIntMsg, + ]) => + val.isInt(next, isIntMsg); + + /// {@template validator_is_double} + /// Creates a validator that verifies if an input value is a [double] or can be + /// parsed into a [double]. If the check succeeds, the transformed value will be + /// passed to the `next` validator. + /// + /// This validator performs two key checks: + /// 1. Direct validation of `double` types + /// 2. String parsing validation for string inputs that represent doubles + /// + /// ## Type Parameters + /// - `T`: The type of the input value. Must extend `Object` to ensure non-null + /// values + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator that receives + /// the converted numeric value for additional validation + /// - `isDoubleMsg` (`String Function(T input)?`): Optional custom error message + /// generator function that receives the invalid input and returns an error + /// message + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation succeeds and no `next` validator is provided + /// - Returns the result of the `next` validator if provided and initial + /// validation succeeds + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic number validation + /// final validator = isDouble(); + /// print(validator(42.0)); // null (valid) + /// print(validator(3.14)); // null (valid) + /// print(validator('123.45')); // null (valid) + /// print(validator('1e-4')); // null (valid) + /// print(validator('abc')); // 'Please enter a valid number' + /// + /// // With custom error message + /// final customValidator = isDouble(null, (input) => 'Invalid number: $input'); + /// print(customValidator('abc')); // 'Invalid number: abc' + /// + /// // With chained validation + /// final rangeValidator = isDouble((value) => + /// value > 1000 ? 'Must be less than 1000' : null); + /// print(rangeValidator('1500')); // 'Must be less than 1000' + /// ``` + /// + /// ## Caveats + /// - If the input is [String], it will be parsed by the [double.tryParse] method. + /// {@endtemplate} + static Validator isDouble([ + Validator? next, + String Function(T input)? isDoubleMsg, + ]) => + val.isDouble(next, isDoubleMsg); + + /// {@template validator_is_num} + /// Creates a validator that verifies if an input value is a [num] or can be + /// parsed into a [num]. If the check succeeds, the transformed value will be + /// passed to the `next` validator. + /// + /// This validator performs two key checks: + /// 1. Direct validation of `num` types (including both `int` and `double`) + /// 2. String parsing validation for string inputs that represent numbers + /// + /// ## Type Parameters + /// - `T`: The type of the input value. Must extend `Object` to ensure non-null + /// values + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator that receives + /// the converted numeric value for additional validation + /// - `isNumMsg` (`String Function(T input)?`): Optional custom error message + /// generator function that receives the invalid input and returns an error + /// message + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation succeeds and no `next` validator is provided + /// - Returns the result of the `next` validator if provided and initial + /// validation succeeds + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic number validation + /// final validator = isNum(); + /// print(validator(42)); // null (valid) + /// print(validator(3.14)); // null (valid) + /// print(validator('123.45')); // null (valid) + /// print(validator('1e-4')); // null (valid) + /// print(validator('abc')); // 'Please enter a valid number' + /// + /// // With custom error message + /// final customValidator = isNum(null, (input) => 'Invalid number: $input'); + /// print(customValidator('abc')); // 'Invalid number: abc' + /// + /// // With chained validation + /// final rangeValidator = isNum((value) => + /// value > 1000 ? 'Must be less than 1000' : null); + /// print(rangeValidator('1500')); // 'Must be less than 1000' + /// ``` + /// + /// ## Caveats + /// - If the input is [String], it will be parsed by the [num.tryParse] method. + /// {@endtemplate} + static Validator isNum([ + Validator? next, + String Function(T input)? isNumMsg, + ]) => + val.isNum(next, isNumMsg); + + /// {@template validator_is_bool} + /// Creates a validator that verifies if an input value is a [bool] or can be + /// parsed into a [bool]. If the check succeeds, the transformed value will be + /// passed to the `next` validator. + /// + /// This validator performs two key checks: + /// 1. Direct validation of `bool` types + /// 2. String parsing validation for string inputs that represent booleans + /// + /// ## Type Parameters + /// - `T`: The type of the input value. Must extend `Object` to ensure non-null + /// values + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator that receives + /// the converted boolean value for additional validation + /// - `isBoolMsg` (`String Function(T input)?`): Optional custom error message + /// generator function that receives the invalid input and returns an error + /// message + /// - `caseSensitive` (`bool`): Controls whether string parsing is case-sensitive. + /// When `false`, values like 'TRUE', 'True', and 'true' are all valid. Defaults + /// to `false` + /// - `trim` (`bool`): Controls whether to remove whitespace before parsing string + /// inputs. When `true`, strings like ' true ' and 'false\n' are valid. Defaults + /// to `true` + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation succeeds and no `next` validator is provided + /// - Returns the result of the `next` validator if provided and initial + /// validation succeeds + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic boolean validation + /// final validator = isBool(); + /// print(validator(true)); // null (valid) + /// print(validator('true')); // null (valid) + /// print(validator('TRUE')); // null (valid) + /// print(validator(' false ')); // null (valid) + /// print(validator('abc')); // 'This field requires a valid boolean (true or false).' + /// + /// // With case sensitivity + /// final strictValidator = isBool(null, null, true); + /// print(strictValidator('True')); // 'This field requires a valid boolean (true or false).' + /// print(strictValidator('true')); // null (valid) + /// + /// // Without trimming + /// final noTrimValidator = isBool(null, null, false, false); + /// print(noTrimValidator(' true')); // 'This field requires a valid boolean (true or false).' + /// + /// // With custom error message + /// final customValidator = isBool(null, (input) => 'Invalid boolean: $input'); + /// print(customValidator('abc')); // 'Invalid boolean: abc' + /// + /// // With chained validation + /// final customValidator = isBool((value) => + /// value == true ? 'Must be false' : null); + /// print(customValidator('true')); // 'Must be false' + /// ``` + /// + /// ## Caveats + /// - If the input is [String], it will be parsed by the [bool.tryParse] method + /// {@endtemplate} + static Validator isBool( + [Validator? next, + String Function(T input)? isBoolMsg, + bool caseSensitive = false, + bool trim = true]) => + val.isBool(next, isBoolMsg, caseSensitive, trim); + + /// {@template validator_is_date_time} + /// Creates a validator that verifies if an input value is a [DateTime] or can be + /// parsed into a [DateTime]. If the check succeeds, the transformed value will be + /// passed to the `next` validator. + /// + /// This validator performs two key checks: + /// 1. Direct validation of `DateTime` types + /// 2. String parsing validation for string inputs that represent dates + /// + /// ## Type Parameters + /// - `T`: The type of the input value. Must extend `Object` to ensure non-null + /// values + /// + /// ## Parameters + /// - `next` (`Validator?`): An optional subsequent validator that + /// receives the converted datetime value for additional validation + /// - `isDateTimeMsg` (`String Function(T input)?`): Optional custom error message + /// generator function that receives the invalid input and returns an error + /// message + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation succeeds and no `next` validator is provided + /// - Returns the result of the `next` validator if provided and initial + /// validation succeeds + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic datetime validation + /// final validator = isDateTime(); + /// print(validator(DateTime.now())); // null (valid) + /// print(validator('2024-12-31')); // null (valid) + /// print(validator('2024-12-31T23:59:59')); // null (valid) + /// print(validator('not a date')); // 'This field requires a valid datetime.' + /// + /// // With custom error message + /// final customValidator = isDateTime( + /// null, + /// (input) => 'Invalid date format: $input' + /// ); + /// print(customValidator('abc')); // 'Invalid date format: abc' + /// + /// // With chained validation + /// final futureValidator = isDateTime((value) => + /// value.isBefore(DateTime.now()) ? 'Date must be in the future' : null); + /// print(futureValidator('2020-01-01')); // 'Date must be in the future' + /// ``` + /// + /// ## Caveats + /// - If the input is [String], it will be parsed by the [DateTime.tryParse] method. + /// - The function parses a subset of ISO 8601, which includes the subset + /// accepted by RFC 3339. + /// {@endtemplate} + static Validator isDateTime([ + Validator? next, + String Function(T input)? isDateTimeMsg, + ]) => + val.isDateTime(next, isDateTimeMsg); + + // Path validators + /// {@template validator_matches_allowed_extensions} + /// A validator function that checks if a file path's extension matches any of + /// the specified allowed extensions. Returns `null` for valid extensions, or an + /// error message for invalid ones. + /// + /// The validator supports both single-level (e.g., '.txt') and multi-level + /// (e.g., '.tar.gz') extensions, with configurable case sensitivity. + /// + /// ## Parameters + /// - `extensions` (`List`): List of valid file extensions. Each extension must start + /// with a dot (e.g., '.pdf', '.tar.gz'). Empty string is considered a valid extension + /// - `matchesAllowedExtensionsMsg` (`String Function(List)?`): Optional custom error + /// message generator. Receives the list of allowed extensions and returns an error message + /// - `caseSensitive` (`bool`): Controls whether extension matching is case-sensitive. + /// Defaults to `true` + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input path's extension matches any allowed extension + /// - Returns an error message (custom or default) if no extension match is found + /// + /// ## Throws + /// - `AssertionError`: When `extensions` list is empty + /// - `AssertionError`: When any extension in `extensions` doesn't start with a dot + /// (except for empty string) + /// + /// ## Examples + /// ```dart + /// // Single-level extension validation + /// final validator = matchesAllowedExtensions(['.pdf', '.doc']); + /// print(validator('document.pdf')); // Returns: null + /// print(validator('document.txt')); // Returns: error message + /// + /// // Multi-level extension validation + /// final archiveValidator = matchesAllowedExtensions(['.tar.gz', '.zip']); + /// print(archiveValidator('archive.tar.gz')); // Returns: null + /// + /// // Case-insensitive validation + /// final caseValidator = matchesAllowedExtensions( + /// ['.PDF', '.DOC'], + /// caseSensitive: false + /// ); + /// print(caseValidator('document.pdf')); // Returns: null + /// ``` + /// + /// ## Caveats + /// - Extensions must explicitly include the leading dot (use '.txt' not 'txt') + /// {@endtemplate} + static Validator matchesAllowedExtensions( + List extensions, { + String Function(List)? matchesAllowedExtensionsMsg, + bool caseSensitive = true, + }) => + val.matchesAllowedExtensions( + extensions, + matchesAllowedExtensionsMsg: matchesAllowedExtensionsMsg, + caseSensitive: caseSensitive, + ); + + // String validators + + /// {@template validator_uuid} + /// A validator function that checks if a given string matches the UUID format. + /// + /// Creates a validator that ensures the input string conforms to the standard + /// UUID (Universally Unique Identifier) format, consisting of 32 hexadecimal + /// digits displayed in 5 groups separated by hyphens (8-4-4-4-12). + /// + /// ## Parameters + /// - `regex` (`RegExp?`): Optional custom regular expression pattern to override + /// the default UUID validation pattern. Useful for supporting different UUID + /// formats or adding additional constraints. + /// - `uuidMsg` (`String Function(String input)?`): Optional callback function that + /// generates a custom error message based on the invalid input. If not provided, + /// defaults to the standard form builder localization error text. + /// + /// ## Returns + /// Returns a `Validator` function that accepts a string input and returns: + /// - `null` if the input is valid + /// - An error message string if the input is invalid + /// + /// ## Examples + /// ```dart + /// // Using default UUID validation + /// final validator = uuid(); + /// print(validator('123e4567-e89b-12d3-a456-426614174000')); // null + /// print(validator('invalid-uuid')); // Returns error message + /// + /// // Using custom error message + /// final customValidator = uuid( + /// uuidMsg: (input) => 'Invalid UUID format: $input', + /// ); + /// + /// // Using custom regex pattern + /// final customPatternValidator = uuid( + /// regex: RegExp(r'^[0-9]{8}-[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{12}$'), + /// ); + /// ``` + /// + /// ## Caveats + /// - The default regex pattern accepts both uppercase and lowercase hexadecimal + /// digits (0-9, a-f, A-F) + /// - The validation only checks the format, not the actual UUID version or + /// variant compliance + /// {@endtemplate} + static Validator uuid({ + RegExp? regex, + String Function(String input)? uuidMsg, + }) => + val.uuid(regex: regex, uuidMsg: uuidMsg); + + /// {@template validator_contains} + /// Creates a validator function that checks if a string contains a specific + /// substring. The validation can be performed with or without case sensitivity. + /// + /// ## Parameters + /// - `substring` (`String`): The text pattern to search for within the validated string. + /// An empty substring will always result in successful validation. + /// - `caseSensitive` (`bool`): Determines whether the substring matching should be + /// case-sensitive. Defaults to `true`. When set to `false`, both the input value + /// and substring are converted to lowercase before comparison. + /// - `containsMsg` (`String Function(String substring, String input)?`): Optional + /// callback function that generates a custom error message. Takes the + /// substring and the user input as parameters and returns the error message + /// string. If not provided, uses the default localized error message. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the validation passes (substring is found or empty) + /// - Returns an error message string if the validation fails (substring not found) + /// + /// ## Examples + /// ```dart + /// // Case-sensitive validation + /// final validator = contains('test'); + /// print(validator('This is a test')); // Returns: null + /// print(validator('This is a TEST')); // Returns: error message + /// + /// // Case-insensitive validation + /// final caseInsensitiveValidator = contains('test', caseSensitive: false); + /// print(caseInsensitiveValidator('This is a TEST')); // Returns: null + /// + /// // Custom error message + /// final customValidator = contains( + /// 'required', + /// containsMsg: (value, _) => 'Text must contain "$value"' + /// ); + /// ``` + /// + /// ## Caveats + /// - Empty substrings are always considered valid and return `null` + /// {@endtemplate} + static Validator contains( + String substring, { + bool caseSensitive = true, + String Function(String substring, String input)? containsMsg, + }) => + val.contains( + substring, + caseSensitive: caseSensitive, + containsMsg: containsMsg, + ); + + /// {@template validator_has_min_uppercase_chars} + /// Creates a validator function that checks if the [String] input contains a + /// minimum number of uppercase characters. The validator returns `null` for + /// valid input and an error message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [hasMinUppercaseCharsMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.containsUppercaseCharErrorText(min)`. + /// + /// ## Parameters + /// - `min` (`int`): The minimum number of uppercase characters required. Defaults + /// to 1. + /// - `customUppercaseCounter` (`int Function(String)?`): Optional custom function + /// to count uppercase characters. If not provided, uses a default Unicode-based + /// counter. + /// - `hasMinUppercaseCharsMsg` (`String Function(String input, int min)?`): + /// Optional function to generate custom error messages. Receives the input and + /// the minimum uppercase count required and returns an error message string. + /// + /// ## Returns + /// Returns a `Validator` function that takes a string input and returns: + /// - `null` if the input contains at least [min] uppercase characters + /// - An error message string if the validation fails + /// + /// ## Throws + /// - `AssertionError`: When [min] is less than 1 + /// + /// ## Examples + /// ```dart + /// // Basic usage with default parameters + /// final validator = hasMinUppercaseChars(); + /// print(validator('Hello')); // Returns null + /// print(validator('hello')); // Returns error message + /// + /// // Custom minimum requirement + /// final strictValidator = hasMinUppercaseChars(min: 2); + /// print(strictValidator('HEllo')); // Returns null + /// print(strictValidator('Hello')); // Returns error message + /// + /// // Custom error message + /// final customValidator = hasMinUppercaseChars( + /// hasMinUppercaseCharsMsg: (_, min) => 'Need $min uppercase letters!', + /// ); + /// ``` + /// + /// ## Caveats + /// - The default counter uses language-independent Unicode mapping, which may not + /// work correctly for all languages. Custom uppercase counter function should + /// be provided for special language requirements + /// {@endtemplate} + static Validator hasMinUppercaseChars({ + int min = 1, + int Function(String)? customUppercaseCounter, + String Function(String input, int min)? hasMinUppercaseCharsMsg, + }) => + val.hasMinUppercaseChars( + min: min, + customUppercaseCounter: customUppercaseCounter, + hasMinUppercaseCharsMsg: hasMinUppercaseCharsMsg, + ); + + /// {@template validator_has_min_lowercase_chars} + /// Creates a validator function that checks if the [String] input contains a + /// minimum number of lowercase characters. The validator returns `null` for + /// valid input and an error message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [hasMinLowercaseCharsMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.containsLowercaseCharErrorText(min)`. + /// + /// ## Parameters + /// - `min` (`int`): The minimum number of lowercase characters required. Defaults + /// to 1. + /// - `customLowercaseCounter` (`int Function(String)?`): Optional custom function + /// to count lowercase characters. If not provided, uses a default Unicode-based + /// counter. + /// - `hasMinLowercaseCharsMsg` (`String Function(String input, int min)?`): + /// Optional function to generate custom error messages. Receives the input and + /// the minimum lowercase count required and returns an error message string. + /// + /// ## Returns + /// Returns a `Validator` function that takes a string input and returns: + /// - `null` if the input contains at least [min] lowercase characters + /// - An error message string if the validation fails + /// + /// ## Throws + /// - `AssertionError`: When [min] is less than 1 + /// + /// ## Examples + /// ```dart + /// // Basic usage with default parameters + /// final validator = hasMinLowercaseChars(); + /// print(validator('hello')); // Returns null + /// print(validator('HELLO')); // Returns error message + /// + /// // Custom minimum requirement + /// final strictValidator = hasMinLowercaseChars(min: 2); + /// print(strictValidator('hEllo')); // Returns null + /// print(strictValidator('HELlO')); // Returns error message + /// + /// // Custom error message + /// final customValidator = hasMinLowercaseChars( + /// hasMinLowercaseCharsMsg: (_, min) => 'Need $min lowercase letters!', + /// ); + /// ``` + /// + /// ## Caveats + /// - The default counter uses language-independent Unicode mapping, which may not + /// work correctly for all languages. Custom lowercase counter function should + /// be provided for special language requirements + /// {@endtemplate} + static Validator hasMinLowercaseChars({ + int min = 1, + int Function(String)? customLowercaseCounter, + String Function(String input, int min)? hasMinLowercaseCharsMsg, + }) => + val.hasMinLowercaseChars( + min: min, + customLowercaseCounter: customLowercaseCounter, + hasMinLowercaseCharsMsg: hasMinLowercaseCharsMsg, + ); + + /// {@template validator_has_min_numeric_chars} + /// Creates a validator function that checks if the [String] input contains a + /// minimum number of numeric characters (0-9). The validator returns `null` for + /// valid input and an error message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [hasMinNumericCharsMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.containsNumberErrorText(min)`. + /// + /// ## Parameters + /// - `min` (`int`): The minimum number of numeric characters required. Defaults + /// to 1. + /// - `customNumericCounter` (`int Function(String)?`): Optional custom function + /// to count numeric characters. If not provided, uses a default regex-based + /// counter matching digits 0-9. + /// - `hasMinNumericCharsMsg` (`String Function(String input, int min)?`): + /// Optional function to generate custom error messages. Receives the input and + /// the minimum numeric count required and returns an error message string. + /// + /// ## Returns + /// Returns a `Validator` function that takes a string input and returns: + /// - `null` if the input contains at least [min] numeric characters + /// - An error message string if the validation fails + /// + /// ## Throws + /// - `AssertionError`: When [min] is less than 1 + /// + /// ## Examples + /// ```dart + /// // Basic usage with default parameters + /// final validator = hasMinNumericChars(); + /// print(validator('hello123')); // Returns null + /// print(validator('hello')); // Returns error message + /// + /// // Custom minimum requirement + /// final strictValidator = hasMinNumericChars(min: 2); + /// print(strictValidator('hello12')); // Returns null + /// print(strictValidator('hello1')); // Returns error message + /// + /// // Custom error message + /// final customValidator = hasMinNumericChars( + /// hasMinNumericCharsMsg: (_, min) => 'Need $min numbers!', + /// ); + /// + /// // Custom numeric counter for special cases + /// final customCounter = hasMinNumericChars( + /// customNumericCounter: countNumericDigits, // From a specialized package, for example. + /// ); + /// ``` + /// + /// ## Caveats + /// - The default counter uses a regular expression matching digits 0-9, which may + /// not work correctly for all languages or number systems. Custom numeric counter + /// function should be provided for special numbering requirements + /// {@endtemplate} + static Validator hasMinNumericChars({ + int min = 1, + int Function(String)? customNumericCounter, + String Function(String input, int min)? hasMinNumericCharsMsg, + }) => + val.hasMinNumericChars( + min: min, + customNumericCounter: customNumericCounter, + hasMinNumericCharsMsg: hasMinNumericCharsMsg, + ); + + /// {@template validator_has_min_special_chars} + /// Creates a validator function that checks if the [String] input contains a + /// minimum number of special characters. The validator returns `null` for + /// valid input and an error message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [hasMinSpecialCharsMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.containsSpecialCharErrorText(min)`. + /// + /// ## Parameters + /// - `min` (`int`): The minimum number of special characters required. Defaults + /// to 1. + /// - `customSpecialCounter` (`int Function(String)?`): Optional custom function + /// to count special characters. If not provided, uses a default calculation + /// that considers special characters as any character that is neither + /// alphanumeric. + /// - `hasMinSpecialCharsMsg` (`String Function(String input, int min)?`): + /// Optional function to generate custom error messages. Receives the input and + /// the minimum special character count required and returns an error message string. + /// + /// ## Returns + /// Returns a `Validator` function that takes a string input and returns: + /// - `null` if the input contains at least [min] special characters + /// - An error message string if the validation fails + /// + /// ## Throws + /// - `AssertionError`: When [min] is less than 1 + /// + /// ## Examples + /// ```dart + /// // Basic usage with default parameters + /// final validator = hasMinSpecialChars(); + /// print(validator('hello@world')); // Returns null + /// print(validator('helloworld')); // Returns error message + /// + /// // Custom minimum requirement + /// final strictValidator = hasMinSpecialChars(min: 2); + /// print(strictValidator('hello@#world')); // Returns null + /// print(strictValidator('hello@world')); // Returns error message + /// + /// // Custom error message + /// final customValidator = hasMinSpecialChars( + /// hasMinSpecialCharsMsg: (_, min) => 'Need $min special characters!', + /// ); + /// + /// // Custom special character counter for US-ASCII + /// final asciiValidator = hasMinSpecialChars( + /// customSpecialCounter: (v) => RegExp('[^A-Za-z0-9]').allMatches(v).length, + /// ); + /// ``` + /// + /// ## Caveats + /// - The default counter uses language-independent Unicode mapping, which may not + /// work correctly for all languages. Custom special character counter function + /// should be provided for specific character set requirements + /// {@endtemplate} + static Validator hasMinSpecialChars({ + int min = 1, + int Function(String)? customSpecialCounter, + String Function(String input, int min)? hasMinSpecialCharsMsg, + }) => + val.hasMinSpecialChars( + min: min, + customSpecialCounter: customSpecialCounter, + hasMinSpecialCharsMsg: hasMinSpecialCharsMsg, + ); + + /// {@template validator_match} + /// Creates a validator function that checks if the [String] input matches a given + /// regular expression pattern. The validator returns `null` for valid input and + /// an error message for invalid input. + /// + /// If validation fails and no custom error message is provided via [matchMsg], + /// returns the default localized error message from + /// `FormBuilderLocalizations.current.matchErrorText`. + /// + /// ## Parameters + /// - `regex` (`RegExp`): The regular expression pattern to match against the input + /// string. + /// - `matchMsg` (`String Function(String input)?`): Optional custom error message + /// to display when the validation fails. If not provided, uses the default + /// localized error message. + /// + /// ## Returns + /// Returns a `Validator` function that takes a string input and returns: + /// - `null` if the input matches the provided regular expression pattern + /// - An error message string if the validation fails + /// + /// ## Examples + /// ```dart + /// // Basic email validation + /// final emailValidator = match( + /// emailRegExp, + /// matchMsg: (_)=>'Please enter a valid email address', + /// ); + /// print(emailValidator('user@example.com')); // Returns null + /// print(emailValidator('invalid-email')); // Returns error message + /// ``` + /// + /// ## Caveats + /// - Complex regular expressions may impact performance for large inputs + /// - Consider using more specific validators for common patterns like email + /// or phone number validation + /// {@endtemplate} + static Validator match( + RegExp regex, { + String Function(String input)? matchMsg, + }) => + val.match(regex, matchMsg: matchMsg); + +// Collection validators + /// {@template validator_min_length} + /// Creates a validator function that checks if the input collection's length is + /// greater than or equal to `min`. The validator returns `null` for valid input + /// and an error message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [minLengthMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.minLengthErrorText(min)`. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Must be a collection, in other words, + /// it must be one of `String`, `Iterable` or `Map`. + /// + /// ## Parameters + /// - `min` (`int`): The minimum length required. Must be non-negative. + /// - `minLengthMsg` (`String Function(T input, int min)?`): Optional + /// function to generate custom error messages. Receives the input and the + /// minimum length required and returns an error message string. + /// + /// ## Return Value + /// A `Validator` function that produces: + /// - `null` for valid inputs (length >= min) + /// - An error message string for invalid inputs (length < min) + /// + /// ## Throws + /// - `ArgumentError` when: + /// - [min] is negative + /// - input runtime type is not a collection + /// + /// ## Examples + /// ```dart + /// // String validation + /// final stringValidator = minLength(3); + /// print(stringValidator('abc')); // Returns null + /// print(stringValidator('ab')); // Returns error message + /// + /// // List validation + /// final listValidator = minLength(2); + /// print(listValidator([1, 2, 3])); // Returns null + /// print(listValidator([1])); // Returns error message + /// + /// // Custom error message + /// final customValidator = minLength( + /// 5, + /// minLengthMsg: (_, min) => 'Text must be at least $min chars long!', + /// ); + /// ``` + /// + /// ## Caveats + /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. + /// While the compiler cannot enforce this restriction, it is the developer's + /// responsibility to maintain this constraint. + /// - The validator treats non-collection inputs as implementation errors rather + /// than validation failures. Validate input types before passing them to + /// this validator. + /// {@endtemplate} + static Validator minLength(int min, + {String Function(T input, int min)? minLengthMsg}) => + val.minLength(min, minLengthMsg: minLengthMsg); + + /// {@template validator_max_length} + /// Creates a validator function that checks if the input collection's length is + /// less than or equal to max. The validator returns null for valid input + /// and an error message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [maxLengthMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.maxLengthErrorText(max)`. + /// + /// ## Type Parameters + /// - T: The type of input to validate. Must be a collection, in other words, + /// it must be one of `String`, `Iterable` or `Map`. + /// + /// ## Parameters + /// - `max` (`int`): The maximum length allowed. Must be non-negative. + /// - `maxLengthMsg` (`String Function(T input, int max)?`): Optional + /// function to generate custom error messages. Receives the input and the + /// maximum length allowed and returns an error message string. + /// + /// ## Return Value + /// A `Validator` function that produces: + /// - null for valid inputs (length <= max) + /// - An error message string for invalid inputs (length > max) + /// + /// ## Throws + /// - `ArgumentError` when: + /// - [max] is negative + /// - input runtime type is not a collection + /// + /// ## Examples + /// ```dart + /// // String validation + /// final stringValidator = maxLength(5); + /// print(stringValidator('hello')); // Returns null + /// print(stringValidator('hello world')); // Returns error message + /// + /// // List validation + /// final listValidator = maxLength(3); + /// print(listValidator([1, 2])); // Returns null + /// print(listValidator([1, 2, 3, 4])); // Returns error message + /// + /// // Custom error message + /// final customValidator = maxLength( + /// 10, + /// maxLengthMsg: (_, max) => 'Text must not exceed $max chars!', + /// ); + /// ``` + /// ## Caveats + /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. + /// While the compiler cannot enforce this restriction, it is the developer's + /// responsibility to maintain this constraint. + /// - The validator treats non-collection inputs as implementation errors rather + /// than validation failures. Validate input types before passing them to + /// this validator. + /// {@endtemplate} + static Validator maxLength(int max, + {String Function(T input, int max)? maxLengthMsg}) => + val.maxLength(max, maxLengthMsg: maxLengthMsg); + + /// {@template validator_between_length} + /// Creates a validator function that checks if the input collection's length falls + /// within an inclusive range defined by `min` and `max`. The validator returns + /// `null` for valid input and an error message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [betweenLengthMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.betweenLengthErrorText(min, max)`. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Must be a collection, in other words, + /// it must be one of `String`, `Iterable` or `Map`. + /// + /// ## Parameters + /// - `min` (`int`): The minimum length required. Must be non-negative. + /// - `max` (`int`): The maximum length allowed. Must be greater than or equal + /// to `min`. + /// - `betweenLengthMsg` (`String Function(T input, {required int min, required int max})?`): + /// Optional function to generate custom error messages. Receives the input and the + /// minimum and maximum lengths required, returning an error message string. + /// + /// ## Return Value + /// A `Validator` function that produces: + /// - `null` for valid inputs (min <= length <= max) + /// - An error message string for invalid inputs (length < min || length > max) + /// + /// ## Throws + /// - `ArgumentError` when: + /// - [min] is negative + /// - [max] is less than [min] + /// - input runtime type is not a collection + /// + /// ## Examples + /// ```dart + /// // String validation + /// final stringValidator = betweenLength(3, 5); + /// print(stringValidator('abc')); // Returns null + /// print(stringValidator('ab')); // Returns error message + /// print(stringValidator('abcdef')); // Returns error message + /// + /// // List validation + /// final listValidator = betweenLength(2, 4); + /// print(listValidator([1, 2, 3])); // Returns null + /// print(listValidator([1])); // Returns error message + /// print(listValidator([1, 2, 3, 4, 5])); // Returns error message + /// + /// // Custom error message + /// final customValidator = betweenLength( + /// 5, + /// 10, + /// betweenLengthMsg: (_, {required min, required max}) => + /// 'Text must be between $min and $max chars long!', + /// ); + /// ``` + /// + /// ## Caveats + /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. + /// While the compiler cannot enforce this restriction, it is the developer's + /// responsibility to maintain this constraint. + /// - The validator treats non-collection inputs as implementation errors rather + /// than validation failures. Validate input types before passing them to + /// this validator. + /// {@endtemplate} + static Validator betweenLength( + int min, + int max, { + String Function(T input, {required int min, required int max})? + betweenLengthMsg, + }) => + val.betweenLength(min, max, betweenLengthMsg: betweenLengthMsg); + + /// {@template validator_equal_length} + /// Creates a validator function that checks if the input collection's length equals + /// the specified length. The validator returns `null` for valid input and an error + /// message for invalid input. + /// + /// If validation fails and no custom error message generator is provided via + /// [equalLengthMsg], returns the default localized error message from + /// `FormBuilderLocalizations.current.equalLengthErrorText(expectedLength)`. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Must be a collection, in other words, + /// it must be one of `String`, `Iterable` or `Map`. + /// + /// ## Parameters + /// - `expectedLength` (`int`): The exact length required. Must be non-negative. + /// - `equalLengthMsg` (`String Function(T input, int expectedLength)?`): Optional + /// function to generate custom error messages. Receives the input and the + /// expected length, returning an error message string. + /// + /// ## Return Value + /// A `Validator` function that produces: + /// - `null` for valid inputs (length == expectedLength) + /// - An error message string for invalid inputs (length != expectedLength) + /// + /// ## Throws + /// - `ArgumentError` when: + /// - [expectedLength] is negative + /// - input runtime type is not a collection + /// + /// ## Examples + /// ```dart + /// // String validation + /// final stringValidator = equalLength(3); + /// print(stringValidator('abc')); // Returns null + /// print(stringValidator('ab')); // Returns error message + /// print(stringValidator('abcd')); // Returns error message + /// + /// // List validation + /// final listValidator = equalLength(2); + /// print(listValidator([1, 2])); // Returns null + /// print(listValidator([1])); // Returns error message + /// print(listValidator([1, 2, 3])); // Returns error message + /// + /// // Custom error message + /// final customValidator = equalLength( + /// 5, + /// equalLengthMsg: (_, expectedLength) => + /// 'Text must be exactly $expectedLength chars long!', + /// ); + /// ``` + /// + /// ## Caveats + /// - Type parameter `T` must be restricted to `String`, `Map`, or `Iterable`. + /// While the compiler cannot enforce this restriction, it is the developer's + /// responsibility to maintain this constraint. + /// - The validator treats non-collection inputs as implementation errors rather + /// than validation failures. Validate input types before passing them to + /// this validator. + /// {@endtemplate} + static Validator equalLength(int expectedLength, + {String Function(T input, int expectedLength)? equalLengthMsg}) => + val.equalLength( + expectedLength, + equalLengthMsg: equalLengthMsg, + ); + + // DateTime Validators + /// {@template validator_is_after} + /// Creates a [DateTime] validator that checks if an input date occurs after + /// `reference`. + /// + /// ## Parameters + /// - `reference` (`DateTime`): The baseline date against which the input will be compared. + /// This serves as the minimum acceptable date (exclusive by default). + /// - `isAfterMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom + /// error message generator. When provided, it receives both the input and reference + /// dates to construct a context-aware error message. + /// - `inclusive` (`bool`): When set to `true`, allows the input date to exactly match + /// the reference date. Defaults to `false`, requiring strictly later dates. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation passes (input is after reference) + /// - Returns an error message string if validation fails + /// + /// ## Examples + /// ```dart + /// // Basic usage requiring date after January 1st, 2025 + /// final validator = isAfter(DateTime(2025)); + /// + /// // Inclusive validation allowing exact match + /// final inclusiveValidator = isAfter( + /// DateTime(2024), + /// inclusive: true, + /// ); + /// + /// // Custom error message + /// final customValidator = isAfter( + /// DateTime(2024), + /// isAfterMsg: (_, ref) => 'Please select a date after ${ref.toString()}', + /// ); + /// ``` + /// {@endtemplate} + static Validator isAfter( + DateTime reference, { + String Function(DateTime input, DateTime reference)? isAfterMsg, + bool inclusive = false, + }) => + val.isAfter(reference, isAfterMsg: isAfterMsg, inclusive: inclusive); + + /// {@template validator_is_before} + /// Creates a [DateTime] validator that checks if an input date occurs before + /// `reference`. + /// + /// ## Parameters + /// - `reference` (`DateTime`): The baseline date against which the input will be compared. + /// This serves as the maximum acceptable date (exclusive by default). + /// - `isBeforeMsg` (`String Function(DateTime input, DateTime reference)?`): Optional custom + /// error message generator. When provided, it receives both the input and reference + /// dates to construct a context-aware error message. + /// - `inclusive` (`bool`): When set to `true`, allows the input date to exactly match + /// the reference date. Defaults to `false`, requiring strictly earlier dates. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation passes (input is before reference) + /// - Returns an error message string if validation fails. If no custom message is provided, + /// falls back to the localized error text from `FormBuilderLocalizations` + /// + /// ## Examples + /// ```dart + /// // Basic usage requiring date before January 1st, 2025 + /// final validator = isBefore(DateTime(2025)); + /// + /// // Inclusive validation allowing exact match + /// final inclusiveValidator = isBefore( + /// DateTime(2024), + /// inclusive: true, + /// ); + /// + /// // Custom error message + /// final customValidator = isBefore( + /// DateTime(2024), + /// isBeforeMsg: (_, ref) => 'Please select a date before ${ref.toString()}', + /// ); + /// ``` + /// {@endtemplate} + static Validator isBefore( + DateTime reference, { + String Function(DateTime input, DateTime reference)? isBeforeMsg, + bool inclusive = false, + }) => + val.isBefore(reference, isBeforeMsg: isBeforeMsg, inclusive: inclusive); + + /// {@template validator_is_date_time_between} + /// Creates a [DateTime] validator that checks if an input date falls within a specified + /// range defined by `minReference` and `maxReference`. + /// + /// The validator ensures the input date occurs after `minReference` and before + /// `maxReference`, with optional inclusive boundaries controlled by `minInclusive` + /// and `maxInclusive` parameters. + /// + /// ## Parameters + /// - `minReference` (`DateTime`): The lower bound of the acceptable date range. + /// Input dates must occur after this date (or equal to it if `minInclusive` is true). + /// - `maxReference` (`DateTime`): The upper bound of the acceptable date range. + /// Input dates must occur before this date (or equal to it if `maxInclusive` is true). + /// - `isDateTimeBetweenMsg` (`String Function(DateTime, DateTime, DateTime)?`): Optional + /// custom error message generator. When provided, it receives the input date and both + /// reference dates to construct a context-aware error message. + /// - `minInclusive` (`bool`): When set to `true`, allows the input date to exactly match + /// the `minReference` date. Defaults to `false`. + /// - `maxInclusive` (`bool`): When set to `true`, allows the input date to exactly match + /// the `maxReference` date. Defaults to `false`. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if validation passes (input is within the specified range) + /// - Returns an error message string if validation fails. If no custom message is provided, + /// falls back to the localized error text from `FormBuilderLocalizations` + /// + /// ## Throws + /// - `AssertionError`: When `minReference` is not chronologically before `maxReference`, + /// indicating an invalid date range configuration. + /// + /// ## Examples + /// ```dart + /// // Basic usage requiring date between 2023 and 2025 + /// final validator = isDateTimeBetween( + /// DateTime(2023), + /// DateTime(2025), + /// ); + /// + /// // Inclusive validation allowing exact matches + /// final inclusiveValidator = isDateTimeBetween( + /// DateTime(2023), + /// DateTime(2025), + /// minInclusive: true, + /// maxInclusive: true, + /// ); + /// + /// // Custom error message + /// final customValidator = isDateTimeBetween( + /// DateTime(2023), + /// DateTime(2025), + /// isDateTimeBetweenMsg: (_, min, max) => + /// 'Please select a date between ${min.toString()} and ${max.toString()}', + /// ); + /// ``` + /// {@endtemplate} + static Validator isDateTimeBetween( + DateTime minReference, + DateTime maxReference, { + String Function( + DateTime input, DateTime minReference, DateTime maxReference)? + isDateTimeBetweenMsg, + bool leftInclusive = false, + bool rightInclusive = false, + }) => + val.isDateTimeBetween(minReference, maxReference, + isDateTimeBetweenMsg: isDateTimeBetweenMsg, + minInclusive: leftInclusive, + maxInclusive: rightInclusive); + + // Generic type validators + /// {@template validator_contains_element} + /// Creates a validator function that verifies if a given input is in `values`. + /// + /// ## Type Parameters + /// - `T`: The type of elements to validate. Must extend Object?, allowing nullable + /// types. + /// + /// ## Parameters + /// - `values` (`List`): A non-empty list of valid values to check against. The input + /// will be validated against these values. + /// - `containsElementMsg` (`String Function(T input, List values)?`): Optional callback + /// function that generates a custom error message when validation fails. The function + /// receives the invalid input and the list of valid values as parameters. If not provided, + /// defaults to the localized error text from FormBuilderLocalizations. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns null if the input value exists in the provided list + /// - Returns a generated error message if the input is not found in the list. + /// + /// ## Throws + /// - `AssertionError`: Thrown if the provided values list is empty, which would + /// make any input invalid. + /// + /// ## Examples + /// ```dart + /// // Creating a validator with a custom error message generator + /// final countryValidator = containsElement( + /// ['USA', 'Canada', 'Mexico'], + /// containsElementMsg: (input, values) => + /// 'Country $input is not in allowed list: ${values.join(", ")}', + /// ); + /// + /// // Using the validator + /// final result = countryValidator('Brazil'); // Returns "Country Brazil is not in allowed list: USA, Canada, Mexico" + /// final valid = countryValidator('USA'); // Returns null (valid) + /// ``` + /// {@endtemplate} + static Validator containsElement( + List values, { + String Function(T input, List values)? containsElementMsg, + }) => + val.containsElement(values, containsElementMsg: containsElementMsg); + + /// {@template validator_is_true} + /// Creates a validator function that checks if a given input represents a `true` + /// boolean value, either as a direct boolean or as a string that can be parsed + /// to `true`. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Must extend `Object` to allow for both + /// boolean and string inputs. + /// + /// ## Parameters + /// - `isTrueMsg` (`String Function(T input)?`): Optional callback function to + /// generate custom error messages for invalid inputs. Receives the invalid + /// input as a parameter. + /// - `caseSensitive` (`bool`): Controls whether string comparison is + /// case-sensitive. Defaults to `false`, making, for example, 'TRUE' and 'true' + /// equivalent. + /// - `trim` (`bool`): Determines if leading and trailing whitespace should be + /// removed from string inputs before validation. Defaults to `true`. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input is `true` or parses to `true` + /// - Returns an error message if the input is invalid, either from `isTrueMsg` + /// or the default localized text + /// + /// ## Examples + /// ```dart + /// // Basic usage with default settings + /// final validator = isTrue(); + /// assert(validator('true') == null); // Valid: case-insensitive match + /// assert(validator(' TRUE ') == null); // Valid: trimmed and case-insensitive + /// assert(validator('t r u e') != null); // Invalid: returns error message + /// assert(validator('false') != null); // Invalid: returns error message + /// + /// // Custom configuration + /// final strictValidator = isTrue( + /// caseSensitive: true, + /// trim: false, + /// isTrueMsg: (input) => 'Value "$input" must be exactly "true"', + /// ); + /// assert(strictValidator('true') == null); // Valid + /// assert(strictValidator('TRUE') != null); // Invalid: case-sensitive + /// assert(strictValidator(' true') != null); // Invalid: no trimming + /// ``` + /// {@endtemplate} + static Validator isTrue( + {String Function(T input)? isTrueMsg, + bool caseSensitive = false, + bool trim = true}) => + val.isTrue( + isTrueMsg: isTrueMsg, caseSensitive: caseSensitive, trim: trim); + + /// {@template validator_is_false} + /// Creates a validator function that checks if a given input represents a `false` + /// boolean value, either as a direct boolean or as a string that can be parsed + /// to `false`. + /// + /// ## Type Parameters + /// - `T`: The type of input to validate. Must extend `Object` to allow for both + /// boolean and string inputs. + /// + /// ## Parameters + /// - `isFalseMsg` (`String Function(T input)?`): Optional callback function to + /// generate custom error messages for invalid inputs. Receives the invalid + /// input as a parameter. + /// - `caseSensitive` (`bool`): Controls whether string comparison is + /// case-sensitive. Defaults to `false`, making, for example, 'FALSE' and 'false' + /// equivalent. + /// - `trim` (`bool`): Determines if leading and trailing whitespace should be + /// removed from string inputs before validation. Defaults to `true`. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the input is `false` or parses to `false` + /// - Returns an error message if the input is invalid, either from `isFalseMsg` + /// or the default localized text + /// + /// ## Examples + /// ```dart + /// // Basic usage with default settings + /// final validator = isFalse(); + /// assert(validator('false') == null); // Valid: case-insensitive match + /// assert(validator(' FALSE ') == null); // Valid: trimmed and case-insensitive + /// assert(validator('f a l s e') != null); // Invalid: returns error message + /// assert(validator('true') != null); // Invalid: returns error message + /// + /// // Custom configuration + /// final strictValidator = isFalse( + /// caseSensitive: true, + /// trim: false, + /// isFalseMsg: (input) => 'Value "$input" must be exactly "false"', + /// ); + /// assert(strictValidator('false') == null); // Valid + /// assert(strictValidator('FALSE') != null); // Invalid: case-sensitive + /// assert(strictValidator(' false') != null); // Invalid: no trimming + /// ``` + /// {@endtemplate} + static Validator isFalse( + {String Function(T input)? isFalseMsg, + bool caseSensitive = false, + bool trim = false}) => + val.isFalse( + isFalseMsg: isFalseMsg, caseSensitive: caseSensitive, trim: trim); + + // Numeric validators + /// {@template validator_greater_than} + /// Creates a validator function that checks if a numeric input exceeds `reference`. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `reference` (`T`): The threshold value that the input must exceed + /// - `greaterThanMsg` (`String Function(T input, T reference)?`): Optional custom error + /// message generator that takes the input value and threshold as parameters + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input is greater than the threshold value `reference` + /// - Returns an error message string if validation fails, either from the custom + /// `greaterThanMsg` function or the default localized error text + /// + /// ## Examples + /// ```dart + /// // Basic usage with integers + /// final ageValidator = greaterThan(18); + /// + /// // Custom error message + /// final priceValidator = greaterThan( + /// 0.0, + /// greaterThanMsg: (_, ref) => 'Price must be greater than \$${ref.toStringAsFixed(2)}', + /// ); + /// ``` + /// + /// ## Caveats + /// - The validator uses strict greater than comparison (`>`) + /// {@endtemplate} + static Validator greaterThan(T reference, + {String Function(num input, num reference)? greaterThanMsg}) => + val.greaterThan(reference, greaterThanMsg: greaterThanMsg); + + /// {@template validator_greater_than_or_equal_to} + /// Creates a validator function that checks if a numeric input is greater than + /// or equal to `reference`. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `reference` (`T`): The threshold value that the input must be greater than or equal to + /// - `greaterThanOrEqualToMsg` (`String Function(T input, T reference)?`): Optional custom error + /// message generator that takes the input value and threshold as parameters + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input is greater than or equal to the threshold value + /// `reference` + /// - Returns an error message string if validation fails, either from the custom + /// `greaterThanOrEqualToMsg` function or the default localized error text from + /// [FormBuilderLocalizations] + /// + /// ## Examples + /// ```dart + /// // Basic usage with integers + /// final ageValidator = greaterThanOrEqualTo(18); + /// + /// // Custom error message + /// final priceValidator = greaterThanOrEqualTo( + /// 0.0, + /// greaterThanOrEqualToMsg: (_, ref) => 'Price must be at least \$${ref.toStringAsFixed(2)}', + /// ); + /// ``` + /// + /// ## Caveats + /// - The validator uses greater than or equal to comparison (`>=`) + /// {@endtemplate} + static Validator greaterThanOrEqualTo(T reference, + {String Function(num input, num reference)? + greaterThanOrEqualToMsg}) => + val.greaterThanOrEqualTo(reference, + greaterThanOrEqualToMsg: greaterThanOrEqualToMsg); + + /// {@template validator_less_than} + /// Creates a validator function that checks if a numeric input is less than `reference`. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `reference` (`T`): The threshold value that the input must be less than + /// - `lessThanMsg` (`String Function(T input, T reference)?`): Optional custom error + /// message generator that takes the input value and threshold as parameters + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input is less than the threshold value `reference` + /// - Returns an error message string if validation fails, either from the custom + /// `lessThanMsg` function or the default localized error text + /// + /// ## Examples + /// ```dart + /// // Basic usage with integers + /// final maxAgeValidator = lessThan(100); + /// + /// // Custom error message + /// final discountValidator = lessThan( + /// 1.0, + /// lessThanMsg: (_, ref) => 'Discount must be less than ${(ref * 100).toStringAsFixed(0)}%', + /// ); + /// ``` + /// + /// ## Caveats + /// - The validator uses strict less than comparison (`<`) + /// {@endtemplate} + static Validator lessThan(T reference, + {String Function(num input, num reference)? lessThanMsg}) => + val.lessThan(reference, lessThanMsg: lessThanMsg); + + /// {@template validator_less_than_or_equal_to} + /// Creates a validator function that checks if a numeric input is less than + /// or equal to `reference`. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `reference` (`T`): The threshold value that the input must be less than or equal to + /// - `lessThanOrEqualToMsg` (`String Function(T input, T reference)?`): Optional custom error + /// message generator that takes the input value and threshold as parameters + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input is less than or equal to the threshold value + /// `reference` + /// - Returns an error message string if validation fails, either from the custom + /// `lessThanOrEqualToMsg` function or the default localized error text from + /// [FormBuilderLocalizations] + /// + /// ## Examples + /// ```dart + /// // Basic usage with integers + /// final maxAgeValidator = lessThanOrEqualTo(100); + /// + /// // Custom error message + /// final maxPriceValidator = lessThanOrEqualTo( + /// 999.99, + /// lessThanOrEqualToMsg: (_, ref) => 'Price cannot exceed \$${ref.toStringAsFixed(2)}', + /// ); + /// ``` + /// + /// ## Caveats + /// - The validator uses less than or equal to comparison (`<=`) + /// {@endtemplate} + static Validator lessThanOrEqualTo(T reference, + {String Function(num input, num reference)? lessThanOrEqualToMsg}) => + val.lessThanOrEqualTo(reference, + lessThanOrEqualToMsg: lessThanOrEqualToMsg); + + /// {@template validator_between} + /// Creates a validator function that checks if a numeric input falls within a specified + /// range defined by `min` and `max` values. + /// + /// ## Type Parameters + /// - `T`: A numeric type that extends [num], allowing `int`, `double` or + /// `num` validations + /// + /// ## Parameters + /// - `min` (`T`): The lower bound of the valid range + /// - `max` (`T`): The upper bound of the valid range + /// - `minInclusive` (`bool`): Determines if the lower bound is inclusive. Defaults to `true` + /// - `maxInclusive` (`bool`): Determines if the upper bound is inclusive. Defaults to `true` + /// - `betweenMsg` (`String Function(T input, T min, T max, bool minInclusive, bool maxInclusive)?`): + /// Optional custom error message generator that takes the input value, inclusivity flags, + /// and range bounds as parameters + /// + /// + /// ## Returns + /// Returns a [Validator] function that: + /// - Returns `null` if the input falls within the specified range according to the + /// inclusivity settings + /// - Returns an error message string if validation fails, either from the custom + /// `betweenMsg` function or the default localized error text from + /// [FormBuilderLocalizations] + /// + /// ## Throw + /// - `AssertionError`: when `max` is not greater than or equal to `min`. + /// + /// ## Examples + /// ```dart + /// // Basic usage with inclusive bounds + /// final ageValidator = between(18, 65); // [18, 65] + /// + /// // Exclusive upper bound for decimal values + /// final priceValidator = between( // [0.0, 100.0) + /// 0.0, + /// 100.0, + /// maxInclusive: false, + /// ); + /// + /// // Custom error message + /// final scoreValidator = between( // + /// 0.0, + /// 10.0, + /// betweenMsg: (_, min, max, __, ___) => + /// 'Score must be between $min and $max (inclusive)', + /// ); + /// ``` + /// + /// ## Caveats + /// - The default behavior uses inclusive bounds (`>=` and `<=`) + /// {@endtemplate} + static Validator between(T min, T max, + {bool minInclusive = true, + bool maxInclusive = true, + String Function( + T input, + T min, + T max, + bool minInclusive, + bool maxInclusive, + )? betweenMsg}) => + val.between( + min, + max, + minInclusive: minInclusive, + maxInclusive: maxInclusive, + betweenMsg: betweenMsg, + ); + + // User information validators + /// {@template validator_password} + /// Creates a composite validator for password validation that enforces multiple + /// password strength requirements simultaneously. + /// + /// This validator combines multiple validation rules including length constraints, + /// character type requirements (uppercase, lowercase, numbers, and special characters), + /// and allows for custom error message overriding. + /// + /// ## Parameters + /// - `minLength` (`int`): Minimum required length for the password. Defaults to `16` + /// - `maxLength` (`int`): Maximum allowed length for the password. Defaults to `32` + /// - `minUppercaseCount` (`int`): Minimum required uppercase characters. Defaults to `1` + /// - `minLowercaseCount` (`int`): Minimum required lowercase characters. Defaults to `1` + /// - `minNumberCount` (`int`): Minimum required numeric characters. Defaults to `1` + /// - `minSpecialCharCount` (`int`): Minimum required special characters. Defaults to `1` + /// - `passwordMsg` (`String Function(String input)?`): Optional custom error message + /// that overrides all validation error messages. When `null`, individual validator + /// messages are used. + /// + /// ## Returns + /// Returns a `Validator` that combines all specified password requirements + /// into a single validator. The validator returns null if all conditions are met, + /// otherwise returns the appropriate error message. + /// + /// ## Examples + /// ```dart + /// // Default password validation + /// final validator = password(); + /// + /// // Custom requirements + /// final strictValidator = Validator.password( + /// minLength: 12, + /// minUppercaseCount: 2, + /// minSpecialCharCount: 2, + /// passwordMsg: (_)=>'Password does not meet security requirements' + /// ); + /// ``` + /// + /// ## Caveats + /// - When `passwordMsg` is provided, individual validation failure details + /// are not available to the user. + /// {@endtemplate} + static Validator password({ + int minLength = 8, + int maxLength = 32, + int minUppercaseCount = 1, + int minLowercaseCount = 1, + int minNumberCount = 1, + int minSpecialCharCount = 1, + String Function(String input)? passwordMsg, + }) => + val.password( + minLength: minLength, + maxLength: maxLength, + minUppercaseCount: minUppercaseCount, + minLowercaseCount: minLowercaseCount, + minNumberCount: minNumberCount, + minSpecialCharCount: minSpecialCharCount, + passwordMsg: passwordMsg, + ); + + /// {@template validator_phoneNumber} + /// A validator function for phone number validation that supports various international + /// phone number formats with optional country codes, area codes, and separators. + /// + /// The validator checks if the input string matches a flexible phone number pattern + /// that accommodates: + /// - Optional leading '+' for international numbers + /// - Country codes (1-4 digits) + /// - Area codes with optional parentheses + /// - Multiple number groups separated by spaces, dots, or hyphens + /// + /// ## Parameters + /// - `regex` (`RegExp?`): Optional custom regular expression pattern for phone + /// number validation. If not provided, defaults to the internal `_phoneNumberRegex` + /// pattern that supports international formats + /// - `phoneNumberMsg` (`String Function(String input)?`): Optional callback function + /// that generates a custom error message based on the invalid input. If not + /// provided, uses the default error message from `FormBuilderLocalizations` + /// + /// ## Returns + /// Returns `null` if the phone number is valid according to the specified pattern, + /// otherwise returns an error message string. The error message can be either: + /// - A custom message generated by the provided `phoneNumberMsg` callback + /// - The default localized error text from `FormBuilderLocalizations` + /// + /// ## Examples + /// ```dart + /// // Basic usage with default regex + /// final validator = phoneNumber(); + /// print(validator('+1 (555) 123-4567')); // Returns: null + /// print(validator('invalid')); // Returns: error message + /// + /// // Custom regex and error message + /// final customValidator = phoneNumber( + /// regex: RegExp(r'^\d{10}$'), + /// phoneNumberMsg: (input) => 'Invalid number: $input', + /// ); + /// ``` + /// {@endtemplate} + static Validator phoneNumber({ + RegExp? regex, + String Function(String input)? phoneNumberMsg, + }) => + val.phoneNumber(regex: regex, phoneNumberMsg: phoneNumberMsg); + + /// {@template validator_email} + /// A validator function that checks if a given string is a valid email address. + /// Uses either a custom or default RFC 5322 compliant regular expression for validation. + /// + /// ## Parameters + /// - `regex` (`RegExp?`): Optional custom regular expression for email validation. + /// If not provided, uses a default RFC 5322 compliant pattern that supports: + /// - ASCII characters + /// - Unicode characters (including IDN domains) + /// - Special characters in local part + /// - Quoted strings + /// - Multiple dots + /// + /// - `emailMsg` (`String Function(String input)?`): Optional custom error message + /// generator function that takes the invalid input and returns a custom error + /// message. If not provided, uses the default localized error text. + /// + /// ## Returns + /// Returns a `Validator` function that: + /// - Returns `null` if the email is valid + /// - Returns an error message string if the email is invalid + /// + /// ## Examples + /// Basic usage with default settings: + /// ```dart + /// final emailValidator = email(); + /// final result = emailValidator('user@example.com'); + /// print(result); // null (valid email) + /// ``` + /// + /// Using custom regex and error message: + /// ```dart + /// final customValidator = email( + /// regex: RegExp(r'^[a-zA-Z0-9.]+@company\.com$'), + /// emailMsg: (input) => '$input is not a valid company email', + /// ); + /// ``` + /// {@endtemplate} + static Validator email({ + RegExp? regex, + String Function(String input)? emailMsg, + }) => + val.email(regex: regex, emailMsg: emailMsg); + + // Finance validators + /// {@template validator_credit_card} + /// A validator function for credit card number validation that supports major card + /// types and implements the Luhn algorithm for number verification. + /// + /// The validator performs a two-step validation process: + /// 1. Matches the input against patterns for major credit card providers + /// 2. Validates the number using the Luhn algorithm to ensure mathematical + /// validity + /// + /// The validator supports major credit card formats including: + /// - Visa (13 and 16 digits) + /// - MasterCard (16 digits) + /// - American Express (15 digits) + /// - Discover (16 digits) + /// - Diners Club (14 digits) + /// - JCB (15 or 16 digits) + /// + /// ## Parameters + /// - `regex` (`RegExp?`): Optional custom regular expression pattern for credit + /// card validation. If not provided, defaults to the internal `_creditCardRegex` + /// pattern that supports major card providers + /// - `creditCardMsg` (`String Function(String input)?`): Optional callback function + /// that generates a custom error message based on the invalid input. If not + /// provided, uses the default error message from `FormBuilderLocalizations` + /// + /// ## Returns + /// Returns `null` if the credit card number is valid according to both the pattern + /// matching and Luhn algorithm verification. Otherwise returns an error message + /// string that can be either: + /// - A custom message generated by the provided `creditCardMsg` callback + /// - The default localized error text from `FormBuilderLocalizations` + /// + /// ## Examples + /// ```dart + /// // Basic usage with default regex + /// final validator = creditCard(); + /// print(validator('4111111111111111')); // Returns: null (valid Visa format) + /// print(validator('invalid')); // Returns: error message + /// + /// // Custom regex and error message + /// final customValidator = creditCard( + /// regex: RegExp(r'^4[0-9]{12}(?:[0-9]{3})?$'), // Visa cards only + /// creditCardMsg: (input) => 'Invalid card number: $input', + /// ); + /// ``` + /// {@endtemplate} + static Validator creditCard({ + RegExp? regex, + String Function(String input)? creditCardMsg, + }) => + val.creditCard(regex: regex, creditCardMsg: creditCardMsg); + +// Network validators + + /// {@template validator_ip} + /// Creates a validator function for IP address validation, supporting both IPv4 and IPv6 formats. + /// The validator ensures that input strings conform to the specified IP address version's format + /// and structure requirements. + /// + /// ## Parameters + /// - `version` (`IpVersion`): Specifies the IP address version to validate against. + /// Currently supports version 4 (`iPv4`), 6 (`iPv6`) or both (`any`). + /// - `regex` (`RegExp?`): Optional custom regular expression pattern for IP address + /// validation. If provided, this pattern will be used instead of the default + /// version-specific patterns. + /// - `ipMsg` (`String Function(String input)?`): Optional custom error message + /// generator function. Takes the invalid input string and returns a custom error + /// message. If not provided, defaults to the standard localized error message. + /// + /// ## Returns + /// Returns a `Validator` function that takes a string input and returns: + /// - `null` if the input is a valid IP address according to the specified version + /// - An error message string if the input is invalid + /// + /// ## Examples + /// ```dart + /// // Basic IPv4 validation + /// final ipv4Validator = ip(); + /// print(ipv4Validator('192.168.1.1')); // null (valid) + /// print(ipv4Validator('256.1.2.3')); // Returns error message (invalid) + /// + /// // Custom error message for IPv6 + /// final ipv6Validator = ip( + /// version: IpVersion.iPv6, + /// ipMsg: (input) => 'Invalid IPv6 address: $input', + /// ); + /// ``` + /// ## Caveats + /// - The validator does not verify if the IP address is actually accessible or + /// assigned on the network. + /// {@endtemplate} + static Validator ip({ + IpVersion version = IpVersion.iPv4, + RegExp? regex, + String Function(String input)? ipMsg, + }) => + val.ip( + version: version, + regex: regex, + ipMsg: ipMsg, + ); + + /// {@template validator_url} + /// A validator function that checks if a given string represents a valid URL + /// based on specified criteria. This validator supports customizable URL + /// validation including protocol verification, TLD requirements, and host + /// filtering. + /// + /// ## Parameters + /// - `protocols` (`List`): List of allowed protocols. Defaults to + /// `['http', 'https', 'ftp']` if not specified + /// - `requireTld` (`bool`): Whether the URL must contain a top-level domain. + /// Defaults to `true` + /// - `requireProtocol` (`bool`): Whether the URL must include a protocol. + /// Defaults to `false` + /// - `allowUnderscore` (`bool`): Whether underscores are permitted in the URL. + /// Defaults to `false` + /// - `hostAllowList` (`List`): List of explicitly allowed host names. + /// Empty by default + /// - `hostBlockList` (`List`): List of blocked host names. + /// Empty by default + /// - `regex` (`RegExp?`): Optional custom regular expression for additional + /// URL validation + /// - `urlMsg` (`String Function(String input)?`): Custom error message generator + /// function that takes the invalid URL as input + /// + /// ## Returns + /// Returns `null` if the URL is valid according to all specified criteria. + /// Otherwise, returns an error message string, either custom-generated via + /// `urlMsg` or the default localized URL error text. + /// + /// ## Examples + /// ```dart + /// // Basic URL validation + /// final validator = url(); + /// print(validator('https://example.com')); // Returns: null + /// + /// // Custom protocol validation + /// final ftpValidator = url( + /// protocols: ['ftp'], + /// requireProtocol: true + /// ); + /// print(ftpValidator('ftp://server.com')); // Returns: null + /// + /// // With host filtering + /// final restrictedValidator = url( + /// hostBlockList: ['example.com'], + /// hostAllowList: ['trusted-domain.com'] + /// ); + /// ``` + /// {@endtemplate} + static Validator url({ + List protocols = val.kDefaultUrlValidationProtocols, + bool requireTld = true, + bool requireProtocol = false, + bool allowUnderscore = false, + List hostAllowList = const [], + List hostBlockList = const [], + RegExp? regex, + String Function(String input)? urlMsg, + }) => + val.url( + protocols: protocols, + requireTld: requireTld, + requireProtocol: requireProtocol, + allowUnderscore: allowUnderscore, + hostAllowList: hostAllowList, + hostBlockList: hostBlockList, + regex: regex, + urlMsg: urlMsg, + ); +} diff --git a/lib/src/validators/collection_validators.dart b/lib/src/validators/collection_validators.dart new file mode 100644 index 00000000..59bd08c2 --- /dev/null +++ b/lib/src/validators/collection_validators.dart @@ -0,0 +1,133 @@ +import '../../localization/l10n.dart'; +import 'constants.dart'; + +/// {@macro validator_min_length} +Validator minLength(int min, + {String Function(T input, int min)? minLengthMsg}) { + if (min < 0) { + throw ArgumentError.value(min, 'min', 'This argument may not be negative'); + } + return (T input) { + int valueLength; + if (input is String) { + valueLength = input.length; + } else if (input is Iterable) { + valueLength = input.length; + } else if (input is Map) { + valueLength = input.length; + } else { + throw ArgumentError.value( + input, + 'input', + 'Input must be a collection (String, Iterable, or Map). Received type "${input.runtimeType}"', + ); + } + return valueLength < min + ? minLengthMsg?.call(input, min) ?? + FormBuilderLocalizations.current.minLengthErrorText(min) + : null; + }; +} + +/// {@macro validator_max_length} +Validator maxLength(int max, + {String Function(T input, int max)? maxLengthMsg}) { + if (max < 0) { + throw ArgumentError.value(max, 'max', 'This argument may not be negative'); + } + return (T input) { + int valueLength; + + if (input is String) { + valueLength = input.length; + } else if (input is Iterable) { + valueLength = input.length; + } else if (input is Map) { + valueLength = input.length; + } else { + throw ArgumentError.value( + input, + 'input', + 'Input must be a collection (String, Iterable, or Map). Received type "${input.runtimeType}"', + ); + } + + return valueLength > max + ? maxLengthMsg?.call(input, max) ?? + FormBuilderLocalizations.current.maxLengthErrorText(max) + : null; + }; +} + +/// {@macro validator_between_length} +Validator betweenLength( + int min, + int max, { + String Function(T input, {required int min, required int max})? + betweenLengthMsg, +}) { + if (min < 0) { + throw ArgumentError.value(min, 'min', 'This argument may not be negative'); + } + if (max < min) { + throw ArgumentError.value( + max, + 'max', + 'This argument must be greater than or equal to "min" length ($min)', + ); + } + return (T input) { + int valueLength; + + if (input is String) { + valueLength = input.length; + } else if (input is Iterable) { + valueLength = input.length; + } else if (input is Map) { + valueLength = input.length; + } else { + throw ArgumentError.value( + input, + 'input', + 'Input must be a collection (String, Iterable, or Map). Received type "${input.runtimeType}"', + ); + } + return (valueLength < min || valueLength > max) + ? betweenLengthMsg?.call(input, min: min, max: max) ?? + FormBuilderLocalizations.current.betweenLengthErrorText(min, max) + : null; + }; +} + +/// {@macro validator_equal_length} +Validator equalLength(int expectedLength, + {String Function(T input, int expectedLength)? equalLengthMsg}) { + if (expectedLength < 0) { + throw ArgumentError.value( + expectedLength, 'expectedLength', 'This argument may not be negative'); + } + + return (T input) { + int valueLength; + + if (input is String) { + valueLength = input.length; + } else if (input is Iterable) { + valueLength = input.length; + } else if (input is Map) { + valueLength = input.length; + } else { + throw ArgumentError.value( + input, + 'input', + 'Input must be a collection (String, Iterable, or Map). Received type "${input.runtimeType}"', + ); + } + + return valueLength != expectedLength + ? equalLengthMsg?.call(input, expectedLength) ?? + FormBuilderLocalizations.current + .equalLengthErrorText(expectedLength) + : null; + }; +} diff --git a/lib/src/validators/constants.dart b/lib/src/validators/constants.dart new file mode 100644 index 00000000..46a482a1 --- /dev/null +++ b/lib/src/validators/constants.dart @@ -0,0 +1,25 @@ +/// A validator function receives an object of type `T`, which can be any +/// type/subtype of nullable Object, and then it returns `null`, if the input +/// is valid or a [String] with the validation failure message. +/// +/// ## Why do not we use Flutter's `FormFieldValidator`? +/// Both signatures are very close to each other: +/// `FormFieldValidator = String? Function(T? value)` +/// `Validator = String? Function(T value)` +/// But there is an important difference: the `T` from [Validator] extends from +/// nullable Object and it parameter is not necessarily nullable, which makes +/// it possible to create validator that assume the input is not null. With +/// `FormFieldValidator`, the input must be checked for null value even if the +/// validator expects to receive only non-nullable inputs. +/// - Example: +/// ```dart +/// Validator isLowerCase1 = (value) { +/// return value == value.toLowerCase() ? null : 'should be lowercase'; +/// }; +/// FormFieldValidator isLowerCase2 = (value) { +/// // toLowerCase is called conditionally if `value` is not null. +/// return value == value?.toLowerCase() ? null : 'should be lowercase'; +/// }; +/// ``` +/// +typedef Validator = String? Function(T); diff --git a/lib/src/validators/core_validators/compose_validators.dart b/lib/src/validators/core_validators/compose_validators.dart new file mode 100644 index 00000000..5fe13aa3 --- /dev/null +++ b/lib/src/validators/core_validators/compose_validators.dart @@ -0,0 +1,62 @@ +// Compose validators +import '../../../localization/l10n.dart'; +import '../constants.dart'; + +/// {@macro validator_and} +Validator and( + List> validators, { + String prefix = '', + String suffix = '', + String? separator, + bool printErrorAsSoonAsPossible = true, +}) { + if (validators.isEmpty) { + throw ArgumentError.value( + '[]', 'validators', 'The list of validators must not be empty'); + } + final List> immutableValidators = + List>.unmodifiable(validators); + return (T value) { + final List errorMessageBuilder = []; + for (final Validator validator in immutableValidators) { + final String? errorMessage = validator(value); + if (errorMessage != null) { + if (printErrorAsSoonAsPossible) { + return errorMessage; + } + errorMessageBuilder.add(errorMessage); + } + } + if (errorMessageBuilder.isNotEmpty) { + return '$prefix${errorMessageBuilder.join(separator ?? FormBuilderLocalizations.current.andSeparator)}$suffix'; + } + + return null; + }; +} + +/// {@macro validator_or} +Validator or( + List> validators, { + String prefix = '', + String suffix = '', + String? separator, +}) { + if (validators.isEmpty) { + throw ArgumentError.value( + '[]', 'validators', 'The list of validators must not be empty'); + } + final List> immutableValidators = + List>.unmodifiable(validators); + return (T value) { + final List errorMessageBuilder = []; + for (final Validator validator in immutableValidators) { + final String? errorMessage = validator(value); + if (errorMessage == null) { + return null; + } + errorMessageBuilder.add(errorMessage); + } + return '$prefix${errorMessageBuilder.join(separator ?? FormBuilderLocalizations.current.orSeparator)}$suffix'; + }; +} diff --git a/lib/src/validators/core_validators/conditional_validators.dart b/lib/src/validators/core_validators/conditional_validators.dart new file mode 100644 index 00000000..9fa613a1 --- /dev/null +++ b/lib/src/validators/core_validators/conditional_validators.dart @@ -0,0 +1,13 @@ +import '../constants.dart'; + +/// {@macro validator_validate_if} +Validator validateIf( + bool Function(T value) condition, Validator v) { + return (T value) => condition(value) ? v(value) : null; +} + +/// {@macro validator_skip_if} +Validator skipIf( + bool Function(T value) condition, Validator v) { + return (T value) => condition(value) ? null : v(value); +} diff --git a/lib/src/validators/core_validators/core_validators.dart b/lib/src/validators/core_validators/core_validators.dart new file mode 100644 index 00000000..fd84e2a7 --- /dev/null +++ b/lib/src/validators/core_validators/core_validators.dart @@ -0,0 +1,7 @@ +export 'compose_validators.dart'; +export 'conditional_validators.dart'; +export 'debug_print_validator.dart'; +export 'equality_validators.dart'; +export 'required_validators.dart'; +export 'transform_validator.dart'; +export 'type_validators.dart'; diff --git a/lib/src/validators/core_validators/debug_print_validator.dart b/lib/src/validators/core_validators/debug_print_validator.dart new file mode 100644 index 00000000..f7ff9b01 --- /dev/null +++ b/lib/src/validators/core_validators/debug_print_validator.dart @@ -0,0 +1,12 @@ +import 'package:flutter/widgets.dart'; + +import '../constants.dart'; + +/// {@macro validator_debug_print_validator} +Validator debugPrintValidator( + {Validator? next, String Function(T)? logOnInput}) { + return (T value) { + debugPrint(logOnInput?.call(value) ?? value.toString()); + return next?.call(value); + }; +} diff --git a/lib/src/validators/core_validators/equality_validators.dart b/lib/src/validators/core_validators/equality_validators.dart new file mode 100644 index 00000000..7aedd1a4 --- /dev/null +++ b/lib/src/validators/core_validators/equality_validators.dart @@ -0,0 +1,30 @@ +import '../../../localization/l10n.dart'; +import '../constants.dart'; + +/// {@macro validator_is_equal} +Validator isEqual( + T referenceValue, { + String Function(T input, T referenceValue)? isEqualMsg, +}) { + return (T input) { + return referenceValue == input + ? null + : isEqualMsg?.call(input, referenceValue) ?? + FormBuilderLocalizations.current + .equalErrorText(referenceValue.toString()); + }; +} + +/// {@macro validator_is_not_equal} +Validator isNotEqual( + T referenceValue, { + String Function(T input, T referenceValue)? isNotEqualMsg, +}) { + return (T input) { + return referenceValue != input + ? null + : isNotEqualMsg?.call(input, referenceValue) ?? + FormBuilderLocalizations.current + .notEqualErrorText(referenceValue.toString()); + }; +} diff --git a/lib/src/validators/core_validators/required_validators.dart b/lib/src/validators/core_validators/required_validators.dart new file mode 100644 index 00000000..5c999a77 --- /dev/null +++ b/lib/src/validators/core_validators/required_validators.dart @@ -0,0 +1,58 @@ +import '../../../localization/l10n.dart'; +import '../constants.dart'; + +/// {@macro validator_is_required} +Validator isRequired([ + Validator? next, + String? isRequiredMsg, +]) { + String? finalValidator(T? value) { + final (bool isValid, T? transformedValue) = + _isRequiredValidateAndConvert(value); + if (!isValid) { + return isRequiredMsg ?? + FormBuilderLocalizations.current.requiredErrorText; + } + return next?.call(transformedValue!); + } + + return finalValidator; +} + +/// {@macro validator_validate_with_default} +Validator validateWithDefault( + T defaultValue, Validator next) { + return (T? value) => next(value ?? defaultValue); +} + +/// {@macro validator_is_optional} +Validator isOptional([ + Validator? next, + String Function(T input, String nextErrorMsg)? isOptionalMsg, +]) { + return (T? input) { + final (bool isValid, T? transformedValue) = + _isRequiredValidateAndConvert(input); + if (!isValid) { + // field not provided + return null; + } + final String? nextErrorMessage = next?.call(transformedValue!); + if (nextErrorMessage == null) { + return null; + } + + return isOptionalMsg?.call(input!, nextErrorMessage) ?? + FormBuilderLocalizations.current.isOptionalErrorText(nextErrorMessage); + }; +} + +(bool, T?) _isRequiredValidateAndConvert(T? value) { + if (value != null && + (value is! String || value.trim().isNotEmpty) && + (value is! Iterable || value.isNotEmpty) && + (value is! Map || value.isNotEmpty)) { + return (true, value); + } + return (false, null); +} diff --git a/lib/src/validators/core_validators/transform_validator.dart b/lib/src/validators/core_validators/transform_validator.dart new file mode 100644 index 00000000..7308fc29 --- /dev/null +++ b/lib/src/validators/core_validators/transform_validator.dart @@ -0,0 +1,24 @@ +import '../../../localization/l10n.dart'; +import '../constants.dart'; + +/// {@macro validator_transform_and_validate} +Validator transformAndValidate( + OUT Function(IN) transformFunction, { + Validator? next, + String Function(IN)? transformAndValidateMsg, + String? transformedResultTypeDescription, +}) { + return (IN input) { + try { + final OUT transformedValue = transformFunction(input); + return next?.call(transformedValue); + } catch (_) { + return transformAndValidateMsg?.call(input) ?? + (transformedResultTypeDescription == null + ? FormBuilderLocalizations.current.transformAndValidateErrorTextV1 + : FormBuilderLocalizations.current + .transformAndValidateErrorTextV2( + transformedResultTypeDescription)); + } + }; +} diff --git a/lib/src/validators/core_validators/type_validators.dart b/lib/src/validators/core_validators/type_validators.dart new file mode 100644 index 00000000..607999a5 --- /dev/null +++ b/lib/src/validators/core_validators/type_validators.dart @@ -0,0 +1,189 @@ +// Type validator: + +import '../../../localization/l10n.dart'; +import '../constants.dart'; + +/// {@macro validator_is_string} +Validator isString([ + Validator? next, + String Function(T input)? isStringMsg, +]) { + String? finalValidator(T input) { + final (bool isValid, String? typeTransformedValue) = + _isStringValidateAndConvert(input); + if (!isValid) { + return isStringMsg?.call(input) ?? + FormBuilderLocalizations.current.isStringErrorText; + } + return next?.call(typeTransformedValue!); + } + + return finalValidator; +} + +(bool, String?) _isStringValidateAndConvert(T value) { + if (value is String) { + return (true, value); + } + return (false, null); +} + +/// {@macro validator_is_int} +Validator isInt( + [Validator? next, String Function(T input)? isIntMsg]) { + String? finalValidator(T input) { + final (bool isValid, int? typeTransformedValue) = + _isIntValidateAndConvert(input); + if (!isValid) { + return isIntMsg?.call(input) ?? + FormBuilderLocalizations.current.integerErrorText; + } + return next?.call(typeTransformedValue!); + } + + return finalValidator; +} + +(bool, int?) _isIntValidateAndConvert(T value) { + if (value is int) { + return (true, value); + } + if (value is String) { + final int? candidateValue = int.tryParse(value); + if (candidateValue != null) { + return (true, candidateValue); + } + } + return (false, null); +} + +/// {@macro validator_is_num} +Validator isNum( + [Validator? next, String Function(T input)? isNumMsg]) { + String? finalValidator(T input) { + final (bool isValid, num? typeTransformedValue) = + _isNumValidateAndConvert(input); + if (!isValid) { + return isNumMsg?.call(input) ?? + FormBuilderLocalizations.current.numericErrorText; + } + return next?.call(typeTransformedValue!); + } + + return finalValidator; +} + +(bool, num?) _isNumValidateAndConvert(T value) { + if (value is num) { + return (true, value); + } + if (value is String) { + final num? candidateValue = num.tryParse(value); + if (candidateValue != null) { + return (true, candidateValue); + } + } + return (false, null); +} + +/// {@macro validator_is_double} +Validator isDouble( + [Validator? next, String Function(T input)? isDoubleMsg]) { + String? finalValidator(T input) { + final (bool isValid, double? typeTransformedValue) = + _isDoubleValidateAndConvert(input); + if (!isValid) { + // Numeric error text is enough for the final user. He does not know what + // a double is. + return isDoubleMsg?.call(input) ?? + FormBuilderLocalizations.current.numericErrorText; + } + return next?.call(typeTransformedValue!); + } + + return finalValidator; +} + +(bool, double?) _isDoubleValidateAndConvert(T value) { + if (value is double) { + return (true, value); + } + if (value is String) { + final double? candidateValue = double.tryParse(value); + if (candidateValue != null) { + return (true, candidateValue); + } + } + return (false, null); +} + +/// {@macro validator_is_bool} +Validator isBool( + [Validator? next, + String Function(T input)? isBoolMsg, + bool caseSensitive = false, + bool trim = true]) { + String? finalValidator(T input) { + final (bool isValid, bool? typeTransformedValue) = + _isBoolValidateAndConvert(input, + caseSensitive: caseSensitive, trim: trim); + if (!isValid) { + return isBoolMsg?.call(input) ?? + FormBuilderLocalizations.current.booleanErrorText; + } + return next?.call(typeTransformedValue!); + } + + return finalValidator; +} + +(bool, bool?) _isBoolValidateAndConvert(T value, + {bool caseSensitive = false, bool trim = true}) { + if (value is bool) { + return (true, value); + } + if (value is String) { + final bool? candidateValue = bool.tryParse(trim ? value.trim() : value, + caseSensitive: caseSensitive); + if (candidateValue != null) { + return (true, candidateValue); + } + } + return (false, null); +} + +/// {@macro validator_is_date_time} +Validator isDateTime([ + Validator? next, + String Function(T input)? isDateTimeMsg, +]) { + String? finalValidator(T input) { + final (bool isValid, DateTime? typeTransformedValue) = + _isDateTimeValidateAndConvert(input); + if (!isValid) { + return isDateTimeMsg?.call(input) ?? + FormBuilderLocalizations.current.dateTimeErrorText; + } + return next?.call(typeTransformedValue!); + } + + return finalValidator; +} + +(bool, DateTime?) _isDateTimeValidateAndConvert(T value) { + if (value is DateTime) { + return (true, value); + } + + if (value is String) { + final DateTime? transformedValue = DateTime.tryParse(value); + if (transformedValue != null) { + return (true, transformedValue); + } + } + return (false, null); +} + +// TODO add other types like: Uri, isT(which checks if input is T and tries to +// apply the parsing strategy from the user. It is close to transformAndValidate, +// but not altogether) etc. Is collection (if an use case is found) diff --git a/lib/src/validators/datetime_validators.dart b/lib/src/validators/datetime_validators.dart new file mode 100644 index 00000000..5ff13fd7 --- /dev/null +++ b/lib/src/validators/datetime_validators.dart @@ -0,0 +1,59 @@ +import '../../localization/l10n.dart'; +import 'constants.dart'; + +/// {@macro validator_is_after} +Validator isAfter( + DateTime reference, { + String Function(DateTime input, DateTime reference)? isAfterMsg, + bool inclusive = false, +}) { + return (DateTime input) { + return input.isAfter(reference) || + (inclusive ? input.isAtSameMomentAs(reference) : false) + ? null + : isAfterMsg?.call(input, reference) ?? + FormBuilderLocalizations.current + .dateMustBeAfterErrorText(reference.toLocal()); + }; +} + +/// {@macro validator_is_before} +Validator isBefore( + DateTime reference, { + String Function(DateTime input, DateTime reference)? isBeforeMsg, + bool inclusive = false, +}) { + return (DateTime input) { + return input.isBefore(reference) || + (inclusive ? input.isAtSameMomentAs(reference) : false) + ? null + : isBeforeMsg?.call(input, reference) ?? + FormBuilderLocalizations.current + .dateMustBeBeforeErrorText(reference.toLocal()); + }; +} + +/// {@macro validator_is_date_time_between} +Validator isDateTimeBetween( + DateTime minReference, + DateTime maxReference, { + String Function(DateTime input, DateTime minReference, DateTime maxReference)? + isDateTimeBetweenMsg, + bool minInclusive = false, + bool maxInclusive = false, +}) { + assert(minReference.isBefore(maxReference), + 'leftReference must be before rightReference'); + return (DateTime input) { + return (input.isBefore(maxReference) || + (maxInclusive + ? input.isAtSameMomentAs(maxReference) + : false)) && + (input.isAfter(minReference) || + (minInclusive ? input.isAtSameMomentAs(minReference) : false)) + ? null + : isDateTimeBetweenMsg?.call(input, minReference, maxReference) ?? + FormBuilderLocalizations.current.dateMustBeBetweenErrorText( + minReference.toLocal(), maxReference.toLocal()); + }; +} diff --git a/lib/src/validators/finance_validators.dart b/lib/src/validators/finance_validators.dart new file mode 100644 index 00000000..1fc11f0b --- /dev/null +++ b/lib/src/validators/finance_validators.dart @@ -0,0 +1,53 @@ +import '../../localization/l10n.dart'; +import 'constants.dart'; + +final RegExp _creditCardRegex = RegExp( + r'^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$', +); + +/// {@macro validator_creditCard} +Validator creditCard({ + RegExp? regex, + String Function(String input)? creditCardMsg, +}) { + return (String input) { + return _isCreditCard(input, regex ?? _creditCardRegex) + ? null + : creditCardMsg?.call(input) ?? + FormBuilderLocalizations.current.creditCardErrorText; + }; +} + +//****************************************************************************** +//* Aux functions * +//****************************************************************************** +bool _isCreditCard(String value, RegExp regex) { + final String sanitized = value.replaceAll(RegExp('[^0-9]+'), ''); + if (!regex.hasMatch(sanitized)) { + return false; + } + + // Luhn algorithm + int sum = 0; + String digit; + bool shouldDouble = false; + + for (int i = sanitized.length - 1; i >= 0; i--) { + digit = sanitized.substring(i, i + 1); + int tmpNum = int.parse(digit); + + if (shouldDouble) { + tmpNum *= 2; + if (tmpNum >= 10) { + sum += (tmpNum % 10) + 1; + } else { + sum += tmpNum; + } + } else { + sum += tmpNum; + } + shouldDouble = !shouldDouble; + } + + return (sum % 10 == 0); +} diff --git a/lib/src/validators/generic_type_validators.dart b/lib/src/validators/generic_type_validators.dart new file mode 100644 index 00000000..8234c1de --- /dev/null +++ b/lib/src/validators/generic_type_validators.dart @@ -0,0 +1,75 @@ +import '../../localization/l10n.dart'; +import 'constants.dart'; + +/// {@macro validator_contains_element} +Validator containsElement( + List values, { + String Function(T input, List values)? containsElementMsg, +}) { + if (values.isEmpty) { + throw ArgumentError.value( + '[]', 'values', 'The list of values must not be empty'); + } + final Set setOfValues = values.toSet(); + return (T input) { + return setOfValues.contains(input) + ? null + : containsElementMsg?.call(input, values) ?? + FormBuilderLocalizations.current.containsElementErrorText; + }; +} + +/// {@macro validator_is_true} +Validator isTrue( + {String Function(T input)? isTrueMsg, + bool caseSensitive = false, + bool trim = true}) { + return (T input) { + final (bool isValid, bool? typeTransformedValue) = + _isBoolValidateAndConvert( + input, + caseSensitive: caseSensitive, + trim: trim, + ); + if (isValid && typeTransformedValue! == true) { + return null; + } + return isTrueMsg?.call(input) ?? + FormBuilderLocalizations.current.mustBeTrueErrorText; + }; +} + +/// {@macro validator_is_false} +Validator isFalse( + {String Function(T input)? isFalseMsg, + bool caseSensitive = false, + bool trim = true}) { + return (T input) { + final (bool isValid, bool? typeTransformedValue) = + _isBoolValidateAndConvert( + input, + caseSensitive: caseSensitive, + trim: trim, + ); + if (isValid && typeTransformedValue! == false) { + return null; + } + return isFalseMsg?.call(input) ?? + FormBuilderLocalizations.current.mustBeFalseErrorText; + }; +} + +(bool, bool?) _isBoolValidateAndConvert(T value, + {bool caseSensitive = false, bool trim = true}) { + if (value is bool) { + return (true, value); + } + if (value is String) { + final bool? candidateValue = bool.tryParse(trim ? value.trim() : value, + caseSensitive: caseSensitive); + if (candidateValue != null) { + return (true, candidateValue); + } + } + return (false, null); +} diff --git a/lib/src/validators/network_validators.dart b/lib/src/validators/network_validators.dart new file mode 100644 index 00000000..0f44cf74 --- /dev/null +++ b/lib/src/validators/network_validators.dart @@ -0,0 +1,287 @@ +import '../../localization/l10n.dart'; +import 'constants.dart'; + +/// Represents supported Internet Protocol versions +/// Supported versions: +/// - `iPv4` +/// - `iPv6` +/// - `any` (supports all versions) +enum IpVersion { + /// IPv4 (RFC 791) - 32-bit addresses + /// - Format: Four 8-bit decimal numbers (0-255) separated by dots + /// - Example: 192.168.1.1, 10.0.0.1 + iPv4, + + /// IPv6 (RFC 2460) - 128-bit addresses + /// - Format: Eight 16-bit hexadecimal groups separated by colons + /// - Example: 2001:0db8:85a3:0000:0000:8a2e:0370:7334 + /// - Allows compression of zeros (::) and leading zero omission + iPv6, + + /// Accepts both IPv4 and IPv6 formats + any, +} + +/// {@macro ip_validator} +Validator ip({ + IpVersion version = IpVersion.iPv4, + // TODO(ArturAssisComp): study the possibility to replace this parameter with + // a more generic one. Instead of accepting a regex for IP, accepts a function + // that returns if the ip address is valid or not. Something like: + // bool Function(String input) customIpValidator, + RegExp? regex, + String Function(String input)? ipMsg, +}) { + return (String input) { + return !_isIP(input, version, regex) + // TODO(ArturAssisComp): study the possibility to make the error message more + // meaningful. One good example is the password validator, which may be + // configured to return a detailed message of why it is invalid. + ? ipMsg?.call(input) ?? FormBuilderLocalizations.current.ipErrorText + : null; + }; +} + +/// Default protocols to be used for the url validation. +/// The protocols are: +/// - `http` +/// - `https` +/// - `ftp` +const List kDefaultUrlValidationProtocols = [ + 'http', + 'https', + 'ftp' +]; + +/// {@macro validator_url} +Validator url({ + List protocols = kDefaultUrlValidationProtocols, + bool requireTld = true, + bool requireProtocol = false, + bool allowUnderscore = false, + List hostAllowList = const [], + List hostBlockList = const [], + RegExp? regex, + String Function(String input)? urlMsg, +}) { + final List immutableProtocols = List.unmodifiable(protocols); + final List immutableHostAllowList = + List.unmodifiable(hostAllowList); + final List immutableHostBlockList = + List.unmodifiable(hostBlockList); + return (String value) { + return (regex != null && !regex.hasMatch(value)) || + !_isURL( + value, + protocols: immutableProtocols, + requireTld: requireTld, + requireProtocol: requireProtocol, + allowUnderscore: allowUnderscore, + hostAllowList: immutableHostAllowList, + hostBlockList: immutableHostBlockList, + ) + ? urlMsg?.call(value) ?? FormBuilderLocalizations.current.urlErrorText + : null; + }; +} + +//****************************************************************************** +//* Aux functions * +//****************************************************************************** +const int _maxUrlLength = 2083; +final RegExp _ipv4Maybe = + RegExp(r'^(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)\.(\d?\d?\d)$'); +final RegExp _ipv6 = RegExp( + r'^((?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(?::0{1,4})?:)?(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$', +); + +/// Check if the string [str] is IP [version] 4 or 6. +/// +/// * [version] is a String or an `int`. +bool _isIP(String str, IpVersion version, RegExp? regex) { + if (regex != null) { + return regex.hasMatch(str); + } + switch (version) { + case IpVersion.iPv4: + if (!_ipv4Maybe.hasMatch(str)) { + return false; + } + final List parts = str.split('.'); + return !parts.any((String e) => int.parse(e) > 255); + case IpVersion.iPv6: + return _ipv6.hasMatch(str); + case IpVersion.any: + return _isIP(str, IpVersion.iPv4, regex) || + _isIP(str, IpVersion.iPv6, regex); + } +} + +/// Check if the string [value] is a URL. +/// +/// * [protocols] sets the list of allowed protocols +/// * [requireTld] sets if TLD is required +/// * [requireProtocol] is a `bool` that sets if protocol is required for validation +/// * [allowUnderscore] sets if underscores are allowed +/// * [hostAllowList] sets the list of allowed hosts +/// * [hostBlockList] sets the list of disallowed hosts +bool _isURL( + String value, { + required List protocols, + bool requireTld = true, + bool requireProtocol = false, + bool allowUnderscore = false, + required List hostAllowList, + required List hostBlockList, + RegExp? regexp, +}) { + if (value.isEmpty || + value.length > _maxUrlLength || + value.startsWith('mailto:')) { + return false; + } + final int port; + final String? protocol; + final String? auth; + final String user; + final String host; + final String hostname; + final String portStr; + final String path; + final String query; + final String hash; + + // check protocol + List split = value.split('://'); + if (split.length > 1) { + protocol = _shift(split).toLowerCase(); + if (!protocols.contains(protocol)) { + return false; + } + } else if (requireProtocol == true) { + return false; + } + final String str1 = split.join('://'); + + // check hash + split = str1.split('#'); + final String str2 = _shift(split); + hash = split.join('#'); + if (hash.isNotEmpty && RegExp(r'\s').hasMatch(hash)) { + return false; + } + + // check query params + split = str2.split('?'); + final String str3 = _shift(split); + query = split.join('?'); + if (query.isNotEmpty && RegExp(r'\s').hasMatch(query)) { + return false; + } + + // check path + split = str3.split('/'); + final String str4 = _shift(split); + path = split.join('/'); + if (path.isNotEmpty && RegExp(r'\s').hasMatch(path)) { + return false; + } + + // check auth type urls + split = str4.split('@'); + if (split.length > 1) { + auth = _shift(split); + if (auth?.contains(':') ?? false) { + user = _shift(auth!.split(':')); + if (!RegExp(r'^\S+$').hasMatch(user)) { + return false; + } + if (!RegExp(r'^\S*$').hasMatch(user)) { + return false; + } + } + } + + // check hostname + hostname = split.join('@'); + split = hostname.split(':'); + host = _shift(split).toLowerCase(); + if (split.isNotEmpty) { + portStr = split.join(':'); + try { + port = int.parse(portStr, radix: 10); + } catch (e) { + return false; + } + if (!RegExp(r'^[0-9]+$').hasMatch(portStr) || port <= 0 || port > 65535) { + return false; + } + } + + if (!_isIP(host, IpVersion.any, regexp) && + !_isFQDN( + host, + requireTld: requireTld, + allowUnderscores: allowUnderscore, + ) && + host != 'localhost') { + return false; + } + + if (hostAllowList.isNotEmpty && !hostAllowList.contains(host)) { + return false; + } + + if (hostBlockList.isNotEmpty && hostBlockList.contains(host)) { + return false; + } + + return true; +} + +/// Check if the string [str] is a fully qualified domain name (e.g., domain.com). +/// +/// * [requireTld] sets if TLD is required +/// * [allowUnderscores] sets if underscores are allowed +bool _isFQDN( + String str, { + bool requireTld = true, + bool allowUnderscores = false, +}) { + final List parts = str.split('.'); + if (requireTld) { + final String tld = parts.removeLast(); + if (parts.isEmpty || !RegExp(r'^[a-z]{2,}$').hasMatch(tld)) { + return false; + } + } + + final String partPattern = allowUnderscores + ? r'^[a-z\u00a1-\uffff0-9-_]+$' + : r'^[a-z\u00a1-\uffff0-9-]+$'; + + for (final String part in parts) { + if (!RegExp(partPattern).hasMatch(part)) { + return false; + } + if (part[0] == '-' || + part[part.length - 1] == '-' || + part.contains('---') || + (allowUnderscores && part.contains('__'))) { + return false; + } + } + return true; +} + +/// Remove and return the first element from a list. +T _shift(List l) { + if (l.isNotEmpty) { + final T first = l.first; + // TODO why not iterating the list? + l.removeAt(0); + return first; + } + // TODO refactor that. + return null as T; +} diff --git a/lib/src/validators/numeric_validators.dart b/lib/src/validators/numeric_validators.dart new file mode 100644 index 00000000..601095f3 --- /dev/null +++ b/lib/src/validators/numeric_validators.dart @@ -0,0 +1,81 @@ +import '../../localization/l10n.dart'; +import 'constants.dart'; + +/// {@macro validator_greater_than} +Validator greaterThan(T reference, + {String Function(T input, T reference)? greaterThanMsg}) { + return (T input) { + return input > reference + ? null + : greaterThanMsg?.call(input, reference) ?? + FormBuilderLocalizations.current.greaterThanErrorText(reference); + }; +} + +/// {@macro validator_greater_than_or_equal_to} +Validator greaterThanOrEqualTo(T reference, + {String Function(T input, T reference)? greaterThanOrEqualToMsg}) { + return (T input) { + return input >= reference + ? null + : greaterThanOrEqualToMsg?.call(input, reference) ?? + FormBuilderLocalizations.current + .greaterThanOrEqualToErrorText(reference); + }; +} + +/// {@macro validator_less_than} +Validator lessThan(T reference, + {String Function(T input, T reference)? lessThanMsg}) { + return (T input) { + return input < reference + ? null + : lessThanMsg?.call(input, reference) ?? + FormBuilderLocalizations.current.lessThanErrorText(reference); + }; +} + +/// {@macro validator_less_than_or_equal_to} +Validator lessThanOrEqualTo(T reference, + {String Function(T input, T reference)? lessThanOrEqualToMsg}) { + return (T input) { + return input <= reference + ? null + : lessThanOrEqualToMsg?.call(input, reference) ?? + FormBuilderLocalizations.current + .lessThanOrEqualToErrorText(reference); + }; +} + +/// {@macro validator_between} +Validator between(T min, T max, + {bool minInclusive = true, + bool maxInclusive = true, + String Function( + T input, + T min, + T max, + bool minInclusive, + bool maxInclusive, + )? betweenMsg}) { + if (min > max) { + throw ArgumentError.value( + min, 'min', 'Min must be less than or equal to max(=$max)'); + } + return (T input) { + return (minInclusive ? input >= min : input > min) && + (maxInclusive ? input <= max : input < max) + ? null + : betweenMsg?.call( + input, + min, + max, + minInclusive, + maxInclusive, + ) ?? + FormBuilderLocalizations.current.betweenNumErrorText( + min, max, minInclusive.toString(), maxInclusive.toString()); + }; +} + +// other possible validators: isPositive/isNegative, isMultipleOf, etc. diff --git a/lib/src/validators/path_validators.dart b/lib/src/validators/path_validators.dart new file mode 100644 index 00000000..83b3d586 --- /dev/null +++ b/lib/src/validators/path_validators.dart @@ -0,0 +1,57 @@ +import 'package:path/path.dart' as p; + +import '../../localization/l10n.dart'; +import 'constants.dart'; + +bool _isValidExtension(String v) => v.isEmpty || v[0] == '.'; + +/// Expects not empty v +int _numOfExtensionLevels(String v) => r'.'.allMatches(v).length; + +/// {@macro validator_matches_allowed_extensions} +Validator matchesAllowedExtensions( + List extensions, { + String Function(List)? matchesAllowedExtensionsMsg, + bool caseSensitive = true, +}) { + if (extensions.isEmpty) { + throw ArgumentError.value( + '[]', 'extensions', 'The list of extensions must not be empty'); + } + int maxLevel = 1; + for (final (int i, String ex) in extensions.indexed) { + if (!_isValidExtension(ex)) { + throw ArgumentError.value(ex, 'extensions[$i]', 'Invalid extension'); + } + final int currentLevel = _numOfExtensionLevels(ex); + if (currentLevel > maxLevel) { + maxLevel = currentLevel; + } + } + Set extensionsSet = {}; + if (caseSensitive) { + extensionsSet.addAll(extensions); + } else { + for (final String extension in extensions) { + extensionsSet.add(extension.toLowerCase()); + } + } + return (String input) { + final String finalInput = caseSensitive ? input : input.toLowerCase(); + final String firstLevelExtension = p.extension(finalInput); + final Set extensionsFromUserInput = {firstLevelExtension}; + + if (firstLevelExtension.isNotEmpty) { + for (int i = 1; i < maxLevel; i++) { + final String extension = p.extension(finalInput, i + 1); + extensionsFromUserInput.add(extension); + } + } + return extensionsFromUserInput.intersection(extensionsSet).isNotEmpty + ? null + : matchesAllowedExtensionsMsg?.call(extensions) ?? + FormBuilderLocalizations.current.fileExtensionErrorText( + extensions.join(', '), + ); + }; +} diff --git a/lib/src/validators/string_validators.dart b/lib/src/validators/string_validators.dart new file mode 100644 index 00000000..8ad6ab93 --- /dev/null +++ b/lib/src/validators/string_validators.dart @@ -0,0 +1,161 @@ +import '../../../localization/l10n.dart'; +import 'constants.dart'; + +final RegExp _numericRegex = RegExp('[0-9]'); + +({int upperCount, int lowerCount}) _upperAndLowerCaseCounter(String value) { + int uppercaseCount = 0; + int lowercaseCount = 0; + final String upperCaseVersion = value.toUpperCase(); + final String lowerCaseVersion = value.toLowerCase(); + // initial version: 1.0 + final RuneIterator o = value.runes.iterator; + final RuneIterator u = upperCaseVersion.runes.iterator; + final RuneIterator l = lowerCaseVersion.runes.iterator; + while (o.moveNext() && u.moveNext() && l.moveNext()) { + if (o.current == u.current && o.current != l.current) { + uppercaseCount++; + } else if (o.current != u.current && o.current == l.current) { + lowercaseCount++; + } + } + return (lowerCount: lowercaseCount, upperCount: uppercaseCount); +} + +/// {@macro validator_has_min_uppercase_chars} +Validator hasMinUppercaseChars({ + int min = 1, + int Function(String)? customUppercaseCounter, + String Function(String input, int min)? hasMinUppercaseCharsMsg, +}) { + if (min < 1) { + throw ArgumentError.value(min, 'min', 'This argument must be at least 1'); + } + + return (String input) { + int uppercaseCount = customUppercaseCounter?.call(input) ?? + _upperAndLowerCaseCounter(input).upperCount; + + return uppercaseCount >= min + ? null + : hasMinUppercaseCharsMsg?.call(input, min) ?? + FormBuilderLocalizations.current + .containsUppercaseCharErrorText(min); + }; +} + +/// {@macro validator_has_min_lowercase_chars} +Validator hasMinLowercaseChars({ + int min = 1, + int Function(String)? customLowercaseCounter, + String Function(String input, int min)? hasMinLowercaseCharsMsg, +}) { + if (min < 1) { + throw ArgumentError.value(min, 'min', 'This argument must be at least 1'); + } + return (String input) { + int lowercaseCount = customLowercaseCounter?.call(input) ?? + _upperAndLowerCaseCounter(input).lowerCount; + + return lowercaseCount >= min + ? null + : hasMinLowercaseCharsMsg?.call(input, min) ?? + FormBuilderLocalizations.current + .containsLowercaseCharErrorText(min); + }; +} + +/// {@macro validator_has_min_numeric_chars} +Validator hasMinNumericChars({ + int min = 1, + int Function(String)? customNumericCounter, + String Function(String input, int min)? hasMinNumericCharsMsg, +}) { + if (min < 1) { + throw ArgumentError.value(min, 'min', 'This argument must be at least 1'); + } + return (String input) { + final int numericCount = customNumericCounter?.call(input) ?? + _numericRegex.allMatches(input).length; + return numericCount >= min + ? null + : hasMinNumericCharsMsg?.call(input, min) ?? + FormBuilderLocalizations.current.containsNumberErrorText(min); + }; +} + +/// {@macro validator_has_min_special_chars} +Validator hasMinSpecialChars({ + int min = 1, + int Function(String)? customSpecialCounter, + String Function(String input, int min)? hasMinSpecialCharsMsg, +}) { + if (min < 1) { + throw ArgumentError.value(min, 'min', 'This argument must be at least 1'); + } + return (String input) { + int specialCount; + if (customSpecialCounter == null) { + final (lowerCount: int lowerCount, upperCount: int upperCount) = + _upperAndLowerCaseCounter(input); + specialCount = input.length - + (lowerCount + upperCount + _numericRegex.allMatches(input).length); + } else { + specialCount = customSpecialCounter.call(input); + } + + return specialCount >= min + ? null + : hasMinSpecialCharsMsg?.call(input, min) ?? + FormBuilderLocalizations.current.containsSpecialCharErrorText(min); + }; +} + +/// {@macro validator_match} +Validator match( + RegExp regex, { + String Function(String input)? matchMsg, +}) { + return (String input) { + return regex.hasMatch(input) + ? null + : matchMsg?.call(input) ?? + FormBuilderLocalizations.current.matchErrorText; + }; +} + +final RegExp _uuidRegex = RegExp( + r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$', +); + +/// {@macro validator_uuid} +Validator uuid({ + RegExp? regex, + String Function(String input)? uuidMsg, +}) { + return (String input) { + return (regex ?? _uuidRegex).hasMatch(input) + ? null + : uuidMsg?.call(input) ?? + FormBuilderLocalizations.current.uuidErrorText; + }; +} + +/// {@macro validator_contains} +Validator contains( + String substring, { + bool caseSensitive = true, + String Function(String substring, String input)? containsMsg, +}) { + return (String input) { + if (substring.isEmpty) { + return null; + } else if (caseSensitive + ? input.contains(substring) + : input.toLowerCase().contains(substring.toLowerCase())) { + return null; + } + return containsMsg?.call(substring, input) ?? + FormBuilderLocalizations.current.containsErrorText(substring); + }; +} diff --git a/lib/src/validators/user_information_validators.dart b/lib/src/validators/user_information_validators.dart new file mode 100644 index 00000000..7812b00a --- /dev/null +++ b/lib/src/validators/user_information_validators.dart @@ -0,0 +1,65 @@ +import '../../form_builder_validators.dart'; +import 'collection_validators.dart' as collection_val; +import 'core_validators/compose_validators.dart'; +import 'string_validators.dart'; + +/// {@macro validator_password} +Validator password({ + int minLength = 16, + int maxLength = 32, + int minUppercaseCount = 1, + int minLowercaseCount = 1, + int minNumberCount = 1, + int minSpecialCharCount = 1, + String Function(String input)? passwordMsg, +}) { + if (maxLength < minLength) { + throw ArgumentError.value(maxLength, 'maxLength', + 'The maxLength may not be less than minLength (=$minLength)'); + } + final Validator andValidator = and(>[ + collection_val.minLength(minLength), + collection_val.maxLength(maxLength), + hasMinUppercaseChars(min: minUppercaseCount), + hasMinLowercaseChars(min: minLowercaseCount), + hasMinNumericChars(min: minNumberCount), + hasMinSpecialChars(min: minSpecialCharCount), + ]); + String? validatorWithPasswordMsg(String input) => + andValidator(input) == null ? null : passwordMsg?.call(input); + return passwordMsg != null ? validatorWithPasswordMsg : andValidator; +} + +final RegExp _phoneNumberRegex = RegExp( + r'^\+?(\d{1,4}[\s.-]?)?(\(?\d{1,4}\)?[\s.-]?)?(\d{1,4}[\s.-]?)?(\d{1,4}[\s.-]?)?(\d{1,9})$', +); + +/// {@macro validator_phoneNumber} +Validator phoneNumber({ + RegExp? regex, + String Function(String input)? phoneNumberMsg, +}) { + return (String input) { + final String phoneNumber = input.replaceAll(' ', '').replaceAll('-', ''); + return (regex ?? _phoneNumberRegex).hasMatch(phoneNumber) + ? null + : phoneNumberMsg?.call(input) ?? + FormBuilderLocalizations.current.phoneErrorText; + }; +} + +/// {@macro validator_email} +Validator email({ + RegExp? regex, + String Function(String input)? emailMsg, +}) { + final RegExp defaultRegex = RegExp( + r"^((([a-z]|\d|[!#$%&'*+\-/=?^_`{|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#$%&'*+\-/=?^_`{|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)(((([\x20\x09])*(\x0d\x0a))?([\x20\x09])+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*((([\x20\x09])*(\x0d\x0a))?([\x20\x09])+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$", + ); + return (String input) { + return (regex ?? defaultRegex).hasMatch(input.toLowerCase()) + ? null + : emailMsg?.call(input) ?? + FormBuilderLocalizations.current.emailErrorText; + }; +} diff --git a/lib/src/validators/validators.dart b/lib/src/validators/validators.dart new file mode 100644 index 00000000..a1f57f02 --- /dev/null +++ b/lib/src/validators/validators.dart @@ -0,0 +1,11 @@ +export 'collection_validators.dart'; +export 'constants.dart'; +export 'core_validators/core_validators.dart'; +export 'datetime_validators.dart'; +export 'finance_validators.dart'; +export 'generic_type_validators.dart'; +export 'network_validators.dart'; +export 'numeric_validators.dart'; +export 'path_validators.dart'; +export 'string_validators.dart'; +export 'user_information_validators.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 2b157fe1..19ca75d1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: flutter_localizations: sdk: flutter intl: ^0.19.0 + path: ^1.9.0 dev_dependencies: faker_dart: ^0.2.2 diff --git a/test/src/datetime/date_past_validator_test.dart b/test/src/datetime/date_past_validator_test.dart index efe1be3f..d2f4fe77 100644 --- a/test/src/datetime/date_past_validator_test.dart +++ b/test/src/datetime/date_past_validator_test.dart @@ -57,6 +57,8 @@ void main() { expect(result, equals(customErrorMessage)); }); + // TODO(ArturAssisComp): fix this test case. It passes when executed alone + // but fails when executed with the complete test suite. test('should return null when the date is today', () { // Arrange const DatePastValidator validator = DatePastValidator(); diff --git a/test/src/validators/collection_validators/between_length_validator_test.dart b/test/src/validators/collection_validators/between_length_validator_test.dart new file mode 100644 index 00000000..01a958bb --- /dev/null +++ b/test/src/validators/collection_validators/between_length_validator_test.dart @@ -0,0 +1,77 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: betweenLength', () { + group('Validators with default error message', () { + test( + 'Should validate the input when it is expected to have minLength equals to maxLength', + () { + final Validator> v = betweenLength(0, 0); + final Validator v2 = betweenLength(3, 3); + + expect(v([]), isNull); + expect(v([12]), + FormBuilderLocalizations.current.betweenLengthErrorText(0, 0)); + expect(v2([12, '23', 456]), isNull); + expect(v2([12]), + FormBuilderLocalizations.current.betweenLengthErrorText(3, 3)); + }); + test( + 'Should validate the input when it is expected to have minLength 1 and maxLength 5', + () { + final Validator> v = betweenLength(1, 5); + final Validator v2 = betweenLength(1, 5); + + expect(v([]), + FormBuilderLocalizations.current.betweenLengthErrorText(1, 5)); + expect(v([0]), isNull); + expect(v([0, 1]), isNull); + expect(v([0, 2, 3]), isNull); + expect(v([23, 432, 52, 65, 1]), isNull); + expect(v([1, 2, 3, 4, 5, 6]), + FormBuilderLocalizations.current.betweenLengthErrorText(1, 5)); + expect(v([1, 2, 3, 4, 5, 6, 7]), + FormBuilderLocalizations.current.betweenLengthErrorText(1, 5)); + + expect(v2([]), + FormBuilderLocalizations.current.betweenLengthErrorText(1, 5)); + expect(v2(''), + FormBuilderLocalizations.current.betweenLengthErrorText(1, 5)); + expect(v2(['0']), isNull); + expect(v2([1, '3']), isNull); + expect(v2('hi '), isNull); + expect(v2(' '), + FormBuilderLocalizations.current.betweenLengthErrorText(1, 5)); + }); + }); + + test('Should validate input returning custom message error when invalid', + () { + const String customMsg = 'custom msg'; + final Validator v = betweenLength(3, 4, + betweenLengthMsg: (_, {required int min, required int max}) => + customMsg); + + expect(v({89: 123}), equals(customMsg)); + expect(v([1, '2', 3, 4]), isNull); + expect(v(' '), isNull); + }); + group('Throws Argument error', () { + test('Should throw ArgumentError when minLength is negative', () { + expect(() => betweenLength(-2, 3), throwsArgumentError); + expect(() => betweenLength(-2, -3), throwsArgumentError); + }); + test('Should throw ArgumentError when maxLength is less than minLength', + () { + expect(() => betweenLength(3, 2), throwsArgumentError); + expect(() => betweenLength(3, -2), throwsArgumentError); + }); + test('Should throw ArgumentError when input is not a collection', () { + expect(() => betweenLength(2, 3)(('this is a record',)), + throwsArgumentError); + }); + }); + }); +} diff --git a/test/src/validators/collection_validators/equal_length_validator_test.dart b/test/src/validators/collection_validators/equal_length_validator_test.dart new file mode 100644 index 00000000..5a22ab78 --- /dev/null +++ b/test/src/validators/collection_validators/equal_length_validator_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: equalLength', () { + group('Validations with default error message', () { + test('Should validate the input when it is expected to have length 0', + () { + final Validator> v = equalLength(0); + final Validator v2 = equalLength(0); + + expect(v([]), isNull); + expect(v2([12]), + FormBuilderLocalizations.current.equalLengthErrorText(0)); + expect(v2({12}), + FormBuilderLocalizations.current.equalLengthErrorText(0)); + }); + + test('Should validate the input when it is expected to have length 1', + () { + final Validator> v = equalLength(1); + final Validator v2 = equalLength(1); + + expect(v([0]), isNull); + expect(v2(['0']), isNull); + expect(v2('0'), isNull); + expect(v([]), + FormBuilderLocalizations.current.equalLengthErrorText(1)); + expect(v2([]), + FormBuilderLocalizations.current.equalLengthErrorText(1)); + expect(v([1, 2]), + FormBuilderLocalizations.current.equalLengthErrorText(1)); + expect(v2([1, '3']), + FormBuilderLocalizations.current.equalLengthErrorText(1)); + }); + test('Should validate the input when it is expected to have length 4', + () { + final Validator> v = equalLength(4); + final Validator v2 = equalLength(4); + + expect(v(['1', '2', '3', '4']), isNull); + expect(v([]), + FormBuilderLocalizations.current.equalLengthErrorText(4)); + expect(v(['2', '3', '4']), + FormBuilderLocalizations.current.equalLengthErrorText(4)); + + expect(v2('1234'), isNull); + expect( + v2(''), FormBuilderLocalizations.current.equalLengthErrorText(4)); + expect(v2('123'), + FormBuilderLocalizations.current.equalLengthErrorText(4)); + }); + }); + + test('Should validate input returning custom message error when invalid', + () { + const String customMsg = 'custom msg'; + final Validator v = + equalLength(3, equalLengthMsg: (_, __) => customMsg); + + expect(v('hey'), isNull); + expect(v([1, '2', 3, 4]), equals(customMsg)); + }); + test('Should throw ArgumentError when length is negative', () { + expect(() => equalLength(-2), throwsArgumentError); + }); + test('Should throw ArgumentError when input is not a collection', () { + expect( + () => equalLength(2)(() => 'I am a function'), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/collection_validators/max_length_validator_test.dart b/test/src/validators/collection_validators/max_length_validator_test.dart new file mode 100644 index 00000000..798fcc43 --- /dev/null +++ b/test/src/validators/collection_validators/max_length_validator_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: maxLength', () { + group('Validators with default error message', () { + test('Should validate the input when it is expected to have maxLength 0', + () { + final Validator> v = maxLength(0); + final Validator v2 = maxLength(0); + + expect(v([]), isNull); + expect(v2([12]), + FormBuilderLocalizations.current.maxLengthErrorText(0)); + }); + test('Should validate the input when it is expected to have maxLength 1', + () { + final Validator> v = maxLength(1); + final Validator v2 = maxLength(1); + + expect(v([]), isNull); + expect(v([0]), isNull); + expect(v2(['0']), isNull); + expect(v2({0}), isNull); + expect(v([1, 2]), + FormBuilderLocalizations.current.maxLengthErrorText(1)); + expect(v2([1, '3']), + FormBuilderLocalizations.current.maxLengthErrorText(1)); + }); + test('Should validate the input when it is expected to have maxLength 4', + () { + final Validator> v = maxLength(4); + final Validator v2 = maxLength(4); + + expect(v([]), isNull); + expect(v(['1']), isNull); + expect(v(['1', '2']), isNull); + expect(v(['1', '2', '3']), isNull); + expect(v(['1', '2', '3', '4']), isNull); + + expect(v2(''), isNull); + expect(v2('1'), isNull); + expect(v2('12'), isNull); + expect(v2('123'), isNull); + expect(v2('1234'), isNull); + }); + }); + + test('Should validate input returning custom message error when invalid', + () { + const String customMsg = 'custom msg'; + final Validator v = + maxLength(3, maxLengthMsg: (_, __) => customMsg); + + expect(v('hey'), isNull); + expect(v([1, '2', 3, 4]), equals(customMsg)); + }); + test('Should throw ArgumentError when maxLength is negative', () { + expect(() => maxLength(-2), throwsArgumentError); + }); + test('Should throw ArgumentError when input is not a collection', () { + expect(() => maxLength(2)(123), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/collection_validators/min_length_validator_test.dart b/test/src/validators/collection_validators/min_length_validator_test.dart new file mode 100644 index 00000000..9999f0b1 --- /dev/null +++ b/test/src/validators/collection_validators/min_length_validator_test.dart @@ -0,0 +1,76 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: minLength', () { + group('Validators with default error message', () { + test('Should validate the input when it is expected to have minLength 0', + () { + final Validator> v = minLength(0); + final Validator v2 = minLength(0); + + expect(v([]), isNull); + expect(v2([12]), isNull); + }); + test('Should validate the input when it is expected to have minLength 1', + () { + final Validator> v = minLength(1); + final Validator v2 = minLength(1); + + expect( + v([]), FormBuilderLocalizations.current.minLengthErrorText(1)); + expect(v([0]), isNull); + expect(v([1, 2]), isNull); + + expect(v2([]), + FormBuilderLocalizations.current.minLengthErrorText(1)); + expect(v2(['0']), isNull); + expect(v2([1, '3']), isNull); + }); + test('Should validate the input when it is expected to have minLength 4', + () { + final Validator> v = minLength(4); + final Validator v2 = minLength(4); + + expect(v([]), + FormBuilderLocalizations.current.minLengthErrorText(4)); + expect(v(['1']), + FormBuilderLocalizations.current.minLengthErrorText(4)); + expect(v(['1', '2']), + FormBuilderLocalizations.current.minLengthErrorText(4)); + expect(v(['1', '2', '3']), + FormBuilderLocalizations.current.minLengthErrorText(4)); + expect(v(['1', '2', '3', '4']), isNull); + expect(v(['1', '2', '3', '4', '5']), isNull); + expect(v(['1', '2', '3', '4', '5', '6']), isNull); + + expect(v2(''), FormBuilderLocalizations.current.minLengthErrorText(4)); + expect(v2('1'), FormBuilderLocalizations.current.minLengthErrorText(4)); + expect( + v2('12'), FormBuilderLocalizations.current.minLengthErrorText(4)); + expect( + v2('123'), FormBuilderLocalizations.current.minLengthErrorText(4)); + expect(v2('1234'), isNull); + expect(v2('12345'), isNull); + expect(v2('123456'), isNull); + }); + }); + + test('Should validate input returning custom message error when invalid', + () { + const String customMsg = 'custom msg'; + final Validator v = + minLength(3, minLengthMsg: (_, __) => customMsg); + + expect(v({1: '1', 2: '2'}), equals(customMsg)); + expect(v([1, '2', 3, 4]), isNull); + }); + test('Should throw ArgumentError when minLength is negative', () { + expect(() => minLength(-2), throwsArgumentError); + }); + test('Should throw ArgumentError when input is not collection', () { + expect(() => minLength(2)(12.3), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/core_validators/composition_validators/and_validator_test.dart b/test/src/validators/core_validators/composition_validators/and_validator_test.dart new file mode 100644 index 00000000..02e3df45 --- /dev/null +++ b/test/src/validators/core_validators/composition_validators/and_validator_test.dart @@ -0,0 +1,163 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +const String errorGt = 'error gt'; +Validator gt(num target) { + return (num v) { + return v > target ? null : errorGt; + }; +} + +const String errorLtE = 'error lte'; +Validator ltE(num target) { + return (num v) { + return v <= target ? null : errorLtE; + }; +} + +const String errorIsEven = 'error is even'; +Validator isEven() { + return (num v) { + if (v is int && v % 2 == 0) { + return null; + } + return errorIsEven; + }; +} + +void main() { + group('Composition: and', () { + test('Should validate using only one validator', () { + final Validator v1 = and(>[gt(10)]); + final Validator v2 = + and(>[gt(10)], printErrorAsSoonAsPossible: false); + + expect(v1(9), errorGt); + expect(v1(10), errorGt); + expect(v1(11), isNull); + + expect(v2(9), errorGt); + expect(v2(10), errorGt); + expect(v2(11), isNull); + }); + test('Should validate using two validators', () { + final Validator v1 = and(>[gt(10), ltE(19)]); + final Validator v2 = and(>[ltE(19), gt(10)], + printErrorAsSoonAsPossible: false); + + expect(v1(9), errorGt); + expect(v1(10), errorGt); + expect(v1(11), isNull); + expect(v1(18), isNull); + expect(v1(19), isNull); + expect(v1(20), errorLtE); + + expect(v2(9), errorGt); + expect(v2(10), errorGt); + expect(v2(11), isNull); + expect(v2(18), isNull); + expect(v2(19), isNull); + expect(v2(20), errorLtE); + }); + + test( + 'Should validate if the input is even and a number greater than 4.6 and less than or equal to 9.0', + () { + final Validator v1 = and( + >[isEven(), gt(4.6), ltE(9.0)], + printErrorAsSoonAsPossible: false); + final Validator v2 = + and(>[ltE(9.0), gt(4.6), isEven()]); + + expect(v1(3), + '$errorIsEven${FormBuilderLocalizations.current.andSeparator}$errorGt'); + expect(v1(4), errorGt); + expect(v1(5), errorIsEven); + expect(v1(6.0), errorIsEven); + expect(v1(6), isNull); + expect(v1(10.9), + '$errorIsEven${FormBuilderLocalizations.current.andSeparator}$errorLtE'); + + expect(v2(3), errorGt); + expect(v2(4), errorGt); + expect(v2(5), errorIsEven); + expect(v2(6.0), errorIsEven); + expect(v2(6), isNull); + expect(v2(10.9), errorLtE); + }); + + test( + 'Should validate if the input is even, greater than 5 and divisible by 37 with custom prefix, suffix and separator.', + () { + const String prefix = 'prefix'; + const String suffix = 'suffix'; + const String separator = ' aNd '; + const String errorDivBy37 = 'not divisible by 37'; + final Validator v = and( + >[ + isEven(), + gt(5), + (int v) => v % 37 == 0 ? null : errorDivBy37 + ], + printErrorAsSoonAsPossible: false, + prefix: prefix, + suffix: suffix, + separator: separator, + ); + + expect( + v(1), + equals('$prefix${[ + errorIsEven, + errorGt, + errorDivBy37 + ].join(separator)}$suffix')); + expect( + v(2), + equals('$prefix${[ + errorGt, + errorDivBy37 + ].join(separator)}$suffix')); + expect( + v(7), + equals('$prefix${[ + errorIsEven, + errorDivBy37 + ].join(separator)}$suffix')); + expect(v(8), + equals('$prefix${[errorDivBy37].join(separator)}$suffix')); + expect(v(37), + equals('$prefix${[errorIsEven].join(separator)}$suffix')); + expect(v(74), isNull); + }); + + test('Should throw AssertionError when the validators input is empty', () { + expect(() => and(>[]), throwsArgumentError); + }); + + test('should be immutable even when input validators change', () { + final List validators = + [gt(12), ltE(15)]; + + final Validator v = and(validators); + expect(v(12), errorGt); + expect(v(13), isNull); + expect(v(15), isNull); + expect(v(16.5), errorLtE); + + validators.add(gt(13)); + expect(v(12), errorGt); + expect(v(13), isNull); + expect(v(15), isNull); + expect(v(16.5), errorLtE); + + validators.removeLast(); + validators.removeLast(); + expect(v(12), errorGt); + expect(v(13), isNull); + expect(v(15), isNull); + expect(v(16.5), errorLtE); + }); + }); +} diff --git a/test/src/validators/core_validators/composition_validators/or_validator_test.dart b/test/src/validators/core_validators/composition_validators/or_validator_test.dart new file mode 100644 index 00000000..bc576223 --- /dev/null +++ b/test/src/validators/core_validators/composition_validators/or_validator_test.dart @@ -0,0 +1,129 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +const String errorGt = 'error gt'; +Validator gt(num target) { + return (num v) { + return v > target ? null : errorGt; + }; +} + +const String errorLtE = 'error lte'; +Validator ltE(num target) { + return (num v) { + return v <= target ? null : errorLtE; + }; +} + +const String errorIsEven = 'error is even'; +Validator isEven() { + return (num v) { + if (v is int && v % 2 == 0) { + return null; + } + return errorIsEven; + }; +} + +void main() { + group('Composition: or', () { + test('Should validate using only one validator', () { + final Validator v = or(>[gt(10)]); + + expect(v(9), errorGt); + expect(v(10), errorGt); + expect(v(11), isNull); + }); + + test('Should validate using two validators', () { + final Validator v1 = or(>[gt(100), ltE(19)]); + final Validator v2 = or(>[ltE(19), gt(100)]); + + expect(v1(9), isNull); + expect(v1(23), + '$errorGt${FormBuilderLocalizations.current.orSeparator}$errorLtE'); + expect(v1(1002), isNull); + + expect(v2(19), isNull); + expect(v2(100), + '$errorLtE${FormBuilderLocalizations.current.orSeparator}$errorGt'); + expect(v2(101), isNull); + }); + + test( + 'Should validate if the input is even or a number greater than 10.4 or less than or equal to 9.0', + () { + final Validator v1 = + or(>[isEven(), gt(10.4), ltE(9.0)]); + final Validator v2 = + or(>[ltE(9.0), gt(10.4), isEven()]); + + expect(v1(3), isNull); + expect(v1(10), isNull); + expect(v1(10.1), + '$errorIsEven${FormBuilderLocalizations.current.orSeparator}$errorGt${FormBuilderLocalizations.current.orSeparator}$errorLtE'); + expect(v1(13), isNull); + + expect(v2(3), isNull); + expect(v2(10), isNull); + expect(v2(10.1), + '$errorLtE${FormBuilderLocalizations.current.orSeparator}$errorGt${FormBuilderLocalizations.current.orSeparator}$errorIsEven'); + expect(v2(13), isNull); + }); + + test( + 'Should validate if the input is even or greater than 5 or divisible by 37 with custom prefix, suffix and separator.', + () { + const String prefix = 'prefix'; + const String suffix = 'suffix'; + const String separator = ' oR '; + const String errorDivBy37 = 'not divisible by 37'; + final Validator v = or( + >[ + isEven(), + gt(5), + (int v) => v % 37 == 0 ? null : errorDivBy37 + ], + prefix: prefix, + suffix: suffix, + separator: separator, + ); + + expect( + v(1), + equals('$prefix${[ + errorIsEven, + errorGt, + errorDivBy37 + ].join(separator)}$suffix')); + expect(v(2), isNull); + expect(v(7), isNull); + expect(v(74), isNull); + }); + test('Should throw AssertionError when the validators input is empty', () { + expect(() => or(>[]), throwsArgumentError); + }); + + test('should be immutable even when input validators change', () { + final List validators = + [ltE(10), gt(17)]; + + final Validator v = or(validators); + expect(v(12), stringContainsInOrder([errorLtE, errorGt])); + expect(v(10), isNull); + expect(v(19.0), isNull); + + validators.add(gt(11)); + expect(v(12), stringContainsInOrder([errorLtE, errorGt])); + expect(v(10), isNull); + expect(v(19.0), isNull); + + validators.removeLast(); + validators.removeLast(); + expect(v(12), stringContainsInOrder([errorLtE, errorGt])); + expect(v(10), isNull); + expect(v(19.0), isNull); + }); + }); +} diff --git a/test/src/validators/core_validators/conditional_validators/skip_if_validator_test.dart b/test/src/validators/core_validators/conditional_validators/skip_if_validator_test.dart new file mode 100644 index 00000000..f75d881c --- /dev/null +++ b/test/src/validators/core_validators/conditional_validators/skip_if_validator_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +const String errorGt = 'error gt'; +Validator gt(num target) { + return (num v) { + return v > target ? null : errorGt; + }; +} + +void main() { + group('Validator: skipIf', () { + test('Should check if a number is greater than 10 if it is an even int', + () { + // Only even integers will be validated: ... 0, 2, 4 ... + final Validator v = + skipIf((num v) => v is! int || v % 2 != 0, gt(10)); + + expect(v(2.3), isNull); + + expect(v(8), errorGt); + expect(v(10), errorGt); + expect(v(12), isNull); // This is null because it is valid + expect(v(13), isNull); // This is null because it was not validated. + }); + }); +} diff --git a/test/src/validators/core_validators/conditional_validators/validate_if_validator_test.dart b/test/src/validators/core_validators/conditional_validators/validate_if_validator_test.dart new file mode 100644 index 00000000..70a09355 --- /dev/null +++ b/test/src/validators/core_validators/conditional_validators/validate_if_validator_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +const String errorGt = 'error gt'; +Validator gt(num target) { + return (num v) { + return v > target ? null : errorGt; + }; +} + +void main() { + group('Validator: validateIf', () { + test('Should check if a number is greater than 10 if it is an even int', + () { + // Only even integers will be validated: ... 0, 2, 4 ... + final Validator v = + validateIf((num v) => v is int && v % 2 == 0, gt(10)); + + expect(v(2.3), isNull); + + expect(v(8), errorGt); + expect(v(10), errorGt); + expect(v(12), isNull); // This is null because it is valid + expect(v(13), isNull); // This is null because it was not validated. + }); + }); +} diff --git a/test/src/validators/core_validators/debug_print_validator_test.dart b/test/src/validators/core_validators/debug_print_validator_test.dart new file mode 100644 index 00000000..a3b8004d --- /dev/null +++ b/test/src/validators/core_validators/debug_print_validator_test.dart @@ -0,0 +1,66 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +class _CustomClass { + @override + String toString() => 'this is a string'; +} + +void main() { + group('Validator: debugPrintValidator', () { + final List<({String objectDescription, Object? value})> testCases = + <({String objectDescription, Object? value})>[ + (objectDescription: 'empty string', value: ''), + (objectDescription: 'null', value: null), + (objectDescription: 'custom class', value: _CustomClass()), + (objectDescription: 'string \'input 1\'', value: 'input 1'), + ]; + for (final (objectDescription: String dsc, value: Object? value) + in testCases) { + test('Should print the String representation for: $dsc', () { + final Validator v = debugPrintValidator(); + expect(() => v(value), prints(equals('$value\n'))); + expect(v(value), isNull); + }); + } + + test( + 'Should print the String representation of the input with next validator', + () { + const String errorMsg = 'error msg'; + final Validator v = debugPrintValidator( + next: (Object? v) => v is int && v % 2 == 0 ? null : errorMsg); + + expect(() => v(13), prints(equals('13\n')), + reason: 'check stdout when input is \'13\''); + expect(v(13), errorMsg, + reason: 'check return value when input is \'13\''); + + expect(() => v('not int'), prints(equals('not int\n')), + reason: 'check stdout when input is \'not int\''); + expect(v('not int'), errorMsg, + reason: 'check return value when input is \'not int\''); + + expect(() => v(10), prints(equals('10\n')), + reason: 'check stdout when input is \'10\''); + expect(v(10), isNull, reason: 'check return value when input is \'10\''); + }); + test('Should print the custom String representation of the input', () { + String logOnInput(Object? v) { + return 'Hello world $v'; + } + + final Validator v = debugPrintValidator(logOnInput: logOnInput); + + expect(() => v(13), prints(equals('${logOnInput(13)}\n')), + reason: 'check stdout when input is \'13\''); + expect(v(13), isNull, reason: 'check return value when input is \'13\''); + + final _CustomClass c = _CustomClass(); + expect(() => v(c), prints(equals('${logOnInput(c)}\n')), + reason: 'check stdout when input is a custom object'); + expect(v(c), isNull, + reason: 'check return value when input is a custom object'); + }); + }); +} diff --git a/test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart b/test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart new file mode 100644 index 00000000..b2fce6ec --- /dev/null +++ b/test/src/validators/core_validators/equality_validators/is_equal_validator_test.dart @@ -0,0 +1,112 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +class _CustomClass {} + +void main() { + final _CustomClass myObject = _CustomClass(); + + group('Validator: isEqual', () { + final List< + ({ + String description, + bool testFails, + Object? referenceValue, + Object? userInput + })> testCases = <({ + String description, + Object? referenceValue, + Object? userInput, + bool testFails + })>[ + // Edge cases + ( + description: 'Should match the null', + referenceValue: null, + userInput: null, + testFails: false + ), + ( + description: 'Should not match the null', + referenceValue: null, + userInput: 123, + testFails: true + ), + ( + description: 'Should match the empty string', + referenceValue: '', + userInput: '', + testFails: false + ), + ( + description: 'Should not match the empty string', + referenceValue: '', + userInput: ' ', + testFails: true + ), + // Domain cases + ( + description: 'Should match the integer 123', + referenceValue: 123, + userInput: 123, + testFails: false + ), + ( + description: 'Should match the string "Hello, World!"', + referenceValue: 'Hello, World!', + userInput: 'Hello, World!', + testFails: false + ), + ( + description: 'Should not match the string "Hello, World!"', + referenceValue: 'Hello, World!', + userInput: 'Hello, World!\n', + testFails: true + ), + ( + description: 'Should match a custom class object', + referenceValue: myObject, + userInput: myObject, + testFails: false + ), + ( + description: 'Should not match a custom class object', + referenceValue: myObject, + userInput: _CustomClass(), + testFails: true + ), + ]; + + for (final ( + description: String desc, + referenceValue: Object? referenceValue, + userInput: Object? userInput, + testFails: bool testFails + ) in testCases) { + test(desc, () { + final Validator v = isEqual(referenceValue); + + expect( + v(userInput), + testFails + ? equals(FormBuilderLocalizations.current + .equalErrorText(referenceValue.toString())) + : isNull); + }); + } + + test('Should return custom error message', () { + const String ref = 'hello'; + const String customErrorMessage = 'custom error'; + final Validator v = + isEqual(ref, isEqualMsg: (_, __) => customErrorMessage); + + // success + expect(v(ref), isNull); + + // failure + expect(v(123), customErrorMessage); + }); + }); +} diff --git a/test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart b/test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart new file mode 100644 index 00000000..2f844bd2 --- /dev/null +++ b/test/src/validators/core_validators/equality_validators/is_not_equal_validator_test.dart @@ -0,0 +1,115 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +class _CustomClass {} + +void main() { + final _CustomClass myObject = _CustomClass(); + + group('Validator: isNoEqual', () { + final List< + ({ + String description, + bool testFails, + Object? referenceValue, + Object? userInput + })> testCases = <({ + String description, + Object? referenceValue, + Object? userInput, + bool testFails + })>[ + // Edge cases + ( + description: 'Should pass when the input is not null', + referenceValue: null, + userInput: 123, + testFails: false + ), + ( + description: 'Should fail when the value is null', + referenceValue: null, + userInput: null, + testFails: true + ), + ( + description: 'Should pass when the input is not the empty string', + referenceValue: '', + userInput: '\t', + testFails: false + ), + ( + description: 'Should fail when the value is the empty string', + referenceValue: '', + userInput: '', + testFails: true + ), + // Domain cases + ( + description: 'Should pass when the input is not the integer 123', + referenceValue: 123, + userInput: 122, + testFails: false + ), + ( + description: + 'Should pass when the input is not the string "Hello, World!"', + referenceValue: 'Hello, World!', + userInput: 'Hello, World', + testFails: false + ), + ( + description: 'Should fail when the input is "Hello, World!"', + referenceValue: 'Hello, World!', + userInput: 'Hello, World!', + testFails: true + ), + ( + description: + 'Should pass when the input is not the same as a custom object', + referenceValue: myObject, + userInput: _CustomClass(), + testFails: false + ), + ( + description: + 'Should fail when the input is the same as a custom class object', + referenceValue: myObject, + userInput: myObject, + testFails: true + ), + ]; + + for (final ( + description: String desc, + referenceValue: Object? referenceValue, + userInput: Object? userInput, + testFails: bool testFails + ) in testCases) { + test(desc, () { + final Validator v = isNotEqual(referenceValue); + + expect( + v(userInput), + testFails + ? equals(FormBuilderLocalizations.current + .notEqualErrorText(referenceValue.toString())) + : isNull); + }); + } + + test('Should return custom error message', () { + const String ref = 'hello'; + const String customErrorMessage = 'custom error'; + final Validator v = + isNotEqual(ref, isNotEqualMsg: (_, __) => customErrorMessage); + + // success + expect(v(123), isNull); + + // failure + expect(v(ref), customErrorMessage); + }); + }); +} diff --git a/test/src/validators/core_validators/required_validators/is_optional_validator_test.dart b/test/src/validators/core_validators/required_validators/is_optional_validator_test.dart new file mode 100644 index 00000000..096f7e71 --- /dev/null +++ b/test/src/validators/core_validators/required_validators/is_optional_validator_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +const String errorMultBy6 = 'error'; +String? isMultipleBy6(int value) { + return value % 6 == 0 ? null : errorMultBy6; +} + +void main() { + final String defaultError = + FormBuilderLocalizations.current.isOptionalErrorText(errorMultBy6); + + group('Validator: isOptional', () { + test('Should make the input optional', () { + final Validator v = isOptional(); + + expect(v(null), isNull); + expect(v(''), isNull); + expect(v([]), isNull); + expect(v({}), isNull); + expect(v(123), isNull); + expect(v(' '), isNull); + expect(v('hello'), isNull); + }); + + test('Should make the input optional with composed validator `v`', () { + final Validator v = isOptional(isMultipleBy6); + + expect(v(null), isNull); + expect(v(0), isNull); + expect(v(1), equals(defaultError)); + expect(v(5), equals(defaultError)); + expect(v(6), isNull); + }); + + test('Should return custom message for invalid input', () { + const String customMsg = 'custom error message '; + final Validator v = isOptional(null, (_, __) => customMsg); + final Validator v1 = + isOptional(isMultipleBy6, (_, __) => customMsg); + + expect(v(null), isNull); + expect(v(''), isNull); + expect(v1(null), isNull); + expect(v(0), isNull); + expect(v1(1), customMsg); + expect(v1(6), isNull); + }); + }); +} diff --git a/test/src/validators/core_validators/required_validators/is_required_validator_test.dart b/test/src/validators/core_validators/required_validators/is_required_validator_test.dart new file mode 100644 index 00000000..2b0727fb --- /dev/null +++ b/test/src/validators/core_validators/required_validators/is_required_validator_test.dart @@ -0,0 +1,52 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +const String errorMultBy6 = 'error'; +String? isMultipleBy6(int value) { + return value % 6 == 0 ? null : errorMultBy6; +} + +void main() { + final String defaultError = + FormBuilderLocalizations.current.requiredErrorText; + + group('Validator: isRequired', () { + test('Should check if the input value is not null/empty', () { + final Validator v = isRequired(); + + expect(v(null), defaultError); + expect(v(''), defaultError); + expect(v([]), defaultError); + expect(v({}), defaultError); + expect(v(123), isNull); + expect(v(' '), defaultError); + expect(v('hello'), isNull); + }); + + test( + 'Should check if the input value is not null/empty with composed validator `v`', + () { + final Validator v = isRequired(isMultipleBy6); + + expect(v(null), equals(defaultError)); + expect(v(0), isNull); + expect(v(1), equals(errorMultBy6)); + expect(v(5), equals(errorMultBy6)); + expect(v(6), isNull); + }); + + test('Should return custom message for null input', () { + const String customMsg = 'custom error message '; + final Validator v = isRequired(null, customMsg); + final Validator v1 = isRequired(isMultipleBy6, customMsg); + + expect(v(null), equals(customMsg)); + expect(v(''), equals(customMsg)); + expect(v1(null), equals(customMsg)); + expect(v(0), isNull); + expect(v1(1), equals(errorMultBy6)); + expect(v1(6), isNull); + }); + }); +} diff --git a/test/src/validators/core_validators/required_validators/validate_with_default_validator_test.dart b/test/src/validators/core_validators/required_validators/validate_with_default_validator_test.dart new file mode 100644 index 00000000..c3d4b8b3 --- /dev/null +++ b/test/src/validators/core_validators/required_validators/validate_with_default_validator_test.dart @@ -0,0 +1,38 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +const String errorMultBy6 = 'error'; +String? isMultipleBy6(int value) { + return value % 6 == 0 ? null : errorMultBy6; +} + +void main() { + group('Validator: validateWithDefault', () { + test('Should validate with valid default', () { + final Validator v = validateWithDefault(0, isMultipleBy6); + final Validator v1 = validateWithDefault(12, isMultipleBy6); + + expect(v(null), isNull); + expect(v1(null), isNull); + + expect(v(6), isNull); + expect(v1(18), isNull); + + expect(v(2), equals(errorMultBy6)); + expect(v1(14), equals(errorMultBy6)); + }); + test('Should validate with invalid default', () { + final Validator v = validateWithDefault(1, isMultipleBy6); + final Validator v1 = validateWithDefault(15, isMultipleBy6); + + expect(v(null), equals(errorMultBy6)); + expect(v1(null), equals(errorMultBy6)); + + expect(v(6), isNull); + expect(v1(18), isNull); + + expect(v(2), equals(errorMultBy6)); + expect(v1(14), equals(errorMultBy6)); + }); + }); +} diff --git a/test/src/validators/core_validators/transform_validator_test.dart b/test/src/validators/core_validators/transform_validator_test.dart new file mode 100644 index 00000000..805b24dd --- /dev/null +++ b/test/src/validators/core_validators/transform_validator_test.dart @@ -0,0 +1,62 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + const String defaultMsg = 'default error msg'; + String? isEvenGreaterThan56(int input) { + return input > 56 && (input % 2 == 0) ? null : defaultMsg; + } + + group('Validator: transformAndValidate', () { + test('Should check if the String input is integer, transforming it', () { + final Validator v = transformAndValidate(int.parse); + + expect(v('12'), isNull); + expect(v('not integer'), + FormBuilderLocalizations.current.transformAndValidateErrorTextV1); + }); + test('Should transform and apply a next validator', () { + final Validator v = + transformAndValidate(int.parse, next: isEvenGreaterThan56); + + expect(v('12'), defaultMsg); + expect(v('56'), defaultMsg); + expect(v('59'), defaultMsg); + expect(v('60'), isNull); + expect(v('not integer'), + FormBuilderLocalizations.current.transformAndValidateErrorTextV1); + }); + test('Should return a custom transformation error message', () { + const String customErrorMsg = 'custom error msg'; + final Validator v = transformAndValidate( + int.parse, + next: isEvenGreaterThan56, + transformAndValidateMsg: (_) => customErrorMsg, + ); + + expect(v('12'), defaultMsg); + expect(v('56'), defaultMsg); + expect(v('59'), defaultMsg); + expect(v('60'), isNull); + expect(v('not integer'), customErrorMsg); + }); + test( + 'Should return an error message with transformed result type description', + () { + const String t = 'integer'; + final Validator v = transformAndValidate( + int.parse, + next: isEvenGreaterThan56, + transformedResultTypeDescription: t, + ); + + expect(v('12'), defaultMsg); + expect(v('56'), defaultMsg); + expect(v('59'), defaultMsg); + expect(v('60'), isNull); + expect(v('not integer'), + FormBuilderLocalizations.current.transformAndValidateErrorTextV2(t)); + }); + }); +} diff --git a/test/src/validators/core_validators/type_validators_test.dart b/test/src/validators/core_validators/type_validators_test.dart new file mode 100644 index 00000000..0b73b806 --- /dev/null +++ b/test/src/validators/core_validators/type_validators_test.dart @@ -0,0 +1,337 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + const String errorMsg = 'error msg'; + String? hasLengthGreaterThan3(String input) => + input.length > 3 ? null : errorMsg; + String? isEven(int input) => input % 2 == 0 ? null : errorMsg; + String? greaterThan9(num input) => input > 9 ? null : errorMsg; + String? isT(bool input) => input ? null : errorMsg; + String? isLaterThan1995(DateTime input) => + input.year > 1995 ? null : errorMsg; + + group('Validator: isString', () { + test('Should only check if the input is a String', () { + final Validator v = isString(); + + expect( + v(123), equals(FormBuilderLocalizations.current.isStringErrorText)); + expect(v('123'), isNull); + expect(v('1'), isNull); + expect(v(''), isNull); + }); + test('Should check if the input is a String with length greater than 3', + () { + final Validator v = isString(hasLengthGreaterThan3); + + expect( + v(123), equals(FormBuilderLocalizations.current.isStringErrorText)); + expect(v('1234'), isNull); + expect(v('12'), errorMsg); + expect(v(''), errorMsg); + }); + test('Should check if the input is a String with using custom error', () { + const String customError = 'custom error'; + final Validator v = isString(null, (_) => customError); + + expect(v(123), equals(customError)); + expect(v('1234'), isNull); + expect(v('12'), isNull); + }); + }); + + group('Validator: isInt', () { + test('Should only check if the input is an int/parsable to int', () { + final Validator v = isInt(); + + expect(v('not an int'), + equals(FormBuilderLocalizations.current.integerErrorText)); + expect( + v('1-3'), equals(FormBuilderLocalizations.current.integerErrorText)); + expect(v('123.0'), + equals(FormBuilderLocalizations.current.integerErrorText)); + expect( + v(123.0), equals(FormBuilderLocalizations.current.integerErrorText)); + expect(v('123'), isNull); + expect(v('1'), isNull); + expect(v(24), isNull); + expect(v(1 + 1), isNull); + expect(v(1 ~/ 1), isNull); + expect(v(-24), isNull); + expect(v('-0'), isNull); + }); + test('Should check if the input is an even integer', () { + final Validator v = isInt(isEven); + + expect(v('not an int'), + equals(FormBuilderLocalizations.current.integerErrorText)); + expect(v('1234'), isNull); + expect(v(-4), isNull); + expect(v('1233'), equals(errorMsg)); + }); + test('Should check if the input is an int using custom error', () { + const String customError = 'custom error'; + final Validator v = isInt(null, (_) => customError); + + expect(v('not int'), equals(customError)); + expect(v('23'), isNull); + expect(v(23), isNull); + }); + }); + + group('Validator: isNum', () { + test('Should only check if the input is a num/parsable to num', () { + final Validator v = isNum(); + + expect(v('not an num'), + equals(FormBuilderLocalizations.current.numericErrorText)); + expect( + v('1-3'), equals(FormBuilderLocalizations.current.numericErrorText)); + expect( + v(true), equals(FormBuilderLocalizations.current.numericErrorText)); + expect(v('123.0'), isNull); + expect(v('123'), isNull); + expect(v('1'), isNull); + expect(v('1e3'), isNull); + expect(v(24 / 3), isNull); + expect(v(24), isNull); + expect(v(-24), isNull); + expect(v('-0'), isNull); + }); + test('Should check if the input is an numeric greater than 9', () { + final Validator v = isNum(greaterThan9); + + expect(v('not an int'), + equals(FormBuilderLocalizations.current.numericErrorText)); + expect(v('1234'), isNull); + expect(v(10), isNull); + expect(v(9), equals(errorMsg)); + expect(v(8), equals(errorMsg)); + expect(v(-4), equals(errorMsg)); + expect(v('-1234'), equals(errorMsg)); + }); + test('Should check if the input is a num using custom error', () { + const String customError = 'custom error'; + final Validator v = isNum(null, (_) => customError); + + expect(v('not num'), equals(customError)); + expect(v('23'), isNull); + expect(v(-23.34), isNull); + }); + }); + + group('Validator: isDouble', () { + test('Should only check if the input is a double/parsable to double', () { + final Validator v = isDouble(); + + expect(v('not an double'), + equals(FormBuilderLocalizations.current.numericErrorText)); + expect( + v('1-3'), equals(FormBuilderLocalizations.current.numericErrorText)); + expect( + v(true), equals(FormBuilderLocalizations.current.numericErrorText)); + expect(v('123.0'), isNull); + expect(v('123'), isNull); + expect(v('1'), isNull); + expect(v('1e3'), isNull); + expect(v(24 / 3), isNull); + expect(v(24.0), isNull); + expect(v(-24.0), isNull); + expect(v('-0'), isNull); + }); + test('Should check if the input is a double greater than 9', () { + final Validator v = isDouble(greaterThan9); + + expect(v('not an int'), + equals(FormBuilderLocalizations.current.numericErrorText)); + expect(v('1234'), isNull); + expect(v(10.0), isNull); + expect(v(9.0), equals(errorMsg)); + expect(v(8.0), equals(errorMsg)); + expect(v(-4.0), equals(errorMsg)); + expect(v('-1234'), equals(errorMsg)); + }); + test('Should check if the input is a double using custom error', () { + const String customError = 'custom error'; + final Validator v = isDouble(null, (_) => customError); + + expect(v('not double'), equals(customError)); + expect(v('23'), isNull); + expect(v(-23.34), isNull); + }); + }); + + group('Validator: isBool', () { + test('Should only check if the input is a bool/parsable to bool', () { + // defaults to case insensitive and trim + final Validator v = isBool(); + + expect(v('not a bool'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('T'), equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('isTrue'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('true.'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('true true'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v(true), isNull); + expect(v(1 > 2), isNull); + expect(v(false), isNull); + expect(v('True'), isNull); + expect(v('TrUe'), isNull); + expect(v(' true'), isNull); + expect(v('true\n'), isNull); + }); + test( + 'Should only check if the input is a bool/parsable to bool without trim and with case sensitiveness', + () { + final Validator v = isBool(null, null, true, false); + + expect(v('not a bool'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('T'), equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('isTrue'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('true.'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v(true), isNull); + expect(v(1 > 2), isNull); + expect(v(false), isNull); + expect( + v('True'), equals(FormBuilderLocalizations.current.booleanErrorText)); + expect( + v('TrUe'), equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v(' true'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v('true\n'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + }); + test('Should check if the input is true', () { + final Validator v = isBool(isT); + + expect(v('not a bool'), + equals(FormBuilderLocalizations.current.booleanErrorText)); + expect(v(true), isNull); + expect(v(1 > 2), equals(errorMsg)); + expect(v(false), equals(errorMsg)); + expect(v('False'), equals(errorMsg)); + expect(v('fAlSE \n '), equals(errorMsg)); + }); + test('Should check if the input is a bool using custom error', () { + const String customError = 'custom error'; + final Validator v = isBool(null, (_) => customError); + + expect(v('not num'), equals(customError)); + expect(v(true), isNull); + expect(v(false), isNull); + }); + }); + + group('Validator: isDateTime', () { + test('Should only check if the input is an DateTime/parsable to DateTime', + () { + final Validator v = isDateTime(); + + expect(v('not an DateTime'), + equals(FormBuilderLocalizations.current.dateTimeErrorText)); + expect(v('1/2.0/2023.0'), + equals(FormBuilderLocalizations.current.dateTimeErrorText)); + expect( + v(123.0), equals(FormBuilderLocalizations.current.dateTimeErrorText)); + + expect( + v('1992-04-20'), + isNull, + reason: 'Valid date in YYYY-MM-DD format (1992-04-20)', + ); + expect( + v('2012-02-27'), + isNull, + reason: 'Valid date in YYYY-MM-DD format (2012-02-27)', + ); + expect( + v('2012-02-27 13:27:00'), + isNull, + reason: 'Valid datetime with time in YYYY-MM-DD HH:MM:SS format', + ); + expect( + v('2012-02-27 13:27:00.123456789z'), + isNull, + reason: 'Valid datetime with fractional seconds and Z suffix', + ); + expect( + v('2012-02-27 13:27:00,123456789z'), + isNull, + reason: + 'Valid datetime with fractional seconds using comma and Z suffix', + ); + expect( + v('20120227 13:27:00'), + isNull, + reason: 'Valid compact date and time in YYYYMMDD HH:MM:SS format', + ); + expect( + v('20120227T132700'), + isNull, + reason: + 'Valid compact datetime with T separator in YYYYMMDDTHHMMSS format', + ); + expect( + v('20120227'), + isNull, + reason: 'Valid compact date in YYYYMMDD format', + ); + expect( + v('+20120227'), + isNull, + reason: 'Valid date with plus sign in +YYYYMMDD format', + ); + expect( + v('2012-02-27T14Z'), + isNull, + reason: 'Valid datetime with time and Z suffix', + ); + expect( + v('2012-02-27T14+00:00'), + isNull, + reason: 'Valid datetime with time and timezone offset +00:00', + ); + expect( + v('-123450101 00:00:00 Z'), + isNull, + reason: 'Valid historical date with negative year -12345 and Z suffix', + ); + expect( + v('2002-02-27T14:00:00-0500'), + isNull, + reason: + 'Valid datetime with timezone offset -0500, equivalent to "2002-02-27T19:00:00Z"', + ); + expect( + v(DateTime.now()), + isNull, + reason: 'Current DateTime object is valid', + ); + }); + test('Should check if the input is a DateTime with year later than 1995', + () { + final Validator v = isDateTime(isLaterThan1995); + + expect(v('not a datetime'), + equals(FormBuilderLocalizations.current.dateTimeErrorText)); + expect(v('12330803'), equals(errorMsg)); + }); + + test('Should check if the input is a DateTime using custom error', () { + const String customError = 'custom error'; + final Validator v = isDateTime(null, (_) => customError); + + expect(v('not datetime'), equals(customError)); + expect(v('1289-02-12'), isNull); + expect(v(DateTime(1990)), isNull); + }); + }); +} diff --git a/test/src/validators/datetime_validators/is_after_validator_test.dart b/test/src/validators/datetime_validators/is_after_validator_test.dart new file mode 100644 index 00000000..a833f816 --- /dev/null +++ b/test/src/validators/datetime_validators/is_after_validator_test.dart @@ -0,0 +1,117 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +void main() { + setUpAll(() async { + await initializeDateFormatting('en', null); + }); + group('Validator: isAfter', () { + test('Validation for the year 1994', () { + final DateTime reference = DateTime(1994); + final DateTime eq = reference.copyWith(); + final DateTime after10Years = DateTime(2004); + final DateTime after1Ms = reference.add(const Duration(milliseconds: 1)); + final DateTime before1Year = DateTime(1993); + final DateTime before1Sec = + reference.subtract(const Duration(seconds: 1)); + final Validator v = isAfter(reference); + final String errorMsg = + FormBuilderLocalizations.current.dateMustBeAfterErrorText(reference); + + expect( + v(eq), + errorMsg, + reason: 'Should return error against 1994', + ); + expect( + v(after10Years), + isNull, + reason: 'Should return null against 2004', + ); + expect( + v(after1Ms), + isNull, + reason: 'Should return null against 1994 + 1ms', + ); + expect( + v(before1Year), + errorMsg, + reason: 'Should return errorMsg against 1993', + ); + expect( + v(before1Sec), + errorMsg, + reason: 'Should return errorMsg against 1994 - 1s', + ); + }); + test( + 'Inclusive validation for datetime 2089, month 3, day 23, h 3, min 46, s 12, 233 ms', + () { + final DateTime reference = DateTime(2089, 3, 23, 3, 46, 12, 233); + final DateTime eq = reference.copyWith(); + final DateTime after10Years = reference.copyWith(year: 2099); + final DateTime after1Ms = reference.add(const Duration(milliseconds: 1)); + final DateTime before1Year = reference.copyWith(year: 2088); + final DateTime before1Sec = + reference.subtract(const Duration(seconds: 1)); + final Validator v = isAfter(reference, inclusive: true); + final String errorMsg = + FormBuilderLocalizations.current.dateMustBeAfterErrorText(reference); + + expect( + v(eq), + isNull, + reason: 'Should return null against the same datetime', + ); + expect( + v(after10Years), + isNull, + reason: 'Should return null against the reference shifted +10 years', + ); + expect( + v(after1Ms), + isNull, + reason: 'Should return null against the reference shifted +1 ms', + ); + expect( + v(before1Year), + errorMsg, + reason: + 'Should return errorMsg against a datetime 1 year before the reference', + ); + expect( + v(before1Sec), + errorMsg, + reason: + 'Should return errorMsg against a datetime 1 sec before the reference', + ); + }); + + test('Should return a custom message after validating', () { + const String errorMsg = 'error msg'; + final DateTime reference = DateTime(2); + final Validator v = + isAfter(reference, isAfterMsg: (_, __) => errorMsg); + + expect( + v(reference.copyWith()), + errorMsg, + reason: + 'Should return custom message when input is equal to the reference', + ); + expect( + v(reference.subtract(const Duration(microseconds: 1))), + errorMsg, + reason: + 'Should return custom message when input is before the reference', + ); + expect( + v(reference.add(const Duration(days: 1))), + isNull, + reason: 'Should return null when the input is after the reference', + ); + }); + }); +} diff --git a/test/src/validators/datetime_validators/is_before_validator_test.dart b/test/src/validators/datetime_validators/is_before_validator_test.dart new file mode 100644 index 00000000..219f3111 --- /dev/null +++ b/test/src/validators/datetime_validators/is_before_validator_test.dart @@ -0,0 +1,117 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +void main() { + setUpAll(() async { + await initializeDateFormatting('en', null); + }); + group('Validator: isBefore', () { + test('Validation for the year 1994', () { + final DateTime reference = DateTime(1994); + final DateTime eq = reference.copyWith(); + final DateTime after10Years = DateTime(2004); + final DateTime after1Ms = reference.add(const Duration(milliseconds: 1)); + final DateTime before1Year = DateTime(1993); + final DateTime before1Sec = + reference.subtract(const Duration(seconds: 1)); + final Validator v = isBefore(reference); + final String errorMsg = + FormBuilderLocalizations.current.dateMustBeBeforeErrorText(reference); + + expect( + v(eq), + errorMsg, + reason: 'Should return error against 1994', + ); + expect( + v(after10Years), + errorMsg, + reason: 'Should return error against 2004', + ); + expect( + v(after1Ms), + errorMsg, + reason: 'Should return error against 1994 + 1ms', + ); + expect( + v(before1Year), + isNull, + reason: 'Should return null against 1993', + ); + expect( + v(before1Sec), + isNull, + reason: 'Should return null against 1994 - 1s', + ); + }); + test( + 'Inclusive validation for datetime 2089, month 3, day 23, h 3, min 46, s 12, 233 ms', + () { + final DateTime reference = DateTime(2089, 3, 23, 3, 46, 12, 233); + final DateTime eq = reference.copyWith(); + final DateTime after10Years = reference.copyWith(year: 2099); + final DateTime after1Ms = reference.add(const Duration(milliseconds: 1)); + final DateTime before1Year = reference.copyWith(year: 2088); + final DateTime before1Sec = + reference.subtract(const Duration(seconds: 1)); + final Validator v = isBefore(reference, inclusive: true); + final String errorMsg = + FormBuilderLocalizations.current.dateMustBeBeforeErrorText(reference); + + expect( + v(eq), + isNull, + reason: 'Should return null against the same datetime', + ); + expect( + v(after10Years), + errorMsg, + reason: 'Should return error against the reference shifted +10 years', + ); + expect( + v(after1Ms), + errorMsg, + reason: 'Should return error against the reference shifted +1 ms', + ); + expect( + v(before1Year), + isNull, + reason: + 'Should return null against a datetime 1 year before the reference', + ); + expect( + v(before1Sec), + isNull, + reason: + 'Should return null against a datetime 1 sec before the reference', + ); + }); + + test('Should return a custom message after validating', () { + const String errorMsg = 'error msg'; + final DateTime reference = DateTime(2); + final Validator v = + isBefore(reference, isBeforeMsg: (_, __) => errorMsg); + + expect( + v(reference.copyWith()), + errorMsg, + reason: + 'Should return custom message when input is equal to the reference', + ); + expect( + v(reference.subtract(const Duration(microseconds: 1))), + isNull, + reason: 'Should return null when input is before the reference', + ); + expect( + v(reference.add(const Duration(days: 1))), + errorMsg, + reason: + 'Should return custom message when the input is after the reference', + ); + }); + }); +} diff --git a/test/src/validators/datetime_validators/is_datetime_between_validator_test.dart b/test/src/validators/datetime_validators/is_datetime_between_validator_test.dart new file mode 100644 index 00000000..d478798a --- /dev/null +++ b/test/src/validators/datetime_validators/is_datetime_between_validator_test.dart @@ -0,0 +1,181 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +void main() { + setUpAll(() async { + await initializeDateFormatting('en', null); + }); + group('Validator: isDateTimeBetween', () { + test('Validation for the range 1994 and 1997', () { + final DateTime leftReference = DateTime(1994); + final DateTime rightReference = DateTime(1997); + final DateTime leftEq = leftReference.copyWith(); + final DateTime rightEq = rightReference.copyWith(); + final DateTime afterRight = DateTime(1998); + final DateTime afterRight1Second = + rightReference.add(const Duration(seconds: 1)); + final DateTime beforeRight1Second = + rightReference.subtract(const Duration(seconds: 1)); + final DateTime between = DateTime(1995); + final DateTime beforeLeft = DateTime(199); + final DateTime afterLeft1Micro = + leftReference.add(const Duration(microseconds: 1)); + final DateTime beforeLeft1Micro = + leftReference.subtract(const Duration(microseconds: 1)); + final Validator v = + isDateTimeBetween(leftReference, rightReference); + final String errorMsg = FormBuilderLocalizations.current + .dateMustBeBetweenErrorText(leftReference, rightReference); + + expect( + v(leftEq), + errorMsg, + reason: 'Should return error against 1994', + ); + expect( + v(rightEq), + errorMsg, + reason: 'Should return error against 1997', + ); + expect( + v(afterRight), + errorMsg, + reason: 'Should return error against 1998', + ); + expect( + v(beforeLeft), + errorMsg, + reason: 'Should return error against 199', + ); + expect( + v(afterRight1Second), + errorMsg, + reason: 'Should return error against 1997 + 1 s', + ); + expect( + v(beforeRight1Second), + isNull, + reason: 'Should return null against 1997 - 1 s', + ); + expect( + v(between), + isNull, + reason: 'Should return null against 1995', + ); + expect( + v(afterLeft1Micro), + isNull, + reason: 'Should return null against 1994 + 1 us', + ); + expect( + v(beforeLeft1Micro), + errorMsg, + reason: 'Should return error against 1994 - 1 us', + ); + }); + test( + 'Left inclusive validation for left reference 2089, month 3, day 23, h 3, min 46, s 12, 233 ms and right reference 2089, month 3, day 23, h 7', + () { + final DateTime leftReference = DateTime(2089, 3, 23, 3, 46, 12, 233); + final DateTime rightReference = DateTime(2089, 3, 23, 7); + final DateTime between = + leftReference.add(Duration(hours: 2, seconds: 10)); + final DateTime leftEq = leftReference.copyWith(); + final DateTime rightEq = rightReference.copyWith(); + final DateTime afterRight = rightReference.copyWith(year: 2099); + final DateTime after1Ms = + rightReference.add(const Duration(milliseconds: 1)); + final DateTime before1Year = leftReference.copyWith(year: 2088); + final DateTime before1Sec = + leftReference.subtract(const Duration(seconds: 1)); + final Validator v = + isDateTimeBetween(leftReference, rightReference, minInclusive: true); + final String errorMsg = FormBuilderLocalizations.current + .dateMustBeBetweenErrorText(leftReference, rightReference); + + expect( + v(leftEq), + isNull, + reason: 'Should return null against the same left datetime', + ); + expect( + v(between), + isNull, + reason: 'Should return null against a datetime between the references', + ); + expect( + v(rightEq), + errorMsg, + reason: 'Should return errorMsg against the same right datetime', + ); + expect( + v(afterRight), + errorMsg, + reason: + 'Should return error against the right reference shifted +10 years', + ); + expect( + v(after1Ms), + errorMsg, + reason: + 'Should return error against the right reference shifted +1 millisecond', + ); + expect( + v(after1Ms), + errorMsg, + reason: 'Should return error against the reference shifted +1 ms', + ); + expect( + v(before1Year), + errorMsg, + reason: + 'Should return error against a datetime 1 year before the left reference', + ); + expect( + v(before1Sec), + errorMsg, + reason: + 'Should return error against a datetime 1 sec before the left reference', + ); + }); + + test('Should return a custom message after validating', () { + const String errorMsg = 'error msg'; + final DateTime leftReference = DateTime(2); + final DateTime rightReference = DateTime(5); + final Validator v = isDateTimeBetween( + leftReference, rightReference, + isDateTimeBetweenMsg: (_, __, ___) => errorMsg); + + expect( + v(rightReference.copyWith()), + errorMsg, + reason: + 'Should return custom message when input is equal to the reference', + ); + expect( + v(rightReference.subtract(const Duration(microseconds: 1))), + isNull, + reason: + 'Should return null when input is before the right reference for 1 s', + ); + expect( + v(rightReference.add(const Duration(days: 1))), + errorMsg, + reason: + 'Should return custom message when the input is after the right reference', + ); + }); + + test( + 'Should throw AssertionError when the right reference is not after left reference', + () { + expect( + () => isDateTimeBetween( + DateTime(1990, 12, 23, 20), DateTime(1990, 12, 22, 20)), + throwsAssertionError); + }); + }); +} diff --git a/test/src/validators/finance_validators/credit_card_validator_test.dart b/test/src/validators/finance_validators/credit_card_validator_test.dart new file mode 100644 index 00000000..2ed9a521 --- /dev/null +++ b/test/src/validators/finance_validators/credit_card_validator_test.dart @@ -0,0 +1,154 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('Validator: creditCard', () { + test('should return null if the credit card number is valid (Visa)', () { + // Arrange + final Validator validator = creditCard(); + const String validVisaCard = '4111111111111111'; + + // Act + final String? result = validator(validVisaCard); + + // Assert + expect(result, isNull); + }); + + test('should return null if the credit card number is valid (MasterCard)', + () { + // Arrange + final Validator validator = creditCard(); + const String validMasterCard = '5500000000000004'; + + // Act + final String? result = validator(validMasterCard); + + // Assert + expect(result, isNull); + }); + + test( + 'should return null if the credit card number is valid (American Express)', + () { + // Arrange + final Validator validator = creditCard(); + const String validAmexCard = '340000000000009'; + + // Act + final String? result = validator(validAmexCard); + + // Assert + expect(result, isNull); + }); + + test('should return null if the credit card number is valid (Discover)', + () { + // Arrange + final Validator validator = creditCard(); + const String validDiscoverCard = '6011111111111117'; + + // Act + final String? result = validator(validDiscoverCard); + + // Assert + expect(result, isNull); + }); + + test('should return null if the credit card number is valid (JCB)', () { + // Arrange + final Validator validator = creditCard(); + const String validJcbCard = '3530111333300000'; + + // Act + final String? result = validator(validJcbCard); + + // Assert + expect(result, isNull); + }); + + test('should return null if the credit card number is valid (Diners Club)', + () { + // Arrange + final Validator validator = creditCard(); + const String validDinersClubCard = '30569309025904'; + + // Act + final String? result = validator(validDinersClubCard); + + // Assert + expect(result, isNull); + }); + + test( + 'should return the default error message if the credit card number is invalid', + () { + // Arrange + final Validator validator = creditCard(); + const String invalidCard = '1234567890123456'; + + // Act + final String? result = validator(invalidCard); + + // Assert + expect( + result, + equals(FormBuilderLocalizations.current.creditCardErrorText), + ); + }); + + test( + 'should return the custom error message if the credit card number is invalid', + () { + // Arrange + final Validator validator = + creditCard(creditCardMsg: (_) => customErrorMessage); + const String invalidCard = '1234567890123456'; + + // Act + final String? result = validator(invalidCard); + + // Assert + expect(result, equals(customErrorMessage)); + }); + + test( + 'should return the default error message if the value is an empty string', + () { + // Arrange + final Validator validator = creditCard(); + const String value = ''; + + // Act + final String? result = validator(value); + + // Assert + expect( + result, + equals(FormBuilderLocalizations.current.creditCardErrorText), + ); + }); + + test( + 'should return the default error message if the credit card number fails the Luhn check', + () { + // Arrange + final Validator validator = creditCard(); + const String invalidLuhnCard = '4111111111111112'; // Fails Luhn check + + // Act + final String? result = validator(invalidLuhnCard); + + // Assert + expect( + result, + equals(FormBuilderLocalizations.current.creditCardErrorText), + ); + }); + }); +} diff --git a/test/src/validators/generic_type_validators/contains_element_validator_test.dart b/test/src/validators/generic_type_validators/contains_element_validator_test.dart new file mode 100644 index 00000000..29e92351 --- /dev/null +++ b/test/src/validators/generic_type_validators/contains_element_validator_test.dart @@ -0,0 +1,83 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: containsElement', () { + group('Validations with default error message', () { + test('Should return null when int 0 is provided', () { + final Validator validator = containsElement([0]); + + expect(validator(0), isNull); + expect(validator(2), + equals(FormBuilderLocalizations.current.containsElementErrorText)); + }); + test('Should return null when int 0 or String "2" is provided', () { + final Validator validator = containsElement([0, '2']); + + expect(validator(0), isNull); + expect(validator('2'), isNull); + expect(validator(2), + equals(FormBuilderLocalizations.current.containsElementErrorText)); + }); + test('Should return null when int 0, int 2, or null is provided', () { + final Validator validator = + containsElement([0, 2, null]); + + expect(validator(0), isNull); + expect(validator(2), isNull); + expect(validator(null), isNull); + expect(validator('2'), + equals(FormBuilderLocalizations.current.containsElementErrorText)); + }); + }); + + test('Should throw ArgumentError when list input is empty', () { + expect(() => containsElement([]), throwsArgumentError); + }); + + test('Should return custom error message when invalid input is provided', + () { + const String customMessage = 'custom message'; + final Validator validator = containsElement([1, 2, 3], + containsElementMsg: (_, __) => customMessage); + + expect(validator(4), equals(customMessage)); + }); + + test('should remain immutable when input elements change', () { + final List elements = [12, 15, 'hi']; + + final Validator v = containsElement(elements); + + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect( + v(12.02), FormBuilderLocalizations.current.containsElementErrorText); + expect(v(2), FormBuilderLocalizations.current.containsElementErrorText); + expect( + v(true), FormBuilderLocalizations.current.containsElementErrorText); + + elements.removeLast(); + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect( + v(12.02), FormBuilderLocalizations.current.containsElementErrorText); + expect(v(2), FormBuilderLocalizations.current.containsElementErrorText); + expect( + v(true), FormBuilderLocalizations.current.containsElementErrorText); + + elements.add(true); + expect(v(12), isNull); + expect(v(15), isNull); + expect(v('hi'), isNull); + expect( + v(12.02), FormBuilderLocalizations.current.containsElementErrorText); + expect(v(2), FormBuilderLocalizations.current.containsElementErrorText); + expect( + v(true), FormBuilderLocalizations.current.containsElementErrorText); + }); + }); +} diff --git a/test/src/validators/generic_type_validators/is_false_validator_test.dart b/test/src/validators/generic_type_validators/is_false_validator_test.dart new file mode 100644 index 00000000..40cdb3b4 --- /dev/null +++ b/test/src/validators/generic_type_validators/is_false_validator_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart' as v; + +void main() { + const String customErrorMessage = 'custom error message'; + group('Validator: isFalse', () { + group('Validations with default error message', () { + final List<({Object input, bool isValid})> testCases = + <({Object input, bool isValid})>[ + (input: '', isValid: false), + (input: '1', isValid: false), + (input: 'notFalse', isValid: false), + (input: 'F', isValid: false), + (input: 'fals e', isValid: false), + (input: 'false', isValid: true), + (input: ' faLSe ', isValid: true), + (input: 'false\n', isValid: true), + (input: '\tfalse', isValid: true), + (input: 'fAlSe', isValid: true), + (input: false, isValid: true), + (input: 'true', isValid: false), + (input: 'tRUe', isValid: false), + (input: true, isValid: false), + ]; + + for (final (input: Object input, isValid: bool isValid) in testCases) { + final String? expectedReturnValue = isValid + ? null + : FormBuilderLocalizations.current.mustBeFalseErrorText; + test( + 'Should return ${expectedReturnValue == null ? null : 'default error message'} with input "$input"', + () { + expect( + v.isFalse()(input), + equals(expectedReturnValue), + ); + }); + } + }); + + test( + 'Should return default error message when input is invalid with caseSensitive = true', + () { + expect(v.isFalse(caseSensitive: true)('False'), + FormBuilderLocalizations.current.mustBeFalseErrorText); + }); + test( + 'Should return default error message when input is invalid with trim = false', + () { + expect(v.isFalse(trim: false)('false '), + FormBuilderLocalizations.current.mustBeFalseErrorText); + expect(v.isFalse(trim: false)(' false'), + FormBuilderLocalizations.current.mustBeFalseErrorText); + expect(v.isFalse(trim: false)(' false '), + FormBuilderLocalizations.current.mustBeFalseErrorText); + }); + + test('should return the custom error message when the value is not false', + () { + // Arrange + final v.Validator validator = + v.isFalse(isFalseMsg: (_) => customErrorMessage); + const String value = 'not valid false'; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(customErrorMessage)); + }); + }); +} diff --git a/test/src/validators/generic_type_validators/is_true_validator_test.dart b/test/src/validators/generic_type_validators/is_true_validator_test.dart new file mode 100644 index 00000000..789b46c5 --- /dev/null +++ b/test/src/validators/generic_type_validators/is_true_validator_test.dart @@ -0,0 +1,73 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart' as v; + +void main() { + const String customErrorMessage = 'custom error message'; + group('Validator: isTrue', () { + group('Validations with default error message', () { + final List<({Object input, bool isValid})> testCases = + <({Object input, bool isValid})>[ + (input: '', isValid: false), + (input: '1', isValid: false), + (input: 'notTrue', isValid: false), + (input: 'T', isValid: false), + (input: 'tru e', isValid: false), + (input: 'true', isValid: true), + (input: ' tRUe ', isValid: true), + (input: 'true\n', isValid: true), + (input: '\ttrue', isValid: true), + (input: 'tRuE', isValid: true), + (input: true, isValid: true), + (input: 'false', isValid: false), + (input: 'fAlSe', isValid: false), + (input: false, isValid: false), + ]; + + for (final (input: Object input, isValid: bool isValid) in testCases) { + final String? expectedReturnValue = isValid + ? null + : FormBuilderLocalizations.current.mustBeTrueErrorText; + test( + 'Should return ${expectedReturnValue == null ? null : 'default error message'} with input "$input"', + () { + expect( + v.isTrue()(input), + equals(expectedReturnValue), + ); + }); + } + }); + + test( + 'Should return default error message when input is invalid with caseSensitive = true', + () { + expect(v.isTrue(caseSensitive: true)('tRue'), + FormBuilderLocalizations.current.mustBeTrueErrorText); + }); + test( + 'Should return default error message when input is invalid with trim = false', + () { + expect(v.isTrue(trim: false)('true '), + FormBuilderLocalizations.current.mustBeTrueErrorText); + expect(v.isTrue(trim: false)(' true'), + FormBuilderLocalizations.current.mustBeTrueErrorText); + expect(v.isTrue(trim: false)(' true '), + FormBuilderLocalizations.current.mustBeTrueErrorText); + }); + + test('should return the custom error message when the value is not true', + () { + // Arrange + final v.Validator validator = + v.isTrue(isTrueMsg: (_) => customErrorMessage); + const String value = 'not valid true'; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(customErrorMessage)); + }); + }); +} diff --git a/test/src/validators/network_validators/ip_validator_test.dart b/test/src/validators/network_validators/ip_validator_test.dart new file mode 100644 index 00000000..1830b8d8 --- /dev/null +++ b/test/src/validators/network_validators/ip_validator_test.dart @@ -0,0 +1,239 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/constants.dart'; +import 'package:form_builder_validators/src/validators/network_validators.dart'; + +void main() { + final Faker faker = Faker.instance; + group('Validator: ip', () { + group('Validate ipV4', () { + final List<({String input, bool isValid})> testCases = + <({String input, bool isValid})>[ + // Invalid cases + (input: '', isValid: false), + (input: ' ', isValid: false), + (input: '123', isValid: false), + (input: 'invalid', isValid: false), + (input: '256.256.256.256', isValid: false), + (input: '192.168.1.256', isValid: false), + (input: '192.168.-1.256', isValid: false), // negative number + (input: '192.168.1', isValid: false), + (input: '192.168.1.1.1', isValid: false), + (input: '192.168.1.a', isValid: false), + (input: '::1', isValid: false), + (input: '2001:0db8:85a3:0000:0000:8a2e:0370:7334', isValid: false), + (input: '2001:db8:85a3:0:0:8a2e:370:7334', isValid: false), + (input: '2001:db8:85a3::8a2e:370:7334', isValid: false), + (input: '192.168..1', isValid: false), // Double dot + (input: '.192.168.1.1', isValid: false), // Leading dot + (input: '192.168.1.1.', isValid: false), // Trailing dot + (input: ' 192.168.1.1', isValid: false), // Leading space + (input: '192.168.1.1 ', isValid: false), // Trailing space + (input: '192.168.1.1\n', isValid: false), // Newline + (input: '192.168.1.1\t', isValid: false), // Tab + (input: '192,168,1,1', isValid: false), // Wrong separator + + // Valid cases + (input: faker.internet.ip(), isValid: true), + (input: '0.0.0.0', isValid: true), + (input: '192.168.1.1', isValid: true), + (input: '10.0.0.1', isValid: true), + (input: '172.16.0.1', isValid: true), + (input: '255.255.255.255', isValid: true), + (input: '127.0.0.1', isValid: true), // Localhost + // Be aware of leading zeros: 09 may not be equal to 9 depending on the tool: + // https://superuser.com/questions/857603/are-ip-addresses-with-and-without-leading-zeroes-the-same + (input: '192.168.09.1', isValid: true), // Leading zeros + ]; + + final Validator v = ip(); + for (final (input: String input, isValid: bool isValid) in testCases) { + test( + 'should return ${isValid ? 'null' : 'error message for ipV4'} for input "$input"', + () => expect( + v(input), + isValid + ? isNull + : equals(FormBuilderLocalizations.current.ipErrorText))); + } + }); + group('Validate ipV6', () { + final List<({String input, bool isValid})> testCases = + <({String input, bool isValid})>[ + // Invalid cases + (input: '', isValid: false), // Empty string + (input: ' ', isValid: false), // Whitespace only + (input: '123', isValid: false), // Incomplete address + (input: 'invalid', isValid: false), // Non-IP string + (input: '256.256.256.256', isValid: false), // Invalid ipV4 + (input: '192.168.1.256', isValid: false), // Invalid ipV4 + ( + input: faker.internet.ip(), + isValid: false + ), // Valid ipV4 (but invalid ipV6) + (input: '0.0.0.0', isValid: false), // Valid ipV4 (but invalid ipV6) + (input: '127.0.0.1', isValid: false), // Valid ipV4 (but invalid ipV6) + (input: '12345::', isValid: false), // Invalid short IPv6 + ( + input: 'GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG', + isValid: false + ), // Invalid hex + ( + input: '2001:0db8:85a3::8a2e:037j:7334', + isValid: false + ), // Invalid hex 'j' + ( + input: '2001:0db8:85a3::8a2e:037z:7334', + isValid: false + ), // Invalid hex 'z' + (input: '::12345', isValid: false), // Invalid compressed IPv6 + (input: '1::1::1', isValid: false), // Multiple compression markers + (input: '2001:db8::1::1', isValid: false), // Multiple compression + (input: 'fe80:2030:31:24', isValid: false), // Incomplete IPv6 + ( + input: '1200:0000:AB00:1234:O000:2552:7777:1313', + isValid: false + ), // 'O' vs '0' + + // Valid cases + (input: '::1', isValid: true), // IPv6 loopback + ( + input: '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + isValid: true + ), // Full IPv6 + ( + input: '2001:db8:85a3:0:0:8a2e:370:7334', + isValid: true + ), // IPv6 with zeros + ( + input: '2001:db8:85a3::8a2e:370:7334', + isValid: true + ), // Compressed IPv6 + (input: 'fe80::1', isValid: true), // Link-local IPv6 + (input: '::ffff:192.0.2.128', isValid: true), // IPv4-mapped IPv6 + (input: '2001:db8::', isValid: true), // Network address + ( + input: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + isValid: true + ), // Max value + ]; + + final Validator v = ip(version: IpVersion.iPv6); + for (final (input: String input, isValid: bool isValid) in testCases) { + test( + 'should return ${isValid ? 'null' : 'error message for ipV6'} for input "$input"', + () => expect( + v(input), + isValid + ? isNull + : equals(FormBuilderLocalizations.current.ipErrorText))); + } + }); + group('Validate ipV4 or ipV6', () { + final List<({String input, bool isValid})> testCases = + <({String input, bool isValid})>[ + // Invalid cases + (input: '', isValid: false), + (input: ' ', isValid: false), + (input: '123', isValid: false), + (input: 'invalid', isValid: false), + (input: '256.256.256.256', isValid: false), + (input: '192.168.1.256', isValid: false), + (input: '192.168.-1.256', isValid: false), // negative number + (input: '192.168.1', isValid: false), + (input: '192.168.1.1.1', isValid: false), + (input: '192.168.1.a', isValid: false), + (input: '192.168..1', isValid: false), // Double dot + (input: '.192.168.1.1', isValid: false), // Leading dot + (input: '192.168.1.1.', isValid: false), // Trailing dot + (input: ' 192.168.1.1', isValid: false), // Leading space + (input: '192.168.1.1 ', isValid: false), // Trailing space + (input: '192.168.1.1\n', isValid: false), // Newline + (input: '192.168.1.1\t', isValid: false), // Tab + (input: '192,168,1,1', isValid: false), // Wrong separator + (input: '12345::', isValid: false), // Invalid short IPv6 + ( + input: 'GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG:GGGG', + isValid: false + ), // Invalid hex + ( + input: '2001:0db8:85a3::8a2e:037j:7334', + isValid: false + ), // Invalid hex 'j' + ( + input: '2001:0db8:85a3::8a2e:037z:7334', + isValid: false + ), // Invalid hex 'z' + (input: '::12345', isValid: false), // Invalid compressed IPv6 + (input: '1::1::1', isValid: false), // Multiple compression markers + (input: '2001:db8::1::1', isValid: false), // Multiple compression + (input: 'fe80:2030:31:24', isValid: false), // Incomplete IPv6 + ( + input: '1200:0000:AB00:1234:O000:2552:7777:1313', + isValid: false + ), // 'O' vs '0' + + // Valid cases + (input: faker.internet.ip(), isValid: true), + (input: '0.0.0.0', isValid: true), + (input: '192.168.1.1', isValid: true), + (input: '10.0.0.1', isValid: true), + (input: '172.16.0.1', isValid: true), + (input: '255.255.255.255', isValid: true), + (input: '127.0.0.1', isValid: true), // Localhost + // Be aware of leading zeros: 09 may not be equal to 9 depending on the tool: + // https://superuser.com/questions/857603/are-ip-addresses-with-and-without-leading-zeroes-the-same + (input: '192.168.09.1', isValid: true), // Leading zeros + (input: '::1', isValid: true), // IPv6 loopback + ( + input: '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + isValid: true + ), // Full IPv6 + ( + input: '2001:db8:85a3:0:0:8a2e:370:7334', + isValid: true + ), // IPv6 with zeros + ( + input: '2001:db8:85a3::8a2e:370:7334', + isValid: true + ), // Compressed IPv6 + (input: 'fe80::1', isValid: true), // Link-local IPv6 + (input: '::ffff:192.0.2.128', isValid: true), // IPv4-mapped IPv6 + (input: '2001:db8::', isValid: true), // Network address + ( + input: 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff', + isValid: true + ), // Max value + ]; + + final Validator v = ip(version: IpVersion.any); + for (final (input: String input, isValid: bool isValid) in testCases) { + test( + 'should return ${isValid ? 'null' : 'error message for ipV4/6'} for input "$input"', + () => expect( + v(input), + isValid + ? isNull + : equals(FormBuilderLocalizations.current.ipErrorText))); + } + }); + + test('should return custom message', () { + final String errorMessage = 'error Msg'; + final Validator validator = ip(ipMsg: (_) => errorMessage); + + expect(validator('1.2.3.4'), isNull); + expect(validator('1.2.3.256'), errorMessage); + }); + + test('should validate with custom regex', () { + final Validator validator = + ip(version: IpVersion.any, regex: RegExp('this is valid')); + + expect( + validator('1.2.3.4'), FormBuilderLocalizations.current.ipErrorText); + expect(validator('this is valid'), isNull); + }); + }); +} diff --git a/test/src/validators/network_validators/url_validator_test.dart b/test/src/validators/network_validators/url_validator_test.dart new file mode 100644 index 00000000..08d19765 --- /dev/null +++ b/test/src/validators/network_validators/url_validator_test.dart @@ -0,0 +1,374 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/constants.dart'; +import 'package:form_builder_validators/src/validators/network_validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('UrlValidator -', () { + test('should return null for valid URLs with default protocols', () { + // Arrange + final Validator validator = url(); + const List validUrls = [ + 'http://example.com', + 'https://example.com', + 'ftp://example.com', + 'http://www.example.com', + 'https://www.example.com', + 'ftp://www.example.com', + 'http://subdomain.example.com', + 'https://subdomain.example.com', + 'ftp://subdomain.example.com', + ]; + + // Act & Assert + for (final String value in validUrls) { + expect(validator(value), isNull); + } + }); + + test('should return null for valid URLs with custom protocols', () { + // Arrange + final Validator validator = + url(protocols: ['http', 'https']); + const List validUrls = [ + 'http://example.com', + 'https://example.com', + 'http://www.example.com', + 'https://www.example.com', + 'http://subdomain.example.com', + 'https://subdomain.example.com', + ]; + + // Act & Assert + for (final String value in validUrls) { + expect(validator(value), isNull); + } + }); + + test('should return the default error message for invalid URLs', () { + // Arrange + final Validator validator = url(); + const List invalidUrls = [ + 'example', + 'http:/example.com', + 'http://example', + 'http://example..com', + 'http://example.com:99999', + 'http://-example.com', + 'http://example-.com', + 'http://example.com:port', + 'ftp://example.com:port', + ]; + + // Act & Assert + for (final String value in invalidUrls) { + final String? result = validator(value); + expect(result, isNotNull); + expect(result, equals(FormBuilderLocalizations.current.urlErrorText)); + } + }); + + test('should return the custom error message for invalid URLs', () { + // Arrange + final Validator validator = + url(urlMsg: (_) => customErrorMessage); + const List invalidUrls = [ + 'example', + 'http:/example.com', + 'http://example', + 'http://example..com', + 'http://example.com:99999', + 'http://-example.com', + 'http://example-.com', + 'http://example.com:port', + 'ftp://example.com:port', + ]; + + // Act & Assert + for (final String value in invalidUrls) { + final String? result = validator(value); + expect(result, equals(customErrorMessage)); + } + }); + + test('should return null for valid URLs with custom regex', () { + // Arrange + final RegExp customRegex = + RegExp(r'^(http|https)://[a-zA-Z0-9\-_]+\.[a-zA-Z]{2,}$'); + final Validator validator = url(regex: customRegex); + const List validUrls = [ + 'http://example.com', + 'https://example.com', + ]; + + // Act & Assert + for (final String value in validUrls) { + expect(validator(value), isNull); + } + }); + + test( + 'should return the custom error message for invalid URLs with custom regex', + () { + // Arrange + final RegExp customRegex = + RegExp(r'^(http|https)://[a-zA-Z0-9\-_]+\.[a-zA-Z]{2,}$'); + final Validator validator = + url(regex: customRegex, urlMsg: (_) => customErrorMessage); + const List invalidUrls = [ + 'ftp://example.com', + 'http://example', + 'https://example', + 'http://example..com', + 'https://example..com', + ]; + + // Act & Assert + for (final String value in invalidUrls) { + final String? result = validator(value); + expect(result, equals(customErrorMessage)); + } + }); + + test( + 'should return the default error message when the URL is an empty string', + () { + // Arrange + final Validator validator = url(); + const String emptyUrl = ''; + + // Act + final String? result = validator(emptyUrl); + + // Assert + expect(result, isNotNull); + expect(result, equals(FormBuilderLocalizations.current.urlErrorText)); + }); + + test('should return null for valid URLs with allowed underscore', () { + // Arrange + final Validator validator = url(allowUnderscore: true); + const List validUrls = [ + 'http://example_com.com', + 'https://example_com.com', + 'ftp://example_com.com', + 'http://subdomain_example.com', + 'https://subdomain_example.com', + ]; + + // Act & Assert + for (final String value in validUrls) { + expect(validator(value), isNull); + } + }); + + test( + 'should return the default error message for invalid URLs with disallowed underscore', + () { + // Arrange + final Validator validator = url(); + const List invalidUrls = [ + 'http://example_com.com', + 'https://example_com.com', + 'ftp://example_com.com', + 'http://subdomain_example.com', + 'https://subdomain_example.com', + ]; + + // Act & Assert + for (final String value in invalidUrls) { + final String? result = validator(value); + expect(result, isNotNull); + expect(result, equals(FormBuilderLocalizations.current.urlErrorText)); + } + }); + + test('should return null for valid URLs with host whitelist', () { + // Arrange + final Validator validator = url( + hostAllowList: ['example.com', 'subdomain.example.com'], + ); + const List validUrls = [ + 'http://example.com', + 'https://example.com', + 'http://subdomain.example.com', + 'https://subdomain.example.com', + ]; + + // Act & Assert + for (final String value in validUrls) { + expect(validator(value), isNull); + } + }); + + test( + 'should return the default error message for URLs not in the host whitelist', + () { + // Arrange + final Validator validator = url( + hostAllowList: ['example.com', 'subdomain.example.com'], + ); + const List invalidUrls = [ + 'http://notexample.com', + 'https://notexample.com', + 'http://example.org', + 'https://example.org', + ]; + + // Act & Assert + for (final String value in invalidUrls) { + final String? result = validator(value); + expect(result, isNotNull); + expect(result, equals(FormBuilderLocalizations.current.urlErrorText)); + } + }); + + test('should return null for valid URLs not in the host blacklist', () { + // Arrange + final Validator validator = + url(hostBlockList: ['banned.com', 'blocked.com']); + const List validUrls = [ + 'http://example.com', + 'https://example.com', + 'http://subdomain.example.com', + 'https://subdomain.example.com', + ]; + + // Act & Assert + for (final String value in validUrls) { + expect(validator(value), isNull); + } + }); + + test( + 'should return the default error message for URLs in the host blacklist', + () { + // Arrange + final Validator validator = + url(hostBlockList: ['banned.com', 'blocked.com']); + const List invalidUrls = [ + 'http://banned.com', + 'https://banned.com', + 'http://blocked.com', + 'https://blocked.com', + ]; + + // Act & Assert + for (final String value in invalidUrls) { + final String? result = validator(value); + expect(result, isNotNull); + expect(result, equals(FormBuilderLocalizations.current.urlErrorText)); + } + }); + + test('should return null for valid URLs with user authentication', () { + // Arrange + final Validator validator = url(); + const List validUrls = [ + 'http://user@example.com', + 'https://user:password@example.com', + 'ftp://user:password@example.com', + 'http://user@www.example.com', + 'https://user:password@www.example.com', + 'ftp://user:password@www.example.com', + ]; + + // Act & Assert + for (final String value in validUrls) { + expect(validator(value), isNull); + } + }); + + group('Validator immutability', () { + test( + 'should maintain validation rules when protocol input list is changed', + () { + final List protocols = ['a', 'b']; + final Validator v = url(protocols: protocols); + + expect(v('a://user@example.com'), isNull); + expect(v('b://user@example.com'), isNull); + expect(v('c://user@example.com'), + FormBuilderLocalizations.current.urlErrorText); + + protocols.removeLast(); + expect(v('a://user@example.com'), isNull); + expect(v('b://user@example.com'), isNull); + expect(v('c://user@example.com'), + FormBuilderLocalizations.current.urlErrorText); + + protocols.add('b'); + expect(v('a://user@example.com'), isNull); + expect(v('b://user@example.com'), isNull); + expect(v('c://user@example.com'), + FormBuilderLocalizations.current.urlErrorText); + + protocols.add('c'); + expect(v('a://user@example.com'), isNull); + expect(v('b://user@example.com'), isNull); + expect(v('c://user@example.com'), + FormBuilderLocalizations.current.urlErrorText); + }); + test( + 'should maintain validation rules when protocol allow list is changed', + () { + final List allow = [ + 'example.com', + 'subdomain.example.com' + ]; + final Validator v = url(hostAllowList: allow); + + expect(v('https://example.com'), isNull); + expect(v('https://subdomain.example.com'), isNull); + expect(v('https://exampleNotAllowed.com'), + FormBuilderLocalizations.current.urlErrorText); + + allow.removeLast(); + expect(v('https://example.com'), isNull); + expect(v('https://subdomain.example.com'), isNull); + expect(v('https://exampleNotAllowed.com'), + FormBuilderLocalizations.current.urlErrorText); + + allow.add('exampleNotAllowed.com'); + expect(v('https://example.com'), isNull); + expect(v('https://subdomain.example.com'), isNull); + expect(v('https://exampleNotAllowed.com'), + FormBuilderLocalizations.current.urlErrorText); + }); + + test( + 'should maintain validation rules when protocol block list is changed', + () { + final List block = [ + 'example.com', + 'subdomain.example.com' + ]; + final Validator v = url(hostBlockList: block); + + expect(v('https://example.com'), + FormBuilderLocalizations.current.urlErrorText); + expect(v('https://subdomain.example.com'), + FormBuilderLocalizations.current.urlErrorText); + expect(v('https://exampleNotAllowed.com'), isNull); + + block.removeLast(); + expect(v('https://example.com'), + FormBuilderLocalizations.current.urlErrorText); + expect(v('https://subdomain.example.com'), + FormBuilderLocalizations.current.urlErrorText); + expect(v('https://exampleNotAllowed.com'), isNull); + + block.add('exampleNotAllowed.com'); + expect(v('https://example.com'), + FormBuilderLocalizations.current.urlErrorText); + expect(v('https://subdomain.example.com'), + FormBuilderLocalizations.current.urlErrorText); + expect(v('https://exampleNotAllowed.com'), isNull); + }); + }); + }); +} diff --git a/test/src/validators/numeric_validators/between_validator_test.dart b/test/src/validators/numeric_validators/between_validator_test.dart new file mode 100644 index 00000000..1c9b1f80 --- /dev/null +++ b/test/src/validators/numeric_validators/between_validator_test.dart @@ -0,0 +1,147 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: between', () { + group('Valid inclusive comparisons', () { + final List<(List<(num, bool)>, {num max, int min})> testCases = + <(List<(num, bool)>, {num max, int min})>[ + ( + min: 0, + max: 0, + <(num, bool)>[ + // (input, isValid) + (-1, false), + (-0.1, false), + (0, true), + (0.001, false), + (10, false), + ] + ), + ( + min: 7, + max: 10.90, + <(num, bool)>[ + // (input, isValid) + (6.7, false), + (6.9, false), + (7, true), + (7.1, true), + (10, true), + (10.9, true), + (10.91, false), + (110, false), + ] + ), + ( + min: -232, + max: 510, + <(num, bool)>[ + // (input, isValid) + (-1000, false), + (6.7, true), + (110, true), + (4510, false), + ] + ), + ]; + for (final (List<(num, bool)>, {num max, int min}) testCase + in testCases) { + final int min = testCase.min; + final num max = testCase.max; + test('Should return error message if input is not in [$min, $max]', () { + final Validator v = between(min, max); + final String errorMsg = FormBuilderLocalizations.current + .betweenNumErrorText(min, max, 'true', 'true'); + for (final (num input, bool isValid) in testCase.$1) { + expect(v(input), isValid ? isNull : errorMsg, + reason: + '"$input" should ${isValid ? '' : 'not'} be between [$min, $max]'); + } + }); + } + }); + test('Should throw Argument error when min is greater than max', () { + expect(() => between(0, -2), throwsArgumentError); + expect(() => between(232, 89), throwsArgumentError); + }); + + test('Should validate with non-inclusive references', () { + const int left = -3; + const int right = 45; + const double mid = (left + right) / 2; + final Validator vLeft = between(left, right, minInclusive: false); + final String leftErrorMsg = FormBuilderLocalizations.current + .betweenNumErrorText(left, right, 'false', 'true'); + final Validator vRight = between(left, right, maxInclusive: false); + final String rightErrorMsg = FormBuilderLocalizations.current + .betweenNumErrorText(left, right, 'true', 'false'); + final Validator vBoth = + between(left, right, minInclusive: false, maxInclusive: false); + final String bothErrorMsg = FormBuilderLocalizations.current + .betweenNumErrorText(left, right, 'false', 'false'); + + expect( + vLeft(left), + equals(leftErrorMsg), + reason: + '(L1) Left boundary should be invalid when left is non-inclusive', + ); + expect( + vRight(left), + isNull, + reason: '(L2) Left boundary should be valid when left is inclusive', + ); + expect( + vBoth(left), + equals(bothErrorMsg), + reason: + '(L3) Left boundary should be invalid when both sides are non-inclusive', + ); + + expect( + vLeft(mid), + isNull, + reason: '(M1) Midpoint should always be valid with non-inclusive left', + ); + expect( + vRight(mid), + isNull, + reason: '(M2) Midpoint should always be valid with non-inclusive right', + ); + expect( + vBoth(mid), + isNull, + reason: + '(M3) Midpoint should always be valid with both sides non-inclusive', + ); + + expect( + vLeft(right), + isNull, + reason: '(R1) Right boundary should be valid when right is inclusive', + ); + expect( + vRight(right), + equals(rightErrorMsg), + reason: + '(R2) Right boundary should be invalid when right is non-inclusive', + ); + expect( + vBoth(right), + equals(bothErrorMsg), + reason: + '(R3) Right boundary should be invalid when both sides are non-inclusive', + ); + }); + test('Should validate with custom message', () { + const String msg = 'error msg'; + final Validator v = + between(0, 34, betweenMsg: (_, __, ___, ____, _____) => msg); + + expect(v(3), isNull); + expect(v(-1234), msg); + }); + }); +} diff --git a/test/src/validators/numeric_validators/greater_than_or_equal_to_validator_test.dart b/test/src/validators/numeric_validators/greater_than_or_equal_to_validator_test.dart new file mode 100644 index 00000000..89d5083a --- /dev/null +++ b/test/src/validators/numeric_validators/greater_than_or_equal_to_validator_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart' as val; + +void main() { + group('Validator: greaterThanOrEqualTo', () { + group('Valid comparisons', () { + final List<(List<(num, bool)>, {num n})> testCases = + <(List<(num, bool)>, {num n})>[ + ( + n: 0, + <(num, bool)>[ + // (input, isValid) + (-1, false), + (-0.1, false), + (0, true), + (0.001, true), + (10, true), + ] + ), + ( + n: 7, + <(num, bool)>[ + // (input, isValid) + (6.7, false), + (6.9, false), + (7, true), + (7.1, true), + (10, true), + ] + ), + ( + n: -232, + <(num, bool)>[ + // (input, isValid) + (-1000, false), + (6.7, true), + (4510, true), + ] + ), + ]; + for (final (List<(num, bool)>, {num n}) testCase in testCases) { + final num n = testCase.n; + test( + 'Should return error message if input is not greater than or equal to $n', + () { + final val.Validator v = val.greaterThanOrEqualTo(n); + final String errorMsg = + FormBuilderLocalizations.current.greaterThanOrEqualToErrorText(n); + for (final (num input, bool isValid) in testCase.$1) { + expect(v(input), isValid ? isNull : errorMsg, + reason: + '"$input" should ${isValid ? '' : 'not'} be greater than or equal to $n'); + } + }); + } + }); + test('Should validate with custom message', () { + const String msg = 'error msg'; + final val.Validator v = + val.greaterThanOrEqualTo(34, greaterThanOrEqualToMsg: (_, __) => msg); + + expect(v(35), isNull); + expect(v(-1234), msg); + }); + }); +} diff --git a/test/src/validators/numeric_validators/greater_than_validator_test.dart b/test/src/validators/numeric_validators/greater_than_validator_test.dart new file mode 100644 index 00000000..865256fd --- /dev/null +++ b/test/src/validators/numeric_validators/greater_than_validator_test.dart @@ -0,0 +1,65 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart' as val; + +void main() { + group('Validator: greaterThan', () { + group('Valid comparisons', () { + final List<(List<(num, bool)>, {num n})> testCases = + <(List<(num, bool)>, {num n})>[ + ( + n: 0, + <(num, bool)>[ + // (input, isValid) + (-1, false), + (-0.1, false), + (0, false), + (0.001, true), + (10, true), + ] + ), + ( + n: 7, + <(num, bool)>[ + // (input, isValid) + (6.7, false), + (6.9, false), + (7, false), + (7.1, true), + (10, true), + ] + ), + ( + n: -232, + <(num, bool)>[ + // (input, isValid) + (-1000, false), + (6.7, true), + (4510, true), + ] + ), + ]; + for (final (List<(num, bool)>, {num n}) testCase in testCases) { + final num n = testCase.n; + test('Should return error message if input is not greater than $n', () { + final val.Validator v = val.greaterThan(n); + final String errorMsg = + FormBuilderLocalizations.current.greaterThanErrorText(n); + for (final (num input, bool isValid) in testCase.$1) { + expect(v(input), isValid ? isNull : errorMsg, + reason: + '"$input" should ${isValid ? '' : 'not'} be greater than $n'); + } + }); + } + }); + test('Should validate with custom message', () { + const String msg = 'error msg'; + final val.Validator v = + val.greaterThan(34, greaterThanMsg: (_, __) => msg); + + expect(v(35), isNull); + expect(v(-1234), msg); + }); + }); +} diff --git a/test/src/validators/numeric_validators/less_than_or_equal_to_validator_test.dart b/test/src/validators/numeric_validators/less_than_or_equal_to_validator_test.dart new file mode 100644 index 00000000..cf70ecca --- /dev/null +++ b/test/src/validators/numeric_validators/less_than_or_equal_to_validator_test.dart @@ -0,0 +1,67 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart' as val; + +void main() { + group('Validator: lessThanOrEqualTo', () { + group('Valid comparisons', () { + final List<(List<(num, bool)>, {num n})> testCases = + <(List<(num, bool)>, {num n})>[ + ( + n: 0, + <(num, bool)>[ + // (input, isValid) + (-1, true), + (-0.1, true), + (0, true), + (0.001, false), + (10, false), + ] + ), + ( + n: 7, + <(num, bool)>[ + // (input, isValid) + (6.7, true), + (6.9, true), + (7, true), + (7.1, false), + (10, false), + ] + ), + ( + n: -232, + <(num, bool)>[ + // (input, isValid) + (-1000, true), + (6.7, false), + (4510, false), + ] + ), + ]; + for (final (List<(num, bool)>, {num n}) testCase in testCases) { + final num n = testCase.n; + test( + 'Should return error message if input is not less than or equal to $n', + () { + final val.Validator v = val.lessThanOrEqualTo(n); + final String errorMsg = + FormBuilderLocalizations.current.lessThanOrEqualToErrorText(n); + for (final (num input, bool isValid) in testCase.$1) { + expect(v(input), isValid ? isNull : errorMsg, + reason: + '"$input" should ${isValid ? '' : 'not'} be less than or equal to $n'); + } + }); + } + }); + test('Should validate with custom message', () { + const String msg = 'error msg'; + final val.Validator v = + val.lessThanOrEqualTo(34, lessThanOrEqualToMsg: (_, __) => msg); + + expect(v(35), msg); + expect(v(-1234), isNull); + }); + }); +} diff --git a/test/src/validators/numeric_validators/less_than_validator_test.dart b/test/src/validators/numeric_validators/less_than_validator_test.dart new file mode 100644 index 00000000..5da93f09 --- /dev/null +++ b/test/src/validators/numeric_validators/less_than_validator_test.dart @@ -0,0 +1,65 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart' as val; + +void main() { + group('Validator: lessThan', () { + group('Valid comparisons', () { + final List<(List<(num, bool)>, {num n})> testCases = + <(List<(num, bool)>, {num n})>[ + ( + n: 0, + <(num, bool)>[ + // (input, isValid) + (-1, true), + (-0.1, true), + (0, false), + (0.001, false), + (10, false), + ] + ), + ( + n: 7, + <(num, bool)>[ + // (input, isValid) + (6.7, true), + (6.9, true), + (7, false), + (7.1, false), + (10, false), + ] + ), + ( + n: -232, + <(num, bool)>[ + // (input, isValid) + (-1000, true), + (6.7, false), + (4510, false), + ] + ), + ]; + for (final (List<(num, bool)>, {num n}) testCase in testCases) { + final num n = testCase.n; + test('Should return error message if input is not less than $n', () { + final val.Validator v = val.lessThan(n); + final String errorMsg = + FormBuilderLocalizations.current.lessThanErrorText(n); + for (final (num input, bool isValid) in testCase.$1) { + expect(v(input), isValid ? isNull : errorMsg, + reason: + '"$input" should ${isValid ? '' : 'not'} be less than $n'); + } + }); + } + }); + test('Should validate with custom message', () { + const String msg = 'error msg'; + final val.Validator v = + val.lessThan(34, lessThanMsg: (_, __) => msg); + + expect(v(35), msg); + expect(v(-1234), isNull); + }); + }); +} diff --git a/test/src/validators/path_validators/matches_allowed_extensions_validator_test.dart b/test/src/validators/path_validators/matches_allowed_extensions_validator_test.dart new file mode 100644 index 00000000..2b4e4899 --- /dev/null +++ b/test/src/validators/path_validators/matches_allowed_extensions_validator_test.dart @@ -0,0 +1,178 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + group('Validator: matchesAllowedExtensionsValidator', () { + final List<({List extensions, bool isValid, String userInput})> + testCases = + <({List extensions, bool isValid, String userInput})>[ + (userInput: '.gitignore', extensions: ['.', ''], isValid: true), + (userInput: 'file.txt', extensions: ['.txt'], isValid: true), + (userInput: 'file.exe', extensions: ['.txt'], isValid: false), + + // Empty and null cases + (userInput: '', extensions: ['.txt'], isValid: false), + (userInput: 'noextension', extensions: ['.txt'], isValid: false), + + // Multiple extensions + ( + userInput: 'script.js', + extensions: ['.js', '.ts'], + isValid: true + ), + ( + userInput: 'style.css', + extensions: ['.js', '.ts'], + isValid: false + ), + + // Case sensitivity + (userInput: 'file.TXT', extensions: ['.txt'], isValid: false), + (userInput: 'file.Txt', extensions: ['.txt'], isValid: false), + + // Path handling + (userInput: '../dir1/file.2', extensions: ['.2'], isValid: true), + ( + userInput: '/absolute/path/file.txt', + extensions: ['.txt'], + isValid: true + ), + ( + userInput: 'relative/path/file.txt', + extensions: ['.txt'], + isValid: true + ), + + // Multiple dots + ( + userInput: '.gitignore.exe', + extensions: ['.exe'], + isValid: true + ), + ( + userInput: '.gitignore.nexe.exe', + extensions: ['.exe'], + isValid: true + ), + ( + userInput: '.gitignore.exe.nexe', + extensions: ['.exe'], + isValid: false + ), + ( + userInput: '.gitignore.exe.nexe', + extensions: ['.exe.nexe'], + isValid: true + ), + (userInput: 'archive.tar.gz', extensions: ['.gz'], isValid: true), + (userInput: '.hidden', extensions: ['.hidden'], isValid: false), + ( + userInput: '.hidden.hidden', + extensions: ['.hidden'], + isValid: true + ), + (userInput: '..double', extensions: ['.double'], isValid: true), + (userInput: 'file.', extensions: [''], isValid: false), + (userInput: 'file.', extensions: ['.', ''], isValid: true), + (userInput: '.', extensions: ['.', ''], isValid: true), + + // Special characters + (userInput: 'file name.txt', extensions: ['.txt'], isValid: true), + (userInput: 'file-name.txt', extensions: ['.txt'], isValid: true), + ]; + for (final ({ + List extensions, + bool isValid, + String userInput + }) testCase in testCases) { + test( + 'Should ${testCase.isValid ? 'return null' : 'return error message'} when input is "${testCase.userInput}" for extensions ${testCase.extensions}', + () { + final String errorMsg = + FormBuilderLocalizations.current.fileExtensionErrorText( + testCase.extensions.join(', '), + ); + final Validator v = + matchesAllowedExtensions(testCase.extensions); + + expect( + v(testCase.userInput), + testCase.isValid ? isNull : errorMsg, + ); + }); + } + test('Should throw ArgumentError when allowed extensions is empty', () { + expect(() => matchesAllowedExtensions([]), throwsArgumentError); + }); + test( + 'Should throw ArgumentError when an invalid extension is provided to extensions', + () { + expect( + () => matchesAllowedExtensions(['invalid extension', '.txt']), + throwsArgumentError); + }); + + test( + 'Should accept files with extension "tXt" or empty when case insensitive', + () { + final List extensions = ['.tXt', '']; + final String errorMsg = + FormBuilderLocalizations.current.fileExtensionErrorText( + extensions.join(', '), + ); + final Validator v = + matchesAllowedExtensions(extensions, caseSensitive: false); + + expect(v('valid.tXt'), isNull, reason: 'Input: "valid.tXt"'); + expect(v('valid.tXT'), isNull, reason: 'Input: "valid.tXT"'); + expect(v('emptyExtension'), isNull, reason: 'Input: "emptyExtension"'); + expect(v(''), isNull, reason: 'Input: empty string'); + expect(v('invalid.txt '), errorMsg, reason: 'Input: "invalid.txt "'); + expect(v('text.ttxt'), errorMsg, reason: 'Input: "text.ttxt"'); + expect(v('text. txt'), errorMsg, reason: 'Input: "text. txt"'); + }); + test( + 'Should accept files with extension ".abc" or ".a.b.d" or "" with custom message', + () { + final List extensions = ['.abc', '.a.b.c', '']; + const String errorMsg = 'custom error message'; + final Validator v = matchesAllowedExtensions(extensions, + matchesAllowedExtensionsMsg: (_) => errorMsg); + + expect(v('/valid/.abc'), isNull, reason: 'Input: "/valid/.abc"'); + // todo check if there is a bug with the p.extension function. + // It seems to have a bug => https://github.com/dart-lang/core/issues/723 + //expect(v('/valid/.a.b'), errorMsg, reason: 'Input: "/valid/.a.b"'); + }); + + test('should be immutable even when the input list of extensions change', + () { + final List validExtensions = ['.ext1', '.ext2']; + + final Validator v = matchesAllowedExtensions(validExtensions); + expect(v('/valid/v.ext1'), isNull); + expect(v('/valid/v.ext2'), isNull); + expect( + v('/valid/v.ext3'), + FormBuilderLocalizations.current + .fileExtensionErrorText(validExtensions.join(', '))); + + validExtensions.removeLast(); + expect(v('/valid/v.ext1'), isNull); + expect(v('/valid/v.ext2'), isNull); + expect( + v('/valid/v.ext3'), + FormBuilderLocalizations.current + .fileExtensionErrorText(validExtensions.join(', '))); + + validExtensions.add('.ext3'); + expect(v('/valid/v.ext1'), isNull); + expect(v('/valid/v.ext2'), isNull); + expect( + v('/valid/v.ext3'), + FormBuilderLocalizations.current + .fileExtensionErrorText(validExtensions.join(', '))); + }); + }); +} diff --git a/test/src/validators/string_validators/contains_validator_test.dart b/test/src/validators/string_validators/contains_validator_test.dart new file mode 100644 index 00000000..9db98e81 --- /dev/null +++ b/test/src/validators/string_validators/contains_validator_test.dart @@ -0,0 +1,115 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/validators.dart' as val; + +void main() { + group('Validator: contains', () { + group('Case sensitive validation', () { + final List<({String input, bool isValid, String substring})> testCases = + <({String input, bool isValid, String substring})>[ + // Empty substring + (substring: '', input: '', isValid: true), + (substring: '', input: ' ', isValid: true), + (substring: '', input: 'this', isValid: true), + (substring: '', input: 'tHIs', isValid: true), + // "a" substring + (substring: 'a', input: '', isValid: false), + (substring: 'a', input: 'a', isValid: true), + (substring: 'a', input: 'A', isValid: false), + (substring: 'a', input: 'b', isValid: false), + (substring: 'a', input: 'b a c', isValid: true), + // " a " substring + (substring: ' a ', input: 'a', isValid: false), + (substring: ' a ', input: 'b a c', isValid: true), + // "hello" substring + (substring: 'hello', input: 'hell o', isValid: false), + (substring: 'hello', input: 'Hello, hello world!', isValid: true), + (substring: 'hello', input: 'Hello, helLo world!', isValid: false), + (substring: 'hello', input: 'helllo', isValid: false), + // "Hello World!" substring + (substring: 'Hello World!', input: 'hello world!', isValid: false), + (substring: 'Hello World!', input: 'Hello, World!', isValid: false), + // " " substring + (substring: ' ', input: ' \t\t\t', isValid: false), + (substring: ' ', input: ' ', isValid: false), + (substring: ' ', input: ' ', isValid: false), + (substring: ' ', input: ' ', isValid: true), + (substring: ' ', input: ' ', isValid: true), + ]; + + for (final ( + substring: String substring, + input: String input, + isValid: bool isValid + ) in testCases) { + test( + 'should return ${isValid ? 'null' : 'error message'} when input is "$input" for substring "$substring"', + () { + final val.Validator v = val.contains(substring); + expect( + v(input), + isValid + ? isNull + : FormBuilderLocalizations.current + .containsErrorText(substring)); + }); + } + }); + group('Case insensitive validation', () { + final List<({String input, bool isValid, String substring})> testCases = + <({String input, bool isValid, String substring})>[ + // Empty substring + (substring: '', input: '', isValid: true), + (substring: '', input: ' ', isValid: true), + (substring: '', input: 'this', isValid: true), + (substring: '', input: 'tHIs', isValid: true), + // "a" substring + (substring: 'a', input: '', isValid: false), + (substring: 'a', input: 'a', isValid: true), + (substring: 'a', input: 'A', isValid: true), + (substring: 'a', input: 'b', isValid: false), + (substring: 'a', input: 'b a c', isValid: true), + // " a " substring + (substring: ' a ', input: 'a', isValid: false), + (substring: ' a ', input: 'b a c', isValid: true), + (substring: ' a ', input: 'b A c', isValid: true), + // "hello" substring + (substring: 'hello', input: 'hell o', isValid: false), + (substring: 'hello', input: 'Hello, hello world!', isValid: true), + (substring: 'hello', input: 'Hello, helLo world!', isValid: true), + (substring: 'hello', input: 'helllo', isValid: false), + // "Hello World!" substring + (substring: 'Hello World!', input: 'hello world!', isValid: true), + (substring: 'Hello World!', input: 'Hello, World!', isValid: false), + ]; + + for (final ( + substring: String substring, + input: String input, + isValid: bool isValid + ) in testCases) { + test( + 'should return ${isValid ? 'null' : 'error message'} when input is "$input" for substring "$substring"', + () { + final val.Validator v = + val.contains(substring, caseSensitive: false); + expect( + v(input), + isValid + ? isNull + : FormBuilderLocalizations.current + .containsErrorText(substring)); + }); + } + }); + + test('should return custom error message', () { + const String errorMsg = 'error msg'; + final val.Validator v = val.contains('substring test', + caseSensitive: false, containsMsg: (_, __) => errorMsg); + + expect(v('This must pass: subSTRING TESt'), isNull); + expect(v('This must not pass: subSTRIN TESt'), errorMsg); + }); + }); +} diff --git a/test/src/validators/string_validators/has_min_lowercase_chars_validator_test.dart b/test/src/validators/string_validators/has_min_lowercase_chars_validator_test.dart new file mode 100644 index 00000000..072c19f9 --- /dev/null +++ b/test/src/validators/string_validators/has_min_lowercase_chars_validator_test.dart @@ -0,0 +1,114 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + const String customErrorMessage = 'custom error message'; + + group('Validator: hasMinLowercaseChars', () { + group('Validations with default error message', () { + String turkishLowercase = 'abcçdefgğhıijklmnoöprsştuüvyz'; + String randomLowercase = + 'b, æ, à, ø, ĉ, ij, ɣ, φ, ϋ, ϗ, ϧ, ѓ, ѽ, ծ, ⴌ, ḝ, ἄ, ⓜ, ⰲ, ⲫ, a, 𐑄'; + final List<({String input, bool isValid, int? minValue})> testCases = + <({int? minValue, String input, bool isValid})>[ + (minValue: null, input: '', isValid: false), + (minValue: null, input: ' \t', isValid: false), + (minValue: null, input: 'D', isValid: false), + (minValue: null, input: 'PASSWORD123', isValid: false), + (minValue: 1, input: 'd', isValid: true), + (minValue: 1, input: '1 2dAD', isValid: true), + (minValue: 1, input: 'di', isValid: true), + (minValue: 1, input: 'Password123', isValid: true), + (minValue: 2, input: 'dD', isValid: false), + (minValue: 2, input: 'dl', isValid: true), + (minValue: 2, input: 'idl', isValid: true), + (minValue: 2, input: 'PAssWORD123', isValid: true), + (minValue: 2, input: 'Password123', isValid: true), + (minValue: 4, input: 'Password123', isValid: true), + // Testing for non A-Z chars + (minValue: 1, input: 'ç', isValid: true), + (minValue: 1, input: 'Ç', isValid: false), + (minValue: 1, input: '123!@#\$%¨&*()_+`{}[]´^~/?:', isValid: false), + (minValue: 1, input: 'Aá12', isValid: true), + (minValue: 29, input: turkishLowercase, isValid: true), + (minValue: 30, input: turkishLowercase, isValid: false), + (minValue: 22, input: randomLowercase, isValid: true), + (minValue: 23, input: randomLowercase, isValid: false), + // Examples that does not work: + // (minValue: 3, input: 'ფ, ꟶ, 𐓀', isValid: true), + ]; + + for (final ( + minValue: int? minValue, + input: String input, + isValid: bool isValid + ) in testCases) { + final String? expectedReturnValue = isValid + ? null + : FormBuilderLocalizations.current + .containsLowercaseCharErrorText(minValue ?? 1); + test( + 'Should return ${expectedReturnValue == null ? null : 'default error message'} with input "$input", min lowercase = ${minValue ?? 1}', + () { + expect( + hasMinLowercaseChars(min: minValue ?? 1)(input), + equals(expectedReturnValue), + ); + }); + } + }); + + group('Validations with custom error message', () { + test( + 'should return the custom error message when the value does not have any lowercase characters', + () { + // Arrange + final Validator validator = hasMinLowercaseChars( + hasMinLowercaseCharsMsg: (_, __) => customErrorMessage); + const String value = 'PASSWORD123'; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(customErrorMessage)); + }); + + test( + 'should return the custom error message when the value does not have enough lowercase characters', + () { + // Arrange + final Validator validator = hasMinLowercaseChars( + min: 2, hasMinLowercaseCharsMsg: (_, __) => customErrorMessage); + const String value = 'PASSWOrD'; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(customErrorMessage)); + }); + }); + + test('Should pass with custom counter that identifies # as lowercase', () { + const String value = 'ABC#abc'; + expect( + hasMinUppercaseChars(min: 4)(value), + equals(FormBuilderLocalizations.current + .containsUppercaseCharErrorText(4))); + expect( + hasMinUppercaseChars( + min: 4, + customUppercaseCounter: (String v) => + RegExp('[a-z#]').allMatches(v).length)(value), + isNull); + }); + + test('Should throw argument error when the min parameter is invalid', () { + expect(() => hasMinLowercaseChars(min: -10), throwsArgumentError); + expect(() => hasMinLowercaseChars(min: -1), throwsArgumentError); + expect(() => hasMinLowercaseChars(min: 0), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/string_validators/has_min_numeric_chars_validator_test.dart b/test/src/validators/string_validators/has_min_numeric_chars_validator_test.dart new file mode 100644 index 00000000..35146b9b --- /dev/null +++ b/test/src/validators/string_validators/has_min_numeric_chars_validator_test.dart @@ -0,0 +1,86 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + const String customErrorMessage = 'custom error message'; + group('Validator: hasMinNumericCharsValidator', () { + group('Validations with default error message', () { + final List<({String input, bool isValid, int? minValue})> testCases = + <({int? minValue, String input, bool isValid})>[ + (minValue: null, input: '', isValid: false), + (minValue: null, input: ' \t', isValid: false), + (minValue: null, input: 'D', isValid: false), + (minValue: null, input: 'password', isValid: false), + (minValue: 1, input: '9', isValid: true), + (minValue: 1, input: 'dA1D', isValid: true), + (minValue: 1, input: '08', isValid: true), + (minValue: 1, input: 'Password123', isValid: true), + (minValue: 2, input: '4D', isValid: false), + (minValue: 2, input: '40', isValid: true), + (minValue: 2, input: '287', isValid: true), + (minValue: 2, input: 'password13', isValid: true), + (minValue: 2, input: 'Password123', isValid: true), + (minValue: 4, input: 'Pass0word123', isValid: true), + ]; + + for (final ( + minValue: int? minValue, + input: String input, + isValid: bool isValid + ) in testCases) { + final String? expectedReturnValue = isValid + ? null + : FormBuilderLocalizations.current + .containsNumberErrorText(minValue ?? 1); + test( + 'Should return ${expectedReturnValue == null ? null : 'default error message'} with input "$input", min numeric = ${minValue ?? 1}', + () { + expect( + hasMinNumericChars(min: minValue ?? 1)(input), + equals(expectedReturnValue), + ); + }); + } + }); + group('Validations with custom error message', () { + test( + 'Should return custom error message when the value does not have any numeric value', + () { + final Validator validator = hasMinNumericChars( + hasMinNumericCharsMsg: (_, __) => customErrorMessage, + ); + expect(validator('passWORD'), equals(customErrorMessage)); + }); + test( + 'Should return custom error message when the value does not have enough numeric value', + () { + final Validator validator = hasMinNumericChars( + min: 4, + hasMinNumericCharsMsg: (_, __) => customErrorMessage, + ); + expect(validator('pas4sWORD1'), equals(customErrorMessage)); + }); + }); + + test('Should pass when abcdef and ABCDEF is also considered numeric digits', + () { + const String s = '123aBc ijklm*'; + + expect(hasMinNumericChars(min: 6)(s), + equals(FormBuilderLocalizations.current.containsNumberErrorText(6))); + expect( + hasMinNumericChars( + min: 6, + customNumericCounter: (String v) => + RegExp('[0-9a-fA-F]').allMatches(v).length)(s), + isNull); + }); + + test('Should throw argument error when the min parameter is invalid', () { + expect(() => hasMinNumericChars(min: -10), throwsArgumentError); + expect(() => hasMinNumericChars(min: -1), throwsArgumentError); + expect(() => hasMinNumericChars(min: 0), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/string_validators/has_min_special_chars_validator_test.dart b/test/src/validators/string_validators/has_min_special_chars_validator_test.dart new file mode 100644 index 00000000..edd2f416 --- /dev/null +++ b/test/src/validators/string_validators/has_min_special_chars_validator_test.dart @@ -0,0 +1,79 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + String turkishLowercase = 'abcçdefgğhıijklmnoöprsştuüvyz'; + String randomAlphabeticalChars = 'bæàøĉijɣφϋϗϧѓѽծⴌḝἄⓜⰲⲫa'; + const String customErrorMessage = 'custom error message'; + group('Validator: hasMinSpecialCharsValidator', () { + group('Validations with default error message', () { + final List<({String input, bool isValid, int? minValue})> testCases = + <({int? minValue, String input, bool isValid})>[ + (minValue: null, input: '', isValid: false), + (minValue: null, input: ' ', isValid: true), + (minValue: null, input: 'D', isValid: false), + (minValue: null, input: 'password', isValid: false), + (minValue: 1, input: '*', isValid: true), + (minValue: 1, input: '\$A1D', isValid: true), + (minValue: 1, input: '(¨', isValid: true), + (minValue: 1, input: 'Password123#', isValid: true), + (minValue: 2, input: '+D', isValid: false), + (minValue: 2, input: '==', isValid: true), + (minValue: 2, input: '@_-', isValid: true), + (minValue: 2, input: 'password13%%', isValid: true), + (minValue: 2, input: 'paççword13', isValid: false), + (minValue: 2, input: 'Password123#@#', isValid: true), + (minValue: 4, input: 'Pass0word123!!!!', isValid: true), + // Testing for non A-Z chars + (minValue: 1, input: 'ç', isValid: false), + (minValue: 1, input: randomAlphabeticalChars, isValid: false), + (minValue: 1, input: turkishLowercase, isValid: false), + ]; + + for (final ( + minValue: int? minValue, + input: String input, + isValid: bool isValid + ) in testCases) { + final String? expectedReturnValue = isValid + ? null + : FormBuilderLocalizations.current + .containsSpecialCharErrorText(minValue ?? 1); + test( + 'Should return ${expectedReturnValue == null ? null : 'default error message'} with input "$input", min special = ${minValue ?? 1}', + () { + expect( + hasMinSpecialChars(min: minValue ?? 1)(input), + equals(expectedReturnValue), + ); + }); + } + }); + group('Validations with custom error message', () { + test( + 'Should return custom error message when the value does not have any special value', + () { + final Validator validator = hasMinSpecialChars( + hasMinSpecialCharsMsg: (_, __) => customErrorMessage, + ); + expect(validator('passWORD'), equals(customErrorMessage)); + }); + test( + 'Should return custom error message when the value does not have enough special value', + () { + final Validator validator = hasMinSpecialChars( + min: 4, + hasMinSpecialCharsMsg: (_, __) => customErrorMessage, + ); + expect(validator('pas4sWORD1'), equals(customErrorMessage)); + }); + }); + + test('Should throw argument error when the min parameter is invalid', () { + expect(() => hasMinSpecialChars(min: -10), throwsArgumentError); + expect(() => hasMinSpecialChars(min: -1), throwsArgumentError); + expect(() => hasMinSpecialChars(min: 0), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/string_validators/has_min_uppercase_chars_validator_test.dart b/test/src/validators/string_validators/has_min_uppercase_chars_validator_test.dart new file mode 100644 index 00000000..834c94f6 --- /dev/null +++ b/test/src/validators/string_validators/has_min_uppercase_chars_validator_test.dart @@ -0,0 +1,113 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + const String customErrorMessage = 'custom error message'; + + group('Validator: hasMinUppercaseChars', () { + group('Validations with default error message', () { + String turkishUpper = 'ABCÇDEFGĞHIİJKLMNOÖPRSŞTUÜVYZ'; + String randomUppercase = + 'B, Æ, À, Ø, Ĉ, IJ, Ɣ, Φ, Ϋ, Ϗ, Ϧ, Ѓ, Ѽ, Ծ, Ⴌ, Ḝ, Ἄ, Ⓜ, Ⰲ, Ⲫ, A, 𐐜'; + final List<({String input, bool isValid, int? minValue})> testCases = + <({int? minValue, String input, bool isValid})>[ + (minValue: null, input: '', isValid: false), + (minValue: null, input: ' \t', isValid: false), + (minValue: null, input: 'd', isValid: false), + (minValue: null, input: 'password123', isValid: false), + (minValue: 1, input: 'D', isValid: true), + (minValue: 1, input: '1 2dAd', isValid: true), + (minValue: 1, input: 'DI', isValid: true), + (minValue: 1, input: 'PASSword123', isValid: true), + (minValue: 2, input: 'dD', isValid: false), + (minValue: 2, input: 'DL', isValid: true), + (minValue: 2, input: 'IDL', isValid: true), + (minValue: 2, input: 'passWOrd123', isValid: true), + (minValue: 4, input: 'PASSWOrd123', isValid: true), + // Testing for non A-Z chars + (minValue: 1, input: 'Ç', isValid: true), + (minValue: 1, input: 'ç', isValid: false), + (minValue: 1, input: '123!@#\$%¨&*()_+`{}[]´^~/?:', isValid: false), + (minValue: 1, input: 'aÁ12', isValid: true), + (minValue: 29, input: turkishUpper, isValid: true), + (minValue: 30, input: turkishUpper, isValid: false), + + (minValue: 22, input: randomUppercase, isValid: true), + (minValue: 23, input: randomUppercase, isValid: false), + // Examples that does not work: + // (minValue: 3, input: 'Ფ, Ꟶ, 𐓀', isValid: true), + ]; + + for (final ( + minValue: int? minValue, + input: String input, + isValid: bool isValid + ) in testCases) { + final String? expectedReturnValue = isValid + ? null + : FormBuilderLocalizations.current + .containsUppercaseCharErrorText(minValue ?? 1); + test( + 'Should return ${expectedReturnValue == null ? null : 'default error message'} with input "$input", min uppercase = ${minValue ?? 1}', + () { + expect( + hasMinUppercaseChars(min: minValue ?? 1)(input), + equals(expectedReturnValue), + ); + }); + } + }); + + group('Validations with custom error message', () { + test( + 'should return the custom error message when the value does not have any uppercase characters', + () { + // Arrange + final Validator validator = hasMinUppercaseChars( + hasMinUppercaseCharsMsg: (_, __) => customErrorMessage); + const String value = 'password123'; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(customErrorMessage)); + }); + + test( + 'should return the custom error message when the value does not have enough uppercase characters', + () { + // Arrange + final Validator validator = hasMinUppercaseChars( + min: 2, hasMinUppercaseCharsMsg: (_, __) => customErrorMessage); + const String value = 'passwoRd'; + + // Act + final String? result = validator(value); + + // Assert + expect(result, equals(customErrorMessage)); + }); + }); + + test('Should pass with custom counter that identifies # as uppercase', () { + const String value = 'ABC#abc'; + expect( + hasMinUppercaseChars(min: 4)(value), + equals(FormBuilderLocalizations.current + .containsUppercaseCharErrorText(4))); + expect( + hasMinUppercaseChars( + min: 4, + customUppercaseCounter: (String v) => + RegExp('[A-Z#]').allMatches(v).length)(value), + isNull); + }); + test('Should throw argument error when the min parameter is invalid', () { + expect(() => hasMinUppercaseChars(min: -10), throwsArgumentError); + expect(() => hasMinUppercaseChars(min: -1), throwsArgumentError); + expect(() => hasMinUppercaseChars(min: 0), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/string_validators/uuid_validator_test.dart b/test/src/validators/string_validators/uuid_validator_test.dart new file mode 100644 index 00000000..dd93116b --- /dev/null +++ b/test/src/validators/string_validators/uuid_validator_test.dart @@ -0,0 +1,102 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('Validator: uuid', () { + test('should return null if the UUID is valid', () { + // Arrange + final Validator validator = + uuid(uuidMsg: (_) => customErrorMessage); + const String validUuid = '123e4567-e89b-12d3-a456-426614174000'; + + // Act + final String? result = validator(validUuid); + + // Assert + expect(result, isNull); + }); + + test('should return error if the UUID is invalid', () { + // Arrange + final Validator validator = + uuid(uuidMsg: (_) => customErrorMessage); + const String invalidUuid = 'invalid-uuid'; + + // Act + final String? result = validator(invalidUuid); + + // Assert + expect(result, customErrorMessage); + }); + + test('should return error if the UUID is too short', () { + // Arrange + final Validator validator = + uuid(uuidMsg: (_) => customErrorMessage); + const String shortUuid = '123e4567-e89b-12d3-a456-426614174'; + + // Act + final String? result = validator(shortUuid); + + // Assert + expect(result, customErrorMessage); + }); + + test('should return error if the UUID is too long', () { + // Arrange + final Validator validator = + uuid(uuidMsg: (_) => customErrorMessage); + const String longUuid = '123e4567-e89b-12d3-a456-4266141740000000'; + + // Act + final String? result = validator(longUuid); + + // Assert + expect(result, customErrorMessage); + }); + + test('should return error if the UUID has incorrect format', () { + // Arrange + final Validator validator = + uuid(uuidMsg: (_) => customErrorMessage); + const String invalidFormatUuid = '123e4567-e89b-12d3-456-426614174000'; + + // Act + final String? result = validator(invalidFormatUuid); + + // Assert + expect(result, customErrorMessage); + }); + + test('should return error if the value is empty', () { + // Arrange + final Validator validator = + uuid(uuidMsg: (_) => customErrorMessage); + const String value = ''; + + // Act + final String? result = validator(value); + + // Assert + expect(result, customErrorMessage); + }); + + test('should return the default error message if no custom message is set', + () { + // Arrange + final Validator validator = uuid(); + const String invalidUuid = 'invalid-uuid'; + + // Act + final String? result = validator(invalidUuid); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.uuidErrorText)); + }); + }); +} diff --git a/test/src/validators/user_information_validators/email_validator_test.dart b/test/src/validators/user_information_validators/email_validator_test.dart new file mode 100644 index 00000000..31dc449f --- /dev/null +++ b/test/src/validators/user_information_validators/email_validator_test.dart @@ -0,0 +1,123 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('Validator: email', () { + test('should return null if the email address is valid', () { + // Arrange + final Validator validator = + email(emailMsg: (_) => customErrorMessage); + const String validEmail = 'user@example.com'; + + // Act + final String? result = validator(validEmail); + + // Assert + expect(result, isNull); + }); + + test('should return null if the email address is a valid generated email', + () { + // Arrange + final Validator validator = + email(emailMsg: (_) => customErrorMessage); + final String validEmail = faker.internet.email(); + + // Act + final String? result = validator(validEmail); + + // Assert + expect(result, isNull); + }); + + test('should return the error text if the email address is invalid', () { + // Arrange + final Validator validator = + email(emailMsg: (_) => customErrorMessage); + const String invalidEmail = 'invalid-email'; + + // Act + final String? result = validator(invalidEmail); + + // Assert + expect(result, customErrorMessage); + }); + + test('should return the error text if the email address is empty', () { + // Arrange + final Validator validator = + email(emailMsg: (_) => customErrorMessage); + const String emptyEmail = ''; + + // Act + final String? result = validator(emptyEmail); + + // Assert + expect(result, customErrorMessage); + }); + + test('should return the error text if the email address is whitespace', () { + // Arrange + final Validator validator = + email(emailMsg: (_) => customErrorMessage); + const String whitespaceEmail = ' '; + + // Act + final String? result = validator(whitespaceEmail); + + // Assert + expect(result, customErrorMessage); + }); + + test( + 'should return the default error text if the email address is invalid and no custom error message is provided', + () { + // Arrange + final Validator validator = email(); + const String invalidEmail = 'invalid-email'; + + // Act + final String? result = validator(invalidEmail); + + // Assert + expect(result, FormBuilderLocalizations.current.emailErrorText); + }); + + test( + 'should return null if the email address is valid according to a custom regex', + () { + // Arrange + final RegExp customRegex = RegExp(r'^[\w\.-]+@example\.com$'); + final Validator validator = + email(regex: customRegex, emailMsg: (_) => customErrorMessage); + const String validEmail = 'user@example.com'; + + // Act + final String? result = validator(validEmail); + + // Assert + expect(result, isNull); + }); + + test( + 'should return the custom error text if the email address is invalid according to a custom regex', + () { + // Arrange + final RegExp customRegex = RegExp(r'^[\w\.-]+@example\.com$'); + final Validator validator = + email(regex: customRegex, emailMsg: (_) => customErrorMessage); + const String invalidEmail = 'user@notexample.com'; + + // Act + final String? result = validator(invalidEmail); + + // Assert + expect(result, customErrorMessage); + }); + }); +} diff --git a/test/src/validators/user_information_validators/password_validator_test.dart b/test/src/validators/user_information_validators/password_validator_test.dart new file mode 100644 index 00000000..65e192c7 --- /dev/null +++ b/test/src/validators/user_information_validators/password_validator_test.dart @@ -0,0 +1,112 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:form_builder_validators/src/validators/user_information_validators.dart' + as u; + +void main() { + group('Validator: password', () { + group('Validations with default error message', () { + final List<({String? expectedOutput, String password})> testCases = + <({String? expectedOutput, String password})>[ + // Valid one + (password: 'A1@abcefrtdsgjto', expectedOutput: null), + // Invalid ones + ( + password: '', + expectedOutput: + FormBuilderLocalizations.current.minLengthErrorText(16) + ), + ( + password: + 'this string has more than 32 characters, which is not allowed as a password by default', + expectedOutput: + FormBuilderLocalizations.current.maxLengthErrorText(32) + ), + ( + password: 'almostvalidpassword123@', + expectedOutput: + FormBuilderLocalizations.current.containsUppercaseCharErrorText(1) + ), + ( + password: 'ALMOSTVALIDPASSWORD123@', + expectedOutput: + FormBuilderLocalizations.current.containsLowercaseCharErrorText(1) + ), + ( + password: 'Almostvalidpassword!@#@', + expectedOutput: + FormBuilderLocalizations.current.containsNumberErrorText(1) + ), + ( + password: 'Almostvalidpaççword1234', + expectedOutput: + FormBuilderLocalizations.current.containsSpecialCharErrorText(1) + ), + ]; + + final Validator validator = u.password(); + for (final ( + password: String password, + expectedOutput: String? expectedOutput + ) in testCases) { + test( + 'Should ${expectedOutput == null ? 'pass' : 'fail'} with password "$password"', + () => expect(validator(password), equals(expectedOutput))); + } + }); + group('Validations with custom', () { + final String customErrorMessage = 'invalid password'; + final List<({bool fails, String password})> testCases = + <({bool fails, String password})>[ + // Valid one + (password: 'A1@abcefrtdsgjto', fails: false), + // Invalid ones + (password: '', fails: true), + ( + password: + 'this string has more than 32 characters, which is not allowed as a password by default', + fails: true + ), + (password: 'almostvalidpassword123@', fails: true), + (password: 'ALMOSTVALIDPASSWORD123@', fails: true), + (password: 'ALMOSTVALIDPASSWORD!@#@', fails: true), + (password: 'Almostvalidpaççword1234', fails: true), + ]; + + final Validator validator = + u.password(passwordMsg: (_) => customErrorMessage); + for (final (password: String password, fails: bool fails) in testCases) { + test( + 'Should ${fails ? 'fail' : 'pass'} with password "$password" (custom message)', + () => expect(validator(password), + equals(fails ? customErrorMessage : null))); + } + }); + + test('should return null if the password meets all customized requirements', + () { + // Arrange + final Validator validator = u.password( + minLength: 10, + maxLength: 20, + minUppercaseCount: 2, + minLowercaseCount: 2, + minNumberCount: 2, + minSpecialCharCount: 2, + ); + + expect(validator('AbcdefG12!@'), isNull); + }); + + test('Should throw argument error if any of the arguments are invalid', () { + expect(() => u.password(minLength: -12), throwsArgumentError); + expect(() => u.password(maxLength: -12), throwsArgumentError); + expect(() => u.password(minUppercaseCount: -12), throwsArgumentError); + expect(() => u.password(minLowercaseCount: -12), throwsArgumentError); + expect(() => u.password(minNumberCount: -12), throwsArgumentError); + expect(() => u.password(minSpecialCharCount: -12), throwsArgumentError); + expect( + () => u.password(minLength: 12, maxLength: 3), throwsArgumentError); + }); + }); +} diff --git a/test/src/validators/user_information_validators/phone_number_validator_test.dart b/test/src/validators/user_information_validators/phone_number_validator_test.dart new file mode 100644 index 00000000..bd5c8368 --- /dev/null +++ b/test/src/validators/user_information_validators/phone_number_validator_test.dart @@ -0,0 +1,79 @@ +import 'package:faker_dart/faker_dart.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:form_builder_validators/localization/l10n.dart'; +import 'package:form_builder_validators/src/validators/validators.dart'; + +void main() { + final Faker faker = Faker.instance; + final String customErrorMessage = faker.lorem.sentence(); + + group('Validator: phoneNumber', () { + test('should return null for valid phone numbers', () { + // Arrange + final Validator validator = phoneNumber(); + final List validPhoneNumbers = [ + '+1-800-555-5555', + '1234567890', + '+44 7911 123456', + '07911 123456', + '+91-9876543210', + '9876543210', + '123-456-7890', + '123.456.7890', + '+49 123 456 7890', + ]; + + // Act & Assert + for (final String value in validPhoneNumbers) { + expect(validator(value), isNull); + } + }); + + test('should return the default error message for invalid phone numbers', + () { + // Arrange + final Validator validator = phoneNumber(); + const List invalidPhoneNumbers = [ + 'phone123', + '123-abc-7890', + ]; + + // Act & Assert + for (final String value in invalidPhoneNumbers) { + final String? result = validator(value); + expect(result, equals(FormBuilderLocalizations.current.phoneErrorText)); + } + }); + + test('should return the custom error message for invalid phone numbers', + () { + // Arrange + final Validator validator = + phoneNumber(phoneNumberMsg: (_) => customErrorMessage); + const List invalidPhoneNumbers = [ + 'phone123', + '123-abc-7890', + ]; + + // Act & Assert + for (final String value in invalidPhoneNumbers) { + final String? result = validator(value); + expect(result, equals(customErrorMessage)); + } + }); + + test( + 'should return the default error message when the phone number is an empty string', + () { + // Arrange + final Validator validator = phoneNumber(); + const String emptyPhoneNumber = ''; + + // Act + final String? result = validator(emptyPhoneNumber); + + // Assert + expect(result, equals(FormBuilderLocalizations.current.phoneErrorText)); + }); + }); +}