diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 000000000..4fd6ab254
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,6 @@
+.dockerignore
+.gitignore
+README.md
+apps/server/node_modules
+apps/client/node_modules
+node_modules
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..ebff48c76
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,3 @@
+dist/
+build/
+client/_localeFileMap.ts
diff --git a/.gitignore b/.gitignore
index d03abb4fe..be5587ee9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,11 +1,8 @@
-settings.php
-cache/*
.DS_Store
-.project
-.buildpath
-.settings/*
-.idea*
-resources/themes/classic/.sass-cache/*
-resources/themes/default/.sass-cache/*
-resources/themes/.sass-cache/*
-node_modules*
\ No newline at end of file
+node_modules/*
+.config
+.env
+data/db
+!data/db/nodelete.txt
+.turbo
+.npm
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 000000000..70f6554c7
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+v20.19.4
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 000000000..4a1506530
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "semi": true,
+ "trailingComma": "none",
+ "singleQuote": true,
+ "printWidth": 140
+}
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 1dd1c508c..000000000
--- a/.travis.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-language: php
-php:
- - "5.5"
- - "5.4"
- - "5.3"
-services: mysql
-before_script:
- - mysql -e 'create database generatedata_test;'
-script: phpunit --configuration tests/phpunit_config.xml --coverage-text
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..ea26052d2
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,10 @@
+{
+ "editor.defaultFormatter": "rvest.vs-code-prettier-eslint",
+ "editor.formatOnType": false,
+ "editor.formatOnPaste": false,
+ "editor.formatOnSave": true,
+ "editor.formatOnSaveMode": "file",
+ "files.autoSave": "onFocusChange",
+ "vs-code-prettier-eslint.prettierLast": false,
+ "editor.tabSize": 2
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..8e4227e95
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,254 @@
+## Changelog
+
+- `4.1.9` - Feb 16, 2025
+ - Bug fixes
+ - https://github.com/benkeen/generatedata/milestone/53?closed=1
+- `4.1.8` - Dec 16, 2024
+ - Bug fix
+ - https://github.com/benkeen/generatedata/milestone/52?closed=1
+- `4.1.7` - Dec 14, 2024
+ - Mostly bug fixes and package updates
+ - https://github.com/benkeen/generatedata/milestone/51?closed=1
+- `4.1.6` - Dec 10, 2023
+ - Mexican first and last names added
+ - https://github.com/benkeen/generatedata/milestone/50?closed=1
+- `4.1.5` - Aug 7, 2023
+ - Misc bug fixes, including fix for generating valid PAN numbers (thanks @benjamindonnachie!)
+ - https://github.com/benkeen/generatedata/milestone/49?closed=1
+- `4.1.4` - Jun 20, 2023
+ - Misc bug fixes.
+ - https://github.com/benkeen/generatedata/milestone/48?closed=1
+- `4.1.3` - Mar 2, 2023
+ - Misc bug fixes.
+ - https://github.com/benkeen/generatedata/milestone/47?closed=1
+- `4.1.2` - Mar 1, 2023
+ - Misc bug fixes.
+ - https://github.com/benkeen/generatedata/milestone/46?closed=1
+- `4.1.1` - Feb 25, 2023
+ - Misc bug fixes.
+ - https://github.com/benkeen/generatedata/milestone/45?closed=1
+- `4.1.0` - Feb 14, 2023
+ - Refactored code to accommodate upcoming npm package version (command-line generation).
+ - Moved to Google Identity Services for sign-in process.
+ - Misc bug fixes.
+ - https://github.com/benkeen/generatedata/milestone/43?closed=1
+- `4.0.15` - Nov 26, 2022
+ - Weighted List Data Type added
+ - Misc bug fixes.
+ - https://github.com/benkeen/generatedata/milestone/41?closed=1
+- `4.0.14` - Mar 5, 2022
+ - URLs Data Type added
+ - Ukrainian, Singapore and South Africa country data added
+ - https://github.com/benkeen/generatedata/milestone/40?closed=1
+- `4.0.13` - Mar 3, 2022
+ - Philippines country data added
+ - https://github.com/benkeen/generatedata/milestone/39?closed=1
+- `4.0.12` - Dec 28, 2021
+ - List Data Type now offers a "between" option
+ - Norwegian country data added. Thanks @maddingo!
+ - https://github.com/benkeen/generatedata/milestone/38?closed=1
+- `4.0.11` - Dec 8, 2021
+ - Dutch name data - thanks @rvanraamsdonk!
+ - Misc bug fixes
+ - https://github.com/benkeen/generatedata/milestone/37?closed=1
+- `4.0.10` - Dec 6, 2021
+ - Language selection overhauled
+ - Bug fixes
+ - Turkey name data - thanks @alicanipek!
+ - https://github.com/benkeen/generatedata/milestone/36?closed=1
+- `4.0.9` - Nov 20, 2021
+ - Node updated to 14
+ - German name data - thanks @ntauch!
+ - https://github.com/benkeen/generatedata/milestone/35?closed=1
+- `4.0.8` - Nov 13, 2021
+ - additional bug fix for regions, cities and postal code Data Types throwing errors
+ - Chilean country names added
+ - Admin: status filter and total count added to accounts page
+ - https://github.com/benkeen/generatedata/milestone/34?closed=1
+- `4.0.7` - Nov 11, 2021
+ - bug fix for regions, cities and postal code Data Types throwing errors (didn't fully work)
+ - Fix for error thrown when closing Export Type overlay
+ - https://github.com/benkeen/generatedata/milestone/33?closed=1
+- `4.0.6` - Nov 6, 2021
+ - Regional names added
+ - Email Data Type now lets you target other fields for more realistic data
+ - China country data added
+ - C# now handles auto-increment numeric values better
+ - https://github.com/benkeen/generatedata/milestone/32?closed=1
+- `4.0.5` - Oct 5, 2021
+ - Portuguese translation added
+ - Misc code improvements
+ - https://github.com/benkeen/generatedata/milestone/31?closed=1
+- `4.0.4` - Sept 27, 2021
+ - Hindi locale added
+ - Improvements for small screens
+ - Misc bug fixes
+ - https://github.com/benkeen/generatedata/milestone/30?closed=1
+- `4.0.3` - Sept 22, 2021
+ - Bug fix for CSV Export Type.
+ - https://github.com/benkeen/generatedata/milestone/29?closed=1
+- `4.0.2` - Sept 21, 2021
+ - misc bug fixes, UX improvements
+ - minor dependency updates
+ - https://github.com/benkeen/generatedata/milestone/28?closed=1
+- `4.0.1` - Sept 19, 2021:
+ - localization files now cache-busted
+ - check for is-safari updated.
+- `4.0.0` - Sept 17, 2021:
+ - initial release!
+ - bug fixes
+- `4.0.0-beta-20210911`:
+ - Color Data Type added
+ - Localized date picker components
+ - bug fixes
+- `4.0.0-beta-20210906`:
+ - Date format standardization in code
+ - Better error handling for expired accounts
+ - misc bug fixes
+- `4.0.0-beta-20210903`:
+ - Time Data Type added
+ - misc bug fixes
+- `4.0.0-beta-20210826`:
+ - account searching
+ - misc bug fixes
+- `4.0.0-beta-20210809`:
+ - back in the game! Returning to work on generatedata - misc updates.
+ - Last logged in col on accounts page
+ - Expiry date added to accounts page
+ - Fix for TextFixed Data Type
+- `4.0.0-alpha-20210608`:
+ - minor authentication + password reset bug fixes.
+- `4.0.0-alpha-20210607`:
+ - tons of updates & fixes - too many to note! UI updates, database changes, performances fixes and more.
+- `4.0.0-alpha-20210429`:
+ - preview panel scrolling fix.
+ - List Data Type now allows customizable delimiter.
+- `4.0.0-alpha-20210424`:
+ - Safari error page.
+- `4.0.0-alpha-20210418`:
+ - Email support added.
+ - Forget password email; expiry emails.
+- `4.0.0-alpha-20210305`:
+ - Track1 Data Type added.
+- `4.0.0-alpha-20210301`:
+ - Credit Card PAN Data Type added.
+- `4.0.0-alpha-20210224`:
+ - Assorted lib updates.
+- `4.0.0-alpha-20210223`:
+ - Improved generation settings panel.
+ - i18n fixes.
+- `4.0.0-alpha-20201111`:
+ - Chilean RUT number, PIN and CVV Data Types added.
+ - Fixed/Random Number of Words Data Types expanded to allow providing your own text as word source.
+- `4.0.0-alpha-20201108`:
+ - CSV, LDIF Export Types added.
+ - All Data Types and Export Types translated.
+- `4.0.0-alpha-20201104`:
+ - Computed and Composite Data Types combined.
+ - Names Data Type options structure change.
+ - Fix for help dialog not resetting search text
+ - version now links to changelog
+- `4.0.0-alpha-20201102`:
+ - all 9 languages now available to toggle between.
+ - "Clear Page" modal now lets you either just clear the grid, or reset everything (all plugins) to their default
+ settings
+- `4.0.0-alpha-20201101`:
+ - initial functional alpha. Data now generates!
+- `3.4.1` - Nov 24, 2019
+ - Excel Export Type updated for new PHP lib, thanks [@adibaby](https://github.com/adibaby)!
+ - Bug fix for SocialSecurityNumber, courtesy of [@guzzisti](https://github.com/guzzisti).
+ - https://github.com/benkeen/generatedata/milestone/26?closed=1
+- `3.4.0` - Nov 16, 2019
+ - Misc updates,
+ - new inject SQL feature added. Great work, [@harish81](https://github.com/harish81)!
+ - https://github.com/benkeen/generatedata/milestone/25?closed=1
+- `3.3.1` - July 18, 2019
+ - https://github.com/benkeen/generatedata/milestone/23?closed=1
+- `3.3.0` - July 1, 2019
+ - misc bug fixes
+- `3.2.8` - Sep 12, 2017
+ - misc bug fixes
+- `3.2.7` - Jul 29, 2017
+ - "Computed" Data Type added.
+ - misc bug fixes
+- `3.2.6` - Apr 17, 2017
+ - misc bug fixes: https://github.com/benkeen/generatedata/milestone/20?closed=1
+- `3.2.5` - Apr 16, 2016
+ - bug fixes: https://github.com/benkeen/generatedata/issues?utf8=%E2%9C%93&q=milestone%3A3.2.3+ - thanks for your
+ help, [Conrad Hagemans](https://github.com/conradhagemans)!
+ - "Precision" option added to Normal Distribution Data Type - thanks [@aevans84](https://github.com/aevans84).
+ - generation of complex JSON structures added by [Tony OHagan](https://github.com/tohagan).
+ See: https://github.com/benkeen/generatedata/tree/master/plugins/exportTypes/JSON#generating-complex-objects
+- `3.2.4` - Dec 6, 2015
+ - patch release for per-user settings.
+- `3.2.3` - Nov 15, 2015
+ - SIRET/SIREN Data Type added (French business numbers) added.
+ Merci, [Fabrice Marquès](https://github.com/fmarques56)!
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?utf8=%E2%9C%93&q=milestone%3A3.2.3+
+- `3.2.2` - Nov 12, 2015
+ - The plugins (Data Types, Export Types, Countries) seen in the interface may not be configured on a per-user level.
+ - Installation script updated to allow customization of plugin selection.
+- `3.2.1` - May 25, 2015
+ - Configuration history option added to store the last 200 (this is configurable) versions of a data set. In case of
+ data loss, you can now revert to an older version very simply.
+ - Assorted bug fixes, including some improvements to the installation script.
+- `3.2.0` - Jan 29, 2015
+ - Adds a new REST API as an alternative way to generate data. See
+ the [API Documentation](http://benkeen.github.io/generatedata/api.html) for more information.
+- `3.1.4` - Sept 6, 2014
+ - Chinese language file added, thanks to [Zhao Yang](https://github.com/jptiancai)
+ - PAN, Track 1 and Track 2 data type updates, courtesy of Zeeshan Shaikh
+ - Turkey Country plugin added
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?q=milestone%3A3.1.4+is%3Aclosed
+- `3.1.3` - July 20, 2014
+ - Misc data generation efficiency improvements
+ - Batch Size SQL export option added by [Anton Nizhegorodov](https://github.com/an1zhegorodov)
+ - Poland, Nigeria Country plugins added
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=13&page=1&state=closed
+- `3.1.2` - July 12, 2014
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=12&page=1&state=closed
+- `3.1.1` - Jan 31, 2014
+ - New credit card data types: PAN, PIN, CVV, Track 1 and Track 2 courtesy of Zeeshan Shaikh
+ - INSERT IGNORE option added to the SQL Export Type, thanks to [Ap.Mathu](https://github.com/apmuthu)
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=11&page=1&state=closed
+- `3.1.0` - Dec 19, 2013
+ - Bug fix for accidental short-tags that were introduced in earlier code
+- `3.0.9` - Dec 11, 2013
+ - Compression option added to reduce download sizes, courtesy of [Manu Ullas](https://github.com/unullmass) -
+ thanks!
+ - New credit card Data Type, thanks to [rsicher1](https://github.com/rsicher1)
+ - You can now make copies of Data Sets, via the main dialog window. Just check a single row and click "Copy Data
+ Set" button.
+ - CodeMirror updated to v3.2.0
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=9&page=1&state=closed
+- `3.0.8` - Oct 28, 2013
+ - International Bank Numbers - thanks, Joeri Noort!
+ - PostgreSQL database support added to SQL Export Type
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=8&page=1&state=closed
+- `3.0.7` - Sept 7, 2013
+ - LDIF Export Type support - thanks, [Marco Corona](https://github.com/coronam)!
+ - Proper (genuine!) French translation courtesy of [Michel Roca](https://github.com/mRoca)
+ - Optional JS, CSS minimization and bundling via Grunt. See help documentation for more information:
+ [http://benkeen.github.io/generatedata/developer.html#bundling](http://benkeen.github.io/generatedata/developer.html#bundling)
+ - PHP 5.5 compatibility fixes: database connection now with mysqli; Generator class renamed to DataGenerator due to
+ naming conflict
+ - Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=7&page=1&state=closed
+- `3.0.6` - Aug 1, 2013
+ - Costa Rica Country plugin, Phone-Regional Data Type added, courtesy of [Andre Fortin](https://github.com/twindual)
+ - bug fixes, see: https://github.com/benkeen/generatedata/issues?milestone=6&page=1&state=closed
+- `3.0.5` - July 13, 2013
+ - Currency Data Type added
+ - Assorted bug fixes, see: https://github.com/benkeen/generatedata/issues?milestone=5&page=1&state=closed
+- `3.0.4` - July 2nd, 2013
+ - Italy Export Type, courtesy of [Marcello Verona](https://github.com/marciuz)
+ - Regional Names Data Type added, data for Italy and France from [Marcello Verona](https://github.com/marciuz)
+- `3.0.3` - June 23, 2013
+ - Bug fixes. See: https://github.com/benkeen/generatedata/issues?milestone=3
+- `3.0.2` - June 12, 2013
+ - Spanish translation and Country plugin added (thanks, [@robarago](https://github.com/robarago)!)
+ - bug fixes, other updates: https://github.com/benkeen/generatedata/issues?milestone=2&state=closed
+- `3.0.1` - June 1st, 2013
+ - MSSQL support added (thanks, [Kent](https://github.com/kchenery)!)
+ - Assorted bug fixes / updates. See: https://github.com/benkeen/generatedata/issues?milestone=1&state=closed
+- `3.0.0` - May 21st, 2013
+ - Initial release
diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
new file mode 100644
index 000000000..70a65e7f4
--- /dev/null
+++ b/DEVELOPMENT.md
@@ -0,0 +1,93 @@
+# Development
+
+Version 4 of generatedata uses Docker to simplify packaging up the app for development and distribution. Docker
+wasn't quite the wonder that I hoped it would be, but the benefits overall are undeniable.
+
+I experimented with getting the dev environment running _entirely_ within Docker containers so you wouldn't require
+to install anything locally, but I found it was simply too slow to be of practical use as a dev env. So instead, the
+local dev env just uses docker containers for the _server and database_; the FE code is still ran locally. I know that's
+a pain for non-frontend developers especially who aren't so familiar with setting up NVM, Grunt etc., but it's a
+trade-off I had to make.
+
+#### Pre-requisites:
+
+- [Docker](https://docs.docker.com/get-docker/)
+- [NVM](https://github.com/nvm-sh/nvm#installing-and-updating) - namely node 12.
+- Grunt CLI (`npm install grunt-cli -g`)
+
+### Running dev environment
+
+- `git clone https://github.com/benkeen/generatedata.git` - this clones the repo to your local computer. On Mac, I'd
+ suggest putting it in your `~` folder; I tried it in other locations but Docker ran into permissions issues.
+
+- `nvm install`
+
+ - assuming your have NVM installed (see above), this'll choose the right node version. If not, just choose the
+ right node version specified in the `.nvmrc` file. If you're not running the correct version of node it will
+ throw an error during startup.
+
+- `pnpm install`
+- After starting Docker, in one tab run: `npm run startAndBuildDevServer` - this boots up the server + database containers.
+ For subsequent runs you can just use `npm run startDevServer` and it'll be faster.
+- In a second tab, `npm run start` - this boots up the client-side code. Be warned: this does a _LOT_ of stuff and the
+ first time you run it it'll take a very long time to run.
+
+After running the second command it should open up `http://localhost:9000` in your browser.
+
+#### Shutting down dev env
+
+`npm run stopDevServer` - shuts down docker.
+
+I've found that sometimes that command chokes and you have to wait a few minutes before it runs properly. Presumably
+it's because the docker container was still in the process of booting up. If there are still problems, you might want to just run
+`npm run dockerCleanup`. I do this on the live server every time I update it. It completely clears everything out so you
+can start from scratch. It WON'T, however, delete your
+
+### Troubleshooting
+
+> ERROR: for db Cannot start service db: error while creating mount source path '/host_mnt/xxx/data/db': mkdir /host_mnt/Users/xxx/data/db: no such file or directory
+> ERROR: Encountered errors while bringing up the project.
+
+Restarting Docker seemed to fix this. I did that via the UI tool.
+
+## Locale file helpers
+
+There are a several grunt helper functions for validation and managing the locale files. It's important to keep the files
+up to date so every i18n file contains the same keys and won't cause bugs when the user selects the language.
+
+#### Core localization files
+
+These are found in `src/i18n`. They contain all the core i18n files.
+
+- `grunt validateI18n` - general validation function to examine all the localization files and check everything in sync.
+- `grunt validateI18n --key=fr` - same as above, except it only looks at a particular locale file.
+- `grunt removeI18nKey --key=xxx` - where xxx is the property name.
+- `grunt sortI18nFiles` - sorts the keys of all i18n files alphabetically.
+
+### Text rules:
+
+- titles, headings: capitalize every letter
+- tooltips: sentence case, no ending period
+
+### Building
+
+Local dev, general steps:
+
+- `npm run start` - builds and rubs the client-side code
+- `npm run startDevServer` - starts the dev server
+
+### Common problems
+
+#### Logging in with Google works but logs out when page is refreshed
+
+If you find that after logging in with Google it gets lost after refreshing the page, check your system clock. The
+OAuth2Client lib we're using uses the system clock when re-validating the google auth info. My own computer locally
+(an 2017 Mac) when I leave it on for too long the time gets very out of whack, causing this problem. Restarting the
+computer (which restarts the clock) fixes it.
+
+#### M1 mac
+
+After upgrading to Ventura, I found I had problems running Docker.
+
+To fix it I enabled "Use Rosetta for x86/amd64 emulation on Apple Silicon" in Docker Desktop and added a
+`export DOCKER_DEFAULT_PLATFORM=linux/amd64` env variable, then wiped out all existing docker containers and started afresh.
diff --git a/README.md b/README.md
index f10cbb88d..55767f38a 100644
--- a/README.md
+++ b/README.md
@@ -1,105 +1,43 @@
# generatedata.com
-[](https://travis-ci.org/benkeen/generatedata)
-
-This is the repo for the standalone, downloadable version of [generatedata.com](http://www.generatedata.com).
-
-Generally the trunk is pretty stable, but it's never guaranteed. If you're downloading the code, I'd suggest getting the most recent tag: https://github.com/benkeen/generatedata/releases
+This is the repo for the downloadable version of [generatedata.com](https://generatedata.com). The script is essentially
+an _engine_ to generate any sort of random data in any format. It currently comes with 30 or
+so _Data Types_ (types of data it generates), 12 _Export Types_ (formats for the data, like CSV, SQL, JSON), plus
+around 32 data sets for specific countries (city names, regions etc). But more importantly it can be extended in any
+way you want. Check out the [developer documentation](https://benkeen.github.io/generatedata/developerdoc/intro/) for more
+information on that.
+
+### Programmatic generation
+
+The current major version of the script is 4.x, which was a big change over earlier releases. Earlier versions were written
+in PHP and MYSQL and 3.x offered a REST API to let you generate the data programmatically rather than via an API. While this is
+still planned for 4.x it's not currently offered, and the plan it to tackle that _after_ making the script available
+as an npm package - which we feel will be a more convenient way to programmatically generate data over the more agnostic
+REST approach.
+`
## Requirements
-- PHP 5.3 or later
-- MySQL 4.1.3 or later
-## How to Install / Documentation
+- Docker
+- node
+- nvm
-For the installation instructions, user documentation and developer documentation, check out:
-http://benkeen.github.io/generatedata/
+See the [Installation instructions](https://benkeen.github.io/generatedata/userdoc/installation/intro) for full details.
-Installation is really, really simple. I deliberately wrote the script to be as self-contained as possible and not require
-additional PHP/Server configuration when setting it up. That said, it *does* require PHP 5.3.0 or later. See the documentation
-for more info.
-
-## Test Coverage
+## How to Install / Documentation
-Test coverage is pretty weak right now! I'm in the midst of adding phpunit tests and integrating it with Travis, but it's going to be a little hairy for a while just yet.
+For installation instructions, user and developer documentation, check out:
+https://benkeen.github.io/generatedata/
## License
-This script is freely available under the GPL 3 license. See license.txt in the root folder. Please note that all contributors agree that all code is released under this license.
+This script is freely available under the GPL 3 license. See license.txt in the root folder. Please note that all
+contributors agree that all code is released under this license.
## Contributors
-In addition to the many folks who submit bug reports, a big thanks to the following for their help extending the script:
-
-- Zeeshan Shaikh - PAN, PIN, CVV, Track 1 and 2 Data Types (3.1.1)
-- [Ap.Mathu](https://github.com/apmuthu) - SQL Export Type updates (INSERT IGNORE)
-- [Manu Ullas](https://github.com/unullmass) - compression option for downloads (3.0.9)
-- [rsicher1](https://github.com/rsicher1) - credit card Data Type (3.0.9)
-- Joeri Noort - IBAN numbers (3.0.8)
-- [Michel Roca](https://github.com/mRoca) - Full and correct French translation (3.0.7)
-- Marco Corona - LDIF Export Type added (3.0.7)
-- [Andre Fortin](https://github.com/twindual) - original Costa Rica Country plugin & Phone-Regional Data Type (3.0.6)
-- [Marcello Verona](https://github.com/marciuz) - Italy Country plugin (3.0.4)
-- [Roberto Aragón](https://github.com/robarago), Charo Baena - Spanish translation & Country plugin (3.0.2)
-- [Kent Chenery](https://github.com/kchenery) - MS SQL plugin (3.0.1)
-
-## Changelog
-
-3.1.1 - Jan 31, 2014
-- New credit card data types: PAN, PIN, CVV, Track 1 and Track 2 courtesy of Zeeshan Shaikh.
-- INSERT IGNORE option added to the SQL Export Type, thanks to [Ap.Mathu](https://github.com/apmuthu)
-- Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=11&page=1&state=closed
-
-3.1.0 - Dec 19, 2013
-- Bug fix for accidental short-tags that were introduced in earlier code
-
-3.0.9 - Dec 11, 2013
-- Compression option added to reduce download sizes, courtesy of [Manu Ullas](https://github.com/unullmass) - thanks!
-- New credit card Data Type, thanks to [rsicher1](https://github.com/rsicher1)
-- You can now make copies of Data Sets, via the main dialog window. Just check a single row and click "Copy Data Set" button.
-- CodeMirror updated to v3.2.0
-- Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=9&page=1&state=closed
-
-3.0.8 - Oct 28, 2013
-- International Bank Numbers - thanks, Joeri Noort!
-- PostgreSQL database support added to SQL Export Type
-- Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=8&page=1&state=closed
-
-3.0.7 - Sept 7, 2013
-- LDIF Export Type support - thanks, [Marco Corona](https://github.com/coronam)!
-- Proper (genuine!) French translation courtesy of [Michel Roca](https://github.com/mRoca)
-- Optional JS, CSS minimization and bundling via Grunt. See help documentation for more information:
-[http://benkeen.github.io/generatedata/developer.html#bundling](http://benkeen.github.io/generatedata/developer.html#bundling)
-- PHP 5.5 compatibility fixes: database connection now with mysqli; Generator class renamed to DataGenerator due
-to naming conflict.
-- Bug fixes: https://github.com/benkeen/generatedata/issues?milestone=7&page=1&state=closed
-
-3.0.6 - Aug 1, 2013
-- Costa Rica Country plugin, Phone-Regional Data Type added, courtesy of [Andre Fortin](https://github.com/twindual)
-- bug fixes, see: https://github.com/benkeen/generatedata/issues?milestone=6&page=1&state=closed
-
-3.0.5 - July 13, 2013
-- Currency Data Type added.
-- Assorted bug fixes, see: https://github.com/benkeen/generatedata/issues?milestone=5&page=1&state=closed
-
-3.0.4 - July 2nd, 2013
-- Italy Export Type, courtesy of [Marcello Verona](https://github.com/marciuz)
-- Regional Names Data Type added, data for Italy and France from [Marcello Verona](https://github.com/marciuz)
-
-3.0.3 - June 23, 2013
-- Bug fixes. See: https://github.com/benkeen/generatedata/issues?milestone=3
-
-3.0.2 - June 12, 2013
-- Spanish translation and Country plugin added (thanks, [@robarago](https://github.com/robarago)!)
-- bug fixes, other updates: https://github.com/benkeen/generatedata/issues?milestone=2&state=closed
-
-3.0.1 - June 1st, 2013
-- MSSQL support added (thanks, [Kent](https://github.com/kchenery)!)
-- Assorted bug fixes / updates. See: https://github.com/benkeen/generatedata/issues?milestone=1&state=closed
-
-3.0.0 - May 21st, 2013
-- Initial release.
-
+In addition to the many fine folk who submit bug reports, a big thanks to the following for their help extending the script:
+https://github.com/benkeen/generatedata/graphs/contributors
Ben Keen
-[@vancouverben](https://twitter.com/#!/vancouverben)
+https://github.com/benkeen
diff --git a/V5_CHANGES.md b/V5_CHANGES.md
new file mode 100644
index 000000000..5183ca892
--- /dev/null
+++ b/V5_CHANGES.md
@@ -0,0 +1,39 @@
+# 5.x Changes
+
+This file tracks the changes in 5.x. I'll keep this updated as I progress.
+
+## High-level changes
+
+The repo was a bit of a mess architecturally. Code was being referenced all over the place - env vars were read by FE and BE code, shared code was sometimes duplicated and there was a ton of complex logic around building the app and dynamically generating files out of plugins and so on and so forth. Basically it had grown into a bit of a ball of mud. The 4.x version did tons of great stuff, but didn't really choose a sane architecture.
+
+Version 5.x is devoted to a _re-architecture only_ - I'm not changing any UI or functionality. I know - _booooring_. The biggest change is that **I'm converted to a standard monorepo using [Turborepo](https://turborepo.com/)**. I haven't used it much before but it seems like a usable and sensible tool. There won't be any fundamental changes to any of the technologies beyond various version updates. It'll still use Docker (sigh, I hate Docker), React, Redux, TS etc. but the structure of the code will be quite different.
+
+The chief motivation for this major version bump that whenever I return to work on this project to complete the unfinished CLI, I find myself stymied by the lack of a formal architecture and run into problem and problem trying to create the CLI in a clean standalone format. Also, since I the simplicity of PHP / MySQL / MAMP/WAMP etc. it became far harder for devs to actually set up and use. That's a real pity. I don't think I can get around the fact that Docker is far more complex but I can at least make the on-ramp easier.
+
+## Packages
+
+Logical units of the code are now going to be split into standard _npm packages_, found under `apps` and `packages`.
+
+**Apps**
+
+- `apps/client` - the main client-side app.
+- `apps/server` - the server-side app. I'd LOVE to actually convert this to TS and not continue to use plain wild-west JS, but I don't want to bloat the work, so will probably punt on it until a later version. We'll see.
+
+**Packages**
+
+- `packages/cli` - this was the never-completed CLI package. Still **definitely** something I want to complete!
+- `packages/cli-test` - testing for the CLI package. This might be temporary. Not sure it needs to be separate from `cli` itself.
+- `packages/config` - this'll house the main configuration settings and replace the old `.env` file. For simplicity and backward compatibility, I've left the same uppercase names from the old .env variables.
+- `packages/plugins` - the countries, Data Types and Export Types. Perhaps I'll split them into separate packages for each, but for now they're lumped in the same package.
+- `packages/types` - global types.
+
+## Other changes
+
+- moved to pnpm
+
+## Bootstrap process for new clones
+
+(Need instructions):
+
+- install turborepo CLI, nvm, pnpm globals
+- `pnpm install` - this bootstraps the whole repo
diff --git a/ajax.php b/ajax.php
deleted file mode 100755
index 0a2e6cf8d..000000000
--- a/ajax.php
+++ /dev/null
@@ -1,43 +0,0 @@
-getResponse());
-$errorCode = json_last_error();
-if ($errorCode) {
- switch ($errorCode) {
- case JSON_ERROR_NONE:
- echo ' - No errors';
- break;
- case JSON_ERROR_DEPTH:
- echo ' - Maximum stack depth exceeded';
- break;
- case JSON_ERROR_STATE_MISMATCH:
- echo ' - Underflow or the modes mismatch';
- break;
- case JSON_ERROR_CTRL_CHAR:
- echo ' - Unexpected control character found';
- break;
- case JSON_ERROR_SYNTAX:
- echo ' - Syntax error, malformed JSON';
- break;
- case JSON_ERROR_UTF8:
- echo ' - Malformed UTF-8 characters, possibly incorrectly encoded';
- break;
- default:
- echo ' - Unknown error';
- break;
- }
-} else {
- echo $encoded;
-}
diff --git a/apps/client/.babelrc b/apps/client/.babelrc
new file mode 100644
index 000000000..801c900bf
--- /dev/null
+++ b/apps/client/.babelrc
@@ -0,0 +1,29 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "targets": {
+ "esmodules": true
+ }
+ }
+ ],
+ [
+ "@babel/preset-react", {}
+ ]
+ ],
+ "plugins": [
+ "@babel/plugin-syntax-dynamic-import"
+ ],
+ "env": {
+ "BUILD": {
+ "comments": true
+ },
+ "DEV": {
+ "comments": true
+ },
+ "DIST": {
+ "comments": true
+ }
+ }
+}
diff --git a/apps/client/.eslintrc.js b/apps/client/.eslintrc.js
new file mode 100644
index 000000000..d1c6a3b5f
--- /dev/null
+++ b/apps/client/.eslintrc.js
@@ -0,0 +1,64 @@
+module.exports = {
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ ecmaVersion: 6,
+ sourceType: 'module',
+ ecmaFeatures: {
+ jsx: true,
+ experimentalObjectRestSpread: true
+ }
+ },
+ env: {
+ browser: true,
+ es6: true,
+ node: true
+ },
+ plugins: ['react'],
+ extends: ['plugin:react/recommended', 'plugin:@typescript-eslint/recommended'],
+ rules: {
+ semi: [2, 'always'],
+ indent: [
+ 'error',
+ 'tab',
+ {
+ SwitchCase: 1
+ }
+ ],
+ 'max-len': [1, { code: 140 }],
+ // '@stylistic/js/indent/max-len': ['warning', 140],
+ 'object-curly-spacing': ['error', 'always'],
+ allowIndentationTabs: 0,
+ 'no-extra-parens': ['off'],
+ 'no-multi-spaces': 'error',
+ 'react/prop-types': ['off'],
+ 'comma-dangle': 'off',
+ 'no-tabs': 'off',
+ 'no-multiple-empty-lines': 'off',
+ 'no-plusplus': 'off',
+ 'import/no-unresolved': 'off',
+ 'arrow-body-style': 'off',
+ 'import/extensions': 'off',
+ 'import/prefer-default-export': 'off',
+ 'lines-between-class-members': 'off',
+ 'object-curly-newline': 'off',
+ quotes: ['error', 'single'],
+ 'import/no-mutable-exports': 'off',
+ 'react/no-unused-prop-types': 'off',
+ 'react/no-unescaped-entities': 'off',
+ 'react/jsx-indent': [2, 'tab'],
+ 'react/jsx-indent-props': [2, 'tab'],
+ '@typescript-eslint/no-empty-function': 'off',
+ '@typescript-eslint/ban-ts-ignore': 'off',
+ '@typescript-eslint/interface-name-prefix': 'off',
+ '@typescript-eslint/no-use-before-define': 'off',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-var-requires': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'off',
+ '@typescript-eslint/ban-ts-comment': 'off'
+ },
+ settings: {
+ react: {
+ version: '16.13.1'
+ }
+ }
+};
diff --git a/apps/client/.gitignore b/apps/client/.gitignore
new file mode 100644
index 000000000..032880991
--- /dev/null
+++ b/apps/client/.gitignore
@@ -0,0 +1,4 @@
+_env.ts
+dist/*
+node_modules/*
+.turbo
\ No newline at end of file
diff --git a/apps/client/_localeFileMap.ts b/apps/client/_localeFileMap.ts
new file mode 100644
index 000000000..b0413105d
--- /dev/null
+++ b/apps/client/_localeFileMap.ts
@@ -0,0 +1,17 @@
+/* eslint quotes:0 */
+import { GDLocaleMap } from '@generatedata/types';
+
+export const localeFileMap: GDLocaleMap = {
+ "ar": "ar-082d52b0.js",
+ "de": "de-9c5c6e88.js",
+ "en": "en-ec98c99d.js",
+ "es": "es-d2dc0ab7.js",
+ "fr": "fr-76831d50.js",
+ "hi": "hi-dd5691a1.js",
+ "ja": "ja-79d51d8b.js",
+ "nl": "nl-1499548d.js",
+ "pt": "pt-c7796933.js",
+ "ru": "ru-32bc90c7.js",
+ "ta": "ta-9e10d34d.js",
+ "zh": "zh-20134d2b.js"
+};
\ No newline at end of file
diff --git a/apps/client/_pluginWebWorkers.ts b/apps/client/_pluginWebWorkers.ts
new file mode 100644
index 000000000..549f6840d
--- /dev/null
+++ b/apps/client/_pluginWebWorkers.ts
@@ -0,0 +1,7 @@
+/* eslint quotes:0 */
+export default {
+ "generationWorker": "generation.worker-1dc7caad6d37493ad34aaebb76baa983.js",
+ "workerUtils": "workerUtils-d87db21604e56391c5d7def129452a61.js",
+ "dataTypes": {},
+ "exportTypes": {}
+};
\ No newline at end of file
diff --git a/apps/client/build/build.js b/apps/client/build/build.js
new file mode 100644
index 000000000..9f3407344
--- /dev/null
+++ b/apps/client/build/build.js
@@ -0,0 +1,100 @@
+// TODO remove this file once everything is finalized
+const fs = require('fs');
+const path = require('path');
+const helpers = require('./helpers');
+
+if (result.error) {
+ console.error("\nMissing .env file. Please see the documentation about setting up your environment.\n");
+ return;
+}
+
+const banner = `/**
+ * This file is autogenerated. Do not edit!
+ * ----------------------------------------
+ **/`;
+
+// TODO move to CLI package
+// const createCliTypesFile = () => {
+// let content = banner + '\n\nimport { DataType, ExportType } from \'../../../client/_plugins\';\n';
+
+// const blacklistedDataTypes = process.env.GD_DATA_TYPE_BLACKLIST.split(',');
+// const dataTypes = helpers.getPlugins('dataTypes', []);
+// const dtList = dataTypes.filter((dt) => blacklistedDataTypes.indexOf(dt) === -1); // TODO can this be in the prev lines, second param?
+
+// dtList.forEach((dt) => {
+// content += `import { generate as ${dt}G } from '../../../client/src/plugins/dataTypes/${dt}/${dt}.generate';\n`
+// content += `import { defaultGenerationOptions as ${dt}DGO } from '../../../client/src/plugins/dataTypes/${dt}/${dt}.state';\n`
+// });
+
+// content += `\n\nexport const dataTypeNodeData = {\n`;
+// const rows = dtList.map((dt) => `\t[DataType.${dt}]: { generate: ${dt}G, defaultGenerationOptions: ${dt}DGO }`);
+// content += `${rows.join(',\n')}\n};\n\n`
+
+// const blacklistedExportTypes = process.env.GD_EXPORT_TYPE_BLACKLIST.split(',');
+// const etList = helpers.getPlugins('exportTypes', blacklistedExportTypes);
+
+// etList.forEach((et) => {
+// content += `import { generate as ${et}G } from '../../../client/src/plugins/exportTypes/${et}/${et}.generate';\n`
+// content += `import { defaultGenerationOptions as ${et}DGO } from '../../../client/src/plugins/exportTypes/${et}/${et}.state';\n`
+// });
+
+// content += `\n\nexport const exportTypeNodeData = {\n`;
+// const etRows = etList.map((et) => `\t[ExportType.${et}]: { generate: ${et}G, defaultGenerationOptions: ${et}DGO }`);
+// content += `${etRows.join(',\n')}\n};\n\n`
+
+// const file = path.join(__dirname, '../../../packages/cli/src', '_cliTypes.ts'); // TODO
+// if (fs.existsSync(file)) {
+// fs.unlinkSync(file);
+// }
+// fs.writeFileSync(file, content);
+// };
+
+
+// TODO move this to the website code. Until it's needed for the core script; dump it.
+// const createImportFile = () => {
+// const importLines = [];
+// const files = process.env.GD_IMPORT_FILES;
+
+// if (files) {
+// files.split(',').forEach((filePathFromRoot) => {
+// importLines.push(`import '../${filePathFromRoot}';`);
+// });
+// }
+
+// const file = path.join(__dirname, '..', '_imports.ts');
+// if (fs.existsSync(file)) {
+// fs.unlinkSync(file);
+// }
+
+// // rollup gets confused with an empty file, so we add a default exports just in case
+// if (!importLines.length) {
+// importLines.push(`// DO NOT EDIT: This is autogenerated by a node script \nexport default {};`);
+// }
+// fs.writeFileSync(file, importLines.join('\n'));
+// };
+
+
+// const generateNamesFile = () => {
+// const namePlugins = helpers.getNamePlugins();
+
+// let content = banner + '\n\n';
+// namePlugins.forEach((folder) => {
+// content += `import ${folder} from './src/plugins/countries/${folder}/names';\n`;
+// });
+
+// content += `\nconst nameFiles = {\n\t${namePlugins.join(',\n\t')}\n};`;
+// content += `\nexport default nameFiles;\n`;
+// content += '\nexport type CountryNameFiles = keyof typeof nameFiles;\n';
+
+// const file = path.join(__dirname, '..', '_namePlugins.ts');
+// if (fs.existsSync(file)) {
+// fs.unlinkSync(file);
+// }
+// fs.writeFileSync(file, content);
+// };
+
+// generateEnvFile('_env.ts', JSON.stringify(envFile, null, '\t'));
+// generateNamesFile();
+
+// createCliTypesFile();
+// createImportFile();
diff --git a/apps/client/build/helpers.js b/apps/client/build/helpers.js
new file mode 100644
index 000000000..8a0a6b14c
--- /dev/null
+++ b/apps/client/build/helpers.js
@@ -0,0 +1,97 @@
+const fs = require('fs');
+const md5File = require('md5-file');
+const path = require('path');
+
+const getPlugins = (pluginType, blacklist, checkConfigFileExistence = true) => {
+ console.log(require.resolve(`@generatedata/plugins/dist/plugins/${pluginType}`));
+
+ // const baseFolder = path.join(__dirname, '..', `/src/plugins/${pluginType}`);
+ // const folders = fs.readdirSync(baseFolder);
+
+ // return folders.filter((folder) => {
+ // if (blacklist.indexOf(folder) !== -1) {
+ // return false;
+ // }
+ // const bundle = `${baseFolder}/${folder}/bundle.ts`;
+ // const config = `${baseFolder}/${folder}/config.ts`;
+ // if (checkConfigFileExistence) {
+ // return fs.existsSync(bundle) && fs.existsSync(config);
+ // } else {
+ // return fs.existsSync(bundle);
+ // }
+ // });
+};
+
+// const getNamePlugins = () => {
+// const baseFolder = path.join(__dirname, '..', `/src/plugins/countries`);
+
+// const folders = fs.readdirSync(baseFolder);
+
+// return folders.filter((folder) => {
+// const nameFile = `${baseFolder}/${folder}/names.ts`;
+// return fs.existsSync(nameFile);
+// });
+// };
+
+const getHashFilename = (target) => `__hash-${path.basename(target, path.extname(target))}`;
+
+const generateWorkerHashfile = (src, folder) => {
+ const hashFilename = getHashFilename(src);
+ const fileHash = md5File.sync(`${folder}/${src}`);
+ const fileWithPath = `${folder}/${hashFilename}`;
+
+ if (fs.existsSync(fileWithPath)) {
+ fs.unlinkSync(fileWithPath);
+ }
+ fs.writeFileSync(fileWithPath, fileHash);
+};
+
+/**
+ * Get a new scoped filename for a web worker plugin file (Export Type, Data Type, Country).
+ * @param file - the full path including filename or just the filename
+ * @param workerType - "country", "dataType" or "exportType
+ */
+const getScopedWorkerFilename = (file, workerType) => {
+ let fileWithoutExt = path.basename(file, path.extname(file));
+
+ let prefix = '';
+ if (workerType === 'dataType') {
+ prefix = 'DT-';
+ } else if (workerType === 'exportType') {
+ prefix = 'ET-';
+ } else if (workerType === 'country') {
+ prefix = 'C-';
+ const folder = path.dirname(file).split(path.sep);
+ fileWithoutExt = folder[folder.length-1];
+ }
+
+ return `${prefix}${fileWithoutExt}.js`;
+};
+
+const hasWorkerFileChanged = (filename, hashFile) => {
+ let hasChanged = true;
+
+ if (fs.existsSync(hashFile) && fs.existsSync(filename)) {
+ const hash = fs.readFileSync(hashFile, 'utf8');
+
+ if (md5File.sync(filename) === hash) {
+ hasChanged = false;
+ }
+ }
+
+ return hasChanged;
+};
+
+// returns all the items in arr1 that are not in arr2
+const arrayDiff = (arr1, arr2) => arr1.filter((a) => arr2.indexOf(a) === -1);
+
+
+module.exports = {
+ getPlugins,
+ // getNamePlugins,
+ getHashFilename,
+ generateWorkerHashfile,
+ getScopedWorkerFilename,
+ hasWorkerFileChanged,
+ arrayDiff
+};
diff --git a/apps/client/build/i18n.js b/apps/client/build/i18n.js
new file mode 100644
index 000000000..f1b9a889d
--- /dev/null
+++ b/apps/client/build/i18n.js
@@ -0,0 +1,237 @@
+const fs = require('fs');
+const path = require('path');
+const helpers = require('./helpers');
+
+const result = require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
+if (result.error) {
+ return;
+}
+
+const locales = process.env.GD_LOCALES.split(',');
+
+const getCoreLocaleFileStrings = (locale) => {
+ return require(getCoreLocaleFilePath(locale));
+};
+
+const findMissingStrings = (stringsByLocale, targetLocale = null, baseLocale = 'en') => {
+ const locales = Object.keys(stringsByLocale);
+ const results = [];
+
+ const baseLocaleKeys = Object.keys(stringsByLocale[baseLocale]);
+ locales.forEach((locale) => {
+ if (targetLocale && targetLocale !== locale) {
+ return;
+ }
+
+ const targetLocaleKeys = Object.keys(stringsByLocale[locale]);
+
+ // missing from source file
+ const missing = helpers.arrayDiff(baseLocaleKeys, targetLocaleKeys);
+ missing.forEach((key) => {
+ results.push({ key, locale });
+ });
+
+ // extra ones in locale file
+ const extra = helpers.arrayDiff(targetLocaleKeys, baseLocaleKeys);
+ extra.forEach((key) => {
+ results.push({ key, locale, isExtra: true });
+ });
+ });
+
+ return results;
+};
+
+const findStringsInDataTypeEnFileMissingFromOtherLangFiles = (results, dataType, stringsByLocale) => {
+ const langs = Object.keys(stringsByLocale);
+
+ let count = 0;
+ results.lines.push(`\nEnglish strings missing from other lang files:\n-------------------------------------------`);
+ Object.keys(stringsByLocale['en']).forEach((key) => {
+ const missing = [];
+ langs.forEach((locale) => {
+ if (targetLocale && targetLocale !== locale) {
+ return;
+ }
+
+ if (!stringsByLocale[locale][key]) {
+ missing.push(locale);
+ }
+ });
+ if (missing.length > 0) {
+ count++;
+ results.lines.push(`${key}\n -missing from: ${missing.join(', ')}`);
+ }
+ });
+
+ if (count > 0) {
+ results.error = true;
+ results.lines.push(`-- MISSING ${count}`);
+ } else {
+ results.lines.push('All good!\n');
+ }
+
+ return results;
+};
+
+
+
+const getCoreLocaleFilePath = (locale) => path.join(__dirname, '..', `src/i18n/${locale}.json`);
+const getDataTypeLocaleFilePath = (dataType, locale) => path.join(__dirname, '..', `src/plugins/dataTypes/${dataType}/i18n/${locale}.json`);
+
+const getPluginLocaleFilePath = (plugin, pluginType, locale) => {
+ const pluginFolder = pluginType === 'dataType' ? 'dataTypes' : 'exportTypes';
+ return path.join(__dirname, '..', `src/plugins/${pluginFolder}/${plugin}/i18n/${locale}.json`);
+};
+
+const removeKeyFromI18nFiles = (key) => {
+ locales.forEach((locale) => {
+ const localeFile = getCoreLocaleFileStrings(locale);
+ delete localeFile[key];
+ const file = getCoreLocaleFilePath(locale);
+ fs.writeFileSync(file, JSON.stringify(localeFile, null, '\t'));
+ });
+};
+
+const parseCoreToFindUnusedStrings = (results, en) => {
+ // let missingKeys = Object.keys(en);
+ //
+ // const ignoreFolders = [
+ // 'src/global/lang/',
+ // 'src/global/vendor/',
+ // 'src/global/codemirror/',
+ // 'src/global/fancybox/',
+ // 'src/global/images/',
+ // 'dist/',
+ // 'node_modules/',
+ // 'src/modules/'
+ // ];
+ //
+ // const files = walk('./src');
+ // files.forEach((file) => {
+ // for (let i=0; i {
+ // const regex = new RegExp(key);
+ //
+ // // very kludgy, but the only place Form Tools uses dynamic keys is for dates: ignore all those keys.
+ // // We also ignore any i18n keys flagged for global use across FT modules
+ // if (!(/^date_/.test(key)) && !regex.test(line) && globalI18nStrings.indexOf(key) === -1) {
+ // updatedKeys.push(key);
+ // }
+ // });
+ //
+ // missingKeys = updatedKeys;
+ // }
+ // });
+ //
+ // if (missingKeys.length > 0) {
+ // results.error = true;
+ // results.lines.push(`\nUNUSED KEYS:\n${missingKeys.join('\n --')}`);
+ // }
+};
+
+const getPluginLocaleStrings = (plugin, pluginType) => {
+ const result = {};
+ locales.forEach((locale) => {
+ result[locale] = require(getPluginLocaleFilePath(plugin, pluginType, locale));
+ });
+ return result;
+};
+
+const validateCoreI18n = (baseLocale, targetLocale) => {
+ const stringsByLocale = {};
+ locales.forEach((locale) => {
+ stringsByLocale[locale] = getCoreLocaleFileStrings(locale);
+ });
+
+ const missing = findMissingStrings(stringsByLocale, targetLocale, baseLocale);
+ return getMissingStrMessage(missing, baseLocale);
+};
+
+const validateDataTypeI18n = (baseLocale, targetDataType) => {
+ const dataTypes = helpers.getPlugins('dataTypes', [], false);
+
+ let str = '';
+ dataTypes.forEach((dataType) => {
+ if (targetDataType && targetDataType !== dataType) {
+ return;
+ }
+
+ const stringsByLocale = getPluginLocaleStrings(dataType, 'dataType');
+ const missing = findMissingStrings(stringsByLocale);
+
+ str += getMissingStrMessage(missing, baseLocale, `${dataType} -- `);
+ });
+
+ return str;
+};
+
+const validateExportTypeI18n = (baseLocale, targetExportType) => {
+ const exportTypes = helpers.getPlugins('exportTypes', [], false);
+
+ let str = '';
+ exportTypes.forEach((dataType) => {
+ if (targetExportType && targetExportType !== dataType) {
+ return;
+ }
+
+ const stringsByLocale = getPluginLocaleStrings(dataType, 'exportType');
+ const missing = findMissingStrings(stringsByLocale);
+
+ str += getMissingStrMessage(missing, baseLocale, `${dataType} -- `);
+ });
+
+ return str;
+};
+
+const getMissingStrMessage = (missing, baseLocale, prefix) => {
+ let str = '';
+ if (missing.length) {
+ let missingStr = [];
+ let extraStr = [];
+ missing.forEach(({ key, locale, isExtra }) => {
+ if (isExtra) {
+ extraStr.push(`- ${key}: ${locale}`);
+ } else {
+ missingStr.push(`- ${key}: ${locale}`);
+ }
+ });
+
+ if (missingStr.length) {
+ str += `\n\n${prefix}"${baseLocale}" strings missing from other lang files:\n-------------------------------------------\n`;
+ str += missingStr.join('\n');
+ }
+ if (extraStr.length) {
+ str += `\n\n${prefix}Extra strings in locale files that are NOT in "${baseLocale}" file:\n-------------------------------------------\n`;
+ str += extraStr.join('\n');
+ }
+ }
+ return str;
+};
+
+module.exports = {
+ locales,
+ getCoreLocaleFileStrings,
+ parseCoreToFindUnusedStrings,
+ removeKeyFromI18nFiles,
+ getPluginLocaleStrings,
+ validateCoreI18n,
+ validateDataTypeI18n,
+ validateExportTypeI18n
+};
diff --git a/apps/client/build/rollup-plugin-remove-imports.js b/apps/client/build/rollup-plugin-remove-imports.js
new file mode 100644
index 000000000..d83ee3a98
--- /dev/null
+++ b/apps/client/build/rollup-plugin-remove-imports.js
@@ -0,0 +1,12 @@
+const RemoveImports = () => ({
+ name: 'remove-imports', // TODO rename to remove-gd-utils-import if this is all it ends up doing
+
+ transform (code) {
+ // replace the import utils line with a declaration. This is because utils is included once for all Data Types
+ // and we don't want to be re-bundling it with each Data Type
+ const cleanCode = code.replace(/\bimport\sutils[^;]+;/, 'declare var utils: any;');
+ return cleanCode;
+ }
+});
+
+export default RemoveImports;
diff --git a/apps/client/build/rollup-plugin-worker-hash.js b/apps/client/build/rollup-plugin-worker-hash.js
new file mode 100644
index 000000000..8e04f14d5
--- /dev/null
+++ b/apps/client/build/rollup-plugin-worker-hash.js
@@ -0,0 +1,15 @@
+const helpers = require('./build/helpers.js'); // ... yup.
+
+// runs after each build. It generates a file in the dist folder containing the hash of the file just generated. This
+// lets the grunt tasks only every regenerate the files that are necessary - because it's really slow
+const WorkerHash = () => {
+ return {
+ name: 'WorkerHash',
+ writeBundle (outputOptions, bundle) {
+ const file = Object.keys(bundle)[0];
+ helpers.generateWorkerHashfile(file, `dist/workers`);
+ }
+ };
+};
+
+export default WorkerHash;
diff --git a/apps/client/gruntfile.js b/apps/client/gruntfile.js
new file mode 100644
index 000000000..b52684197
--- /dev/null
+++ b/apps/client/gruntfile.js
@@ -0,0 +1,524 @@
+const fs = require('fs');
+const path = require('path');
+const crypto = require('crypto');
+const helpers = require('./build/helpers');
+const i18n = require('./build/i18n');
+const clientConfig = require('@generatedata/config/clientConfig');
+
+const locales = clientConfig.default.appSettings.GD_LOCALES;
+
+const distFolder = path.join(__dirname, '/dist');
+if (!fs.existsSync(distFolder)) {
+ fs.mkdirSync(distFolder);
+}
+
+const workersFolder = path.join(__dirname, '/dist/workers');
+if (!fs.existsSync(workersFolder)) {
+ fs.mkdirSync(workersFolder);
+}
+
+// returns an 8 char version of the filename hash, used for cache-busting
+const getFilenameHash = (filename) => {
+ const fileBuffer = fs.readFileSync(filename);
+ const hashSum = crypto.createHash('sha256');
+ hashSum.update(fileBuffer);
+
+ return hashSum.digest('hex').substring(0, 8);
+};
+
+// stored in memory here. For the dev environment, changes to web worker files are watched and built separately,
+// then this object is updated with the change & the final map file is regenerated. For prod it's just done in
+// one go
+const webWorkerMap = {
+ generationWorker: '',
+ workerUtils: '',
+ dataTypes: {},
+ exportTypes: {}
+};
+
+module.exports = function (grunt) {
+ const dataTypesFolder = 'dataTypes';
+ const exportTypesFolder = 'exportTypes';
+ const countriesFolder = 'countries';
+ const mainTranslationsFolder = 'src/i18n/';
+
+ const checkPlugin = (pluginType) => {
+ const folderMap = {
+ dataType: dataTypesFolder,
+ exportType: exportTypesFolder,
+ countries: countriesFolder
+ };
+
+ const en = getPluginLocaleFiles(grunt, 'en', folderMap[pluginType]);
+
+ const propsWithI18n = {};
+ Object.keys(en).forEach((plugin) => {
+ Object.keys(en[plugin]).forEach((prop) => {
+ const matches = en[plugin][prop].match(/%\d/g);
+ if (!matches) {
+ return;
+ }
+
+ if (!propsWithI18n[plugin]) {
+ propsWithI18n[plugin] = [];
+ }
+ propsWithI18n[plugin].push({ prop, count: matches.length });
+ });
+ });
+
+ const invalidPlugins = [];
+ locales.forEach((locale) => {
+ if (locale === 'en') {
+ return;
+ }
+ const currLangStrings = getPluginLocaleFiles(grunt, locale, folderMap[pluginType]);
+
+ Object.keys(propsWithI18n).forEach((plugin) => {
+ propsWithI18n[plugin].forEach(({ prop, count }) => {
+ // now loop through each of the placeholders and confirm that the
+ let isValid = true;
+ for (let i = 1; i <= count; i++) {
+ const re = new RegExp(`%${i}`);
+ if (!re.test(currLangStrings[plugin][prop])) {
+ isValid = false;
+ }
+ }
+
+ if (!isValid) {
+ invalidPlugins.push(`Invalid: "${prop}", lang "${locale}", DT: "${plugin}": ${currLangStrings[plugin][prop]}`);
+ }
+ });
+ });
+ });
+
+ return invalidPlugins;
+ };
+
+ const validateStringsWithPlaceholders = () => {
+ let errors = '';
+ const dtErrors = checkPlugin('dataType');
+ if (dtErrors.length) {
+ errors += '\n\nData Type placeholder errors:\n\n' + dtErrors.join('\n');
+ }
+
+ const etErrors = checkPlugin('exportType');
+ if (etErrors.length) {
+ errors += 'Export Type placeholder errors:\n\n' + etErrors.join('\n');
+ }
+
+ const countriesErrors = checkPlugin('countries');
+ if (countriesErrors.length) {
+ errors += 'Export Type placeholder errors:\n\n' + countriesErrors.join('\n');
+ }
+
+ return errors;
+ };
+
+ const generateI18nBundles = () => {
+ const fileHashMap = locales.reduce((acc, locale) => {
+ const coreLocaleStrings = JSON.parse(fs.readFileSync(`src/i18n/${locale}.json`, 'utf8'));
+ const dtImports = getPluginLocaleFiles(grunt, locale, dataTypesFolder);
+ const etImports = getPluginLocaleFiles(grunt, locale, exportTypesFolder);
+ const countryImports = getPluginLocaleFiles(grunt, locale, countriesFolder);
+
+ acc = {
+ ...acc,
+ ...generateLocaleFileTemplate(locale, coreLocaleStrings, dtImports, etImports, countryImports)
+ };
+ return acc;
+ }, {});
+
+ // generate the i18n hashmap file. This is imported by the source code to know what files to load
+ generateI18nHashMap(fileHashMap);
+ };
+
+ const generateI18nHashMap = (content) => {
+ const filename = './_localeFileMap.ts';
+ const tsContent = `/* eslint quotes:0 */
+import { GDLocaleMap } from '@generatedata/types';
+
+export const localeFileMap: GDLocaleMap = ${JSON.stringify(content, null, '\t')};`;
+ fs.writeFileSync(filename, tsContent);
+ };
+
+ const getPluginLocaleFiles = (grunt, locale, pluginTypeFolder) => {
+ const fullPluginFolder = path.resolve(__dirname, `./node_modules/@generatedata/plugins/dist/${pluginTypeFolder}`);
+ const plugins = fs.readdirSync(fullPluginFolder);
+ const imports = {};
+ plugins.forEach((folder) => {
+ const localeFile = `${fullPluginFolder}/${folder}/i18n/${locale}.json`;
+ if (fs.existsSync(localeFile)) {
+ try {
+ imports[folder] = JSON.parse(fs.readFileSync(localeFile, 'utf8'));
+ } catch (e) {
+ grunt.fail.fatal('problem parsing i18n file: ' + localeFile);
+ }
+ }
+ });
+ return imports;
+ };
+
+ const generateLocaleFileTemplate = (locale, coreLocaleStrings, dtImports, etImports, countryImports) => {
+ const template = `// DO NOT EDIT. This file is generated by a Grunt task.
+// ----------------------------------------------------
+
+(function() {
+const i18n = {
+ core: ${JSON.stringify(coreLocaleStrings)},
+ dataTypes: ${JSON.stringify(dtImports)},
+ exportTypes: ${JSON.stringify(etImports)},
+ countries: ${JSON.stringify(countryImports)}
+};
+
+// load the locale info via an exposed global
+window.gd.localeLoaded(i18n);
+})();`;
+
+ const filename = `./dist/${locale}.js`;
+ fs.writeFileSync(filename, template);
+
+ const hash = getFilenameHash(filename);
+ const hashedFilename = `${locale}-${hash}.js`;
+ fs.renameSync(filename, `./dist/${hashedFilename}`);
+
+ return {
+ [locale]: hashedFilename
+ };
+ };
+
+ // looks through the plugins and finds the plugins that have a generator web worker file
+ const dataTypeWebWorkerMap = (() => {
+ const baseFolder = path.join(__dirname, 'node_modules/@generatedata/plugins/dist/dataTypes');
+ const folders = fs.readdirSync(baseFolder);
+
+ const map = {};
+ folders.forEach((folder) => {
+ const webworkerFile = path.join(`${baseFolder}/${folder}/${folder}.worker.ts`);
+ if (!fs.existsSync(webworkerFile)) {
+ return;
+ }
+ // map[`dist/workers/DT-${folder}.worker.js`] = [`src/plugins/dataTypes/${folder}/${folder}.worker.ts`];
+ map[`dist/workers/DT-${folder}.worker.js`] = [`${baseFolder}/${folder}/${folder}.worker.ts`];
+ });
+
+ return map;
+ })();
+
+ console.log(dataTypeWebWorkerMap);
+
+ // const exportTypeWebWorkerMap = (() => {
+ // const baseFolder = path.join(__dirname, '/src/plugins/exportTypes');
+ // const folders = fs.readdirSync(baseFolder);
+
+ // const map = {};
+ // folders.forEach((folder) => {
+ // const webworkerFile = path.join(__dirname, `/src/plugins/exportTypes/${folder}/${folder}.worker.ts`);
+ // if (!fs.existsSync(webworkerFile)) {
+ // return;
+ // }
+ // map[`dist/workers/ET-${folder}.worker.js`] = [`src/plugins/exportTypes/${folder}/${folder}.worker.ts`];
+ // });
+
+ // return map;
+ // })();
+
+ const webWorkerFileListWithType = [
+ { file: 'src/core/generator/generation.worker.ts', type: 'core' },
+ { file: 'src/utils/workerUtils.ts', type: 'core' }
+ ];
+ // Object.values(dataTypeWebWorkerMap).forEach((dt) => {
+ // webWorkerFileListWithType.push({ file: dt[0], type: 'dataType' });
+ // });
+ // Object.values(exportTypeWebWorkerMap).forEach((et) => {
+ // webWorkerFileListWithType.push({ file: et[0], type: 'exportType' });
+ // });
+
+ // const webWorkerFileList = webWorkerFileListWithType.map((i) => i.file);
+
+ const generateWorkerMapFile = () => {
+ fs.writeFileSync('./_pluginWebWorkers.ts', `/* eslint quotes:0 */\nexport default ${JSON.stringify(webWorkerMap, null, '\t')};`);
+ };
+
+ const getWebWorkerShellCommands = (omitFiles = {}) => {
+ const commands = {};
+
+ webWorkerFileListWithType.forEach(({ file, type }, index) => {
+ if (omitFiles[file]) {
+ return;
+ }
+
+ const filename = path.basename(file, path.extname(file));
+ let target = `dist/workers/${filename}.js`;
+
+ if (['dataType', 'exportType'].indexOf(type) !== -1) {
+ // 'country'
+ const filename = helpers.getScopedWorkerFilename(file, type);
+ target = `dist/workers/${filename}`;
+ }
+
+ // TODO detect when the command is run and look for the generated __hash-[filename] content, then update
+ commands[`buildWebWorker${index}`] = {
+ command: `npx rollup -c --config-src=${file} --config-target=${target}`
+ };
+ });
+
+ return commands;
+ };
+
+ // generating every web worker bundle takes time. To get around that, rollup generates a file in the dist/workers
+ // file for each bundle, with the filename of form:
+ // Plugins (e.g.):
+ // __hash-DT-Alphanumeric.generator
+ // __hash-ET-JSON.generator
+ //
+ // Core workers:
+ // __hash-core.worker
+ // __hash-generation.worker
+ // __hash-workerUtils
+ // we then use that information here to check to see if we need to regenerate or not
+ const getWebWorkerBuildCommandNames = () => {
+ const omitFiles = {};
+ webWorkerFileListWithType.forEach(({ file, type }) => {
+ const filename = helpers.getScopedWorkerFilename(file, type);
+ const filenameHash = helpers.getHashFilename(filename);
+
+ if (!helpers.hasWorkerFileChanged(`${workersFolder}/${filename}`, `${workersFolder}/${filenameHash}`)) {
+ omitFiles[file] = true;
+ }
+ });
+
+ return Object.keys(getWebWorkerShellCommands(omitFiles)).map((cmdName) => `shell:${cmdName}`);
+ };
+
+ // const webWorkerWatchers = (() => {
+ // const tasks = {};
+
+ // // this contains *ALL* web worker tasks. It ensures that everything is watched.
+ // webWorkerFileList.forEach((workerPath, index) => {
+ // tasks[`webWorkerWatcher${index}`] = {
+ // files: [workerPath],
+ // options: { spawn: false },
+ // tasks: [`shell:buildWebWorker${index}`, `md5:webWorkerMd5Task${index}`, 'generateWorkerMapFile']
+ // };
+ // });
+
+ // return tasks;
+ // })();
+
+ const processMd5Change = (fileChanges) => {
+ const oldPath = fileChanges[0].oldPath;
+ const oldFile = path.basename(oldPath);
+ const newFilename = path.basename(fileChanges[0].newPath);
+
+ if (oldPath === 'dist/workers/generation.worker.js') {
+ webWorkerMap.generationWorker = newFilename;
+ } else if (oldPath === 'dist/workers/workerUtils.js') {
+ webWorkerMap.workerUtils = newFilename;
+ } else {
+ const [pluginFolder] = oldFile.split('.');
+ const cleanPluginFolder = pluginFolder.replace(/^(DT-|ET-)/, '');
+
+ if (/^DT-/.test(oldFile)) {
+ webWorkerMap.dataTypes[cleanPluginFolder] = newFilename;
+ } else if (/^ET-/.test(oldFile)) {
+ webWorkerMap.exportTypes[cleanPluginFolder] = newFilename;
+ }
+ }
+ };
+
+ // these tasks execute individually AFTER the worker has already been generated in the dist/workers folder
+ const webWorkerMd5Tasks = (() => {
+ const tasks = {};
+ webWorkerFileListWithType.forEach(({ file, type }, index) => {
+ const fileName = helpers.getScopedWorkerFilename(file, type);
+ const newFileLocation = `dist/workers/${fileName}`; // N.B. here it's now a JS file, not TS
+
+ tasks[`webWorkerMd5Task${index}`] = {
+ files: {
+ [newFileLocation]: newFileLocation
+ },
+ options: {
+ after: (fileChanges) => processMd5Change(fileChanges, webWorkerMap)
+ }
+ };
+ });
+
+ return tasks;
+ })();
+
+ const getWebWorkerMd5TaskNames = () => {
+ return Object.keys(webWorkerMd5Tasks).map((cmdName) => `md5:${cmdName}`);
+ };
+
+ grunt.initConfig({
+ cssmin: {
+ options: {
+ mergeIntoShorthands: false,
+ roundingPrecision: -1
+ },
+ target: {
+ files: {
+ 'dist/styles.css': [
+ 'src/resources/codemirror.css',
+ 'src/resources/ambience.css',
+ 'src/resources/bespin.css',
+ 'src/resources/cobalt.css',
+ 'src/resources/darcula.css',
+ 'src/resources/lucario.css'
+ ]
+ }
+ }
+ },
+
+ copy: {
+ main: {
+ files: [
+ {
+ expand: true,
+ cwd: 'src/images',
+ src: ['*'],
+ dest: 'dist/images/'
+ }
+ ]
+ },
+
+ // TODO should minify these too
+ codeMirrorModes: {
+ files: [
+ {
+ expand: true,
+ cwd: './node_modules/codemirror/mode',
+ src: ['**/*'],
+ dest: 'dist/codeMirrorModes/'
+ }
+ ]
+ }
+ },
+
+ clean: {
+ dist: ['dist']
+ },
+
+ shell: {
+ webpackProd: {
+ command: 'npm run prod'
+ },
+
+ // note these aren't executed right away, so they contain ALL web workers, even those don't need regeneration
+ ...getWebWorkerShellCommands()
+ },
+
+ watch: {
+ // ...webWorkerWatchers
+ },
+
+ md5: {
+ ...webWorkerMd5Tasks
+ }
+ });
+
+ // const validateI18n = () => {
+ // const baseLocale = grunt.option('baseLocale') || 'en';
+ // const targetLocale = grunt.option('locale') || null;
+ // const targetDataType = grunt.option('dataType') || null;
+ // const targetExportType = grunt.option('exportType') || null;
+
+ // let errors = '';
+ // if (targetDataType) {
+ // errors += i18n.validateDataTypeI18n(baseLocale, targetDataType);
+ // } else if (targetExportType) {
+ // errors += i18n.validateExportTypeI18n(baseLocale, targetDataType);
+ // } else {
+ // errors += i18n.validateCoreI18n(baseLocale, targetLocale);
+ // errors += i18n.validateDataTypeI18n(baseLocale);
+ // errors += i18n.validateExportTypeI18n(baseLocale);
+ // }
+
+ // errors += validateStringsWithPlaceholders();
+
+ // if (errors) {
+ // grunt.fail.fatal(errors);
+ // }
+ // };
+
+ // const sortI18nFiles = () => {
+ // i18n.locales.forEach((locale) => {
+ // const data = i18n.getCoreLocaleFileStrings(locale);
+ // const file = `./src/i18n/${locale}.json`;
+ // const sortedKeys = Object.keys(data).sort();
+
+ // let sortedObj = {};
+ // sortedKeys.forEach((key) => {
+ // sortedObj[key] = data[key];
+ // });
+
+ // fs.writeFileSync(file, JSON.stringify(sortedObj, null, '\t'));
+ // });
+ // };
+
+ // helper methods to operate on all lang files at once
+ // grunt.registerTask('removeI18nKey', () => {
+ // const key = grunt.option('key') || null;
+ // if (!key) {
+ // grunt.fail.fatal('Please enter a key to remove. Format: `grunt removeI18nKey --key=word_goodbye');
+ // }
+ // i18n.removeKeyFromI18nFiles(grunt.option('key'));
+ // });
+
+ // grunt.registerTask('addLocale', () => {
+ // const locale = grunt.option('locale') || null;
+ // if (!locale) {
+ // grunt.fail.fatal('Please enter a locale to add. Locales should be the ISO-3166 2-char code: `grunt addLocale --locale=xy');
+ // }
+
+ // const dataTypes = fs.readdirSync(dataTypesFolder);
+ // dataTypes.forEach((folder) => {
+ // const en = `${dataTypesFolder}/${folder}/i18n/en.json`;
+ // const newLocaleFile = `${dataTypesFolder}/${folder}/i18n/${locale}.json`;
+ // if (fs.existsSync(en)) {
+ // fs.copyFileSync(en, newLocaleFile);
+ // }
+ // });
+
+ // const exportTypes = fs.readdirSync(exportTypesFolder);
+ // exportTypes.forEach((folder) => {
+ // const en = `${exportTypesFolder}/${folder}/i18n/en.json`;
+ // const newLocaleFile = `${exportTypesFolder}/${folder}/i18n/${locale}.json`;
+ // if (fs.existsSync(en)) {
+ // fs.copyFileSync(en, newLocaleFile);
+ // }
+ // });
+
+ // const countries = fs.readdirSync(countriesFolder);
+ // countries.forEach((folder) => {
+ // const en = `${countriesFolder}/${folder}/i18n/en.json`;
+ // const newLocaleFile = `${countriesFolder}/${folder}/i18n/${locale}.json`;
+ // if (fs.existsSync(en)) {
+ // fs.copyFileSync(en, newLocaleFile);
+ // }
+ // });
+
+ // // main translation file
+ // const mainEn = `${mainTranslationsFolder}/en.json`;
+ // if (fs.existsSync(mainEn)) {
+ // fs.copyFileSync(mainEn, `${mainTranslationsFolder}/${locale}.json`);
+ // }
+ // });
+
+ grunt.loadNpmTasks('grunt-contrib-cssmin');
+ grunt.loadNpmTasks('grunt-contrib-copy');
+ grunt.loadNpmTasks('grunt-contrib-clean');
+ grunt.loadNpmTasks('grunt-shell');
+ grunt.loadNpmTasks('grunt-contrib-uglify');
+ grunt.loadNpmTasks('grunt-contrib-watch');
+ grunt.loadNpmTasks('grunt-md5');
+
+ // grunt.registerTask('sortI18nFiles', sortI18nFiles);
+ grunt.registerTask('default', ['cssmin', 'copy', 'generateI18nBundles', 'webWorkers']);
+ // grunt.registerTask('dev', ['cssmin', 'copy', 'generateI18nBundles', 'webWorkers', 'watch']);
+ grunt.registerTask('generateWorkerMapFile', generateWorkerMapFile);
+ grunt.registerTask('generateI18nBundles', generateI18nBundles);
+ // grunt.registerTask('validateI18n', validateI18n);
+
+ grunt.registerTask('webWorkers', [...getWebWorkerBuildCommandNames(), ...getWebWorkerMd5TaskNames(), 'generateWorkerMapFile']);
+};
diff --git a/apps/client/package.json b/apps/client/package.json
new file mode 100644
index 000000000..143fab46a
--- /dev/null
+++ b/apps/client/package.json
@@ -0,0 +1,205 @@
+{
+ "name": "@generatedata/client",
+ "description": "Client-side code for generatedata app",
+ "private": true,
+ "scripts": {
+ "build": "grunt && npm run webpackProd",
+ "clean": "npx rimraf client/dist",
+ "coverage": "npx jest --coverage",
+ "i18n": "grunt && grunt i18n",
+ "start": "grunt dev & npm run build & npm run webpackDev",
+ "test": "env NODE_ENV=test jest",
+ "webWorkers": "grunt && grunt webWorkers",
+ "webpackDev": "webpack serve --config ./webpack.config.js --mode=development",
+ "webpackProd": "webpack --config ./webpack.config.js --mode=production"
+ },
+ "jest": {
+ "moduleFileExtensions": [
+ "ts",
+ "tsx",
+ "js"
+ ],
+ "testEnvironment": "jsdom",
+ "transform": {
+ "node_modules/nanoid/index.js$": "ts-jest",
+ "node_modules/@react-hook/throttle/dist/module/index.js$": "ts-jest",
+ "\\.(ts|tsx)$": "ts-jest"
+ },
+ "transformIgnorePatterns": [
+ "node_modules/(?!nanoid)/.*",
+ "node_modules/@react-hook/throttle/dist/module/.*"
+ ],
+ "setupFilesAfterEnv": [
+ "/client/tests/jestSetup.ts"
+ ],
+ "testRegex": "/__tests__/.*\\.(ts|tsx)$",
+ "moduleNameMapper": {
+ "\\.(css|less|scss|sss|styl)$": "/node_modules/jest-css-modules",
+ "^~components(.*)$": "/client/src/components$1",
+ "^~types(.*)$": "/client/types$1",
+ "^~utils(.*)$": "/client/src/utils$1",
+ "^~store(.*)$": "/client/src/core/store$1",
+ "^~core(.*)$": "/client/src/core$1"
+ },
+ "collectCoverageFrom": [
+ "/client/src/**/*.(ts|tsx)"
+ ],
+ "coveragePathIgnorePatterns": [
+ "bundle.ts",
+ ".*.scss.d.ts",
+ ".*.types.d.ts"
+ ],
+ "modulePathIgnorePatterns": [
+ "/cli/dist"
+ ]
+ },
+ "globals": {
+ "ts-jest": {
+ "tsconfig": {
+ "allowJs": true
+ }
+ }
+ },
+ "dependencies": {
+ "@apollo/client": "^4.0.0",
+ "@date-io/date-fns": "^1.3.13",
+ "@mui/material": "^7.3.1",
+ "@mui/icons-material": "^7.3.1",
+ "@mui/x-date-pickers": "^8.10.2",
+ "@react-hook/throttle": "^2.2.0",
+ "@reduxjs/toolkit": "^2.8.2",
+ "@types/randomcolor": "^0.5.9",
+ "@uidotdev/usehooks": "^2.4.1",
+ "codemirror": "^5.58.2",
+ "date-fns": "^2.17.0",
+ "dotenv": "^8.2.0",
+ "googleapis": "^65.0.0",
+ "graphql": "^16.11.0",
+ "immer": "^10.1.1",
+ "js-cookie": "^3.0.5",
+ "nanoid": "^5.1.5",
+ "pretty-bytes": "^7.0.1",
+ "randomcolor": "^0.6.2",
+ "react": "^19.1.1",
+ "react-beautiful-dnd": "^13.1.1",
+ "react-codemirror2": "^8.0.1",
+ "react-copy-to-clipboard": "^5.1.0",
+ "react-countup": "^6.5.3",
+ "react-dom": "^19.1.1",
+ "react-hooks-window-size": "^0.3.0",
+ "react-input-autosize": "^3.0.0",
+ "react-number-format": "5.4.4",
+ "react-redux": "^9.2.0",
+ "react-router": "^7.8.2",
+ "react-router-dom": "^7.8.2",
+ "react-select": "^5.10.2",
+ "react-sortable-hoc": "^1.11.0",
+ "react-split-pane": "^0.1.89",
+ "reactour": "^1.18.0",
+ "recharts": "^3.1.2",
+ "redux": "^5.0.1",
+ "redux-persist": "^6.0.0",
+ "rollup": "^2.31.0",
+ "rxjs": "^7.3.0",
+ "sass": "^1.49.9",
+ "styled-components": "^4.0.0",
+ "terser-webpack-plugin": "^3.0.3",
+ "underscore": "^1.13.7"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.7.4",
+ "@babel/plugin-proposal-class-properties": "^7.10.4",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/preset-env": "^7.7.4",
+ "@babel/preset-react": "^7.7.4",
+ "@esbuild-plugins/tsconfig-paths": "^0.0.4",
+ "@generatedata/config": "workspace:*",
+ "@generatedata/plugins": "workspace:*",
+ "@generatedata/utils": "workspace:*",
+ "@generatedata/types": "workspace:*",
+ "@rollup/plugin-commonjs": "^15.1.0",
+ "@rollup/plugin-node-resolve": "^8.4.0",
+ "@stylistic/eslint-plugin-js": "^3.1.0",
+ "@teamsupercell/typings-for-css-modules-loader": "^2.5.2",
+ "@testing-library/react": "^16.3.0",
+ "@types/codemirror": "^0.0.99",
+ "@types/jest": "^26.0.15",
+ "@types/js-cookie": "^3.0.5",
+ "@types/loadable__component": "^5.13.8",
+ "@types/node": "^20.19.4",
+ "@types/react": "^19.1.11",
+ "@types/react-beautiful-dnd": "^13.1.1",
+ "@types/react-copy-to-clipboard": "^5.0.7",
+ "@types/react-input-autosize": "^2.2.4",
+ "@types/reactour": "^1.18.5",
+ "@types/redux-testkit": "^1.0.8",
+ "@types/sinon": "^7.5.1",
+ "@typescript-eslint/eslint-plugin": "^5.54.0",
+ "@typescript-eslint/parser": "^5.54.0",
+ "autoprefixer": "6.7.2",
+ "babel-eslint": "^10.0.3",
+ "babel-loader": "^8.0.6",
+ "babel-preset-airbnb": "^4.4.0",
+ "browser-env": "3.3.0",
+ "case-sensitive-paths-webpack-plugin": "^2.3.0",
+ "concurrently": "^5.2.0",
+ "coveralls": "^3.0.9",
+ "cross-fetch": "^3.1.5",
+ "css-loader": "^3.4.0",
+ "cypress": "^6.0.1",
+ "dotenv-webpack": "^6.0.0",
+ "eslint": "^8.52.0",
+ "eslint-config-airbnb": "^19.0.4",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-plugin-import": "^2.27.5",
+ "eslint-plugin-jsx-a11y": "^6.7.1",
+ "eslint-plugin-react": "^7.32.0",
+ "eslint-webpack-plugin": "^4.0.1",
+ "extract-text-webpack-plugin": "1.0.1",
+ "grunt": "^1.6.1",
+ "grunt-cli": "^1.3.2",
+ "grunt-contrib-clean": "^2.0.0",
+ "grunt-contrib-copy": "^1.0.0",
+ "grunt-contrib-cssmin": "^5.0.0",
+ "grunt-contrib-uglify": "^5.2.2",
+ "grunt-contrib-watch": "^1.1.0",
+ "grunt-md5": "^0.1.12",
+ "grunt-shell": "^4.0.0",
+ "history": "^5.3.0",
+ "html-webpack-plugin": "^5.5.0",
+ "jest": "^29.4.3",
+ "jest-cli": "^29.4.3",
+ "jest-css-modules": "^2.1.0",
+ "jest-environment-jsdom": "^29.4.3",
+ "json-loader": "0.5.4",
+ "md5-file": "^5.0.0",
+ "mini-css-extract-plugin": "^0.8.0",
+ "postcss-loader": "1.2.2",
+ "prettier": "^3.4.2",
+ "prettier-eslint": "^16.3.0",
+ "promise": "7.1.1",
+ "redux-testkit": "^1.0.6",
+ "reselect": "^4.0.0",
+ "rimraf": "^6.0.1",
+ "rollup-plugin-multi-entry": "^2.1.0",
+ "rollup-plugin-strip-exports": "^2.0.6",
+ "rollup-plugin-terser": "^6.1.0",
+ "rollup-plugin-typescript2": "^0.27.1",
+ "sass-loader": "^16.0.5",
+ "sinon": "^8.0.4",
+ "style-loader": "^1.0.0",
+ "ts-jest": "^29.2.5",
+ "ts-loader": "^9.4.2",
+ "ts-node": "^10.9.2",
+ "tsconfig-paths": "4.1.1",
+ "typescript": "^4.9.3",
+ "use-onclickoutside": "^0.3.1",
+ "webpack": "^5.94.0",
+ "webpack-bundle-analyzer": "^4.8.0",
+ "webpack-cli": "^5.0.1",
+ "webpack-dev-middleware": "^6.1.2",
+ "webpack-dev-server": "^4.11.0",
+ "webpack-hot-middleware": "^2.25.3",
+ "webpack-manifest-plugin": "5.0.0"
+ }
+}
diff --git a/apps/client/rollup.config.js b/apps/client/rollup.config.js
new file mode 100644
index 000000000..16a9faa74
--- /dev/null
+++ b/apps/client/rollup.config.js
@@ -0,0 +1,72 @@
+/**
+ * This generates es5 files for single entry-point TS files. It's used for the webworker files: core, core utils, plugins.
+ *
+ * TODO at the moment we're actually loading the utils code twice: once for the web workers, one in the code bundle.
+ * The core script COULD load this generated file & use the methods from the window object; as long as the typings were
+ * provided that'd cut down on build size. But honestly it's <20KB and there are bigger fish to fry.
+ */
+import path from 'path';
+import { nodeResolve } from '@rollup/plugin-node-resolve';
+import commonjs from '@rollup/plugin-commonjs';
+import typescript from 'rollup-plugin-typescript2';
+import removeExports from 'rollup-plugin-strip-exports';
+import { terser } from 'rollup-plugin-terser';
+import removeImports from './build/rollup-plugin-remove-imports';
+import workerHash from './build/rollup-plugin-worker-hash';
+
+// example usage:
+// npx rollup -c --config-src=src/utils/coreUtils.ts --config-target=dist/workers/coreUtils.js
+// npx rollup -c --config-src=src/utils/workerUtils.ts --config-target=dist/debug.js
+// npx rollup -c --config-src=src/plugins/countries/Australia/bundle.ts --config-target=dist/australia.js
+// npx rollup -c --config-src=src/plugins/dataTypes/AutoIncrement/AutoIncrement.generator.ts --config-target=dist/workers/DT-AutoIncrement.generator.js
+export default (cmdLineArgs) => {
+ const { 'config-src': src, 'config-target': target } = cmdLineArgs;
+
+ if (!src || !target) {
+ console.error('\n*** Missing command line args. See file for usage. ***\n');
+ return;
+ }
+
+ const terserCompressProps = {};
+
+ // the whole point of the workerUtils file is to expose all utility methods in a single `utils` object
+ // for use by plugin web workers. This is available on the global scope within a web worker
+ if (src === 'src/utils/workerUtils.ts') {
+ terserCompressProps.top_retain = ['utils', 'onmessage'];
+ } else if (/src\/plugins\/countries/.test(src)) {
+ const folder = path.dirname(src).split(path.sep);
+ terserCompressProps.top_retain = [folder[folder.length - 1]];
+ } else {
+ terserCompressProps.unused = true;
+ terserCompressProps.top_retain = ['utils', 'onmessage'];
+ }
+
+ return {
+ input: src,
+ output: {
+ file: target,
+ format: 'es'
+ },
+ treeshake: false,
+ plugins: [
+ removeImports(),
+ commonjs(),
+ nodeResolve(),
+ typescript({
+ tsconfigOverride: {
+ compilerOptions: {
+ target: 'es5'
+ }
+ }
+ }),
+ terser({
+ mangle: false,
+ compress: {
+ ...terserCompressProps
+ }
+ }),
+ removeExports(),
+ workerHash()
+ ]
+ };
+};
diff --git a/apps/client/src/app.tsx b/apps/client/src/app.tsx
new file mode 100644
index 000000000..4106df99f
--- /dev/null
+++ b/apps/client/src/app.tsx
@@ -0,0 +1,109 @@
+/* istanbul ignore file */
+import React, { useEffect } from 'react';
+import { Provider } from 'react-redux';
+import { BrowserRouter as Router, Routes, Route, useNavigation } from 'react-router-dom';
+import { ApolloProvider } from '@apollo/client/react';
+import * as codemirror from 'codemirror';
+import { PersistGate } from 'redux-persist/integration/react';
+import { ThemeProvider } from '@mui/material/styles';
+import { apolloClient } from '~core/apolloClient';
+import store, { persistor } from '~core/store';
+import Page from '~core/page/Page.container';
+import * as core from '~core/index';
+import ErrorBoundary from '~core/ErrorBoundary.component';
+import theme from '~core/theme';
+import SaveDataSetDialog from '~core/dialogs/saveDataSet/SaveDataSet.container';
+import Toast from '~components/toast/Toast.component';
+import C from '@generatedata/config/constants';
+import { getAppStateVersion } from '~store/main/main.selectors';
+import { resetStore, initRouteListener } from '~store/main/main.actions';
+import { getRoutes } from '~utils/routeUtils';
+import { getLocaleMap } from '@generatedata/utils/lang';
+import '~store/generator/generator.reducer';
+import './styles/global.scss';
+
+window.CodeMirror = codemirror;
+
+const checkState = async (state: any): Promise => {
+ const lastAppStateVersion = getAppStateVersion(state.getState());
+ if (lastAppStateVersion !== C.APP_STATE_VERSION) {
+ await state.dispatch(resetStore());
+ }
+};
+
+const routes = getRoutes();
+
+// there's probably a cleaner way to do this, but this seems performant and not too complicated
+const LocalizationWrapper = (args: any) => {
+ const availableLocalesMap = getLocaleMap();
+ const lang = args.match?.params?.lang;
+ let localizedRoutes = routes;
+
+ // this rewrites any routes that include a valid (known) lang path root folder so the routing
+ // works as expected. Note: the actual loading of the locale file will have taken place prior to here. It either
+ // occurs on first boot and handles waiting to show the whole application until it's ready, or when
+ // the user changes is via the lang selector dialog - that alters the URL to include the locale only after it's been
+ // successfully loaded
+ if (lang && lang !== 'en' && availableLocalesMap[lang]) {
+ localizedRoutes = routes.map((route) => ({
+ ...route,
+ path: `/${lang}${route.path}`
+ }));
+ }
+
+ return (
+
+ {localizedRoutes.map(({ path, component: Component }, index) => (
+
+
+
+ ))}
+
+ );
+};
+
+const App = () => {
+ const navigation = useNavigation();
+
+ useEffect(() => {
+ initRouteListener(navigation);
+ }, []);
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+});
+
+const AppWrapper = () => (
+
+
+
+ => checkState(store)}>
+ {(bootstrapped) => {
+ // PersistGate handles repopulating the redux store; core.init() re-initializes everything else we
+ // need, including checking auth and loading the appropriate locale file
+ if (bootstrapped) {
+ core.init();
+ }
+
+ return (
+
+
+
+ );
+ }}
+
+
+
+
+);
+
+export default AppWrapper;
diff --git a/apps/client/src/components/Buttons.component.tsx b/apps/client/src/components/Buttons.component.tsx
new file mode 100644
index 000000000..700736d0e
--- /dev/null
+++ b/apps/client/src/components/Buttons.component.tsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import { styled } from '@mui/material/styles';
+import Button, { ButtonProps } from '@mui/material/Button';
+
+export const PrimaryButton = ({ children, ...props }: ButtonProps) => (
+
+ {children}
+
+);
+
+export const NullButton = ({ children, ...props }: ButtonProps) => (
+
+ {children}
+
+);
+
+const SecondaryStyledButton = styled(Button)(() => ({
+ root: {
+ border: '1px solid #047a12',
+ color: '#047a12',
+ backgroundColor: '#fafffb',
+ '&:hover': {
+ border: '1px solid #0e961e',
+ backgroundColor: '#f6fff7'
+ }
+ }
+}));
+
+export const SecondaryButton = ({ children, ...props }: ButtonProps) => (
+
+ {children}
+
+);
+
+export const StyledPreviewPanelButton = styled(Button)(() => ({
+ root: {
+ borderColor: '#ffffff',
+ color: '#ffffff',
+ marginRight: 6,
+ '&:hover': {
+ backgroundColor: '#0069d9',
+ borderColor: '#0062cc',
+ boxShadow: 'none'
+ }
+ }
+}));
+
+export const PreviewPanelButton = ({ children, ...props }: ButtonProps) => (
+
+ {children}
+
+);
diff --git a/apps/client/src/components/Link.component.tsx b/apps/client/src/components/Link.component.tsx
new file mode 100644
index 000000000..a55711da7
--- /dev/null
+++ b/apps/client/src/components/Link.component.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+
+export type LinkParams = {
+ url: string;
+ children?: any;
+ offSite?: boolean;
+};
+
+const Link = ({ url, children = null, offSite = false }: LinkParams) => {
+ const props: any = {
+ href: url
+ };
+ if (offSite) {
+ props.target = '_blank';
+ props.rel = 'noopener noreferrer';
+ }
+
+ return {children ? children : url} ;
+};
+
+export default Link;
diff --git a/apps/client/src/components/Pagination.tsx b/apps/client/src/components/Pagination.tsx
new file mode 100644
index 000000000..9666aef65
--- /dev/null
+++ b/apps/client/src/components/Pagination.tsx
@@ -0,0 +1,6 @@
+import React from 'react';
+import MuiPagination from '@mui/material/Pagination';
+
+const Pagination = ({ numPages, currentPage, onChange }: any) => ;
+
+export default Pagination;
diff --git a/apps/client/src/components/Portal.tsx b/apps/client/src/components/Portal.tsx
new file mode 100644
index 000000000..82eff71f3
--- /dev/null
+++ b/apps/client/src/components/Portal.tsx
@@ -0,0 +1,11 @@
+import { ReactNode, ReactPortal } from 'react';
+import { createPortal } from 'react-dom';
+import usePortal from '../hooks/usePortal';
+
+const Portal = ({ id, children }: { id: string; children: ReactNode }): ReactPortal => {
+ const target = usePortal(id);
+
+ return createPortal(children, target, null);
+};
+
+export default Portal;
diff --git a/apps/client/src/components/TextField.tsx b/apps/client/src/components/TextField.tsx
new file mode 100644
index 000000000..7069e7092
--- /dev/null
+++ b/apps/client/src/components/TextField.tsx
@@ -0,0 +1,79 @@
+import React, { useState } from 'react';
+import { ErrorTooltip } from '~components/tooltips';
+import sharedStyles from '../styles/shared.scss';
+import { useThrottle } from '../hooks/useThrottle';
+
+type TextFieldProps = {
+ value: string;
+ onChange: (e: any) => void;
+ throttle?: boolean;
+ error?: string;
+ ref?: React.MutableRefObject;
+ placeholder?: string;
+ autoFocus?: boolean;
+ className?: string;
+ tooltipPlacement?: string;
+ name?: string;
+ style?: React.CSSProperties;
+ disabled?: boolean;
+ type?: string;
+ onPaste?: (e: any) => void;
+ onKeyDown?: (e: any) => void;
+};
+
+const TextField = ({ throttle, error, value, onChange, tooltipPlacement, className, ref, ...props }: TextFieldProps) => {
+ let classes = className ? className : '';
+ if (error) {
+ classes += ' ' + sharedStyles.errorField;
+ }
+
+ const [innerValue, setInnerValue] = useState(value || '');
+ const [lastEvent, setChangeEvent] = useThrottle(null, 2); // second param is frames per second...
+
+ const cleanProps = { ...props };
+ if (props.type === 'intOnly') {
+ cleanProps.type = 'number';
+ cleanProps.onKeyDown = (e: any): void => {
+ if (e.key === '.') {
+ e.preventDefault();
+ }
+ };
+ }
+
+ React.useEffect(() => {
+ if (lastEvent === null || !throttle) {
+ return;
+ }
+ onChange(lastEvent);
+ }, [lastEvent]);
+
+ React.useEffect(() => {
+ setInnerValue(value);
+ }, [value]);
+
+ const controlledOnChange = (e: any): void => {
+ if (throttle) {
+ e.persist();
+ setChangeEvent(e);
+ setInnerValue(e.target.value);
+ } else {
+ onChange(e);
+ }
+ };
+
+ return (
+
+
+
+ );
+};
+TextField.displayName = 'TextField';
+
+TextField.defaultProps = {
+ throttle: true,
+ type: 'text',
+ error: '',
+ tooltipPlacement: 'bottom'
+};
+
+export default TextField;
diff --git a/apps/client/src/components/__tests__/Link.component.tsx b/apps/client/src/components/__tests__/Link.component.tsx
new file mode 100644
index 000000000..bb00fc88c
--- /dev/null
+++ b/apps/client/src/components/__tests__/Link.component.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import Link from '../Link.component';
+
+describe('Link', () => {
+ it('renders link as label if not supplied', () => {
+ const { container } = render( );
+
+ expect(container.innerHTML).toEqual('http://google.com ');
+ });
+
+ it('renders link as label if not supplied', () => {
+ const { container } = render( Link here);
+
+ expect(container.innerHTML).toEqual('Link here ');
+ });
+
+ it('renders offsite links', () => {
+ const { container } = render(
+
+ Link here
+
+ );
+
+ expect(container.innerHTML).toEqual('Link here ');
+ });
+});
diff --git a/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.component.tsx b/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.component.tsx
new file mode 100644
index 000000000..892c3e0ee
--- /dev/null
+++ b/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.component.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { AccountStatus } from '~types/account';
+import styles from './AccountStatusPill.scss';
+
+type AccountStatusPillProps = {
+ status: AccountStatus;
+ i18n: any;
+};
+
+const AccountStatusPill = ({ status, i18n }: AccountStatusPillProps) => {
+ let label;
+ if (status === 'live') {
+ label = i18n.live;
+ } else if (status === 'expired') {
+ label = i18n.expired;
+ } else if (status === 'disabled') {
+ label = i18n.disabled;
+ }
+
+ return {label} ;
+};
+
+export default AccountStatusPill;
diff --git a/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.scss b/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.scss
new file mode 100644
index 000000000..0bb39445f
--- /dev/null
+++ b/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.scss
@@ -0,0 +1,20 @@
+.pill {
+ border-radius: 3px;
+ padding: 1px 6px 2px;
+ font-size: 11px;
+}
+
+.live {
+ background-color: green;
+ color: white;
+}
+
+.disabled {
+ background-color: #999999;
+ color: white;
+}
+
+.expired {
+ background-color: #990000;
+ color: white;
+}
diff --git a/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.scss.d.ts b/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.scss.d.ts
new file mode 100644
index 000000000..42c0d5c15
--- /dev/null
+++ b/apps/client/src/components/accounts/accountStatusPill/AccountStatusPill.scss.d.ts
@@ -0,0 +1,15 @@
+declare namespace AccountStatusPillScssNamespace {
+ export interface IAccountStatusPillScss {
+ disabled: string;
+ expired: string;
+ live: string;
+ pill: string;
+ }
+}
+
+declare const AccountStatusPillScssModule: AccountStatusPillScssNamespace.IAccountStatusPillScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: AccountStatusPillScssNamespace.IAccountStatusPillScss;
+};
+
+export = AccountStatusPillScssModule;
diff --git a/apps/client/src/components/accounts/accountStatusPill/__tests__/AccountStatusPill.test.tsx b/apps/client/src/components/accounts/accountStatusPill/__tests__/AccountStatusPill.test.tsx
new file mode 100644
index 000000000..a7875d305
--- /dev/null
+++ b/apps/client/src/components/accounts/accountStatusPill/__tests__/AccountStatusPill.test.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import AccountStatusPill from '../AccountStatusPill.component';
+import { render } from '@testing-library/react';
+import { AccountStatus } from '~types/account';
+
+const i18n = require('../../../../i18n/en.json');
+
+describe('AccountStatusPill', () => {
+ it('renders live pill', () => {
+ const { baseElement } = render( );
+ expect((baseElement.querySelector('span') as HTMLSpanElement).innerHTML).toEqual(i18n.live);
+ });
+
+ it('renders expired pill', () => {
+ const { baseElement } = render( );
+ expect((baseElement.querySelector('span') as HTMLSpanElement).innerHTML).toEqual(i18n.expired);
+ });
+
+ it('renders disabled pill', () => {
+ const { baseElement } = render( );
+ expect((baseElement.querySelector('span') as HTMLSpanElement).innerHTML).toEqual(i18n.disabled);
+ });
+});
diff --git a/apps/client/src/components/accounts/mainFields/MainFields.component.tsx b/apps/client/src/components/accounts/mainFields/MainFields.component.tsx
new file mode 100644
index 000000000..45a37565f
--- /dev/null
+++ b/apps/client/src/components/accounts/mainFields/MainFields.component.tsx
@@ -0,0 +1,224 @@
+import React, { useRef, useEffect, useState } from 'react';
+import Button from '@mui/material/Button';
+import TextField from '~components/TextField';
+import IconButton from '@mui/material/IconButton';
+import Dropdown from '~components/dropdown/Dropdown';
+import FormGroup from '@mui/material/FormGroup';
+import FormControlLabel from '@mui/material/FormControlLabel';
+import Checkbox from '@mui/material/Checkbox';
+import { canadianProvinceOptions, countryDropdownOptions } from '@generatedata/plugins';
+import Refresh from '@mui/icons-material/Refresh';
+import { AccountEditingData } from '~store/account/account.reducer';
+import { isValidEmail } from '@generatedata/utils/general';
+import { generateRandomAlphanumericStr } from '@generatedata/utils/random';
+import sharedStyles from '../../../styles/shared.scss';
+
+export type MainFieldsProps = {
+ data: AccountEditingData;
+ accountHasChanges: boolean;
+ updateAccount: (data: AccountEditingData) => void;
+ submitButtonLabel: string;
+ i18n: any;
+ onSave: (setOneTimePassword: boolean) => void;
+ onCancel: () => void;
+ showRequiredFieldError: boolean;
+ isAddingUser: boolean;
+ className?: string;
+};
+
+const MainFields = ({
+ data,
+ accountHasChanges,
+ updateAccount,
+ onSave,
+ onCancel,
+ submitButtonLabel,
+ i18n,
+ showRequiredFieldError,
+ isAddingUser,
+ className = ''
+}: MainFieldsProps) => {
+ const emailFieldRef = useRef(null);
+ const [oneTimePasswordFieldVisible, setOneTimePasswordFieldVisible] = useState(false);
+
+ // very fussy indeed!
+ const [emailFieldHasFocus, setEmailFieldHasFocus] = useState(false);
+ const [emailFieldHasHadFocus, setEmailFieldHasHadFocus] = useState(false);
+
+ const onBlurEmail = (): void => {
+ setEmailFieldHasFocus(false);
+ setEmailFieldHasHadFocus(true);
+ };
+
+ const update = (fieldName: string, value: string): void => {
+ updateAccount({
+ ...data,
+ [fieldName]: value
+ });
+ };
+
+ let fieldsValid = true;
+ if (!data.firstName.trim() || !data.lastName.trim() || !data.email.trim()) {
+ if (showRequiredFieldError) {
+ fieldsValid = false;
+ }
+ }
+ let emailError;
+ if (data.email.trim() === '') {
+ if (showRequiredFieldError) {
+ emailError = i18n.requiredField;
+ }
+ } else if (!isValidEmail(data.email)) {
+ // subtle. We only want to show the email field is in an invalid state when
+ // (a) adding an email and the user's moved off the field & left it in an invalid state
+ // (b) is editing the email
+ if (!isAddingUser || (emailFieldHasHadFocus && !emailFieldHasFocus)) {
+ emailError = i18n.validationInvalidEmail;
+ fieldsValid = false;
+ }
+ }
+ let oneTimePasswordError;
+ if (isAddingUser && data.oneTimePassword?.trim() === '') {
+ oneTimePasswordError = i18n.requiredField;
+ }
+
+ const saveButtonEnabled = accountHasChanges && fieldsValid;
+
+ const getCanadianRegions = () => {
+ if (data.country !== 'CA') {
+ return null;
+ }
+
+ return (
+ <>
+ {i18n.province}
+
+ update('region', item.value)} options={canadianProvinceOptions} />
+
+ >
+ );
+ };
+
+ const handleSave = (e: any): void => {
+ e.preventDefault();
+
+ if (!fieldsValid) {
+ return;
+ }
+
+ onSave(isAddingUser && oneTimePasswordFieldVisible);
+ };
+
+ const generatePassword = () => {
+ const pwd = generateRandomAlphanumericStr('CcEVvFLlDXxH');
+ update('oneTimePassword', pwd);
+ };
+
+ const firstNameError = showRequiredFieldError && data.firstName.trim() === '' ? i18n.requiredField : '';
+ const lastNameError = showRequiredFieldError && data.lastName.trim() === '' ? i18n.requiredField : '';
+
+ let cancelLinkClasses = sharedStyles.cancelLink;
+ if (!saveButtonEnabled) {
+ cancelLinkClasses += ` ${sharedStyles.hidden}`;
+ }
+
+ return (
+
+ );
+};
+
+export default MainFields;
diff --git a/apps/client/src/components/accounts/manageAccount/ManageAccount.component.tsx b/apps/client/src/components/accounts/manageAccount/ManageAccount.component.tsx
new file mode 100644
index 000000000..ca99dae26
--- /dev/null
+++ b/apps/client/src/components/accounts/manageAccount/ManageAccount.component.tsx
@@ -0,0 +1,178 @@
+import React, { useState } from 'react';
+import { format, fromUnixTime, add } from 'date-fns';
+import MainFields from '~components/accounts/mainFields/MainFields.component';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import { LocalizedDatePicker, LocalizedDatePickerProvider } from '~components/datePicker/LocalizedDatePicker.component';
+import { getFormattedNum } from '@generatedata/utils/number';
+import * as dateStyles from '@generatedata/plugins/dist/dataTypes/Date/Date.scss';
+import C from '@generatedata/config/constants';
+import styles from './ManageAccount.scss';
+
+export type ManageAccountProps = {
+ i18n: any;
+ onSave: (state: ManageAccountState) => void;
+ initialState: ManageAccountState;
+ submitButtonLabel: string;
+ onCancel?: () => void;
+};
+
+export enum ExpiryOption {
+ none = 'none',
+ date = 'date'
+}
+
+export type ManageAccountState = {
+ firstName: string;
+ lastName: string;
+ email: string;
+ country: string;
+ region: string;
+ oneTimePassword?: string;
+ disabled: boolean;
+ expiry: ExpiryOption;
+ expiryDate: null | number;
+ numRowsGenerated: number;
+ isAddingUser: boolean;
+};
+
+const yearFromNow = Number(format(add(new Date(), { years: 1 }), 't'));
+
+const ManageAccount = ({ i18n, onCancel, onSave, initialState, submitButtonLabel }: ManageAccountProps) => {
+ const [data, setData] = useState(initialState);
+ const [showDatepicker, setShowDatepicker] = useState(false);
+ const [showErrors] = useState(false);
+
+ let accountHasChanges = data.firstName !== '' && data.lastName !== '' && data.email !== '' && data.country !== '';
+ if (data.country === 'CA' && data.region === '') {
+ accountHasChanges = false;
+ }
+
+ const onClickCancel = (): void => {
+ setData(initialState);
+ if (onCancel) {
+ onCancel();
+ }
+ };
+
+ const updateAccountData = (newData: any): void => {
+ setData({
+ ...data,
+ ...newData
+ });
+ };
+
+ const toggleAccountDisabled = (disabled: boolean): void => {
+ setData({
+ ...data,
+ disabled
+ });
+ };
+
+ const toggleExpiry = (expiry: ExpiryOption): void => {
+ setData({
+ ...data,
+ expiry
+ });
+
+ if (expiry === ExpiryOption.date) {
+ setShowDatepicker(true);
+ }
+ };
+
+ const onSelectDate = (expiryDate: any): void => {
+ setData({
+ ...data,
+ expiryDate
+ });
+ };
+
+ const accountData = {
+ firstName: data.firstName,
+ lastName: data.lastName,
+ email: data.email,
+ country: data.country,
+ region: data.region,
+ oneTimePassword: data.oneTimePassword,
+ numRowsGenerated: data.numRowsGenerated
+ };
+
+ let expiryLabel = i18n.selectExpiryDate;
+
+ if (data.expiryDate) {
+ expiryLabel = format(fromUnixTime(Math.round(data.expiryDate / 1000)), C.DATE_FORMAT);
+ }
+
+ return (
+
+
+ {
+ if (!setOneTimePassword) {
+ data.oneTimePassword = undefined;
+ }
+ onSave(data);
+ }}
+ isAddingUser={data.isAddingUser}
+ />
+
+
+
+
+ toggleAccountDisabled(e.target.checked)}
+ />
+ {i18n.accountDisabled}
+
+
+
+
+
+ toggleExpiry(ExpiryOption.none)}
+ name="expiry"
+ checked={data.expiry === 'none'}
+ style={{ marginRight: 6 }}
+ />
+ toggleExpiry(ExpiryOption.date)}
+ name="expiry"
+ checked={data.expiry === 'date'}
+ />
+
+
+
+
+
{i18n.totalNumGeneratedRows}
+
{getFormattedNum(initialState.numRowsGenerated)}
+
+
+
+
+
+ onSelectDate(format(val, 'T'))}
+ onClose={(): void => setShowDatepicker(false)}
+ />
+
+
+
+ );
+};
+
+export default ManageAccount;
diff --git a/apps/client/src/components/accounts/manageAccount/ManageAccount.scss b/apps/client/src/components/accounts/manageAccount/ManageAccount.scss
new file mode 100644
index 000000000..0c536e507
--- /dev/null
+++ b/apps/client/src/components/accounts/manageAccount/ManageAccount.scss
@@ -0,0 +1,38 @@
+.root {
+ display: flex;
+ flex: 1;
+ margin-bottom: 15px;
+}
+
+.rightCol {
+ flex: 1;
+ margin-left: 20px;
+ border-left: 1px solid #f2f2f2;
+ padding-left: 20px;
+}
+
+.disabledFieldRow {
+ display: flex;
+ margin-bottom: 15px;
+ margin-top: 2px;
+}
+
+.rightBlock {
+ & > div {
+ margin-bottom: 15px;
+ font-size: 32px;
+ }
+}
+
+@media (max-width: 720px) {
+ .root {
+ flex-direction: column;
+ }
+
+ .rightCol {
+ margin-top: 20px;
+ margin-left: 0;
+ border-left: 0;
+ padding-left: 0;
+ }
+}
diff --git a/apps/client/src/components/accounts/manageAccount/ManageAccount.scss.d.ts b/apps/client/src/components/accounts/manageAccount/ManageAccount.scss.d.ts
new file mode 100644
index 000000000..b548ad859
--- /dev/null
+++ b/apps/client/src/components/accounts/manageAccount/ManageAccount.scss.d.ts
@@ -0,0 +1,15 @@
+declare namespace ManageAccountScssNamespace {
+ export interface IManageAccountScss {
+ disabledFieldRow: string;
+ rightBlock: string;
+ rightCol: string;
+ root: string;
+ }
+}
+
+declare const ManageAccountScssModule: ManageAccountScssNamespace.IManageAccountScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ManageAccountScssNamespace.IManageAccountScss;
+};
+
+export = ManageAccountScssModule;
diff --git a/apps/client/src/components/copyToClipboard/CopyToClipboard.scss b/apps/client/src/components/copyToClipboard/CopyToClipboard.scss
new file mode 100644
index 000000000..f755a16df
--- /dev/null
+++ b/apps/client/src/components/copyToClipboard/CopyToClipboard.scss
@@ -0,0 +1,9 @@
+.copyIcon {
+ color: #bbbbbb;
+ cursor: pointer;
+ font-size: 12px;
+}
+
+:global(.MuiAlert-message) {
+ font-size: 13px;
+}
diff --git a/apps/client/src/components/copyToClipboard/CopyToClipboard.scss.d.ts b/apps/client/src/components/copyToClipboard/CopyToClipboard.scss.d.ts
new file mode 100644
index 000000000..49cfc1c6a
--- /dev/null
+++ b/apps/client/src/components/copyToClipboard/CopyToClipboard.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace CopyToClipboardScssNamespace {
+ export interface ICopyToClipboardScss {
+ copyIcon: string;
+ }
+}
+
+declare const CopyToClipboardScssModule: CopyToClipboardScssNamespace.ICopyToClipboardScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: CopyToClipboardScssNamespace.ICopyToClipboardScss;
+};
+
+export = CopyToClipboardScssModule;
diff --git a/apps/client/src/components/copyToClipboard/CopyToClipboard.tsx b/apps/client/src/components/copyToClipboard/CopyToClipboard.tsx
new file mode 100644
index 000000000..b875c2bb5
--- /dev/null
+++ b/apps/client/src/components/copyToClipboard/CopyToClipboard.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import FileCopyIcon from '@mui/icons-material/FileCopy';
+import { addToast } from '@generatedata/utils/general';
+import styles from './CopyToClipboard.scss';
+
+const Copy = ({ message, tooltip, content }: any) => {
+ const onCopy = (): void => {
+ addToast({
+ type: 'success',
+ message,
+ verticalPosition: 'top'
+ });
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default Copy;
diff --git a/apps/client/src/components/copyToClipboard/__tests__/CopyToClipboard.test.tsx b/apps/client/src/components/copyToClipboard/__tests__/CopyToClipboard.test.tsx
new file mode 100644
index 000000000..d87ed6ac4
--- /dev/null
+++ b/apps/client/src/components/copyToClipboard/__tests__/CopyToClipboard.test.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import CopyToClipboard from '../CopyToClipboard';
+
+jest.mock('copy-to-clipboard', () => {
+ return jest.fn();
+});
+
+const defaultProps = {
+ message: 'Message here',
+ tooltip: 'Tooltip here',
+ content: 'Content here',
+ autoHideDuration: 0
+};
+
+describe('CopyToClipboard', () => {
+ it('renders copy icon with tooltip', () => {
+ const { baseElement } = render( );
+
+ expect(baseElement.querySelector('.copyIcon')).toBeTruthy();
+ expect(baseElement.innerHTML).toContain(defaultProps.tooltip);
+ });
+});
diff --git a/apps/client/src/components/creatablePillField/CreatablePillField.scss b/apps/client/src/components/creatablePillField/CreatablePillField.scss
new file mode 100644
index 000000000..0e84a8742
--- /dev/null
+++ b/apps/client/src/components/creatablePillField/CreatablePillField.scss
@@ -0,0 +1,6 @@
+@use '../../styles/variables' as c;
+
+.errorField > div,
+.errorField:hover > div {
+ border: 1px solid c.$error;
+}
diff --git a/apps/client/src/components/creatablePillField/CreatablePillField.scss.d.ts b/apps/client/src/components/creatablePillField/CreatablePillField.scss.d.ts
new file mode 100644
index 000000000..98d9da3ae
--- /dev/null
+++ b/apps/client/src/components/creatablePillField/CreatablePillField.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace CreatablePillFieldScssNamespace {
+ export interface ICreatablePillFieldScss {
+ errorField: string;
+ }
+}
+
+declare const CreatablePillFieldScssModule: CreatablePillFieldScssNamespace.ICreatablePillFieldScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: CreatablePillFieldScssNamespace.ICreatablePillFieldScss;
+};
+
+export = CreatablePillFieldScssModule;
diff --git a/apps/client/src/components/creatablePillField/CreatablePillField.tsx b/apps/client/src/components/creatablePillField/CreatablePillField.tsx
new file mode 100644
index 000000000..70029da7d
--- /dev/null
+++ b/apps/client/src/components/creatablePillField/CreatablePillField.tsx
@@ -0,0 +1,158 @@
+import * as React from 'react';
+import { components } from 'react-select';
+import CreatableSelect from 'react-select/creatable';
+import { SortableContainer, SortableElement } from 'react-sortable-hoc';
+import { DropdownOption } from '../dropdown/Dropdown';
+import { ErrorTooltip } from '~components/tooltips';
+import { arrayMove } from '@generatedata/utils/array';
+import styles from './CreatablePillField.scss';
+
+export const SortableMultiValue = SortableElement((props: any) => {
+ const onMouseDown = (e: any): void => {
+ e.preventDefault();
+ e.stopPropagation();
+ };
+ const innerProps = { onMouseDown };
+ return ;
+});
+
+const customComponents = {
+ DropdownIndicator: null,
+ MultiValue: SortableMultiValue
+};
+
+const selectStyles = {
+ control: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base,
+ boxShadow: 'none',
+ minHeight: 30,
+ maxHeight: 100,
+ overflow: 'scroll'
+ }),
+ dropdownIndicator: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base,
+ padding: 4
+ }),
+ clearIndicator: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base,
+ padding: 4
+ }),
+ multiValue: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base,
+ backgroundColor: '#e0ebfd'
+ }),
+ valueContainer: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base,
+ padding: '0px 2px'
+ }),
+ container: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base
+ }),
+ input: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base,
+ margin: 0,
+ padding: 0
+ }),
+ indicatorsContainer: (base: React.CSSProperties): React.CSSProperties => ({
+ ...base,
+ alignItems: 'flex-start'
+ })
+};
+
+export const createOption = (label: string): DropdownOption => ({
+ label,
+ value: label
+});
+
+const SortableCreatableSelect: any = SortableContainer(CreatableSelect);
+
+export type CreatablePillFieldProps = {
+ onChange: (newValues: string[]) => void;
+ value: string[];
+ error: string;
+ placeholder: string;
+ onValidateNewItem?: (value: string) => boolean;
+ className?: string;
+ isClearable?: boolean;
+};
+
+const CreatablePillField = ({
+ onChange,
+ onValidateNewItem,
+ value,
+ error,
+ placeholder,
+ className,
+ isClearable = true
+}: CreatablePillFieldProps) => {
+ const [tempValue, setTempValue] = React.useState('');
+ const options = value.map(createOption);
+
+ const handleInputChange = (newTempValue: string): void => setTempValue(newTempValue);
+ const handleKeyDown = (e: any): void => {
+ if (!tempValue) {
+ return;
+ }
+ switch (e.key) {
+ case 'Enter':
+ case 'Tab':
+ if (onValidateNewItem) {
+ const isValid = onValidateNewItem(tempValue);
+ if (!isValid) {
+ return;
+ }
+ }
+ setTempValue('');
+ onChange([...value, tempValue]);
+ e.preventDefault();
+ }
+ };
+
+ const onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }): void => {
+ const sortedOptions = arrayMove(options, oldIndex, newIndex);
+ onChange(sortedOptions.map((i: DropdownOption) => i.value));
+ };
+
+ const classes: string[] = [];
+ if (className) {
+ classes.push(className);
+ }
+ if (error) {
+ classes.push(styles.errorField);
+ }
+
+ // onCreateOption={(a: any) => console.log(a)}
+ return (
+
+ node.getBoundingClientRect()}
+ isClearable={isClearable}
+ isMulti
+ onSortEnd={onSortEnd}
+ menuIsOpen={false}
+ onChange={(options: any): void => {
+ const newValues = options ? options.map(({ value }: DropdownOption) => value) : [];
+ onChange(newValues);
+ }}
+ onInputChange={handleInputChange}
+ onKeyDown={handleKeyDown}
+ placeholder={placeholder}
+ value={options}
+ menuPlacement="auto"
+ menuPortalTarget={document.body}
+ />
+
+ );
+};
+CreatablePillField.defaultProps = {
+ placeholder: 'Press enter to create item',
+ error: ''
+};
+
+export default CreatablePillField;
diff --git a/apps/client/src/components/creatablePillField/__tests__/CreatablePillField.test.tsx b/apps/client/src/components/creatablePillField/__tests__/CreatablePillField.test.tsx
new file mode 100644
index 000000000..aef4f7a00
--- /dev/null
+++ b/apps/client/src/components/creatablePillField/__tests__/CreatablePillField.test.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import CreatablePillField from '../CreatablePillField';
+
+const defaultProps = {
+ onChange: () => {},
+ value: ['one', 'two', 'three']
+};
+
+describe('CreatablePillField', () => {
+ it('renders', () => {
+ const { baseElement } = render( );
+ });
+});
diff --git a/apps/client/src/components/datePicker/LocalizedDatePicker.component.tsx b/apps/client/src/components/datePicker/LocalizedDatePicker.component.tsx
new file mode 100644
index 000000000..a35379bfa
--- /dev/null
+++ b/apps/client/src/components/datePicker/LocalizedDatePicker.component.tsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import DateFnsUtils from '@date-io/date-fns';
+// import { DatePicker, MuiPickersUtilsProvider } from '@mui/lab';
+import { DatePicker } from '@mui/x-date-pickers/DatePicker';
+import { getLocale, getStrings } from '@generatedata/utils/lang';
+import { arDZ, de, enUS, es, fr, ja, hi, nl, pt, ru, ta, zhCN } from 'date-fns/locale';
+
+// localized wrapper for the date picker provider
+export const LocalizedDatePicker = (props: any) => {
+ const { core: i18n } = getStrings();
+
+ return ;
+};
+
+// localized wrapper for the date picker provider
+export const LocalizedDatePickerProvider = ({ children }: any) => {
+ const locale = getLocale();
+
+ const localeMap = {
+ ar: arDZ,
+ en: enUS,
+ fr,
+ de,
+ es,
+ ja,
+ hi,
+ nl,
+ pt,
+ ru,
+ ta,
+ zh: zhCN
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/apps/client/src/components/dialogs/index.tsx b/apps/client/src/components/dialogs/index.tsx
new file mode 100644
index 000000000..2a148985a
--- /dev/null
+++ b/apps/client/src/components/dialogs/index.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import MuiDialog from '@mui/material/Dialog';
+import MuiDialogTitle from '@mui/material/DialogTitle';
+import MuiDialogContent from '@mui/material/DialogContent';
+import MuiDialogActions from '@mui/material/DialogActions';
+import { styled, withStyles, makeStyles } from '@mui/material';
+import IconButton from '@mui/material/IconButton';
+import CloseIcon from '@mui/icons-material/Close';
+import Typography from '@mui/material/Typography';
+
+const dialogStyles = (theme: any): any => ({
+ root: {
+ margin: 0,
+ padding: theme.spacing(2)
+ },
+ closeButton: {
+ position: 'absolute',
+ right: theme.spacing(1),
+ top: 6,
+ color: theme.palette.grey[500]
+ }
+});
+
+export const DialogTitle = withStyles(dialogStyles)((props: any): any => {
+ const { children, classes, onClose, customCloseIcon, ...other } = props;
+ const Close = customCloseIcon ? customCloseIcon : CloseIcon;
+
+ return (
+
+ {children}
+ {onClose ? (
+
+
+
+ ) : null}
+
+ );
+});
+
+export const DialogContent = styled(MuiDialogContent)((theme) => ({
+ root: {
+ padding: theme.spacing(2)
+ }
+}));
+
+export const DialogActions = styled(MuiDialogActions)((theme) => ({
+ root: {
+ margin: 0,
+ padding: theme.spacing(1)
+ }
+}));
+
+const useDialogStyles = makeStyles({
+ root: {
+ // @ts-ignore-line
+ zIndex: '5005 !important',
+ width: '100%'
+ },
+ paper: {
+ borderRadius: 6,
+ maxWidth: 'inherit'
+ }
+});
+
+export const Dialog = (props: any) => {
+ const { root, paper } = useDialogStyles(props);
+
+ return ;
+};
diff --git a/apps/client/src/components/dropdown/Dropdown.scss b/apps/client/src/components/dropdown/Dropdown.scss
new file mode 100644
index 000000000..9deb9690c
--- /dev/null
+++ b/apps/client/src/components/dropdown/Dropdown.scss
@@ -0,0 +1,5 @@
+@use '../../styles/variables' as c;
+
+.error > div {
+ border: 1px solid c.$error !important;
+}
diff --git a/apps/client/src/components/dropdown/Dropdown.scss.d.ts b/apps/client/src/components/dropdown/Dropdown.scss.d.ts
new file mode 100644
index 000000000..946c30fdf
--- /dev/null
+++ b/apps/client/src/components/dropdown/Dropdown.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace DropdownScssNamespace {
+ export interface IDropdownScss {
+ error: string;
+ }
+}
+
+declare const DropdownScssModule: DropdownScssNamespace.IDropdownScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: DropdownScssNamespace.IDropdownScss;
+};
+
+export = DropdownScssModule;
diff --git a/apps/client/src/components/dropdown/Dropdown.tsx b/apps/client/src/components/dropdown/Dropdown.tsx
new file mode 100644
index 000000000..6fcf3ef8b
--- /dev/null
+++ b/apps/client/src/components/dropdown/Dropdown.tsx
@@ -0,0 +1,88 @@
+import * as React from 'react';
+import Select from 'react-select';
+import { getStrings } from '@generatedata/utils/lang';
+import C from '@generatedata/config/constants';
+import * as styles from './Dropdown.scss';
+
+export type DropdownOption = {
+ value: string;
+ label: string;
+};
+
+const selectStyles = {
+ control: (provided: any): any => ({
+ ...provided,
+ minHeight: 20,
+ boxShadow: 'none'
+ }),
+ indicatorsContainer: (provided: any): any => ({
+ ...provided,
+ height: 28
+ }),
+ indicatorContainer: (provided: any): any => ({
+ ...provided,
+ padding: 5
+ }),
+ menuPortal: (base: any): any => ({ ...base, zIndex: C.ZINDEXES.DIALOG })
+};
+
+const Dropdown = ({ value, isGrouped, options, hasError, placeholder, ...props }: any) => {
+ const i18n = getStrings();
+
+ // react-select has a terrible API. You need to pass the entire selected object as the `value` prop to prefill it.
+ // That's not something you'd normally save - you just want to save a single value, because, well, duh. This makes
+ // thing like localization a total pain. So to make it behave like a sane component, our component here just accepts
+ // a single `value` prop - which is either the single value for the field, or an array if it's isMulti. This code
+ // converts it to an object/array of objects for react-select
+ let selectedValue: any = '';
+ if (isGrouped) {
+ if (props.isMulti) {
+ selectedValue = [];
+ options.filter(() => {
+ // group: any
+ // TODO
+ return true;
+ });
+ } else {
+ options.find((group: any) => {
+ const found = group.options.find((row: any) => row.value === value);
+ if (found && found !== -1) {
+ selectedValue = found;
+ return true;
+ }
+ return false;
+ });
+ }
+ } else {
+ if (props.isMulti) {
+ selectedValue = options.filter((row: any): any => value.indexOf(row.value) !== -1);
+ } else {
+ selectedValue = options.find((row: any): any => row.value === value);
+ }
+ }
+
+ const placeholderStr = placeholder ? placeholder : i18n.core.selectEllipsis;
+
+ // for debugging: menuIsOpen={true}
+
+ let className = props.className || '';
+ if (hasError) {
+ className += ` ${styles.error}`;
+ }
+
+ return (
+
+ );
+};
+
+export default Dropdown;
diff --git a/apps/client/src/components/email/Email.component.tsx b/apps/client/src/components/email/Email.component.tsx
new file mode 100644
index 000000000..54457fb0d
--- /dev/null
+++ b/apps/client/src/components/email/Email.component.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import CopyToClipboard from '../copyToClipboard/CopyToClipboard';
+import styles from './Email.scss';
+
+export type EmailProps = {
+ email: string;
+ i18n: any;
+ text?: string;
+};
+
+const Email = ({ email, text = '', i18n }: EmailProps) => {
+ const textString = text || email;
+
+ return (
+ <>
+
+ {textString}
+
+
+ >
+ );
+};
+
+export default Email;
diff --git a/apps/client/src/components/email/Email.container.tsx b/apps/client/src/components/email/Email.container.tsx
new file mode 100644
index 000000000..fad85ec25
--- /dev/null
+++ b/apps/client/src/components/email/Email.container.tsx
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import Email, { EmailProps } from './Email.component';
+import { Store } from '~types/general';
+
+const mapStateToProps = (state: Store): Partial => ({
+ i18n: selectors.getCoreI18n(state)
+});
+
+const container: any = connect(mapStateToProps)(Email);
+
+export default container;
diff --git a/apps/client/src/components/email/Email.scss b/apps/client/src/components/email/Email.scss
new file mode 100644
index 000000000..5635e3cef
--- /dev/null
+++ b/apps/client/src/components/email/Email.scss
@@ -0,0 +1,3 @@
+.copy {
+ margin-left: 4px;
+}
diff --git a/apps/client/src/components/email/Email.scss.d.ts b/apps/client/src/components/email/Email.scss.d.ts
new file mode 100644
index 000000000..bfe0bd497
--- /dev/null
+++ b/apps/client/src/components/email/Email.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace Email2ScssNamespace {
+ export interface IEmail2Scss {
+ copy: string;
+ }
+}
+
+declare const Email2ScssModule: Email2ScssNamespace.IEmail2Scss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: Email2ScssNamespace.IEmail2Scss;
+};
+
+export = Email2ScssModule;
diff --git a/apps/client/src/components/icons.tsx b/apps/client/src/components/icons.tsx
new file mode 100644
index 000000000..1920a4c7f
--- /dev/null
+++ b/apps/client/src/components/icons.tsx
@@ -0,0 +1,27 @@
+import * as React from 'react';
+
+export const Github = () => (
+
+
+
+);
+
+// Cockroach by Michael Thompson from the Noun Project
+export const Cockroach = ({ size = 100 }) => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/apps/client/src/components/loaders/loaders.scss b/apps/client/src/components/loaders/loaders.scss
new file mode 100644
index 000000000..57c8e7474
--- /dev/null
+++ b/apps/client/src/components/loaders/loaders.scss
@@ -0,0 +1,11 @@
+.dialogLoadingSpinner {
+ display: flex;
+ align-items: center;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: #ffffff;
+ opacity: 0.5;
+ justify-content: center;
+ transition: all 0.5s ease-in;
+}
diff --git a/apps/client/src/components/loaders/loaders.scss.d.ts b/apps/client/src/components/loaders/loaders.scss.d.ts
new file mode 100644
index 000000000..eb2672857
--- /dev/null
+++ b/apps/client/src/components/loaders/loaders.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace LoadersScssNamespace {
+ export interface ILoadersScss {
+ dialogLoadingSpinner: string;
+ }
+}
+
+declare const LoadersScssModule: LoadersScssNamespace.ILoadersScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: LoadersScssNamespace.ILoadersScss;
+};
+
+export = LoadersScssModule;
diff --git a/apps/client/src/components/loaders/loaders.tsx b/apps/client/src/components/loaders/loaders.tsx
new file mode 100644
index 000000000..e9960756a
--- /dev/null
+++ b/apps/client/src/components/loaders/loaders.tsx
@@ -0,0 +1,101 @@
+import React from 'react';
+import CircularProgress from '@mui/material/CircularProgress';
+import * as styles from './loaders.scss';
+
+export const SmallSpinner = (props: any): any => (
+
+);
+
+export const MediumSpinner = (props: any): any => (
+
+);
+
+export const DefaultSpinner = () => ;
+
+export type DialogLoadingSpinnerProps = {
+ visible: boolean;
+};
+
+export const DialogLoadingSpinner = ({ visible }: DialogLoadingSpinnerProps) => {
+ if (!visible) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
+
+export const Centered = ({ children }: any) => (
+
+ {children}
+
+);
+
+export const FullPageLoadingSpinner = () => (
+ <>
+
+
+
+
+
+ >
+);
diff --git a/apps/client/src/components/pills/BasePill.scss b/apps/client/src/components/pills/BasePill.scss
new file mode 100644
index 000000000..cffc07470
--- /dev/null
+++ b/apps/client/src/components/pills/BasePill.scss
@@ -0,0 +1,14 @@
+.row {
+ margin-bottom: 10px;
+
+ input {
+ margin: 0 6px 0 0;
+ }
+
+ & > * {
+ margin-right: 10px;
+ }
+ &:last-child {
+ margin-right: 0;
+ }
+}
diff --git a/apps/client/src/components/pills/BasePill.scss.d.ts b/apps/client/src/components/pills/BasePill.scss.d.ts
new file mode 100644
index 000000000..9358d5cca
--- /dev/null
+++ b/apps/client/src/components/pills/BasePill.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace BasePillScssNamespace {
+ export interface IBasePillScss {
+ row: string;
+ }
+}
+
+declare const BasePillScssModule: BasePillScssNamespace.IBasePillScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: BasePillScssNamespace.IBasePillScss;
+};
+
+export = BasePillScssModule;
diff --git a/apps/client/src/components/pills/BasePill.tsx b/apps/client/src/components/pills/BasePill.tsx
new file mode 100644
index 000000000..527532382
--- /dev/null
+++ b/apps/client/src/components/pills/BasePill.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import Button from '@mui/material/Button';
+import { Tooltip } from '../tooltips';
+import styles from './BasePill.scss';
+
+export const PillRow = ({ className, children }: any) => {
+ let classes = styles.row;
+ if (className) {
+ classes += ` ${className}`;
+ }
+ return {children}
;
+};
+
+export const enum PillType {
+ radio = 'radio',
+ checkbox = 'checkbox'
+}
+
+type PillProps = {
+ type: PillType;
+ label: string;
+ onClick: () => void;
+ name: string;
+ checked: boolean;
+ disabled?: boolean;
+ tooltip?: string;
+ style?: any;
+};
+
+const BasePill = ({ type, label, onClick, name, checked, disabled, tooltip, style }: PillProps) => {
+ const button = (
+
+ {}} />
+ {label}
+
+ );
+
+ if (tooltip) {
+ return (
+ }
+ arrow
+ disableHoverListener={disabled}
+ disableFocusListener={disabled}
+ >
+ {button}
+
+ );
+ }
+
+ return button;
+};
+
+BasePill.defaultProps = {
+ disabled: false,
+ style: {}
+};
+
+export default BasePill;
diff --git a/apps/client/src/components/pills/CheckboxPill.tsx b/apps/client/src/components/pills/CheckboxPill.tsx
new file mode 100644
index 000000000..366762088
--- /dev/null
+++ b/apps/client/src/components/pills/CheckboxPill.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+import BasePill, { PillType } from './BasePill';
+
+type CheckboxPillProps = {
+ label: string;
+ onClick: () => void;
+ name: string;
+ checked: boolean;
+ disabled?: boolean;
+ tooltip?: string;
+ style?: any;
+};
+
+const CheckboxPill = ({ label, onClick, name, checked, disabled, tooltip, style }: CheckboxPillProps) => (
+
+);
+
+export default CheckboxPill;
diff --git a/apps/client/src/components/pills/RadioPill.tsx b/apps/client/src/components/pills/RadioPill.tsx
new file mode 100644
index 000000000..a9e19a4d4
--- /dev/null
+++ b/apps/client/src/components/pills/RadioPill.tsx
@@ -0,0 +1,29 @@
+import React from 'react';
+import BasePill, { PillType } from './BasePill';
+
+export { PillRow as RadioPillRow } from './BasePill';
+
+type RadioPillProps = {
+ label: string;
+ onClick: () => void;
+ name: string;
+ checked: boolean;
+ disabled?: boolean;
+ tooltip?: string;
+ style?: any;
+};
+
+const RadioPill = ({ label, onClick, name, checked, disabled, tooltip, style }: RadioPillProps) => (
+
+);
+
+export default RadioPill;
diff --git a/apps/client/src/components/pills/__tests__/RadioPill.test.tsx b/apps/client/src/components/pills/__tests__/RadioPill.test.tsx
new file mode 100644
index 000000000..ee012e137
--- /dev/null
+++ b/apps/client/src/components/pills/__tests__/RadioPill.test.tsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import RadioPill, { RadioPillRow } from '../RadioPill';
+import { render } from '@testing-library/react';
+
+describe('RadioPillRow', () => {
+ it('renders children', () => {
+ const { baseElement } = render(
+
+ Children!
+
+ );
+
+ expect(baseElement.innerHTML).toContain('Children!');
+ });
+
+ it('includes the classname passed', () => {
+ const { baseElement } = render(
+
+ Children!
+
+ );
+
+ expect(baseElement.querySelector('.this-is-a-test')).toBeTruthy();
+ });
+});
+
+describe('RadioPill', () => {
+ it('renders label', () => {
+ const { container } = render(
+ {}} name="name123" checked={false} disabled={false} tooltip="Yo yo yo" />
+ );
+
+ expect(container.innerHTML).toContain('Label!');
+ });
+
+ it('includes name field', () => {
+ const { container } = render(
+ {}} name="name123" checked={false} disabled={false} />
+ );
+
+ expect(container.querySelector('[name=name123]')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/components/tables/TableHeader.component.tsx b/apps/client/src/components/tables/TableHeader.component.tsx
new file mode 100644
index 000000000..9340df805
--- /dev/null
+++ b/apps/client/src/components/tables/TableHeader.component.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import ArrowDropUp from '@mui/icons-material/ArrowDropUp';
+import ArrowDropDown from '@mui/icons-material/ArrowDropDown';
+import * as styles from './TableHeader.scss';
+
+export type TableCol = {
+ label?: string;
+ field?: string;
+ className?: string;
+ sortable?: boolean;
+};
+
+export const enum ColSortDir {
+ asc = 'ASC',
+ desc = 'DESC'
+}
+
+export type TableHeaderProps = {
+ cols: TableCol[];
+ sortCol?: string;
+ sortDir?: ColSortDir;
+ onSort?: (col: string, dir: ColSortDir) => void;
+};
+
+const TableHeader = ({ cols, sortCol, sortDir, onSort }: TableHeaderProps) => {
+ const columns = cols.map((col: TableCol, index: number) => {
+ let colClasses = styles.colHeader;
+ if (col.className) {
+ colClasses += ` ${col.className}`;
+ }
+ if (col.sortable) {
+ colClasses += ` ${styles.sortable}`;
+ }
+
+ const colProps: any = {
+ className: colClasses
+ };
+
+ let sorter: any = null;
+ let colSortDir = sortDir === ColSortDir.asc ? ColSortDir.desc : ColSortDir.asc;
+
+ if (col.field === sortCol) {
+ if (sortDir === ColSortDir.asc) {
+ sorter = ;
+ colSortDir = ColSortDir.desc;
+ } else {
+ sorter = ;
+ colSortDir = ColSortDir.asc;
+ }
+ }
+
+ if (col.sortable) {
+ colProps.onClick = (): void => onSort!(col.field!, colSortDir);
+ }
+
+ return (
+
+ {col.label || ''}
+ {sorter}
+
+ );
+ });
+
+ return {columns}
;
+};
+
+export default TableHeader;
diff --git a/apps/client/src/components/tables/TableHeader.scss b/apps/client/src/components/tables/TableHeader.scss
new file mode 100644
index 000000000..8ccbf5f97
--- /dev/null
+++ b/apps/client/src/components/tables/TableHeader.scss
@@ -0,0 +1,39 @@
+.header {
+ font-weight: bold;
+ font-size: 13px;
+ color: #666666;
+ padding-top: 2px;
+ border-bottom: 1px solid #333333;
+}
+
+.row {
+ display: flex;
+ width: 100%;
+ padding: 6px;
+ align-items: center;
+ font-size: 13px;
+}
+
+.colHeader {
+ display: flex;
+ align-items: center;
+
+ &.sortable {
+ cursor: pointer;
+
+ svg {
+ font-size: 20px;
+ margin-right: 6px;
+ }
+
+ &:hover {
+ svg {
+ fill: #111111;
+ }
+ }
+ }
+
+ span {
+ flex: 1;
+ }
+}
diff --git a/apps/client/src/components/tables/TableHeader.scss.d.ts b/apps/client/src/components/tables/TableHeader.scss.d.ts
new file mode 100644
index 000000000..4df8af32c
--- /dev/null
+++ b/apps/client/src/components/tables/TableHeader.scss.d.ts
@@ -0,0 +1,15 @@
+declare namespace TableHeaderScssNamespace {
+ export interface ITableHeaderScss {
+ colHeader: string;
+ header: string;
+ row: string;
+ sortable: string;
+ }
+}
+
+declare const TableHeaderScssModule: TableHeaderScssNamespace.ITableHeaderScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: TableHeaderScssNamespace.ITableHeaderScss;
+};
+
+export = TableHeaderScssModule;
diff --git a/apps/client/src/components/toast/Toast.component.tsx b/apps/client/src/components/toast/Toast.component.tsx
new file mode 100644
index 000000000..0217393e6
--- /dev/null
+++ b/apps/client/src/components/toast/Toast.component.tsx
@@ -0,0 +1,78 @@
+import React, { useEffect, useImperativeHandle, useRef } from 'react';
+import MuiAlert, { AlertProps, Color } from '@mui/material/Alert';
+import Snackbar, { SnackbarOrigin } from '@mui/material/Snackbar';
+import Portal from '~components/Portal';
+import { initToast, ToastType } from '@generatedata/utils/general';
+import './Toast.scss';
+
+const defaultMessage: ToastType = {
+ type: 'success' as Color,
+ message: '',
+ verticalPosition: 'top' as SnackbarOrigin['vertical'],
+ horizontalPosition: 'center' as SnackbarOrigin['horizontal'],
+ autoHideDuration: 5000
+};
+
+const Alert = (props: AlertProps) => ;
+
+let timeout: any;
+const Toast = () => {
+ const snackbarRef = useRef();
+ const [open, setOpen] = React.useState(false);
+ const [payload, setPayload] = React.useState(defaultMessage);
+
+ useEffect(() => {
+ initToast(snackbarRef.current);
+ }, []);
+
+ // @ts-ignore-line
+ useImperativeHandle(snackbarRef, () => ({
+ add: (message: ToastType): void => {
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+
+ const payload = {
+ ...defaultMessage,
+ ...message
+ } as ToastType;
+ setOpen(true);
+ setPayload(payload);
+
+ timeout = setTimeout(() => {
+ setOpen(false);
+ clearTimeout(timeout);
+ }, payload.autoHideDuration);
+ }
+ }));
+
+ const handleClose = (): void => {
+ setOpen(false);
+ };
+
+ return (
+
+
+
+ {payload.message}
+
+
+
+ );
+};
+
+Toast.defaultProps = {
+ type: 'success',
+ autoHideDuration: 5000
+};
+
+export default Toast;
diff --git a/apps/client/src/components/toast/Toast.scss b/apps/client/src/components/toast/Toast.scss
new file mode 100644
index 000000000..63e0da394
--- /dev/null
+++ b/apps/client/src/components/toast/Toast.scss
@@ -0,0 +1,3 @@
+:global(#gd-toast) :global(.MuiSnackbar-root) {
+ z-index: 5010;
+}
diff --git a/apps/client/src/components/toast/Toast.scss.d.ts b/apps/client/src/components/toast/Toast.scss.d.ts
new file mode 100644
index 000000000..e39605711
--- /dev/null
+++ b/apps/client/src/components/toast/Toast.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace ToastScssNamespace {
+ export interface IToastScss {
+ toast: string;
+ }
+}
+
+declare const ToastScssModule: ToastScssNamespace.IToastScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ToastScssNamespace.IToastScss;
+};
+
+export = ToastScssModule;
diff --git a/apps/client/src/components/toast/__tests__/Toast.test.tsx b/apps/client/src/components/toast/__tests__/Toast.test.tsx
new file mode 100644
index 000000000..dd3d210ab
--- /dev/null
+++ b/apps/client/src/components/toast/__tests__/Toast.test.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import Toast from '../Toast.component';
+import { render, act } from '@testing-library/react';
+import { addToast } from '@generatedata/utils/general';
+
+describe('Toast', () => {
+ it('renders', () => {
+ const { baseElement } = render( );
+ expect(baseElement.querySelector('#gd-toast')).toBeTruthy();
+ });
+
+ it('shows a toast', () => {
+ const { baseElement } = render( );
+
+ act(() => {
+ addToast({
+ message: 'Hello world!',
+ type: 'success'
+ });
+ });
+
+ expect(baseElement.innerHTML).toContain('Hello world!');
+ });
+});
diff --git a/apps/client/src/components/tooltips.tsx b/apps/client/src/components/tooltips.tsx
new file mode 100644
index 000000000..ba411dc75
--- /dev/null
+++ b/apps/client/src/components/tooltips.tsx
@@ -0,0 +1,43 @@
+import MuiTooltip from '@mui/material/Tooltip';
+import { styled } from '@mui/material/styles';
+
+export const HtmlTooltip = styled(MuiTooltip)(() => ({
+ tooltip: {
+ backgroundColor: '#ffffff',
+ color: 'rgba(0, 0, 0, 0.87)',
+ fontSize: 12,
+ padding: 0,
+ boxShadow: '3px 3px 6px #999999'
+ },
+ arrow: {
+ color: '#ffffff'
+ }
+}));
+
+export const Tooltip = styled(MuiTooltip)(() => ({
+ tooltip: {
+ backgroundColor: '#333333',
+ maxWidth: 220,
+ color: '#dddddd',
+ lineHeight: '16px',
+ fontSize: 11,
+ padding: 10
+ },
+ arrow: {
+ color: '#333333'
+ }
+}));
+
+export const ErrorTooltip = styled(MuiTooltip)(() => ({
+ tooltip: {
+ backgroundColor: '#D80000',
+ maxWidth: 220,
+ color: '#ffffff',
+ lineHeight: '16px',
+ fontSize: 11,
+ padding: 10
+ },
+ arrow: {
+ color: '#D80000'
+ }
+}));
diff --git a/apps/client/src/core/ErrorBoundary.component.tsx b/apps/client/src/core/ErrorBoundary.component.tsx
new file mode 100644
index 000000000..028217bc1
--- /dev/null
+++ b/apps/client/src/core/ErrorBoundary.component.tsx
@@ -0,0 +1,76 @@
+import React from 'react';
+import { persistor } from './store';
+import Button from '@mui/material/Button';
+import { Cockroach } from '~components/icons';
+import Header from './header/Header.container';
+import Footer from './footer/Footer.container';
+
+class ErrorBoundary extends React.Component {
+ constructor(props: any) {
+ super(props);
+ this.state = {
+ hasError: false,
+ error: ''
+ };
+ this.onClear = this.onClear.bind(this);
+ }
+
+ static getDerivedStateFromError(error: string): any {
+ return {
+ hasError: true,
+ error
+ };
+ }
+
+ // componentDidCatch(error: any, errorInfo: any): any {
+ // // logErrorToMyService(error, errorInfo);
+ // }
+
+ onClear(): void {
+ persistor.purge().then(() => {
+ this.setState({
+ hasError: false,
+ error: ''
+ });
+ });
+ }
+
+ render(): any {
+ if (this.state.hasError) {
+ return (
+ <>
+
+
+
Something went terribly, terribly wrong.
+
+
+
+
+
+
+
+ Sorry! Some sort of error occurred. This project is still in alpha so you may see this page a little more than you'd like.
+ Feel free to complain about it via a{' '}
+
+ github issue
+ {' '}
+ — but we will get to it!
+
+
+
+ Curse the developer and start anew
+
+
+
{this.state.error}
+
+
+
+
+ >
+ );
+ }
+ return this.props.children;
+ }
+}
+
+export default ErrorBoundary;
diff --git a/apps/client/src/core/__tests__/ErrorBoundary.component.test.tsx b/apps/client/src/core/__tests__/ErrorBoundary.component.test.tsx
new file mode 100644
index 000000000..61a9426a1
--- /dev/null
+++ b/apps/client/src/core/__tests__/ErrorBoundary.component.test.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import ErrorBoundary from '../ErrorBoundary.component';
+
+describe('ErrorBoundary', () => {
+ it('renders', () => {
+ const { baseElement } = render(
+
+ Test
+
+ );
+
+ expect(baseElement.querySelector('.hello')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/account/Account.component.tsx b/apps/client/src/core/account/Account.component.tsx
new file mode 100644
index 000000000..aee599e67
--- /dev/null
+++ b/apps/client/src/core/account/Account.component.tsx
@@ -0,0 +1,78 @@
+import React, { useEffect, useState } from 'react';
+import { SelectedAccountTab } from '~types/account';
+import DataSets from './dataSets/DataSets.container';
+import YourAccount from './yourAccount/YourAccount.container';
+import ChangePassword from './changePassword/ChangePassword.container';
+import sharedStyles from '../../styles/shared.scss';
+import styles from './Account.scss';
+
+export type AccountPageProps = {
+ selectedTab: SelectedAccountTab;
+ onChangeTab: (tab: SelectedAccountTab) => void;
+ i18n: any;
+};
+
+const AccountPage = ({ selectedTab, onChangeTab, i18n }: AccountPageProps) => {
+ const [dataSetsClasses, setDataSetsClasses] = useState(styles.hidden);
+ const [yourAccountClasses, setYourAccountClasses] = useState(styles.hidden);
+ const [changePasswordClasses, setChangePasswordClasses] = useState(styles.hidden);
+
+ useEffect(() => {
+ let dsClasses = styles.hidden;
+ let cpClasses = styles.hidden;
+ let yaClasses = styles.hidden;
+
+ if (selectedTab === SelectedAccountTab.yourAccount) {
+ yaClasses = styles.shown;
+ } else if (selectedTab === SelectedAccountTab.dataSets) {
+ dsClasses = styles.shown;
+ } else {
+ cpClasses = styles.shown;
+ }
+
+ setDataSetsClasses(dsClasses);
+ setChangePasswordClasses(cpClasses);
+ setYourAccountClasses(yaClasses);
+ }, [selectedTab]);
+
+ const getTab = () => {
+ if (selectedTab === SelectedAccountTab.dataSets) {
+ return ;
+ }
+ if (selectedTab === SelectedAccountTab.yourAccount) {
+ return ;
+ }
+
+ return ;
+ };
+
+ return (
+
+
+
+ onChangeTab(SelectedAccountTab.dataSets)}
+ >
+ {i18n.dataSets}
+
+ onChangeTab(SelectedAccountTab.yourAccount)}
+ >
+ {i18n.yourAccount}
+
+ onChangeTab(SelectedAccountTab.changePassword)}
+ >
+ {i18n.changePassword}
+
+
+
+ {getTab()}
+
+ );
+};
+
+export default AccountPage;
diff --git a/apps/client/src/core/account/Account.container.ts b/apps/client/src/core/account/Account.container.ts
new file mode 100644
index 000000000..bb296ec75
--- /dev/null
+++ b/apps/client/src/core/account/Account.container.ts
@@ -0,0 +1,22 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountActions from '~store/account/account.actions';
+import * as accountSelectors from '~store/account/account.selectors';
+import AccountPage, { AccountPageProps } from './Account.component';
+import { withAuth } from '../auth/withAuth';
+import { Store } from '~types/general';
+import { SelectedAccountTab } from '~types/account';
+
+const mapStateToProps = (state: Store): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ selectedTab: accountSelectors.getSelectedTab(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ onChangeTab: (tab: SelectedAccountTab): any => dispatch(accountActions.onChangeTab(tab))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(AccountPage);
+
+export default withAuth(container);
diff --git a/apps/client/src/core/account/Account.scss b/apps/client/src/core/account/Account.scss
new file mode 100644
index 000000000..417778e02
--- /dev/null
+++ b/apps/client/src/core/account/Account.scss
@@ -0,0 +1,43 @@
+@use '../../styles/variables.scss';
+
+.hidden {
+ opacity: 0;
+}
+
+.shown {
+ opacity: 1;
+ transition: opacity 200ms ease-in-out;
+}
+
+.yourAccountPage {
+ display: flex;
+ flex-direction: row;
+ margin-bottom: 10px;
+ & > * {
+ flex: 1;
+ }
+}
+
+.rightCol {
+ border-left: 1px solid #f2f2f2;
+ padding-left: 20px;
+ height: 83%;
+}
+
+.rightBlock {
+ & > div {
+ margin-bottom: 15px;
+ font-size: 32px;
+ }
+}
+
+.yourAccountRightCol {
+ flex: 1;
+ margin-left: 20px;
+}
+
+@media (max-width: 720px) {
+ .yourAccountRightCol {
+ display: none;
+ }
+}
diff --git a/apps/client/src/core/account/Account.scss.d.ts b/apps/client/src/core/account/Account.scss.d.ts
new file mode 100644
index 000000000..bdb907d7b
--- /dev/null
+++ b/apps/client/src/core/account/Account.scss.d.ts
@@ -0,0 +1,17 @@
+declare namespace AccountScssNamespace {
+ export interface IAccountScss {
+ hidden: string;
+ rightBlock: string;
+ rightCol: string;
+ shown: string;
+ yourAccountPage: string;
+ yourAccountRightCol: string;
+ }
+}
+
+declare const AccountScssModule: AccountScssNamespace.IAccountScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: AccountScssNamespace.IAccountScss;
+};
+
+export = AccountScssModule;
diff --git a/apps/client/src/core/account/__tests__/Account.component.test.tsx b/apps/client/src/core/account/__tests__/Account.component.test.tsx
new file mode 100644
index 000000000..7c6f93ee4
--- /dev/null
+++ b/apps/client/src/core/account/__tests__/Account.component.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+import Account from '../Account.component';
+import { SelectedAccountTab } from '~types/account';
+
+const i18n = require('../../../i18n/en.json');
+
+describe('Account component', () => {
+ it('render', () => {
+ const { container } = renderWithStoreAndRouter(
+ {}} i18n={i18n} />
+ );
+
+ const page = container.querySelector('[data-automation=account-page]');
+ expect(page).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/account/__tests__/Account.container.test.tsx b/apps/client/src/core/account/__tests__/Account.container.test.tsx
new file mode 100644
index 000000000..28abae483
--- /dev/null
+++ b/apps/client/src/core/account/__tests__/Account.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import AccountContainer from '../Account.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('Account Container', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/account/changePassword/ChangePassword.component.tsx b/apps/client/src/core/account/changePassword/ChangePassword.component.tsx
new file mode 100644
index 000000000..1cda69b3d
--- /dev/null
+++ b/apps/client/src/core/account/changePassword/ChangePassword.component.tsx
@@ -0,0 +1,162 @@
+import React, { useRef, useState } from 'react';
+import Alert from '@mui/material/Alert';
+import Button from '@mui/material/Button';
+import TextField from '~components/TextField';
+
+export type ChangePasswordProps = {
+ oneTimePassword: string;
+ onSave: (currentPassword: string, newPassword: string, onSuccess: () => void, onError: () => void) => void;
+ className: string;
+ i18n: any;
+ throttle?: boolean;
+};
+
+const ChangePassword = ({ oneTimePassword, onSave, className, throttle, i18n }: ChangePasswordProps) => {
+ const currentPasswordField = useRef();
+ const [currentPassword, setCurrentPassword] = useState('');
+ const [currentPasswordError, setCurrentPasswordError] = useState('');
+
+ const passwordField = useRef();
+ const [password, setPassword] = useState('');
+
+ const password2Field = useRef();
+ const [password2, setPassword2] = useState('');
+ const [password2Error, setPassword2Error] = useState('');
+
+ const onSuccess = (): void => {
+ setCurrentPassword('');
+ setPassword('');
+ setPassword2('');
+ };
+ const onError = (): void => {
+ setCurrentPasswordError(i18n.passwordUpdateInvalidPassword);
+ currentPasswordField.current!.focus();
+ };
+
+ const handleSave = (e: any): void => {
+ e.preventDefault();
+
+ if (isValid(true)) {
+ // for one-time password logins, just send back the one-time password for confirmation on the server
+ const currPassword = oneTimePassword ? oneTimePassword : currentPassword;
+ onSave(currPassword, password, onSuccess, onError);
+ }
+ };
+
+ // TODO move validation errors to RIGHT of field, and making them show up immediately would be nice
+ const isValid = (showErrors = false): boolean => {
+ let hasError = false;
+ if (!oneTimePassword && currentPassword.trim() === '') {
+ hasError = true;
+ }
+
+ if (password.trim() === '') {
+ hasError = true;
+ } else if (password2.trim() === '') {
+ hasError = true;
+ } else if (password !== password2) {
+ if (showErrors) {
+ setPassword2Error(i18n.validationDifferentNewPasswords);
+ password2Field.current!.focus();
+ hasError = true;
+ }
+ }
+
+ return !hasError;
+ };
+
+ const submitButtonEnabled = isValid();
+
+ const getCurrentPasswordBlock = () => {
+ if (oneTimePassword) {
+ return (
+
+ {i18n.oneTimePasswordLogin}
+
+ );
+ }
+
+ return (
+ <>
+ {i18n.currentPassword}
+
+ {
+ setCurrentPasswordError('');
+ setCurrentPassword(e.target.value);
+ }}
+ style={{ width: 220 }}
+ tooltipPlacement="right"
+ throttle={throttle}
+ autoFocus
+ />
+
+ >
+ );
+ };
+
+ return (
+
+ );
+};
+
+ChangePassword.defaultProps = {
+ throttle: false
+};
+
+export default ChangePassword;
diff --git a/apps/client/src/core/account/changePassword/ChangePassword.container.ts b/apps/client/src/core/account/changePassword/ChangePassword.container.ts
new file mode 100644
index 000000000..3013c4bcc
--- /dev/null
+++ b/apps/client/src/core/account/changePassword/ChangePassword.container.ts
@@ -0,0 +1,21 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountSelectors from '~store/account/account.selectors';
+import * as accountActions from '~store/account/account.actions';
+import ChangePassword, { ChangePasswordProps } from './ChangePassword.component';
+import { Store } from '~types/general';
+
+const mapStateToProps = (state: Store): Pick => ({
+ oneTimePassword: accountSelectors.getOneTimePassword(state),
+ i18n: selectors.getCoreI18n(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ onSave: (currentPassword: string, newPassword: string, onSuccess: () => void, onError: () => void): any =>
+ dispatch(accountActions.savePassword(currentPassword, newPassword, onSuccess, onError))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(ChangePassword);
+
+export default container;
diff --git a/apps/client/src/core/account/changePassword/__tests__/ChangePassword.component.tsx b/apps/client/src/core/account/changePassword/__tests__/ChangePassword.component.tsx
new file mode 100644
index 000000000..f9d17291f
--- /dev/null
+++ b/apps/client/src/core/account/changePassword/__tests__/ChangePassword.component.tsx
@@ -0,0 +1,85 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import ChangePassword from '../ChangePassword.component';
+
+const i18n = require('../../../../i18n/en.json');
+
+describe('ChangePassword container', () => {
+ it('submitting form does not work when fields empty', () => {
+ const onSave = jest.fn();
+ const { container } = render(
+
+ );
+
+ const submitButton = container.querySelector('button[type=submit]') as HTMLButtonElement;
+ fireEvent.click(submitButton);
+
+ expect(onSave).not.toHaveBeenCalled();
+ });
+
+ it('submitting form still does not works when fields non-empty but new passwords are not identical', () => {
+ const onSave = jest.fn();
+ const { container } = render(
+
+ );
+
+ const currentPasswordField = container.querySelector('input[name=currentPassword]') as HTMLInputElement;
+ fireEvent.change(currentPasswordField, {
+ target: {
+ value: 'current-password'
+ }
+ });
+
+ const newPasswordField1 = container.querySelector('input[name=password]') as HTMLInputElement;
+ fireEvent.change(newPasswordField1, {
+ target: {
+ value: 'password'
+ }
+ });
+
+ const newPasswordField2 = container.querySelector('input[name=password2]') as HTMLInputElement;
+ fireEvent.change(newPasswordField2, {
+ target: {
+ value: 'passwordX'
+ }
+ });
+
+ const submitButton = container.querySelector('button[type=submit]') as HTMLButtonElement;
+ fireEvent.click(submitButton);
+
+ expect(onSave).not.toHaveBeenCalled();
+ });
+
+ it('submitting form works when fields non-empty and new passwords are identical', () => {
+ const onSave = jest.fn();
+ const { container } = render(
+
+ );
+
+ const currentPasswordField = container.querySelector('input[name=currentPassword]') as HTMLInputElement;
+ fireEvent.change(currentPasswordField, {
+ target: {
+ value: 'current-password'
+ }
+ });
+
+ const newPasswordField1 = container.querySelector('input[name=password]') as HTMLInputElement;
+ fireEvent.change(newPasswordField1, {
+ target: {
+ value: 'password'
+ }
+ });
+
+ const newPasswordField2 = container.querySelector('input[name=password2]') as HTMLInputElement;
+ fireEvent.change(newPasswordField2, {
+ target: {
+ value: 'password'
+ }
+ });
+
+ const submitButton = container.querySelector('button[type=submit]') as HTMLButtonElement;
+ fireEvent.click(submitButton);
+
+ expect(onSave).toHaveBeenCalled();
+ });
+});
diff --git a/apps/client/src/core/account/changePassword/__tests__/ChangePassword.container.test.tsx b/apps/client/src/core/account/changePassword/__tests__/ChangePassword.container.test.tsx
new file mode 100644
index 000000000..d9fadf0b7
--- /dev/null
+++ b/apps/client/src/core/account/changePassword/__tests__/ChangePassword.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import ChangePassword from '../ChangePassword.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('ChangePassword container', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/account/dataSets/DataSets.component.tsx b/apps/client/src/core/account/dataSets/DataSets.component.tsx
new file mode 100644
index 000000000..521133907
--- /dev/null
+++ b/apps/client/src/core/account/dataSets/DataSets.component.tsx
@@ -0,0 +1,199 @@
+import React, { useState } from 'react';
+import { useMutation, useQuery } from '@apollo/client/react';
+import Button from '@mui/material/Button';
+import HighlightOffIcon from '@mui/icons-material/HighlightOff';
+import Pagination from '~components/Pagination';
+import TableHeader, { ColSortDir } from '~components/tables/TableHeader.component';
+import * as queries from '~core/queries';
+import DeleteDataSetDialog from '~core/dialogs/deleteDataSet/DeleteDataSetDialog.component';
+import styles from './DataSets.scss';
+import * as sharedStyles from '../../../styles/shared.scss';
+import { DataSetListItem } from '@generatedata/types';
+import { formatUnixTime } from '@generatedata/utils/date';
+import { getGeneratorPageRoute } from '~utils/routeUtils';
+import { useNavigate } from 'react-router';
+import { getFormattedNum } from '@generatedata/utils/number';
+import { GDLocale } from '~types/general';
+
+const Row = ({ onDelete, onLoad, dataSet, i18n }: any) => (
+
+
{dataSet.dataSetName}
+
{formatUnixTime(dataSet.historyDateCreatedUnix)}
+
{getFormattedNum(dataSet.numRowsGenerated)}
+
+
+ {i18n.open}
+
+
+
+
+
+
+);
+
+export type DataSetsProps = {
+ locale: GDLocale;
+ onLoadDataSet: (dataSet: DataSetListItem) => void;
+ onClearCurrentDataSet: () => void;
+ currentDataSetId: number | null;
+ className: string;
+ i18n: any;
+};
+
+// to be moved to a user setting at some point
+const NUM_PER_PAGE = 10;
+
+const DataSets = ({ onLoadDataSet, locale, i18n, currentDataSetId, className = '', onClearCurrentDataSet }: DataSetsProps) => {
+ const navigate = useNavigate();
+ const [selectedDataSet, selectDataSet] = useState();
+ const [currentPage, setCurrentPage] = useState(1);
+ const [dialogVisible, setDeleteDialogVisibility] = useState(false);
+ const [sortCol, setSortCol] = useState('dataSetName');
+ const [sortDir, setSortDir] = useState(ColSortDir.asc);
+
+ const { data } = useQuery(queries.GET_DATA_SETS, {
+ fetchPolicy: 'cache-and-network',
+ variables: {
+ offset: (currentPage - 1) * NUM_PER_PAGE,
+ limit: NUM_PER_PAGE,
+ sortDir,
+ sortCol
+ }
+ });
+
+ const loadDataSet = (dataSet: DataSetListItem): void => {
+ onLoadDataSet(dataSet);
+ navigate(getGeneratorPageRoute(locale));
+ };
+
+ const numItemsOnPage = data?.dataSets?.results?.length || 0;
+ const afterDeletePage = numItemsOnPage === 1 ? currentPage - 1 : currentPage;
+ let offset = (afterDeletePage - 1) * NUM_PER_PAGE;
+ offset = offset < 0 ? 0 : offset;
+
+ const [deleteDataSet] = useMutation(queries.DELETE_DATA_SET, {
+ refetchQueries: [
+ {
+ query: queries.GET_DATA_SETS,
+ variables: {
+ offset,
+ limit: NUM_PER_PAGE,
+ sortDir,
+ sortCol
+ }
+ }
+ ],
+ onCompleted: () => {
+ setDeleteDialogVisibility(false);
+
+ if (currentDataSetId) {
+ const { dataSetId: deletedDataSetId } = selectedDataSet as DataSetListItem;
+
+ // double == intentional. Maybe a string
+ if (currentDataSetId == deletedDataSetId) {
+ onClearCurrentDataSet();
+ }
+ }
+ }
+ });
+
+ // show spinner here
+ if (!data || !data.dataSets) {
+ return null;
+ }
+
+ const onShowDeleteDialog = (dataSet: DataSetListItem): void => {
+ selectDataSet(dataSet);
+ setDeleteDialogVisibility(true);
+ };
+
+ const { results, totalCount } = data.dataSets;
+
+ if (totalCount === 0) {
+ return (
+
+ {i18n.noDataSetsSaved}
+
+ );
+ }
+
+ const paginationRow =
+ totalCount > NUM_PER_PAGE ? (
+
+
setCurrentPage(pageNum)}
+ />
+
+ ) : null;
+
+ const cols = [
+ {
+ label: i18n.dataSetName,
+ className: styles.dataSetName,
+ field: 'dataSetName',
+ sortable: true
+ },
+ {
+ label: i18n.lastModified,
+ className: styles.lastModified,
+ field: 'lastUpdated',
+ sortable: true
+ },
+ {
+ label: i18n.rowsGenerated,
+ className: styles.numRowsGenerated,
+ field: 'numRowsGenerated',
+ sortable: true
+ },
+ { label: i18n.open, className: styles.open },
+ { label: '', className: styles.del }
+ ];
+
+ return (
+ <>
+
+
+
{
+ setSortCol(col);
+ setSortDir(dir);
+ }}
+ />
+
+ {results.map((dataSet: DataSetListItem) => (
+ onShowDeleteDialog(dataSet)}
+ onLoad={(): void => loadDataSet(dataSet)}
+ i18n={i18n}
+ />
+ ))}
+
+
+ {paginationRow}
+
+
+ setDeleteDialogVisibility(false)}
+ onDelete={(): any =>
+ deleteDataSet({
+ variables: {
+ dataSetId: selectedDataSet!.dataSetId
+ }
+ })
+ }
+ i18n={i18n}
+ />
+ >
+ );
+};
+
+export default DataSets;
diff --git a/apps/client/src/core/account/dataSets/DataSets.container.ts b/apps/client/src/core/account/dataSets/DataSets.container.ts
new file mode 100644
index 000000000..b63e344b5
--- /dev/null
+++ b/apps/client/src/core/account/dataSets/DataSets.container.ts
@@ -0,0 +1,25 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+import * as mainSelectors from '~store/main/main.selectors';
+import * as generatorActions from '~store/generator/generator.actions';
+import DataSets, { DataSetsProps } from './DataSets.component';
+import { Store } from '~types/general';
+import { withAuth } from '~core/auth/withAuth';
+import { DataSetListItem } from '@generatedata/types';
+
+const mapStateToProps = (state: Store): Partial => ({
+ locale: mainSelectors.getLocale(state),
+ i18n: selectors.getCoreI18n(state),
+ currentDataSetId: selectors.getCurrentDataSetId(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ onLoadDataSet: (dataSet: DataSetListItem): any => dispatch(actions.loadDataSet(dataSet)),
+ onClearCurrentDataSet: (): any => dispatch(generatorActions.clearPage())
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(DataSets);
+
+export default withAuth(container);
diff --git a/apps/client/src/core/account/dataSets/DataSets.scss b/apps/client/src/core/account/dataSets/DataSets.scss
new file mode 100644
index 000000000..bafc54d04
--- /dev/null
+++ b/apps/client/src/core/account/dataSets/DataSets.scss
@@ -0,0 +1,97 @@
+.page {
+ font-size: 13px;
+ max-width: 1024px;
+ margin: 0 auto 0;
+ width: 100%;
+
+ h2 {
+ margin-top: 0;
+ }
+}
+
+.table {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.paginationRow {
+ margin: 15px 0;
+}
+
+.row {
+ display: flex;
+ width: 100%;
+ padding: 6px;
+ align-items: center;
+}
+
+.header {
+ font-weight: bold;
+ font-size: 13px;
+ color: #666666;
+ padding-top: 2px;
+ border-bottom: 1px solid #333333;
+}
+
+.body {
+ overflow: scroll;
+
+ .row {
+ &:hover {
+ background-color: #f9f9f9;
+ }
+ }
+}
+
+.dataSetName {
+ flex: 1;
+}
+
+.dateCreated {
+ flex: 1;
+}
+
+.lastModified {
+ flex: 1;
+}
+
+.numRowsGenerated {
+ flex: 1;
+ color: #666666;
+}
+
+.status {
+ flex: 0 0 80px;
+}
+
+.open {
+ flex: 0 0 80px;
+ text-align: center;
+}
+
+.history {
+ flex: 0 0 80px;
+ text-align: center;
+}
+
+.del {
+ flex: 0 0 30px;
+ align-items: center;
+ cursor: pointer;
+
+ &:hover {
+ svg {
+ fill: #990000;
+ }
+ }
+}
+
+@media (max-width: 720px) {
+ .status,
+ .lastModified,
+ .numRowsGenerated {
+ display: none;
+ }
+}
diff --git a/apps/client/src/core/account/dataSets/DataSets.scss.d.ts b/apps/client/src/core/account/dataSets/DataSets.scss.d.ts
new file mode 100644
index 000000000..914676659
--- /dev/null
+++ b/apps/client/src/core/account/dataSets/DataSets.scss.d.ts
@@ -0,0 +1,25 @@
+declare namespace DataSetsScssNamespace {
+ export interface IDataSetsScss {
+ body: string;
+ dataSetName: string;
+ dateCreated: string;
+ del: string;
+ header: string;
+ history: string;
+ lastModified: string;
+ numRowsGenerated: string;
+ open: string;
+ page: string;
+ paginationRow: string;
+ row: string;
+ status: string;
+ table: string;
+ }
+}
+
+declare const DataSetsScssModule: DataSetsScssNamespace.IDataSetsScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: DataSetsScssNamespace.IDataSetsScss;
+};
+
+export = DataSetsScssModule;
diff --git a/apps/client/src/core/account/dataSets/__tests__/DataSets.container.test.tsx b/apps/client/src/core/account/dataSets/__tests__/DataSets.container.test.tsx
new file mode 100644
index 000000000..9b14587fd
--- /dev/null
+++ b/apps/client/src/core/account/dataSets/__tests__/DataSets.container.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import DataSets from '../DataSets.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('DataSets', () => {
+ // need to finish deciding exactly what the header will contain before adding these tests
+
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/account/yourAccount/YourAccount.component.tsx b/apps/client/src/core/account/yourAccount/YourAccount.component.tsx
new file mode 100644
index 000000000..f11088df4
--- /dev/null
+++ b/apps/client/src/core/account/yourAccount/YourAccount.component.tsx
@@ -0,0 +1,77 @@
+import React, { useEffect } from 'react';
+import MainFields from '~components/accounts/mainFields/MainFields.component';
+import { AccountEditingData } from '~store/account/account.reducer';
+import { getFormattedNum } from '@generatedata/utils/number';
+import { formatUnixTime } from '@generatedata/utils/date';
+import styles from '../Account.scss';
+import C from '@generatedata/config/constants';
+
+export type YourAccountProps = {
+ data: AccountEditingData;
+ numGeneratedRows: number;
+ accountHasChanges: boolean;
+ updateAccount: (data: AccountEditingData) => void;
+ expiryDate: string;
+ onInit: () => void;
+ onSave: () => void;
+ onCancel: () => void;
+ className: string;
+ i18n: any;
+};
+
+const YourAccount = ({
+ data,
+ numGeneratedRows,
+ accountHasChanges,
+ updateAccount,
+ onSave,
+ onCancel,
+ className,
+ i18n,
+ onInit,
+ expiryDate
+}: YourAccountProps) => {
+ useEffect(() => {
+ onInit();
+ }, []);
+
+ const getExpiryDate = () => {
+ if (!expiryDate) {
+ return null;
+ }
+
+ return (
+
+
{i18n.accountExpiryDate}
+
{formatUnixTime(parseInt(expiryDate) / 1000, C.DATE_FORMAT)}
+
+ );
+ };
+
+ return (
+
+
+
+
+
+
{i18n.totalNumGeneratedRows}
+
{getFormattedNum(numGeneratedRows)}
+
+ {getExpiryDate()}
+
+
+
+ );
+};
+
+export default YourAccount;
diff --git a/apps/client/src/core/account/yourAccount/YourAccount.container.ts b/apps/client/src/core/account/yourAccount/YourAccount.container.ts
new file mode 100644
index 000000000..832167ae5
--- /dev/null
+++ b/apps/client/src/core/account/yourAccount/YourAccount.container.ts
@@ -0,0 +1,27 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountSelectors from '~store/account/account.selectors';
+import * as accountActions from '~store/account/account.actions';
+import YourAccount, { YourAccountProps } from './YourAccount.component';
+import { Store } from '~types/general';
+import { AccountEditingData } from '~store/account/account.reducer';
+
+const mapStateToProps = (state: Store): Partial => ({
+ data: accountSelectors.getEditingData(state),
+ numGeneratedRows: accountSelectors.getNumGeneratedRows(state),
+ accountHasChanges: accountSelectors.accountHasChanges(state),
+ expiryDate: accountSelectors.getExpiryDate(state),
+ i18n: selectors.getCoreI18n(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ updateAccount: (data: AccountEditingData): any => dispatch(accountActions.updateAccount(data)),
+ onInit: (): any => dispatch(accountActions.onEditYourAccount()),
+ onCancel: (): any => dispatch(accountActions.cancelChanges()),
+ onSave: (): any => dispatch(accountActions.saveYourAccount())
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(YourAccount);
+
+export default container;
diff --git a/apps/client/src/core/accounts/Accounts.component.tsx b/apps/client/src/core/accounts/Accounts.component.tsx
new file mode 100644
index 000000000..520f97754
--- /dev/null
+++ b/apps/client/src/core/accounts/Accounts.component.tsx
@@ -0,0 +1,54 @@
+import React, { useEffect } from 'react';
+import * as sharedStyles from '../../styles/shared.scss';
+import AccountsList from './accountsList/AccountsList.container';
+import CreateAccount from './createAccount/CreateAccount.container';
+import EditAccount from './editAccount/EditAccount.container';
+import { SelectedAccountsTab } from '~types/account';
+
+export type AccountsPageProps = {
+ i18n: any;
+ selectedTab: SelectedAccountsTab;
+ onChangeTab: (tab: any) => void;
+ onDestroy: () => void;
+};
+
+const Accounts = ({ selectedTab, onChangeTab, onDestroy, i18n }: AccountsPageProps) => {
+ useEffect((): any => {
+ return (): void => {
+ onDestroy();
+ };
+ }, []);
+
+ const getTab = () => {
+ if (selectedTab === SelectedAccountsTab.accounts) {
+ return ;
+ } else if (selectedTab === SelectedAccountsTab.editAccount) {
+ return ;
+ }
+ return ;
+ };
+
+ return (
+
+
+
+ onChangeTab(SelectedAccountsTab.accounts)}
+ >
+ {i18n.accounts}
+
+ onChangeTab(SelectedAccountsTab.createAccount)}
+ >
+ {i18n.createAccount}
+
+
+
+ {getTab()}
+
+ );
+};
+
+export default Accounts;
diff --git a/apps/client/src/core/accounts/Accounts.container.tsx b/apps/client/src/core/accounts/Accounts.container.tsx
new file mode 100644
index 000000000..57f23393a
--- /dev/null
+++ b/apps/client/src/core/accounts/Accounts.container.tsx
@@ -0,0 +1,23 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountSelectors from '~store/account/account.selectors';
+import * as accountActions from '~store/account/account.actions';
+import AccountsPage, { AccountsPageProps } from './Accounts.component';
+import { SelectedAccountsTab } from '~types/account';
+import { Store } from '~types/general';
+import { withAuth } from '~core/auth/withAuth';
+
+const mapStateToProps = (state: Store): Partial => ({
+ i18n: selectors.getCoreI18n(state),
+ selectedTab: accountSelectors.getSelectedAccountsPageTab(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ onChangeTab: (tab: SelectedAccountsTab): any => dispatch(accountActions.onChangeAccountsTab(tab)),
+ onDestroy: (): any => dispatch(accountActions.onCleanupAccountsPage())
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(AccountsPage);
+
+export default withAuth(container);
diff --git a/apps/client/src/core/accounts/accountsList/AccountsList.component.tsx b/apps/client/src/core/accounts/accountsList/AccountsList.component.tsx
new file mode 100644
index 000000000..ec43f3eef
--- /dev/null
+++ b/apps/client/src/core/accounts/accountsList/AccountsList.component.tsx
@@ -0,0 +1,264 @@
+import React, { useEffect, useState } from 'react';
+import Button from '@mui/material/Button';
+import HighlightOffIcon from '@mui/icons-material/HighlightOff';
+import { format, fromUnixTime } from 'date-fns';
+import { useMutation, useQuery } from '@apollo/client/react';
+import { addToast } from '@generatedata/utils/general';
+import Pagination from '~components/Pagination';
+import TableHeader, { ColSortDir } from '~components/tables/TableHeader.component';
+import { SmallSpinner } from '~components/loaders/loaders';
+import AccountStatusPill from '~components/accounts/accountStatusPill/AccountStatusPill.component';
+import DeleteAccountDialog from '~core/dialogs/deleteAccount/DeleteAccount.component';
+import SearchFilter from './SearchFilter.component';
+import C from '@generatedata/config/constants';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import { AccountStatusFilter } from '~types/general';
+import * as queries from '~core/queries';
+import * as styles from './AccountsList.scss';
+import * as sharedStyles from '../../../styles/shared.scss';
+
+export type AccountsListProps = {
+ accountsCurrentPage: number;
+ accountsSortCol: string;
+ accountsSortDir: ColSortDir;
+ accountsFilterStr: string;
+ accountStatusFilter: AccountStatusFilter;
+ onEditAccount: (accountId: number) => void;
+ setAccountsSortDir: (sortDir: ColSortDir) => void;
+ setAccountsSortCol: (sortCol: string) => void;
+ setAccountsCurrentPage: (page: number) => void;
+ setAccountsFilterString: (filter: string) => void;
+ setAccountStatusFilter: (status: AccountStatusFilter) => void;
+ i18n: any;
+};
+
+const Row = ({ i18n, firstName, lastName, onEdit, onDelete, accountStatus, lastLoggedIn, expiryDate }: any) => {
+ let expiryDateVal: any = — ;
+ try {
+ if (expiryDate) {
+ expiryDateVal = format(fromUnixTime(expiryDate / 1000), C.DATE_FORMAT);
+ }
+ } catch (e) {}
+
+ let lastLoggedInVal: any = — ;
+ try {
+ lastLoggedInVal = format(fromUnixTime(lastLoggedIn), C.DATE_FORMAT);
+ } catch (lastLoggedInVal) {}
+
+ return (
+
+
{firstName}
+
{lastName}
+
+
{lastLoggedInVal}
+
{expiryDateVal}
+
+
+ {i18n.edit}
+
+
+
+
+
+
+ );
+};
+
+const NUM_PER_PAGE = 10;
+const AccountsList = ({
+ accountsCurrentPage,
+ accountsSortCol,
+ accountsSortDir,
+ accountsFilterStr,
+ accountStatusFilter,
+ onEditAccount,
+ setAccountsSortDir,
+ setAccountsSortCol,
+ setAccountsCurrentPage,
+ setAccountsFilterString,
+ setAccountStatusFilter,
+ i18n
+}: AccountsListProps) => {
+ const [dialogVisible, setDialogVisible] = useState(false);
+ const [deleteAccountInfo, setDeleteAccountInfo] = useState(null);
+ const [lastData, setLastData] = useState(null);
+
+ const { data, loading, refetch } = useQuery(queries.GET_ACCOUNTS, {
+ fetchPolicy: 'cache-and-network',
+ variables: {
+ offset: (accountsCurrentPage - 1) * NUM_PER_PAGE,
+ limit: NUM_PER_PAGE,
+ sortCol: accountsSortCol,
+ sortDir: accountsSortDir,
+ filterStr: accountsFilterStr,
+ status: accountStatusFilter
+ }
+ });
+
+ useEffect(() => {
+ refetch();
+ }, [accountsFilterStr]);
+
+ useEffect(() => {
+ if (data) {
+ setLastData(data);
+ }
+ }, [data]);
+
+ const numItemsOnPage = lastData?.accounts?.results?.length || 0;
+ const afterDeletePage = numItemsOnPage === 1 && accountsCurrentPage > 1 ? accountsCurrentPage - 1 : accountsCurrentPage;
+
+ const [deleteAccount] = useMutation(queries.DELETE_ACCOUNT, {
+ refetchQueries: [
+ {
+ query: queries.GET_ACCOUNTS,
+ variables: {
+ offset: (afterDeletePage - 1) * NUM_PER_PAGE,
+ limit: NUM_PER_PAGE,
+ sortCol: accountsSortCol,
+ sortDir: accountsSortDir,
+ filterStr: accountsFilterStr,
+ status: accountStatusFilter
+ }
+ }
+ ],
+ onCompleted: () => {
+ setDialogVisible(false);
+ setDeleteAccountInfo(null);
+
+ addToast({
+ message: i18n.accountDeleted,
+ type: 'success'
+ });
+ }
+ });
+
+ if (!lastData) {
+ return null;
+ }
+
+ const { results, totalCount } = lastData.accounts;
+ const cols = [
+ {
+ label: i18n.firstName,
+ className: styles.firstName,
+ field: 'firstName',
+ sortable: true
+ },
+ {
+ label: i18n.lastName,
+ className: styles.lastName,
+ field: 'lastName',
+ sortable: true
+ },
+ {
+ label: i18n.status,
+ className: styles.status,
+ field: 'accountStatus',
+ sortable: true
+ },
+ {
+ label: i18n.lastLoggedIn,
+ className: styles.lastLoggedIn,
+ field: 'lastLoggedIn',
+ sortable: true
+ },
+ {
+ label: i18n.expiryDate,
+ className: styles.expiryDate,
+ field: 'expiryDate',
+ sortable: true
+ },
+ { label: i18n.edit, className: styles.edit },
+ { label: '', className: styles.del }
+ ];
+
+ let content;
+ if (totalCount === 0) {
+ content = {i18n.noAccountsCreated}
;
+ } else {
+ const paginationRow =
+ totalCount > NUM_PER_PAGE ? (
+
+
setAccountsCurrentPage(pageNum)}
+ />
+
+ ) : null;
+
+ content = (
+ <>
+
+
{
+ setAccountsSortCol(col);
+ setAccountsSortDir(dir);
+ }}
+ />
+
+ {results.map((row: any) => (
+ onEditAccount(row)}
+ onDelete={(): void => {
+ setDialogVisible(true);
+ setDeleteAccountInfo(row);
+ }}
+ />
+ ))}
+
+
+ {paginationRow}
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+ setAccountStatusFilter(selected.value as AccountStatusFilter)}
+ options={[
+ { value: AccountStatusFilter.all, label: 'Any status' },
+ { value: AccountStatusFilter.live, label: i18n.live },
+ { value: AccountStatusFilter.expired, label: i18n.expired },
+ { value: AccountStatusFilter.disabled, label: i18n.disabled }
+ ]}
+ />
+ {loading && }
+ {totalCount} result(s)
+
+ {content}
+
+ setDialogVisible(false)}
+ onExited={(): void => setDeleteAccountInfo(null)}
+ onDelete={(): any => {
+ deleteAccount({
+ variables: {
+ accountId: deleteAccountInfo?.accountId
+ }
+ });
+ setAccountsCurrentPage(afterDeletePage);
+ }}
+ name={`${deleteAccountInfo?.firstName} ${deleteAccountInfo?.lastName}`}
+ i18n={i18n}
+ />
+ >
+ );
+};
+
+export default AccountsList;
diff --git a/apps/client/src/core/accounts/accountsList/AccountsList.container.ts b/apps/client/src/core/accounts/accountsList/AccountsList.container.ts
new file mode 100644
index 000000000..8dd2491cb
--- /dev/null
+++ b/apps/client/src/core/accounts/accountsList/AccountsList.container.ts
@@ -0,0 +1,31 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import * as mainSelectors from '~store/main/main.selectors';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountActions from '~store/account/account.actions';
+import * as mainActions from '~store/main/main.actions';
+import AccountsList, { AccountsListProps } from './AccountsList.component';
+import { AccountStatusFilter, Store } from '~types/general';
+import { ColSortDir } from '~components/tables/TableHeader.component';
+
+const mapStateToProps = (state: Store): Partial => ({
+ accountsCurrentPage: mainSelectors.getAccountsCurrentPage(state),
+ accountsSortCol: mainSelectors.getAccountsSortCol(state),
+ accountsSortDir: mainSelectors.getAccountsSortDir(state),
+ accountsFilterStr: mainSelectors.getAccountsFilterStr(state),
+ accountStatusFilter: mainSelectors.getAccountStatusFilter(state),
+ i18n: selectors.getCoreI18n(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ onEditAccount: (accountInfo: any): any => dispatch(accountActions.editAccount(accountInfo)),
+ setAccountsSortDir: (sortDir: ColSortDir): void => dispatch(mainActions.setAccountsSortDir(sortDir)),
+ setAccountsSortCol: (sortCol: string): void => dispatch(mainActions.setAccountsSortCol(sortCol)),
+ setAccountsCurrentPage: (page: number): void => dispatch(mainActions.setAccountsCurrentPage(page)),
+ setAccountsFilterString: (filter: string): void => dispatch(mainActions.setAccountsFilterString(filter)),
+ setAccountStatusFilter: (status: AccountStatusFilter): void => dispatch(mainActions.setAccountStatusFilter(status))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(AccountsList);
+
+export default container;
diff --git a/apps/client/src/core/accounts/accountsList/AccountsList.scss b/apps/client/src/core/accounts/accountsList/AccountsList.scss
new file mode 100644
index 000000000..cfc7168c3
--- /dev/null
+++ b/apps/client/src/core/accounts/accountsList/AccountsList.scss
@@ -0,0 +1,111 @@
+// TODO move common code to a shared.scss file, like tables.scss or whatever
+.page {
+ font-size: 13px;
+}
+
+.body {
+ overflow: scroll;
+
+ .row {
+ &:hover {
+ background-color: #f9f9f9;
+ }
+ }
+}
+
+.paginationRow {
+ margin: 15px 0;
+}
+
+.accountsListTable {
+ width: 100%;
+ margin-bottom: 20px;
+
+ .row {
+ display: flex;
+ width: 100%;
+ padding: 6px;
+ align-items: center;
+ font-size: 13px;
+ }
+
+ .firstName {
+ flex: 1 0 110px;
+ }
+
+ .lastName {
+ flex: 1 0 110px;
+ }
+
+ .expiryDate {
+ flex: 1 0 90px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .lastLoggedIn {
+ flex: 1 0 90px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .status {
+ flex: 1 0 70px;
+ }
+
+ .edit {
+ flex: 0 0 90px;
+ text-align: center;
+ }
+
+ .del {
+ flex: 0 0 30px;
+ align-items: center;
+ cursor: pointer;
+
+ &:hover {
+ svg {
+ fill: #990000;
+ }
+ }
+ }
+}
+
+.filtersRow {
+ display: flex;
+ margin-bottom: 15px;
+ align-items: center;
+
+ h4 {
+ margin-left: auto;
+ }
+}
+
+.searchFilter {
+ display: flex;
+ margin-right: 8px;
+}
+
+.accountsFilter {
+ width: 130px;
+}
+
+@media (max-width: 720px) {
+ .status,
+ .lastLoggedIn,
+ .expiryDate {
+ display: none;
+ }
+
+ .paginationRow nav {
+ border-bottom: 0;
+ }
+}
+
+@media (max-width: 900px) {
+ .lastLoggedIn {
+ display: none;
+ }
+}
diff --git a/apps/client/src/core/accounts/accountsList/AccountsList.scss.d.ts b/apps/client/src/core/accounts/accountsList/AccountsList.scss.d.ts
new file mode 100644
index 000000000..8acaf596c
--- /dev/null
+++ b/apps/client/src/core/accounts/accountsList/AccountsList.scss.d.ts
@@ -0,0 +1,26 @@
+declare namespace AccountsListScssNamespace {
+ export interface IAccountsListScss {
+ accountsFilter: string;
+ accountsListTable: string;
+ body: string;
+ del: string;
+ edit: string;
+ expiryDate: string;
+ filtersRow: string;
+ firstName: string;
+ lastLoggedIn: string;
+ lastName: string;
+ page: string;
+ paginationRow: string;
+ row: string;
+ searchFilter: string;
+ status: string;
+ }
+}
+
+declare const AccountsListScssModule: AccountsListScssNamespace.IAccountsListScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: AccountsListScssNamespace.IAccountsListScss;
+};
+
+export = AccountsListScssModule;
diff --git a/apps/client/src/core/accounts/accountsList/SearchFilter.component.tsx b/apps/client/src/core/accounts/accountsList/SearchFilter.component.tsx
new file mode 100644
index 000000000..73648c8e3
--- /dev/null
+++ b/apps/client/src/core/accounts/accountsList/SearchFilter.component.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+import TextField from '~components/TextField';
+import styles from './AccountsList.scss';
+
+export type SearchFilterProps = {
+ value: string;
+ onChange: (val: string) => void;
+};
+
+const SearchFilter = ({ value, onChange }: SearchFilterProps) => (
+
+ onChange(e.target.value)} />
+
+);
+
+export default SearchFilter;
diff --git a/apps/client/src/core/accounts/createAccount/CreateAccount.container.ts b/apps/client/src/core/accounts/createAccount/CreateAccount.container.ts
new file mode 100644
index 000000000..6268dfed9
--- /dev/null
+++ b/apps/client/src/core/accounts/createAccount/CreateAccount.container.ts
@@ -0,0 +1,43 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { generateRandomAlphanumericStr } from '@generatedata/utils/random';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountActions from '~store/account/account.actions';
+import ManageAccount, {
+ ManageAccountState,
+ ManageAccountProps,
+ ExpiryOption
+} from '../../../components/accounts/manageAccount/ManageAccount.component';
+import { Store } from '~types/general';
+
+const initialState: ManageAccountState = {
+ firstName: '',
+ lastName: '',
+ email: '',
+ country: '',
+ region: '',
+ oneTimePassword: generateRandomAlphanumericStr('CcEVvFLlDXxH'),
+ disabled: false,
+ expiry: ExpiryOption.none,
+ expiryDate: null,
+ numRowsGenerated: 0,
+ isAddingUser: true
+};
+
+const mapStateToProps = (state: Store): Partial => {
+ const i18n = selectors.getCoreI18n(state);
+ return {
+ initialState,
+ i18n,
+ submitButtonLabel: i18n.createAccount
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ // @ts-ignore-line
+ onSave: (data: any): any => dispatch(accountActions.createAccount(data))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(ManageAccount);
+
+export default container;
diff --git a/apps/client/src/core/accounts/createAccount/__tests__/CreateAccount.container.test.tsx b/apps/client/src/core/accounts/createAccount/__tests__/CreateAccount.container.test.tsx
new file mode 100644
index 000000000..a616dc639
--- /dev/null
+++ b/apps/client/src/core/accounts/createAccount/__tests__/CreateAccount.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import CreateAccount from '../CreateAccount.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('YourAccount container', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/accounts/editAccount/EditAccount.container.ts b/apps/client/src/core/accounts/editAccount/EditAccount.container.ts
new file mode 100644
index 000000000..e4ca9404e
--- /dev/null
+++ b/apps/client/src/core/accounts/editAccount/EditAccount.container.ts
@@ -0,0 +1,37 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountActions from '~store/account/account.actions';
+import * as actionSelectors from '~store/account/account.selectors';
+import ManageAccount, {
+ ExpiryOption,
+ ManageAccountProps,
+ ManageAccountState
+} from '~components/accounts/manageAccount/ManageAccount.component';
+import { Store } from '~types/general';
+import { SelectedAccountsTab } from '~types/account';
+
+const mapStateToProps = (state: Store): Partial => {
+ const i18n = selectors.getCoreI18n(state);
+ const initialState = actionSelectors.getEditingData(state);
+
+ return {
+ initialState: {
+ ...initialState,
+ expiry: initialState.expiryDate ? ExpiryOption.date : ExpiryOption.none,
+ isAddingUser: false
+ } as ManageAccountState,
+ i18n,
+ submitButtonLabel: i18n.updateAccount
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ // @ts-ignore-line
+ onSave: (data: any): any => dispatch(accountActions.saveAccount(data)),
+ onCancel: (): any => dispatch(accountActions.onChangeAccountsTab(SelectedAccountsTab.accounts))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(ManageAccount);
+
+export default container;
diff --git a/apps/client/src/core/actionInterceptor.ts b/apps/client/src/core/actionInterceptor.ts
new file mode 100644
index 000000000..0a5a1ab8a
--- /dev/null
+++ b/apps/client/src/core/actionInterceptor.ts
@@ -0,0 +1,57 @@
+import { Store } from 'redux';
+import { getRows } from '~store/generator/generator.selectors';
+import { DataRow } from '~store/generator/generator.reducer';
+import { onConfigureDataType } from '~store/generator/generator.actions';
+import { DataTypeFolder } from '@generatedata/plugins';
+import { DTActionInterceptors, DTInterceptorSingleAction } from '~types/dataTypes';
+
+// TODO what if onload, a user interacts with a pre-saved config prior to the data type loading and the interceptor
+// isn't registered yet? Other than defensively coding the Data Type generation I'm not sure how to handle that... I
+// guess we could also lock down the UI...
+const actionInterceptors: any = {};
+export const registerInterceptors = (dataType: DataTypeFolder, interceptors: DTActionInterceptors): void => {
+ Object.keys(interceptors).forEach((action) => {
+ if (!actionInterceptors[action]) {
+ actionInterceptors[action] = [];
+ }
+ actionInterceptors[action].push({
+ dataType,
+ // singular, note. A single DataType only every supplies a single interceptor for a single action
+ interceptor: interceptors[action]
+ });
+ });
+};
+
+export const getActionInterceptors = (action: string): DTInterceptorSingleAction[] => {
+ return actionInterceptors[action] ? actionInterceptors[action] : [];
+};
+
+const actionInterceptor =
+ (store: Store) =>
+ (next: any): any =>
+ (action: any): any => {
+ // returns all interceptors for the current action
+ const interceptors = action && action.type ? getActionInterceptors(action.type) : [];
+
+ if (interceptors.length) {
+ const rows = getRows(store.getState());
+ interceptors.forEach(({ dataType, interceptor }) => {
+ Object.keys(rows).forEach((rowId: string) => {
+ const row: DataRow = rows[rowId];
+ if (row.dataType === dataType) {
+ const result = interceptor(rowId, row.data, action.payload);
+ if (result) {
+ // TODO note: interceptors don't currently support options metadata. e.g. the Names DataType
+ // onUpdate() returns both the new row state, but also some metadata that tells the core
+ // script to load the country names. Hence the `undefined` 3rd param here.
+ store.dispatch(onConfigureDataType(rowId, result, undefined, true));
+ }
+ }
+ });
+ });
+ }
+
+ return next(action);
+ };
+
+export default actionInterceptor;
diff --git a/apps/client/src/core/apolloClient.ts b/apps/client/src/core/apolloClient.ts
new file mode 100644
index 000000000..20f23877b
--- /dev/null
+++ b/apps/client/src/core/apolloClient.ts
@@ -0,0 +1,48 @@
+import { ApolloClient, InMemoryCache, NormalizedCacheObject, ApolloLink, HttpLink, concat } from '@apollo/client';
+import fetch from 'cross-fetch';
+import store from './store';
+import * as mainSelectors from './store/main/main.selectors';
+
+// TODO: generalized error handling when logged out
+// import { onError } from 'apollo-link-error';
+//
+// const logoutLink = onError(({ networkError }) => {
+// if (networkError.statusCode === 401) logout();
+// })
+
+const protocol = process.env.GD_WEB_USE_HTTPS === 'true' ? 'https' : 'http';
+
+// this could be improved For prod environments we use the same port and use nginx to route the requests
+// to the graphql server
+let uri = `${protocol}://${process.env.GD_WEB_DOMAIN}/graphql`;
+if (process.env.NODE_ENV === 'development') {
+ uri = `${protocol}://${process.env.GD_WEB_DOMAIN}:${process.env.GD_API_SERVER_PORT}/graphql`;
+}
+
+const httpLink = new HttpLink({
+ uri,
+ fetch,
+ credentials: 'include'
+});
+
+const authMiddleware = new ApolloLink((operation, forward) => {
+ const token = mainSelectors.getAuthToken(store.getState());
+
+ // console.log('adding token header?', mainSelectors.getAuthToken(store.getState()));
+
+ // this adds the current active jwt token to all requests so the server can authenticate the user
+ if (token) {
+ operation.setContext({
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ }
+
+ return forward(operation);
+});
+
+export const apolloClient = new ApolloClient({
+ link: concat(authMiddleware, httpLink),
+ cache: new InMemoryCache()
+});
diff --git a/apps/client/src/core/auth/google/google.tsx b/apps/client/src/core/auth/google/google.tsx
new file mode 100644
index 000000000..246fbeb41
--- /dev/null
+++ b/apps/client/src/core/auth/google/google.tsx
@@ -0,0 +1,126 @@
+import React, { useRef } from 'react';
+import { gql } from '@apollo/client';
+import Cookies from 'js-cookie';
+import { apolloClient } from '../../apolloClient';
+import store from '../../store';
+import { onLoginSuccess, setAuthenticated, setAuthenticationData, setOnloadAuthDetermined } from '~store/main/main.actions';
+import { AuthMethod } from '~types/general';
+import * as mainSelectors from '~store/main/main.selectors';
+import { addToast } from '@generatedata/utils/general';
+import langUtils from '@generatedata/utils/lang';
+import clientConfig from '@generatedata/config/clientConfig';
+
+const googleBtnId = 'google-signin-button';
+
+export const initGoogleAuth = (): void => {
+ const script = document.createElement('script');
+ script.src = 'https://accounts.google.com/gsi/client';
+ script.async = true;
+ script.defer = true;
+ document.body.appendChild(script);
+};
+
+export type AuthenticatedOptions = {
+ onPageRender?: boolean;
+};
+
+const onAuthenticated = async (googleUser: any, opts: AuthenticatedOptions = {}): Promise => {
+ const i18n = langUtils.getStrings();
+
+ const options = {
+ onPageRender: false,
+ ...opts
+ };
+
+ const isLoggedIn = mainSelectors.isLoggedIn(store.getState());
+
+ if (isLoggedIn) {
+ store.dispatch(setAuthenticated(true));
+
+ // needed to complete the page load
+ store.dispatch(setOnloadAuthDetermined());
+ } else {
+ const googleToken = googleUser.credential;
+ const response = await apolloClient.mutate({
+ mutation: gql`
+ mutation LoginWithGoogle($googleToken: String!) {
+ loginWithGoogle(googleToken: $googleToken) {
+ token
+ refreshToken
+ tokenExpiry
+ success
+ error
+ firstName
+ lastName
+ expiryDate
+ accountType
+ dateCreated
+ email
+ numRowsGenerated
+ profileImage
+ country
+ region
+ }
+ }
+ `,
+ variables: { googleToken }
+ });
+
+ if (response.data.loginWithGoogle.success) {
+ const { tokenExpiry, refreshToken } = response.data.loginWithGoogle;
+
+ store.dispatch(
+ setAuthenticationData({
+ ...response.data.loginWithGoogle,
+ authMethod: AuthMethod.google
+ })
+ );
+
+ Cookies.set('refreshToken', refreshToken, {
+ expires: new Date(tokenExpiry)
+ });
+ onLoginSuccess(tokenExpiry, options.onPageRender, store.dispatch);
+ } else {
+ if (response.data.loginWithGoogle.error === 'accountExpired') {
+ addToast({
+ type: 'error',
+ message: i18n.core.accountExpiredMsg
+ });
+ } else if (response.data.loginWithGoogle.error === 'noUserAccount') {
+ addToast({
+ type: 'error',
+ message: i18n.core.userAccountNotFound
+ });
+ }
+ }
+ }
+};
+
+export const SignInWithGoogleButton = () => {
+ const divRef = useRef(null);
+ const [loaded, setLoaded] = React.useState(false);
+
+ React.useEffect(() => {
+ if (divRef.current && !loaded) {
+ // TODO still need to execute something here for scenarios where script takes too long to load.
+ setLoaded(true);
+
+ // timeout seems to be needed for the fade-in, perhaps? The DOM element clearly exists at this point but
+ // it won't show. Possibly a conflict with whatever google is doing.
+ setTimeout(() => {
+ if (document.contains(document.getElementById(googleBtnId)) && window.google) {
+ window.google.accounts.id.initialize({
+ /* eslint-disable @typescript-eslint/camelcase */
+ client_id: clientConfig.auth.GD_GOOGLE_AUTH_CLIENT_ID,
+ callback: onAuthenticated
+ });
+ window.google.accounts.id.renderButton(document.getElementById(googleBtnId), { type: 'standard' });
+ }
+ }, 500);
+ }
+ }, [divRef.current]);
+
+ return
;
+};
+
+SignInWithGoogleButton.displayName = 'SignInWithGoogleButton';
diff --git a/apps/client/src/core/auth/loginPage/LoginPage.component.tsx b/apps/client/src/core/auth/loginPage/LoginPage.component.tsx
new file mode 100644
index 000000000..b4c669767
--- /dev/null
+++ b/apps/client/src/core/auth/loginPage/LoginPage.component.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+export type LoginPageProps = {
+ i18n: any;
+};
+
+const LoginPage = ({ i18n }: LoginPageProps) => {
+ return {i18n.login}
;
+};
+
+export default LoginPage;
diff --git a/apps/client/src/core/auth/loginPage/LoginPage.container.tsx b/apps/client/src/core/auth/loginPage/LoginPage.container.tsx
new file mode 100644
index 000000000..b66259cb4
--- /dev/null
+++ b/apps/client/src/core/auth/loginPage/LoginPage.container.tsx
@@ -0,0 +1,15 @@
+import { connect } from 'react-redux';
+// import { Dispatch } from 'redux';
+import * as selectors from '~store/generator/generator.selectors';
+import LoginPage, { LoginPageProps } from './LoginPage.component';
+
+const mapStateToProps = (state: any): Partial => ({
+ i18n: selectors.getCoreI18n(state)
+});
+
+// dispatch: Dispatch
+const mapDispatchToProps = (): Partial => ({});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(LoginPage);
+
+export default container;
diff --git a/apps/client/src/core/auth/withAuth.tsx b/apps/client/src/core/auth/withAuth.tsx
new file mode 100644
index 000000000..180d02918
--- /dev/null
+++ b/apps/client/src/core/auth/withAuth.tsx
@@ -0,0 +1,44 @@
+import React, { useEffect } from 'react';
+import { useSelector } from 'react-redux';
+import { useNavigate } from 'react-router-dom';
+import { isOnloadAuthDetermined, isLoggedIn, getLocale } from '~store/main/main.selectors';
+import { DefaultSpinner, Centered } from '~components/loaders/loaders';
+
+// simple HOC to require authentication before rendering a component. If the onload auth isn't determined yet it shows a
+// loading spinner; if the auth is determined and they're not logged in it redirects to the homepage
+export const withAuth = (Component: any): any => {
+ const ComponentWithAuth = ({ props }: any) => {
+ const authDetermined = useSelector(isOnloadAuthDetermined);
+ const loggedIn = useSelector(isLoggedIn);
+ const locale = useSelector(getLocale);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (authDetermined && !loggedIn) {
+ let path = '/';
+ if (locale !== 'en') {
+ path += locale;
+ }
+ navigate(path);
+ }
+ }, [loggedIn, authDetermined]);
+
+ if (!authDetermined) {
+ return (
+
+
+
+ );
+ }
+
+ if (!loggedIn) {
+ return null;
+ }
+
+ return ;
+ };
+
+ ComponentWithAuth.displayName = 'ComponentWithAuth';
+
+ return ComponentWithAuth;
+};
diff --git a/apps/client/src/core/constants.ts b/apps/client/src/core/constants.ts
new file mode 100644
index 000000000..9bb9a234a
--- /dev/null
+++ b/apps/client/src/core/constants.ts
@@ -0,0 +1,61 @@
+// export default {
+// // any time we roll out backward incompatible redux structure changes, this number should be bumped. It causes
+// // anyone's setup to automatically reset & forget any previous saved redux store structure so there aren't
+// // incompatibility problems with the latest code
+// APP_STATE_VERSION: 8,
+
+// NUM_DEFAULT_ROWS: 4,
+// GITHUB_URL: 'https://github.com/benkeen/generatedata',
+// CHANGELOG_URL: 'https://github.com/benkeen/generatedata/blob/master/CHANGELOG.md',
+
+// // these map to the GD_APP_TYPE env var. They're the different available configurations for this GD installation
+// APP_TYPES: {
+// LOGIN: 'login',
+// SINGLE: 'single',
+// OPEN: 'open',
+// CLOSED: 'closed'
+// },
+
+// THEMES: [
+// { value: 'ambiance', label: 'Ambiance' },
+// { value: 'bespin', label: 'Bespin' },
+// { value: 'cobalt', label: 'Cobalt' },
+// { value: 'darcula', label: 'Darcula' },
+// { value: 'lucario', label: 'Lucario' }
+// ],
+
+// MIN_PREVIEW_ROWS: 5,
+// MAX_PREVIEW_ROWS: 20,
+// HEADER_HEIGHT: 66,
+// FOOTER_HEIGHT: 66,
+// SMALL_SCREEN_WIDTH: 720,
+// GENERATION_BATCH_SIZE: 50,
+// SMALL_GENERATION_COUNT: 1000,
+// SMALL_GENERATION_COUNT_WITH_GRAPH: 5000,
+
+// GRAPH_RANGES: {
+// RANGE1: 5000,
+// RANGE2: 10000,
+// RANGE3: 100000
+// },
+
+// GRID: {
+// SMALL_BREAKPOINT: 650,
+// MEDIUM_BREAKPOINT: 780
+// },
+
+// ZINDEXES: {
+// DRAWER: 1300,
+// DIALOG: 5005
+// },
+
+// DATA_TYPE_GROUPS: ['humanData', 'geo', 'text', 'numeric', 'other', 'financial', 'countrySpecific'],
+
+// EXPORT_TYPE_GROUPS: ['core', 'programmingLanguage'],
+
+// CONTINENTS: ['africa', 'asia', 'centralAmerica', 'europe', 'northAmerica', 'oceania', 'southAmerica'],
+
+// DATE_FORMAT: 'MMM d, y',
+// DATETIME_FORMAT: 'MMM d, y h:mm a',
+// TIME_FORMAT: 'h:mm a'
+// };
diff --git a/apps/client/src/core/dialogs/about/About.component.tsx b/apps/client/src/core/dialogs/about/About.component.tsx
new file mode 100644
index 000000000..cbaf00b6a
--- /dev/null
+++ b/apps/client/src/core/dialogs/about/About.component.tsx
@@ -0,0 +1,53 @@
+import React from 'react';
+import { PrimaryButton } from '~components/Buttons.component';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import { Github } from '~components/icons';
+import Link from '~components/Link.component';
+import { Tooltip } from '~components/tooltips';
+import styles from './About.scss';
+
+export type AboutProps = {
+ visible: boolean;
+ onClose: any;
+ scriptVersion: string;
+ i18n: any;
+};
+
+const AboutDialog = ({ visible, onClose, scriptVersion, i18n }: AboutProps) => (
+
+
+
{i18n.about}
+
+
+
+ generatedata.com —
+
+
+
+ v{scriptVersion}
+
+
+
+
+
+ {i18n.aboutInfoPara1}
+ {i18n.aboutInfoPara2}
+
+
+
+ {i18n.close}
+
+ {
+ window.open('https://github.com/benkeen/generatedata', '_blank');
+ }}
+ >
+
+ {i18n.viewOnGithub}
+
+
+
+
+);
+
+export default AboutDialog;
diff --git a/apps/client/src/core/dialogs/about/About.scss b/apps/client/src/core/dialogs/about/About.scss
new file mode 100644
index 000000000..b0b60b1c5
--- /dev/null
+++ b/apps/client/src/core/dialogs/about/About.scss
@@ -0,0 +1,24 @@
+@use '../../../styles/variables' as c;
+
+.aboutDialog {
+ .actions {
+ button {
+ svg {
+ fill: c.$primary-color;
+ height: 18px;
+ width: 18px;
+ margin-right: 6px;
+ margin-left: -4px;
+ }
+ }
+ }
+
+ h4 {
+ margin-top: 0;
+ }
+
+ :global(.MuiDialogContent-root) {
+ font-size: 13px;
+ line-height: 20px;
+ }
+}
diff --git a/apps/client/src/core/dialogs/about/About.scss.d.ts b/apps/client/src/core/dialogs/about/About.scss.d.ts
new file mode 100644
index 000000000..2dbad7302
--- /dev/null
+++ b/apps/client/src/core/dialogs/about/About.scss.d.ts
@@ -0,0 +1,13 @@
+declare namespace AboutScssNamespace {
+ export interface IAboutScss {
+ aboutDialog: string;
+ actions: string;
+ }
+}
+
+declare const AboutScssModule: AboutScssNamespace.IAboutScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: AboutScssNamespace.IAboutScss;
+};
+
+export = AboutScssModule;
diff --git a/apps/client/src/core/dialogs/clearPage/ClearPage.component.tsx b/apps/client/src/core/dialogs/clearPage/ClearPage.component.tsx
new file mode 100644
index 000000000..d05dbaa11
--- /dev/null
+++ b/apps/client/src/core/dialogs/clearPage/ClearPage.component.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import Button from '@mui/material/Button';
+import WarningIcon from '@mui/icons-material/Warning';
+import { PrimaryButton } from '~components/Buttons.component';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import styles from './ClearPage.scss';
+
+export type ClearPageDialogProps = {
+ visible: boolean;
+ onClose: any;
+ onClear: () => void;
+ i18n: any;
+};
+
+const ClearPageDialog = ({ visible, onClose, onClear, i18n }: ClearPageDialogProps) => (
+
+
+
{i18n.clearPage}
+
+
+
+
{i18n.clearPageConfirmation}
+
+
+
+
+ {i18n.yes}
+
+
+ {i18n.no}
+
+
+
+
+);
+
+export default ClearPageDialog;
diff --git a/apps/client/src/core/dialogs/clearPage/ClearPage.container.tsx b/apps/client/src/core/dialogs/clearPage/ClearPage.container.tsx
new file mode 100644
index 000000000..11b97f4e4
--- /dev/null
+++ b/apps/client/src/core/dialogs/clearPage/ClearPage.container.tsx
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+import ClearPageDialog, { ClearPageDialogProps } from './ClearPage.component';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ visible: selectors.isClearPageDialogVisible(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ onClose: (): any => dispatch(actions.hideClearPageDialog()),
+ onClear: (): any => dispatch(actions.clearPage())
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(ClearPageDialog);
+
+export default container;
diff --git a/apps/client/src/core/dialogs/clearPage/ClearPage.scss b/apps/client/src/core/dialogs/clearPage/ClearPage.scss
new file mode 100644
index 000000000..e33145789
--- /dev/null
+++ b/apps/client/src/core/dialogs/clearPage/ClearPage.scss
@@ -0,0 +1,37 @@
+@use '../../../styles/variables' as c;
+
+div.contentPanel {
+ display: flex;
+ align-items: center;
+ overflow: hidden !important;
+ padding: 10px 16px;
+
+ svg {
+ color: orange;
+ font-size: 50px;
+ margin-right: 10px;
+ }
+
+ ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+
+ li {
+ display: flex;
+ margin-bottom: 4px;
+ }
+
+ input {
+ margin-right: 5px;
+ }
+ }
+
+ .clearPageSelection {
+ svg {
+ font-size: 18px;
+ color: c.$primary-color;
+ margin-left: 6px;
+ }
+ }
+}
diff --git a/apps/client/src/core/dialogs/clearPage/ClearPage.scss.d.ts b/apps/client/src/core/dialogs/clearPage/ClearPage.scss.d.ts
new file mode 100644
index 000000000..519a6b093
--- /dev/null
+++ b/apps/client/src/core/dialogs/clearPage/ClearPage.scss.d.ts
@@ -0,0 +1,13 @@
+declare namespace ClearPageScssNamespace {
+ export interface IClearPageScss {
+ clearPageSelection: string;
+ contentPanel: string;
+ }
+}
+
+declare const ClearPageScssModule: ClearPageScssNamespace.IClearPageScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ClearPageScssNamespace.IClearPageScss;
+};
+
+export = ClearPageScssModule;
diff --git a/apps/client/src/core/dialogs/clearPage/__tests__/ClearPage.component.test.tsx b/apps/client/src/core/dialogs/clearPage/__tests__/ClearPage.component.test.tsx
new file mode 100644
index 000000000..a96da61f0
--- /dev/null
+++ b/apps/client/src/core/dialogs/clearPage/__tests__/ClearPage.component.test.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import ClearPageDialog from '../ClearPage.component';
+const i18n = require('../../../../i18n/en.json');
+
+jest.mock('copy-to-clipboard', () => {
+ return jest.fn();
+});
+
+const defaultProps = {
+ visible: true,
+ onClose: () => {},
+ onClear: () => {},
+ i18n
+};
+
+describe('ClearPageDialog', () => {
+ it('clicking "No" calls onClose', () => {
+ const onClose = jest.fn();
+ const { baseElement } = render( );
+
+ const closeButton = baseElement.querySelector('.cancelClearPage') as HTMLButtonElement;
+ fireEvent.click(closeButton);
+ expect(onClose).toHaveBeenCalled();
+ });
+});
diff --git a/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.component.tsx b/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.component.tsx
new file mode 100644
index 000000000..6cb08c67d
--- /dev/null
+++ b/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.component.tsx
@@ -0,0 +1,40 @@
+import React from 'react';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import { PrimaryButton, NullButton } from '~components/Buttons.component';
+import styles from './DeleteAccount.scss';
+
+export type DeleteAccountDialogProps = {
+ visible: boolean;
+ name: string;
+ onClose: () => void;
+ onDelete: () => void;
+ onExited: () => void;
+ i18n: any;
+};
+
+const DeleteAccountDialog = ({ visible, name, onClose, onDelete, onExited, i18n }: DeleteAccountDialogProps) => (
+
+
+
{i18n.deleteAccount}
+
+ {i18n.confirmDeleteAccount}
+
+
+ {name}
+
+
+
+ {i18n.cancel}
+ {i18n.delete}
+
+
+
+);
+
+export default DeleteAccountDialog;
diff --git a/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.scss b/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.scss
new file mode 100644
index 000000000..91c3d249b
--- /dev/null
+++ b/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.scss
@@ -0,0 +1,11 @@
+@use '../../../styles/variables';
+
+div.contentPanel {
+ padding: 10px 16px;
+}
+
+.accountName {
+ font-size: 18px;
+ margin-top: 5px;
+ color: #888888;
+}
diff --git a/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.scss.d.ts b/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.scss.d.ts
new file mode 100644
index 000000000..e3d04dd5e
--- /dev/null
+++ b/apps/client/src/core/dialogs/deleteAccount/DeleteAccount.scss.d.ts
@@ -0,0 +1,13 @@
+declare namespace DeleteAccountScssNamespace {
+ export interface IDeleteAccountScss {
+ accountName: string;
+ contentPanel: string;
+ }
+}
+
+declare const DeleteAccountScssModule: DeleteAccountScssNamespace.IDeleteAccountScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: DeleteAccountScssNamespace.IDeleteAccountScss;
+};
+
+export = DeleteAccountScssModule;
diff --git a/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.component.tsx b/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.component.tsx
new file mode 100644
index 000000000..f3bdcfe55
--- /dev/null
+++ b/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.component.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { PrimaryButton, NullButton } from '~components/Buttons.component';
+import WarningIcon from '@mui/icons-material/Warning';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import styles from './DeleteDataSetDialog.scss';
+
+export type DeleteDataSetDialogProps = {
+ visible: boolean;
+ onClose: () => void;
+ onDelete: () => void;
+ i18n: any;
+ dataSetName?: string | null;
+};
+
+const DeleteDataSetDialog = ({ visible, dataSetName, onClose, onDelete, i18n }: DeleteDataSetDialogProps) => (
+
+
+
{i18n.deleteDataSet}
+
+
+
+ {i18n.deleteDataSetConfirm}
+ {dataSetName ?
{dataSetName}
: null}
+
+
+
+ {i18n.cancel}
+ {i18n.delete}
+
+
+
+);
+
+export default DeleteDataSetDialog;
diff --git a/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.scss b/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.scss
new file mode 100644
index 000000000..a9a73e3e8
--- /dev/null
+++ b/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.scss
@@ -0,0 +1,35 @@
+@use '../../../styles/variables';
+
+div.contentPanel {
+ display: flex;
+ align-items: center;
+ overflow: hidden !important;
+ padding: 10px 16px;
+
+ svg {
+ color: orange;
+ font-size: 50px;
+ margin-right: 10px;
+ }
+
+ ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+
+ li {
+ display: flex;
+ margin-bottom: 4px;
+ }
+
+ input {
+ margin-right: 5px;
+ }
+ }
+}
+
+.dataSetName {
+ font-size: 18px;
+ margin-top: 5px;
+ color: #888888;
+}
diff --git a/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.scss.d.ts b/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.scss.d.ts
new file mode 100644
index 000000000..81f4b8bb9
--- /dev/null
+++ b/apps/client/src/core/dialogs/deleteDataSet/DeleteDataSetDialog.scss.d.ts
@@ -0,0 +1,13 @@
+declare namespace DeleteDataSetDialogScssNamespace {
+ export interface IDeleteDataSetDialogScss {
+ contentPanel: string;
+ dataSetName: string;
+ }
+}
+
+declare const DeleteDataSetDialogScssModule: DeleteDataSetDialogScssNamespace.IDeleteDataSetDialogScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: DeleteDataSetDialogScssNamespace.IDeleteDataSetDialogScss;
+};
+
+export = DeleteDataSetDialogScssModule;
diff --git a/apps/client/src/core/dialogs/deleteDataSet/__tests__/DeleteDataSetDialog.component.test.tsx b/apps/client/src/core/dialogs/deleteDataSet/__tests__/DeleteDataSetDialog.component.test.tsx
new file mode 100644
index 000000000..2b396ee8b
--- /dev/null
+++ b/apps/client/src/core/dialogs/deleteDataSet/__tests__/DeleteDataSetDialog.component.test.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import DeleteDataSetDialog from '../DeleteDataSetDialog.component';
+const i18n = require('../../../../i18n/en.json');
+
+const defaultProps = {
+ visible: true,
+ onClose: () => {},
+ onDelete: () => {},
+ i18n,
+ dataSetName: 'Data Set Name'
+};
+
+describe('DeleteDataSetDialog', () => {
+ it('clicking close calls the onClose callback', () => {
+ const onClose = jest.fn();
+ const { baseElement } = render( );
+
+ const closeButton = baseElement.querySelector('.MuiDialogTitle-root button') as HTMLButtonElement;
+ fireEvent.click(closeButton);
+ expect(onClose).toBeCalled();
+ });
+});
diff --git a/apps/client/src/core/dialogs/help/HelpDialog.component.tsx b/apps/client/src/core/dialogs/help/HelpDialog.component.tsx
new file mode 100644
index 000000000..cf3cfde27
--- /dev/null
+++ b/apps/client/src/core/dialogs/help/HelpDialog.component.tsx
@@ -0,0 +1,112 @@
+import React, { useState } from 'react';
+import Button from '@mui/material/Button';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import { DropdownOption } from '~components/dropdown/Dropdown';
+import { MediumSpinner } from '~components/loaders/loaders';
+import { getSortedGroupedDataTypes, getDataType } from '~utils/dataTypes';
+import styles from './HelpDialog.scss';
+import { DataTypeFolder } from '@generatedata/plugins';
+
+export type HelpDialogProps = {
+ visible: boolean;
+ initialDataType: DataTypeFolder | null;
+ loadedDataTypes: any; // done on purpose. This ensures it repaints after a new DT is loaded
+ onClose: any;
+ coreI18n: any;
+ dataTypeI18n: any;
+ onSelectDataType: (dataType: DataTypeFolder) => void;
+};
+
+const DataTypeList = ({ onSelect, filterString }: any): any => {
+ const dataTypes = getSortedGroupedDataTypes();
+ const regex = new RegExp(filterString, 'i');
+ const content: any = [];
+
+ dataTypes.forEach(({ label, options }: { label: string; options: any }) => {
+ let list: any = options;
+ if (filterString.trim() !== '') {
+ list = list.filter(({ value, label }: DropdownOption) => regex.test(value) || regex.test(label));
+ }
+ list = list.map(({ value, label }: DropdownOption) => (
+ onSelect(value)}>
+ {label}
+
+ ));
+
+ if (list.length) {
+ content.push(
+
+ );
+ }
+ });
+
+ return content;
+};
+
+const HelpDialog = ({ visible, initialDataType, onClose, coreI18n, dataTypeI18n, onSelectDataType }: HelpDialogProps) => {
+ const [dataType, setDataType] = useState(null);
+ const [filterString, setFilterString] = useState('');
+
+ const selectDataType = (dataType: DataTypeFolder): void => {
+ onSelectDataType(dataType);
+ setDataType(dataType);
+ };
+
+ React.useEffect(() => {
+ setDataType(initialDataType);
+ }, [initialDataType]);
+
+ const i18n = dataType ? dataTypeI18n[dataType] : {};
+ const { Help } = getDataType(dataType);
+
+ let spinnerStyles = styles.spinner;
+ if (Help) {
+ spinnerStyles += ` ${styles.fadeOut}`;
+ }
+
+ return (
+ {
+ setFilterString('');
+ }
+ }}
+ >
+
+
{i18n.NAME}
+
+
+
setFilterString(e.target.value)}
+ />
+
+
+
+
+
+ {Help ? : null}
+
+
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export default HelpDialog;
diff --git a/apps/client/src/core/dialogs/help/HelpDialog.container.tsx b/apps/client/src/core/dialogs/help/HelpDialog.container.tsx
new file mode 100644
index 000000000..1a387cce1
--- /dev/null
+++ b/apps/client/src/core/dialogs/help/HelpDialog.container.tsx
@@ -0,0 +1,25 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+import HelpDialog, { HelpDialogProps } from './HelpDialog.component';
+import { DataTypeFolder } from '@generatedata/plugins';
+
+const mapStateToProps = (
+ state: any
+): Pick => ({
+ visible: selectors.isHelpDialogVisible(state),
+ coreI18n: selectors.getCoreI18n(state),
+ dataTypeI18n: selectors.getDataTypeI18n(state),
+ initialDataType: selectors.getHelpDialogSection(state),
+ loadedDataTypes: selectors.getLoadedDataTypes(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ onClose: (): any => dispatch(actions.hideHelpDialog()),
+ onSelectDataType: (dataType: DataTypeFolder): any => dispatch(actions.onSelectDataType(dataType))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(HelpDialog);
+
+export default container;
diff --git a/apps/client/src/core/dialogs/help/HelpDialog.scss b/apps/client/src/core/dialogs/help/HelpDialog.scss
new file mode 100644
index 000000000..d1cb7a8a2
--- /dev/null
+++ b/apps/client/src/core/dialogs/help/HelpDialog.scss
@@ -0,0 +1,78 @@
+@use '../../../styles/variables' as c;
+
+.helpDialog {
+ height: 100%;
+
+ :global(.MuiDialog-paper) {
+ height: 100%;
+ }
+}
+
+.contentPanel {
+ display: flex;
+ overflow: hidden !important;
+}
+
+.dataTypeList {
+ flex: 0 0 200px;
+ margin-right: 15px;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ font-size: 13px;
+
+ input {
+ width: 100%;
+ flex: 0 0 auto;
+ }
+
+ ul {
+ margin: 0;
+ list-style: none;
+ padding: 0 0 0 10px;
+ font-size: 12px;
+ line-height: 20px;
+ }
+ li {
+ color: c.$primary-color;
+ cursor: pointer;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
+.helpContent {
+ position: relative;
+ flex: 1;
+ padding-bottom: 15px;
+ overflow: scroll;
+ font-size: 13px;
+ line-height: 21px;
+ p:first-child {
+ margin-top: 0;
+ }
+}
+
+.list {
+ flex: 1;
+ overflow: scroll;
+}
+
+.spinner {
+ position: absolute;
+ top: calc(50% - 40px);
+ left: calc(50% - 40px);
+
+ &.fadeOut {
+ opacity: 0;
+ transition: opacity 0.25s ease-in-out;
+ }
+}
+
+.dialog {
+ width: 800px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
diff --git a/apps/client/src/core/dialogs/help/HelpDialog.scss.d.ts b/apps/client/src/core/dialogs/help/HelpDialog.scss.d.ts
new file mode 100644
index 000000000..93516beb1
--- /dev/null
+++ b/apps/client/src/core/dialogs/help/HelpDialog.scss.d.ts
@@ -0,0 +1,19 @@
+declare namespace HelpDialogScssNamespace {
+ export interface IHelpDialogScss {
+ contentPanel: string;
+ dataTypeList: string;
+ dialog: string;
+ fadeOut: string;
+ helpContent: string;
+ helpDialog: string;
+ list: string;
+ spinner: string;
+ }
+}
+
+declare const HelpDialogScssModule: HelpDialogScssNamespace.IHelpDialogScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: HelpDialogScssNamespace.IHelpDialogScss;
+};
+
+export = HelpDialogScssModule;
diff --git a/apps/client/src/core/dialogs/help/__tests__/HelpDialog.component.test.tsx b/apps/client/src/core/dialogs/help/__tests__/HelpDialog.component.test.tsx
new file mode 100644
index 000000000..bf393a0a0
--- /dev/null
+++ b/apps/client/src/core/dialogs/help/__tests__/HelpDialog.component.test.tsx
@@ -0,0 +1,45 @@
+import React from 'react';
+import sinon from 'sinon';
+import { render, fireEvent } from '@testing-library/react';
+import HelpDialog from '../HelpDialog.component';
+import { DataTypeFolder } from '@generatedata/plugins';
+import * as langUtils from '@generatedata/utils/lang';
+import * as dataTypeUtils from '~utils/dataTypes';
+const i18n = require('../../../../i18n/en.json');
+const NamesI18n = require('../../../../plugins/dataTypes/Names/i18n/en.json');
+
+const defaultProps = {
+ initialDataType: 'Names' as DataTypeFolder,
+ visible: true,
+ onClose: () => {},
+ coreI18n: i18n,
+ dataTypeI18n: {
+ Names: NamesI18n
+ },
+ onSelectDataType: () => {},
+ loadedDataTypes: {}
+};
+
+describe('HelpDialog', () => {
+ it('clicking close calls the onClose callback', () => {
+ sinon.stub(langUtils, 'getStrings').returns({
+ core: i18n,
+ dataTypes: {
+ Names: NamesI18n
+ }
+ });
+ sinon.stub(dataTypeUtils, 'getSortedGroupedDataTypes').returns([
+ {
+ label: 'Blah',
+ options: [{ value: 'Names', label: 'Names' }]
+ }
+ ]);
+
+ const onClose = jest.fn();
+ const { baseElement } = render( );
+
+ const closeButton = baseElement.querySelector('.MuiDialogTitle-root button') as HTMLButtonElement;
+ fireEvent.click(closeButton);
+ expect(onClose).toBeCalled();
+ });
+});
diff --git a/apps/client/src/core/dialogs/login/Login.component.tsx b/apps/client/src/core/dialogs/login/Login.component.tsx
new file mode 100644
index 000000000..9bb470dbc
--- /dev/null
+++ b/apps/client/src/core/dialogs/login/Login.component.tsx
@@ -0,0 +1,220 @@
+import React, { useEffect, useRef, useState } from 'react';
+import Button from '@mui/material/Button';
+import TextField from '~components/TextField';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import { isValidEmail, addToast } from '@generatedata/utils/general';
+import { DialogLoadingSpinner } from '~components/loaders/loaders';
+import { hasVendorLogin, getVendorLoginButtons } from '@generatedata/utils/auth';
+import styles from './Login.scss';
+import { useNavigate } from 'react-router';
+
+const showVendorLoginColumn = hasVendorLogin();
+const vendorLoginButtons = getVendorLoginButtons();
+
+export type LoginDialogProps = {
+ visible: boolean;
+ defaultEmail: string;
+ dialogProcessing: boolean;
+ onClose: () => void;
+ onExited: () => void;
+ onSubmit: (email: string, password: string, navigate: any, onError: Function) => void;
+ showPasswordResetDialog: (email: string) => void;
+ i18n: any;
+};
+
+/**
+ * The login dialog has baked-in support for standard logging into our database, but also optionally supports
+ * logging in via external vendors: Google, Facebook and Github.
+ */
+const LoginDialog = ({
+ visible,
+ defaultEmail,
+ onClose,
+ dialogProcessing,
+ onSubmit,
+ onExited,
+ showPasswordResetDialog,
+ i18n
+}: LoginDialogProps) => {
+ const navigate = useNavigate();
+
+ const textFieldRef = useRef();
+ const [email, setEmail] = useState('');
+ const [emailError, setEmailError] = useState('');
+ const [password, setPassword] = useState('');
+ const [passwordError, setPasswordError] = useState('');
+ const [autoFocusPasswordField, shouldAutoFocusPasswordField] = useState(false);
+ const passwordFieldRef = useRef();
+
+ useEffect(() => {
+ if (!visible) {
+ setEmail('');
+ setPassword('');
+ return;
+ }
+ }, [visible]);
+
+ useEffect(() => {
+ if (email === '' && defaultEmail) {
+ setEmail(defaultEmail);
+ shouldAutoFocusPasswordField(true);
+ }
+ }, [defaultEmail]);
+
+ const onLogin = (e: any): void => {
+ e.preventDefault();
+
+ let eError = '';
+ if (!email.trim()) {
+ eError = i18n.validationNoEmail;
+ } else if (!isValidEmail(email)) {
+ eError = i18n.validationInvalidEmail;
+ }
+ setEmailError(eError);
+
+ const pError = password ? '' : i18n.validationNoPassword;
+ setPasswordError(pError);
+
+ if (!eError && !pError) {
+ onSubmit(email, password, navigate, (error: string) => {
+ let errorMessage = i18n.userNotFound;
+ if (error === 'accountExpired') {
+ errorMessage = i18n.accountExpiredMsg;
+ }
+
+ addToast({
+ type: 'error',
+ message: errorMessage
+ });
+
+ if (passwordFieldRef && passwordFieldRef.current) {
+ passwordFieldRef.current.select();
+ passwordFieldRef.current.focus();
+ }
+ });
+ }
+ };
+
+ const updateEmail = (email: string): void => {
+ if (emailError) {
+ setEmailError('');
+ }
+ setEmail(email);
+ };
+
+ const updatePassword = (password: string): void => {
+ if (passwordError) {
+ setPasswordError('');
+ }
+ setPassword(password);
+ };
+
+ let width = 380;
+ let layoutClass = '';
+
+ if (showVendorLoginColumn) {
+ width = 500;
+ layoutClass = styles.withSecondCol;
+ }
+
+ const getSecondColumn = () => {
+ if (!showVendorLoginColumn) {
+ return null;
+ }
+
+ // @ts-ignore-line
+ const buttons = vendorLoginButtons.map((VendorButton, index) => );
+
+ return (
+ <>
+
+ {buttons}
+ >
+ );
+ };
+
+ const onEntered = (): void => {
+ if (autoFocusPasswordField) {
+ passwordFieldRef?.current?.focus();
+ shouldAutoFocusPasswordField(false);
+ }
+ };
+
+ const onPastePassword = (e: any): void => {
+ const pwd = (e.clipboardData || window.clipboardData).getData('text');
+ if (!password && pwd) {
+ setPassword(pwd.trim());
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+ >
+ );
+};
+
+export default LoginDialog;
diff --git a/apps/client/src/core/dialogs/login/Login.container.ts b/apps/client/src/core/dialogs/login/Login.container.ts
new file mode 100644
index 000000000..746c099d3
--- /dev/null
+++ b/apps/client/src/core/dialogs/login/Login.container.ts
@@ -0,0 +1,31 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as mainSelectors from '~store/main/main.selectors';
+import LoginDialog, { LoginDialogProps } from './Login.component';
+import * as mainActions from '~store/main/main.actions';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ visible: mainSelectors.shouldShowLoginDialog(state),
+ dialogProcessing: mainSelectors.isDialogProcessing(state),
+ defaultEmail: mainSelectors.getLoginDefaultEmail(state)
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ onClose: (): any => dispatch(mainActions.setLoginDialogVisibility(false)),
+ onExited: (): any => dispatch(mainActions.clearLoginFlow()),
+ onSubmit: (email: string, password: string, navigate: any, onLoginError: any): any => {
+ dispatch(mainActions.login(email, password, navigate, onLoginError));
+ },
+ showPasswordResetDialog: (email: string): void => {
+ dispatch(mainActions.setLoginDialogVisibility(false));
+ dispatch(mainActions.setPasswordResetDialogVisibility(true, email));
+ }
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(LoginDialog);
+
+export default container;
diff --git a/apps/client/src/core/dialogs/login/Login.scss b/apps/client/src/core/dialogs/login/Login.scss
new file mode 100644
index 000000000..a4fddab38
--- /dev/null
+++ b/apps/client/src/core/dialogs/login/Login.scss
@@ -0,0 +1,50 @@
+@use '../../../styles/variables' as c;
+
+.loginDialog {
+ label {
+ color: #999999;
+ }
+ input {
+ font-size: 14px;
+ }
+}
+
+.withSecondCol {
+ display: flex;
+ flex-direction: row;
+}
+
+.col {
+ flex: 1;
+}
+
+.separator {
+ margin: 0 25px;
+ position: relative;
+ display: flex;
+ align-items: center;
+ border-left: 1px solid #efefef;
+
+ & > div {
+ position: absolute;
+ left: -8px;
+ background-color: white;
+ padding: 2px;
+ color: #555555;
+ }
+}
+
+div.actionsRow {
+ justify-content: space-between;
+}
+
+.forgotPasswordLink {
+ margin-left: 10px;
+ color: #666666;
+ cursor: pointer;
+
+ &:hover {
+ color: c.$primary-color;
+ text-decoration: underline;
+ }
+}
diff --git a/apps/client/src/core/dialogs/login/Login.scss.d.ts b/apps/client/src/core/dialogs/login/Login.scss.d.ts
new file mode 100644
index 000000000..2e2d34d52
--- /dev/null
+++ b/apps/client/src/core/dialogs/login/Login.scss.d.ts
@@ -0,0 +1,17 @@
+declare namespace LoginScssNamespace {
+ export interface ILoginScss {
+ actionsRow: string;
+ col: string;
+ forgotPasswordLink: string;
+ loginDialog: string;
+ separator: string;
+ withSecondCol: string;
+ }
+}
+
+declare const LoginScssModule: LoginScssNamespace.ILoginScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: LoginScssNamespace.ILoginScss;
+};
+
+export = LoginScssModule;
diff --git a/apps/client/src/core/dialogs/login/__tests__/Login.component.test.tsx b/apps/client/src/core/dialogs/login/__tests__/Login.component.test.tsx
new file mode 100644
index 000000000..70f9335c1
--- /dev/null
+++ b/apps/client/src/core/dialogs/login/__tests__/Login.component.test.tsx
@@ -0,0 +1,74 @@
+import React from 'react';
+import { render, fireEvent } from '@testing-library/react';
+import LoginDialog from '../Login.component';
+import sharedStyles from '../../../../styles/shared.scss';
+const i18n = require('../../../../i18n/en.json');
+
+const defaultProps = {
+ visible: false,
+ onClose: () => {},
+ onSubmit: () => {},
+ onExited: () => {},
+ showPasswordResetDialog: () => {},
+ dialogProcessing: false,
+ defaultEmail: '',
+ i18n
+};
+
+describe('LoginDialog', () => {
+ it('clicking close calls the onClose callback', () => {
+ const onClose = jest.fn();
+ const { baseElement } = render( );
+
+ const closeButton = baseElement.querySelector('.MuiDialogTitle-root button') as HTMLButtonElement;
+ fireEvent.click(closeButton);
+ expect(onClose).toBeCalled();
+ });
+
+ it('clicking enter in the fields submits the form and shows errors on the fields', () => {
+ const onSubmit = jest.fn();
+ const { baseElement } = render( );
+
+ const inputFields = baseElement.querySelectorAll('input') as NodeListOf;
+
+ // weird, but it seems you can't simulate the click in another way
+ fireEvent.submit(inputFields[0]);
+
+ expect(inputFields[0].classList.contains(sharedStyles.errorField)).toBeTruthy();
+ expect(inputFields[1].classList.contains(sharedStyles.errorField)).toBeTruthy();
+ expect(onSubmit).not.toHaveBeenCalled();
+ });
+
+ it('when email is invalid but password correct only shows erorr on password field', () => {
+ const onSubmit = jest.fn();
+ const { baseElement } = render( );
+
+ const inputFields = baseElement.querySelectorAll('input') as NodeListOf;
+
+ // weird, but it seems you can't simulate the click in another way
+ fireEvent.change(inputFields[0], { target: { value: 'tom@something' } });
+ fireEvent.change(inputFields[1], { target: { value: 'password123' } });
+
+ fireEvent.submit(inputFields[0]);
+
+ expect(inputFields[0].classList.contains(sharedStyles.errorField)).toBeTruthy();
+ expect(inputFields[1].classList.contains(sharedStyles.errorField)).toBeFalsy();
+ });
+
+ it('when fields are valid it submits the form', () => {
+ const onSubmit = jest.fn();
+ const { baseElement } = render( );
+
+ const inputFields = baseElement.querySelectorAll('input') as NodeListOf;
+
+ // weird, but it seems you can't simulate the click in another way
+ fireEvent.change(inputFields[0], {
+ target: { value: 'tom@something.com' }
+ });
+ fireEvent.change(inputFields[1], { target: { value: 'password123' } });
+
+ fireEvent.submit(inputFields[0]);
+
+ expect(onSubmit).toHaveBeenCalledWith('tom@something.com', 'password123', undefined, expect.anything());
+ });
+});
diff --git a/apps/client/src/core/dialogs/login/__tests__/Login.container.test.tsx b/apps/client/src/core/dialogs/login/__tests__/Login.container.test.tsx
new file mode 100644
index 000000000..3a14ea6d0
--- /dev/null
+++ b/apps/client/src/core/dialogs/login/__tests__/Login.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import LoginDialog from '../Login.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('LoginDialog container', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/dialogs/passwordReset/PasswordReset.component.tsx b/apps/client/src/core/dialogs/passwordReset/PasswordReset.component.tsx
new file mode 100644
index 000000000..14984e665
--- /dev/null
+++ b/apps/client/src/core/dialogs/passwordReset/PasswordReset.component.tsx
@@ -0,0 +1,115 @@
+import React, { useEffect, useRef, useState } from 'react';
+import Button from '@mui/material/Button';
+import ArrowLeftIcon from '@mui/icons-material/ArrowLeft';
+import TextField from '~components/TextField';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import { isValidEmail } from '@generatedata/utils/general';
+import { DialogLoadingSpinner } from '~components/loaders/loaders';
+import styles from './PasswordReset.scss';
+
+export type PasswordResetDialogProps = {
+ visible: boolean;
+ dialogProcessing: boolean;
+ defaultEmail: string;
+ onClose: () => void;
+ onSubmit: (email: string, onError: Function) => void;
+ showLoginDialog: (email: string) => void;
+ i18n: any;
+};
+
+const PasswordResetDialog = ({
+ visible,
+ onClose,
+ dialogProcessing,
+ onSubmit,
+ showLoginDialog,
+ defaultEmail,
+ i18n
+}: PasswordResetDialogProps) => {
+ const textFieldRef = useRef();
+ const [email, setEmail] = useState('');
+ const [emailError, setEmailError] = useState('');
+
+ useEffect(() => {
+ if (!visible) {
+ setEmail('');
+ return;
+ }
+ }, [visible]);
+
+ useEffect(() => {
+ setEmail(defaultEmail);
+ }, [defaultEmail]);
+
+ const onLogin = (e: any): void => {
+ e.preventDefault();
+
+ let eError = '';
+ if (!email.trim()) {
+ eError = i18n.validationNoEmail;
+ } else if (!isValidEmail(email)) {
+ eError = i18n.validationInvalidEmail;
+ }
+ setEmailError(eError);
+
+ if (!eError) {
+ onSubmit(email, () => {
+ // addToast({
+ // type: 'error',
+ // message: i18n.userNotFound
+ // });
+ //
+ // if (textFieldRef && textFieldRef.current) {
+ // textFieldRef.current.select();
+ // textFieldRef.current.focus();
+ // }
+ });
+ }
+ };
+
+ const updateEmail = (email: string): void => {
+ if (emailError) {
+ setEmailError('');
+ }
+ setEmail(email);
+ };
+
+ return (
+
+
+
+
+ );
+};
+
+export default PasswordResetDialog;
diff --git a/apps/client/src/core/dialogs/passwordReset/PasswordReset.container.ts b/apps/client/src/core/dialogs/passwordReset/PasswordReset.container.ts
new file mode 100644
index 000000000..aa3af24e6
--- /dev/null
+++ b/apps/client/src/core/dialogs/passwordReset/PasswordReset.container.ts
@@ -0,0 +1,28 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import * as selectors from '~store/generator/generator.selectors';
+import * as mainSelectors from '~store/main/main.selectors';
+import PasswordReset, { PasswordResetDialogProps } from './PasswordReset.component';
+import * as mainActions from '~store/main/main.actions';
+
+const mapStateToProps = (state: any): Partial => ({
+ i18n: selectors.getCoreI18n(state),
+ visible: mainSelectors.shouldShowPasswordResetDialog(state),
+ dialogProcessing: mainSelectors.isDialogProcessing(state),
+ defaultEmail: mainSelectors.getPasswordResetDialogDefaultEmail(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ onClose: (): any => dispatch(mainActions.setPasswordResetDialogVisibility(false)),
+ onSubmit: (email: string, onLoginError: any): any => {
+ dispatch(mainActions.sendPasswordResetEmail(email, onLoginError));
+ },
+ showLoginDialog: (email: string): void => {
+ dispatch(mainActions.setLoginDialogVisibility(true, email));
+ dispatch(mainActions.setPasswordResetDialogVisibility(false));
+ }
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(PasswordReset);
+
+export default container;
diff --git a/apps/client/src/core/dialogs/passwordReset/PasswordReset.scss b/apps/client/src/core/dialogs/passwordReset/PasswordReset.scss
new file mode 100644
index 000000000..70977689a
--- /dev/null
+++ b/apps/client/src/core/dialogs/passwordReset/PasswordReset.scss
@@ -0,0 +1,54 @@
+@use '../../../styles/variables' as c;
+
+.loginDialog {
+ label {
+ color: #999999;
+ }
+ input {
+ font-size: 14px;
+ }
+}
+
+.withSecondCol {
+ display: flex;
+ flex-direction: row;
+}
+
+.col {
+ flex: 1;
+}
+
+.separator {
+ margin: 0 25px;
+ position: relative;
+ display: flex;
+ align-items: center;
+ border-left: 1px solid #efefef;
+
+ & > div {
+ position: absolute;
+ left: -8px;
+ background-color: white;
+ padding: 2px;
+ color: #555555;
+ }
+}
+
+div.actionsRow {
+ justify-content: space-between;
+}
+
+.loginLink {
+ margin-left: 2px;
+ color: #666666;
+ cursor: pointer;
+
+ & > div {
+ display: flex;
+ }
+
+ &:hover {
+ color: c.$primary-color;
+ text-decoration: underline;
+ }
+}
diff --git a/apps/client/src/core/dialogs/passwordReset/PasswordReset.scss.d.ts b/apps/client/src/core/dialogs/passwordReset/PasswordReset.scss.d.ts
new file mode 100644
index 000000000..5fbd7e088
--- /dev/null
+++ b/apps/client/src/core/dialogs/passwordReset/PasswordReset.scss.d.ts
@@ -0,0 +1,17 @@
+declare namespace PasswordResetScssNamespace {
+ export interface IPasswordResetScss {
+ actionsRow: string;
+ col: string;
+ loginDialog: string;
+ loginLink: string;
+ separator: string;
+ withSecondCol: string;
+ }
+}
+
+declare const PasswordResetScssModule: PasswordResetScssNamespace.IPasswordResetScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: PasswordResetScssNamespace.IPasswordResetScss;
+};
+
+export = PasswordResetScssModule;
diff --git a/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.component.tsx b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.component.tsx
new file mode 100644
index 000000000..059b476b5
--- /dev/null
+++ b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.component.tsx
@@ -0,0 +1,110 @@
+import React, { useCallback, useRef, useState } from 'react';
+import { useHistory } from 'react-router';
+import PersonAddIcon from '@mui/icons-material/PersonAdd';
+import TextField from '~components/TextField';
+import { PrimaryButton } from '~components/Buttons.component';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import styles from './SaveDataSet.scss';
+import { SaveDataDialogType } from '~store/account/account.reducer';
+
+export type SaveDataSetDialogProps = {
+ visible: boolean;
+ isLoggedIn: boolean;
+ dialogType: SaveDataDialogType;
+ showRegistration: boolean;
+ onRedirectToLogin: () => void;
+ onClose: any;
+ onSave: (dataSetName: string) => void;
+ i18n: any;
+};
+
+const SaveDataSetDialog = ({
+ visible,
+ isLoggedIn,
+ dialogType,
+ onClose,
+ onSave,
+ showRegistration,
+ onRedirectToLogin,
+ i18n
+}: SaveDataSetDialogProps) => {
+ const history = useHistory();
+
+ const newDataSetNameField = useRef();
+ const [newDataSetName, setNewDataSetName] = useState('');
+ const [newDataSetNameError, setNewDataSetErrorName] = useState('');
+
+ const onExited = (): void => {
+ setNewDataSetName('');
+ setNewDataSetErrorName('');
+ };
+
+ let title = dialogType === SaveDataDialogType.save ? i18n.save : i18n.saveAs;
+ let content = (
+
+ {
+ setNewDataSetName(e.target.value);
+ setNewDataSetErrorName('');
+ }}
+ />
+
+ );
+
+ const saveDataSet = (e: any): void => {
+ e.preventDefault();
+
+ if (!newDataSetName.trim()) {
+ setNewDataSetErrorName(i18n.missingDataSetName);
+ newDataSetNameField.current!.focus();
+ } else {
+ onSave(newDataSetName);
+ // TODO loading spinner
+ }
+ };
+
+ const gotoRegistration = useCallback(() => {
+ onClose();
+ history.push('/register');
+ }, []);
+
+ let buttons = {i18n.save} ;
+
+ if (!isLoggedIn) {
+ title = i18n.pleaseLogin;
+ const msg = showRegistration ? i18n.loginOrRegisterToSave : i18n.loginToSave;
+ content = (
+
+ );
+ buttons = (
+ <>
+ {i18n.login}
+ {showRegistration && {i18n.register} }
+ >
+ );
+ }
+
+ return (
+
+
+
+ );
+};
+
+export default SaveDataSetDialog;
diff --git a/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.container.tsx b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.container.tsx
new file mode 100644
index 000000000..bfb12fb13
--- /dev/null
+++ b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.container.tsx
@@ -0,0 +1,37 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import SaveDataSetDialog, { SaveDataSetDialogProps } from './SaveDataSet.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as accountSelectors from '~store/account/account.selectors';
+import * as actions from '~store/account/account.actions';
+import * as mainActions from '~store/main/main.actions';
+import * as mainSelectors from '~store/main/main.selectors';
+import clientConfig from '@generatedata/config/clientConfig';
+
+const mapStateToProps = (
+ state: any
+): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ visible: accountSelectors.shouldShowSaveDataSetDialog(state),
+ isLoggedIn: mainSelectors.isLoggedIn(state),
+ dialogType: accountSelectors.getSaveDataDialogType(state),
+
+ // TODO. We should offer an option for users to register themselves and not rely on the admin to do it for them
+ showRegistration: clientConfig.appSettings.GD_APP_TYPE === 'prod'
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ onClose: (): any => dispatch(actions.hideSaveDataSetDialog()),
+ onRedirectToLogin: (): any => {
+ dispatch(actions.hideSaveDataSetDialog());
+ dispatch(mainActions.setLoginDialogVisibility(true));
+
+ // this returns them to the save dialog after logging in
+ mainActions.setReturnToSaveDataSet();
+ },
+ onSave: (dataSetName: string): any => dispatch(actions.saveNewDataSet(dataSetName))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(SaveDataSetDialog);
+
+export default container;
diff --git a/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.scss b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.scss
new file mode 100644
index 000000000..e99af03e2
--- /dev/null
+++ b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.scss
@@ -0,0 +1,51 @@
+@use '../../../styles/variables';
+
+div.contentPanel {
+ display: flex;
+ align-items: center;
+ overflow: hidden !important;
+ padding: 10px 16px;
+
+ .notLoggedIn {
+ margin: 8px 0;
+ line-height: 20px;
+ font-size: 13px;
+ display: flex;
+ align-items: center;
+
+ svg {
+ color: #275eb5;
+ opacity: 0.8;
+ font-size: 50px;
+ margin-right: 18px;
+ }
+ }
+
+ ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+
+ li {
+ display: flex;
+ margin-bottom: 4px;
+ }
+
+ input {
+ margin-right: 5px;
+ }
+ }
+}
+
+.newDataSet {
+ flex: 1;
+
+ input {
+ font-size: 14px;
+ width: 100%;
+ }
+}
+
+.existingDataSet {
+ font-size: 14px;
+}
diff --git a/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.scss.d.ts b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.scss.d.ts
new file mode 100644
index 000000000..88c453cd2
--- /dev/null
+++ b/apps/client/src/core/dialogs/saveDataSet/SaveDataSet.scss.d.ts
@@ -0,0 +1,15 @@
+declare namespace SaveDataSetScssNamespace {
+ export interface ISaveDataSetScss {
+ contentPanel: string;
+ existingDataSet: string;
+ newDataSet: string;
+ notLoggedIn: string;
+ }
+}
+
+declare const SaveDataSetScssModule: SaveDataSetScssNamespace.ISaveDataSetScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: SaveDataSetScssNamespace.ISaveDataSetScss;
+};
+
+export = SaveDataSetScssModule;
diff --git a/apps/client/src/core/dialogs/saveDataSet/__tests__/SaveDataSet.container.test.tsx b/apps/client/src/core/dialogs/saveDataSet/__tests__/SaveDataSet.container.test.tsx
new file mode 100644
index 000000000..7c08309ad
--- /dev/null
+++ b/apps/client/src/core/dialogs/saveDataSet/__tests__/SaveDataSet.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import SaveDataSet from '../SaveDataSet.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('SaveDataSet Container', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/dialogs/schema/Schema.component.tsx b/apps/client/src/core/dialogs/schema/Schema.component.tsx
new file mode 100644
index 000000000..29a169bfe
--- /dev/null
+++ b/apps/client/src/core/dialogs/schema/Schema.component.tsx
@@ -0,0 +1,121 @@
+import React from 'react';
+import { Theme, styled } from '@mui/material/styles';
+import { Controlled as CodeMirror } from 'react-codemirror2';
+import Tabs from '@mui/material/Tabs';
+import Tab from '@mui/material/Tab';
+import { PrimaryButton } from '~components/Buttons.component';
+import { Dialog, DialogTitle, DialogContent, DialogActions } from '~components/dialogs';
+import FeatureToggles from '../../featureToggles';
+import styles from './Schema.scss';
+
+export type SchemaDialogProps = {
+ visible: boolean;
+ onClose: any;
+ schema: string;
+ theme: string;
+ i18n: any;
+};
+
+function a11yProps(index: number) {
+ return {
+ id: `vertical-tab-${index}`,
+ 'aria-controls': `vertical-tabpanel-${index}`
+ };
+}
+
+interface TabPanelProps {
+ children?: React.ReactNode;
+ index: any;
+ value: any;
+}
+
+function TabPanel(props: TabPanelProps) {
+ const { children, value, index, ...other } = props;
+
+ return (
+
+ {value === index && children}
+
+ );
+}
+
+const Root = styled('div')(({ theme }) => ({
+ flexGrow: 1,
+ backgroundColor: theme.palette.background.paper,
+ display: 'flex',
+ height: 224
+}));
+
+const TabPanelContent = styled('div')(() => ({
+ padding: '5px 15px 0'
+}));
+
+const SchemaDialog = ({ visible, onClose, schema, theme, i18n }: SchemaDialogProps) => {
+ // const classes = useStyles();
+ const [code, setCode] = React.useState(schema);
+ const [value, setValue] = React.useState(0);
+
+ if (!FeatureToggles.DATA_TEMPLATE_GENERATION_UI) {
+ return null;
+ }
+
+ const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
+ setValue(newValue);
+ };
+
+ return (
+
+
+
Data Template
+
+
+
+
+
+
+
+
+
+ The data you create here in the UI can be downloaded as a template for use with the generatedata CLI, letting you generate
+ data
+
+
+
+
+
+ Javascript Typescript JSON template only
+ setCode(value)}
+ options={{
+ mode: 'application/ld+json',
+ theme,
+ lineNumbers: false,
+ lineWrapping: false,
+ readOnly: true
+ }}
+ />
+
+
+
+
+ Import.
+
+
+
+
+ {i18n.close}
+
+
+
+ );
+};
+
+export default SchemaDialog;
diff --git a/apps/client/src/core/dialogs/schema/Schema.container.tsx b/apps/client/src/core/dialogs/schema/Schema.container.tsx
new file mode 100644
index 000000000..e39270674
--- /dev/null
+++ b/apps/client/src/core/dialogs/schema/Schema.container.tsx
@@ -0,0 +1,20 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import SchemaDialog, { SchemaDialogProps } from './Schema.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+
+const mapStateToProps = (state: any): Partial => ({
+ visible: selectors.isSchemaDialogVisible(state),
+ i18n: selectors.getCoreI18n(state),
+ schema: selectors.getGenerationSchema(state),
+ theme: selectors.getTheme(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): SchemaDialogProps['onClose'] => ({
+ onClose: (): any => dispatch(actions.hideSchemaDialog())
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(SchemaDialog);
+
+export default container;
diff --git a/apps/client/src/core/dialogs/schema/Schema.scss b/apps/client/src/core/dialogs/schema/Schema.scss
new file mode 100644
index 000000000..70d97af3f
--- /dev/null
+++ b/apps/client/src/core/dialogs/schema/Schema.scss
@@ -0,0 +1,48 @@
+@use '../../../styles/variables' as c;
+
+.dataTemplateDialog {
+ height: 100%;
+
+ :global(.MuiDialog-paper) {
+ height: 100%;
+ }
+
+ .actions {
+ button {
+ svg {
+ fill: c.$primary-color;
+ height: 18px;
+ width: 18px;
+ margin-right: 6px;
+ margin-left: -4px;
+ }
+ }
+ }
+
+ h4 {
+ margin-top: 0;
+ }
+
+ :global(.MuiDialogContent-root) {
+ font-size: 13px;
+ line-height: 20px;
+ }
+
+ .content {
+ height: 450px;
+ & > * {
+ height: 100%;
+ & > * {
+ height: 100%;
+ }
+ }
+ }
+}
+
+.dataTemplateDialogInner {
+ width: 800px;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+}
diff --git a/apps/client/src/core/dialogs/schema/Schema.scss.d.ts b/apps/client/src/core/dialogs/schema/Schema.scss.d.ts
new file mode 100644
index 000000000..cd790e48e
--- /dev/null
+++ b/apps/client/src/core/dialogs/schema/Schema.scss.d.ts
@@ -0,0 +1,15 @@
+declare namespace SchemaScssNamespace {
+ export interface ISchemaScss {
+ actions: string;
+ content: string;
+ dataTemplateDialog: string;
+ dataTemplateDialogInner: string;
+ }
+}
+
+declare const SchemaScssModule: SchemaScssNamespace.ISchemaScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: SchemaScssNamespace.ISchemaScss;
+};
+
+export = SchemaScssModule;
diff --git a/apps/client/src/core/dialogs/tourIntro/TourIntro.component.tsx b/apps/client/src/core/dialogs/tourIntro/TourIntro.component.tsx
new file mode 100644
index 000000000..c9d91a85f
--- /dev/null
+++ b/apps/client/src/core/dialogs/tourIntro/TourIntro.component.tsx
@@ -0,0 +1,162 @@
+import React, { useEffect, useState } from 'react';
+import { PrimaryButton } from '~components/Buttons.component';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import { DialogLoadingSpinner } from '~components/loaders/loaders';
+import { getTourComponents } from '@generatedata/utils/general';
+import styles from './TourIntro.scss';
+import { useWindowSize } from 'react-hooks-window-size';
+
+// TODO enum
+type Tour = 'intro' | 'gridPanel' | 'previewPanel' | 'yourAccount';
+
+export type TourDialogProps = {
+ tourIntroDialogVisible: boolean;
+ showTourIntroDialog: () => void;
+ onClose: () => void;
+ tourBundleLoaded: boolean;
+ loadTourBundle: () => void;
+ saveGeneratorState: () => void;
+ restoreGeneratorState: () => void;
+ i18n: any;
+};
+
+const TourDialog = ({
+ tourIntroDialogVisible,
+ showTourIntroDialog,
+ onClose,
+ tourBundleLoaded,
+ loadTourBundle,
+ restoreGeneratorState,
+ saveGeneratorState,
+ i18n
+}: TourDialogProps) => {
+ const windowSize = useWindowSize();
+
+ const [loadingBundle, setLoadingBundle] = useState(false);
+ const [currentTour, setCurrentTour] = useState(null);
+
+ useEffect(() => {
+ if (tourBundleLoaded && currentTour !== null) {
+ onClose();
+ }
+ }, [tourBundleLoaded, currentTour]);
+
+ useEffect(() => {
+ if (tourIntroDialogVisible) {
+ setLoadingBundle(false);
+
+ // unreadable logic, but this fires when the tour dialog was just hidden and there's a tour slated to be shown.
+ // It saves the current generator state before the tour messes around with it
+ } else if (currentTour !== null) {
+ saveGeneratorState();
+ }
+ }, [tourIntroDialogVisible]);
+
+ // hide the tour if the screen gets too small
+ useEffect(() => {
+ if (windowSize.width < 900) {
+ if (tourIntroDialogVisible || currentTour !== null) {
+ closeIntroDialog();
+ }
+ }
+ }, [windowSize.width]);
+
+ const closeIntroDialog = (): void => {
+ setCurrentTour(null);
+ onClose();
+ };
+
+ const selectTour = (tour: Tour): void => {
+ // load the tour bundle and show a loading spinner until it's ready. At that point, we backup the current
+ // generator settings, close this intro dialog and open the selected tour
+ setLoadingBundle(true);
+ setCurrentTour(tour);
+
+ if (!tourBundleLoaded) {
+ loadTourBundle();
+ } else {
+ onClose();
+ }
+ };
+
+ // when a user exits a tour they're taken back to the intro panel again
+ const onExit = (completelyExit: boolean): void => {
+ restoreGeneratorState();
+ setCurrentTour(null);
+
+ if (completelyExit !== true) {
+ showTourIntroDialog();
+ }
+ };
+
+ const getCurrentTour = () => {
+ if (tourIntroDialogVisible || !currentTour || !tourBundleLoaded) {
+ return null;
+ }
+
+ const tours = getTourComponents();
+ const Tour = tours[currentTour].component;
+
+ return (
+
+ );
+ };
+
+ return (
+ <>
+
+
+
{i18n.help}
+
+
+
+
{i18n.welcomeToTheGenerator}
+
+
{i18n.tourIntroPara1}
+
+
{i18n.tourIntroPara2}
+
+
+
+
+
+
+
selectTour('intro')}>1. {i18n.introToGenerator}
+
+
+
selectTour('gridPanel')}>2. {i18n.theGridPanel}
+
+
+
selectTour('previewPanel')}>3. {i18n.thePreviewPanel}
+
+
+
selectTour('yourAccount')}>
+ 4. {i18n.yourUserAccount}
+
+
+
+
+
+
+
+ {i18n.close}
+
+
+
+
+
+ {getCurrentTour()}
+ >
+ );
+};
+
+export default TourDialog;
diff --git a/apps/client/src/core/dialogs/tourIntro/TourIntro.container.ts b/apps/client/src/core/dialogs/tourIntro/TourIntro.container.ts
new file mode 100644
index 000000000..227af2681
--- /dev/null
+++ b/apps/client/src/core/dialogs/tourIntro/TourIntro.container.ts
@@ -0,0 +1,29 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import TourIntroDialog, { TourDialogProps } from './TourIntro.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+import * as mainSelectors from '~store/main/main.selectors';
+import * as mainActions from '~store/main/main.actions';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ tourIntroDialogVisible: mainSelectors.tourIntroDialogVisible(state),
+ tourBundleLoaded: mainSelectors.isTourBundleLoaded(state)
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ loadTourBundle: (): any => dispatch(mainActions.loadTourBundle()),
+
+ // @ts-ignore-line
+ onClose: (): any => dispatch(mainActions.hideTourIntroDialog()),
+
+ // @ts-ignore-line
+ showTourIntroDialog: (): any => dispatch(mainActions.showTourIntroDialog()),
+ saveGeneratorState: (): any => dispatch(actions.stashGeneratorState()),
+ restoreGeneratorState: (): any => dispatch(actions.popStashedState())
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(TourIntroDialog);
diff --git a/apps/client/src/core/dialogs/tourIntro/TourIntro.scss b/apps/client/src/core/dialogs/tourIntro/TourIntro.scss
new file mode 100644
index 000000000..58d34b675
--- /dev/null
+++ b/apps/client/src/core/dialogs/tourIntro/TourIntro.scss
@@ -0,0 +1,62 @@
+@use '../../../styles/variables.scss' as c;
+
+.cols {
+ display: flex;
+ flex-direction: row;
+}
+
+.col {
+ flex: 1;
+
+ h3 {
+ margin-top: 0;
+ }
+
+ :global(.MuiButtonBase-root) {
+ margin-bottom: 10px;
+
+ svg {
+ margin-left: -4px;
+ margin-right: 6px;
+ }
+ }
+}
+
+.separator {
+ margin: 0 25px;
+ position: relative;
+ display: flex;
+ align-items: center;
+ border-left: 1px solid #efefef;
+}
+
+svg.tourMask {
+ opacity: 0.7;
+ color: #b2bed3;
+}
+
+.introDialog,
+.tourPage {
+ font-size: 13px;
+
+ p {
+ line-height: 20px;
+ }
+
+ button {
+ font-size: 12px;
+ }
+}
+
+.buttonCol {
+ font-size: 12px;
+
+ b {
+ color: c.$primary-color;
+ }
+}
+
+div[data-tour-elem='controls'] {
+ display: flex;
+ justify-content: center;
+}
diff --git a/apps/client/src/core/dialogs/tourIntro/TourIntro.scss.d.ts b/apps/client/src/core/dialogs/tourIntro/TourIntro.scss.d.ts
new file mode 100644
index 000000000..3aaf3dbb9
--- /dev/null
+++ b/apps/client/src/core/dialogs/tourIntro/TourIntro.scss.d.ts
@@ -0,0 +1,18 @@
+declare namespace TourIntroScssNamespace {
+ export interface ITourIntroScss {
+ buttonCol: string;
+ col: string;
+ cols: string;
+ introDialog: string;
+ separator: string;
+ tourMask: string;
+ tourPage: string;
+ }
+}
+
+declare const TourIntroScssModule: TourIntroScssNamespace.ITourIntroScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: TourIntroScssNamespace.ITourIntroScss;
+};
+
+export = TourIntroScssModule;
diff --git a/apps/client/src/core/featureToggles.ts b/apps/client/src/core/featureToggles.ts
new file mode 100644
index 000000000..9f8db8002
--- /dev/null
+++ b/apps/client/src/core/featureToggles.ts
@@ -0,0 +1,10 @@
+const FeatureToggles = {
+ /**
+ * Ultimately we're going to be showing an option (somewhere...) to let the user download a JSON representation
+ * of what they're currently building: Data Set rows, Export Type choice abd settings. This will be used for the
+ * command-line npm package version of the script being worked on for 4.1.
+ */
+ DATA_TEMPLATE_GENERATION_UI: true
+};
+
+export default FeatureToggles;
diff --git a/apps/client/src/core/footer/Footer.component.tsx b/apps/client/src/core/footer/Footer.component.tsx
new file mode 100644
index 000000000..353d0e5c4
--- /dev/null
+++ b/apps/client/src/core/footer/Footer.component.tsx
@@ -0,0 +1,196 @@
+import React, { useState } from 'react';
+import Button from '@mui/material/Button';
+import ButtonGroup from '@mui/material/ButtonGroup';
+import Grow from '@mui/material/Grow';
+import Popper from '@mui/material/Popper';
+import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
+import SaveIcon from '@mui/icons-material/Save';
+import GearIcon from '@mui/icons-material/Settings';
+import Person from '@mui/icons-material/EmojiPeople';
+import { Tooltip } from '~components/tooltips';
+import { Github } from '~components/icons';
+import ActivePacketsList from '../generationPanel/ActivePacketsList.container';
+import PanelControls from '../generator/panelControls/PanelControls.container';
+import AboutDialog from '~core/dialogs/about/About.component';
+import useOnClickOutside from 'use-onclickoutside';
+import styles from './Footer.scss';
+import { useWindowSize } from 'react-hooks-window-size';
+import C from '@generatedata/config/constants';
+import { isGeneratorPage } from '~utils/routeUtils';
+import { GDLocale } from '~types/general';
+import { useNavigate } from 'react-router';
+
+export type FooterProps = {
+ i18n: any;
+ locale: GDLocale;
+ scriptVersion: string;
+ onGenerate: () => void;
+ onSave: () => void;
+ onSaveNewDataSet: () => void;
+ onSaveAs: () => void;
+ actionButtonsEnabled: boolean;
+ currentPage: string; // isGeneratorPage?
+ currentDataSetId: number | null;
+ showTourDialog: (navigate: any) => void;
+ customFooterLinks: JSX.Element[];
+};
+
+const Footer = ({
+ i18n,
+ locale,
+ actionButtonsEnabled,
+ scriptVersion,
+ onSave,
+ onGenerate,
+ currentPage,
+ currentDataSetId,
+ onSaveNewDataSet,
+ onSaveAs,
+ showTourDialog,
+ customFooterLinks
+}: FooterProps) => {
+ const navigate = useNavigate();
+ const saveAsButtonRef = React.useRef(null);
+ const anchorRef = React.useRef(null);
+ const [saveAsMenuOpen, setSaveAsMenuOpen] = useState(false);
+ const [showAboutDialog, setAboutDialogVisibility] = useState(false);
+
+ const windowSize = useWindowSize();
+
+ useOnClickOutside(saveAsButtonRef, () => {
+ setSaveAsMenuOpen(false);
+ });
+
+ // we always show the login button. It'll show a "you must login in" dialog if they're not logged in/registered
+ const getSaveButton = () => {
+ // if the data set has already been saved, we give them a split button: the main button immediately saves,
+ // the arrow gives them the option to create a new data set via the "Save as" option
+ if (currentDataSetId) {
+ return (
+
+
+
+
+ {i18n.save}
+
+ setSaveAsMenuOpen(!saveAsMenuOpen)}
+ >
+
+
+
+
+
{
+ e.preventDefault();
+ e.stopPropagation();
+ onSaveAs();
+ }}
+ >
+ {({ TransitionProps }): any => (
+
+ {i18n.saveAs}
+
+ )}
+
+
+ );
+ }
+
+ return (
+
+
+ {i18n.save}
+
+ );
+ };
+
+ let footerControlsClasses = styles.footerControls;
+ if (isGeneratorPage(currentPage, locale)) {
+ footerControlsClasses += ` ${styles.visible}`;
+ }
+
+ let panelControls;
+ if (windowSize.width > C.SMALL_SCREEN_WIDTH) {
+ panelControls = ;
+ }
+
+ return (
+ <>
+
+ setAboutDialogVisibility(false)}
+ scriptVersion={scriptVersion}
+ i18n={i18n}
+ />
+ >
+ );
+};
+
+export default Footer;
diff --git a/apps/client/src/core/footer/Footer.container.tsx b/apps/client/src/core/footer/Footer.container.tsx
new file mode 100644
index 000000000..d30be8d8a
--- /dev/null
+++ b/apps/client/src/core/footer/Footer.container.tsx
@@ -0,0 +1,49 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import Footer, { FooterProps } from './Footer.component';
+import { GDLocale } from '~types/general';
+import * as selectors from '../store/generator/generator.selectors';
+import * as actions from '../store/generator/generator.actions';
+import * as mainSelectors from '../store/main/main.selectors';
+import * as mainActions from '../store/main/main.actions';
+import * as accountActions from '../store/account/account.actions';
+import * as coreUtils from '../../utils/coreUtils';
+import { isExportTypeValid } from '~utils/exportTypes';
+import { getCustomFooterLinks } from '@generatedata/utils/extension';
+import { SaveDataDialogType } from '~store/account/account.reducer';
+
+const mapStateToProps = (
+ state: any
+): Pick<
+ FooterProps,
+ 'i18n' | 'locale' | 'scriptVersion' | 'actionButtonsEnabled' | 'currentPage' | 'currentDataSetId' | 'customFooterLinks'
+> => {
+ const exportType = selectors.getExportType(state);
+ const exportTypeSettings = selectors.getCurrentExportTypeSettings(state);
+
+ return {
+ i18n: selectors.getCoreI18n(state),
+ locale: mainSelectors.getLocale(state),
+ scriptVersion: coreUtils.getScriptVersion(),
+ actionButtonsEnabled: selectors.hasData(state) && isExportTypeValid(exportType, exportTypeSettings),
+ currentPage: mainSelectors.getCurrentPage(state),
+ currentDataSetId: selectors.getCurrentDataSetId(state),
+ customFooterLinks: getCustomFooterLinks()
+ };
+};
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ // @ts-ignore-line
+ onChangeLocale: (locale: GDLocale): any => dispatch(mainActions.selectLocale(locale)),
+ onSave: (): any => dispatch(accountActions.saveCurrentDataSet()),
+ onSaveNewDataSet: (): any => dispatch(accountActions.showSaveDataSetDialog(SaveDataDialogType.save)),
+ onSaveAs: (): any => dispatch(accountActions.showSaveDataSetDialog(SaveDataDialogType.saveAs)),
+ onGenerate: (): any => dispatch(actions.showGenerationSettingsPanel()),
+
+ // @ts-ignore-line
+ showTourDialog: (navigate: any): any => dispatch(mainActions.showTourIntroDialog(navigate))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Footer);
diff --git a/apps/client/src/core/footer/Footer.scss b/apps/client/src/core/footer/Footer.scss
new file mode 100644
index 000000000..a8e39fa1d
--- /dev/null
+++ b/apps/client/src/core/footer/Footer.scss
@@ -0,0 +1,183 @@
+@use '../../styles/variables' as c;
+
+.footer {
+ flex: 0 0 60px;
+ background-image: url('./images/bg.png');
+ padding: 16px 20px;
+
+ & > div {
+ display: flex;
+ flex: 0 0 auto;
+ margin: 0 auto;
+ align-content: space-between;
+ max-width: 1400px;
+ height: 32px;
+
+ & > ul {
+ flex: 0 0 auto;
+ display: flex;
+ flex-direction: row;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ align-items: center;
+
+ li {
+ display: flex;
+ margin-right: 10px;
+
+ &:hover {
+ svg {
+ fill: c.$primary-color;
+ }
+ }
+ }
+
+ svg {
+ cursor: pointer;
+ transition: fill 0.15s ease-in-out;
+ }
+ }
+ }
+
+ a:hover {
+ border-bottom: 0;
+ }
+
+ svg {
+ -webkit-mask-image: -webkit-gradient(linear, left top, left bottom, from(black), to(rgba(0, 0, 0, 0.6)));
+ }
+
+ button.generateButton,
+ button.saveButton,
+ button.saveButtonAsMainBtn {
+ font-size: 13px;
+
+ svg {
+ font-size: 15px;
+ margin: 0 5px 0 -5px;
+ }
+
+ &:global(.Mui-disabled) {
+ border-color: #bbbbbb;
+ }
+ }
+
+ .aboutIconEl {
+ height: 24px;
+ }
+}
+
+.saveButtonAs {
+ button.saveButtonAsMainBtn {
+ border-color: #bde9fb;
+ }
+}
+
+.scriptVersion a {
+ margin-right: 30px;
+ color: #333333;
+
+ &:hover {
+ text-decoration: none;
+ }
+}
+
+div.controls {
+ margin-right: 15px;
+}
+
+button.saveButton {
+ background-color: c.$primary-submit;
+ color: white;
+ margin-right: 15px;
+
+ &:hover {
+ background-color: c.$primary-submit-hover;
+ }
+}
+
+div.saveButtonAs {
+ height: 32px;
+ margin-right: 15px;
+ button {
+ background-color: c.$primary-submit;
+ color: white;
+
+ &:hover {
+ background-color: c.$primary-submit-hover;
+ }
+ }
+}
+
+button.saveBtnArrow {
+ min-width: inherit;
+ padding: 0 6px;
+
+ svg {
+ margin: 0;
+ }
+}
+
+.saveAsRow {
+ cursor: pointer;
+ background-color: c.$primary-submit;
+ margin-bottom: 2px;
+ border-radius: 2px;
+ color: white;
+ text-transform: uppercase;
+ font-size: 11px;
+ padding: 6px 12px;
+ z-index: 2;
+
+ &:hover {
+ background-color: c.$primary-submit-hover;
+ }
+}
+
+.tourBtn:global(.MuiButtonBase-root) {
+ text-transform: none;
+ transition: color 0.15s ease-in-out;
+ margin-left: -6px;
+
+ svg {
+ margin-right: 4px;
+ }
+ &:hover {
+ color: c.$primary-color;
+ }
+}
+
+@media (max-width: 900px) {
+ button.tourBtn {
+ display: none;
+ }
+}
+
+.showTourLink {
+ flex: 0 0 auto;
+}
+
+.activePacketsList {
+ flex: 1 1 auto;
+ overflow: scroll;
+}
+
+.footerControls {
+ display: flex;
+ align-items: stretch;
+ flex: 0 0 auto;
+ padding-right: 18px;
+ opacity: 0;
+ transition: opacity 200ms ease-in-out;
+
+ &.visible {
+ opacity: 1;
+ }
+}
+
+@media (max-width: 600px) {
+ .footerControls {
+ padding-right: 0;
+ }
+}
diff --git a/apps/client/src/core/footer/Footer.scss.d.ts b/apps/client/src/core/footer/Footer.scss.d.ts
new file mode 100644
index 000000000..76e799273
--- /dev/null
+++ b/apps/client/src/core/footer/Footer.scss.d.ts
@@ -0,0 +1,26 @@
+declare namespace FooterScssNamespace {
+ export interface IFooterScss {
+ aboutIconEl: string;
+ activePacketsList: string;
+ controls: string;
+ footer: string;
+ footerControls: string;
+ generateButton: string;
+ saveAsRow: string;
+ saveBtnArrow: string;
+ saveButton: string;
+ saveButtonAs: string;
+ saveButtonAsMainBtn: string;
+ scriptVersion: string;
+ showTourLink: string;
+ tourBtn: string;
+ visible: string;
+ }
+}
+
+declare const FooterScssModule: FooterScssNamespace.IFooterScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: FooterScssNamespace.IFooterScss;
+};
+
+export = FooterScssModule;
diff --git a/apps/client/src/core/footer/__tests__/Footer.container.test.tsx b/apps/client/src/core/footer/__tests__/Footer.container.test.tsx
new file mode 100644
index 000000000..ff30a8bf3
--- /dev/null
+++ b/apps/client/src/core/footer/__tests__/Footer.container.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import Footer from '../Footer.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('Footer', () => {
+ // also need to finish deciding exactly what the footer will contain before adding these tests
+
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter();
+
+ expect(baseElement.querySelector('footer')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generationPanel/ActivePacketsList.component.tsx b/apps/client/src/core/generationPanel/ActivePacketsList.component.tsx
new file mode 100644
index 000000000..b6e331397
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivePacketsList.component.tsx
@@ -0,0 +1,82 @@
+import * as React from 'react';
+import { useNavigate } from 'react-router';
+import CheckCircle from '@mui/icons-material/CheckCircle';
+import Chip from '@mui/material/Chip';
+import styles from './ActivityPacketsList.scss';
+import { Cell, Pie, PieChart } from 'recharts';
+import { Tooltip } from '~components/tooltips';
+import { getPercentageLabel } from './generation.helpers';
+
+export type ActivePacketList = {
+ packetId: string;
+ label: string;
+ percentage: number;
+ isPaused: boolean;
+ numRowsToGenerate: number;
+};
+
+export type ActivePacketsListProps = {
+ packetList: ActivePacketList[];
+ openPacket: (packetId: string, navigate: any) => void;
+};
+
+const ActivePacketsList = ({ packetList, openPacket }: ActivePacketsListProps) => {
+ const navigate = useNavigate();
+
+ const chips = packetList.map(({ packetId, label, percentage, numRowsToGenerate, isPaused }, index) => {
+ const color = isPaused ? 'default' : 'primary';
+ const pieChartData = [
+ { value: percentage, color: '#003300' },
+ { value: 100 - percentage, color: '#dddddd' }
+ ];
+
+ // expand with expected time
+ const tooltipContent = `${getPercentageLabel(percentage, numRowsToGenerate)}% complete`;
+
+ let icon = ;
+ if (percentage < 100) {
+ icon = (
+
+
+ {pieChartData.map((entry, index) => (
+ |
+ ))}
+
+
+ );
+ }
+
+ return (
+ } arrow>
+
+ {label}
+ {icon}
+
+ }
+ className={styles.chip}
+ clickable
+ color={color}
+ onClick={(): void => openPacket(packetId, navigate)}
+ variant="outlined"
+ style={{ marginLeft: 10 }}
+ />
+
+ );
+ });
+
+ return {chips}
;
+};
+
+export default ActivePacketsList;
diff --git a/apps/client/src/core/generationPanel/ActivePacketsList.container.tsx b/apps/client/src/core/generationPanel/ActivePacketsList.container.tsx
new file mode 100644
index 000000000..01c24062a
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivePacketsList.container.tsx
@@ -0,0 +1,16 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as actions from '../store/packets/packets.actions';
+import * as selectors from '../store/packets/packets.selectors';
+import ActivePacketsList, { ActivePacketsListProps } from './ActivePacketsList.component';
+
+const mapStateToProps = (state: any): Pick => ({
+ packetList: selectors.getActivePacketList(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ // @ts-ignore
+ openPacket: (packetId: string, navigate: any): any => dispatch(actions.showActivityPanel(packetId, navigate))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ActivePacketsList);
diff --git a/apps/client/src/core/generationPanel/ActivityPacketsList.scss b/apps/client/src/core/generationPanel/ActivityPacketsList.scss
new file mode 100644
index 000000000..885038a5e
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivityPacketsList.scss
@@ -0,0 +1,17 @@
+.chipLabel {
+ display: flex;
+ align-items: center;
+ font-size: 12px;
+
+ & > span {
+ margin-right: 5px;
+ }
+}
+
+.chip {
+ padding-right: 0;
+}
+
+.root {
+ white-space: nowrap;
+}
diff --git a/apps/client/src/core/generationPanel/ActivityPacketsList.scss.d.ts b/apps/client/src/core/generationPanel/ActivityPacketsList.scss.d.ts
new file mode 100644
index 000000000..30747a327
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivityPacketsList.scss.d.ts
@@ -0,0 +1,14 @@
+declare namespace ActivityPacketsListScssNamespace {
+ export interface IActivityPacketsListScss {
+ chip: string;
+ chipLabel: string;
+ root: string;
+ }
+}
+
+declare const ActivityPacketsListScssModule: ActivityPacketsListScssNamespace.IActivityPacketsListScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ActivityPacketsListScssNamespace.IActivityPacketsListScss;
+};
+
+export = ActivityPacketsListScssModule;
diff --git a/apps/client/src/core/generationPanel/ActivityPanel.component.tsx b/apps/client/src/core/generationPanel/ActivityPanel.component.tsx
new file mode 100644
index 000000000..eb7b5c609
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivityPanel.component.tsx
@@ -0,0 +1,258 @@
+import React from 'react';
+import { PieChart, Pie, Cell, BarChart, CartesianGrid, XAxis, YAxis, Bar, Label } from 'recharts';
+import CountUp from 'react-countup';
+import IconButton from '@mui/material/IconButton';
+import Slider from '@mui/material/Slider';
+import Button from '@mui/material/Button';
+import Pause from '@mui/icons-material/Pause';
+import PlayArrow from '@mui/icons-material/PlayArrow';
+import ExpandMore from '@mui/icons-material/ExpandMore';
+import { Dialog, DialogContent, DialogTitle, DialogActions } from '~components/dialogs';
+import usePrevious from '../../hooks/usePrevious';
+import styles from './ActivityPanel.scss';
+import { DataPacket } from '~store/packets/packets.reducer';
+import * as coreUtils from '../../utils/coreUtils';
+import { Tooltip } from '~components/tooltips';
+import { getPercentageLabel } from './generation.helpers';
+import Engine from './Engine.container';
+import { LoadTimeGraphDuration } from '~types/general';
+import { GenerationWorkerActionType } from '~core/generator/generation.types';
+import { useMeasure } from '@uidotdev/usehooks';
+
+export type ActivityPanelProps = {
+ visible: boolean;
+ fullI18n: any;
+ packet: DataPacket | null;
+ onClose: () => void;
+ onPause: () => void;
+ onContinue: () => void;
+ onAbort: () => void;
+ onDownload: () => void;
+ onChangeSpeed: (speed: number) => void;
+ batchLoadTimes: object[];
+ dataSize: string;
+ estimatedSize: string;
+ estimatedTime: string;
+ estimatedTimeRemaining: string;
+ loadTimeGraphDuration: LoadTimeGraphDuration;
+ countUpSpeed: number;
+};
+
+const valueLabelFormat = (value: number): string => `${value}%`;
+
+const ActivityPanel = ({
+ visible,
+ onClose,
+ packet,
+ onContinue,
+ onPause,
+ batchLoadTimes,
+ onAbort,
+ onDownload,
+ onChangeSpeed,
+ dataSize,
+ estimatedSize,
+ estimatedTime,
+ countUpSpeed,
+ estimatedTimeRemaining,
+ fullI18n
+}: ActivityPanelProps): any => {
+ const [measureRef, { width = 0, height = 0 }] = useMeasure(); // TODO check defaults are ok
+
+ if (packet === null || fullI18n === null) {
+ return null;
+ }
+
+ const coreI18n = fullI18n.core;
+ const { isPaused, config, generationWorkerId, numGeneratedRows, speed } = packet;
+ const { numRowsToGenerate } = config;
+
+ const prevGeneratedRows = usePrevious(numGeneratedRows);
+ const generationWorker = coreUtils.getGenerationWorker(generationWorkerId);
+
+ const abortPacket = (): void => {
+ onAbort();
+ generationWorker.postMessage({ action: GenerationWorkerActionType.Abort });
+ coreUtils.destroyGenerationWorker(generationWorkerId);
+ };
+
+ const percentage = (numGeneratedRows / numRowsToGenerate) * 100;
+ const isComplete = percentage === 100;
+
+ const pieChartData = [
+ { name: coreI18n.complete, value: percentage, color: '#275eb5' },
+ { name: coreI18n.incomplete, value: 100 - percentage, color: '#efefef' }
+ ];
+
+ let pauseContinueIcon: any;
+ let pauseContinueIconAction: any;
+ if (isPaused) {
+ pauseContinueIcon = ;
+ pauseContinueIconAction = onContinue;
+ } else {
+ pauseContinueIcon = ;
+ pauseContinueIconAction = onPause;
+ }
+
+ const marks = [
+ { value: 0, label: coreI18n.seriouslySlow },
+ { value: 100, label: coreI18n.cpuMeltinglyFast }
+ ];
+
+ const getActionButtons = () => {
+ if (isComplete) {
+ return (
+
+
+ {coreI18n.clear}
+
+
+ {coreI18n.download}
+
+
+ );
+ }
+
+ return (
+
+
+ {coreI18n.hide}
+
+
+ {coreI18n.abort}
+
+
+ );
+ };
+
+ const getGenerationControls = (): React.ReactNode => {
+ // TODO apply class to fade out instead
+ if (isComplete) {
+ return null;
+ }
+
+ const tooltip = isPaused ? coreI18n.continue : coreI18n.pause;
+
+ return (
+
+
+
+
+ {pauseContinueIcon}
+
+
+
+ onChangeSpeed(value as number)}
+ />
+
+ );
+ };
+
+ const panel1Width = ((width || 0) / 100) * 20;
+ const pieSize = Math.floor(panel1Width * 0.9);
+ const countUpDuration = countUpSpeed;
+
+ return (
+ <>
+
+
+
+ {coreI18n.generatedC}
+
+ {coreI18n.rows}
+
+
+
+
+
+
+
{getPercentageLabel(percentage, numRowsToGenerate)}%
+
+
+ {pieChartData.map((entry, index) => (
+ |
+ ))}
+
+
+
+
+
+
+
{coreI18n.estimatedTime}
+
{estimatedTime}
+
+
+
{coreI18n.remainingTime}
+
{estimatedTimeRemaining}
+
+
+
{coreI18n.estimatedSize}
+
{estimatedSize}
+
+
+
{coreI18n.size}
+
{dataSize}
+
+
+
+
+
+
{coreI18n.rowsGeneratedPerSecond}
+
+ {width && height && (
+
+
+
+
+
+
+
+
+ )}
+
+
+
+
+
+ {getGenerationControls()}
+ {getActionButtons()}
+
+
+
+ {visible && }
+ >
+ );
+};
+
+export default ActivityPanel;
diff --git a/apps/client/src/core/generationPanel/ActivityPanel.container.tsx b/apps/client/src/core/generationPanel/ActivityPanel.container.tsx
new file mode 100644
index 000000000..821ff8574
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivityPanel.container.tsx
@@ -0,0 +1,53 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as packetActions from '../store/packets/packets.actions';
+import * as selectors from '../store/generator/generator.selectors';
+import * as packetSelectors from '../store/packets/packets.selectors';
+import ActivityPanel, { ActivityPanelProps } from './ActivityPanel.component';
+import { GDAction, GenerationActivityPanel } from '~types/general';
+import { getGenerationActivityPanel } from '~core/generationPanel/generation.helpers';
+
+const mapStateToProps = (state: any): Partial & { packetId: string | null } => {
+ const packet = packetSelectors.getCurrentPacket(state);
+ const largePacketSize =
+ !!packet && getGenerationActivityPanel(packet.config.numRowsToGenerate) === GenerationActivityPanel.large;
+
+ const props: Partial & { packetId: string | null } = {
+ visible: largePacketSize && packetSelectors.isGenerating(state),
+ fullI18n: selectors.getI18n(state),
+ packet,
+ packetId: packetSelectors.getCurrentPacketId(state),
+ batchLoadTimes: packetSelectors.getBatchLoadTimes(state),
+ dataSize: packetSelectors.getGeneratedDataSizeLabel(state),
+ estimatedSize: packetSelectors.getEstimatedDataSize(state),
+ estimatedTime: packetSelectors.getEstimatedTimeDisplay(state),
+ estimatedTimeRemaining: packetSelectors.getEstimatedTimeRemaining(state),
+ countUpSpeed: packetSelectors.getLastBatchGenerationDuration(state)
+ };
+
+ if (packet !== null) {
+ props.loadTimeGraphDuration = packetSelectors.getLoadTimeDuration(state);
+ }
+
+ return props;
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): any => ({ dispatch });
+
+const mergeProps = ({ packetId, ...stateProps }: any, { dispatch }: any): ActivityPanelProps => {
+ if (stateProps.packet === null) {
+ return stateProps;
+ }
+
+ return {
+ ...stateProps,
+ onClose: (): void => dispatch(packetActions.hideActivityPanel()),
+ onPause: (): GDAction => dispatch(packetActions.pauseGeneration(packetId)),
+ onContinue: (): GDAction => dispatch(packetActions.continueGeneration(packetId)),
+ onAbort: (): GDAction => dispatch(packetActions.abortGeneration(packetId)),
+ onDownload: (): any => dispatch(packetActions.promptToDownload()),
+ onChangeSpeed: (speed: number): any => dispatch(packetActions.changeSpeed(speed))
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ActivityPanel);
diff --git a/apps/client/src/core/generationPanel/ActivityPanel.scss b/apps/client/src/core/generationPanel/ActivityPanel.scss
new file mode 100644
index 000000000..8e3596f01
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivityPanel.scss
@@ -0,0 +1,158 @@
+.row {
+ display: flex;
+ align-items: center;
+}
+
+.generationRow {
+ font-size: 14px;
+ margin-bottom: 10px;
+
+ input {
+ width: 100px;
+ margin: 0 6px;
+ font-size: 16px;
+ color: #666666;
+ }
+}
+
+.panel1 {
+ flex: 1;
+ position: relative;
+ padding: 10px;
+}
+
+.panel2 {
+ flex: 1;
+}
+
+.generateOverlay {
+ position: absolute;
+ z-index: 1;
+ padding: 30px;
+ background-color: white;
+ border-radius: 10px;
+ top: 50%;
+ margin-right: -50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.overlayWrapper {
+ position: relative;
+ height: 100%;
+}
+
+.background {
+ position: absolute;
+ background-color: #efefef;
+ opacity: 0.7;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+}
+
+.fadeOut {
+ opacity: 0;
+ transition: opacity 0.25s ease-in-out;
+}
+
+.counter {
+ color: #275eb5;
+ margin: 0 4px;
+}
+
+.actionsRow {
+ height: 72px;
+}
+
+.activityPanel :global(.MuiDialog-paper) {
+ width: 100%;
+ max-width: 1000px;
+ height: 100%;
+ max-height: 600px;
+}
+
+.pie {
+ margin-bottom: 20px;
+ position: relative;
+
+ h3 {
+ position: absolute;
+ color: #666660;
+ top: 50%;
+ left: 50%;
+ font-size: 18px;
+ margin-right: -50%;
+ transform: translate(-50%, -50%);
+ z-index: 1;
+ }
+}
+
+.dataPanel {
+ padding: 0 5%;
+}
+
+.dataRow {
+ display: flex;
+ flex-direction: row;
+ line-height: 42px;
+ width: 100%;
+}
+
+.dataRowLabel {
+ flex: 1 0 60%;
+}
+
+.dataRowValue {
+ flex: 1 0 40%;
+ font-size: 16px;
+}
+
+.generationOverlayBg {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ background-color: white;
+ opacity: 0.9;
+ width: 100%;
+ margin: 0 -20px;
+}
+
+.generationOverlay {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+ margin: 0 -20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+.generationSettingsContent {
+ position: relative;
+}
+
+.generationLabel {
+ font-size: 14px;
+}
+
+.generationComplete {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ width: 100%;
+ margin: 0 -20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ font-size: 18px;
+
+ svg {
+ font-size: 42px;
+ fill: #11811b;
+ }
+}
diff --git a/apps/client/src/core/generationPanel/ActivityPanel.scss.d.ts b/apps/client/src/core/generationPanel/ActivityPanel.scss.d.ts
new file mode 100644
index 000000000..c99101d2e
--- /dev/null
+++ b/apps/client/src/core/generationPanel/ActivityPanel.scss.d.ts
@@ -0,0 +1,32 @@
+declare namespace ActivityPanelScssNamespace {
+ export interface IActivityPanelScss {
+ actionsRow: string;
+ activityPanel: string;
+ background: string;
+ counter: string;
+ dataPanel: string;
+ dataRow: string;
+ dataRowLabel: string;
+ dataRowValue: string;
+ fadeOut: string;
+ generateOverlay: string;
+ generationComplete: string;
+ generationLabel: string;
+ generationOverlay: string;
+ generationOverlayBg: string;
+ generationRow: string;
+ generationSettingsContent: string;
+ overlayWrapper: string;
+ panel1: string;
+ panel2: string;
+ pie: string;
+ row: string;
+ }
+}
+
+declare const ActivityPanelScssModule: ActivityPanelScssNamespace.IActivityPanelScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ActivityPanelScssNamespace.IActivityPanelScss;
+};
+
+export = ActivityPanelScssModule;
diff --git a/apps/client/src/core/generationPanel/Engine.component.tsx b/apps/client/src/core/generationPanel/Engine.component.tsx
new file mode 100644
index 000000000..52c72af44
--- /dev/null
+++ b/apps/client/src/core/generationPanel/Engine.component.tsx
@@ -0,0 +1,85 @@
+import { useEffect } from 'react';
+import * as coreUtils from '../../utils/coreUtils';
+import C from '@generatedata/config/constants';
+import useDidUpdate from '../../hooks/useDidUpdate';
+import { DataPacket } from '~store/packets/packets.reducer';
+import { CountryNamesMap } from '@generatedata/plugins';
+import { GenerationWorkerActionType } from '~core/generator/generation.types';
+import { DataTypeMap, ExportTypeMap } from '@generatedata/plugins';
+import { CountryDataType } from '@generatedata/types';
+
+export type EngineProps = {
+ fullI18n: any;
+ packet: DataPacket | null;
+ logDataBatch: (numGeneratedRows: number, data: any) => void;
+ workerUtilsUrl: string;
+ dataTypeWorkerMap: DataTypeMap;
+ exportTypeWorkerMap: ExportTypeMap;
+ dataTypes: DataTypeMap;
+ countryNames: CountryNamesMap | null;
+ countryData: CountryDataType;
+};
+
+// this component does the actual work of generating the data and populating the store. It doesn't have a DOM presence,
+// it's just done this way to utilize the lifecycle methods
+const Engine = ({
+ packet,
+ fullI18n,
+ logDataBatch,
+ countryNames,
+ countryData,
+ dataTypeWorkerMap,
+ exportTypeWorkerMap,
+ workerUtilsUrl
+}: EngineProps): any => {
+ if (packet === null || fullI18n === null) {
+ return null;
+ }
+ const { isPaused, config, generationWorkerId, numGeneratedRows, speed } = packet;
+ const { numRowsToGenerate, columns, template, exportType, exportTypeSettings, stripWhitespace } = config;
+ const generationWorker = coreUtils.getGenerationWorker(generationWorkerId);
+
+ useEffect(() => {
+ if (numGeneratedRows !== 0) {
+ return;
+ }
+
+ generationWorker.postMessage({
+ action: GenerationWorkerActionType.Generate,
+ numResults: numRowsToGenerate,
+ batchSize: C.GENERATION_BATCH_SIZE,
+ speed,
+ columns,
+ i18n: fullI18n,
+ template,
+ countryNames,
+ countryData,
+ workerUtilsUrl,
+ dataTypeWorkerMap,
+ exportTypeWorkerUrl: exportTypeWorkerMap[exportType],
+ exportTypeSettings,
+ stripWhitespace
+ });
+
+ generationWorker.onmessage = ({ data }): void => {
+ logDataBatch(data.numGeneratedRows, data.data);
+ };
+ }, [numGeneratedRows]);
+
+ useDidUpdate(() => {
+ generationWorker.postMessage({
+ action: isPaused ? GenerationWorkerActionType.Pause : GenerationWorkerActionType.Continue
+ });
+ }, [isPaused]);
+
+ useDidUpdate(() => {
+ generationWorker.postMessage({
+ action: GenerationWorkerActionType.SetSpeed,
+ speed
+ });
+ }, [speed]);
+
+ return null;
+};
+
+export default Engine;
diff --git a/apps/client/src/core/generationPanel/Engine.container.tsx b/apps/client/src/core/generationPanel/Engine.container.tsx
new file mode 100644
index 000000000..98c14f810
--- /dev/null
+++ b/apps/client/src/core/generationPanel/Engine.container.tsx
@@ -0,0 +1,45 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as packetActions from '../store/packets/packets.actions';
+import * as selectors from '../store/generator/generator.selectors';
+import * as packetSelectors from '../store/packets/packets.selectors';
+import Engine, { EngineProps } from './Engine.component';
+import * as coreUtils from '../../utils/coreUtils';
+import { GDAction } from '~types/general';
+import { getCountryData } from '@generatedata/plugins';
+
+const mapStateToProps = (state: any): Partial & { packetId: any } => {
+ const packet = packetSelectors.getCurrentPacket(state);
+ const packetId = packetSelectors.getCurrentPacketId(state);
+
+ const props: Partial & { packetId: any } = {
+ packetId,
+ fullI18n: selectors.getI18n(state),
+ packet,
+ countryNames: coreUtils.getCountryNames()
+ };
+
+ if (packet !== null) {
+ props.workerUtilsUrl = coreUtils.getWorkerUtilsUrl();
+ props.exportTypeWorkerMap = coreUtils.getExportTypeWorkerMap(selectors.getLoadedExportTypes(state));
+ props.dataTypeWorkerMap = coreUtils.getDataTypeWorkerMap(packet.config.dataTypes);
+ props.countryData = getCountryData();
+ }
+
+ return props;
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): any => ({ dispatch });
+
+const mergeProps = ({ packetId, ...stateProps }: any, { dispatch }: any): EngineProps => {
+ if (stateProps.packet === null) {
+ return stateProps;
+ }
+
+ return {
+ ...stateProps,
+ logDataBatch: (numGenRows: number, dataStr: string): GDAction => dispatch(packetActions.logDataBatch(packetId, numGenRows, dataStr))
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(Engine);
diff --git a/apps/client/src/core/generationPanel/GenerationSettings.component.tsx b/apps/client/src/core/generationPanel/GenerationSettings.component.tsx
new file mode 100644
index 000000000..e24482566
--- /dev/null
+++ b/apps/client/src/core/generationPanel/GenerationSettings.component.tsx
@@ -0,0 +1,186 @@
+import * as React from 'react';
+import { NumericFormat } from 'react-number-format';
+import Button from '@mui/material/Button';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import { getI18nString } from '@generatedata/utils/lang';
+import { getFormattedNum } from '@generatedata/utils/number';
+import styles from './ActivityPanel.scss';
+import sharedStyles from '../../styles/shared.scss';
+import { ErrorTooltip } from '~components/tooltips';
+import { MediumSpinner } from '~components/loaders/loaders';
+import Engine from './Engine.container';
+import { DataPacket } from '~store/packets/packets.reducer';
+import * as coreUtils from '../../utils/coreUtils';
+import CheckIcon from '@mui/icons-material/Check';
+import { GenerationWorkerActionType } from '~core/generator/generation.types';
+import clientConfig from '@generatedata/config/clientConfig';
+
+export type GenerationSettingsProps = {
+ visible: boolean;
+ packet: DataPacket | null;
+ isLoggedIn: boolean;
+ isGenerating: boolean;
+ onChangeNumRowsToGenerate: (numRows: number) => void;
+ onClose: () => void;
+ onGenerate: () => void;
+ onAbort: () => void;
+ onDownload: () => void;
+ numRowsToGenerate: number;
+ i18n: any;
+ stripWhitespace: boolean;
+ onToggleStripWhitespace: () => void;
+ workerResources: any;
+};
+
+const GenerationSettingsPanel = ({
+ visible,
+ isLoggedIn,
+ onClose,
+ i18n,
+ stripWhitespace,
+ numRowsToGenerate,
+ onChangeNumRowsToGenerate,
+ onToggleStripWhitespace,
+ onGenerate,
+ isGenerating,
+ packet,
+ onAbort,
+ onDownload
+}: GenerationSettingsProps) => {
+ let error = '';
+
+ if (!numRowsToGenerate) {
+ error = i18n.requiredField;
+ } else if (!isLoggedIn && numRowsToGenerate > clientConfig.appSettings.GD_MAX_DEMO_MODE_ROWS) {
+ error = getI18nString(i18n.overMaxAnonRows, [getFormattedNum(clientConfig.appSettings.GD_MAX_DEMO_MODE_ROWS)]);
+ }
+
+ const getEngine = () => {
+ if (!visible || !isGenerating) {
+ return null;
+ }
+
+ return ;
+ };
+
+ const getGenerationOverlay = () => {
+ if (!isGenerating || !packet) {
+ return null;
+ }
+
+ const { numGeneratedRows } = packet;
+
+ if (packet.numGeneratedRows === numRowsToGenerate) {
+ return (
+ <>
+
+
+
+ {i18n.dataGenerated}
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+ {i18n.generated} {numGeneratedRows} / {numRowsToGenerate}
+
+
+ >
+ );
+ };
+
+ let buttonLabel = i18n.generate;
+ let actionButtonClick = onGenerate;
+ let actionButtonDisabled = !!error;
+
+ if (packet) {
+ if (packet.numGeneratedRows === numRowsToGenerate) {
+ buttonLabel = i18n.download;
+ actionButtonClick = onDownload;
+ actionButtonDisabled = false;
+ }
+ }
+
+ const closeModal = (): void => {
+ if (packet) {
+ const { generationWorkerId } = packet;
+ const generationWorker = coreUtils.getGenerationWorker(generationWorkerId);
+
+ onAbort();
+ onClose();
+ generationWorker.postMessage({
+ action: GenerationWorkerActionType.Abort
+ });
+ coreUtils.destroyGenerationWorker(generationWorkerId);
+ } else {
+ onClose();
+ }
+ };
+
+ let cancelButton: any = (
+
+ {i18n.cancel}
+
+ );
+ if (packet && packet.numGeneratedRows === numRowsToGenerate) {
+ cancelButton = null;
+ }
+
+ return (
+ <>
+
+
+
{i18n.generate}
+
+ {getGenerationOverlay()}
+
+ {i18n.generate}
+
+ onChangeNumRowsToGenerate(parseInt(value, 10))}
+ />
+
+ {i18n.rows}
+
+
+
+ {i18n.stripWhitespace}
+
+
+
+
+
+
+ {cancelButton}
+
+ {buttonLabel}
+
+
+
+
+
+
+ {getEngine()}
+ >
+ );
+};
+
+export default GenerationSettingsPanel;
diff --git a/apps/client/src/core/generationPanel/GenerationSettings.container.tsx b/apps/client/src/core/generationPanel/GenerationSettings.container.tsx
new file mode 100644
index 000000000..c3b657cc0
--- /dev/null
+++ b/apps/client/src/core/generationPanel/GenerationSettings.container.tsx
@@ -0,0 +1,52 @@
+import { Action, Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as actions from '../store/generator/generator.actions';
+import * as packetActions from '../store/packets/packets.actions';
+import * as selectors from '../store/generator/generator.selectors';
+import GenerationSettings, { GenerationSettingsProps } from './GenerationSettings.component';
+import C from '@generatedata/config/constants';
+import * as packetSelectors from '~store/packets/packets.selectors';
+import { GDAction } from '~types/general';
+import { isLoggedIn } from '~store/main/main.selectors';
+
+const mapStateToProps = (state: any, ownProps: Partial): Partial => {
+ const packet = packetSelectors.getCurrentPacket(state);
+ const largePacketSize = !!packet && packet.config.numRowsToGenerate > C.SMALL_GENERATION_COUNT;
+
+ return {
+ packet,
+ visible: selectors.isGenerationSettingsPanelVisible(state),
+ isLoggedIn: isLoggedIn(state),
+ isGenerating: !largePacketSize && packetSelectors.isGenerating(state),
+ i18n: selectors.getCoreI18n(state),
+ numRowsToGenerate: selectors.getNumRowsToGenerate(state),
+ stripWhitespace: selectors.shouldStripWhitespace(state),
+ ...ownProps
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): { dispatch: any } => ({
+ dispatch
+});
+
+const mergeProps = ({ packetId, ...stateProps }: any, { dispatch }: any): GenerationSettingsProps => {
+ const props = {
+ ...stateProps,
+ onClose: (): Action => dispatch(actions.hideStartGenerationPanel()),
+ onChangeNumRowsToGenerate: (numRows: number): Action => dispatch(actions.updateNumRowsToGenerate(numRows)),
+ onToggleStripWhitespace: (): Action => dispatch(actions.toggleStripWhitespace()),
+ onGenerate: (): Action => dispatch(packetActions.startGeneration())
+ };
+
+ if (stateProps.packet === null) {
+ return props;
+ }
+
+ return {
+ ...props,
+ onAbort: (): GDAction => dispatch(packetActions.abortGeneration(packetId)),
+ onDownload: (): any => dispatch(packetActions.promptToDownload())
+ };
+};
+
+export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(GenerationSettings);
diff --git a/apps/client/src/core/generationPanel/__tests__/ActivePacketsList.container.test.tsx b/apps/client/src/core/generationPanel/__tests__/ActivePacketsList.container.test.tsx
new file mode 100644
index 000000000..d4d0f69e5
--- /dev/null
+++ b/apps/client/src/core/generationPanel/__tests__/ActivePacketsList.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import ActivePacketsList from '../ActivePacketsList.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('ActivePacketsList', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generationPanel/__tests__/ActivityPanel.container.test.tsx b/apps/client/src/core/generationPanel/__tests__/ActivityPanel.container.test.tsx
new file mode 100644
index 000000000..7af8e5ed6
--- /dev/null
+++ b/apps/client/src/core/generationPanel/__tests__/ActivityPanel.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import ActivityPanel from '../ActivityPanel.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('ActivityPanel', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generationPanel/__tests__/GenerationSettings.container.test.tsx b/apps/client/src/core/generationPanel/__tests__/GenerationSettings.container.test.tsx
new file mode 100644
index 000000000..060f996f2
--- /dev/null
+++ b/apps/client/src/core/generationPanel/__tests__/GenerationSettings.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import GenerationSettings from '../GenerationSettings.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('GenerationSettings', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generationPanel/__tests__/generation.helpers.test.ts b/apps/client/src/core/generationPanel/__tests__/generation.helpers.test.ts
new file mode 100644
index 000000000..f27e818a7
--- /dev/null
+++ b/apps/client/src/core/generationPanel/__tests__/generation.helpers.test.ts
@@ -0,0 +1,60 @@
+import { getRowGenerationRatePerSecond, getPercentageLabel } from '../generation.helpers';
+
+describe('getRowGenerationRatePerSecond', () => {
+ it('if exactly 1 second has gone by, the generation rate should be the exact generation count', () => {
+ expect(getRowGenerationRatePerSecond(0, 0, 1000, 123)).toEqual({ 1: 123 });
+ expect(getRowGenerationRatePerSecond(0, 1000, 2000, 55)).toEqual({ 2: 55 });
+ });
+
+ it('base time should be factored in and still return data in seconds', () => {
+ expect(getRowGenerationRatePerSecond(5000, 5000, 6000, 123)).toEqual({
+ 1: 123
+ });
+ expect(getRowGenerationRatePerSecond(5000, 6000, 7000, 55)).toEqual({
+ 2: 55
+ });
+ });
+
+ it('base time should be factored in', () => {
+ expect(getRowGenerationRatePerSecond(0, 5000, 6000, 10)).toEqual({ 6: 10 });
+ expect(getRowGenerationRatePerSecond(0, 6000, 7000, 19)).toEqual({ 7: 19 });
+ });
+
+ it('if exactly 2 seconds have passed, the generation rate is half the numRows passed', () => {
+ expect(getRowGenerationRatePerSecond(0, 0, 2000, 100)).toEqual({
+ 1: 50,
+ 2: 50
+ });
+ expect(getRowGenerationRatePerSecond(0, 0, 2000, 150)).toEqual({
+ 1: 75,
+ 2: 75
+ });
+ expect(getRowGenerationRatePerSecond(0, 0, 2000, 60)).toEqual({
+ 1: 30,
+ 2: 30
+ });
+ });
+
+ it('end fractions of a second are factored in', () => {
+ expect(getRowGenerationRatePerSecond(0, 0, 1500, 60)).toEqual({
+ 1: 40,
+ 2: 20
+ });
+ });
+
+ it('start fractions of a second are factored in', () => {
+ expect(getRowGenerationRatePerSecond(0, 500, 2000, 60)).toEqual({
+ 1: 20,
+ 2: 40
+ });
+ });
+});
+
+describe('getPercentageLabel', () => {
+ it('returns appropriate decimal places based on num generated', () => {
+ expect(getPercentageLabel(90, 1000)).toEqual('90');
+ expect(getPercentageLabel(90, 10000)).toEqual('90.0');
+ expect(getPercentageLabel(90, 100000)).toEqual('90.0');
+ expect(getPercentageLabel(90, 1000000)).toEqual('90.00');
+ });
+});
diff --git a/apps/client/src/core/generationPanel/generation.helpers.ts b/apps/client/src/core/generationPanel/generation.helpers.ts
new file mode 100644
index 000000000..62de134b9
--- /dev/null
+++ b/apps/client/src/core/generationPanel/generation.helpers.ts
@@ -0,0 +1,182 @@
+import { GenerationActivityPanel, LoadTimeGraphDuration } from '~types/general';
+import { ColumnData } from '@generatedata/plugins';
+import { affectedDataTypes } from '~utils/dataTypes';
+import C from '@generatedata/config/constants';
+import { UnchangedGenerationData } from '~types/generator';
+
+export const getPercentageLabel = (percentage: number, numRowsToGenerate: number): string => {
+ let decimalPlaces = 0;
+ if (numRowsToGenerate >= 1000000) {
+ decimalPlaces = 2;
+ } else if (numRowsToGenerate >= 10000) {
+ decimalPlaces = 1;
+ }
+ return percentage.toFixed(decimalPlaces);
+};
+
+export const getByteSize = (str: string): number => encodeURI(str).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1;
+
+export const downloadFile = (filename: string, data: any, type: string): void => {
+ const file = new Blob([data], { type: type });
+ const a = document.createElement('a');
+ const url = URL.createObjectURL(file);
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+
+ setTimeout(() => {
+ document.body.removeChild(a);
+ window.URL.revokeObjectURL(url);
+ }, 0);
+};
+
+type secondCount = {
+ [second: number]: number;
+};
+
+// used in the stats generation. This does the job of figuring out the row generation rate per second. It returns
+// an object of the following form:
+// {
+// 4: 30
+// 5: 50
+// 6: 14
+// }
+// where the keys are a particular second, and the values are the number of rows generated for that second. It's up to the
+// consuming script to append that data to existing generated data. Note that since the start/end times for the generation
+// block almost certainly don't lie directly on the start second, second #4 would already have some number from the
+// previous batch
+export const getRowGenerationRatePerSecond = (
+ // this is the time the entire generation started. It'll remain fixed for an entire packet
+ baseTime: number,
+
+ // the time the batch was started
+ batchStartTime: number,
+
+ // the time this batch stopped generating
+ batchEndTime: number,
+
+ // how many rows were generated during the interval (will always be C.GENERATION_BATCH_SIZE)
+ numRows: number
+): secondCount => {
+ // the math below relies on this being correct otherwise we'll get stuck in an infinite loop
+ if (batchEndTime <= batchStartTime) {
+ throw Error('invalid data passed to getRowGenerationRatePerSecond()');
+ }
+
+ // calculate the time duration of a single row over the time duration
+ const singleRowTime = (batchEndTime - batchStartTime) / numRows;
+
+ // now split the duration into discrete seconds, and loop over them, figuring out how many would
+ let currTimeBlock = batchStartTime;
+ let currentSecond;
+ const result: secondCount = {};
+
+ while (true) {
+ const endOfCurrentSecondMs = Math.floor(Math.floor(currTimeBlock / 1000) * 1000) + 1000;
+
+ let secondDuration;
+ let shouldBreak = false;
+ if (endOfCurrentSecondMs >= batchEndTime) {
+ secondDuration = batchEndTime - currTimeBlock;
+ shouldBreak = true;
+ } else {
+ secondDuration = endOfCurrentSecondMs - currTimeBlock;
+ }
+
+ // now see how many of the rows could fit into this duration
+ const numRowsInCurrentDuration = secondDuration / singleRowTime;
+
+ const currentSecondMs = Math.floor(currTimeBlock - baseTime);
+ currentSecond = (currentSecondMs === 0 ? 0 : Math.floor(currentSecondMs / 1000)) + 1;
+ result[currentSecond] = parseFloat(numRowsInCurrentDuration.toFixed(2));
+
+ if (shouldBreak) {
+ break;
+ }
+
+ currTimeBlock += secondDuration;
+ }
+
+ return result;
+};
+
+/*
+ * Used for the preview panel only. When a user changes a row we need to figure out what rows to invalidate in the preview
+ * panel. Examples:
+ * - A row change only affects itself. Just that row should be updated, all other rows can be marked as unchanged.
+ * - A region row changes, and a city row depends on it. Here, both the region + city rows need to refresh.
+ * - A county row changes, and 10 other rows are based on it. All 11 rows need to update.
+ *
+ * Note that this isn't as good as it could be. If a Region field changes, ALL City field get repainted - even if they're
+ * not mapped to the region that just changed. This is because we're not yet formally defining the relationships
+ * between the Data Types in a well-defined way. If we did that we could make this logic smarter. But right now, each
+ * data type maps to other data types in whatever way they find useful (e.g. a Composite field just referencing it
+ * via its Options field and entering a {{ROWX}} placeholder string).
+ */
+export const getUnchangedData = (
+ idsToRefresh: string[],
+ columns: (ColumnData & { id: string })[],
+ dataTypePreviewData: any
+): UnchangedGenerationData => {
+ if (!idsToRefresh.length) {
+ return {};
+ }
+
+ const columnsWithIndex = columns.map((col, colIndex) => ({
+ ...col,
+ colIndex
+ }));
+
+ // first pass is to look at the list of IDs about to be refreshed, and get a list of data types in the
+ // grid that aren't affected by this change
+ const unchangedDataTypes: any = {};
+ columnsWithIndex
+ .filter((col) => idsToRefresh.indexOf(col.id) === -1)
+ .forEach((col) => {
+ unchangedDataTypes[col.dataType] = true;
+ });
+
+ // now loop through the changed row IDs and whittle down the list by examining any data types affected by those
+ // row changes
+ columnsWithIndex.forEach((col) => {
+ if (idsToRefresh.indexOf(col.id) !== -1) {
+ affectedDataTypes[col.dataType].forEach((dt: any) => {
+ if (unchangedDataTypes[dt]) {
+ delete unchangedDataTypes[dt];
+ }
+ });
+ }
+ });
+
+ const result: any = {};
+ columnsWithIndex
+ .filter((col) => {
+ // just for clarity, because this is kinda dense
+ const colIsNotAffectedDataType = unchangedDataTypes[col.dataType];
+ const isNotDirectlyChangedCol = idsToRefresh.indexOf(col.id) === -1;
+
+ return colIsNotAffectedDataType && isNotDirectlyChangedCol;
+ })
+ .map((col) => {
+ result[col.colIndex] = dataTypePreviewData[col.id];
+ });
+
+ return result;
+};
+
+export const getGenerationActivityPanel = (numRowsToGenerate: number): GenerationActivityPanel => {
+ return numRowsToGenerate > C.SMALL_GENERATION_COUNT ? GenerationActivityPanel.large : GenerationActivityPanel.small;
+};
+
+export const getGraphDuration = (numRowsToGenerate: number): LoadTimeGraphDuration => {
+ if (numRowsToGenerate < C.GRAPH_RANGES.RANGE1) {
+ return LoadTimeGraphDuration.all;
+ } else if (numRowsToGenerate < C.GRAPH_RANGES.RANGE2) {
+ return LoadTimeGraphDuration.s15;
+ } else if (numRowsToGenerate < C.GRAPH_RANGES.RANGE3) {
+ return LoadTimeGraphDuration.s30;
+ }
+
+ return LoadTimeGraphDuration.m1;
+};
diff --git a/apps/client/src/core/generator/Generator.component.tsx b/apps/client/src/core/generator/Generator.component.tsx
new file mode 100644
index 000000000..43af76a07
--- /dev/null
+++ b/apps/client/src/core/generator/Generator.component.tsx
@@ -0,0 +1,111 @@
+import React from 'react';
+import { useWindowSize } from 'react-hooks-window-size';
+import SplitPane from 'react-split-pane';
+import Grid from './grid/Grid.container';
+import Preview from './previewPanel/PreviewPanel.container';
+import { GeneratorPanel } from '~types/general';
+import ExportSettings from './exportSettings/ExportSettings.container';
+import ActivityPanel from '../generationPanel/ActivityPanel.container';
+import GenerationSettings from '../generationPanel/GenerationSettings.container';
+import TourDialog from '~core/dialogs/tourIntro/TourIntro.container';
+import DataSetHistory from './dataSetHistory/DataSetHistory.container';
+import HelpDialog from '../dialogs/help/HelpDialog.container';
+import ClearPageDialog from '../dialogs/clearPage/ClearPage.container';
+import SchemaDialog from '../dialogs/schema/Schema.container';
+import { GeneratorLayout } from '@generatedata/types';
+import C from '@generatedata/config/constants';
+import './Generator.scss';
+
+export type GeneratorProps = {
+ i18n: any;
+ isGridVisible: boolean;
+ isPreviewVisible: boolean;
+ generatorLayout: GeneratorLayout;
+ onResizePanels: (size: number) => void;
+ lastLayoutWidth: number | null;
+ lastLayoutHeight: number | null;
+ smallScreenVisiblePanel: GeneratorPanel;
+ showDataSetHistory: boolean;
+};
+
+const Builder = ({
+ isGridVisible,
+ isPreviewVisible,
+ generatorLayout,
+ onResizePanels,
+ lastLayoutWidth,
+ lastLayoutHeight,
+ smallScreenVisiblePanel,
+ showDataSetHistory,
+ i18n
+}: GeneratorProps) => {
+ const windowSize = useWindowSize();
+ const onResize = (size: number): void => onResizePanels(size);
+
+ let minSize: number;
+ let maxSize: number;
+ let defaultSize: number | string = '50%';
+ if (generatorLayout === GeneratorLayout.vertical) {
+ minSize = 350;
+ maxSize = windowSize.width - 350;
+ if (lastLayoutWidth) {
+ defaultSize = lastLayoutWidth < maxSize ? lastLayoutWidth : maxSize;
+ }
+ } else {
+ minSize = 100;
+ maxSize = windowSize.height - (C.HEADER_HEIGHT + C.FOOTER_HEIGHT) - 100;
+ if (lastLayoutHeight) {
+ defaultSize = lastLayoutHeight < maxSize ? lastLayoutHeight : maxSize;
+ }
+ }
+
+ const getContent = () => {
+ if (windowSize.width < C.SMALL_SCREEN_WIDTH) {
+ return smallScreenVisiblePanel === 'grid' ? : ;
+ }
+
+ // data set history only available on desktop
+ if (showDataSetHistory) {
+ return ;
+ }
+
+ if (isGridVisible && isPreviewVisible) {
+ return (
+ /* @ts-ignore-line */
+
+
+
+
+ );
+ }
+ if (isGridVisible) {
+ return ;
+ }
+ return ;
+ };
+
+ return (
+ <>
+
+
{getContent()}
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+export default Builder;
diff --git a/apps/client/src/core/generator/Generator.container.ts b/apps/client/src/core/generator/Generator.container.ts
new file mode 100644
index 000000000..a941d474b
--- /dev/null
+++ b/apps/client/src/core/generator/Generator.container.ts
@@ -0,0 +1,23 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import Generator, { GeneratorProps } from './Generator.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+import { GDAction } from '~types/general';
+
+const mapStateToProps = (state: any): Omit => ({
+ i18n: selectors.getCoreI18n(state),
+ isGridVisible: selectors.isGridVisible(state),
+ isPreviewVisible: selectors.isPreviewVisible(state),
+ generatorLayout: selectors.getGeneratorLayout(state),
+ lastLayoutWidth: selectors.getLastLayoutWidth(state),
+ lastLayoutHeight: selectors.getLastLayoutHeight(state),
+ smallScreenVisiblePanel: selectors.getSmallScreenVisiblePanel(state),
+ showDataSetHistory: selectors.shouldShowDataSetHistory(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ onResizePanels: (size: number): GDAction => dispatch(actions.setPanelSize(size))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Generator);
diff --git a/apps/client/src/core/generator/Generator.scss b/apps/client/src/core/generator/Generator.scss
new file mode 100644
index 000000000..927917b5a
--- /dev/null
+++ b/apps/client/src/core/generator/Generator.scss
@@ -0,0 +1,46 @@
+:global(.Pane2) {
+ max-width: 100%;
+ overflow: hidden;
+}
+
+:global(.Resizer) {
+ background-image: url('./images/bg.png');
+ z-index: 1;
+ box-sizing: border-box;
+ background-clip: padding-box;
+
+ &:global(.horizontal) {
+ flex: 0 0 auto;
+ height: 5px;
+ margin: 0;
+ cursor: row-resize;
+ width: 100%;
+
+ &:hover {
+ background-image: none;
+ background-color: #dddddd;
+ }
+ }
+
+ &:global(.vertical) {
+ width: 5px;
+ margin: 0;
+ cursor: col-resize;
+
+ &:hover {
+ background-image: none;
+ background-color: #dddddd;
+ }
+ }
+
+ &:hover(.disabled) {
+ cursor: not-allowed;
+ }
+}
+
+.controlRow {
+ display: flex;
+ flex-direction: row;
+ padding: 6px 10px;
+ justify-content: flex-end;
+}
diff --git a/apps/client/src/core/generator/Generator.scss.d.ts b/apps/client/src/core/generator/Generator.scss.d.ts
new file mode 100644
index 000000000..e5ea37047
--- /dev/null
+++ b/apps/client/src/core/generator/Generator.scss.d.ts
@@ -0,0 +1,13 @@
+declare namespace GeneratorScssNamespace {
+ export interface IGeneratorScss {
+ controlRow: string;
+ disabled: string;
+ }
+}
+
+declare const GeneratorScssModule: GeneratorScssNamespace.IGeneratorScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: GeneratorScssNamespace.IGeneratorScss;
+};
+
+export = GeneratorScssModule;
diff --git a/apps/client/src/core/generator/NOTES.md b/apps/client/src/core/generator/NOTES.md
new file mode 100644
index 000000000..33d34393d
--- /dev/null
+++ b/apps/client/src/core/generator/NOTES.md
@@ -0,0 +1,47 @@
+## Generator notes
+
+A few notes on how the current generator code works with the web workers so I can keep it straight in my head.
+
+**DataTypeWorker**
+
+1. core/generationPanel/Engine.component.tsx
+ - uses both dataTypeWorker and exportTypeWorker to generate the actual data. No in-between, just churns through them
+ passing off work from response of dataTypeWorker to exportTypeWorker.
+ - also providing pause, continue and set speed options. All done through dataTypeWorker. Weird.
+2. core/generationPanel/ActivityPanel.component.tsx
+ - allows aborting the process.
+ - uses dataTypeWorker to abort it. Again, weird.
+3. core/generationPanel/GenerationSettings.component.tsx
+ - aborts process on close.
+4. store/generator/generator.actions.ts
+ - Refresh panel action. This _separately_ calls dataTypeWorker to generate the data and persist in the store.
+ - it does some clever/dense stuff to figure out what's changed, then pass that into the dataTypeWorker so it
+ keeps the existing unchanged data.
+
+**ExportTypeWorker**
+
+1. CodeMirrorWrapper.component.tsx
+ - used in the previous panel for actually generating the preview string based on the stored preview data.
+
+**Backend code**
+
+- The backend code won't need 2 separate pieces for the data type + export type parts. It'll always be treating the
+ generation as a single action.
+- It also won't need the additional complexities of the preview panel stuff, but that can just be ignored.
+
+### Notes
+
+Having a separate dataTypeWorker and exportTypeWorker makes sense for the preview panel, which is what I initially
+wrote it for. But for the actual code generation it feels super clunky. Singling out the dataTypeWorker to cancel/pause/continue/set
+speed on the whole generation process is totally weird and hard to make sense of. Wouldn't be hard to just wrap an
+interface over this part... but keep the two separate for use elsewhere...
+
+Now that we're moving the bulk of the web workers into separate, runtime-free modules (web workers + node), the actual
+workers are super small. Combine into the single `generator.worker.ts` as originally planned and offer a more complex
+`action` interface to allow either interacting with the individual actions (data types, export types) or the general
+generation interface. I kinda like this...
+
+Note that this still means nested web workers. Whoah! https://bugs.webkit.org/show_bug.cgi?id=22723 - maybe
+Safari now supports it.
+
+\*\*\* Need to reconcile "template" with what we want to pass as the BE code interface.
diff --git a/apps/client/src/core/generator/__tests__/Generator.container.test.tsx b/apps/client/src/core/generator/__tests__/Generator.container.test.tsx
new file mode 100644
index 000000000..ba8b518cc
--- /dev/null
+++ b/apps/client/src/core/generator/__tests__/Generator.container.test.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import Generator from '../Generator.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('Generator', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ // TODO bug here. The CSS modules classes are rendering as undefined... it's not the lib we're using: identify-obj-proxy
+ // also has the same issue.
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generator/dataSetHistory/DataSetHistory.component.tsx b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.component.tsx
new file mode 100644
index 000000000..fb82a9aeb
--- /dev/null
+++ b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.component.tsx
@@ -0,0 +1,171 @@
+import React, { useEffect, useState } from 'react';
+import { format, fromUnixTime } from 'date-fns';
+import { TypedDocumentNode } from '@apollo/client';
+import { useQuery } from '@apollo/client/react';
+import Drawer from '@mui/material/Drawer';
+import InfoIcon from '@mui/icons-material/InfoOutlined';
+import HighlightOffIcon from '@mui/icons-material/HighlightOff';
+import { DefaultSpinner, Centered } from '~components/loaders/loaders';
+import { PrimaryButton, SecondaryButton } from '~components/Buttons.component';
+import { Tooltip } from '~components/tooltips';
+import * as queries from '~core/queries';
+import * as styles from './DataSetHistory.scss';
+import C from '@generatedata/config/constants';
+import { CurrentDataSet } from '~store/generator/generator.reducer';
+
+export type DataSetHistoryProps = {
+ showPanel: boolean;
+ dataSet: CurrentDataSet;
+ selectedDataSetHistoryItem: {
+ historyId: number | null;
+ isLatest: boolean;
+ };
+ setSelectedDataHistoryItem: (historyId: number, isLatest: boolean) => void;
+ closePanel: () => void;
+ loadHistoryVersion: (content: any) => void;
+ loadStashedVersion: () => void;
+ i18n: any;
+};
+
+const NUM_PER_PAGE = 200;
+const currentPage = 1;
+
+const Row = ({ rowLabel, dateCreated, content, loadHistoryVersion, isSelected, i18n, Btn }: any) => {
+ let classes = styles.row;
+ if (isSelected) {
+ classes += ` ${styles.selectedRow}`;
+ }
+
+ return (
+
+ {rowLabel &&
{rowLabel} }
+
+
{format(fromUnixTime(dateCreated / 1000), C.DATETIME_FORMAT)}
+
+ loadHistoryVersion(content)}>
+ {i18n.view}
+
+
+
+
+ );
+};
+
+export const DataSetHistory = ({
+ showPanel,
+ dataSet,
+ closePanel,
+ loadHistoryVersion,
+ loadStashedVersion,
+ selectedDataSetHistoryItem,
+ setSelectedDataHistoryItem,
+ i18n
+}: DataSetHistoryProps): React.ReactElement | null => {
+ const { dataSetId, dataSetName, lastSaved } = dataSet;
+ const { historyId } = selectedDataSetHistoryItem;
+ const [called, setCalled] = useState(false);
+
+ const { data, loading, error, refetch } = useQuery(queries.GET_DATA_SET_HISTORY, {
+ fetchPolicy: 'cache-and-network',
+ variables: {
+ dataSetId,
+ offset: (currentPage - 1) * NUM_PER_PAGE,
+ limit: NUM_PER_PAGE
+ },
+ skip: !dataSetId || !showPanel
+ });
+
+ useEffect(() => {
+ if (!loading && !error) {
+ setCalled(true);
+ }
+ }, [loading, error]);
+
+ // need to clear the cache whenever the lastSaved changes
+ useEffect(() => {
+ if (called && showPanel && dataSetId) {
+ refetch();
+ }
+ }, [dataSetId, called, lastSaved, showPanel]);
+
+ if (!dataSetId) {
+ return null;
+ }
+
+ const loadVersion = (version: any, isLatest: boolean): void => {
+ const { historyId, content } = version;
+ setSelectedDataHistoryItem(historyId, isLatest);
+ loadHistoryVersion(content);
+ };
+
+ let content = null;
+
+ if (data?.dataSetHistory) {
+ if (data.dataSetHistory.totalCount === 0) {
+ content = {i18n.noHistory}
;
+ } else {
+ const latestRow = data.dataSetHistory.results[0];
+
+ content = (
+ <>
+
+
+
+
+ {data.dataSetHistory.results.map((row: any) => (
+ loadVersion(row, false)}
+ isSelected={row.historyId === historyId}
+ i18n={i18n}
+ Btn={PrimaryButton}
+ />
+ ))}
+
+ >
+ );
+ }
+ }
+
+ let loader = null;
+ if (loading) {
+ loader = (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ {dataSetName}
+
+
+
+
+
+
+
+
+ {i18n.closePanel}
+
+
+
+
+ );
+};
diff --git a/apps/client/src/core/generator/dataSetHistory/DataSetHistory.container.ts b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.container.ts
new file mode 100644
index 000000000..edb24e02c
--- /dev/null
+++ b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.container.ts
@@ -0,0 +1,27 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { DataSetHistory, DataSetHistoryProps } from './DataSetHistory.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ dataSet: selectors.getCurrentDataSet(state),
+ selectedDataSetHistoryItem: selectors.getSelectedDataSetHistoryItem(state),
+ showPanel: selectors.shouldShowDataSetHistory(state)
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ closePanel: (): any => {
+ dispatch(actions.popStashedState());
+ dispatch(actions.hideDataSetHistory());
+ },
+ setSelectedDataHistoryItem: (historyId: number, isLatest: boolean): any =>
+ dispatch(actions.selectDataSetHistoryItem(historyId, isLatest)),
+ loadHistoryVersion: (content: any): any => dispatch(actions.loadDataSetHistoryItem(content)),
+ loadStashedVersion: (): any => dispatch(actions.loadStashedState())
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(DataSetHistory);
diff --git a/apps/client/src/core/generator/dataSetHistory/DataSetHistory.scss b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.scss
new file mode 100644
index 000000000..5ba0cf01d
--- /dev/null
+++ b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.scss
@@ -0,0 +1,136 @@
+@use '../../../styles/variables' as c;
+
+.panel {
+ display: flex;
+ flex-direction: column;
+ width: 400px;
+ height: 100%;
+
+ h2 {
+ display: flex;
+ align-items: center;
+ flex: 0 0 auto;
+ margin: 15px 20px;
+
+ span {
+ flex: 1;
+ }
+
+ svg {
+ margin-right: 5px;
+ font-size: 19px;
+ fill: #888888;
+ }
+ }
+
+ section {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow: hidden;
+ padding: 0 20px;
+ }
+
+ footer {
+ flex: 0 0 auto;
+ padding: 15px;
+ button {
+ width: 100%;
+
+ svg {
+ margin-right: 5px;
+ }
+ }
+ }
+}
+
+.rows {
+ flex: 1;
+ overflow: auto;
+}
+
+.rowWrapper {
+ display: flex;
+ font-size: 13px;
+ align-items: center;
+}
+
+div.selectedRow {
+ border: 1px solid c.$primary-color;
+ background-color: c.$primary-pale-color;
+ border-radius: 4px;
+}
+
+.row {
+ padding: 6px;
+ border: 1px solid transparent;
+ transition: all 0.2s ease-in-out;
+
+ label {
+ color: #999999;
+ font-size: 11px;
+ }
+
+ .id {
+ margin-left: 10px;
+ flex: 0 1 50px;
+ color: #999999;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .dateCreated {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .edit {
+ margin-right: 10px;
+ }
+
+ .del {
+ flex: 0 0 30px;
+ align-items: center;
+ cursor: pointer;
+
+ &.disabled {
+ cursor: inherit;
+
+ svg {
+ opacity: 0.2;
+ }
+ }
+
+ &:hover {
+ svg {
+ fill: #990000;
+ }
+ }
+ }
+}
+
+button.dataSetHistoryBtnClass {
+ color: white;
+ border-color: white;
+
+ &:disabled {
+ color: white;
+ border-color: white;
+ opacity: 0.2;
+ }
+}
+
+.currentVersionRow {
+ flex: 0 0 auto;
+
+ .row {
+ border: 1px solid c.$secondary-color;
+ background-color: c.$secondary-pale-color;
+ color: c.$secondary-dark-color;
+ margin-bottom: 10px;
+ border-radius: 4px;
+ }
+}
diff --git a/apps/client/src/core/generator/dataSetHistory/DataSetHistory.scss.d.ts b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.scss.d.ts
new file mode 100644
index 000000000..798d06da2
--- /dev/null
+++ b/apps/client/src/core/generator/dataSetHistory/DataSetHistory.scss.d.ts
@@ -0,0 +1,23 @@
+declare namespace DataSetHistoryScssNamespace {
+ export interface IDataSetHistoryScss {
+ currentVersionRow: string;
+ dataSetHistoryBtnClass: string;
+ dateCreated: string;
+ del: string;
+ disabled: string;
+ edit: string;
+ id: string;
+ panel: string;
+ row: string;
+ rowWrapper: string;
+ rows: string;
+ selectedRow: string;
+ }
+}
+
+declare const DataSetHistoryScssModule: DataSetHistoryScssNamespace.IDataSetHistoryScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: DataSetHistoryScssNamespace.IDataSetHistoryScss;
+};
+
+export = DataSetHistoryScssModule;
diff --git a/apps/client/src/core/generator/dataSetHistory/PanelButtons.component.tsx b/apps/client/src/core/generator/dataSetHistory/PanelButtons.component.tsx
new file mode 100644
index 000000000..7b8db6894
--- /dev/null
+++ b/apps/client/src/core/generator/dataSetHistory/PanelButtons.component.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { PreviewPanelButton } from '~components/Buttons.component';
+import * as styles from './DataSetHistory.scss';
+
+export type PanelButtonsProps = {
+ selectVersion: () => void;
+ selectedDataSetHistoryItem: {
+ historyId: number | null;
+ isLatest: boolean;
+ };
+ i18n: any;
+};
+
+export const PanelButtons = ({ selectVersion, selectedDataSetHistoryItem, i18n }: PanelButtonsProps) => {
+ const { isLatest } = selectedDataSetHistoryItem;
+
+ return (
+
+
+ {i18n.revertToVersion}
+
+
+ );
+};
diff --git a/apps/client/src/core/generator/dataSetHistory/PanelButtons.container.ts b/apps/client/src/core/generator/dataSetHistory/PanelButtons.container.ts
new file mode 100644
index 000000000..0233b1853
--- /dev/null
+++ b/apps/client/src/core/generator/dataSetHistory/PanelButtons.container.ts
@@ -0,0 +1,16 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { PanelButtons, PanelButtonsProps } from './PanelButtons.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ selectedDataSetHistoryItem: selectors.getSelectedDataSetHistoryItem(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ selectVersion: (): any => dispatch(actions.revertToHistoryVersion())
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(PanelButtons);
diff --git a/apps/client/src/core/generator/exportSettings/ExportSettings.component.tsx b/apps/client/src/core/generator/exportSettings/ExportSettings.component.tsx
new file mode 100644
index 000000000..ca0ade93e
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/ExportSettings.component.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import HighlightOffIcon from '@mui/icons-material/HighlightOff';
+import Button from '@mui/material/Button';
+import Drawer from '@mui/material/Drawer';
+import Tabs from '@mui/material/Tabs';
+import Tab from '@mui/material/Tab';
+import PreviewSettingsTab from './PreviewSettingsTab.container';
+import ExportTypeTab from './ExportTypeTab.container';
+import * as styles from './ExportSettings.scss';
+
+export type ExportSettingsProps = {
+ i18n: any;
+ showExportSettings: boolean;
+ toggleExportSettings: any;
+};
+
+export const ExportSettings = ({ i18n, showExportSettings, toggleExportSettings }: ExportSettingsProps): React.ReactElement => {
+ const [selectedTabIndex, setSelectedTabIndex] = React.useState(0);
+ const handleChange = (e: React.ChangeEvent<{}>, newValue: number): void => setSelectedTabIndex(newValue);
+ const getTab = () => {
+ if (selectedTabIndex === 0) {
+ return ;
+ } else {
+ return ;
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {i18n.closePanel}
+
+
+
+
+ );
+};
diff --git a/apps/client/src/core/generator/exportSettings/ExportSettings.container.ts b/apps/client/src/core/generator/exportSettings/ExportSettings.container.ts
new file mode 100644
index 000000000..fb826b7d2
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/ExportSettings.container.ts
@@ -0,0 +1,16 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { ExportSettings, ExportSettingsProps } from './ExportSettings.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ showExportSettings: selectors.shouldShowExportSettings(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ toggleExportSettings: (): any => dispatch(actions.toggleExportSettings())
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ExportSettings);
diff --git a/apps/client/src/core/generator/exportSettings/ExportSettings.scss b/apps/client/src/core/generator/exportSettings/ExportSettings.scss
new file mode 100644
index 000000000..4abc4a779
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/ExportSettings.scss
@@ -0,0 +1,71 @@
+@use '../../../styles/variables';
+
+.tabContent {
+ padding: 20px;
+}
+
+.row {
+ display: flex;
+ flex-direction: row;
+ height: 40px;
+ align-items: center;
+}
+
+.label {
+ flex: 0 0 180px;
+}
+
+.field {
+ flex: 1;
+}
+
+.panelHorizontal {
+ width: 100%;
+}
+
+.panel {
+ display: flex;
+ flex-direction: column;
+ width: 400px;
+ height: 100%;
+
+ button {
+ width: 50%;
+ }
+ section {
+ flex: 1;
+ }
+ footer {
+ padding: 15px;
+ button {
+ width: 100%;
+
+ svg {
+ margin-right: 5px;
+ }
+ }
+ }
+}
+
+.exportFormatRow {
+ background-image: url('./images/bg.png');
+ padding: 10px;
+ margin-bottom: 10px;
+ border-radius: 5px;
+ height: inherit;
+ .label {
+ font-size: 14px;
+ flex: 0 0 140px;
+ }
+}
+
+.spinner {
+ position: absolute;
+ top: calc(50% - 40px);
+ left: calc(50% - 40px);
+
+ &.fadeOut {
+ opacity: 0;
+ transition: opacity 0.25s ease-in-out;
+ }
+}
diff --git a/apps/client/src/core/generator/exportSettings/ExportSettings.scss.d.ts b/apps/client/src/core/generator/exportSettings/ExportSettings.scss.d.ts
new file mode 100644
index 000000000..262460737
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/ExportSettings.scss.d.ts
@@ -0,0 +1,20 @@
+declare namespace ExportSettingsScssNamespace {
+ export interface IExportSettingsScss {
+ exportFormatRow: string;
+ fadeOut: string;
+ field: string;
+ label: string;
+ panel: string;
+ panelHorizontal: string;
+ row: string;
+ spinner: string;
+ tabContent: string;
+ }
+}
+
+declare const ExportSettingsScssModule: ExportSettingsScssNamespace.IExportSettingsScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ExportSettingsScssNamespace.IExportSettingsScss;
+};
+
+export = ExportSettingsScssModule;
diff --git a/apps/client/src/core/generator/exportSettings/ExportSettings.types.ts b/apps/client/src/core/generator/exportSettings/ExportSettings.types.ts
new file mode 100644
index 000000000..3995cb1e7
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/ExportSettings.types.ts
@@ -0,0 +1 @@
+export type ExportSettingsTab = 'exportType' | 'previewPanel';
diff --git a/apps/client/src/core/generator/exportSettings/ExportTypeTab.component.tsx b/apps/client/src/core/generator/exportSettings/ExportTypeTab.component.tsx
new file mode 100644
index 000000000..75386dce5
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/ExportTypeTab.component.tsx
@@ -0,0 +1,63 @@
+import React from 'react';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import { getGroupedExportTypes } from '~utils/exportTypes';
+import * as styles from './ExportSettings.scss';
+import { ExportSettingsTab } from './ExportSettings.types';
+import { MediumSpinner } from '~components/loaders/loaders';
+
+export type ExportTypeTabProps = {
+ exportType: string;
+ i18n: any;
+ exportTypeI18n: any;
+ onChangeExportType: (exportType: string) => void;
+ onUpdate: any;
+ exportSettingsTab: ExportSettingsTab;
+ SettingsComponent: any;
+ exportTypeSettings: any;
+};
+
+export const ExportTypeTab = ({
+ exportType,
+ i18n,
+ exportTypeI18n,
+ onChangeExportType,
+ SettingsComponent,
+ onUpdate,
+ exportTypeSettings
+}: ExportTypeTabProps) => {
+ let spinnerStyles = styles.spinner;
+ if (SettingsComponent) {
+ spinnerStyles += ` ${styles.fadeOut}`;
+ }
+
+ return (
+
+
+
{i18n.format}
+
+
+ onChangeExportType(value)}
+ />
+
+
+
+
+
+ {SettingsComponent ? (
+
+ ) : null}
+
+
+
+ );
+};
diff --git a/apps/client/src/core/generator/exportSettings/ExportTypeTab.container.ts b/apps/client/src/core/generator/exportSettings/ExportTypeTab.container.ts
new file mode 100644
index 000000000..2da863d5d
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/ExportTypeTab.container.ts
@@ -0,0 +1,39 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { ExportTypeTab, ExportTypeTabProps } from './ExportTypeTab.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+import { getExportTypeSettingsComponent } from '~utils/exportTypes';
+import { ExportTypeFolder } from '@generatedata/plugins';
+
+const mapStateToProps = (
+ state: any
+): Pick<
+ ExportTypeTabProps,
+ 'exportType' | 'exportSettingsTab' | 'i18n' | 'exportTypeI18n' | 'exportTypeSettings' | 'SettingsComponent'
+> => {
+ const exportType = selectors.getExportType(state);
+ let exportTypeI18n = selectors.getExportTypeI18n(state);
+
+ if (exportTypeI18n !== null && exportTypeI18n[exportType]) {
+ exportTypeI18n = exportTypeI18n[exportType];
+ }
+ const exportTypeSettings = selectors.getExportTypeSettings(state);
+ const settings = exportTypeSettings[exportType] ? exportTypeSettings[exportType] : {};
+
+ return {
+ exportType,
+ exportSettingsTab: selectors.getExportSettingsTab(state),
+ i18n: selectors.getCoreI18n(state),
+ exportTypeI18n,
+ exportTypeSettings: settings,
+ SettingsComponent: getExportTypeSettingsComponent(exportType)
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): Pick => ({
+ onChangeExportType: (exportType: string) => dispatch(actions.onSelectExportType(exportType as ExportTypeFolder)),
+ onUpdate: (data: any): any => dispatch(actions.configureExportType(data))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(ExportTypeTab);
diff --git a/apps/client/src/core/generator/exportSettings/PreviewSettingsTab.component.tsx b/apps/client/src/core/generator/exportSettings/PreviewSettingsTab.component.tsx
new file mode 100644
index 000000000..a1b70dfea
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/PreviewSettingsTab.component.tsx
@@ -0,0 +1,84 @@
+import React from 'react';
+import Dropdown from '~components/dropdown/Dropdown';
+import Switch from '@mui/material/Switch';
+import { getArrayOfSize } from '@generatedata/utils/array';
+import * as styles from './ExportSettings.scss';
+import C from '@generatedata/config/constants';
+
+export type PreviewSettingsTabProps = {
+ theme: string;
+ numPreviewRows: number;
+ showLineNumbers: boolean;
+ enableLineWrapping: boolean;
+ previewTextSize: number;
+ onChangeTheme: Function;
+ toggleLineNumbers: Function;
+ toggleLineWrapping: Function;
+ onChangePreviewTextSize: Function;
+ updateNumPreviewRows: Function;
+ i18n: any;
+};
+
+const previewRowOptions = getArrayOfSize(C.MAX_PREVIEW_ROWS - C.MIN_PREVIEW_ROWS + 1).map((_i: any, index: number) => {
+ const rowNum = index + C.MIN_PREVIEW_ROWS;
+ return {
+ value: rowNum,
+ label: rowNum
+ };
+});
+
+export const PreviewSettingsTab = ({
+ theme,
+ numPreviewRows,
+ showLineNumbers,
+ enableLineWrapping,
+ previewTextSize,
+ onChangeTheme,
+ toggleLineNumbers,
+ toggleLineWrapping,
+ onChangePreviewTextSize,
+ updateNumPreviewRows,
+ i18n
+}: PreviewSettingsTabProps) => {
+ return (
+
+
+
{i18n.theme}
+
+ onChangeTheme(value)} />
+
+
+
+
{i18n.showLineNumbers}
+
+ toggleLineNumbers()} />
+
+
+
+
{i18n.lineWrapping}
+
+ toggleLineWrapping()} />
+
+
+
+
{i18n.textSize}
+
+ onChangePreviewTextSize(parseInt(e.target.value, 10))}
+ />
+
+
+
+
{i18n.previewRows}
+
+
+ updateNumPreviewRows(item.value)} options={previewRowOptions} />
+
+
+
+
+ );
+};
diff --git a/apps/client/src/core/generator/exportSettings/PreviewSettingsTab.container.ts b/apps/client/src/core/generator/exportSettings/PreviewSettingsTab.container.ts
new file mode 100644
index 000000000..0dbb8f433
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/PreviewSettingsTab.container.ts
@@ -0,0 +1,29 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { PreviewSettingsTab, PreviewSettingsTabProps } from './PreviewSettingsTab.component';
+import * as generatorSelectors from '~store/generator/generator.selectors';
+import * as generatorActions from '~store/generator/generator.actions';
+
+const mapStateToProps = (state: any): Partial => ({
+ i18n: generatorSelectors.getCoreI18n(state),
+ numPreviewRows: generatorSelectors.getNumPreviewRows(state),
+ showLineNumbers: generatorSelectors.shouldShowLineNumbers(state),
+ enableLineWrapping: generatorSelectors.shouldEnableLineWrapping(state),
+ theme: generatorSelectors.getTheme(state),
+ previewTextSize: generatorSelectors.getPreviewTextSize(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ onChangeTheme: (theme: string): any => dispatch(generatorActions.changeTheme(theme)),
+ toggleLineNumbers: (): any => dispatch(generatorActions.toggleShowLineNumbers()),
+ toggleLineWrapping: (): any => dispatch(generatorActions.toggleLineWrapping()),
+ onChangePreviewTextSize: (size: number): any => dispatch(generatorActions.setPreviewTextSize(size)),
+ updateNumPreviewRows: (numRows: number): any => dispatch(generatorActions.updateNumPreviewRows(numRows))
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+
+ // @ts-ignore
+)(PreviewSettingsTab);
diff --git a/apps/client/src/core/generator/exportSettings/__tests__/ExportSettings.container.test.tsx b/apps/client/src/core/generator/exportSettings/__tests__/ExportSettings.container.test.tsx
new file mode 100644
index 000000000..bcc34bb26
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/__tests__/ExportSettings.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import ExportSettings from '../ExportSettings.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('ExportSettings', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generator/exportSettings/__tests__/ExportTypeTab.container.test.tsx b/apps/client/src/core/generator/exportSettings/__tests__/ExportTypeTab.container.test.tsx
new file mode 100644
index 000000000..e73902f63
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/__tests__/ExportTypeTab.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import ExportTypeTab from '../ExportTypeTab.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('ExportTypeTab', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generator/exportSettings/__tests__/PreviewSettingsTab.container.test.tsx b/apps/client/src/core/generator/exportSettings/__tests__/PreviewSettingsTab.container.test.tsx
new file mode 100644
index 000000000..5caa6b3ba
--- /dev/null
+++ b/apps/client/src/core/generator/exportSettings/__tests__/PreviewSettingsTab.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import PreviewSettingsTab from '../PreviewSettingsTab.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('ExportTypeTab', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generator/generation.types.ts b/apps/client/src/core/generator/generation.types.ts
new file mode 100644
index 000000000..42b52af16
--- /dev/null
+++ b/apps/client/src/core/generator/generation.types.ts
@@ -0,0 +1,103 @@
+import { GenerationTemplate } from '~types/general';
+import { UnchangedGenerationData } from '~types/generator';
+import { CountryDataType } from '@generatedata/types';
+import { CountryNamesMap, DataTypeMap } from '@generatedata/plugins';
+
+export enum GenerationWorkerActionType {
+ Generate = 'GENERATE',
+ ProcessDataTypesOnly = 'PROCESS_DATA_TYPES_ONLY',
+ ProcessExportTypeOnly = 'PROCESS_EXPORT_TYPES_ONLY',
+ Pause = 'PAUSE',
+ Abort = 'ABORT',
+ Continue = 'CONTINUE',
+ SetSpeed = 'SET_SPEED',
+ DataTypesProcessed = 'DATA_TYPES_PROCESSED',
+ ExportTypeProcessed = 'EXPORT_TYPE_PROCESSED'
+}
+
+export type ProcessDataTypesOnlyAction = {
+ data: {
+ action: GenerationWorkerActionType.ProcessDataTypesOnly;
+ numResults: number;
+ batchSize: number;
+ unchanged: UnchangedGenerationData;
+ columns: any; // TODO
+ i18n: any;
+ template: GenerationTemplate;
+ countryNames: CountryNamesMap;
+ workerUtilsUrl: string;
+ dataTypeWorkerMap: DataTypeMap;
+ countryData: CountryDataType;
+ };
+};
+
+export type ProcessExportTypeOnlyAction = {
+ data: {
+ action: GenerationWorkerActionType.ProcessExportTypeOnly;
+ rows: any; // TODO
+ columns: any; // TODO
+ isFirstBatch: boolean;
+ isLastBatch: boolean;
+ currentBatch: number;
+ batchSize: number;
+ numResults: number;
+ exportTypeSettings: any;
+ stripWhitespace: boolean;
+ workerUtilsUrl: string;
+ exportTypeWorkerUrl: string;
+ };
+};
+
+export type GenerateAction = {
+ data: {
+ action: GenerationWorkerActionType.Generate;
+ numResults: number;
+ batchSize: number;
+ speed: number;
+ columns: any; // TODO
+ i18n: any;
+ template: GenerationTemplate;
+ countryData: CountryDataType;
+ countryNames: CountryNamesMap;
+ workerUtilsUrl: string;
+ exportTypeWorkerUrl: string;
+ dataTypeWorkerMap: DataTypeMap;
+ exportTypeSettings: any;
+ stripWhitespace: boolean;
+ logDataBatch: () => void; // TODO
+ };
+};
+
+export type PauseAction = {
+ data: {
+ action: GenerationWorkerActionType.Pause;
+ };
+};
+
+export type AbortAction = {
+ data: {
+ action: GenerationWorkerActionType.Abort;
+ };
+};
+
+export type ContinueAction = {
+ data: {
+ action: GenerationWorkerActionType.Continue;
+ };
+};
+
+export type SetSpeedAction = {
+ data: {
+ action: GenerationWorkerActionType.SetSpeed;
+ speed: number;
+ };
+};
+
+export type GenerationActions =
+ | ProcessDataTypesOnlyAction
+ | ProcessExportTypeOnlyAction
+ | GenerateAction
+ | AbortAction
+ | PauseAction
+ | ContinueAction
+ | SetSpeedAction;
diff --git a/apps/client/src/core/generator/generation.worker.ts b/apps/client/src/core/generator/generation.worker.ts
new file mode 100644
index 000000000..f94675999
--- /dev/null
+++ b/apps/client/src/core/generator/generation.worker.ts
@@ -0,0 +1,159 @@
+import generatorUtils from '../../utils/generatorUtils';
+import { GenerationActions, GenerationWorkerActionType } from '~core/generator/generation.types';
+import { DataTypeBatchGeneratedPayload, DataTypeWorkerInterface, WorkerInterface } from '~types/generator';
+import { DataTypeMap } from '@generatedata/plugins';
+
+const context: Worker = self as any;
+const workerCache: any = {};
+
+context.onmessage = (e: GenerationActions) => {
+ if (e.data.action === GenerationWorkerActionType.Pause) {
+ generatorUtils.pause();
+ } else if (e.data.action === GenerationWorkerActionType.Abort) {
+ generatorUtils.pause();
+ } else if (e.data.action === GenerationWorkerActionType.Continue) {
+ generatorUtils.continue();
+ } else if (e.data.action === GenerationWorkerActionType.SetSpeed) {
+ generatorUtils.setSpeed(e.data.speed);
+ } else if (e.data.action === GenerationWorkerActionType.ProcessDataTypesOnly) {
+ generatorUtils.generateDataTypes({
+ ...e.data,
+ onBatchComplete: (data: DataTypeBatchGeneratedPayload) => {
+ context.postMessage({
+ event: GenerationWorkerActionType.DataTypesProcessed,
+ data
+ });
+ },
+ dataTypeInterface: getDataTypeWorkerInterface(e.data.dataTypeWorkerMap)
+ });
+ } else if (e.data.action === GenerationWorkerActionType.ProcessExportTypeOnly) {
+ generatorUtils.generateExportTypes({
+ ...e.data,
+ settings: e.data.exportTypeSettings,
+ onComplete: (data: string) => {
+ context.postMessage({
+ event: GenerationWorkerActionType.ExportTypeProcessed,
+ data
+ });
+ },
+ exportTypeInterface: getWorkerInterface(e.data.exportTypeWorkerUrl)
+ });
+
+ // this worker action combines the two above for easier usage. Used in the generator where it doesn't need such
+ // granular control as with the preview panel
+ } else if (e.data.action === GenerationWorkerActionType.Generate) {
+ // TODO move whole chunk to generatorUtils ...
+ const {
+ columns,
+ numResults,
+ batchSize,
+ i18n,
+ template,
+ countryNames,
+ workerUtilsUrl,
+ countryData,
+ stripWhitespace,
+ exportTypeSettings,
+ exportTypeWorkerUrl,
+ dataTypeWorkerMap
+ } = e.data;
+
+ const onBatchComplete = ({ completedBatchNum, numGeneratedRows, generatedData }: any): void => {
+ const isLastBatch = numGeneratedRows >= numResults;
+ const displayData = generatedData.map((row: any) => row.map((i: any) => i.display));
+
+ generatorUtils.generateExportTypes({
+ numResults,
+ isFirstBatch: completedBatchNum === 1,
+ isLastBatch,
+ currentBatch: completedBatchNum,
+ batchSize,
+ rows: displayData,
+ columns,
+ stripWhitespace,
+ settings: exportTypeSettings,
+ workerUtilsUrl,
+ onComplete: (data: string) => {
+ context.postMessage({
+ event: GenerationWorkerActionType.ExportTypeProcessed,
+ numGeneratedRows,
+ data
+ });
+ },
+ exportTypeInterface: getWorkerInterface(exportTypeWorkerUrl)
+ });
+ };
+
+ generatorUtils.generateDataTypes({
+ numResults,
+ batchSize,
+ i18n,
+ template,
+ countryNames,
+ workerUtilsUrl,
+ countryData,
+ onBatchComplete,
+ dataTypeInterface: getDataTypeWorkerInterface(dataTypeWorkerMap)
+ });
+ }
+};
+
+// this standardizes the interface for communication between the workers, allowing generatorUtils to work for both
+// workers + backend code
+interface GetWorkerInterface {
+ (workerMap: DataTypeMap): DataTypeWorkerInterface;
+}
+
+// this standardizes the interface for communication between the workers, allowing generatorUtils to work for both
+// workers + backend code
+const getDataTypeWorkerInterface: GetWorkerInterface = (workerMap) => {
+ const workerInterface: DataTypeWorkerInterface = {};
+ Object.keys(workerMap).forEach((plugin) => {
+ // @ts-ignore
+ workerInterface[plugin] = getWorkerInterface(workerMap[plugin]);
+ });
+ return workerInterface;
+};
+
+const getWorkerInterface = (workerPath: string): WorkerInterface => {
+ let workerInterface: WorkerInterface;
+
+ if (workerCache[workerPath]) {
+ workerInterface = workerCache[workerPath];
+ } else {
+ const worker = new Worker(workerPath);
+
+ let onSuccess: any;
+ const onRegisterSuccess = (f: any) => (onSuccess = f);
+ worker.onmessage = (resp: any) => {
+ // TODO typings
+ if (onSuccess) {
+ onSuccess(resp);
+ }
+ };
+
+ let onError: any;
+ const onRegisterError = (f: any) => (onError = f);
+ worker.onerror = (resp: any) => {
+ if (onError) {
+ onError(resp);
+ }
+ };
+
+ workerInterface = {
+ context: 'worker',
+ send: worker.postMessage,
+ onSuccess: onRegisterSuccess,
+ onError: onRegisterError
+ };
+
+ // bind the method to this new worker, not the main generation worker (i.e. this file)
+ workerInterface.send = workerInterface.send.bind(worker);
+
+ workerCache[workerPath] = workerInterface;
+ }
+
+ return workerInterface;
+};
+
+export {};
diff --git a/apps/client/src/core/generator/grid/Grid.component.tsx b/apps/client/src/core/generator/grid/Grid.component.tsx
new file mode 100644
index 000000000..6b72b5590
--- /dev/null
+++ b/apps/client/src/core/generator/grid/Grid.component.tsx
@@ -0,0 +1,119 @@
+import React, { useMemo } from 'react';
+import { DragDropContext, Droppable } from 'react-beautiful-dnd';
+import { useWindowSize } from 'react-hooks-window-size';
+import CloseIcon from '@mui/icons-material/Close';
+import IconButton from '@mui/material/IconButton';
+import * as styles from './Grid.scss';
+import { Tooltip } from '~components/tooltips';
+import { PrimaryButton } from '~components/Buttons.component';
+import { DataRow } from '~store/generator/generator.reducer';
+import { DataTypeFolder } from '@generatedata/plugins';
+import GridRow from './GridRow.container';
+import C from '@generatedata/config/constants';
+import { useMeasure } from '@uidotdev/usehooks';
+
+export type GridProps = {
+ rows: DataRow[];
+ onAddRows: (numRows: number) => void;
+ onSort: (id: string, newIndex: number) => void;
+ toggleGrid: () => void;
+ i18n: any;
+ columnTitle: string;
+ changeSmallScreenVisiblePanel: () => void;
+ showHelpDialog: (section: DataTypeFolder) => void;
+};
+
+const Grid = ({ rows, onAddRows, onSort, i18n, columnTitle, toggleGrid, changeSmallScreenVisiblePanel, showHelpDialog }: GridProps) => {
+ const [numRows, setNumRows] = React.useState(1);
+ const windowSize = useWindowSize();
+ const [measureRef, { width = 0, height = 0 }] = useMeasure();
+
+ let gridSizeClass = '';
+ if (width && width < C.GRID.SMALL_BREAKPOINT) {
+ gridSizeClass = styles.gridSmall;
+ } else if (width && width < C.GRID.MEDIUM_BREAKPOINT) {
+ gridSizeClass = styles.gridMedium;
+ }
+
+ const addRowsBtnLabel = numRows === 1 ? i18n.row : i18n.rows;
+
+ const onClose = (): void => {
+ if (windowSize.width <= C.SMALL_SCREEN_WIDTH) {
+ changeSmallScreenVisiblePanel();
+ } else {
+ toggleGrid();
+ }
+ };
+
+ // to prevent repaints
+ const memoizedDimensions = useMemo(() => ({ width, height }), [width, height]) as { width: number; height: number };
+
+ return (
+ <>
+
+
+ } placement="bottom" arrow>
+
+
+
+
+
+
+
+
+
+
+
+
{rows.length}
+
{i18n.dataType}
+
{columnTitle}
+
{i18n.examples}
+
{i18n.options}
+
+
+
+
+
+
+
+
onSort(draggableId, destination.index)}>
+
+ {(provided: any): any => (
+
+ {rows.map((row, index) => (
+
+ ))}
+ {provided.placeholder}
+
+ )}
+
+
+
+
+
+
+
+ >
+ );
+};
+
+export default Grid;
diff --git a/apps/client/src/core/generator/grid/Grid.container.ts b/apps/client/src/core/generator/grid/Grid.container.ts
new file mode 100644
index 000000000..51386852a
--- /dev/null
+++ b/apps/client/src/core/generator/grid/Grid.container.ts
@@ -0,0 +1,26 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import * as actions from '~store/generator/generator.actions';
+import * as selectors from '~store/generator/generator.selectors';
+import Grid, { GridProps } from './Grid.component';
+import { DataTypeFolder } from '@generatedata/plugins';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ columnTitle: selectors.getExportTypeColumnTitle(state),
+ rows: selectors.getSortedRowsArray(state)
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ onAddRows: (numRows: number): any => dispatch(actions.addRows(numRows)),
+ onSort: (id: string, newIndex: number): any => dispatch(actions.repositionRow(id, newIndex)),
+ toggleGrid: (): any => dispatch(actions.toggleGrid()),
+ changeSmallScreenVisiblePanel: (): any => dispatch(actions.changeSmallScreenVisiblePanel()),
+ showHelpDialog: (dataType: DataTypeFolder): any => dispatch(actions.showHelpDialog(dataType))
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(Grid);
+
+export default container;
diff --git a/apps/client/src/core/generator/grid/Grid.scss b/apps/client/src/core/generator/grid/Grid.scss
new file mode 100644
index 000000000..c11bd5d21
--- /dev/null
+++ b/apps/client/src/core/generator/grid/Grid.scss
@@ -0,0 +1,224 @@
+@use '../../../styles/variables' as c;
+
+.gridWrapper {
+ width: 100%;
+ padding: 0 10px;
+ height: 100%;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ margin: 0 auto;
+}
+
+.gridHeaderWrapper {
+ max-width: 1024px;
+ margin: 15px auto 0;
+}
+
+div.gridHeader {
+ width: 100%;
+ font-weight: bold;
+ color: #666666;
+ min-height: 26px;
+ font-size: 13px;
+ border-bottom: 1px solid black;
+ border-radius: 0;
+
+ .orderCol {
+ font-style: italic;
+ color: #cccccc;
+ padding: 0 0 0 17px;
+ margin-top: 4px;
+ font-size: 10px;
+ }
+}
+
+.smallScreenMode {
+ display: none;
+ position: absolute;
+ right: 10px;
+ margin: 2px 0 0;
+ font-size: 10px;
+ cursor: pointer;
+}
+
+.scrollableGridRows {
+ width: 100%;
+ flex: 1;
+ overflow: scroll;
+ position: relative;
+ padding-top: 4px;
+}
+
+.gridRowsWrapper {
+ max-width: 1024px;
+ margin: 0 auto;
+}
+
+.grid {
+ margin-bottom: 12px;
+}
+
+.gridRow {
+ display: flex;
+ flex-direction: row;
+ min-height: 35px;
+ align-items: flex-start;
+ border-radius: 4px;
+
+ & > div {
+ margin-right: 4px;
+ padding-top: 2px;
+ }
+}
+
+div.orderCol {
+ flex: 0 0 40px;
+ display: flex;
+ margin-top: 6px;
+ padding: 4px 0 2px 2px;
+ color: #999999;
+ svg {
+ color: #cccccc;
+ }
+}
+
+.titleCol {
+ flex: 2;
+ input {
+ width: 100%;
+ }
+}
+
+.dataTypeCol {
+ flex: 2;
+ display: flex;
+ align-items: center;
+}
+
+.dataTypeColDropdown {
+ flex: 1;
+ margin-right: 3px;
+}
+
+.dataTypeHelp {
+ flex: 0 0 15px;
+ font-size: 16px;
+ svg {
+ color: c.$primary-color;
+ cursor: pointer;
+ margin-top: 5px;
+ }
+}
+
+.examplesCol {
+ flex: 2;
+}
+
+.optionsCol {
+ flex: 3;
+}
+
+div.deleteCol,
+div.settingsIconCol {
+ flex: 0 0 30px;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ padding-top: 8px;
+
+ svg {
+ fill: #333333;
+ transition: fill 0.5s ease-in-out;
+ }
+}
+
+div.settingsIconCol svg.disabledBtn {
+ fill: #eeeeee;
+ cursor: default;
+}
+
+div.deleteCol {
+ margin-right: 0;
+ &:hover {
+ svg {
+ fill: #990000;
+ }
+ }
+}
+
+.addRows {
+ display: inline-flex;
+ align-items: center;
+ margin-bottom: 12px;
+ font-size: 12px;
+
+ span,
+ input {
+ margin-right: 6px;
+ }
+
+ input {
+ height: 28px;
+ }
+
+ input {
+ width: 42px;
+ }
+
+ button {
+ font-size: 11px;
+ min-width: 60px;
+ }
+}
+
+div.settingsIconCol {
+ display: none;
+}
+
+.gridSmall {
+ .examplesCol,
+ .optionsCol {
+ display: none;
+ }
+ .smallScreenMode,
+ .settingsIconCol {
+ display: inherit;
+ }
+}
+
+.gridMedium {
+ .examplesCol {
+ display: none;
+ }
+ .smallScreenMode {
+ display: block;
+ }
+}
+
+.disabledBtn {
+ fill: #eeeeee;
+}
+
+.smallScreenSpinner {
+ margin-top: -2px !important;
+}
+
+.smallScreenSettingsTooltip {
+ padding: 10px;
+ width: 280px;
+
+ h4 {
+ margin: 0 0 5px;
+
+ &:nth-child(2) {
+ margin-top: 10px;
+ }
+ }
+}
+
+.gridOverlay {
+ flex: 1;
+ height: 100%;
+ background-color: #f2f2f2;
+}
diff --git a/apps/client/src/core/generator/grid/Grid.scss.d.ts b/apps/client/src/core/generator/grid/Grid.scss.d.ts
new file mode 100644
index 000000000..1670433ea
--- /dev/null
+++ b/apps/client/src/core/generator/grid/Grid.scss.d.ts
@@ -0,0 +1,35 @@
+declare namespace GridScssNamespace {
+ export interface IGridScss {
+ addRows: string;
+ dataTypeCol: string;
+ dataTypeColDropdown: string;
+ dataTypeHelp: string;
+ deleteCol: string;
+ disabledBtn: string;
+ examplesCol: string;
+ grid: string;
+ gridHeader: string;
+ gridHeaderWrapper: string;
+ gridMedium: string;
+ gridOverlay: string;
+ gridRow: string;
+ gridRowsWrapper: string;
+ gridSmall: string;
+ gridWrapper: string;
+ optionsCol: string;
+ orderCol: string;
+ scrollableGridRows: string;
+ settingsIconCol: string;
+ smallScreenMode: string;
+ smallScreenSettingsTooltip: string;
+ smallScreenSpinner: string;
+ titleCol: string;
+ }
+}
+
+declare const GridScssModule: GridScssNamespace.IGridScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: GridScssNamespace.IGridScss;
+};
+
+export = GridScssModule;
diff --git a/apps/client/src/core/generator/grid/GridRow.component.tsx b/apps/client/src/core/generator/grid/GridRow.component.tsx
new file mode 100644
index 000000000..a6d90a0e9
--- /dev/null
+++ b/apps/client/src/core/generator/grid/GridRow.component.tsx
@@ -0,0 +1,204 @@
+import * as React from 'react';
+import { Draggable } from 'react-beautiful-dnd';
+import HighlightOffIcon from '@mui/icons-material/HighlightOff';
+import DragIndicator from '@mui/icons-material/DragIndicator';
+import InfoIcon from '@mui/icons-material/InfoOutlined';
+import Dropdown from '~components/dropdown/Dropdown';
+import { DataRow } from '~store/generator/generator.reducer';
+import { LoadDataTypeBundleOptions } from '~store/generator/generator.actions';
+import { DataTypeFolder } from '@generatedata/plugins';
+import * as styles from './Grid.scss';
+import * as sharedStyles from '../../../styles/shared.scss';
+import TextField from '~components/TextField';
+import { SmallSpinner } from '~components/loaders/loaders';
+import { SmallScreenSettingsIcon } from './SmallScreenSettingsIcon';
+import { DTOptionsMetadata } from '~types/dataTypes';
+import { CountryNamesMap } from '@generatedata/plugins';
+
+const getItemStyle = (isDragging: boolean, draggableStyle: any): React.CSSProperties => {
+ const styles: React.CSSProperties = {
+ ...draggableStyle,
+ userSelect: 'none',
+ margin: '0 0 0 0'
+ };
+ if (isDragging) {
+ styles.background = '#e0ebfd';
+ }
+ return styles;
+};
+
+export type GridRowProps = {
+ row: DataRow;
+ index: number;
+ Example: any;
+ Options: any;
+ i18n: any;
+ countryI18n: any;
+ selectedDataTypeI18n: any;
+ isDataTypeLoaded: boolean;
+ onChangeTitle: (id: string, value: string) => void;
+ onConfigureDataType: (id: string, data: any, metadata?: DTOptionsMetadata) => void;
+ onSelectDataType: (dataType: DataTypeFolder, opts: LoadDataTypeBundleOptions) => void;
+ onRemove: (id: string) => void;
+ dtCustomProps: { [propName: string]: any };
+ dtDropdownOptions: any;
+ gridPanelDimensions: {
+ width: number;
+ height: number;
+ };
+ showHelpDialog: (dataType: DataTypeFolder) => void;
+ isCountryNamesLoading: boolean;
+ isCountryNamesLoaded: boolean;
+ countryNamesMap: CountryNamesMap | null;
+};
+
+const NoExample = ({ coreI18n, emptyColClass }: any) => {coreI18n.noExamplesAvailable}
;
+const NoOptions = ({ coreI18n, emptyColClass }: any) => {coreI18n.noOptionsAvailable}
;
+
+export const GridRow = ({
+ row,
+ index,
+ Example,
+ Options,
+ onRemove,
+ onChangeTitle,
+ onConfigureDataType,
+ onSelectDataType,
+ dtDropdownOptions,
+ i18n,
+ countryI18n,
+ selectedDataTypeI18n,
+ dtCustomProps,
+ gridPanelDimensions,
+ showHelpDialog,
+ isDataTypeLoaded,
+ isCountryNamesLoading,
+ isCountryNamesLoaded,
+ countryNamesMap
+}: GridRowProps) => {
+ let example: any = null;
+ let options: any = null;
+
+ if (isDataTypeLoaded) {
+ if (Example) {
+ example = (
+ onConfigureDataType(row.id, data)}
+ emptyColClass={sharedStyles.emptyCol}
+ gridPanelDimensions={gridPanelDimensions}
+ />
+ );
+ } else {
+ example = ;
+ }
+
+ if (Options) {
+ options = (
+ onConfigureDataType(row.id, data, metadata)}
+ gridPanelDimensions={gridPanelDimensions}
+ emptyColClass={sharedStyles.emptyCol}
+ isCountryNamesLoading={isCountryNamesLoading}
+ isCountryNamesLoaded={isCountryNamesLoaded}
+ countryNamesMap={countryNamesMap}
+ {...dtCustomProps}
+ />
+ );
+ } else {
+ options = ;
+ }
+ } else if (!isDataTypeLoaded && row.dataType) {
+ example = ;
+ }
+
+ const onClickShowHelp = React.useCallback(() => {
+ showHelpDialog(row.dataType as DataTypeFolder);
+ }, [row.dataType]);
+
+ return (
+
+ {(provided: any, snapshot: any): any => {
+ // the title field is always required, regardless of Export Type
+ let titleColError = '';
+ if (row.dataType) {
+ if (row.title.trim() === '') {
+ titleColError = i18n.requiredField;
+ } else if (row.titleError) {
+ titleColError = row.titleError;
+ }
+ }
+
+ return (
+
+
+
+ {index + 1}
+
+
+
onSelectDataType(i.value, { gridRowId: row.id })}
+ options={dtDropdownOptions}
+ />
+ {row.dataType ? : null}
+
+
+ onChangeTitle(row.id, e.target.value)}
+ throttle={false}
+ />
+
+
{example}
+
{options}
+
{
+ if (row.dataType === null) {
+ return;
+ }
+ }}
+ >
+
+
+
onRemove(row.id)}>
+
+
+
+ );
+ }}
+
+ );
+};
diff --git a/apps/client/src/core/generator/grid/GridRow.container.tsx b/apps/client/src/core/generator/grid/GridRow.container.tsx
new file mode 100644
index 000000000..b0aef96cd
--- /dev/null
+++ b/apps/client/src/core/generator/grid/GridRow.container.tsx
@@ -0,0 +1,73 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { GridRow, GridRowProps } from './GridRow.component';
+import { getCustomProps, getDataType, getSortedGroupedDataTypes } from '~utils/dataTypes';
+import * as coreUtils from '../../../utils/coreUtils';
+import { Store } from '~types/general';
+import { DataRow } from '~store/generator/generator.reducer';
+import * as actions from '~store/generator/generator.actions';
+import { LoadDataTypeBundleOptions } from '~store/generator/generator.actions';
+import * as selectors from '~store/generator/generator.selectors';
+import { DataTypeFolder } from '@generatedata/plugins';
+import { DTOptionsMetadata } from '~types/dataTypes';
+
+type OwnProps = {
+ row: DataRow;
+ index: number;
+ gridPanelDimensions: {
+ width: number;
+ height: number;
+ };
+ showHelpDialog: (dataType: DataTypeFolder) => void;
+};
+
+const mapStateToProps = (
+ state: Store,
+ ownProps: OwnProps
+): Pick<
+ GridRowProps,
+ | 'dtDropdownOptions'
+ | 'i18n'
+ | 'countryI18n'
+ | 'selectedDataTypeI18n'
+ | 'Example'
+ | 'Options'
+ | 'isDataTypeLoaded'
+ | 'isCountryNamesLoading'
+ | 'isCountryNamesLoaded'
+ | 'countryNamesMap'
+ | 'dtCustomProps'
+> => {
+ const { dataType } = ownProps.row;
+
+ const { Example, Options, customProps, isLoaded } = getDataType(dataType);
+ const dataTypeI18n = selectors.getDataTypeI18n(state);
+ const dtCustomProps = getCustomProps(customProps, state);
+
+ return {
+ dtDropdownOptions: getSortedGroupedDataTypes(),
+ i18n: selectors.getCoreI18n(state),
+ countryI18n: selectors.getCountryI18n(state),
+ selectedDataTypeI18n: dataTypeI18n && dataType ? dataTypeI18n[dataType] : null,
+ Example,
+ Options,
+ isDataTypeLoaded: isLoaded,
+ isCountryNamesLoading: selectors.isCountryNamesLoading(state),
+ isCountryNamesLoaded: selectors.isCountryNamesLoaded(state),
+ countryNamesMap: coreUtils.getCountryNames(),
+ dtCustomProps,
+ ...ownProps
+ };
+};
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ onRemove: (id: string): any => dispatch(actions.removeRow(id)),
+ onChangeTitle: (id: string, value: string): any => dispatch(actions.onChangeTitle(id, value)),
+ onConfigureDataType: (id: string, data: any, metadata?: DTOptionsMetadata): any =>
+ dispatch(actions.onConfigureDataType(id, data, metadata)),
+ onSelectDataType: (dataType: DataTypeFolder, opts: LoadDataTypeBundleOptions): any => dispatch(actions.onSelectDataType(dataType, opts))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(GridRow);
diff --git a/apps/client/src/core/generator/grid/SmallScreenSettingsIcon.tsx b/apps/client/src/core/generator/grid/SmallScreenSettingsIcon.tsx
new file mode 100644
index 000000000..ccecc8a42
--- /dev/null
+++ b/apps/client/src/core/generator/grid/SmallScreenSettingsIcon.tsx
@@ -0,0 +1,119 @@
+import React from 'react';
+import * as sharedStyles from '../../../styles/shared.scss';
+import { SmallSpinner } from '~components/loaders/loaders';
+import * as styles from './Grid.scss';
+import SettingsIcon from '@mui/icons-material/SettingsOutlined';
+import { HtmlTooltip } from '~components/tooltips';
+import useOnClickOutside from 'use-onclickoutside';
+import C from '@generatedata/config/constants';
+
+export const SmallScreenSettingsIcon = ({
+ id,
+ data,
+ dataType,
+ isDataTypeLoaded,
+ Example,
+ Options,
+ i18n,
+ countryI18n,
+ gridPanelDimensions,
+ selectedDataTypeI18n,
+ onConfigureDataType,
+ dtCustomProps
+}: any): any => {
+ const popoverRef = React.useRef(null);
+ const [open, setOpen] = React.useState(false);
+
+ useOnClickOutside(popoverRef, (e) => {
+ // the `gd-is-portal` part is added in case Data Types use other portal-based content besides react select. If
+ // that's the case, clicking it will always close the tooltip here. So to get around it, give the portal a class
+ // of gd-is-portal. That'll suppress the close event here,
+
+ // @ts-ignore-line
+ if (e.target && e.target.closest && (e.target.closest('.react-select__menu') || e.target.closest('gd-is-portal'))) {
+ return;
+ }
+ setOpen(false);
+ });
+
+ const handleTooltipOpen = (): void => setOpen(true);
+
+ if (!dataType || gridPanelDimensions.width >= C.GRID.MEDIUM_BREAKPOINT) {
+ return null;
+ }
+
+ if (!isDataTypeLoaded) {
+ return ;
+ }
+
+ let example = null;
+ let options = null;
+
+ if (Example !== null) {
+ example = (
+ <>
+ {i18n.example}
+
+ onConfigureDataType(id, data)}
+ emptyColClass={sharedStyles.emptyCol}
+ gridPanelDimensions={gridPanelDimensions}
+ />
+
+ >
+ );
+ }
+
+ if (Options !== null) {
+ const titleStyle = Example === null ? {} : { marginTop: 10 };
+ options = (
+ <>
+ {i18n.options}
+ onConfigureDataType(id, data)}
+ gridPanelDimensions={gridPanelDimensions}
+ emptyColClass={sharedStyles.emptyCol}
+ {...dtCustomProps}
+ />
+ >
+ );
+ }
+
+ if (example === null && options === null) {
+ return ;
+ }
+
+ if (!open) {
+ return ;
+ }
+
+ return (
+
+ {example}
+ {options}
+
+ }
+ arrow
+ >
+
+
+ );
+};
diff --git a/apps/client/src/core/generator/grid/__tests__/Grid.container.test.tsx b/apps/client/src/core/generator/grid/__tests__/Grid.container.test.tsx
new file mode 100644
index 000000000..f2159c2e9
--- /dev/null
+++ b/apps/client/src/core/generator/grid/__tests__/Grid.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import Grid from '../Grid.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('Grid', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generator/grid/__tests__/GridRow.container.test.tsx b/apps/client/src/core/generator/grid/__tests__/GridRow.container.test.tsx
new file mode 100644
index 000000000..5c79b15b0
--- /dev/null
+++ b/apps/client/src/core/generator/grid/__tests__/GridRow.container.test.tsx
@@ -0,0 +1,15 @@
+test.skip('skip', () => {});
+
+// import React from 'react';
+// import GridRow from '../GridRow.container';
+// import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+//
+// describe('GridRow', () => {
+// it('renders', () => {
+// const { baseElement } = renderWithStoreAndRouter(
+//
+// );
+//
+// expect(baseElement.querySelector('div')).toBeTruthy();
+// });
+// });
diff --git a/apps/client/src/core/generator/panelControls/PanelControls.component.tsx b/apps/client/src/core/generator/panelControls/PanelControls.component.tsx
new file mode 100644
index 000000000..f670a10f9
--- /dev/null
+++ b/apps/client/src/core/generator/panelControls/PanelControls.component.tsx
@@ -0,0 +1,134 @@
+import React from 'react';
+import Button from '@mui/material/Button';
+import ButtonGroup from '@mui/material/ButtonGroup';
+import Delete from '@mui/icons-material/Delete';
+import CheckBox from '@mui/icons-material/CheckBox';
+import CheckBoxOutlineBlank from '@mui/icons-material/CheckBoxOutlineBlank';
+import SwapHoriz from '@mui/icons-material/SwapHoriz';
+import SwapVert from '@mui/icons-material/SwapVert';
+import CodeIcon from '@mui/icons-material/Code';
+import { toSentenceCase } from '@generatedata/utils/string';
+import { Tooltip } from '~components/tooltips';
+import { GeneratorLayout } from '@generatedata/types';
+import FeatureToggles from '~core/featureToggles';
+import * as styles from './PanelControls.scss';
+
+export type PanelControlsProps = {
+ className: string;
+ toggleGrid: () => void;
+ togglePreview: () => void;
+ toggleLayout: () => void;
+ showClearPageDialog: () => void;
+ isGridVisible: boolean;
+ isPreviewVisible: boolean;
+ generatorLayout: GeneratorLayout;
+ showDataTemplateDialog: () => void;
+ i18n: any;
+};
+
+export const PanelControls = ({
+ className,
+ toggleGrid,
+ togglePreview,
+ toggleLayout,
+ showClearPageDialog,
+ isGridVisible,
+ isPreviewVisible,
+ generatorLayout,
+ showDataTemplateDialog,
+ i18n
+}: PanelControlsProps) => {
+ const toggleLayoutEnabled = isGridVisible && isPreviewVisible;
+ const GridIcon = isGridVisible ? CheckBox : CheckBoxOutlineBlank;
+ const PreviewIcon = isPreviewVisible ? CheckBox : CheckBoxOutlineBlank;
+ const ToggleDirectionIcon = generatorLayout === 'horizontal' ? SwapHoriz : SwapVert;
+
+ let gridBtnClasses = '';
+ if (isGridVisible) {
+ gridBtnClasses += ` ${styles.btnSelected}`;
+ }
+
+ let previewBtnClasses = '';
+ if (isPreviewVisible) {
+ previewBtnClasses += ` ${styles.btnSelected}`;
+ }
+
+ // Material UI throws an error when it comes to having a tooltip on a disabled button, and within a ButtonGroup
+ // context it messes up the styles wrapping in a like we do elsewhere. So this just constructs
+ // the JSX differently for the enabled/disabled state
+ const getToggleLayoutBtn = () => {
+ if (toggleLayoutEnabled) {
+ return (
+ }
+ arrow
+ disableHoverListener={!toggleLayoutEnabled}
+ disableFocusListener={!toggleLayoutEnabled}
+ >
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+ );
+ };
+
+ const getDataTemplateButton = () => {
+ if (!FeatureToggles.DATA_TEMPLATE_GENERATION_UI) {
+ return null;
+ }
+
+ return (
+
+ } placement="bottom" arrow>
+ } />
+
+
+ );
+ };
+
+ return (
+ <>
+
+ } arrow>
+ }>
+ {i18n.grid}
+
+
+ } arrow>
+ }>
+ {i18n.preview}
+
+
+ {getToggleLayoutBtn()}
+
+ }
+ arrow
+ >
+
+
+
+
+
+ {getDataTemplateButton()}
+ >
+ );
+};
+
+export default PanelControls;
diff --git a/apps/client/src/core/generator/panelControls/PanelControls.container.tsx b/apps/client/src/core/generator/panelControls/PanelControls.container.tsx
new file mode 100644
index 000000000..babd55089
--- /dev/null
+++ b/apps/client/src/core/generator/panelControls/PanelControls.container.tsx
@@ -0,0 +1,26 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import PanelControls, { PanelControlsProps } from './PanelControls.component';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ isGridVisible: selectors.isGridVisible(state),
+ isPreviewVisible: selectors.isPreviewVisible(state),
+ generatorLayout: selectors.getGeneratorLayout(state)
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ toggleGrid: (): any => dispatch(actions.toggleGrid()),
+ togglePreview: (): any => dispatch(actions.togglePreview()),
+ toggleLayout: (): any => dispatch(actions.toggleLayout()),
+ showClearPageDialog: (): any => dispatch(actions.showClearPageDialog()),
+ showDataTemplateDialog: (): any => dispatch(actions.showDataTemplateDialog())
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(PanelControls);
+
+export default container;
diff --git a/apps/client/src/core/generator/panelControls/PanelControls.scss b/apps/client/src/core/generator/panelControls/PanelControls.scss
new file mode 100644
index 000000000..34fe9ebc8
--- /dev/null
+++ b/apps/client/src/core/generator/panelControls/PanelControls.scss
@@ -0,0 +1,62 @@
+.builderControls {
+ margin: 0 6px 0 12px;
+ background-color: #ffffff;
+ box-shadow: 0 1px 1px #cccccc;
+
+ & > button {
+ border: 0;
+ }
+
+ svg {
+ fill: #aaaaaa;
+ }
+
+ :global(.MuiButton-root) {
+ font-size: 11px;
+ color: #444444;
+ }
+}
+
+.dataTemplateControls {
+ margin: 0 6px 0 0;
+ background-color: #ffffff;
+ box-shadow: 0 1px 1px #cccccc;
+
+ & > button {
+ border: 0;
+ }
+
+ svg {
+ fill: #aaaaaa;
+ }
+
+ :global(.MuiButton-startIcon) {
+ margin-right: 0;
+ }
+}
+
+.btnSelected {
+ svg {
+ fill: #aaaaaa;
+ }
+}
+
+.toggleLayoutBtn {
+ border: 1px solid rgba(0, 0, 0, 0.23);
+ border-right: 0;
+ width: 40px;
+
+ button {
+ height: 30px;
+ width: 40px;
+ min-width: inherit;
+ }
+}
+
+:global(.MuiButton-root.Mui-disabled).toggleLayoutBtnDisabled {
+ border: 0;
+
+ svg {
+ fill: #dddddd;
+ }
+}
diff --git a/apps/client/src/core/generator/panelControls/PanelControls.scss.d.ts b/apps/client/src/core/generator/panelControls/PanelControls.scss.d.ts
new file mode 100644
index 000000000..637f1ac36
--- /dev/null
+++ b/apps/client/src/core/generator/panelControls/PanelControls.scss.d.ts
@@ -0,0 +1,16 @@
+declare namespace PanelControlsScssNamespace {
+ export interface IPanelControlsScss {
+ btnSelected: string;
+ builderControls: string;
+ dataTemplateControls: string;
+ toggleLayoutBtn: string;
+ toggleLayoutBtnDisabled: string;
+ }
+}
+
+declare const PanelControlsScssModule: PanelControlsScssNamespace.IPanelControlsScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: PanelControlsScssNamespace.IPanelControlsScss;
+};
+
+export = PanelControlsScssModule;
diff --git a/apps/client/src/core/generator/panelControls/__tests__/PanelControls.container.test.tsx b/apps/client/src/core/generator/panelControls/__tests__/PanelControls.container.test.tsx
new file mode 100644
index 000000000..c04222a97
--- /dev/null
+++ b/apps/client/src/core/generator/panelControls/__tests__/PanelControls.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import PanelControls from '../PanelControls.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('PanelControls', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generator/previewPanel/CodeMirrorWrapper.component.tsx b/apps/client/src/core/generator/previewPanel/CodeMirrorWrapper.component.tsx
new file mode 100644
index 000000000..ea372cf2c
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/CodeMirrorWrapper.component.tsx
@@ -0,0 +1,98 @@
+import React, { useEffect } from 'react';
+import { Controlled as CodeMirror } from 'react-codemirror2';
+import * as coreUtils from '../../../utils/coreUtils';
+import { getCountryData } from '@generatedata/plugins';
+import { GeneratorLayout } from '@generatedata/types';
+import { GenerationWorkerActionType } from '~core/generator/generation.types';
+import C from '@generatedata/config/constants';
+
+export type CodeMirrorWrapperProps = {
+ previewRows: any;
+ columns: any;
+ exportTypeWorkerUrl: string;
+ exportTypeSettings: any;
+ theme: string;
+ codeMirrorMode: string;
+ showLineNumbers: boolean;
+ enableLineWrapping: boolean;
+ generatorLayout: GeneratorLayout;
+};
+
+const CodeMirrorWrapper = (props: CodeMirrorWrapperProps) => {
+ const {
+ previewRows,
+ columns,
+ exportTypeSettings,
+ codeMirrorMode,
+ theme,
+ showLineNumbers,
+ generatorLayout,
+ enableLineWrapping,
+ exportTypeWorkerUrl
+ } = props;
+ const [code, setCode] = React.useState('');
+ const [codeMirrorInstance, setCodeMirrorInstance] = React.useState(null);
+
+ useEffect(() => {
+ if (!columns.length || !previewRows.length) {
+ return;
+ }
+ generatePreviewString(props).then((str: string) => {
+ setCode(str);
+ });
+ }, [previewRows, columns, exportTypeWorkerUrl, exportTypeSettings]);
+
+ useEffect(() => {
+ if (codeMirrorInstance) {
+ codeMirrorInstance.refresh();
+ }
+ }, [generatorLayout]);
+
+ return (
+ setCode(value)}
+ editorDidMount={(editor): void => setCodeMirrorInstance(editor)}
+ options={{
+ mode: codeMirrorMode,
+ theme,
+ lineNumbers: showLineNumbers,
+ lineWrapping: enableLineWrapping,
+ readOnly: true
+ }}
+ />
+ );
+};
+
+export default CodeMirrorWrapper;
+
+export const generatePreviewString = (props: any): Promise => {
+ const { previewRows, columns, exportTypeSettings, exportTypeWorkerUrl } = props;
+ const generationWorker = coreUtils.getGenerationWorker('preview');
+
+ return new Promise((resolve) => {
+ coreUtils.performTask(
+ 'exportTypeWorker',
+ generationWorker,
+ {
+ action: GenerationWorkerActionType.ProcessExportTypeOnly,
+ isFirstBatch: true,
+ isLastBatch: true,
+ batchSize: C.GENERATION_BATCH_SIZE,
+ currentBatch: 1,
+ rows: previewRows,
+ columns,
+ exportTypeSettings,
+ stripWhitespace: false,
+ workerUtilsUrl: coreUtils.getWorkerUtilsUrl(),
+ exportTypeWorkerUrl,
+ countryData: getCountryData()
+ },
+ ({ data }: MessageEvent): void => {
+ if (data.event === GenerationWorkerActionType.ExportTypeProcessed) {
+ resolve(data.data);
+ }
+ }
+ );
+ });
+};
diff --git a/apps/client/src/core/generator/previewPanel/CodeMirrorWrapper.container.ts b/apps/client/src/core/generator/previewPanel/CodeMirrorWrapper.container.ts
new file mode 100644
index 000000000..9b0c5ed60
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/CodeMirrorWrapper.container.ts
@@ -0,0 +1,18 @@
+import { connect } from 'react-redux';
+import * as selectors from '~store/generator/generator.selectors';
+import { Store } from '~types/general';
+import CodeMirrorWrapper, { CodeMirrorWrapperProps } from './CodeMirrorWrapper.component';
+
+const mapStateToProps = (state: Store): CodeMirrorWrapperProps => ({
+ previewRows: selectors.getPreviewRows(state),
+ columns: selectors.getColumns(state),
+ showLineNumbers: selectors.shouldShowLineNumbers(state),
+ enableLineWrapping: selectors.shouldEnableLineWrapping(state),
+ theme: selectors.getTheme(state),
+ codeMirrorMode: selectors.getCodeMirrorMode(state),
+ exportTypeWorkerUrl: selectors.getCurrentExportTypeWorkerUrl(state),
+ exportTypeSettings: selectors.getCurrentExportTypeSettings(state),
+ generatorLayout: selectors.getGeneratorLayout(state)
+});
+
+export default connect(mapStateToProps)(CodeMirrorWrapper);
diff --git a/apps/client/src/core/generator/previewPanel/PreviewPanel.component.tsx b/apps/client/src/core/generator/previewPanel/PreviewPanel.component.tsx
new file mode 100644
index 000000000..b70ef76ed
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/PreviewPanel.component.tsx
@@ -0,0 +1,242 @@
+import React, { useEffect, CSSProperties } from 'react';
+import { useWindowSize } from 'react-hooks-window-size';
+import CloseIcon from '@mui/icons-material/Close';
+import ErrorIcon from '@mui/icons-material/ErrorOutline';
+import ErrorSolidIcon from '@mui/icons-material/Error';
+import Refresh from '@mui/icons-material/Refresh';
+import AddCircle from '@mui/icons-material/AddCircle';
+import IconButton from '@mui/material/IconButton';
+import CodeMirrorWrapper from './CodeMirrorWrapper.container';
+import { Tooltip } from '~components/tooltips';
+import { PreviewPanelButton } from '~components/Buttons.component';
+import Portal from '~components/Portal';
+import { PreviewPanelLoader } from './PreviewPanelLoader.component';
+import PanelButtons from '~core/generator/dataSetHistory/PanelButtons.container';
+import C from '@generatedata/config/constants';
+import * as styles from './PreviewPanel.scss';
+
+export type PreviewPanelProps = {
+ togglePreview: () => void;
+ refreshPreview: () => void;
+ changeSmallScreenVisiblePanel: () => void;
+ exportTypeLoaded: boolean;
+ toggleExportSettings: () => void;
+ closeOverlayPanels: () => void;
+ exportSettingsVisible: boolean;
+ dataSetHistoryVisible: boolean;
+ hasData: boolean;
+ theme: string;
+ previewTextSize: number;
+ exportTypeLabel: string;
+ i18n: any;
+ hasValidExportTypeSettings: boolean;
+ hasBulkActionPending: boolean;
+ initialDependenciesLoaded: boolean; // set once on load
+ previewPanelDependenciesLoaded: boolean; // set every time a user selects
+ initRefresh: any;
+};
+
+const getThemeName = (theme: string): string => `theme${theme.charAt(0).toUpperCase() + theme.slice(1)}`;
+
+const NoResultsBlock = ({ i18n, type }: any) => {
+ const map: any = {
+ invalidSettings: {
+ icon: ErrorSolidIcon,
+ title: i18n.invalidSettings,
+ label: i18n.editExportTypeSettings
+ },
+ noData: {
+ icon: AddCircle,
+ title: i18n.previewPanelNoData,
+ label: i18n.addSomeDataDesc
+ }
+ };
+
+ const Icon = map[type].icon;
+
+ return (
+
+
+
+
+
{map[type].title}
+
{map[type].label}
+
+
+
+ );
+};
+
+const PreviewPanel = ({
+ i18n,
+ theme,
+ togglePreview,
+ hasData,
+ previewTextSize,
+ refreshPreview,
+ toggleExportSettings,
+ exportSettingsVisible,
+ dataSetHistoryVisible,
+ exportTypeLabel,
+ changeSmallScreenVisiblePanel,
+ exportTypeLoaded,
+ initialDependenciesLoaded,
+ hasValidExportTypeSettings,
+ hasBulkActionPending,
+ previewPanelDependenciesLoaded,
+ initRefresh,
+ closeOverlayPanels
+}: PreviewPanelProps): React.ReactNode => {
+ const windowSize = useWindowSize();
+
+ // on load, and after a user loads a data set, rather than retrigger a refresh of the preview panel after every little
+ // change, we do it ONCE when all data types, the export type and locale file have been loaded
+ useEffect(() => {
+ if (!hasBulkActionPending) {
+ return;
+ }
+
+ if (previewPanelDependenciesLoaded) {
+ initRefresh();
+ }
+ }, [hasBulkActionPending, previewPanelDependenciesLoaded]);
+
+ const getNoResults = () => {
+ if (!hasValidExportTypeSettings) {
+ return ;
+ }
+
+ if (hasData) {
+ return null;
+ }
+
+ return ;
+ };
+
+ let closeIconAction: any;
+ let exportTypeLabelBtnAction: any;
+ if (exportSettingsVisible || dataSetHistoryVisible) {
+ closeIconAction = closeOverlayPanels;
+ exportTypeLabelBtnAction = (): void => {};
+ } else {
+ if (windowSize.width < C.SMALL_SCREEN_WIDTH) {
+ closeIconAction = changeSmallScreenVisiblePanel;
+ } else {
+ closeIconAction = togglePreview;
+ }
+ exportTypeLabelBtnAction = toggleExportSettings;
+ }
+
+ const themeName = getThemeName(theme);
+ const previewPanelStyles: CSSProperties = {
+ fontSize: `${previewTextSize}px`,
+ lineHeight: `${previewTextSize + 7}px`
+ };
+
+ let refreshTooltipProps = {};
+ let refreshIconProps = {};
+ if (!hasData || !hasValidExportTypeSettings) {
+ previewPanelStyles.flex = 0;
+ refreshTooltipProps = { disableHoverListener: true };
+ refreshIconProps = { disabled: true };
+ }
+
+ let exportTypeButtonClasses = 'tour-exportTypeBtn';
+ if (!hasValidExportTypeSettings) {
+ exportTypeButtonClasses += ` ${styles.error}`;
+ }
+
+ const getExportSettingsBtn = () => {
+ if (exportSettingsVisible) {
+ return
;
+ }
+
+ if (dataSetHistoryVisible) {
+ return ;
+ }
+
+ return (
+
+ {exportTypeLabel}
+ {!hasValidExportTypeSettings ? : null}
+
+ );
+ };
+
+ const getCodeMirrorPanel = (): React.ReactNode => {
+ if (!hasValidExportTypeSettings || !hasData) {
+ return null;
+ }
+
+ if (!exportTypeLoaded) {
+ return ;
+ }
+
+ return ;
+ };
+
+ if (!initialDependenciesLoaded) {
+ return (
+
+ );
+ }
+
+ const content = (
+
+
+ {getExportSettingsBtn()}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {getNoResults()}
+
+
+ {getCodeMirrorPanel()}
+
+
+ );
+
+ if (exportSettingsVisible || dataSetHistoryVisible) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ return {content}
;
+};
+
+export default PreviewPanel;
diff --git a/apps/client/src/core/generator/previewPanel/PreviewPanel.container.ts b/apps/client/src/core/generator/previewPanel/PreviewPanel.container.ts
new file mode 100644
index 000000000..6a8b254ed
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/PreviewPanel.container.ts
@@ -0,0 +1,48 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import PreviewPanel, { PreviewPanelProps } from './PreviewPanel.component';
+import { isExportTypeValid } from '~utils/exportTypes';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+
+const mapStateToProps = (state: any): Partial => {
+ const exportType = selectors.getExportType(state);
+ const exportTypeSettings = selectors.getExportTypeSettings(state);
+
+ return {
+ i18n: selectors.getCoreI18n(state),
+ theme: selectors.getTheme(state),
+ exportSettingsVisible: selectors.shouldShowExportSettings(state),
+ dataSetHistoryVisible: selectors.shouldShowDataSetHistory(state),
+ previewTextSize: selectors.getPreviewTextSize(state),
+ exportTypeLoaded: selectors.selectedExportTypeLoaded(state),
+ exportTypeLabel: selectors.getExportTypeLabel(state),
+ hasData: selectors.hasData(state),
+ initialDependenciesLoaded: selectors.isInitialDependenciesLoaded(state),
+ hasValidExportTypeSettings: isExportTypeValid(exportType, exportTypeSettings[exportType]),
+ hasBulkActionPending: selectors.hasBulkActionPending(state),
+ previewPanelDependenciesLoaded: selectors.previewPanelDependenciesLoaded(state)
+ };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ togglePreview: (): any => dispatch(actions.togglePreview()),
+ refreshPreview: (): any => dispatch(actions.refreshPreview()),
+ initRefresh: (): any => dispatch(actions.refreshPreview([], actions.setInitialDependenciesLoaded)),
+ toggleExportSettings: (): any => dispatch(actions.toggleExportSettings('previewPanel')),
+ changeSmallScreenVisiblePanel: (): any => dispatch(actions.changeSmallScreenVisiblePanel()),
+ closeOverlayPanels: (): any => {
+ dispatch(actions.hideExportSettings());
+ dispatch(actions.popStashedState());
+ dispatch(actions.hideDataSetHistory());
+ }
+});
+
+const container: any = connect(
+ mapStateToProps,
+ mapDispatchToProps
+
+ // @ts-ignore
+)(PreviewPanel);
+
+export default container;
diff --git a/apps/client/src/core/generator/previewPanel/PreviewPanel.scss b/apps/client/src/core/generator/previewPanel/PreviewPanel.scss
new file mode 100644
index 000000000..99a1775dc
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/PreviewPanel.scss
@@ -0,0 +1,104 @@
+.previewPanel {
+ z-index: 1400;
+ height: 100%;
+
+ h1 {
+ margin: 0;
+ }
+}
+
+:global(#overlayPanelFullScreen) {
+ position: absolute;
+ right: 0;
+ top: 0;
+ left: 400px;
+ bottom: 0;
+ z-index: 1400;
+ & > div {
+ height: 100%;
+ }
+}
+
+.topRow {
+ display: flex;
+ flex-direction: row;
+ width: 100%;
+ justify-content: space-between;
+ padding: 10px;
+}
+
+.controls {
+ z-index: 1300;
+ justify-content: flex-end;
+
+ button {
+ cursor: pointer;
+ &:hover svg {
+ fill: #999999;
+ }
+ }
+}
+
+.previewPanelContent {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.panelContent {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+.preview {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ overflow: hidden;
+
+ & > div,
+ & > div > div {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+ overflow: hidden;
+ }
+
+ .loading {
+ align-items: center;
+ justify-content: center;
+ }
+}
+
+.noResults {
+ color: white;
+ margin: auto;
+ position: relative;
+ text-align: center;
+}
+
+.previewLoading {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ justify-items: center;
+ height: 100%;
+}
+
+button.error {
+ border-color: #dc4040;
+ color: #dc4040;
+
+ svg {
+ fill: #dc4040;
+ margin-left: 4px;
+ margin-top: -1px;
+ }
+
+ &:hover {
+ background-color: #503636;
+ border-color: #dc4040;
+ }
+}
diff --git a/apps/client/src/core/generator/previewPanel/PreviewPanel.scss.d.ts b/apps/client/src/core/generator/previewPanel/PreviewPanel.scss.d.ts
new file mode 100644
index 000000000..59a24bb9b
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/PreviewPanel.scss.d.ts
@@ -0,0 +1,21 @@
+declare namespace PreviewPanelScssNamespace {
+ export interface IPreviewPanelScss {
+ controls: string;
+ error: string;
+ loading: string;
+ noResults: string;
+ panelContent: string;
+ preview: string;
+ previewLoading: string;
+ previewPanel: string;
+ previewPanelContent: string;
+ topRow: string;
+ }
+}
+
+declare const PreviewPanelScssModule: PreviewPanelScssNamespace.IPreviewPanelScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: PreviewPanelScssNamespace.IPreviewPanelScss;
+};
+
+export = PreviewPanelScssModule;
diff --git a/apps/client/src/core/generator/previewPanel/PreviewPanelLoader.component.tsx b/apps/client/src/core/generator/previewPanel/PreviewPanelLoader.component.tsx
new file mode 100644
index 000000000..15a14bf68
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/PreviewPanelLoader.component.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+import CircularProgress from '@mui/material/CircularProgress';
+import styles from './PreviewPanel.scss';
+
+export const PreviewPanelLoader = (): any => (
+
+);
+PreviewPanelLoader.displayName = 'PreviewPanelLoader';
diff --git a/apps/client/src/core/generator/previewPanel/__tests__/CodeMirrorWrapper.container.test.tsx b/apps/client/src/core/generator/previewPanel/__tests__/CodeMirrorWrapper.container.test.tsx
new file mode 100644
index 000000000..2cd2bdf8a
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/__tests__/CodeMirrorWrapper.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import CodeMirrorWrapper from '../CodeMirrorWrapper.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('CodeMirrorWrapper', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/generator/previewPanel/__tests__/PreviewPanel.container.test.tsx b/apps/client/src/core/generator/previewPanel/__tests__/PreviewPanel.container.test.tsx
new file mode 100644
index 000000000..aba639fec
--- /dev/null
+++ b/apps/client/src/core/generator/previewPanel/__tests__/PreviewPanel.container.test.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+import PreviewPanel from '../PreviewPanel.container';
+import { renderWithStoreAndRouter } from '../../../../../tests/testHelpers';
+
+describe('PreviewPanel', () => {
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/header/GeneratorControls.component.tsx b/apps/client/src/core/header/GeneratorControls.component.tsx
new file mode 100644
index 000000000..dd03f7c9d
--- /dev/null
+++ b/apps/client/src/core/header/GeneratorControls.component.tsx
@@ -0,0 +1,224 @@
+import React, { useEffect, useRef, useState } from 'react';
+import { useMutation } from '@apollo/client/react';
+import AutoSizer from 'react-input-autosize';
+import { Divider, IconButton, List, ListItemButton, ListItemText } from '@mui/material';
+import { HtmlTooltip } from '~components/tooltips';
+import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
+import useOnClickOutside from 'use-onclickoutside';
+import { addToast } from '@generatedata/utils/general';
+import DeleteDataSetDialog from '~core/dialogs/deleteDataSet/DeleteDataSetDialog.component';
+import * as queries from '~core/queries';
+import { CurrentDataSet } from '~store/generator/generator.reducer';
+import { useMeasure } from '@uidotdev/usehooks';
+
+export type GeneratorControlsProps = {
+ i18n: any;
+ dataSet: CurrentDataSet;
+ isLoggedIn: boolean;
+ onUpdate: (newDataSetName: string) => void;
+ onSaveDataSet: () => void;
+ onSaveAs: () => void;
+ onShowHistory: () => void;
+ onClearGrid: () => void;
+ showClearPageDialog: () => void;
+ disabled: boolean;
+};
+
+const GeneratorControls = ({
+ i18n,
+ isLoggedIn,
+ dataSet,
+ onUpdate,
+ onSaveDataSet,
+ onSaveAs,
+ onClearGrid,
+ onShowHistory,
+ disabled,
+ showClearPageDialog
+}: GeneratorControlsProps) => {
+ const popoverRef = useRef(null);
+ const inputFieldRef = useRef(null);
+ const [measureRef, { width = 0 }] = useMeasure();
+
+ const { dataSetId, dataSetName } = dataSet;
+ const [dialogVisible, setDeleteDialogVisibility] = useState(false);
+ const [newDataSetName, setNewDataSetName] = useState(dataSetName);
+ const [dataSetMenuVisible, setMenuVisibility] = useState(false);
+
+ useOnClickOutside(popoverRef, () => {
+ setMenuVisibility(false);
+ });
+
+ useEffect(() => {
+ setNewDataSetName(dataSetName);
+ }, [dataSetName]);
+
+ const [deleteDataSet] = useMutation(queries.DELETE_DATA_SET, {
+ refetchQueries: [{ query: queries.GET_DATA_SETS }],
+ onCompleted: () => {
+ setDeleteDialogVisibility(false);
+ onClearGrid();
+ }
+ });
+
+ const onChange = (e: any): void => {
+ setNewDataSetName(e.target.value);
+ };
+
+ const onBlur = (): void => {
+ // we save the new name if they click outside - since there's no save button, we want to make it really simple
+ if (newDataSetName !== dataSetName) {
+ saveNewDataSet();
+ }
+ };
+
+ const saveNewDataSet = (): void => {
+ onUpdate(newDataSetName);
+
+ // @ts-ignore-line
+ inputFieldRef.current?.blur();
+
+ addToast({
+ message: i18n.dataSetNameUpdated,
+ type: 'success'
+ });
+ };
+
+ const onKeyUp = (e: any): void => {
+ if (e.key === 'Escape') {
+ setNewDataSetName(dataSetName);
+ // @ts-ignore-line
+ inputFieldRef.current?.blur();
+ } else if (e.key === 'Enter') {
+ saveNewDataSet();
+ }
+ };
+
+ const getMenu = () => {
+ if (!isLoggedIn || dataSetId === null) {
+ return null;
+ }
+
+ return (
+
+
+
+ {
+ setMenuVisibility(false);
+ onSaveAs();
+ }}
+ >
+
+
+ {
+ setMenuVisibility(false);
+ setDeleteDialogVisibility(true);
+ }}
+ >
+
+
+ {
+ setMenuVisibility(false);
+ onShowHistory();
+ }}
+ >
+
+
+
+
+
+ {
+ setMenuVisibility(false);
+ showClearPageDialog();
+ }}
+ >
+
+
+
+
+ }
+ >
+
+ setMenuVisibility(true)}>
+
+
+
+
+
+ );
+ };
+
+ const onFocus = (e: any): void => {
+ e.preventDefault();
+
+ // this prompts the Save Data Set dialog, which contains a note about having to login/register
+ if (!isLoggedIn || dataSetId === null) {
+ onSaveDataSet();
+ // @ts-ignore-line
+ inputFieldRef.current!.blur();
+ }
+ };
+
+ const maxInputFieldWidth = (width || 0) - 30;
+
+ return (
+ <>
+
+
+ setDeleteDialogVisibility(false)}
+ onDelete={(): any =>
+ deleteDataSet({
+ variables: {
+ dataSetId
+ }
+ })
+ }
+ i18n={i18n}
+ />
+ >
+ );
+};
+
+export default GeneratorControls;
diff --git a/apps/client/src/core/header/GeneratorControls.container.ts b/apps/client/src/core/header/GeneratorControls.container.ts
new file mode 100644
index 000000000..be00fed93
--- /dev/null
+++ b/apps/client/src/core/header/GeneratorControls.container.ts
@@ -0,0 +1,32 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import GeneratorControls, { GeneratorControlsProps } from './GeneratorControls.component';
+import * as selectors from '../store/generator/generator.selectors';
+import * as generatorActions from '~store/generator/generator.actions';
+import * as actions from '~store/account/account.actions';
+import * as mainSelectors from '~store/main/main.selectors';
+import { SaveDataDialogType } from '~store/account/account.reducer';
+
+const mapStateToProps = (state: any): Pick => ({
+ i18n: selectors.getCoreI18n(state),
+ isLoggedIn: mainSelectors.isLoggedIn(state),
+ dataSet: selectors.getCurrentDataSet(state)
+});
+
+const mapDispatchToProps = (
+ dispatch: Dispatch
+): Pick => ({
+ onUpdate: (dataSetName: string): any => dispatch(actions.renameDataSet(dataSetName)),
+ onSaveDataSet: (): any => dispatch(actions.showSaveDataSetDialog(SaveDataDialogType.save)),
+ onSaveAs: (): any => dispatch(actions.showSaveDataSetDialog(SaveDataDialogType.saveAs)),
+ onClearGrid: (): any => dispatch(generatorActions.clearPage()),
+ showClearPageDialog: (): any => dispatch(generatorActions.showClearPageDialog()),
+ onShowHistory: (): any => {
+ dispatch(generatorActions.stashGeneratorState());
+ dispatch(generatorActions.showDataSetHistory());
+ }
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(GeneratorControls);
+
+export default container;
diff --git a/apps/client/src/core/header/Header.component.tsx b/apps/client/src/core/header/Header.component.tsx
new file mode 100644
index 000000000..b508a43c8
--- /dev/null
+++ b/apps/client/src/core/header/Header.component.tsx
@@ -0,0 +1,128 @@
+import React from 'react';
+import { useWindowSize } from 'react-hooks-window-size';
+import Button from '@mui/material/Button';
+import Menu from '@mui/material/Menu';
+import MenuIcon from '@mui/icons-material/Menu';
+import LoginDialog from '../dialogs/login/Login.container';
+import PasswordResetDialog from '../dialogs/passwordReset/PasswordReset.container';
+import GeneratorControls from './GeneratorControls.container';
+import C from '@generatedata/config/constants';
+import HeaderLinks, { MobileLinks } from './HeaderLinks.component';
+import { getHeaderLinks, getGeneratorPageRoute, isGeneratorPage } from '~utils/routeUtils';
+import { AccountType } from '~types/account';
+import sharedStyles from '../../styles/shared.scss';
+import styles from './Header.scss';
+import { GDLocale } from '~types/general';
+
+export type HeaderProps = {
+ locale: GDLocale;
+ currentPage: string;
+ isLoggedIn: boolean;
+ accountType: AccountType;
+ showLoginDialog: () => void;
+ i18n: any;
+ onLogout: () => void;
+ isAuth: boolean;
+ profileImage: string | null;
+ isOnloadAuthDetermined: boolean;
+};
+
+const Header = ({
+ i18n,
+ locale,
+ showLoginDialog,
+ profileImage,
+ isLoggedIn,
+ onLogout,
+ accountType,
+ isOnloadAuthDetermined,
+ currentPage
+}: HeaderProps) => {
+ const [anchorEl, setAnchorEl] = React.useState(null);
+ const windowSize = useWindowSize();
+
+ const handleClick = (event: React.MouseEvent): void => {
+ setAnchorEl(event.currentTarget);
+ };
+
+ const handleClose = (): void => {
+ setAnchorEl(null);
+ };
+
+ const getNav = (): React.ReactNode => {
+ const headerLinks = getHeaderLinks(isLoggedIn, accountType);
+
+ if (windowSize.width <= C.SMALL_SCREEN_WIDTH) {
+ return (
+ <>
+
+
+
+ {isOnloadAuthDetermined && (
+
+ )}
+ >
+ );
+ }
+
+ return (
+
+ {isOnloadAuthDetermined ? (
+
+ ) : null}
+
+ );
+ };
+
+ let generatorControlsClasses = `${sharedStyles.generatorControls} ${styles.controls}`;
+ let generatorControlsDisabled = true;
+ let logoClasses = sharedStyles.mainLogo;
+
+ if (isGeneratorPage(currentPage, locale)) {
+ generatorControlsClasses += ` ${sharedStyles.visible}`;
+ generatorControlsDisabled = false;
+ } else {
+ logoClasses += ` ${sharedStyles.visible}`;
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
{getNav()}
+
+
+
+
+ >
+ );
+};
+
+export default Header;
diff --git a/apps/client/src/core/header/Header.container.ts b/apps/client/src/core/header/Header.container.ts
new file mode 100644
index 000000000..4364b13d2
--- /dev/null
+++ b/apps/client/src/core/header/Header.container.ts
@@ -0,0 +1,29 @@
+import { connect } from 'react-redux';
+import { Dispatch } from 'redux';
+import Header, { HeaderProps } from './Header.component';
+import { GDLocale } from '~types/general';
+import * as selectors from '../store/generator/generator.selectors';
+import * as accountSelectors from '../store/account/account.selectors';
+import * as mainSelectors from '../store/main/main.selectors';
+import * as mainActions from '../store/main/main.actions';
+
+const mapStateToProps = (state: any): Partial => ({
+ locale: mainSelectors.getLocale(state),
+ i18n: selectors.getCoreI18n(state),
+ currentPage: mainSelectors.getCurrentPage(state),
+ isLoggedIn: mainSelectors.isLoggedIn(state),
+ accountType: accountSelectors.getAccountType(state),
+ isOnloadAuthDetermined: mainSelectors.isOnloadAuthDetermined(state),
+ profileImage: accountSelectors.getProfileImage(state)
+});
+
+const mapDispatchToProps = (dispatch: Dispatch): Partial => ({
+ // @ts-ignore - this looks invalid
+ onChangeLocale: (locale: GDLocale) => dispatch(mainActions.selectLocale(locale)),
+ showLoginDialog: (): any => dispatch(mainActions.setLoginDialogVisibility(true)),
+ onLogout: (): any => dispatch(mainActions.logout())
+});
+
+const container: any = connect(mapStateToProps, mapDispatchToProps)(Header);
+
+export default container;
diff --git a/apps/client/src/core/header/Header.scss b/apps/client/src/core/header/Header.scss
new file mode 100644
index 000000000..f694d599b
--- /dev/null
+++ b/apps/client/src/core/header/Header.scss
@@ -0,0 +1,183 @@
+@use '../../styles/variables' as c;
+
+.header {
+ background-image: url('./images/bg.png');
+ padding: 0 10px;
+ height: 60px;
+ display: flex;
+
+ img {
+ user-select: none;
+ }
+
+ & > div {
+ width: 100%;
+ max-width: 1400px;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ }
+
+ h1 {
+ flex: 0;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin: 4px 0 4px;
+ padding-right: 10px;
+ }
+
+ section {
+ float: right;
+ }
+
+ nav {
+ flex: 0 0 auto;
+ display: flex;
+ align-items: center;
+
+ ul {
+ display: flex;
+ align-items: center;
+ list-style: none;
+ font-size: 14px;
+
+ li {
+ display: inline-block;
+ margin: 0 10px;
+
+ &.current a {
+ color: #000000;
+ }
+ }
+ }
+ }
+
+ a {
+ text-decoration: none;
+ }
+
+ .controls {
+ flex: 1;
+ z-index: 1;
+
+ input {
+ color: #222222;
+ background-color: transparent;
+ border: 1px solid transparent;
+ transition: border 150ms ease-in-out;
+ text-overflow: ellipsis;
+
+ &:hover,
+ &:focus {
+ border: 1px solid c.$primary-color;
+ text-overflow: inherit;
+ }
+ &:focus {
+ color: #000000;
+ }
+ }
+ }
+
+ li.divider {
+ color: #c0c0c3;
+ user-select: none;
+ cursor: inherit;
+
+ &:hover {
+ color: #c0c0c3;
+ }
+ }
+}
+
+.headerLinks {
+ margin-right: 20px;
+ animation: fadein 1s;
+
+ li,
+ a {
+ color: #2e3d4e;
+ cursor: pointer;
+ font-size: 13px;
+ transition: color 0.1s ease-in-out;
+ text-decoration: none;
+
+ a:hover,
+ &:hover {
+ color: c.$primary-color;
+ text-decoration: none;
+ }
+ }
+
+ .userAccount {
+ display: flex;
+ align-items: center;
+
+ img {
+ width: 24px;
+ height: 24px;
+ border-radius: 12px;
+ margin-right: 6px;
+ }
+ }
+}
+
+.selected a {
+ color: c.$primary-color;
+}
+
+div ul {
+ li.logoutLink {
+ margin-right: 0;
+
+ button {
+ font-size: 22px;
+ }
+ }
+ li.localeSelector {
+ margin-right: 0;
+ button {
+ font-size: 22px;
+ }
+ }
+}
+
+.title {
+ h5 {
+ display: flex;
+ align-items: center;
+ max-width: calc(100% - 40px);
+ }
+
+ .flags {
+ background-image: url('./images/flags.png');
+ background-repeat: no-repeat;
+ background-size: 120px;
+ flex: 1;
+ width: 122px;
+ height: 22px;
+ display: inline-block;
+ margin-left: 15px;
+ margin-top: 2px;
+ }
+}
+
+div.selectedLocale {
+ background-color: c.$primary-pale-bg;
+ &:hover {
+ background-color: c.$primary-pale-bg;
+ }
+ span {
+ font-weight: bold;
+ }
+}
+
+@keyframes fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
diff --git a/apps/client/src/core/header/Header.scss.d.ts b/apps/client/src/core/header/Header.scss.d.ts
new file mode 100644
index 000000000..979e4bec4
--- /dev/null
+++ b/apps/client/src/core/header/Header.scss.d.ts
@@ -0,0 +1,24 @@
+declare namespace HeaderScssNamespace {
+ export interface IHeaderScss {
+ controls: string;
+ current: string;
+ divider: string;
+ fadein: string;
+ flags: string;
+ header: string;
+ headerLinks: string;
+ localeSelector: string;
+ logoutLink: string;
+ selected: string;
+ selectedLocale: string;
+ title: string;
+ userAccount: string;
+ }
+}
+
+declare const HeaderScssModule: HeaderScssNamespace.IHeaderScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: HeaderScssNamespace.IHeaderScss;
+};
+
+export = HeaderScssModule;
diff --git a/apps/client/src/core/header/HeaderLinks.component.tsx b/apps/client/src/core/header/HeaderLinks.component.tsx
new file mode 100644
index 000000000..d94bbc290
--- /dev/null
+++ b/apps/client/src/core/header/HeaderLinks.component.tsx
@@ -0,0 +1,187 @@
+import React from 'react';
+import { Link } from 'react-router-dom';
+import MenuItem from '@mui/material/MenuItem';
+import IconButton from '@mui/material/IconButton';
+import LogoutIcon from '@mui/icons-material/PowerSettingsNew';
+import { GDCustomHeaderLink, GDHeaderLink, GDLocale } from '~types/general';
+import { getUnlocalizedGeneratorRoute, removeLocale } from '~utils/routeUtils';
+import { Tooltip } from '~components/tooltips';
+import LanguageSelector from './LanguageSelector.container';
+import * as styles from './Header.scss';
+
+export type HeaderLinksProps = {
+ locale: GDLocale;
+ currentPage: string;
+ headerLinks: GDHeaderLink[];
+ showLoginDialog: () => void;
+ onLogout: () => void;
+ profileImage: string | null;
+ i18n: any;
+};
+
+const getClassName = (path: string, currentPage: string): string => {
+ const currentPageWithoutLocale = removeLocale(currentPage);
+ const pathWithSlash = path.charAt(0) === '/' ? path : `/${path}`;
+
+ if (pathWithSlash === '/' && currentPage === '/') {
+ return styles.selected;
+ }
+
+ return currentPageWithoutLocale === pathWithSlash ? styles.selected : '';
+};
+
+const getLink = (link: string, locale: GDLocale): string => (locale === 'en' ? link : `/${locale}${link}`);
+
+export const MobileLinks = ({ locale, currentPage, headerLinks, showLoginDialog, onLogout, i18n }: HeaderLinksProps) => {
+ const links: any = [];
+ const generatorPath = getUnlocalizedGeneratorRoute();
+
+ headerLinks.forEach((headerLink) => {
+ if (typeof headerLink === 'object' && headerLink.path) {
+ const link = headerLink as GDCustomHeaderLink;
+ links.push(
+
+ {i18n[link.labelI18nKey]}
+
+ );
+ } else if (headerLink === 'generator') {
+ links.push(
+
+ {i18n.generator}
+
+ );
+ } else if (headerLink === 'dataSets') {
+ links.push(
+
+ {i18n.dataSets}
+
+ );
+ } else if (headerLink === 'userAccount') {
+ const classes = `${styles.userAccount} ${getClassName('account', currentPage)}`;
+ links.push(
+
+ {i18n.yourAccount}
+
+ );
+ } else if (headerLink === 'accounts') {
+ links.push(
+
+ {i18n.accounts}
+
+ );
+ } else if (headerLink === 'logout') {
+ links.push(
+
+ {i18n.logout}
+
+ );
+ } else if (headerLink === 'register') {
+ links.push(
+
+ {i18n.register}
+
+ );
+ } else if (headerLink === 'loginDialog') {
+ links.push(
+
+ {i18n.login}
+
+ );
+ }
+ });
+
+ return (
+ <>
+ {links}
+
+ >
+ );
+};
+
+export const HeaderLinks = ({ locale, currentPage, headerLinks, showLoginDialog, profileImage, onLogout, i18n }: HeaderLinksProps) => {
+ const links: any = [];
+ const generatorPath = getUnlocalizedGeneratorRoute();
+
+ headerLinks.forEach((headerLink, index) => {
+ if (typeof headerLink === 'object' && headerLink.path) {
+ const link = headerLink as GDCustomHeaderLink;
+ links.push(
+
+ {i18n[link.labelI18nKey]}
+
+ );
+ } else if (headerLink === 'generator') {
+ links.push(
+
+ {i18n.generator}
+
+ );
+ } else if (headerLink === 'separator') {
+ links.push(
+
+ |
+
+ );
+ } else if (headerLink === 'dataSets') {
+ links.push(
+
+ {i18n.dataSets}
+
+ );
+ } else if (headerLink === 'userAccount') {
+ const userImage = profileImage ? : null;
+ const classes = `${styles.userAccount} ${getClassName('account', currentPage)}`;
+
+ links.push(
+
+ {userImage} {i18n.yourAccount}
+
+ );
+ } else if (headerLink === 'accounts') {
+ links.push(
+
+ {i18n.accounts}
+
+ );
+ } else if (headerLink === 'logout') {
+ links.push(
+
+
+
+
+
+
+
+
+
+ );
+ } else if (headerLink === 'register') {
+ links.push(
+
+ {i18n.register}
+
+ );
+ } else if (headerLink === 'loginDialog') {
+ links.push(
+
+ {i18n.login}
+
+ );
+ } else if (headerLink === 'loginPage') {
+ links.push(
+
+ {i18n.login}
+
+ );
+ }
+ });
+
+ return (
+ <>
+ {links}
+
+ >
+ );
+};
+
+export default HeaderLinks;
diff --git a/apps/client/src/core/header/LanguageSelector.component.tsx b/apps/client/src/core/header/LanguageSelector.component.tsx
new file mode 100644
index 000000000..a5bd6ba75
--- /dev/null
+++ b/apps/client/src/core/header/LanguageSelector.component.tsx
@@ -0,0 +1,121 @@
+import React, { useCallback } from 'react';
+import { IconButton, List, ListItemButton, ListItemText, MenuItem } from '@mui/material';
+import LanguageIcon from '@mui/icons-material/Language';
+import { Dialog, DialogContent, DialogTitle } from '~components/dialogs';
+import { Tooltip } from '~components/tooltips';
+import { DialogLoadingSpinner } from '~components/loaders/loaders';
+import { GDLocale } from '~types/general';
+import * as styles from '~core/header/Header.scss';
+import { useNavigate } from 'react-router';
+import clientConfig from '@generatedata/config/clientConfig';
+
+const allLocaleOptions = Object.keys(clientConfig.appSettings.GD_LOCALES).map((shortCode) => ({
+ value: shortCode,
+ label: clientConfig.appSettings.GD_LOCALES[shortCode as GDLocale]
+}));
+
+export type SelectorDialogProps = {
+ visible: boolean;
+ currentLocale: GDLocale;
+ onSelect: (locale: GDLocale, navigate: any) => void;
+ onClose: () => void;
+ loading: boolean;
+ onExited: () => void;
+ i18n: any;
+};
+
+const SelectorDialog = ({ visible, currentLocale, onSelect, onClose, onExited, loading, i18n }: SelectorDialogProps) => {
+ const navigate = useNavigate();
+
+ return (
+
+
+
+ {i18n.selectLanguage}
+
+
+
+
+ {allLocaleOptions.map((currLocale: any) => (
+ onSelect(currLocale.value, navigate)}
+ >
+
+
+ ))}
+
+
+
+
+
+ );
+};
+
+export type LanguageSelectorProps = {
+ i18n: any;
+ locale: GDLocale;
+ isMobile: boolean;
+ availableLocales: GDLocale[];
+ onChangeLocale: (locale: GDLocale, history: any) => void;
+ isLocaleFileLoading: boolean;
+};
+
+const LanguageSelector = ({
+ locale,
+ isMobile = false,
+ availableLocales,
+ onChangeLocale,
+ isLocaleFileLoading,
+ i18n
+}: LanguageSelectorProps) => {
+ const [dialogVisible, setSelectorDialogVisible] = React.useState(false);
+ const [lastI18n, setLastI18n] = React.useState(i18n);
+
+ const onShowSelector = useCallback(() => setSelectorDialogVisible(true), []);
+ const onHideSelector = useCallback(() => setSelectorDialogVisible(false), []);
+ const updateLastI18n = (): void => setLastI18n(i18n);
+
+ if (availableLocales.length < 1) {
+ return null;
+ }
+
+ // note: this actually runs on render as well, but it makes no difference
+ React.useEffect(() => {
+ if (!isLocaleFileLoading) {
+ onHideSelector();
+ }
+ }, [isLocaleFileLoading]);
+
+ const trigger = isMobile ? (
+ {lastI18n.selectLanguage}
+ ) : (
+
+
+
+
+
+
+
+
+
+ );
+
+ return (
+ <>
+ {trigger}
+
+ >
+ );
+};
+
+export default LanguageSelector;
diff --git a/apps/client/src/core/header/LanguageSelector.container.tsx b/apps/client/src/core/header/LanguageSelector.container.tsx
new file mode 100644
index 000000000..acee33925
--- /dev/null
+++ b/apps/client/src/core/header/LanguageSelector.container.tsx
@@ -0,0 +1,25 @@
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import LanguageSelector, { LanguageSelectorProps } from './LanguageSelector.component';
+import { GDLocale } from '~types/general';
+import * as selectors from '../store/generator/generator.selectors';
+import * as mainSelectors from '../store/main/main.selectors';
+import * as mainActions from '../store/main/main.actions';
+import clientConfig from '@generatedata/config/clientConfig';
+
+const mapStateToProps = (state: any): Partial => ({
+ i18n: selectors.getCoreI18n(state),
+ locale: mainSelectors.getLocale(state),
+ availableLocales: clientConfig.appSettings.GD_LOCALES,
+ isLocaleFileLoading: mainSelectors.isLocaleFileLoading(state)
+});
+
+interface DispatchProps {
+ (dispatch: Dispatch, ownProps: any): Partial;
+}
+
+const mapDispatchToProps: DispatchProps = (dispatch) => ({
+ onChangeLocale: (locale: GDLocale, navigate: any): any => dispatch(mainActions.selectLocale(locale, navigate))
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(LanguageSelector);
diff --git a/apps/client/src/core/header/__tests__/GeneratorControls.container.test.tsx b/apps/client/src/core/header/__tests__/GeneratorControls.container.test.tsx
new file mode 100644
index 000000000..1e2b69413
--- /dev/null
+++ b/apps/client/src/core/header/__tests__/GeneratorControls.container.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import GeneratorControls from '../GeneratorControls.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('Header', () => {
+ // need to finish deciding exactly what the header will contain before adding these tests
+
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('div')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/header/__tests__/Header.container.test.tsx b/apps/client/src/core/header/__tests__/Header.container.test.tsx
new file mode 100644
index 000000000..c0c7bd91b
--- /dev/null
+++ b/apps/client/src/core/header/__tests__/Header.container.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import Header from '../Header.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('Header', () => {
+ // need to finish deciding exactly what the header will contain before adding these tests
+
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter();
+
+ expect(baseElement.querySelector('header')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/index.ts b/apps/client/src/core/index.ts
new file mode 100644
index 000000000..6cc31117f
--- /dev/null
+++ b/apps/client/src/core/index.ts
@@ -0,0 +1,55 @@
+/* istanbul ignore file */
+import store from './store';
+import C from '@generatedata/config/constants';
+import * as actions from './store/generator/generator.actions';
+import * as mainActions from './store/main/main.actions';
+import * as selectors from './store/generator/generator.selectors';
+import * as mainSelectors from './store/main/main.selectors';
+import { requestCountryNames } from '~store/generator/generator.actions';
+import { DataTypeFolder } from '@generatedata/plugins';
+import * as coreUtils from '../utils/coreUtils';
+import { initAuthVendors } from '@generatedata/utils/auth';
+import { getCurrentPageLocale } from '@generatedata/utils/lang';
+import '../../_imports';
+
+export const init = (): void => {
+ coreUtils.createGenerationWorker('preview');
+
+ const state = store.getState();
+ const pageLocale = getCurrentPageLocale();
+
+ // initialize any external vendors (Google, Github, Facebook) that are set up for login
+ initAuthVendors();
+
+ const isLoggedIn = mainSelectors.isLoggedIn(state);
+ const exportType = selectors.getExportType(state);
+ const numRows = selectors.getNumRows(state);
+
+ store.dispatch(mainActions.selectLocale(pageLocale));
+ store.dispatch(actions.onSelectExportType(exportType, { shouldRefreshPreviewPanel: false }));
+
+ const loadCountryNames = selectors.currentDataSetNeedsCountryNames(state);
+ if (loadCountryNames) {
+ store.dispatch(requestCountryNames());
+ }
+
+ // if there's a live session, verify the JWT is still valid
+ if (isLoggedIn) {
+ store.dispatch(mainActions.updateRefreshToken());
+ } else {
+ store.dispatch(mainActions.setOnloadAuthDetermined());
+ }
+
+ // if there are no rows, load some!
+ if (numRows === 0) {
+ store.dispatch(actions.addRows(C.NUM_DEFAULT_ROWS));
+ }
+
+ const preloadDataTypes = selectors.getRowDataTypes(state);
+
+ preloadDataTypes.forEach((dataType) =>
+ actions.loadDataTypeBundle(store.dispatch, store.getState, dataType as DataTypeFolder, {
+ shouldRefreshPreviewPanel: false
+ })
+ );
+};
diff --git a/apps/client/src/core/page/Page.component.tsx b/apps/client/src/core/page/Page.component.tsx
new file mode 100644
index 000000000..b767dbc50
--- /dev/null
+++ b/apps/client/src/core/page/Page.component.tsx
@@ -0,0 +1,28 @@
+import React, { PropsWithChildren } from 'react';
+import { Centered, DefaultSpinner } from '~components/loaders/loaders';
+import Header from '../header/Header.container';
+import Footer from '../footer/Footer.container';
+import * as styles from './Page.scss';
+
+export type PageProps = {
+ localeFileLoaded: boolean;
+ children: any;
+};
+
+const Page = ({ localeFileLoaded, children }: PropsWithChildren) => {
+ const content = localeFileLoaded ? (
+ <>
+
+ {children}
+
+ >
+ ) : (
+
+
+
+ );
+
+ return {content}
;
+};
+
+export default Page;
diff --git a/apps/client/src/core/page/Page.container.ts b/apps/client/src/core/page/Page.container.ts
new file mode 100644
index 000000000..bd45788e9
--- /dev/null
+++ b/apps/client/src/core/page/Page.container.ts
@@ -0,0 +1,11 @@
+import { connect } from 'react-redux';
+import Page, { PageProps } from './Page.component';
+import * as mainSelectors from '../store/main/main.selectors';
+
+const mapStateToProps = (state: any): Pick => ({
+ localeFileLoaded: mainSelectors.localeFileLoaded(state)
+});
+
+const mapDispatchToProps = () => ({});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Page);
diff --git a/apps/client/src/core/page/Page.scss b/apps/client/src/core/page/Page.scss
new file mode 100644
index 000000000..99ebac856
--- /dev/null
+++ b/apps/client/src/core/page/Page.scss
@@ -0,0 +1,72 @@
+@use '../../styles/variables' as c;
+@use 'sass:color';
+
+* {
+ box-sizing: border-box;
+}
+
+html,
+body {
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ font-family: 'Open Sans', serif;
+ font-size: 12px;
+}
+
+input,
+select,
+textarea {
+ font-family: 'Open Sans', serif;
+ font-size: 12px;
+}
+
+:global(#root) {
+ height: 100%;
+}
+
+.page {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+}
+
+:global(.page-home) .page {
+ display: inherit;
+
+ header {
+ position: fixed;
+ width: 100%;
+ z-index: 2;
+ }
+
+ .content {
+ padding-top: 60px;
+ }
+}
+
+.content {
+ flex: 1;
+ overflow: hidden;
+}
+
+input,
+textarea,
+select {
+ border-radius: 4px;
+ border: 1px solid #cccccc;
+ padding: 6px;
+ outline: none;
+
+ &:focus {
+ border: 1px solid c.$field-focus-color;
+ }
+}
+
+a {
+ color: c.$primary-color;
+
+ &:hover {
+ color: 1px solid color.adjust(c.$primary-color, $lightness: -30%);
+ }
+}
diff --git a/apps/client/src/core/page/Page.scss.d.ts b/apps/client/src/core/page/Page.scss.d.ts
new file mode 100644
index 000000000..effa7bcce
--- /dev/null
+++ b/apps/client/src/core/page/Page.scss.d.ts
@@ -0,0 +1,13 @@
+declare namespace PageScssNamespace {
+ export interface IPageScss {
+ content: string;
+ page: string;
+ }
+}
+
+declare const PageScssModule: PageScssNamespace.IPageScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: PageScssNamespace.IPageScss;
+};
+
+export = PageScssModule;
diff --git a/apps/client/src/core/page/__tests__/Page.container.test.tsx b/apps/client/src/core/page/__tests__/Page.container.test.tsx
new file mode 100644
index 000000000..993acc711
--- /dev/null
+++ b/apps/client/src/core/page/__tests__/Page.container.test.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import Page from '../Page.container';
+import { renderWithStoreAndRouter } from '../../../../tests/testHelpers';
+
+describe('Page', () => {
+ // need to finish deciding exactly what the header will contain before adding these tests
+
+ it('renders', () => {
+ const { baseElement } = renderWithStoreAndRouter( );
+
+ expect(baseElement.querySelector('header')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/core/queries.ts b/apps/client/src/core/queries.ts
new file mode 100644
index 000000000..258186549
--- /dev/null
+++ b/apps/client/src/core/queries.ts
@@ -0,0 +1,213 @@
+import { gql, TypedDocumentNode } from '@apollo/client';
+
+export type GetAccounts = {
+ accounts: {
+ totalCount: number;
+ results: {
+ accountId: number;
+ accountType: string;
+ accountStatus: string;
+ firstName: string;
+ lastName: string;
+ email: string;
+ country: string;
+ region: string;
+ dateCreated: string;
+ expiryDate: string;
+ lastLoggedIn: string;
+ numRowsGenerated: number;
+ }[];
+ };
+};
+
+export const GET_ACCOUNTS: TypedDocumentNode = gql`
+ query GetAccounts($limit: Int, $offset: Int, $sortCol: String, $sortDir: SortDir, $filterStr: String, $status: String) {
+ accounts(limit: $limit, offset: $offset, sortCol: $sortCol, sortDir: $sortDir, filterStr: $filterStr, status: $status) {
+ totalCount
+ results {
+ accountId
+ accountType
+ accountStatus
+ firstName
+ lastName
+ email
+ country
+ region
+ dateCreated
+ expiryDate
+ lastLoggedIn
+ numRowsGenerated
+ }
+ }
+ }
+`;
+
+export type DeleteAccount = {
+ deleteAccount: {
+ success: boolean;
+ error: string;
+ };
+};
+export const DELETE_ACCOUNT: TypedDocumentNode = gql`
+ mutation DeleteAccount($accountId: ID!) {
+ deleteAccount(accountId: $accountId) {
+ success
+ error
+ }
+ }
+`;
+
+export const GET_DATA_SETS = gql`
+ query GetDataSets($limit: Int, $offset: Int, $sortCol: String, $sortDir: SortDir) {
+ dataSets(limit: $limit, offset: $offset, sortCol: $sortCol, sortDir: $sortDir) {
+ totalCount
+ results {
+ dataSetId
+ dataSetName
+ status
+ dateCreated
+ content
+ numRowsGenerated
+ historyDateCreatedUnix
+ }
+ }
+ }
+`;
+
+type GetDataSetHistory = {
+ dataSetHistory: {
+ totalCount: number;
+ results: {
+ historyId: number;
+ dateCreated: string;
+ content: any;
+ }[];
+ };
+};
+export const GET_DATA_SET_HISTORY: TypedDocumentNode = gql`
+ query GetDataSetHistory($dataSetId: ID!, $limit: Int, $offset: Int) {
+ dataSetHistory(dataSetId: $dataSetId, limit: $limit, offset: $offset) {
+ totalCount
+ results {
+ historyId
+ dateCreated
+ content
+ }
+ }
+ }
+`;
+
+export const DELETE_DATA_SET = gql`
+ mutation DeleteDataSet($dataSetId: ID!) {
+ deleteDataSet(dataSetId: $dataSetId) {
+ success
+ error
+ }
+ }
+`;
+
+export const SAVE_NEW_DATA_SET = gql`
+ mutation SaveNewDataSet($dataSetName: String!, $content: String!) {
+ saveNewDataSet(dataSetName: $dataSetName, content: $content) {
+ success
+ error
+ dataSetId
+ savedDate
+ }
+ }
+`;
+
+type RenameDataSet = {
+ renameDataSet: {
+ success: boolean;
+ error: string;
+ };
+};
+export const RENAME_DATA_SET: TypedDocumentNode = gql`
+ mutation RenameDataSet($dataSetId: ID!, $dataSetName: String!) {
+ renameDataSet(dataSetId: $dataSetId, dataSetName: $dataSetName) {
+ success
+ error
+ }
+ }
+`;
+
+export const SAVE_CURRENT_DATA_SET = gql`
+ mutation SaveDataSet($dataSetId: ID!, $content: String!) {
+ saveDataSet(dataSetId: $dataSetId, content: $content) {
+ success
+ error
+ dataSetId
+ savedDate
+ }
+ }
+`;
+
+export const SAVE_CURRENT_ACCOUNT = gql`
+ mutation UpdateCurrentAccount($firstName: String!, $lastName: String!, $email: String!, $country: String!, $region: String) {
+ updateCurrentAccount(firstName: $firstName, lastName: $lastName, email: $email, country: $country, region: $region) {
+ success
+ }
+ }
+`;
+
+export const SAVE_ACCOUNT = gql`
+ mutation UpdateAccount(
+ $accountId: ID!
+ $accountStatus: AccountStatus
+ $firstName: String!
+ $lastName: String!
+ $email: String!
+ $country: String!
+ $region: String
+ $expiryDate: String
+ ) {
+ updateAccount(
+ accountId: $accountId
+ accountStatus: $accountStatus
+ firstName: $firstName
+ lastName: $lastName
+ email: $email
+ country: $country
+ region: $region
+ expiryDate: $expiryDate
+ ) {
+ success
+ }
+ }
+`;
+
+export const UPDATE_PASSWORD = gql`
+ mutation UpdatePassword($currentPassword: String!, $newPassword: String!) {
+ updatePassword(currentPassword: $currentPassword, newPassword: $newPassword) {
+ success
+ error
+ }
+ }
+`;
+
+export const CREATE_USER_ACCOUNT = gql`
+ mutation CreateUserAccount(
+ $firstName: String!
+ $lastName: String!
+ $email: String!
+ $country: String
+ $region: String
+ $accountStatus: AccountStatus
+ $expiryDate: String
+ $oneTimePassword: String
+ ) {
+ createUserAccount(
+ firstName: $firstName
+ lastName: $lastName
+ email: $email
+ country: $country
+ region: $region
+ accountStatus: $accountStatus
+ expiryDate: $expiryDate
+ oneTimePassword: $oneTimePassword
+ ) {
+ success
+ }
+ }
+`;
diff --git a/apps/client/src/core/store/account/__tests__/account.selectors.test.ts b/apps/client/src/core/store/account/__tests__/account.selectors.test.ts
new file mode 100644
index 000000000..c1f96f0ab
--- /dev/null
+++ b/apps/client/src/core/store/account/__tests__/account.selectors.test.ts
@@ -0,0 +1,60 @@
+import { accountHasChanges } from '~store/account/account.selectors';
+import { cloneObj } from '@generatedata/utils/general';
+
+const defaultState: any = {
+ account: {
+ firstName: 'Pim',
+ lastName: 'Buttercup',
+ email: 'pim@whatever.net',
+ country: 'Mali',
+ region: 'Timbuktu',
+ editingData: {
+ firstName: 'Pim',
+ lastName: 'Buttercup',
+ email: 'pim@whatever.net',
+ country: 'Mali',
+ region: 'Timbuktu'
+ }
+ }
+};
+
+describe('accountHasChanges', () => {
+ it('returns false when there are no changes', () => {
+ expect(accountHasChanges(defaultState)).toEqual(false);
+ });
+
+ it('returns true when there are changes #1', () => {
+ const state: any = cloneObj(defaultState);
+ state.account.editingData.firstName = 'Pimmy';
+
+ expect(accountHasChanges(state)).toEqual(true);
+ });
+
+ it('returns true when there are changes #2', () => {
+ const state: any = cloneObj(defaultState);
+ state.account.editingData.lastName = 'Butter';
+
+ expect(accountHasChanges(state)).toEqual(true);
+ });
+
+ it('returns true when there are changes #3', () => {
+ const state: any = cloneObj(defaultState);
+ state.account.editingData.email = 'different@something.com';
+
+ expect(accountHasChanges(state)).toEqual(true);
+ });
+
+ it('returns true when there are changes #4', () => {
+ const state: any = cloneObj(defaultState);
+ state.account.editingData.country = 'United States';
+
+ expect(accountHasChanges(state)).toEqual(true);
+ });
+
+ it('returns true when there are changes #5', () => {
+ const state: any = cloneObj(defaultState);
+ state.account.editingData.region = 'Another region';
+
+ expect(accountHasChanges(state)).toEqual(true);
+ });
+});
diff --git a/apps/client/src/core/store/account/__tests__/account.store.test.ts b/apps/client/src/core/store/account/__tests__/account.store.test.ts
new file mode 100644
index 000000000..3c7374ea8
--- /dev/null
+++ b/apps/client/src/core/store/account/__tests__/account.store.test.ts
@@ -0,0 +1,185 @@
+import { FlushThunks } from 'redux-testkit';
+import { configureStore, Store } from '@reduxjs/toolkit';
+import { rootReducer } from '../../../../../tests/testHelpers';
+import * as actions from '../account.actions';
+import * as selectors from '../account.selectors';
+import { LOGOUT, setAuthenticationData } from '~store/main/main.actions';
+import { SaveDataDialogType } from '~store/account/account.reducer';
+import { AccountStatus, SelectedAccountTab } from '~types/account';
+import { AuthMethod } from '~types/general';
+
+describe('accounts section', () => {
+ let flushThunks;
+ let store: Store;
+
+ beforeEach(() => {
+ flushThunks = FlushThunks.createMiddleware();
+ store = configureStore({
+ reducer: rootReducer
+ });
+ });
+
+ it('updates an account', () => {
+ // check default account info state
+ expect(selectors.getFirstName(store.getState())).toEqual('');
+ expect(selectors.getLastName(store.getState())).toEqual('');
+ expect(selectors.getEmail(store.getState())).toEqual('');
+ expect(selectors.getCountry(store.getState())).toEqual('');
+ expect(selectors.getRegion(store.getState())).toEqual('');
+
+ store.dispatch(
+ actions.updateAccount({
+ firstName: 'Tom',
+ lastName: 'Jones',
+ email: 'tom@jones.net',
+ country: 'Canada',
+ region: 'British Columbia',
+ numRowsGenerated: 100
+ })
+ );
+
+ // check nothing has been changed in the main state for the user
+ expect(selectors.getFirstName(store.getState())).toEqual('');
+ expect(selectors.getLastName(store.getState())).toEqual('');
+ expect(selectors.getEmail(store.getState())).toEqual('');
+ expect(selectors.getCountry(store.getState())).toEqual('');
+ expect(selectors.getRegion(store.getState())).toEqual('');
+
+ // now confirm the editing data has been updated
+ const editingData = selectors.getEditingData(store.getState());
+ expect(editingData.firstName).toEqual('Tom');
+ expect(editingData.lastName).toEqual('Jones');
+ expect(editingData.email).toEqual('tom@jones.net');
+ expect(editingData.country).toEqual('Canada');
+ expect(editingData.region).toEqual('British Columbia');
+ });
+
+ it('cancelling changes resets back to default values', () => {
+ store.dispatch(
+ actions.updateAccount({
+ firstName: 'Tom',
+ lastName: 'Jones',
+ email: 'tom@jones.net',
+ country: 'Canada',
+ region: 'British Columbia',
+ numRowsGenerated: 100
+ })
+ );
+
+ const editingData = selectors.getEditingData(store.getState());
+ expect(editingData.firstName).toEqual('Tom');
+ expect(editingData.lastName).toEqual('Jones');
+ expect(editingData.email).toEqual('tom@jones.net');
+ expect(editingData.country).toEqual('Canada');
+ expect(editingData.region).toEqual('British Columbia');
+
+ store.dispatch(actions.cancelChanges());
+
+ const newEditingData = selectors.getEditingData(store.getState());
+ expect(newEditingData.firstName).toEqual('');
+ expect(newEditingData.lastName).toEqual('');
+ expect(newEditingData.email).toEqual('');
+ expect(newEditingData.country).toEqual('');
+ expect(newEditingData.region).toEqual('');
+ });
+
+ it('updating the account moves the editing data to the final values', () => {
+ store.dispatch(
+ actions.updateAccount({
+ firstName: 'Tom',
+ lastName: 'Jones',
+ email: 'tom@jones.net',
+ country: 'Canada',
+ region: 'British Columbia',
+ numRowsGenerated: 100
+ })
+ );
+
+ expect(selectors.getFirstName(store.getState())).toEqual('');
+ expect(selectors.getLastName(store.getState())).toEqual('');
+ expect(selectors.getEmail(store.getState())).toEqual('');
+ expect(selectors.getCountry(store.getState())).toEqual('');
+ expect(selectors.getRegion(store.getState())).toEqual('');
+
+ store.dispatch(actions.yourAccountUpdated());
+
+ expect(selectors.getFirstName(store.getState())).toEqual('Tom');
+ expect(selectors.getLastName(store.getState())).toEqual('Jones');
+ expect(selectors.getEmail(store.getState())).toEqual('tom@jones.net');
+ expect(selectors.getCountry(store.getState())).toEqual('Canada');
+ expect(selectors.getRegion(store.getState())).toEqual('British Columbia');
+ });
+
+ it('onChangeTab', () => {
+ // default selected tab
+ expect(selectors.getSelectedTab(store.getState())).toEqual('dataSets');
+
+ // change tabs
+ store.dispatch(actions.onChangeTab(SelectedAccountTab.yourAccount));
+ expect(selectors.getSelectedTab(store.getState())).toEqual(SelectedAccountTab.yourAccount);
+
+ store.dispatch(actions.onChangeTab(SelectedAccountTab.changePassword));
+ expect(selectors.getSelectedTab(store.getState())).toEqual(SelectedAccountTab.changePassword);
+ });
+
+ it('save dialog visibility', () => {
+ expect(selectors.shouldShowSaveDataSetDialog(store.getState())).toEqual(false);
+
+ store.dispatch(actions.showSaveDataSetDialog(SaveDataDialogType.save));
+ expect(selectors.shouldShowSaveDataSetDialog(store.getState())).toEqual(true);
+
+ store.dispatch(actions.hideSaveDataSetDialog());
+ expect(selectors.shouldShowSaveDataSetDialog(store.getState())).toEqual(false);
+ });
+
+ it('auth data gets set', () => {
+ store.dispatch(
+ setAuthenticationData({
+ authMethod: AuthMethod.google,
+ token: '123456',
+ accountId: 5,
+ firstName: 'Jim',
+ lastName: 'Beam',
+ email: 'jim@beam.net',
+ country: 'United States',
+ region: 'Montana',
+ profileImage: 'image-here.jpg',
+ expiryDate: '1000010101001',
+ dateCreated: '1000010101001',
+ accountType: 'admin',
+ accountStatus: AccountStatus.live,
+ numRowsGenerated: 50000
+ })
+ );
+
+ expect(selectors.getFirstName(store.getState())).toEqual('Jim');
+ expect(selectors.getLastName(store.getState())).toEqual('Beam');
+ expect(selectors.getEmail(store.getState())).toEqual('jim@beam.net');
+ expect(selectors.getCountry(store.getState())).toEqual('United States');
+ expect(selectors.getRegion(store.getState())).toEqual('Montana');
+ expect(selectors.getProfileImage(store.getState())).toEqual('image-here.jpg');
+ expect(selectors.getAccountType(store.getState())).toEqual('admin');
+ expect(selectors.getNumGeneratedRows(store.getState())).toEqual(50000);
+ });
+
+ it('logging out clears the pertinent account info', () => {
+ store.dispatch(
+ actions.updateAccount({
+ firstName: 'Tom',
+ lastName: 'Jones',
+ email: 'tom@jones.net',
+ country: 'Canada',
+ region: 'British Columbia',
+ numRowsGenerated: 100
+ })
+ );
+
+ store.dispatch({ type: LOGOUT });
+
+ expect(selectors.getFirstName(store.getState())).toEqual('');
+ expect(selectors.getLastName(store.getState())).toEqual('');
+ expect(selectors.getEmail(store.getState())).toEqual('');
+ expect(selectors.getCountry(store.getState())).toEqual('');
+ expect(selectors.getRegion(store.getState())).toEqual('');
+ });
+});
diff --git a/apps/client/src/core/store/account/account.actions.ts b/apps/client/src/core/store/account/account.actions.ts
new file mode 100644
index 000000000..a18cc4fe1
--- /dev/null
+++ b/apps/client/src/core/store/account/account.actions.ts
@@ -0,0 +1,323 @@
+import { Dispatch } from 'redux';
+import { format } from 'date-fns';
+import { apolloClient } from '../../apolloClient';
+import { AccountEditingData, SaveDataDialogType } from '~store/account/account.reducer';
+import { getEditingData, getSelectedAccountsPageTab } from '~store/account/account.selectors';
+import { getCurrentDataSetId, getDataSetSavePackage } from '~store/generator/generator.selectors';
+import { AccountStatus, SelectedAccountsTab, SelectedAccountTab } from '~types/account';
+import { GDAction } from '~types/general';
+import { addToast } from '@generatedata/utils/general';
+import { getStrings } from '@generatedata/utils/lang';
+import * as queries from '~core/queries';
+import type { RenameDataSet } from '~core/queries';
+import { SET_ONE_TIME_PASSWORD } from '~store/main/main.actions';
+
+export const UPDATE_ACCOUNT = 'UPDATE_ACCOUNT';
+export const updateAccount = (data: AccountEditingData): GDAction => ({
+ type: UPDATE_ACCOUNT,
+ payload: {
+ ...data
+ }
+});
+
+export const CHANGE_ACCOUNT_TAB = 'CHANGE_ACCOUNT_TAB';
+export const onChangeTab = (tab: SelectedAccountTab): GDAction => ({
+ type: CHANGE_ACCOUNT_TAB,
+ payload: {
+ tab
+ }
+});
+
+export const CHANGE_ACCOUNTS_TAB = 'CHANGE_ACCOUNTS_TAB';
+export const onChangeAccountsTab = (tab: SelectedAccountsTab): GDAction => ({
+ type: CHANGE_ACCOUNTS_TAB,
+ payload: {
+ tab
+ }
+});
+
+export const ON_EDIT_ACCOUNT = 'ON_EDIT_ACCOUNT';
+export const editAccount = (accountInfo: any): GDAction => ({
+ type: ON_EDIT_ACCOUNT,
+ payload: {
+ accountInfo
+ }
+});
+
+export const ON_EDIT_YOUR_ACCOUNT = 'ON_EDIT_YOUR_ACCOUNT';
+export const onEditYourAccount = (): GDAction => ({
+ type: ON_EDIT_YOUR_ACCOUNT
+});
+
+export const CANCEL_ACCOUNT_CHANGES = 'CANCEL_ACCOUNT_CHANGES';
+export const cancelChanges = (): GDAction => ({ type: CANCEL_ACCOUNT_CHANGES });
+
+export const YOUR_ACCOUNT_UPDATED = 'YOUR_ACCOUNT_UPDATED';
+export const yourAccountUpdated = (): GDAction => ({
+ type: YOUR_ACCOUNT_UPDATED
+});
+
+// simple garbage collection. If the user happened to be on Edit Account page when navigating away, reset it back
+// to the main Accounts page. Otherwise the data may not still be available when navigating back
+export const onCleanupAccountsPage =
+ (): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const tab = getSelectedAccountsPageTab(getState());
+
+ if (tab === SelectedAccountsTab.editAccount) {
+ dispatch(onChangeAccountsTab(SelectedAccountsTab.accounts));
+ }
+ };
+
+export const saveYourAccount =
+ (): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const i18n = getStrings();
+
+ const { firstName, lastName, email, country, region } = getEditingData(getState());
+
+ await apolloClient.mutate({
+ mutation: queries.SAVE_CURRENT_ACCOUNT,
+ variables: { firstName, lastName, email, country, region }
+ });
+
+ addToast({
+ type: 'success',
+ message: i18n.core.yourAccountUpdated
+ });
+
+ dispatch(yourAccountUpdated());
+ };
+
+// for an admin updating an account
+export const saveAccount =
+ (data: any): any =>
+ async (dispatch: Dispatch): Promise => {
+ const i18n = getStrings();
+ const { accountId, firstName, lastName, email, country, region, disabled, expiryDate } = data;
+
+ const accountStatus = getAccountStatus(disabled, expiryDate);
+
+ let cleanExpiryDate = null;
+ if (expiryDate) {
+ cleanExpiryDate = expiryDate;
+ }
+
+ await apolloClient.mutate({
+ mutation: queries.SAVE_ACCOUNT,
+ variables: {
+ accountId,
+ accountStatus,
+ firstName,
+ lastName,
+ email,
+ country,
+ region,
+ expiryDate: cleanExpiryDate
+ }
+ });
+
+ addToast({
+ type: 'success',
+ message: i18n.core.userAccountUpdated
+ });
+
+ dispatch(onChangeAccountsTab(SelectedAccountsTab.accounts));
+ };
+
+export const clearOneTimePassword = (): GDAction => ({
+ type: SET_ONE_TIME_PASSWORD,
+ payload: {
+ password: ''
+ }
+});
+
+export const savePassword =
+ (currentPassword: string, newPassword: string, onSuccess: () => void, onError: () => void): any =>
+ async (dispatch: Dispatch): Promise => {
+ const i18n = getStrings();
+
+ const response = await apolloClient.mutate({
+ mutation: queries.UPDATE_PASSWORD,
+ variables: { currentPassword, newPassword }
+ });
+
+ if (!response.data.updatePassword.success) {
+ onError();
+ return;
+ }
+
+ dispatch(clearOneTimePassword());
+
+ addToast({
+ type: 'success',
+ message: i18n.core.passwordUpdated
+ });
+
+ onSuccess();
+ };
+
+export const SHOW_SAVE_DATA_SET_DIALOG = 'SHOW_SAVE_DATA_SET_DIALOG';
+export const showSaveDataSetDialog = (dialogType: SaveDataDialogType): GDAction => ({
+ type: SHOW_SAVE_DATA_SET_DIALOG,
+ payload: {
+ dialogType
+ }
+});
+
+export const HIDE_SAVE_DATA_SET_DIALOG = 'HIDE_SAVE_DATA_SET_DIALOG';
+export const hideSaveDataSetDialog = (): GDAction => ({
+ type: HIDE_SAVE_DATA_SET_DIALOG
+});
+
+export const LOAD_DATA_SETS = 'LOAD_DATA_SETS';
+export const SET_CURRENT_DATA_SET = 'SET_CURRENT_DATA_SET';
+
+export const saveNewDataSet =
+ (dataSetName: string): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const i18n = getStrings();
+ const data: any = getDataSetSavePackage(getState());
+
+ const response = await apolloClient.mutate({
+ mutation: queries.SAVE_NEW_DATA_SET,
+ variables: {
+ dataSetName,
+ content: JSON.stringify(data)
+ }
+ });
+
+ if (response.data.saveNewDataSet.success) {
+ dispatch({
+ type: SET_CURRENT_DATA_SET,
+ payload: {
+ dataSetName,
+ dataSetId: parseInt(response.data.saveNewDataSet.dataSetId, 10)
+ }
+ });
+
+ dispatch(hideSaveDataSetDialog());
+
+ addToast({
+ type: 'success',
+ message: i18n.core.dataSetSaved
+ });
+ }
+
+ // TODO error handling
+ };
+
+export const UPDATE_CURRENT_DATA_SET_LAST_SAVED = 'UPDATE_CURRENT_DATA_SET_LAST_SAVED';
+export const updateCurrentDataSetLastSaved = (lastSaved: any): GDAction => ({
+ type: UPDATE_CURRENT_DATA_SET_LAST_SAVED,
+ payload: {
+ lastSaved
+ }
+});
+
+export const saveCurrentDataSet =
+ (successMsg?: string): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const i18n = getStrings();
+
+ const successMessage = successMsg || i18n.core.dataSetSaved;
+ const state = getState();
+ const data: any = getDataSetSavePackage(state);
+ const dataSetId = getCurrentDataSetId(state);
+
+ const response = await apolloClient.mutate({
+ mutation: queries.SAVE_CURRENT_DATA_SET,
+ variables: {
+ dataSetId,
+ content: JSON.stringify(data)
+ }
+ });
+
+ if (response.data.saveDataSet.success) {
+ dispatch(updateCurrentDataSetLastSaved(response.data.saveDataSet.savedDate));
+
+ addToast({
+ type: 'success',
+ message: successMessage
+ });
+ }
+ };
+
+export const UPDATE_CURRENT_DATA_SET_NAME = 'UPDATE_CURRENT_DATA_SET_NAME';
+export const renameDataSet =
+ (dataSetName: string): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const dataSetId = getCurrentDataSetId(getState());
+
+ const { data } = await apolloClient.mutate({
+ mutation: queries.RENAME_DATA_SET,
+ variables: {
+ dataSetId,
+ dataSetName
+ }
+ });
+
+ if ((data as RenameDataSet).renameDataSet.success) {
+ dispatch({
+ type: UPDATE_CURRENT_DATA_SET_NAME,
+ payload: {
+ dataSetName
+ }
+ });
+ }
+ };
+
+export const createAccount =
+ (data: any) =>
+ async (dispatch: Dispatch): Promise => {
+ const i18n = getStrings();
+ const { firstName, lastName, email, country, region, disabled, expiry, expiryDate, oneTimePassword } = data;
+ const accountStatus = getAccountStatus(disabled, expiryDate);
+
+ const expiryDateValue = expiry && expiry !== 'none' ? expiryDate.toString() : null;
+ const response = await apolloClient.mutate({
+ mutation: queries.CREATE_USER_ACCOUNT,
+ variables: {
+ firstName,
+ lastName,
+ email,
+ country,
+ region,
+ accountStatus,
+ oneTimePassword,
+ expiryDate: expiryDateValue
+ }
+ });
+
+ if (response.data.createUserAccount.success) {
+ dispatch(onChangeAccountsTab(SelectedAccountsTab.accounts));
+
+ addToast({
+ type: 'success',
+ message: i18n.core.accountCreatedDesc
+ });
+ } else {
+ addToast({
+ type: 'error',
+ message: i18n.core.errorCreatingAccount
+ });
+ }
+ };
+
+export const getAccountStatus = (disabled: boolean, expiryDate: number): AccountStatus => {
+ let accountStatus = AccountStatus.live;
+ if (disabled) {
+ accountStatus = AccountStatus.disabled;
+ } else {
+ // check the expiry date hasn't already passed
+ if (expiryDate) {
+ const now = Number(format(new Date(), 'T'));
+ const expiryDateNum = parseInt(expiryDate.toString()); // ensure it's a number
+
+ if (expiryDateNum < now) {
+ accountStatus = AccountStatus.expired;
+ }
+ }
+ }
+ return accountStatus;
+};
diff --git a/apps/client/src/core/store/account/account.reducer.ts b/apps/client/src/core/store/account/account.reducer.ts
new file mode 100644
index 000000000..12814e0c1
--- /dev/null
+++ b/apps/client/src/core/store/account/account.reducer.ts
@@ -0,0 +1,189 @@
+import { AnyAction } from 'redux';
+import { produce } from 'immer';
+import * as mainActions from '../main/main.actions';
+import * as actions from '../account/account.actions';
+import * as packetActions from '../packets/packets.actions';
+import { AccountStatus, AccountType, SelectedAccountsTab, SelectedAccountTab } from '~types/account';
+
+// use for both Edit Account, Your Account. Your Account only uses a subset of the fields.
+export type AccountEditingData = {
+ firstName: string;
+ lastName: string;
+ email: string;
+ country: string;
+ region: string;
+ oneTimePassword?: string;
+ numRowsGenerated: number;
+ expiryDate?: number;
+ disabled?: boolean;
+ accountId?: number;
+ status?: AccountStatus;
+};
+
+export enum SaveDataDialogType {
+ save = 'save',
+ saveAs = 'saveAs'
+}
+
+export type AccountState = {
+ showSaveDataSetDialog: boolean;
+ saveDataDialogType: SaveDataDialogType;
+ oneTimePassword: string;
+ firstName: string;
+ lastName: string;
+ email: string;
+ country: string;
+ region: string;
+ expiryDate: string;
+ accountType: AccountType;
+ profileImage: string | null;
+ numRowsGenerated: number;
+ dataSets: any[];
+ selectedTab: SelectedAccountTab;
+ selectedAccountsTab: SelectedAccountsTab;
+ editingData: AccountEditingData;
+};
+
+export const initialState: AccountState = {
+ showSaveDataSetDialog: false,
+ saveDataDialogType: SaveDataDialogType.save,
+ oneTimePassword: '',
+ firstName: '',
+ lastName: '',
+ email: '',
+ country: '',
+ region: '',
+ expiryDate: '',
+ accountType: 'user',
+ profileImage: null,
+ numRowsGenerated: 0,
+ dataSets: [],
+ selectedTab: SelectedAccountTab.dataSets,
+ selectedAccountsTab: SelectedAccountsTab.accounts,
+ editingData: {
+ firstName: '',
+ lastName: '',
+ email: '',
+ country: '',
+ region: '',
+ numRowsGenerated: 0,
+ expiryDate: undefined,
+ disabled: undefined,
+ accountId: undefined,
+ status: undefined
+ }
+};
+
+export const reducer = produce((draft: AccountState, action: AnyAction) => {
+ switch (action.type) {
+ case mainActions.LOGOUT:
+ case mainActions.RESET_STORE:
+ Object.keys(initialState).forEach((key) => {
+ // @ts-ignore-line
+ draft[key] = initialState[key];
+ });
+ break;
+
+ case mainActions.SET_AUTHENTICATION_DATA: {
+ const { firstName, lastName, expiryDate, accountType, email, country, region, profileImage, numRowsGenerated } = action.payload;
+ draft.firstName = firstName;
+ draft.lastName = lastName;
+ draft.email = email;
+ draft.country = country;
+ draft.region = region;
+ draft.expiryDate = expiryDate;
+ draft.accountType = accountType;
+ draft.profileImage = profileImage;
+ draft.numRowsGenerated = numRowsGenerated;
+ break;
+ }
+
+ case mainActions.SET_ONE_TIME_PASSWORD:
+ draft.oneTimePassword = action.payload.password;
+ break;
+
+ case actions.CHANGE_ACCOUNT_TAB:
+ draft.selectedTab = action.payload.tab;
+ break;
+
+ case actions.ON_EDIT_YOUR_ACCOUNT:
+ draft.editingData = {
+ firstName: draft.firstName,
+ lastName: draft.lastName,
+ email: draft.email,
+ country: draft.country,
+ region: draft.region,
+ numRowsGenerated: 0,
+ expiryDate: undefined,
+ disabled: undefined,
+ accountId: undefined,
+ status: undefined
+ };
+ break;
+
+ case actions.CHANGE_ACCOUNTS_TAB:
+ draft.selectedAccountsTab = action.payload.tab;
+ break;
+
+ case actions.UPDATE_ACCOUNT:
+ draft.editingData = action.payload;
+ break;
+
+ case actions.CANCEL_ACCOUNT_CHANGES:
+ draft.editingData = {
+ ...draft.editingData,
+ firstName: draft.firstName,
+ lastName: draft.lastName,
+ email: draft.email,
+ country: draft.country,
+ region: draft.region
+ };
+ break;
+
+ case actions.YOUR_ACCOUNT_UPDATED:
+ draft.firstName = draft.editingData.firstName;
+ draft.lastName = draft.editingData.lastName;
+ draft.email = draft.editingData.email;
+ draft.country = draft.editingData.country;
+ draft.region = draft.editingData.region;
+ break;
+
+ case actions.SHOW_SAVE_DATA_SET_DIALOG:
+ draft.showSaveDataSetDialog = true;
+ draft.saveDataDialogType = action.payload.dialogType;
+ break;
+
+ case actions.HIDE_SAVE_DATA_SET_DIALOG:
+ draft.showSaveDataSetDialog = false;
+ break;
+
+ case actions.LOAD_DATA_SETS:
+ draft.dataSets = action.payload.dataSets;
+ break;
+
+ case actions.ON_EDIT_ACCOUNT:
+ const { accountId, accountStatus, firstName, lastName, email, country, region, expiryDate, numRowsGenerated } =
+ action.payload.accountInfo;
+
+ draft.selectedAccountsTab = SelectedAccountsTab.editAccount;
+ draft.editingData = {
+ accountId,
+ disabled: accountStatus === AccountStatus.disabled,
+ status: accountStatus,
+ firstName,
+ lastName,
+ email,
+ country,
+ region,
+ expiryDate,
+ numRowsGenerated
+ };
+ break;
+
+ case packetActions.UPDATE_TOTAL_GENERATION_COUNT:
+ draft.numRowsGenerated += action.payload.count;
+ break;
+ }
+}, initialState);
+
+export default reducer;
diff --git a/apps/client/src/core/store/account/account.selectors.ts b/apps/client/src/core/store/account/account.selectors.ts
new file mode 100644
index 000000000..240fa6530
--- /dev/null
+++ b/apps/client/src/core/store/account/account.selectors.ts
@@ -0,0 +1,36 @@
+import { Store } from '~types/general';
+import { AccountType, SelectedAccountTab, SelectedAccountsTab } from '~types/account';
+import { AccountEditingData, SaveDataDialogType } from '~store/account/account.reducer';
+import { createSelector } from 'reselect';
+
+export const getFirstName = (state: Store): string => state.account.firstName;
+export const getLastName = (state: Store): string => state.account.lastName;
+export const getEmail = (state: Store): string => state.account.email;
+export const getCountry = (state: Store): string => state.account.country;
+export const getRegion = (state: Store): string => state.account.region;
+export const getAccountType = (state: Store): AccountType => state.account.accountType;
+export const getNumGeneratedRows = (state: Store): number => state.account.numRowsGenerated;
+export const getProfileImage = (state: Store): string | null => state.account.profileImage;
+export const getSelectedTab = (state: Store): SelectedAccountTab => state.account.selectedTab;
+export const getEditingData = (state: Store): AccountEditingData => state.account.editingData;
+export const shouldShowSaveDataSetDialog = (state: Store): boolean => state.account.showSaveDataSetDialog;
+export const getSaveDataDialogType = (state: Store): SaveDataDialogType => state.account.saveDataDialogType;
+export const getDataSets = (state: Store): any[] => state.account.dataSets;
+export const getSelectedAccountsPageTab = (state: Store): SelectedAccountsTab => state.account.selectedAccountsTab;
+export const getOneTimePassword = (state: Store): string => state.account.oneTimePassword;
+export const getExpiryDate = (state: Store): string => state.account.expiryDate;
+
+export const accountHasChanges = createSelector(
+ getFirstName,
+ getLastName,
+ getEmail,
+ getCountry,
+ getRegion,
+ getEditingData,
+ (firstName, lastName, email, country, region, editingData): boolean =>
+ firstName !== editingData.firstName ||
+ lastName !== editingData.lastName ||
+ email !== editingData.email ||
+ country !== editingData.country ||
+ region !== editingData.region
+);
diff --git a/apps/client/src/core/store/generator/__tests__/generator.selectors.test.ts b/apps/client/src/core/store/generator/__tests__/generator.selectors.test.ts
new file mode 100644
index 000000000..a1ec29e71
--- /dev/null
+++ b/apps/client/src/core/store/generator/__tests__/generator.selectors.test.ts
@@ -0,0 +1,121 @@
+import { shouldGeneratePreviewRows } from '../generator.selectors';
+import { getTestState } from '../../../../../tests/testHelpers';
+import { Store } from '~types/general';
+
+describe('shouldGeneratePreviewRows', () => {
+ let state: Store;
+ beforeEach(() => {
+ state = getTestState();
+ });
+
+ it('return false when there\'s no data type rows', () => {
+ state.generator.loadedExportTypes.JSON = true;
+ state.main.localeFileLoaded = true;
+ expect(shouldGeneratePreviewRows(state)).toEqual(false);
+ });
+
+ it('return false when there is a data type but it\'s not loaded yet', () => {
+ state.generator.loadedExportTypes.JSON = true;
+ state.main.localeFileLoaded = true;
+ state.generator.rows = {
+ abc: {
+ id: 'abc',
+ dataType: 'Names',
+ title: 'Test',
+ titleError: null,
+ data: {}
+ }
+ };
+ state.generator.loadedDataTypes.Names = false;
+ expect(shouldGeneratePreviewRows(state)).toEqual(false);
+ });
+
+ it('return true when there is a single data type that\'s been loaded', () => {
+ state.generator.loadedExportTypes.JSON = true;
+ state.main.localeFileLoaded = true;
+ state.generator.rows = {
+ abc: {
+ id: 'abc',
+ dataType: 'Names',
+ title: 'Test',
+ titleError: null,
+ data: {}
+ }
+ };
+ state.generator.loadedDataTypes.Names = true;
+ expect(shouldGeneratePreviewRows(state)).toEqual(true);
+ });
+
+ it('with multiple data types only returns true when they\'re all loaded', () => {
+ state.generator.loadedExportTypes.JSON = true;
+ state.main.localeFileLoaded = true;
+ state.generator.rows = {
+ abc: {
+ id: 'abc',
+ dataType: 'Names',
+ title: 'Test 1',
+ titleError: null,
+ data: {}
+ },
+ def: {
+ id: 'def',
+ dataType: 'Phone',
+ title: 'Test 2',
+ titleError: null,
+ data: {}
+ },
+ ghi: {
+ id: 'ghi',
+ dataType: 'City',
+ title: 'Test 3',
+ titleError: null,
+ data: {}
+ }
+ };
+ state.generator.loadedDataTypes.Names = true;
+ expect(shouldGeneratePreviewRows(state)).toEqual(false);
+
+ state.generator.loadedDataTypes.City = true;
+ const newState1 = JSON.parse(JSON.stringify(state));
+ expect(shouldGeneratePreviewRows(newState1)).toEqual(false);
+
+ newState1.generator.loadedDataTypes.Phone = true;
+ const newState2 = JSON.parse(JSON.stringify(newState1));
+ expect(shouldGeneratePreviewRows(newState2)).toEqual(true);
+ });
+
+ it('should return false when the current export type isn\'t loaded yet', () => {
+ state.generator.loadedExportTypes.JSON = false; // just to be explicit
+ state.main.localeFileLoaded = true;
+ state.generator.rows = {
+ abc: {
+ id: 'abc',
+ dataType: 'Names',
+ title: 'Test',
+ titleError: null,
+ data: {}
+ }
+ };
+ state.generator.loadedDataTypes.Names = true;
+ expect(shouldGeneratePreviewRows(state)).toEqual(false);
+ });
+
+ it('should return false when all row data has already been generated', () => {
+ state.generator.loadedExportTypes.JSON = true;
+ state.main.localeFileLoaded = true;
+ state.generator.rows = {
+ abc: {
+ id: 'abc',
+ dataType: 'Names',
+ title: 'Test',
+ titleError: null,
+ data: {}
+ }
+ };
+ state.generator.dataTypePreviewData = {
+ abc: 'random data here'
+ };
+ state.generator.loadedDataTypes.Names = true;
+ expect(shouldGeneratePreviewRows(state)).toEqual(false);
+ });
+});
diff --git a/apps/client/src/core/store/generator/__tests__/generator.test.ts b/apps/client/src/core/store/generator/__tests__/generator.test.ts
new file mode 100644
index 000000000..baa17a5e9
--- /dev/null
+++ b/apps/client/src/core/store/generator/__tests__/generator.test.ts
@@ -0,0 +1,237 @@
+import { createStore, combineReducers } from 'redux';
+import * as actions from '../generator.actions';
+import * as selectors from '../generator.selectors';
+import generatorReducer from '../generator.reducer';
+import mainReducer from '../../main/main.reducer';
+import { GeneratorLayout } from '@generatedata/types';
+
+describe('generator section', () => {
+ let store: any;
+ beforeEach(() => {
+ store = createStore(
+ combineReducers({
+ generator: generatorReducer,
+ main: mainReducer
+ })
+ );
+ });
+
+ it('the grid panel is visible by default', () => {
+ expect(selectors.isGridVisible(store.getState())).toEqual(true);
+ });
+
+ it('toggling GRID updates the store', () => {
+ store.dispatch(actions.toggleGrid());
+ expect(selectors.isGridVisible(store.getState())).toEqual(false);
+
+ store.dispatch(actions.toggleGrid());
+ expect(selectors.isGridVisible(store.getState())).toEqual(true);
+ });
+
+ it('the preview panel is visible by default', () => {
+ expect(selectors.isPreviewVisible(store.getState())).toEqual(true);
+ });
+
+ it('toggling PREVIEW updates the store', () => {
+ store.dispatch(actions.togglePreview());
+ expect(selectors.isPreviewVisible(store.getState())).toEqual(false);
+
+ store.dispatch(actions.togglePreview());
+ expect(selectors.isPreviewVisible(store.getState())).toEqual(true);
+ });
+
+ it('hiding the grid when preview is closed reopens preview', () => {
+ // close the preview panel first
+ store.dispatch(actions.togglePreview());
+ expect(selectors.isPreviewVisible(store.getState())).toEqual(false);
+
+ // now close the grid
+ store.dispatch(actions.toggleGrid());
+ expect(selectors.isGridVisible(store.getState())).toEqual(false);
+
+ // now check preview is back open again
+ expect(selectors.isPreviewVisible(store.getState())).toEqual(true);
+ });
+
+ it('hiding the preview panel when grid is closed reopens grid', () => {
+ // close the grid panel first
+ store.dispatch(actions.toggleGrid());
+ expect(selectors.isGridVisible(store.getState())).toEqual(false);
+
+ // now close the preview panel
+ store.dispatch(actions.togglePreview());
+ expect(selectors.isPreviewVisible(store.getState())).toEqual(false);
+
+ // now check grid is back open again
+ expect(selectors.isGridVisible(store.getState())).toEqual(true);
+ });
+
+ it('layout is horizontal by default', () => {
+ expect(selectors.getGeneratorLayout(store.getState())).toEqual(GeneratorLayout.horizontal);
+ });
+
+ it('toggling the layout', () => {
+ store.dispatch(actions.toggleLayout());
+ expect(selectors.getGeneratorLayout(store.getState())).toEqual(GeneratorLayout.vertical);
+
+ store.dispatch(actions.toggleLayout());
+ expect(selectors.getGeneratorLayout(store.getState())).toEqual(GeneratorLayout.horizontal);
+ });
+
+ it('sets the default number of rows to 5', () => {
+ expect(selectors.getNumPreviewRows(store.getState())).toEqual(5);
+ });
+
+ it('updates the number of preview rows', () => {
+ store.dispatch(actions.updateNumPreviewRows(10));
+ expect(selectors.getNumPreviewRows(store.getState())).toEqual(10);
+ });
+});
+
+describe('grid rows', () => {
+ let store: any;
+ beforeEach(() => {
+ store = createStore(
+ combineReducers({
+ generator: generatorReducer,
+ main: mainReducer
+ })
+ );
+ });
+
+ it('adds rows, well, adds rows', () => {
+ expect(selectors.getNumRows(store.getState())).toEqual(0);
+ store.dispatch(actions.addRows(10));
+ expect(selectors.getNumRows(store.getState())).toEqual(10);
+ });
+
+ it('added rows get listed as sortable', () => {
+ store.dispatch(actions.addRows(5));
+ const rows = selectors.getRows(store.getState());
+ const rowKeys = Object.keys(rows);
+ expect(rowKeys.length).toEqual(5);
+ expect(selectors.getSortedRows(store.getState()).length).toEqual(5);
+ });
+
+ it('added second batch of rows appends to end', () => {
+ store.dispatch(actions.addRows(5));
+ store.dispatch(actions.addRows(5));
+
+ const rows = selectors.getRows(store.getState());
+ const rowKeys = Object.keys(rows);
+ expect(rowKeys.length).toEqual(10);
+ expect(selectors.getSortedRows(store.getState()).length).toEqual(10);
+ });
+
+ it('removing a row removes it from both rows and sorted rows', () => {
+ store.dispatch(actions.addRows(5));
+ const rows = selectors.getRows(store.getState());
+ const rowKeys = Object.keys(rows);
+ store.dispatch(actions.removeRow(rowKeys[0]));
+
+ // check the `rows` property is updated
+ const updatedRows = selectors.getRows(store.getState());
+ const updateRowKeys = Object.keys(updatedRows);
+ expect(updateRowKeys.length).toEqual(4);
+
+ // check the `sortedRows` property is also updated
+ const sortedRows = selectors.getSortedRows(store.getState());
+ expect(sortedRows.length).toEqual(4);
+ });
+
+ // fit('changes the row title', () => {
+ // store.dispatch(actions.addRows(3));
+ //
+ // // should be blank by default
+ // const rows = selectors.getSortedRowsArray(store.getState());
+ // expect(rows[0].title).toEqual('');
+ // expect(rows[1].title).toEqual('');
+ // expect(rows[2].title).toEqual('');
+ //
+ // store.dispatch(actions.onChangeTitle(rows[1].id, 'new value!'));
+ // const updatedRows = selectors.getSortedRowsArray(store.getState());
+ // expect(updatedRows[0].title).toEqual('');
+ // expect(updatedRows[1].title).toEqual('new value!');
+ // expect(updatedRows[2].title).toEqual('');
+ // });
+
+ // fit('changes the data type', () => {
+ // store.dispatch(actions.addRows(3));
+ //
+ // const rows = selectors.getSortedRowsArray(store.getState());
+ // expect(rows[0].dataType).toEqual(null);
+ // expect(rows[1].dataType).toEqual(null);
+ // expect(rows[2].dataType).toEqual(null);
+ //
+ // sinon.stub(dataTypeUtils, 'requestDataTypeBundle').returns(Promise.resolve({}));
+ // actions.onSelectDataType('Names', rows[1].id);
+ //
+ // // const dispatches = Thunk(actions.onSelectDataType(rows[1].id, 'JSON')).execute();
+ // // console.log(dispatches);
+ //
+ // const updatedRows = selectors.getSortedRowsArray(store.getState());
+ // expect(updatedRows[0].dataType).toEqual(null);
+ // expect(updatedRows[1].dataType).toEqual('Names');
+ // expect(updatedRows[2].dataType).toEqual(null);
+ // });
+
+ // it('initializes the default data type state when selecting data type', () => {
+ // const defaultState = {
+ // arbitrary: 1,
+ // content: true
+ // };
+
+ // sinon.stub(dateTypeUtils, 'getDataTypeDefaultState')
+ // .onCall(0).returns(defaultState);
+
+ // store.dispatch(actions.addRows(1));
+ // const rows = selectors.getSortedRowsArray(store.getState());
+ // store.dispatch(actions.onSelectDataType(rows[0].id, 'JSON'));
+
+ // const updatedRows = selectors.getSortedRowsArray(store.getState());
+ // expect(updatedRows[0].data).toEqual(defaultState);
+
+ // sinon.restore();
+ // });
+});
+
+describe('preview panel settings', () => {
+ let store: any;
+ beforeEach(() => {
+ store = createStore(
+ combineReducers({
+ generator: generatorReducer,
+ main: mainReducer
+ })
+ );
+ });
+
+ it('adds rows, well, adds rows', () => {
+ expect(selectors.getNumRows(store.getState())).toEqual(0);
+ store.dispatch(actions.addRows(10));
+ expect(selectors.getNumRows(store.getState())).toEqual(10);
+ });
+});
+
+describe('misc selectors', () => {
+ let store: any;
+ beforeEach(() => {
+ store = createStore(
+ combineReducers({
+ generator: generatorReducer,
+ main: mainReducer
+ })
+ );
+ });
+
+ describe('getRowDataTypes', () => {
+ it('returns nothing when there are no rows', () => {
+ expect(selectors.getRowDataTypes(store.getState())).toEqual([]);
+ });
+
+ // it("returns a single row's data type", () => {
+ // store.dispatch(actions.addRows(1));
+ // expect(selectors.getRowDataTypes(store.getState())).toEqual([]);
+ // });
+ });
+});
diff --git a/apps/client/src/core/store/generator/generator.actions.ts b/apps/client/src/core/store/generator/generator.actions.ts
new file mode 100644
index 000000000..385ea1b56
--- /dev/null
+++ b/apps/client/src/core/store/generator/generator.actions.ts
@@ -0,0 +1,549 @@
+import { Dispatch } from 'redux';
+import * as selectors from './generator.selectors';
+import { getCurrentDataSet, isCountryNamesLoaded, isCountryNamesLoading } from './generator.selectors';
+import { ExportSettingsTab } from '../../generator/exportSettings/ExportSettings.types';
+import { DataTypeFolder, ExportTypeFolder } from '@generatedata/plugins';
+import { registerInterceptors } from '../../actionInterceptor';
+import { requestDataTypeBundle } from '~utils/dataTypes';
+import * as coreUtils from '../../../utils/coreUtils';
+import { getStrings } from '@generatedata/utils/lang';
+import { getUniqueString } from '@generatedata/utils/string';
+import { getExportTypeInitialState, loadExportTypeBundle } from '~utils/exportTypes';
+import { addToast } from '@generatedata/utils/general';
+import { DTBundle, DTOptionsMetadata } from '~types/dataTypes';
+import { GDAction } from '~types/general';
+import C from '@generatedata/config/constants';
+import { getUnchangedData } from '../../generationPanel/generation.helpers';
+import * as accountActions from '~store/account/account.actions';
+import { DataSetListItem } from '@generatedata/types';
+import { getUnique } from '@generatedata/utils/array';
+import { getCountryNamesBundle } from '../../../utils/coreUtils';
+import { getCountryData } from '@generatedata/plugins';
+import { GenerationWorkerActionType } from '~core/generator/generation.types';
+
+export const ADD_ROWS = 'ADD_ROWS';
+export const addRows = (numRows: number): GDAction => ({
+ type: ADD_ROWS,
+ payload: {
+ numRows
+ }
+});
+
+export const REMOVE_ROW = 'REMOVE_ROW';
+export const removeRow = (id: string): GDAction => ({
+ type: REMOVE_ROW,
+ payload: { id }
+});
+
+export const CHANGE_TITLE = 'CHANGE_TITLE';
+export const onChangeTitle =
+ (id: string, value: string): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const state = getState();
+ const validateTitle = selectors.getExportTypeTitleValidationFunction(state);
+
+ let titleError = null;
+ if (validateTitle) {
+ const i18n = selectors.getExportTypeI18n(state);
+ const settings = selectors.getCurrentExportTypeSettings(state);
+ titleError = validateTitle(value, i18n, settings);
+ }
+
+ dispatch({
+ type: CHANGE_TITLE,
+ payload: {
+ id,
+ value,
+ titleError
+ }
+ });
+ };
+
+export type LoadDataTypeBundleOptions = {
+ gridRowId?: string;
+ shouldRefreshPreviewPanel?: boolean;
+ onLoadComplete?: (dataType: DataTypeFolder) => void;
+};
+
+export const SELECT_DATA_TYPE = 'SELECT_DATA_TYPE';
+export const onSelectDataType =
+ (dataType: DataTypeFolder, opts: LoadDataTypeBundleOptions = {}): any =>
+ (dispatch: any, getState: any): any =>
+ loadDataTypeBundle(dispatch, getState, dataType, opts);
+
+export const loadDataTypeBundle = (
+ dispatch: Dispatch,
+ getState: any,
+ dataType: DataTypeFolder,
+ opts: LoadDataTypeBundleOptions = {}
+): void => {
+ const options = {
+ gridRowId: null,
+ shouldRefreshPreviewPanel: true,
+ ...opts
+ };
+
+ const dataTypeI18n = selectors.getDataTypeI18n(getState());
+
+ let defaultTitle: string | null = null;
+ if (dataTypeI18n && dataTypeI18n[dataType]) {
+ defaultTitle = dataTypeI18n[dataType].DEFAULT_TITLE ? dataTypeI18n[dataType].DEFAULT_TITLE : dataType.toLowerCase();
+ const titles = selectors.getTitles(getState());
+ defaultTitle = getUniqueString(defaultTitle as string, titles);
+ }
+
+ requestDataTypeBundle(dataType).then((bundle: DTBundle) => {
+ dispatch(dataTypeLoaded(dataType));
+ if (bundle.actionInterceptors) {
+ registerInterceptors(dataType, bundle.actionInterceptors);
+ }
+
+ // if it's been selected within the grid, select the row and update the preview panel
+ const ids = [];
+ if (options.gridRowId) {
+ ids.push(options.gridRowId);
+ dispatch({
+ type: SELECT_DATA_TYPE,
+ payload: {
+ id: options.gridRowId,
+ value: dataType,
+ data: bundle.initialState,
+ defaultTitle
+ }
+ });
+ }
+
+ if (options.shouldRefreshPreviewPanel) {
+ dispatch(refreshPreview(ids));
+ }
+
+ // workaround to allow batch loading a bunch of different Data Types onload, and have the callee track what's
+ // been loaded to know when to do the refreshPreview() call
+ if (options.onLoadComplete) {
+ options.onLoadComplete(dataType);
+ }
+ });
+};
+
+export const REQUEST_COUNTRY_NAMES = 'REQUEST_COUNTRY_NAMES';
+export const COUNTRY_NAMES_LOADED = 'COUNTRY_NAMES_LOADED';
+export const requestCountryNames =
+ (): any =>
+ (dispatch: any): any => {
+ dispatch({ type: REQUEST_COUNTRY_NAMES });
+
+ getCountryNamesBundle().then(() => {
+ dispatch({ type: COUNTRY_NAMES_LOADED });
+ });
+ };
+
+export const CONFIGURE_DATA_TYPE = 'CONFIGURE_DATA_TYPE';
+export const onConfigureDataType = (id: string, data: any, metadata?: DTOptionsMetadata, triggeredByInterceptor = false): any => {
+ return (dispatch: any, getState: any): any => {
+ if (metadata && metadata.useCountryNames) {
+ const state = getState();
+ if (!isCountryNamesLoaded(state) && !isCountryNamesLoading(state)) {
+ dispatch(requestCountryNames());
+ }
+ }
+
+ const configureDataType = (disp: any): any =>
+ new Promise((resolve: any) => {
+ disp({
+ type: CONFIGURE_DATA_TYPE,
+ triggeredByInterceptor,
+ payload: {
+ id,
+ data,
+ metadata
+ }
+ });
+ resolve();
+ });
+ configureDataType(dispatch).then(() => dispatch(refreshPreview([id])));
+ };
+};
+
+export const CONFIGURE_EXPORT_TYPE = 'CONFIGURE_EXPORT_TYPE';
+export const configureExportType = (data: any): GDAction => ({
+ type: CONFIGURE_EXPORT_TYPE,
+ payload: {
+ data
+ }
+});
+
+export const REPOSITION_ROW = 'REPOSITION_ROW';
+export const repositionRow = (id: string, newIndex: number): GDAction => ({
+ type: REPOSITION_ROW,
+ payload: {
+ id,
+ newIndex
+ }
+});
+
+export const TOGGLE_GRID = 'TOGGLE_GRID';
+export const toggleGrid = (): GDAction => ({ type: TOGGLE_GRID });
+
+export const TOGGLE_PREVIEW = 'TOGGLE_PREVIEW';
+export const togglePreview = (): GDAction => ({ type: TOGGLE_PREVIEW });
+
+export const REFRESH_PREVIEW_DATA = 'REFRESH_PREVIEW_DATA';
+
+// this re-generates the preview panel data. This doesn't have to be called on boot-up because the preview data is
+// generated on the fly, saved in the store and rehydrated when the app loads
+export const refreshPreview = (idsToRefresh: string[] = [], onComplete: any = null): any => {
+ const generationWorker = coreUtils.getGenerationWorker('preview');
+
+ return (dispatch: any, getState: any): any => {
+ const state = getState();
+ const i18n = getStrings();
+
+ const template = selectors.getGenerationTemplate(state);
+ const dataTypePreviewData = { ...selectors.getDataTypePreviewData(state) };
+ const columns = selectors.getColumns(state);
+ const unchanged = getUnchangedData(idsToRefresh, columns, dataTypePreviewData);
+
+ // for initial bootup the page may be empty. This ensures the onComplete action fires (the `onmessage` doesn't fire
+ // when no columns)
+ if (!columns.length && onComplete) {
+ dispatch(onComplete());
+ }
+
+ // here we DO need to generate the data independently of the final string in the appropriate export type format.
+ // That allows us to tease out what changes on each keystroke in the UI and only refresh specific fields - it's
+ // far clearer to the end user that way
+ generationWorker.postMessage({
+ action: GenerationWorkerActionType.ProcessDataTypesOnly,
+ numResults: C.MAX_PREVIEW_ROWS,
+ batchSize: C.MAX_PREVIEW_ROWS,
+ unchanged,
+ columns,
+ i18n,
+ template,
+ countryNames: coreUtils.getCountryNames(),
+ workerUtilsUrl: coreUtils.getWorkerUtilsUrl(),
+ dataTypeWorkerMap: coreUtils.getDataTypeWorkerMap(selectors.getRowDataTypes(state) as DataTypeFolder[]),
+ countryData: getCountryData()
+ });
+
+ generationWorker.onmessage = (e: MessageEvent): void => {
+ if (e.data.event !== GenerationWorkerActionType.DataTypesProcessed) {
+ return;
+ }
+ const { generatedData } = e.data.data;
+
+ columns.forEach(({ id }, index: number) => {
+ if (idsToRefresh.length && idsToRefresh.indexOf(id) === -1) {
+ return;
+ }
+ dataTypePreviewData[id] = generatedData.map((row: any): any => row[index]);
+ });
+
+ // great! So we've generated the data we need and manually only changed those lines that have just changed
+ // by the user via the UI. The CodeMirrorWrapper component handles passing off that info to the export type
+ // web worker to generate the final string
+ dispatch({
+ type: REFRESH_PREVIEW_DATA,
+ payload: {
+ dataTypePreviewData
+ }
+ });
+
+ if (onComplete) {
+ dispatch(onComplete());
+ }
+ };
+ };
+};
+
+export const TOGGLE_LAYOUT = 'TOGGLE_LAYOUT';
+export const toggleLayout = (): GDAction => ({ type: TOGGLE_LAYOUT });
+
+export const UPDATE_NUM_PREVIEW_ROWS = 'UPDATE_NUM_PREVIEW_ROWS';
+export const updateNumPreviewRows = (numRows: number): GDAction => ({
+ type: UPDATE_NUM_PREVIEW_ROWS,
+ payload: { numRows }
+});
+
+export const CHANGE_THEME = 'CHANGE_THEME';
+export const changeTheme = (theme: string): GDAction => ({
+ type: CHANGE_THEME,
+ payload: { theme }
+});
+
+export const TOGGLE_SHOW_LINE_NUMBERS = 'TOGGLE_SHOW_LINE_NUMBERS';
+export const toggleShowLineNumbers = (): GDAction => ({
+ type: TOGGLE_SHOW_LINE_NUMBERS
+});
+
+export const TOGGLE_LINE_WRAPPING = 'TOGGLE_LINE_WRAPPING';
+export const toggleLineWrapping = (): GDAction => ({
+ type: TOGGLE_LINE_WRAPPING
+});
+
+export const SET_PREVIEW_TEXT_SIZE = 'SET_PREVIEW_TEXT_SIZE';
+export const setPreviewTextSize = (previewTextSize: number): GDAction => ({
+ type: SET_PREVIEW_TEXT_SIZE,
+ payload: {
+ previewTextSize
+ }
+});
+
+export const TOGGLE_EXPORT_SETTINGS = 'TOGGLE_EXPORT_SETTINGS';
+export const toggleExportSettings = (tab?: ExportSettingsTab): GDAction => ({
+ type: TOGGLE_EXPORT_SETTINGS,
+ payload: {
+ tab
+ }
+});
+
+export const HIDE_EXPORT_SETTINGS = 'HIDE_EXPORT_SETTINGS';
+export const hideExportSettings = (): GDAction => ({
+ type: HIDE_EXPORT_SETTINGS
+});
+
+export type LoadExportTypeBundleOptions = {
+ shouldRefreshPreviewPanel?: boolean;
+ onLoadComplete?: (exportType: ExportTypeFolder) => void;
+};
+
+export const SELECT_EXPORT_TYPE = 'SELECT_EXPORT_TYPE';
+export const onSelectExportType = (exportType: ExportTypeFolder, opts: LoadExportTypeBundleOptions = {}): any => {
+ return (dispatch: any): any => {
+ dispatch({
+ type: SELECT_EXPORT_TYPE,
+ payload: {
+ exportType
+ }
+ });
+
+ loadExportTypeBundle(exportType).then((bundle: DTBundle) => {
+ dispatch(exportTypeLoaded(exportType, bundle.initialState));
+
+ if (opts.shouldRefreshPreviewPanel) {
+ dispatch(refreshPreview());
+ }
+ if (opts.onLoadComplete) {
+ opts.onLoadComplete(exportType);
+ }
+ });
+ };
+};
+
+export const EXPORT_TYPE_LOADED = 'EXPORT_TYPE_LOADED';
+export const exportTypeLoaded = (exportType: ExportTypeFolder, initialState: any): GDAction => ({
+ type: EXPORT_TYPE_LOADED,
+ payload: {
+ exportType,
+ initialState
+ }
+});
+
+export const DATA_TYPE_LOADED = 'DATA_TYPE_LOADED';
+export const dataTypeLoaded = (dataType: DataTypeFolder): GDAction => ({
+ type: DATA_TYPE_LOADED,
+ payload: {
+ dataType
+ }
+});
+
+export const SHOW_GENERATION_SETTINGS_PANEL = 'SHOW_GENERATION_SETTINGS_PANEL';
+export const showGenerationSettingsPanel = (): GDAction => ({
+ type: SHOW_GENERATION_SETTINGS_PANEL
+});
+
+export const HIDE_START_GENERATION_PANEL = 'HIDE_START_GENERATION_PANEL';
+export const hideStartGenerationPanel = (): GDAction => ({
+ type: HIDE_START_GENERATION_PANEL
+});
+
+export const SHOW_HELP_DIALOG = 'SHOW_HELP_DIALOG';
+export const showHelpDialog = (dataType: DataTypeFolder): GDAction => ({
+ type: SHOW_HELP_DIALOG,
+ payload: {
+ dataType
+ }
+});
+
+export const SHOW_SCHEMA_DIALOG = 'SHOW_SCHEMA_DIALOG';
+export const showDataTemplateDialog = (): GDAction => ({
+ type: SHOW_SCHEMA_DIALOG
+});
+
+export const HIDE_SCHEMA_DIALOG = 'HIDE_SCHEMA_DIALOG';
+export const hideSchemaDialog = (): GDAction => ({ type: HIDE_SCHEMA_DIALOG });
+
+export const HIDE_HELP_DIALOG = 'HIDE_HELP_DIALOG';
+export const hideHelpDialog = (): GDAction => ({ type: HIDE_HELP_DIALOG });
+
+export const UPDATE_NUM_ROWS_TO_GENERATE = 'UPDATE_NUM_ROWS_TO_GENERATE';
+export const updateNumRowsToGenerate = (numRowsToGenerate: number): GDAction => ({
+ type: UPDATE_NUM_ROWS_TO_GENERATE,
+ payload: {
+ numRowsToGenerate
+ }
+});
+
+export const TOGGLE_STRIP_WHITESPACE = 'TOGGLE_STRIP_WHITESPACE';
+export const toggleStripWhitespace = (): GDAction => ({
+ type: TOGGLE_STRIP_WHITESPACE
+});
+
+export const CLEAR_GRID = 'CLEAR_GRID';
+export const RESET_GENERATOR = 'RESET_GENERATOR';
+export const clearPage =
+ (addDefaultRows = true): any =>
+ (dispatch: Dispatch, getState: any): void => {
+ const loadedExportTypes = selectors.getLoadedExportTypesArray(getState());
+
+ const exportTypeInitialStates: any = {};
+ loadedExportTypes.forEach((et) => {
+ exportTypeInitialStates[et] = getExportTypeInitialState(et as ExportTypeFolder);
+ });
+
+ dispatch({
+ type: RESET_GENERATOR,
+ payload: {
+ exportTypeInitialStates
+ }
+ });
+
+ if (addDefaultRows) {
+ dispatch(addRows(5));
+ }
+ };
+
+export const SET_PANEL_SIZE = 'SET_PANEL_SIZE';
+export const setPanelSize = (size: number): GDAction => ({
+ type: SET_PANEL_SIZE,
+ payload: {
+ size
+ }
+});
+
+export const CHANGE_SMALL_SCREEN_VISIBLE_PANEL = 'CHANGE_SMALL_SCREEN_VISIBLE_PANEL';
+export const changeSmallScreenVisiblePanel = (): GDAction => ({
+ type: CHANGE_SMALL_SCREEN_VISIBLE_PANEL
+});
+
+export const SET_INITIAL_DEPENDENCIES_LOADED = 'SET_INITIAL_DEPENDENCIES_LOADED';
+export const setInitialDependenciesLoaded = (): GDAction => ({
+ type: SET_INITIAL_DEPENDENCIES_LOADED
+});
+
+export const SET_BULK_ACTION = 'SET_BULK_ACTION';
+export const LOAD_DATA_SET = 'LOAD_DATA_SET';
+
+export const loadDataSet =
+ (dataSet: DataSetListItem, showToast = true): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const i18n = getStrings();
+ const { exportType, exportTypeSettings, rows, sortedRows } = JSON.parse(dataSet.content);
+
+ const dataTypes = sortedRows.map((hash: string) => rows[hash].dataType).filter((dataType: DataTypeFolder | null) => dataType !== null);
+ const uniqueDataTypes = getUnique(dataTypes);
+
+ dispatch({
+ type: SET_BULK_ACTION,
+ payload: {
+ isComplete: false
+ }
+ });
+
+ // load all the datasets and export type
+ dispatch(onSelectExportType(exportType, { shouldRefreshPreviewPanel: false }));
+ uniqueDataTypes.forEach((dataType) =>
+ loadDataTypeBundle(dispatch, getState, dataType as DataTypeFolder, {
+ shouldRefreshPreviewPanel: false
+ })
+ );
+
+ dispatch({
+ type: LOAD_DATA_SET,
+ payload: {
+ exportType,
+ exportTypeSettings,
+ rows,
+ sortedRows,
+ dataSetId: dataSet.dataSetId,
+ dataSetName: dataSet.dataSetName
+ }
+ });
+
+ if (showToast) {
+ addToast({
+ type: 'success',
+ message: i18n.core.dataSetLoaded
+ });
+ }
+ };
+
+export const STASH_GENERATOR_STATE = 'STASH_GENERATOR_STATE';
+export const stashGeneratorState = (): GDAction => ({
+ type: STASH_GENERATOR_STATE
+});
+
+export const POP_STASHED_STATE = 'POP_STASHED_STATE';
+export const popStashedState = (): GDAction => ({ type: POP_STASHED_STATE });
+
+export const LOAD_STASHED_STATE = 'LOAD_STASHED_STATE';
+export const loadStashedState = (): GDAction => ({ type: LOAD_STASHED_STATE });
+
+export const SHOW_DATA_SET_HISTORY = 'SHOW_DATA_SET_HISTORY';
+export const showDataSetHistory = (): GDAction => ({
+ type: SHOW_DATA_SET_HISTORY
+});
+
+export const HIDE_DATA_SET_HISTORY = 'HIDE_DATA_SET_HISTORY';
+export const hideDataSetHistory = (): GDAction => ({
+ type: HIDE_DATA_SET_HISTORY
+});
+
+export const loadDataSetHistoryItem =
+ (content: any): any =>
+ (dispatch: Dispatch, getState: any): any => {
+ const state = getState();
+ const { dataSetId, dataSetName } = getCurrentDataSet(state);
+
+ dispatch(
+ loadDataSet(
+ {
+ dataSetId,
+ dataSetName,
+ content
+ } as DataSetListItem,
+ false
+ )
+ );
+ };
+
+export const SHOW_CLEAR_PAGE_DIALOG = 'SHOW_CLEAR_PAGE_DIALOG';
+export const showClearPageDialog = (): GDAction => ({
+ type: SHOW_CLEAR_PAGE_DIALOG
+});
+
+export const HIDE_CLEAR_GRID_DIALOG = 'HIDE_CLEAR_GRID_DIALOG';
+export const hideClearPageDialog = (): GDAction => ({
+ type: HIDE_CLEAR_GRID_DIALOG
+});
+
+export const SELECT_DATA_SET_HISTORY_ITEM = 'SELECT_DATA_SET_HISTORY_ITEM';
+export const selectDataSetHistoryItem = (historyId: number, isLatest: boolean): GDAction => ({
+ type: SELECT_DATA_SET_HISTORY_ITEM,
+ payload: {
+ historyId,
+ isLatest
+ }
+});
+
+// N.B. the version was actually selected when they clicked "View". This just closes the overlay and saves the
+// current state of the generator
+export const revertToHistoryVersion =
+ (): any =>
+ (dispatch: Dispatch, getState: any): any => {
+ const state = getState();
+ const i18n = selectors.getCoreI18n(state);
+
+ dispatch(hideDataSetHistory());
+ dispatch(accountActions.saveCurrentDataSet(i18n.dataSetReverted));
+ };
diff --git a/apps/client/src/core/store/generator/generator.reducer.ts b/apps/client/src/core/store/generator/generator.reducer.ts
new file mode 100644
index 000000000..5c33fa5cd
--- /dev/null
+++ b/apps/client/src/core/store/generator/generator.reducer.ts
@@ -0,0 +1,579 @@
+import { AnyAction } from 'redux';
+// @ts-ignore
+import { nanoid } from 'nanoid';
+import { produce } from 'immer';
+import * as actions from './generator.actions';
+import * as mainActions from '../main/main.actions';
+import * as accountActions from '../account/account.actions';
+import * as packetActions from '../packets/packets.actions';
+import { ExportSettingsTab } from '../../generator/exportSettings/ExportSettings.types';
+import { DataTypeFolder, dataTypes, ExportTypeFolder, exportTypes } from '@generatedata/plugins';
+import { GeneratorLayout } from '@generatedata/types';
+import C from '@generatedata/config/constants';
+import { GeneratorPanel } from '~types/general';
+import { DTOptionsMetadata } from '~types/dataTypes';
+import clientConfig from '@generatedata/config/clientConfig';
+
+export type DataRow = {
+ id: string;
+ title: string;
+
+ // a title field can only contain a single error at a time. If it's empty, the UI will automatically show the error.
+ // otherwise it's up to the current Export Type to provide a validateTitleField() method that returns the appropriate
+ // (single) error
+ titleError: string | null;
+ dataType: DataTypeFolder | null;
+ data: any;
+ metadata?: DTOptionsMetadata;
+};
+
+export type DataRows = {
+ [id: string]: DataRow;
+};
+
+export type PreviewData = {
+ [id: string]: any;
+};
+
+// we store all settings separately so in case a user switches from one to another, the previous settings aren't
+// wiped out
+export type ExportTypeSettings = {
+ [exportType in ExportTypeFolder]: any;
+};
+
+// TODO remove the duplications for the types here
+export type StashedGeneratorState = {
+ exportType: ExportTypeFolder;
+ rows: DataRows;
+ sortedRows: string[];
+ showGrid: boolean;
+ showPreview: boolean;
+ smallScreenVisiblePanel: GeneratorPanel;
+ generatorLayout: GeneratorLayout;
+ showExportSettings: boolean;
+ exportTypeSettings: Partial;
+ showGenerationSettingsPanel: boolean;
+ showDataSetHistory: boolean;
+ bulkActionPending: boolean;
+ showHelpDialog: boolean;
+ showSchemaDialog: boolean;
+ showClearPageDialog: boolean;
+ helpDialogSection: DataTypeFolder | null;
+ showLineNumbers: boolean;
+ enableLineWrapping: boolean;
+ theme: string;
+ previewTextSize: number;
+ dataTypePreviewData: PreviewData;
+ exportSettingsTab: ExportSettingsTab;
+ numPreviewRows: number;
+ stripWhitespace: boolean;
+ numRowsToGenerate: number;
+ currentDataSetId: number | null;
+ currentDataSetName: string;
+ selectedDataSetHistory: {
+ historyId: number | null;
+ isLatest: boolean;
+ };
+ isCountryNamesLoading: boolean;
+ isCountryNamesLoaded: boolean;
+};
+
+const stashProps = [
+ 'exportType',
+ 'rows',
+ 'sortedRows',
+ 'showGrid',
+ 'showPreview',
+ 'smallScreenVisiblePanel',
+ 'generatorLayout',
+ 'showExportSettings',
+ 'exportTypeSettings',
+ 'showGenerationSettingsPanel',
+ 'showHelpDialog',
+ 'helpDialogSection',
+ 'showLineNumbers',
+ 'enableLineWrapping',
+ 'theme',
+ 'previewTextSize',
+ 'dataTypePreviewData',
+ 'exportSettingsTab',
+ 'numPreviewRows',
+ 'stripWhitespace',
+ 'numPreviewRows',
+ 'stripWhitespace',
+ 'currentDataSetId',
+ 'currentDataSetName'
+];
+
+export type CurrentDataSet = {
+ dataSetId: number | null;
+ dataSetName: string;
+ lastSaved: any;
+};
+
+export type SelectedDataSetHistoryItem = {
+ historyId: number | null;
+ isLatest: boolean;
+};
+
+export type GeneratorState = {
+ loadedDataTypes: {
+ [str in DataTypeFolder]: boolean;
+ };
+ loadedExportTypes: {
+ [str in ExportTypeFolder]: boolean;
+ };
+
+ // this is set to true on load after all necessary export types, data types for whatever last config they
+ // has have loaded, as well as re-generate a new batch of preview data. It's used to show a spinner in the
+ // Preview panel until it's ready to show data
+ initialDependenciesLoaded: boolean;
+ exportType: ExportTypeFolder;
+ rows: DataRows;
+ sortedRows: string[];
+ showGrid: boolean;
+ showPreview: boolean;
+ smallScreenVisiblePanel: GeneratorPanel;
+ generatorLayout: GeneratorLayout;
+ showExportSettings: boolean;
+ exportTypeSettings: Partial;
+ showGenerationSettingsPanel: boolean;
+ showDataSetHistory: boolean;
+ bulkActionPending: boolean;
+ showHelpDialog: boolean;
+ showSchemaDialog: boolean;
+ showClearPageDialog: boolean;
+ helpDialogSection: DataTypeFolder | null;
+ showLineNumbers: boolean;
+ enableLineWrapping: boolean;
+ theme: string;
+ previewTextSize: number;
+ dataTypePreviewData: PreviewData;
+ exportSettingsTab: ExportSettingsTab;
+ numPreviewRows: number;
+ stripWhitespace: boolean;
+ lastLayoutWidth: number | null;
+ lastLayoutHeight: number | null;
+ numRowsToGenerate: number;
+ currentDataSet: CurrentDataSet;
+ selectedDataSetHistory: SelectedDataSetHistoryItem;
+ isCountryNamesLoading: boolean;
+ isCountryNamesLoaded: boolean;
+ stashedState: StashedGeneratorState | null;
+};
+
+export const getInitialState = (): GeneratorState => ({
+ // the extra check for existence on these vars is just to placate the tests (not sure why needed)
+ loadedDataTypes: Object.keys(dataTypes).reduce((acc: any, name) => ({ ...acc, [name]: false }), {}),
+ loadedExportTypes: Object.keys(exportTypes).reduce((acc: any, name) => ({ ...acc, [name]: false }), {}),
+ initialDependenciesLoaded: false,
+ exportType: clientConfig.appSettings.GD_DEFAULT_EXPORT_TYPE as ExportTypeFolder,
+ rows: {},
+ sortedRows: [],
+ showGrid: true,
+ showPreview: true,
+ smallScreenVisiblePanel: GeneratorPanel.grid,
+ generatorLayout: GeneratorLayout.horizontal,
+ showExportSettings: false,
+ exportTypeSettings: {},
+ numPreviewRows: 5,
+ showLineNumbers: true,
+ enableLineWrapping: true,
+ theme: 'lucario',
+ previewTextSize: 12,
+ dataTypePreviewData: {},
+ exportSettingsTab: 'exportType', // TODO enum
+ showGenerationSettingsPanel: false,
+ showDataSetHistory: false,
+ bulkActionPending: true, // for brand new page loads we assume there's a bulk action to re-load
+ showHelpDialog: false,
+ showSchemaDialog: false,
+ showClearPageDialog: false,
+ helpDialogSection: null,
+ numRowsToGenerate: clientConfig.appSettings.GD_DEFAULT_NUM_ROWS,
+ stripWhitespace: false,
+ lastLayoutWidth: null,
+ lastLayoutHeight: null,
+ currentDataSet: {
+ dataSetId: null,
+ dataSetName: '',
+ lastSaved: null
+ },
+ selectedDataSetHistory: {
+ historyId: null,
+ isLatest: false
+ },
+ isCountryNamesLoading: false,
+ isCountryNamesLoaded: false,
+ stashedState: null
+});
+
+export const reducer = produce((draft: GeneratorState, action: AnyAction) => {
+ switch (action.type) {
+ case mainActions.RESET_STORE:
+ const initialState = getInitialState();
+ Object.keys(initialState).forEach((key) => {
+ // @ts-ignore-line
+ draft[key] = initialState[key];
+ });
+ break;
+
+ // TODO needs to be cleaned up. Combine with LOGOUT action?
+ case mainActions.AUTHENTICATED:
+ if (!action.payload.authenticated) {
+ draft.currentDataSet = {
+ dataSetId: null,
+ dataSetName: '',
+ lastSaved: null
+ };
+ }
+ break;
+
+ case mainActions.LOGOUT:
+ draft.currentDataSet = {
+ dataSetId: null,
+ dataSetName: '',
+ lastSaved: null
+ };
+ break;
+
+ case actions.CLEAR_GRID:
+ draft.rows = {};
+ draft.sortedRows = [];
+ draft.dataTypePreviewData = {};
+ draft.showClearPageDialog = false;
+ draft.currentDataSet = {
+ dataSetId: null,
+ dataSetName: '',
+ lastSaved: null
+ };
+ break;
+
+ case actions.RESET_GENERATOR: {
+ draft.rows = {};
+ draft.sortedRows = [];
+ draft.showClearPageDialog = false;
+
+ const initialState = getInitialState();
+ const settingsToReset = [
+ 'exportType',
+ 'showGrid',
+ 'showPreview',
+ 'showExportSettings',
+ 'numPreviewRows',
+ 'showLineNumbers',
+ 'enableLineWrapping',
+ 'theme',
+ 'previewTextSize',
+ 'exportSettingsTab',
+ 'numRowsToGenerate',
+ 'stripWhitespace',
+ 'currentDataSet'
+ ];
+ settingsToReset.forEach((setting: any) => {
+ // @ts-ignore-line
+ draft[setting] = initialState[setting];
+ });
+
+ Object.keys(action.payload.exportTypeInitialStates).forEach((et) => {
+ draft.exportTypeSettings[et as ExportTypeFolder] = action.payload.exportTypeInitialStates[et];
+ });
+ break;
+ }
+
+ case actions.DATA_TYPE_LOADED:
+ draft.loadedDataTypes[action.payload.dataType as DataTypeFolder] = true;
+ break;
+
+ case actions.EXPORT_TYPE_LOADED:
+ draft.loadedExportTypes[action.payload.exportType as ExportTypeFolder] = true;
+
+ // we only ever initialize the export settings to the initial state when the export type hasn't been
+ // loaded yet. Note: this'll be an interesting upgrade problem
+ if (!draft.exportTypeSettings[action.payload.exportType as ExportTypeFolder]) {
+ draft.exportTypeSettings[action.payload.exportType as ExportTypeFolder] = action.payload.initialState;
+ }
+ break;
+
+ case actions.ADD_ROWS: {
+ const newRowIDs: string[] = [];
+ for (let i = 0; i < action.payload.numRows; i++) {
+ const rowId = nanoid();
+ draft.rows[rowId] = {
+ id: rowId,
+ title: '',
+ titleError: null,
+ dataType: null,
+ data: null
+ };
+ newRowIDs.push(rowId);
+ }
+ draft.sortedRows = [...draft.sortedRows, ...newRowIDs];
+ break;
+ }
+
+ case actions.REMOVE_ROW: {
+ const trimmedRowIds = draft.sortedRows.filter((i) => i !== action.payload.id);
+ const updatedRows: DataRows = {};
+ trimmedRowIds.forEach((id) => {
+ updatedRows[id] = draft.rows[id];
+ });
+ draft.rows = updatedRows;
+ draft.sortedRows = trimmedRowIds;
+ break;
+ }
+
+ case actions.CHANGE_TITLE:
+ draft.rows[action.payload.id].title = action.payload.value;
+ draft.rows[action.payload.id].titleError = action.payload.titleError;
+ break;
+
+ case actions.SELECT_DATA_TYPE: {
+ const { id, value, data, defaultTitle } = action.payload;
+ const title = draft.rows[id].title ? draft.rows[id].title : defaultTitle;
+
+ draft.rows[id] = {
+ ...draft.rows[id],
+ dataType: value,
+ data,
+ title
+ };
+ break;
+ }
+
+ case actions.SELECT_EXPORT_TYPE:
+ draft.exportType = action.payload.exportType;
+ break;
+
+ case actions.REFRESH_PREVIEW_DATA:
+ draft.dataTypePreviewData = {
+ ...action.payload.dataTypePreviewData
+ };
+ break;
+
+ case actions.CONFIGURE_DATA_TYPE:
+ draft.rows[action.payload.id].data = action.payload.data;
+ if (action.payload.metadata) {
+ draft.rows[action.payload.id].metadata = action.payload.metadata;
+ }
+ break;
+
+ case actions.CONFIGURE_EXPORT_TYPE:
+ draft.exportTypeSettings[draft.exportType] = action.payload.data;
+ break;
+
+ case actions.REPOSITION_ROW:
+ const newArray = draft.sortedRows.filter((i) => i !== action.payload.id);
+ newArray.splice(action.payload.newIndex, 0, action.payload.id);
+ draft.sortedRows = newArray;
+ break;
+
+ case actions.TOGGLE_GRID:
+ draft.showGrid = !draft.showGrid;
+ if (!draft.showPreview) {
+ draft.showPreview = true;
+ }
+ break;
+
+ case actions.TOGGLE_PREVIEW:
+ draft.showPreview = !draft.showPreview;
+ if (!draft.showGrid) {
+ draft.showGrid = true;
+ }
+ break;
+
+ case actions.TOGGLE_LAYOUT:
+ draft.generatorLayout = draft.generatorLayout === GeneratorLayout.horizontal ? GeneratorLayout.vertical : GeneratorLayout.horizontal;
+ break;
+
+ case actions.TOGGLE_LINE_WRAPPING:
+ draft.enableLineWrapping = !draft.enableLineWrapping;
+ break;
+
+ case actions.UPDATE_NUM_PREVIEW_ROWS:
+ draft.numPreviewRows = action.payload.numRows;
+ break;
+
+ case actions.CHANGE_THEME:
+ draft.theme = action.payload.theme;
+ break;
+
+ case actions.TOGGLE_SHOW_LINE_NUMBERS:
+ draft.showLineNumbers = !draft.showLineNumbers;
+ break;
+
+ case actions.SET_PREVIEW_TEXT_SIZE:
+ draft.previewTextSize = action.payload.previewTextSize;
+ break;
+
+ case actions.TOGGLE_EXPORT_SETTINGS:
+ draft.showExportSettings = !draft.showExportSettings;
+ if (action.payload?.tab) {
+ draft.exportSettingsTab = action.payload.tab;
+ }
+ break;
+
+ case actions.HIDE_EXPORT_SETTINGS:
+ draft.showExportSettings = false;
+ break;
+
+ case actions.SHOW_GENERATION_SETTINGS_PANEL:
+ draft.showGenerationSettingsPanel = true;
+ break;
+
+ case actions.HIDE_START_GENERATION_PANEL:
+ draft.showGenerationSettingsPanel = false;
+ break;
+
+ case actions.UPDATE_NUM_ROWS_TO_GENERATE:
+ draft.numRowsToGenerate = parseInt(action.payload.numRowsToGenerate, 10);
+ break;
+
+ case actions.TOGGLE_STRIP_WHITESPACE:
+ draft.stripWhitespace = !draft.stripWhitespace;
+ break;
+
+ case actions.SET_PANEL_SIZE:
+ const setting = draft.generatorLayout === 'horizontal' ? 'lastLayoutHeight' : 'lastLayoutWidth';
+ draft[setting] = action.payload.size;
+ break;
+
+ case actions.CHANGE_SMALL_SCREEN_VISIBLE_PANEL:
+ draft.smallScreenVisiblePanel = draft.smallScreenVisiblePanel === GeneratorPanel.grid ? GeneratorPanel.preview : GeneratorPanel.grid;
+ break;
+
+ case actions.SET_BULK_ACTION:
+ draft.bulkActionPending = !action.payload.isComplete;
+ break;
+
+ case actions.SET_INITIAL_DEPENDENCIES_LOADED:
+ draft.initialDependenciesLoaded = true;
+ break;
+
+ // only hide the generation settings panel for larger packet sizes. For smaller sizes, just show the spinner
+ // overlay
+ case packetActions.START_GENERATION:
+ if (action.payload.numRowsToGenerate > C.SMALL_GENERATION_COUNT) {
+ draft.showGenerationSettingsPanel = false;
+ }
+ break;
+
+ case accountActions.SET_CURRENT_DATA_SET:
+ draft.currentDataSet = {
+ dataSetId: action.payload.dataSetId,
+ dataSetName: action.payload.dataSetName,
+ lastSaved: null
+ };
+ break;
+
+ case accountActions.UPDATE_CURRENT_DATA_SET_LAST_SAVED:
+ draft.currentDataSet.lastSaved = action.payload.lastSaved;
+ break;
+
+ case actions.LOAD_DATA_SET: {
+ const { exportType, exportTypeSettings, rows, sortedRows, dataSetId, dataSetName } = action.payload;
+ draft.exportType = exportType;
+ draft.exportTypeSettings[exportType as ExportTypeFolder] = exportTypeSettings;
+ draft.rows = rows;
+ draft.sortedRows = sortedRows;
+ draft.currentDataSet = {
+ dataSetId,
+ dataSetName,
+ lastSaved: null
+ };
+ break;
+ }
+
+ case accountActions.UPDATE_CURRENT_DATA_SET_NAME:
+ draft.currentDataSet.dataSetName = action.payload.dataSetName;
+ break;
+
+ case actions.STASH_GENERATOR_STATE: {
+ const lastStashedState: any = {};
+ stashProps.map((prop: string) => {
+ // @ts-ignore-line
+ lastStashedState[prop] = draft[prop];
+ });
+ draft.stashedState = lastStashedState;
+ break;
+ }
+
+ case actions.POP_STASHED_STATE: {
+ if (draft.stashedState) {
+ stashProps.map((prop: string) => {
+ // @ts-ignore-line
+ draft[prop] = draft.stashedState[prop];
+ });
+ }
+ draft.stashedState = null;
+ break;
+ }
+
+ // loads the last stashed state, but doesn't pop it
+ case actions.LOAD_STASHED_STATE:
+ stashProps.map((prop: string) => {
+ // @ts-ignore-line
+ draft[prop] = draft.stashedState[prop];
+ });
+ draft.selectedDataSetHistory = {
+ historyId: null,
+ isLatest: true
+ };
+ break;
+
+ case actions.SHOW_HELP_DIALOG:
+ draft.showHelpDialog = true;
+ draft.helpDialogSection = action.payload.dataType;
+ break;
+
+ case actions.HIDE_HELP_DIALOG:
+ draft.showHelpDialog = false;
+ break;
+
+ case actions.SHOW_DATA_SET_HISTORY:
+ draft.showDataSetHistory = true;
+ break;
+
+ case actions.HIDE_DATA_SET_HISTORY:
+ draft.showDataSetHistory = false;
+ draft.selectedDataSetHistory = {
+ historyId: null,
+ isLatest: false
+ };
+ break;
+
+ case actions.SHOW_CLEAR_PAGE_DIALOG:
+ draft.showClearPageDialog = true;
+ break;
+
+ case actions.HIDE_CLEAR_GRID_DIALOG:
+ draft.showClearPageDialog = false;
+ break;
+
+ case actions.SELECT_DATA_SET_HISTORY_ITEM:
+ draft.selectedDataSetHistory = {
+ historyId: action.payload.historyId,
+ isLatest: action.payload.isLatest
+ };
+ break;
+
+ case actions.REQUEST_COUNTRY_NAMES:
+ draft.isCountryNamesLoading = true;
+ break;
+
+ case actions.COUNTRY_NAMES_LOADED:
+ draft.isCountryNamesLoading = false;
+ draft.isCountryNamesLoaded = true;
+ break;
+
+ case actions.SHOW_SCHEMA_DIALOG:
+ draft.showSchemaDialog = true;
+ break;
+
+ case actions.HIDE_SCHEMA_DIALOG:
+ draft.showSchemaDialog = false;
+ break;
+ }
+}, getInitialState());
+
+export default reducer;
diff --git a/apps/client/src/core/store/generator/generator.selectors.ts b/apps/client/src/core/store/generator/generator.selectors.ts
new file mode 100644
index 000000000..82cde570a
--- /dev/null
+++ b/apps/client/src/core/store/generator/generator.selectors.ts
@@ -0,0 +1,352 @@
+import { createSelector } from 'reselect';
+import { GeneratorLayout } from '@generatedata/types';
+import { CurrentDataSet, DataRow, DataRows } from './generator.reducer';
+import { GeneratorPanel } from '~types/general';
+import { DataTypeFolder, ExportTypeFolder } from '@generatedata/plugins';
+import * as mainSelectors from '../main/main.selectors';
+import * as coreUtils from '../../../utils/coreUtils';
+import * as langUtils from '@generatedata/utils/lang';
+import { processBatches, getDataType } from '~utils/dataTypes';
+import {
+ getExportTypeLabel as exportTypeUtilsGetExportTypeLabel,
+ getCodeMirrorMode as exportTypeUtilsGetCodeMirrorMode,
+ getExportTypeTitleValidationFunction as exportTypeGetExportTypeTitleValidation
+} from '~utils/exportTypes';
+import { GDLocale, GenerationTemplate, Store } from '~types/general';
+import { ColumnData } from '@generatedata/plugins';
+
+export const getLoadedDataTypes = (state: Store): any => state.generator.loadedDataTypes;
+export const getLoadedExportTypes = (state: Store): any => state.generator.loadedExportTypes;
+export const getExportType = (state: Store): ExportTypeFolder => state.generator.exportType;
+export const getRows = (state: Store): DataRows => state.generator.rows;
+export const getSortedRows = (state: Store): string[] => state.generator.sortedRows;
+export const isGridVisible = (state: Store): boolean => state.generator.showGrid;
+export const isPreviewVisible = (state: Store): boolean => state.generator.showPreview;
+export const getSmallScreenVisiblePanel = (state: Store): GeneratorPanel => state.generator.smallScreenVisiblePanel;
+export const getGeneratorLayout = (state: Store): GeneratorLayout => state.generator.generatorLayout;
+export const getNumPreviewRows = (state: Store): number => state.generator.numPreviewRows;
+export const shouldShowLineNumbers = (state: Store): boolean => state.generator.showLineNumbers;
+export const shouldEnableLineWrapping = (state: Store): boolean => state.generator.enableLineWrapping;
+export const getTheme = (state: Store): string => state.generator.theme;
+export const getPreviewTextSize = (state: Store): number => state.generator.previewTextSize;
+export const getDataTypePreviewData = (state: Store): any => state.generator.dataTypePreviewData;
+export const shouldShowExportSettings = (state: Store): boolean => state.generator.showExportSettings;
+export const shouldShowDataSetHistory = (state: Store): boolean => state.generator.showDataSetHistory;
+export const getSelectedDataSetHistoryItem = (state: Store): any => state.generator.selectedDataSetHistory;
+export const getExportTypeSettings = (state: Store): any => state.generator.exportTypeSettings;
+export const getExportSettingsTab = (state: Store): any => state.generator.exportSettingsTab;
+export const isGenerationSettingsPanelVisible = (state: Store): boolean => state.generator.showGenerationSettingsPanel;
+export const isHelpDialogVisible = (state: Store): boolean => state.generator.showHelpDialog;
+export const isSchemaDialogVisible = (state: Store): boolean => state.generator.showSchemaDialog;
+export const isClearPageDialogVisible = (state: Store): boolean => state.generator.showClearPageDialog;
+export const getHelpDialogSection = (state: Store): DataTypeFolder | null => state.generator.helpDialogSection;
+export const getNumRowsToGenerate = (state: Store): number => state.generator.numRowsToGenerate;
+export const getLastLayoutWidth = (state: Store): number | null => state.generator.lastLayoutWidth;
+export const getLastLayoutHeight = (state: Store): number | null => state.generator.lastLayoutHeight;
+export const isInitialDependenciesLoaded = (state: Store): boolean => state.generator.initialDependenciesLoaded;
+export const shouldStripWhitespace = (state: Store): boolean => state.generator.stripWhitespace;
+export const getCurrentDataSetId = (state: Store): number | null => state.generator.currentDataSet.dataSetId;
+export const getCurrentDataSet = (state: Store): CurrentDataSet => state.generator.currentDataSet;
+export const hasBulkActionPending = (state: Store): boolean => state.generator.bulkActionPending;
+export const isCountryNamesLoading = (state: Store): boolean => state.generator.isCountryNamesLoading;
+export const isCountryNamesLoaded = (state: Store): boolean => state.generator.isCountryNamesLoaded;
+
+export const getCurrentExportTypeWorkerUrl = createSelector(getExportType, getLoadedExportTypes, (exportType, loadedExportTypes) => {
+ const exportTypeWorkerMap = coreUtils.getExportTypeWorkerMap(loadedExportTypes);
+ return exportTypeWorkerMap[exportType] || '';
+});
+
+export const getNumRows = createSelector(getSortedRows, (rows) => rows.length);
+
+export const getSortedRowsArray = createSelector(getRows, getSortedRows, (rows, sorted) => sorted.map((id: string) => rows[id]));
+
+export const getSortedRowsArrayWithIds = createSelector(getRows, getSortedRows, (rows, sorted) =>
+ sorted.map((id: string) => ({ ...rows[id], id }))
+);
+
+export const getTitles = createSelector(getSortedRowsArray, (rows) => rows.map(({ title }) => title));
+
+export const getNonEmptySortedRows = createSelector(getSortedRowsArray, (rows) =>
+ rows.filter((row: DataRow) => row.title.trim() !== '' && row.dataType !== null && row.dataType.trim() !== '')
+);
+
+export const getSortedRowsWithDataTypeSelected = createSelector(getSortedRowsArrayWithIds, (rows) =>
+ rows.filter((row: DataRow) => row.dataType !== null && row.dataType.trim() !== '')
+);
+
+// returns everything in the grid where a Data Type has been selected
+export const getColumns = createSelector(getSortedRowsWithDataTypeSelected, (rows): (ColumnData & { id: string })[] => {
+ return rows
+ .filter((row: DataRow) => row.dataType !== null && row.dataType.trim() !== '')
+ .map(({ dataType, title, data, id }: any) => {
+ const { getMetadata } = getDataType(dataType);
+ const metadata = getMetadata ? getMetadata(data) : null;
+
+ return {
+ title,
+ dataType,
+ metadata,
+ id
+ };
+ });
+});
+
+export const getRowDataTypes = createSelector(getRows, (rows) =>
+ Object.keys(rows)
+ .filter((id: string) => rows[id].dataType !== null)
+ .map((id: string) => rows[id].dataType)
+);
+
+export const getPreviewRows = createSelector(
+ getNumPreviewRows,
+ getNonEmptySortedRows,
+ getDataTypePreviewData,
+ (numPreviewRows, rows, data) => {
+ const numRows = rows.length;
+ const formattedData: any[] = [];
+
+ for (let j = 0; j < numPreviewRows; j++) {
+ const rowData = [];
+ for (let i = 0; i < numRows; i++) {
+ const id = rows[i].id;
+ // this occurs when a new row is first added. The data is generated AFTERWARDS (see logic in onSelectDataType() action)
+ if (!data[id]) {
+ continue;
+ }
+ rowData.push(data[id][j]?.display);
+ }
+
+ if (rowData.length) {
+ formattedData.push(rowData);
+ }
+ }
+ return formattedData;
+ }
+);
+
+// TODO types for rows!
+export const convertRowsToGenerationTemplate = (rows: any): GenerationTemplate => {
+ const templateByProcessOrder: GenerationTemplate = {};
+ rows.map(({ id, title, dataType, data }: any, colIndex: number) => {
+ const processOrder = processBatches[dataType as DataTypeFolder] as number;
+ const { rowStateReducer } = getDataType(dataType);
+
+ if (!templateByProcessOrder[processOrder]) {
+ templateByProcessOrder[processOrder] = [];
+ }
+
+ templateByProcessOrder[processOrder].push({
+ id,
+ title,
+ dataType,
+ colIndex,
+
+ // settings for the DT cell. The rowStateReducer is optional: it lets developers convert the Data Type row
+ // state into something friendlier for the generation step
+ rowState: rowStateReducer ? rowStateReducer(data) : data
+ });
+ });
+
+ return templateByProcessOrder;
+};
+
+export const getGenerationTemplate = createSelector(
+ getSortedRowsWithDataTypeSelected,
+ getLoadedDataTypes, // yup, intentional! This ensures the selector will be re-ran after the data types are async loaded
+ convertRowsToGenerationTemplate
+);
+
+export const hasData = createSelector(getPreviewRows, (rows) => rows.length > 0);
+
+export const selectedExportTypeLoaded = createSelector(
+ getExportType,
+ getLoadedExportTypes,
+ (exportType, loadedExportTypes) => loadedExportTypes[exportType]
+);
+
+export const getLoadedExportTypesArray = createSelector(getLoadedExportTypes, (exportTypes) =>
+ Object.keys(exportTypes).filter((et) => exportTypes[et])
+);
+
+// returns the entire i18n content
+export const getI18n = createSelector(
+ mainSelectors.localeFileLoaded,
+ mainSelectors.getLocale,
+ (localeFileLoaded, locale: GDLocale): any | null => {
+ if (!localeFileLoaded) {
+ return null;
+ }
+ return langUtils.getStrings(locale);
+ }
+);
+
+// TODO hmm.. this kinda belongs in `main` not `generator`
+export const getCoreI18n = createSelector(mainSelectors.getLocale, (locale): any | null => {
+ const strings = langUtils.getStrings(locale);
+ return strings ? strings.core : null;
+});
+
+export const getCountryI18n = createSelector(mainSelectors.getLocale, (locale): any | null => {
+ const strings = langUtils.getStrings(locale);
+ return strings ? strings.countries : null;
+});
+
+export const getDataTypeI18n = createSelector(
+ mainSelectors.localeFileLoaded,
+ mainSelectors.getLocale,
+ (localeFileLoaded, locale): any | null => {
+ if (!localeFileLoaded) {
+ return null;
+ }
+ const strings = langUtils.getStrings(locale);
+ return strings ? strings.dataTypes : null;
+ }
+);
+
+export const getExportTypeI18n = createSelector(mainSelectors.getLocale, getExportType, (locale, exportType): any | null => {
+ const strings = langUtils.getStrings(locale);
+ return strings ? strings.exportTypes[exportType] : null;
+});
+
+export const getExportTypeColumnTitle = createSelector(getExportTypeI18n, (i18n) => i18n.COL_TITLE);
+
+// Export Types can optionally override the label that appears in the Preview panel. For example, instead of
+// just showing "Programming Language", they can show "PHP" or "Programming Language: Perl" or whatever they
+// want. By default it'll just show the localized Export Type name
+// TODO: need validation on the export type to confirm `EXPORT_TYPE_NAME` exists. Perhaps a separate grunt `validate` task
+// that's integrated into the build process to prevent incomplete/invalid bundles from being included
+export const getExportTypeLabel = createSelector(
+ getExportType,
+ getExportTypeSettings,
+ getExportTypeI18n,
+ getLoadedExportTypes,
+ (exportType, exportTypeSettings, i18n, loadedExportTypes): string => {
+ if (loadedExportTypes[exportType]) {
+ const etSettings = exportTypeSettings[exportType];
+ const label = exportTypeUtilsGetExportTypeLabel(exportType, etSettings);
+ if (label) {
+ return label;
+ }
+ }
+ return i18n.EXPORT_TYPE_NAME;
+ }
+);
+
+export const getCurrentExportTypeSettings = createSelector(getExportType, getExportTypeSettings, (exportType, settings) =>
+ exportType ? settings[exportType] : {}
+);
+
+// wrapper function for the Export Type's getCodeMirrorMode function. This ensures it's loaded and does the work
+// of figuring out what export type is currently loaded
+export const getCodeMirrorMode = createSelector(
+ getExportType,
+ getLoadedExportTypes,
+ getExportTypeSettings,
+ (exportType, loadedExportTypes, exportTypeSettings) => {
+ if (!loadedExportTypes[exportType]) {
+ return '';
+ }
+ return exportTypeUtilsGetCodeMirrorMode(exportType, exportTypeSettings[exportType]);
+ }
+);
+
+export const getExportTypeTitleValidationFunction = createSelector(getExportType, getLoadedExportTypes, (exportType, loadedExportTypes) => {
+ if (!loadedExportTypes[exportType]) {
+ return '';
+ }
+ return exportTypeGetExportTypeTitleValidation(exportType);
+});
+
+export const getDataSetSavePackage = createSelector(
+ getExportType,
+ getCurrentExportTypeSettings,
+ getRows,
+ getSortedRows,
+ (exportType, exportTypeSettings, rows, sortedRows) => ({
+ exportType,
+ exportTypeSettings,
+ rows,
+ sortedRows
+ })
+);
+
+export const currentDataSetNeedsCountryNames = createSelector(getSortedRowsArray, (rows) =>
+ rows.reduce((needsCountryNames, { metadata }) => {
+ return needsCountryNames ? true : !!metadata?.useCountryNames;
+ }, false)
+);
+
+export const previewPanelDependenciesLoaded = createSelector(
+ getRowDataTypes,
+ getExportType,
+ getLoadedDataTypes,
+ getLoadedExportTypes,
+ mainSelectors.localeFileLoaded,
+ isCountryNamesLoaded,
+ currentDataSetNeedsCountryNames,
+ (dataTypes, exportType, loadedDataTypes, loadedExportTypes, localeFileLoaded, countryNamesLoaded, needsCountryNames) => {
+ if (!localeFileLoaded || !loadedExportTypes[exportType]) {
+ return false;
+ }
+
+ const allDataTypesLoaded = dataTypes.every((i) => loadedDataTypes[i as DataTypeFolder]);
+ if (!allDataTypesLoaded) {
+ return false;
+ }
+
+ // if the data set has Names set, check the regional names have been loaded
+ if (needsCountryNames && !countryNamesLoaded) {
+ return false;
+ }
+
+ return true;
+ }
+);
+
+export const shouldGeneratePreviewRows = createSelector(
+ getRowDataTypes,
+ previewPanelDependenciesLoaded,
+ getRows,
+ getDataTypePreviewData,
+ (dataTypes, dependenciesLoaded, rowsObj, previewData) => {
+ if (!dataTypes.length) {
+ return false;
+ }
+ if (!dependenciesLoaded) {
+ return false;
+ }
+
+ const alreadyGenerated = Object.keys(rowsObj).every((key: string) => !!previewData[key]);
+ if (alreadyGenerated) {
+ return false;
+ }
+
+ return true;
+ }
+);
+
+// generates a schema of all the data needed to recreate the current data set
+export const getGenerationSchema = createSelector(
+ getRows,
+ getSortedRows,
+ getExportType,
+ getCurrentExportTypeSettings,
+ (rows, sortedRows, exportType, exportTypeSettings): any => {
+ const nonEmptyRows = sortedRows.filter((id) => !!rows[id].dataType);
+ return JSON.stringify(
+ {
+ rows: nonEmptyRows.map((rowId) => {
+ const { title, dataType, data } = rows[rowId];
+ return { title, dataType, data };
+ }),
+ exportType,
+ exportTypeSettings
+ // nope. These are settings specific to the particular generation, not the data itself. Separate them
+ // stripWhitespace,
+ // numRows: 1
+ },
+ null,
+ ' '
+ );
+ }
+);
diff --git a/apps/client/src/core/store/index.ts b/apps/client/src/core/store/index.ts
new file mode 100644
index 000000000..217b143bb
--- /dev/null
+++ b/apps/client/src/core/store/index.ts
@@ -0,0 +1,93 @@
+/* istanbul ignore file */
+import { persistStore, persistReducer } from 'redux-persist';
+import { Persistor } from 'redux-persist/es/types';
+import { combineReducers } from 'redux';
+import { configureStore } from '@reduxjs/toolkit';
+// import actionsInterceptor from '../actionInterceptor'; // TODO
+import storage from 'redux-persist/lib/storage';
+import mainReducer from './main/main.reducer';
+import generatorReducer from './generator/generator.reducer';
+import packetsReducer from './packets/packets.reducer';
+import accountReducer from './account/account.reducer';
+
+let persistor: Persistor;
+function initStore(state: any): any {
+ const rootPersistConfig = {
+ key: 'root',
+ storage,
+ blacklist: ['generator', 'main', 'packets']
+ };
+
+ const generatorPersistConfig = {
+ key: 'generator',
+ storage,
+ blacklist: [
+ 'initialDependenciesLoaded',
+ 'loadedDataTypes',
+ 'loadedExportTypes',
+ 'isGenerating',
+ 'numGeneratedRows',
+ 'dataTypePreviewData',
+ 'bulkActionPending',
+ 'isCountryNamesLoading',
+ 'isCountryNamesLoaded'
+ ]
+ };
+
+ const mainPersistConfig = {
+ key: 'main',
+ storage,
+ blacklist: [
+ 'localeFileLoaded',
+ 'isOnloadAuthDetermined',
+ 'tourBundleLoaded',
+ 'dialogProcessing',
+ 'accountsCurrentPage',
+ 'accountsSortCol',
+ 'accountsSortDir',
+ 'accountsFilterStr'
+ ]
+ };
+
+ // TODO should be able to just blacklist the entire section and not have to pinpoint them here... doc really not clear
+ const packetsPersistConfig = {
+ key: 'packets',
+ storage,
+ blacklist: ['currentPacketId', 'packetIds', 'packets']
+ };
+
+ const accountPersistConfig = {
+ key: 'account',
+ storage,
+ blacklist: ['yourAccount', 'editAccount']
+ };
+
+ const rootReducer = combineReducers({
+ generator: persistReducer(generatorPersistConfig, generatorReducer),
+ main: persistReducer(mainPersistConfig, mainReducer),
+ packets: persistReducer(packetsPersistConfig, packetsReducer),
+ account: persistReducer(accountPersistConfig, accountReducer)
+ });
+
+ const persistedRootReducer = persistReducer(rootPersistConfig, rootReducer);
+
+ const store = configureStore({
+ reducer: persistedRootReducer,
+ preloadedState: state
+ // composeEnhancers(applyMiddleware(thunk, actionsInterceptor), ...enhancers)
+ });
+
+ persistor = persistStore(store);
+
+ return store;
+}
+
+// for testing we set up our own mock stores with the subset of whatever we want to examine
+let store: any;
+if (process.env.NODE_ENV !== 'test') {
+ store = initStore({});
+}
+
+export default store;
+
+export { persistor };
diff --git a/apps/client/src/core/store/main/__tests__/main.actions.test.ts b/apps/client/src/core/store/main/__tests__/main.actions.test.ts
new file mode 100644
index 000000000..86ea49bad
--- /dev/null
+++ b/apps/client/src/core/store/main/__tests__/main.actions.test.ts
@@ -0,0 +1,58 @@
+import { combineReducers, createStore } from 'redux';
+import reducer from '../main.reducer';
+import * as selectors from '../main.selectors';
+import * as actions from '../main.actions';
+import { setAuthenticationData } from '../main.actions';
+import C from '@generatedata/config/constants';
+import { AccountStatus } from '~types/account';
+import { AuthMethod } from '~types/general';
+
+describe('main actions', () => {
+ let store: any;
+ beforeEach(() => {
+ store = createStore(
+ combineReducers({
+ main: reducer
+ })
+ );
+ });
+
+ it('the locale file is not loaded by default', () => {
+ expect(selectors.localeFileLoaded(store.getState())).toEqual(false);
+ });
+
+ it('sets the locale as loaded', () => {
+ store.dispatch(actions.setLocaleFileLoaded('en'));
+ expect(selectors.localeFileLoaded(store.getState())).toEqual(true);
+ });
+
+ it('authorization sets appropriate values', () => {
+ expect(selectors.getAppStateVersion(store.getState())).toEqual(C.APP_STATE_VERSION);
+ expect(selectors.getAuthMethod(store.getState())).toEqual('default');
+ expect(selectors.isLoggedIn(store.getState())).toEqual(false);
+ expect(selectors.getAuthToken(store.getState())).toEqual('');
+
+ store.dispatch(
+ setAuthenticationData({
+ authMethod: AuthMethod.google,
+ token: '123456',
+ accountId: 5,
+ firstName: 'Jim',
+ lastName: 'Beam',
+ email: 'jim@beam.net',
+ country: 'United States',
+ region: 'Montana',
+ profileImage: 'image-here.jpg',
+ expiryDate: '1000010101001',
+ dateCreated: '1000010101001',
+ accountType: 'admin',
+ accountStatus: AccountStatus.live,
+ numRowsGenerated: 50000
+ })
+ );
+
+ expect(selectors.getAuthMethod(store.getState())).toEqual('google');
+ expect(selectors.isLoggedIn(store.getState())).toEqual(true);
+ expect(selectors.getAuthToken(store.getState())).toEqual('123456');
+ });
+});
diff --git a/apps/client/src/core/store/main/__tests__/main.store.test.ts b/apps/client/src/core/store/main/__tests__/main.store.test.ts
new file mode 100644
index 000000000..3506ccd2e
--- /dev/null
+++ b/apps/client/src/core/store/main/__tests__/main.store.test.ts
@@ -0,0 +1,45 @@
+import { createStore, applyMiddleware, Store } from 'redux';
+// import { FlushThunks } from 'redux-testkit';
+import { configureStore } from '@reduxjs/toolkit';
+import { rootReducer } from '../../../../../tests/testHelpers';
+import * as actions from '../main.actions';
+import * as selectors from '../main.selectors';
+
+describe('accounts section', () => {
+ // let flushThunks;
+ let store: Store;
+
+ beforeEach(() => {
+ // flushThunks = FlushThunks.createMiddleware();
+ store = configureStore({
+ reducer: rootReducer
+ });
+ });
+
+ it('resetting store returns data to default state', () => {
+ expect(selectors.localeFileLoaded(store.getState())).toEqual(false);
+
+ store.dispatch(actions.setLocaleFileLoaded('en'));
+ expect(selectors.localeFileLoaded(store.getState())).toEqual(true);
+
+ store.dispatch(actions.resetStore());
+ expect(selectors.localeFileLoaded(store.getState())).toEqual(false);
+ });
+
+ it('login dialog visibility', () => {
+ expect(selectors.shouldShowLoginDialog(store.getState())).toEqual(false);
+
+ store.dispatch(actions.setLoginDialogVisibility(true));
+ expect(selectors.shouldShowLoginDialog(store.getState())).toEqual(true);
+
+ store.dispatch(actions.setLoginDialogVisibility(false));
+ expect(selectors.shouldShowLoginDialog(store.getState())).toEqual(false);
+ });
+
+ it('sets auth', () => {
+ expect(selectors.isLoggedIn(store.getState())).toEqual(false);
+
+ store.dispatch(actions.setAuthenticated(true));
+ expect(selectors.isLoggedIn(store.getState())).toEqual(true);
+ });
+});
diff --git a/apps/client/src/core/store/main/main.actions.ts b/apps/client/src/core/store/main/main.actions.ts
new file mode 100644
index 000000000..229a8d574
--- /dev/null
+++ b/apps/client/src/core/store/main/main.actions.ts
@@ -0,0 +1,449 @@
+import { Dispatch } from 'redux';
+import { gql } from '@apollo/client';
+import Cookies from 'js-cookie';
+import { AccountStatusFilter, AuthMethod, GDAction, GDLocale } from '~types/general';
+import * as langUtils from '@generatedata/utils/lang';
+import { getStrings, getCurrentLocalizedPath } from '@generatedata/utils/lang';
+import { apolloClient } from '../../apolloClient';
+import { getCurrentPage, getLocale } from '~store/main/main.selectors';
+import { setAuthTokenRefresh } from '@generatedata/utils/auth';
+import { AccountStatus, AccountType, SelectedAccountTab } from '~types/account';
+import store from '~core/store';
+import { onChangeTab, showSaveDataSetDialog } from '~store/account/account.actions';
+import { addToast, setTourComponents } from '@generatedata/utils/general';
+import { getGeneratorPageRoute, isGeneratorPage, updateBodyClass } from '~utils/routeUtils';
+import * as actions from '~store/generator/generator.actions';
+import { CLEAR_GRID } from '~store/generator/generator.actions';
+import C from '@generatedata/config/constants';
+import { SaveDataDialogType } from '~store/account/account.reducer';
+import { localeFileMap } from '../../../../_localeFileMap';
+import { ColSortDir } from '~components/tables/TableHeader.component';
+
+export const LOCALE_FILE_LOADED = 'LOCALE_FILE_LOADED';
+export const setLocaleFileLoaded = (locale: GDLocale): GDAction => ({
+ type: LOCALE_FILE_LOADED,
+ payload: {
+ locale
+ }
+});
+
+export const LOCALE_FILE_LOADING = 'LOCALE_FILE_LOADING';
+export const setLocaleFileLoading = (): GDAction => ({
+ type: LOCALE_FILE_LOADING
+});
+
+export const selectLocale =
+ (locale: GDLocale, navigate?: any): any =>
+ (dispatch: Dispatch): any => {
+ dispatch(setLocaleFileLoading());
+
+ window.gd = {};
+ window.gd.localeLoaded = (strings: any): void => {
+ langUtils.setLocale(locale, strings);
+ dispatch(setLocaleFileLoaded(locale));
+ Cookies.set('lang', locale);
+
+ const htmlTag = document.querySelector('html');
+ if (htmlTag) {
+ htmlTag.setAttribute('lang', locale);
+ }
+
+ if (navigate) {
+ navigate(getCurrentLocalizedPath(locale));
+ }
+ };
+ const s = document.createElement('script');
+ const filename = localeFileMap[locale];
+ s.src = `./${filename}`;
+ document.body.appendChild(s);
+ };
+
+export const RESET_STORE = 'RESET_STORE';
+export const resetStore = (): GDAction => ({ type: RESET_STORE });
+
+export const PAGE_CHANGE = 'PAGE_CHANGE';
+export const initRouteListener = (history: any): void => {
+ updateBodyClass(store, history.location.pathname);
+
+ history.listen((location: any) => {
+ updateBodyClass(store, location.pathname);
+ });
+};
+
+export const SET_LOGIN_DIALOG_VISIBILITY = 'SET_LOGIN_DIALOG_VISIBILITY';
+export const setLoginDialogVisibility = (visible: boolean, email = ''): GDAction => ({
+ type: SET_LOGIN_DIALOG_VISIBILITY,
+ payload: {
+ visible,
+ email
+ }
+});
+
+export const SET_PASSWORD_RESET_DIALOG_VISIBILITY = 'SET_PASSWORD_RESET_DIALOG_VISIBILITY';
+export const setPasswordResetDialogVisibility = (visible: boolean, email = ''): GDAction => ({
+ type: SET_PASSWORD_RESET_DIALOG_VISIBILITY,
+ payload: {
+ visible,
+ email
+ }
+});
+
+export const SET_AUTHENTICATION_DATA = 'SET_AUTHENTICATION_DATA';
+
+export type AuthData = {
+ token: string;
+ accountId: number;
+ firstName: string;
+ lastName: string;
+ email: string;
+ country: string;
+ region: string;
+ profileImage: string;
+ expiryDate: string;
+ dateCreated: string;
+ accountType: AccountType;
+ accountStatus: AccountStatus;
+ numRowsGenerated: number;
+ authMethod?: AuthMethod;
+};
+
+export const setAuthenticationData = (authData: AuthData): GDAction => ({
+ type: SET_AUTHENTICATION_DATA,
+ payload: authData
+});
+
+export const AUTHENTICATED = 'AUTHENTICATED';
+export const setAuthenticated = (authenticated = true): GDAction => ({
+ type: AUTHENTICATED,
+ payload: {
+ authenticated
+ }
+});
+
+export const START_DIALOG_PROCESSING = 'START_DIALOG_PROCESSING';
+export const startDialogProcessing = (): GDAction => ({
+ type: START_DIALOG_PROCESSING
+});
+
+export const STOP_DIALOG_PROCESSING = 'STOP_DIALOG_PROCESSING';
+export const stopDialogProcessing = (): GDAction => ({
+ type: STOP_DIALOG_PROCESSING
+});
+
+// hacky, but if we need something like this elsewhere I can revisit. This is used by the Login button in Save dialog.
+// When the user clicks that, we want to return them to the Save dialog after authentication. The register process is more
+// complicated so I think returning them to the save dialog after all that might be a bit much. They can always just
+// re-click the Save button at that step (nothing would have been lost, unlike with v3)
+let loginFlow = '';
+export const setReturnToSaveDataSet = (): void => {
+ loginFlow = 'fromSaveDataSet';
+};
+
+export const CLEAR_DEFAULT_EMAIL = 'CLEAR_DEFAULT_EMAIL';
+export const clearLoginFlow = (): GDAction => {
+ loginFlow = '';
+
+ return { type: CLEAR_DEFAULT_EMAIL };
+};
+
+export const LOGIN_ERROR = 'LOGIN_ERROR';
+export const setLoginError = (): GDAction => ({ type: LOGIN_ERROR });
+
+// default authentication
+export const login = (email: string, password: string, navigate: any, onLoginError: any): any => {
+ return async (dispatch: Dispatch): Promise => {
+ dispatch(startDialogProcessing());
+
+ const response = await apolloClient.mutate({
+ mutation: gql`
+ mutation LoginMutation($email: String!, $password: String!) {
+ login(email: $email, password: $password) {
+ token
+ tokenExpiry
+ error
+ refreshToken
+ success
+ accountId
+ firstName
+ lastName
+ email
+ country
+ region
+ expiryDate
+ accountType
+ accountStatus
+ dateCreated
+ numRowsGenerated
+ profileImage
+ wasOneTimeLogin
+ }
+ }
+ `,
+ variables: { email, password }
+ });
+
+ if (response.data.login.success) {
+ const { tokenExpiry, refreshToken } = response.data.login;
+ dispatch(
+ setAuthenticationData({
+ ...response.data.login,
+ authMethod: 'default'
+ })
+ );
+
+ Cookies.set('refreshToken', refreshToken, {
+ expires: new Date(tokenExpiry)
+ });
+
+ if (response.data.login.wasOneTimeLogin) {
+ onOneTimeLoginSuccess(tokenExpiry, password, navigate, dispatch);
+ } else {
+ onLoginSuccess(tokenExpiry, false, dispatch);
+ }
+ } else {
+ dispatch(setLoginError());
+ onLoginError(response.data.login.error);
+ }
+ };
+};
+
+export const onLoginSuccess = (tokenExpiry: number | null, onPageRender: boolean, dispatch: Dispatch): void => {
+ const i18n = getStrings();
+
+ if (tokenExpiry) {
+ setAuthTokenRefresh(tokenExpiry, (): any => updateRefreshToken()(dispatch));
+ }
+
+ dispatch(setLoginDialogVisibility(false));
+
+ if (!onPageRender) {
+ addToast({
+ type: 'success',
+ message: i18n.core.nowLoggedIn
+ });
+
+ if (loginFlow === 'fromSaveDataSet') {
+ dispatch(showSaveDataSetDialog(SaveDataDialogType.save));
+ loginFlow = '';
+ }
+ }
+};
+
+export const SET_ONE_TIME_PASSWORD = 'SET_ONE_TIME_PASSWORD';
+export const setOneTimePassword = (password: string): GDAction => ({
+ type: SET_ONE_TIME_PASSWORD,
+ payload: { password }
+});
+
+export const onOneTimeLoginSuccess = (tokenExpiry: number, password: string, navigate: any, dispatch: Dispatch): void => {
+ const i18n = getStrings();
+ setAuthTokenRefresh(tokenExpiry, (): any => updateRefreshToken()(dispatch));
+ dispatch(setLoginDialogVisibility(false));
+ dispatch(setOneTimePassword(password));
+
+ navigate('/account');
+ dispatch(onChangeTab(SelectedAccountTab.changePassword));
+
+ addToast({
+ type: 'success',
+ message: i18n.core.nowLoggedIn
+ });
+};
+
+export const LOGOUT = 'LOGOUT';
+export const logout =
+ (): any =>
+ async (dispatch: Dispatch): Promise => {
+ const i18n = getStrings();
+
+ Cookies.remove('refreshToken');
+
+ dispatch({ type: LOGOUT });
+ dispatch({ type: CLEAR_GRID });
+ dispatch(actions.addRows(C.NUM_DEFAULT_ROWS));
+
+ addToast({
+ type: 'success',
+ message: i18n.core.nowLoggedOut
+ });
+
+ // doesn't awfully matter if this fails. It's just for cleanup
+ await apolloClient.mutate({
+ mutation: gql`
+ mutation Logout {
+ logout {
+ success
+ }
+ }
+ `
+ });
+ };
+
+export const SET_AUTH_TOKEN = 'SET_AUTH_TOKEN';
+export const setAuthToken = (token: string): GDAction => ({
+ type: SET_AUTH_TOKEN,
+ payload: { token }
+});
+
+export const REFRESHING_TOKEN = 'REFRESHING_TOKEN';
+export const updateRefreshToken =
+ () =>
+ async (dispatch: Dispatch): Promise => {
+ dispatch({ type: REFRESHING_TOKEN });
+
+ const response = await apolloClient.mutate({
+ mutation: gql`
+ mutation RefreshToken {
+ refreshToken {
+ token
+ tokenExpiry
+ refreshToken
+ success
+ firstName
+ lastName
+ email
+ country
+ region
+ expiryDate
+ accountType
+ dateCreated
+ numRowsGenerated
+ profileImage
+ }
+ }
+ `
+ });
+
+ const success = response.data.refreshToken.success;
+ if (success) {
+ const { token, tokenExpiry, refreshToken } = response.data.refreshToken;
+
+ Cookies.set('refreshToken', refreshToken, {
+ expires: new Date(tokenExpiry)
+ });
+
+ setAuthTokenRefresh(tokenExpiry, (): any => updateRefreshToken()(dispatch));
+ dispatch(setAuthenticationData(response.data.refreshToken));
+ dispatch(setAuthToken(token));
+ } else {
+ // console.log('token NOT refreshed -- user logged out');
+ }
+
+ dispatch(setAuthenticated(success));
+ dispatch(setOnloadAuthDetermined());
+ };
+
+export const ONLOAD_AUTH_DETERMINED = 'ONLOAD_AUTH_DETERMINED';
+export const setOnloadAuthDetermined = (): GDAction => ({
+ type: ONLOAD_AUTH_DETERMINED
+});
+
+export const SHOW_TOUR_INTRO_DIALOG = 'SHOW_TOUR_INTRO_DIALOG';
+export const showTourIntroDialog =
+ (navigate?: any) =>
+ (dispatch: Dispatch, getState: any): any => {
+ const state = getState();
+ const currentPage = getCurrentPage(state);
+ const locale = getLocale(state);
+ const generatorRoute = getGeneratorPageRoute(locale);
+
+ // the tour is specific to the generator page, so always redirect there when showing/hiding it
+ if (navigate && !isGeneratorPage(currentPage, locale)) {
+ navigate(generatorRoute);
+ }
+
+ dispatch({ type: SHOW_TOUR_INTRO_DIALOG });
+ };
+
+export const HIDE_TOUR_INTRO_DIALOG = 'HIDE_TOUR_INTRO_DIALOG';
+export const hideTourIntroDialog = (): GDAction => ({
+ type: HIDE_TOUR_INTRO_DIALOG
+});
+
+export const TOUR_BUNDLE_LOADED = 'TOUR_BUNDLE_LOADED';
+export const loadTourBundle =
+ (): any =>
+ (dispatch: Dispatch): void => {
+ const i18n = getStrings();
+
+ // TODO check hashing of bundle here
+ import(
+ /* webpackChunkName: "tour" */
+ /* webpackMode: "lazy" */
+ '../../../tours'
+ )
+ .then((resp) => {
+ setTourComponents(resp.default);
+ dispatch({ type: TOUR_BUNDLE_LOADED });
+ })
+ .catch(() => {
+ dispatch(hideTourIntroDialog());
+
+ addToast({
+ type: 'success',
+ message: i18n.core.problemLoadingTour
+ });
+ });
+ };
+
+export const sendPasswordResetEmail =
+ (email: string, onLoginError: any): any =>
+ async (dispatch: Dispatch): Promise => {
+ const i18n = getStrings();
+
+ dispatch(startDialogProcessing());
+
+ const response = await apolloClient.mutate({
+ mutation: gql`
+ mutation SendPasswordResetEmailMutation($email: String!) {
+ sendPasswordResetEmail(email: $email) {
+ success
+ }
+ }
+ `,
+ variables: { email }
+ });
+
+ if (response.data.sendPasswordResetEmail.success) {
+ addToast({
+ type: 'success',
+ message: i18n.core.passwordResetMsg
+ });
+ dispatch(setPasswordResetDialogVisibility(false));
+ dispatch(setLoginDialogVisibility(true, email));
+ dispatch(stopDialogProcessing());
+ } else {
+ dispatch(setLoginError());
+ onLoginError();
+ }
+ };
+
+export const SET_ACCOUNTS_SORT_DIR = 'SET_ACCOUNTS_SORT_DIR';
+export const setAccountsSortDir = (sortDir: ColSortDir): any => ({
+ type: SET_ACCOUNTS_SORT_DIR,
+ payload: { sortDir }
+});
+
+export const SET_ACCOUNTS_SORT_COL = 'SET_ACCOUNTS_SORT_COL';
+export const setAccountsSortCol = (sortCol: string): any => ({
+ type: SET_ACCOUNTS_SORT_COL,
+ payload: { sortCol }
+});
+
+export const SET_ACCOUNTS_CURRENT_PAGE = 'SET_ACCOUNTS_CURRENT_PAGE';
+export const setAccountsCurrentPage = (page: number): any => ({
+ type: SET_ACCOUNTS_CURRENT_PAGE,
+ payload: { page }
+});
+
+export const SET_ACCOUNTS_FILTER_STRING = 'SET_ACCOUNTS_FILTER_STRING';
+export const setAccountsFilterString = (filter: string): any => ({
+ type: SET_ACCOUNTS_FILTER_STRING,
+ payload: { filter }
+});
+
+export const SET_ACCOUNT_STATUS_FILTER = 'SET_ACCOUNT_STATUS_FILTER';
+export const setAccountStatusFilter = (status: AccountStatusFilter): any => ({
+ type: SET_ACCOUNT_STATUS_FILTER,
+ payload: { status }
+});
diff --git a/apps/client/src/core/store/main/main.reducer.ts b/apps/client/src/core/store/main/main.reducer.ts
new file mode 100644
index 000000000..e19d2ce75
--- /dev/null
+++ b/apps/client/src/core/store/main/main.reducer.ts
@@ -0,0 +1,184 @@
+import { AnyAction } from 'redux';
+import { produce } from 'immer';
+import * as actions from './main.actions';
+import C from '@generatedata/config/constants';
+import { AccountStatusFilter, AuthMethod, GDLocale } from '~types/general';
+import { ColSortDir } from '~components/tables/TableHeader.component';
+import clientConfig from '@generatedata/config/clientConfig';
+
+export type MainState = {
+ appStateVersion: number;
+ authMethod: AuthMethod;
+ localeFileLoading: boolean;
+ localeFileLoaded: boolean;
+ locale: GDLocale;
+ showLoginDialog: boolean;
+ loginDialogDefaultEmail: string;
+ showPasswordResetDialog: boolean;
+ passwordResetDialogDefaultEmail: string;
+ isLoggedIn: boolean;
+ authToken: string;
+ isOnloadAuthDetermined: boolean;
+ isRefreshingToken: boolean;
+ dialogProcessing: boolean;
+
+ // not 100% sure why this is stored in state - but possibly just to allow components to re-render when it changes
+ currentPage: string;
+ tourIntroDialogVisible: boolean;
+ tourBundleLoaded: boolean;
+ accountsCurrentPage: number;
+ accountsSortCol: string;
+ accountsSortDir: ColSortDir;
+ accountsFilterStr: string;
+ accountStatusFilter: AccountStatusFilter;
+};
+
+export const initialState: MainState = {
+ appStateVersion: C.APP_STATE_VERSION,
+ authMethod: AuthMethod.default,
+ localeFileLoading: false,
+ localeFileLoaded: false,
+ locale: clientConfig.appSettings.GD_DEFAULT_LOCALE,
+ showLoginDialog: false,
+ loginDialogDefaultEmail: '',
+ showPasswordResetDialog: false,
+ passwordResetDialogDefaultEmail: '',
+ isLoggedIn: false,
+ authToken: '',
+ isOnloadAuthDetermined: false,
+ isRefreshingToken: false,
+ dialogProcessing: false,
+ currentPage: '',
+ tourIntroDialogVisible: false,
+ tourBundleLoaded: false,
+ accountsCurrentPage: 1,
+ accountsSortCol: 'lastName',
+ accountsSortDir: ColSortDir.asc,
+ accountsFilterStr: '',
+ accountStatusFilter: AccountStatusFilter.all
+};
+
+export const reducer = produce((draft: MainState, action: AnyAction) => {
+ switch (action.type) {
+ case actions.RESET_STORE:
+ Object.keys(initialState).forEach((key) => {
+ // @ts-ignore-line
+ draft[key] = initialState[key];
+ });
+ break;
+
+ case actions.LOCALE_FILE_LOADING:
+ draft.localeFileLoading = true;
+ break;
+
+ case actions.LOCALE_FILE_LOADED:
+ draft.locale = action.payload.locale;
+ draft.localeFileLoaded = true;
+ draft.localeFileLoading = false;
+ break;
+
+ case actions.SET_LOGIN_DIALOG_VISIBILITY:
+ draft.showLoginDialog = action.payload.visible;
+
+ if (action.payload.email) {
+ draft.loginDialogDefaultEmail = action.payload.email;
+ }
+ break;
+
+ case actions.SET_PASSWORD_RESET_DIALOG_VISIBILITY:
+ draft.showPasswordResetDialog = action.payload.visible;
+ if (action.payload.email) {
+ draft.passwordResetDialogDefaultEmail = action.payload.email;
+ }
+ break;
+
+ case actions.AUTHENTICATED:
+ draft.isLoggedIn = action.payload.authenticated;
+ draft.isRefreshingToken = true; // yup, even if they're not authenticated. Difficult var name - only applies for logged in users
+ break;
+
+ case actions.SET_AUTHENTICATION_DATA:
+ draft.isLoggedIn = true;
+ draft.dialogProcessing = false;
+ draft.authToken = action.payload.token;
+ draft.authMethod = action.payload.authMethod;
+ break;
+
+ case actions.LOGOUT:
+ draft.isLoggedIn = false;
+ draft.authToken = '';
+ draft.accountsCurrentPage = 1;
+ draft.accountsSortCol = 'lastName';
+ draft.accountsSortDir = ColSortDir.asc;
+ draft.accountsFilterStr = '';
+ break;
+
+ case actions.REFRESHING_TOKEN:
+ draft.isRefreshingToken = false;
+ break;
+
+ case actions.SET_AUTH_TOKEN:
+ draft.authToken = action.payload.token;
+ break;
+
+ case actions.START_DIALOG_PROCESSING:
+ draft.dialogProcessing = true;
+ break;
+
+ case actions.STOP_DIALOG_PROCESSING:
+ draft.dialogProcessing = false;
+ break;
+
+ case actions.LOGIN_ERROR:
+ draft.dialogProcessing = false;
+ break;
+
+ case actions.ONLOAD_AUTH_DETERMINED:
+ draft.isOnloadAuthDetermined = true;
+ break;
+
+ case actions.PAGE_CHANGE:
+ draft.currentPage = action.payload.page;
+ break;
+
+ case actions.SHOW_TOUR_INTRO_DIALOG:
+ draft.tourIntroDialogVisible = true;
+ break;
+
+ case actions.HIDE_TOUR_INTRO_DIALOG:
+ draft.tourIntroDialogVisible = false;
+ break;
+
+ case actions.TOUR_BUNDLE_LOADED:
+ draft.tourBundleLoaded = true;
+ break;
+
+ case actions.CLEAR_DEFAULT_EMAIL:
+ draft.loginDialogDefaultEmail = '';
+ break;
+
+ case actions.SET_ACCOUNTS_SORT_DIR:
+ draft.accountsSortDir = action.payload.sortDir;
+ break;
+
+ case actions.SET_ACCOUNTS_SORT_COL:
+ draft.accountsSortCol = action.payload.sortCol;
+ break;
+
+ case actions.SET_ACCOUNTS_CURRENT_PAGE:
+ draft.accountsCurrentPage = action.payload.page;
+ break;
+
+ case actions.SET_ACCOUNTS_FILTER_STRING:
+ draft.accountsCurrentPage = 1;
+ draft.accountsFilterStr = action.payload.filter;
+ break;
+
+ case actions.SET_ACCOUNT_STATUS_FILTER:
+ draft.accountsCurrentPage = 1;
+ draft.accountStatusFilter = action.payload.status;
+ break;
+ }
+}, initialState);
+
+export default reducer;
diff --git a/apps/client/src/core/store/main/main.selectors.ts b/apps/client/src/core/store/main/main.selectors.ts
new file mode 100644
index 000000000..8a49b5f9b
--- /dev/null
+++ b/apps/client/src/core/store/main/main.selectors.ts
@@ -0,0 +1,24 @@
+import { AccountStatusFilter, AuthMethod, GDLocale, Store } from '~types/general';
+import { ColSortDir } from '~components/tables/TableHeader.component';
+
+export const getAppStateVersion = (state: Store): number => state.main.appStateVersion;
+export const getAuthMethod = (state: Store): AuthMethod => state.main.authMethod;
+export const getLocale = (state: Store): GDLocale => state.main.locale;
+export const isLocaleFileLoading = (state: Store): boolean => state.main.localeFileLoading;
+export const localeFileLoaded = (state: Store): boolean => state.main.localeFileLoaded;
+export const shouldShowLoginDialog = (state: Store): boolean => state.main.showLoginDialog;
+export const shouldShowPasswordResetDialog = (state: Store): boolean => state.main.showPasswordResetDialog;
+export const isLoggedIn = (state: Store): boolean => state.main.isLoggedIn;
+export const isOnloadAuthDetermined = (state: Store): boolean => state.main.isOnloadAuthDetermined;
+export const isDialogProcessing = (state: Store): boolean => state.main.dialogProcessing;
+export const getAuthToken = (state: Store): string => state.main.authToken;
+export const getCurrentPage = (state: Store): string => state.main.currentPage;
+export const tourIntroDialogVisible = (state: Store): boolean => state.main.tourIntroDialogVisible;
+export const isTourBundleLoaded = (state: Store): boolean => state.main.tourBundleLoaded;
+export const getLoginDefaultEmail = (state: Store): string => state.main.loginDialogDefaultEmail;
+export const getPasswordResetDialogDefaultEmail = (state: Store): string => state.main.passwordResetDialogDefaultEmail;
+export const getAccountsCurrentPage = (state: Store): number => state.main.accountsCurrentPage;
+export const getAccountsSortCol = (state: Store): string => state.main.accountsSortCol;
+export const getAccountsSortDir = (state: Store): ColSortDir => state.main.accountsSortDir;
+export const getAccountsFilterStr = (state: Store): string => state.main.accountsFilterStr;
+export const getAccountStatusFilter = (state: Store): AccountStatusFilter => state.main.accountStatusFilter;
diff --git a/apps/client/src/core/store/packets/__tests__/packets.selectors.test.ts b/apps/client/src/core/store/packets/__tests__/packets.selectors.test.ts
new file mode 100644
index 000000000..2025df112
--- /dev/null
+++ b/apps/client/src/core/store/packets/__tests__/packets.selectors.test.ts
@@ -0,0 +1,72 @@
+import { getNewPacket, initialState as initialPacketState, PacketsState } from '../packets.reducer';
+import * as selectors from '../packets.selectors';
+import { getInitialState } from '../../generator/generator.reducer';
+import { initialState as initialMainState } from '../../main/main.reducer';
+import { initialState as initialAccountState } from '../../account/account.reducer';
+import * as generalUtils from '@generatedata/utils/general';
+import { Store } from '~types/general';
+
+describe('getCurrentPacket', () => {
+ it('should return false by default', () => {
+ const state: Store = {
+ generator: getInitialState(),
+ main: initialMainState,
+ packets: initialPacketState,
+ account: initialAccountState
+ };
+ expect(selectors.getCurrentPacket(state)).toEqual(null);
+ });
+
+ it('should return the selected packet', () => {
+ const packets: PacketsState = generalUtils.cloneObj(initialPacketState);
+ packets.currentPacketId = '456';
+ packets.packetIds = ['123', '456'];
+ packets.packets['123'] = getNewPacket({ generationWorkerId: 1 });
+ packets.packets['456'] = getNewPacket({ generationWorkerId: 2 });
+
+ const state: Store = {
+ generator: getInitialState(),
+ main: initialMainState,
+ packets,
+ account: initialAccountState
+ };
+ expect(selectors.getCurrentPacket(state)!.generationWorkerId).toEqual(2);
+ });
+
+ it('getActivePacketList', () => {
+ const packets: PacketsState = generalUtils.cloneObj(initialPacketState);
+ packets.currentPacketId = '456';
+ packets.packetIds = ['123', '456'];
+ packets.packets['123'] = getNewPacket({
+ generationWorkerId: 1,
+ numRowsToGenerate: 5
+ });
+ packets.packets['456'] = getNewPacket({
+ generationWorkerId: 2,
+ numRowsToGenerate: 10
+ });
+
+ const state: Store = {
+ generator: getInitialState(),
+ main: initialMainState,
+ packets,
+ account: initialAccountState
+ };
+ expect(selectors.getActivePacketList(state)).toEqual([
+ {
+ isPaused: false,
+ label: '5 rows',
+ numRowsToGenerate: 5,
+ packetId: '123',
+ percentage: 0
+ },
+ {
+ isPaused: false,
+ label: '10 rows',
+ numRowsToGenerate: 10,
+ packetId: '456',
+ percentage: 0
+ }
+ ]);
+ });
+});
diff --git a/apps/client/src/core/store/packets/packets.actions.ts b/apps/client/src/core/store/packets/packets.actions.ts
new file mode 100644
index 000000000..29e456e27
--- /dev/null
+++ b/apps/client/src/core/store/packets/packets.actions.ts
@@ -0,0 +1,131 @@
+import { Dispatch } from 'redux';
+import * as coreUtils from '../../../utils/coreUtils';
+import * as selectors from '../generator/generator.selectors';
+import * as packetSelectors from './packets.selectors';
+import { getDownloadFileInfo } from '~utils/exportTypes';
+import { getGeneratorPageRoute } from '~utils/routeUtils';
+import { GDAction } from '~types/general';
+import { downloadFile } from '../../generationPanel/generation.helpers';
+import * as mainSelectors from '~store/main/main.selectors';
+import { apolloClient } from '~core/apolloClient';
+import { gql } from '@apollo/client';
+
+export const START_GENERATION = 'START_GENERATION';
+export const startGeneration =
+ (): any =>
+ (dispatch: Dispatch, getState: any): void => {
+ const state = getState();
+
+ // whenever we start generating some data, we stash all the current settings into the data packet instance. That way,
+ // we can happily generate multiple independent packets simultaneously while the user starts work on a new
+ // data set in the UI
+ dispatch({
+ type: START_GENERATION,
+ payload: {
+ generationWorkerId: coreUtils.createGenerationWorker(),
+ numRowsToGenerate: selectors.getNumRowsToGenerate(state),
+ stripWhitespace: selectors.shouldStripWhitespace(state),
+ template: selectors.getGenerationTemplate(state),
+ dataTypes: selectors.getRowDataTypes(state),
+ columns: selectors.getColumns(state),
+ exportType: selectors.getExportType(state),
+ exportTypeSettings: selectors.getCurrentExportTypeSettings(state)
+ }
+ });
+ };
+
+export const UPDATE_TOTAL_GENERATION_COUNT = 'UPDATE_TOTAL_GENERATION_COUNT';
+export const updateTotalGenerationCount = (count: number): GDAction => ({
+ type: UPDATE_TOTAL_GENERATION_COUNT,
+ payload: { count }
+});
+
+export const LOG_DATA_BATCH = 'LOG_DATA_BATCH';
+export const logDataBatch =
+ (packetId: string, numGeneratedRows: number, dataStr: string): any =>
+ async (dispatch: Dispatch, getState: any): Promise => {
+ const state = getState();
+ const numRowsToGenerate = selectors.getNumRowsToGenerate(state);
+ const isLoggedIn = mainSelectors.isLoggedIn(state);
+ const currentDataSetId = selectors.getCurrentDataSetId(state);
+
+ // if the packet has been fully generated track the generated row count
+ if (isLoggedIn && currentDataSetId !== null && numRowsToGenerate === numGeneratedRows) {
+ const { data } = await apolloClient.mutate({
+ mutation: gql`
+ mutation UpdateDataSetGenerationCount($dataSetId: ID!, $generatedRows: Int!) {
+ updateDataSetGenerationCount(dataSetId: $dataSetId, generatedRows: $generatedRows) {
+ success
+ error
+ }
+ }
+ `,
+ variables: {
+ dataSetId: currentDataSetId,
+ generatedRows: numGeneratedRows
+ }
+ });
+
+ if (data?.updateDataSetGenerationCount?.success) {
+ dispatch(updateTotalGenerationCount(numGeneratedRows));
+ }
+ }
+
+ dispatch({
+ type: LOG_DATA_BATCH,
+ payload: {
+ packetId,
+ numGeneratedRows,
+ dataStr
+ }
+ });
+ };
+
+export const PAUSE_GENERATION = 'PAUSE_GENERATION';
+export const pauseGeneration = (packetId: string): GDAction => ({ type: PAUSE_GENERATION, payload: { packetId } });
+
+export const CONTINUE_GENERATION = 'CONTINUE_GENERATION';
+export const continueGeneration = (packetId: string): GDAction => ({
+ type: CONTINUE_GENERATION,
+ payload: { packetId }
+});
+
+export const ABORT_GENERATION = 'ABORT_GENERATION';
+export const abortGeneration = (packetId: string): GDAction => ({ type: ABORT_GENERATION, payload: { packetId } });
+
+export const HIDE_ACTIVITY_PANEL = 'HIDE_ACTIVITY_PANEL';
+export const hideActivityPanel = (): GDAction => ({ type: HIDE_ACTIVITY_PANEL });
+
+export const SHOW_ACTIVITY_PANEL = 'SHOW_ACTIVITY_PANEL';
+export const showActivityPanel =
+ (packetId: string, history: any) =>
+ (dispatch: Dispatch, getState: any): void => {
+ const state = getState();
+ const locale = mainSelectors.getLocale(state);
+
+ history.push(getGeneratorPageRoute(locale));
+ dispatch({
+ type: SHOW_ACTIVITY_PANEL,
+ payload: { packetId }
+ });
+ };
+
+export const CHANGE_SPEED = 'CHANGE_SPEED';
+export const changeSpeed = (speed: number): GDAction => ({
+ type: CHANGE_SPEED,
+ payload: { speed }
+});
+
+export const promptToDownload =
+ () =>
+ (dispatch: Dispatch, getState: any): void => {
+ const state = getState();
+ const packetId = packetSelectors.getCurrentPacketId(state);
+ const dataString = packetSelectors.getCompletedDataString(state);
+ const packet = packetSelectors.getCurrentPacket(state);
+ const { exportType, exportTypeSettings } = packet!.config;
+
+ const { filename, fileType } = getDownloadFileInfo(packetId!, exportType, exportTypeSettings);
+
+ downloadFile(filename, dataString, fileType);
+ };
diff --git a/apps/client/src/core/store/packets/packets.reducer.ts b/apps/client/src/core/store/packets/packets.reducer.ts
new file mode 100644
index 000000000..47644c160
--- /dev/null
+++ b/apps/client/src/core/store/packets/packets.reducer.ts
@@ -0,0 +1,238 @@
+import { AnyAction } from 'redux';
+// @ts-ignore
+import { nanoid } from 'nanoid';
+import { produce } from 'immer';
+import * as actions from './packets.actions';
+import { ExportTypeFolder } from '@generatedata/plugins';
+import { getByteSize, getGraphDuration, getRowGenerationRatePerSecond } from '../../generationPanel/generation.helpers';
+import C from '@generatedata/config/constants';
+import * as mainActions from '../main/main.actions';
+import { LoadTimeGraphDuration } from '~types/general';
+
+type GeneratedDataBatch = {
+ byteSize: number;
+ dataStr: string;
+ endTime: number;
+};
+
+export type DataPacket = {
+ generationWorkerId: string;
+ startTime: number | null;
+ endTime: number | null;
+ totalPauseDuration: number;
+ lastPauseTime: number | null;
+ resumeTime: number;
+ isPaused: boolean;
+ numGeneratedRows: number;
+ numBatches: number;
+ speed: number;
+ loadTimeGraphDuration: LoadTimeGraphDuration;
+
+ // this block contains the actual configuration data - data types and export type data - used in this generation packet
+ config: {
+ stripWhitespace: boolean;
+ numRowsToGenerate: number;
+ template: any;
+ dataTypes: any;
+ columns: any;
+ exportType: ExportTypeFolder;
+ exportTypeSettings: any;
+ };
+
+ // the actual generated data
+ data: GeneratedDataBatch[];
+
+ stats: {
+ totalSize: number;
+ averageSpeed: number;
+ rowGenerationRatePerSecond: { [second: number]: number };
+ lastBatchGenerationDuration: number;
+ lastCompleteLoggedSecond: number;
+ };
+};
+
+export type DataPackets = {
+ [packetId: string]: DataPacket;
+};
+
+export type PacketsState = {
+ currentPacketId: string | null;
+ packetIds: string[];
+ packets: DataPackets;
+};
+
+export const initialState: PacketsState = {
+ currentPacketId: null,
+ packetIds: [],
+ packets: {}
+};
+
+export const getNewPacket = ({
+ generationWorkerId,
+ stripWhitespace,
+ numRowsToGenerate,
+ template,
+ dataTypes,
+ columns,
+ exportType,
+ exportTypeSettings
+}: any): DataPacket => {
+ const now = new Date().getTime();
+ const loadTimeGraphDuration = getGraphDuration(numRowsToGenerate);
+
+ return {
+ generationWorkerId,
+ startTime: now,
+ endTime: null,
+ totalPauseDuration: 0,
+ resumeTime: now,
+ isPaused: false,
+ lastPauseTime: null,
+ numGeneratedRows: 0,
+ numBatches: 0,
+ speed: 100,
+ loadTimeGraphDuration,
+ config: {
+ stripWhitespace,
+ numRowsToGenerate,
+ template,
+ dataTypes,
+ columns,
+ exportType,
+ exportTypeSettings
+ },
+ data: [],
+ stats: {
+ totalSize: 0,
+ averageSpeed: 0,
+ rowGenerationRatePerSecond: {},
+ lastCompleteLoggedSecond: 0,
+ lastBatchGenerationDuration: 0
+ }
+ };
+};
+
+export const reducer = produce((draft: PacketsState, action: AnyAction) => {
+ switch (action.type) {
+ case mainActions.RESET_STORE:
+ Object.keys(initialState).forEach((key) => {
+ // @ts-ignore-line
+ draft[key] = initialState[key];
+ });
+ break;
+
+ case actions.START_GENERATION: {
+ const { generationWorkerId, numRowsToGenerate, template, dataTypes, columns, exportType, exportTypeSettings, stripWhitespace } =
+ action.payload;
+
+ const packetId = nanoid();
+ draft.packetIds.push(packetId);
+ draft.packets[packetId] = getNewPacket({
+ generationWorkerId,
+ numRowsToGenerate,
+ template,
+ dataTypes,
+ columns,
+ exportType,
+ exportTypeSettings,
+ stripWhitespace
+ });
+ draft.currentPacketId = packetId;
+ break;
+ }
+
+ case actions.PAUSE_GENERATION:
+ draft.packets[action.payload.packetId].lastPauseTime = new Date().getTime();
+ draft.packets[action.payload.packetId].isPaused = true;
+ break;
+
+ case actions.CONTINUE_GENERATION:
+ const lastPauseTimeMs = draft.packets[action.payload.packetId].lastPauseTime;
+ const pauseDuration = new Date().getTime() - lastPauseTimeMs!;
+ draft.packets[action.payload.packetId].isPaused = false;
+ draft.packets[action.payload.packetId].lastPauseTime = null;
+ draft.packets[action.payload.packetId].totalPauseDuration += pauseDuration;
+ break;
+
+ case actions.ABORT_GENERATION: {
+ const packetId = draft.currentPacketId as string;
+ draft.currentPacketId = null;
+ draft.packetIds.splice(draft.packetIds.indexOf(packetId), 1);
+ delete draft.packets[packetId];
+ break;
+ }
+
+ case actions.HIDE_ACTIVITY_PANEL:
+ draft.currentPacketId = null;
+ break;
+
+ case actions.SHOW_ACTIVITY_PANEL:
+ draft.currentPacketId = action.payload.packetId;
+ break;
+
+ case actions.CHANGE_SPEED: {
+ if (draft.currentPacketId) {
+ draft.packets[draft.currentPacketId].speed = action.payload.speed;
+ }
+ break;
+ }
+
+ // TODO yikes. TOTAAAAAALLLLY needs improving and testing. And maybe taking out behind the shed and shooting.
+ case actions.LOG_DATA_BATCH: {
+ const now = new Date().getTime();
+ const { packetId, numGeneratedRows, dataStr } = action.payload;
+ const byteSize = getByteSize(dataStr);
+
+ // check for existence in case the user just cancelled and an orphaned response comes back
+ if (!draft.packets[packetId]) {
+ break;
+ }
+
+ const numExistingPackets = draft.packets[packetId].data.length;
+
+ // the start time for this batch was either the previous completed batch, or for the very first one - the start
+ // of the generation
+ const startTime =
+ numExistingPackets > 0
+ ? draft.packets[packetId].data[draft.packets[packetId].data.length - 1].endTime
+ : draft.packets[packetId].resumeTime;
+
+ const duration = now - startTime;
+
+ let newAverageSpeed = duration;
+ if (numExistingPackets > 0) {
+ newAverageSpeed = (draft.packets[packetId].stats.averageSpeed * numExistingPackets + duration) / (numExistingPackets + 1);
+ }
+
+ draft.packets[packetId].stats.averageSpeed = newAverageSpeed;
+ draft.packets[packetId].stats.totalSize += byteSize;
+ draft.packets[packetId].stats.lastBatchGenerationDuration = duration;
+
+ draft.packets[packetId].numGeneratedRows = numGeneratedRows;
+ draft.packets[packetId].data.push({
+ dataStr,
+ byteSize,
+ endTime: now
+ });
+
+ const result = getRowGenerationRatePerSecond(draft.packets[packetId].resumeTime, startTime, now, C.GENERATION_BATCH_SIZE);
+
+ const seconds = Object.keys(result);
+ seconds.forEach((second) => {
+ const secondNum = parseInt(second, 10);
+ let currVal = draft.packets[packetId].stats.rowGenerationRatePerSecond[secondNum];
+ if (currVal) {
+ currVal += result[secondNum];
+ } else {
+ currVal = result[secondNum];
+ }
+ draft.packets[packetId].stats.rowGenerationRatePerSecond[secondNum] = currVal;
+ });
+
+ draft.packets[packetId].stats.lastCompleteLoggedSecond = parseInt(seconds[seconds.length - 1], 10) - 1;
+ break;
+ }
+ }
+}, initialState);
+
+export default reducer;
diff --git a/apps/client/src/core/store/packets/packets.selectors.ts b/apps/client/src/core/store/packets/packets.selectors.ts
new file mode 100644
index 000000000..03faef043
--- /dev/null
+++ b/apps/client/src/core/store/packets/packets.selectors.ts
@@ -0,0 +1,131 @@
+/* eslint max-len:0 */
+import { LoadTimeGraphDuration, Store } from '~types/general';
+import { createSelector } from 'reselect';
+import { DataPackets } from './packets.reducer';
+import prettyBytes from 'pretty-bytes';
+import { getFormattedNum } from '@generatedata/utils/number';
+import { formatDuration } from '@generatedata/utils/date';
+import { getLocale } from '../main/main.selectors';
+import C from '@generatedata/config/constants';
+
+export const getCurrentPacketId = (state: Store): string | null => state.packets.currentPacketId;
+export const getPacketIds = (state: Store): string[] => state.packets.packetIds;
+export const getPackets = (state: Store): DataPackets => state.packets.packets;
+
+export const getCurrentPacket = createSelector(getCurrentPacketId, getPackets, (packetId, packets) =>
+ packetId ? packets[packetId] : null
+);
+
+// returns true/false if there's anything currently in the process of being generated
+export const isGenerating = createSelector(getPackets, (packets) => Object.keys(packets).some((i: string) => packets[i].endTime === null));
+
+// returns an ordered list of packet info for displaying some pills in the footer
+export const getActivePacketList = createSelector(getPacketIds, getPackets, getLocale, (packetIds, packets, locale) => {
+ return packetIds.map((packetId: string) => {
+ const packet = packets[packetId];
+
+ return {
+ packetId,
+ label: getFormattedNum(packet.config.numRowsToGenerate, locale) + ' rows', // when we get into saving data sets, this'll be the name
+ percentage: (packet.numGeneratedRows / packet.config.numRowsToGenerate) * 100,
+ isPaused: packet.isPaused,
+ numRowsToGenerate: packet.config.numRowsToGenerate
+ };
+ });
+});
+
+export const getLoadTimeDuration = createSelector(getCurrentPacket, (packet) => packet!.loadTimeGraphDuration);
+
+export const getBatchLoadTimes = createSelector(getCurrentPacket, (packet) => {
+ if (!packet) {
+ return [];
+ }
+
+ const map = {
+ [LoadTimeGraphDuration.s15]: 15,
+ [LoadTimeGraphDuration.s30]: 30,
+ [LoadTimeGraphDuration.all]: 30,
+ [LoadTimeGraphDuration.m1]: 60
+ };
+ const numSecondsToShow = map[packet.loadTimeGraphDuration];
+
+ let seconds = Object.keys(packet.stats.rowGenerationRatePerSecond);
+ const numLoadedSeconds = seconds.length;
+
+ if (numLoadedSeconds <= numSecondsToShow) {
+ for (let i = numLoadedSeconds; i < numSecondsToShow; i++) {
+ seconds.push((i + 1).toString());
+ }
+ } else {
+ const sliceVal = numLoadedSeconds - numSecondsToShow;
+ seconds = seconds.slice(sliceVal + 1);
+ }
+
+ return seconds.map((secondStr) => {
+ const secondNum = parseInt(secondStr, 10);
+ const isComplete = secondNum <= packet.stats.lastCompleteLoggedSecond;
+ const rowsPerSecond = (isComplete && packet.stats.rowGenerationRatePerSecond[secondNum]) || 0;
+
+ return {
+ label: secondStr,
+ rowsPerSecond
+ };
+ });
+});
+
+export const getPacketTotalSize = createSelector(getCurrentPacket, (packet) => {
+ return packet ? packet.stats.totalSize : 0;
+});
+
+// may as well format it in this method too
+export const getGeneratedDataSizeLabel = createSelector(getPacketTotalSize, (totalSize) => prettyBytes(totalSize));
+
+export const getEstimatedDataSize = createSelector(getCurrentPacket, getPacketTotalSize, (packet, packetTotalSize) => {
+ if (!packet || packet.numGeneratedRows === 0) {
+ return '-';
+ }
+ return prettyBytes((packet.config.numRowsToGenerate / packet.numGeneratedRows) * packetTotalSize);
+});
+
+export const getEstimatedTime = createSelector(getCurrentPacket, (packet): number | null => {
+ if (!packet) {
+ return null;
+ }
+ return packet.stats.averageSpeed * (packet.config.numRowsToGenerate / C.GENERATION_BATCH_SIZE);
+});
+
+export const getEstimatedTimeDisplay = createSelector(getEstimatedTime, (time) => {
+ return time ? formatDuration(time / 1000) : '-';
+});
+
+export const getEstimatedTimeRemaining = createSelector(getCurrentPacket, getEstimatedTime, (packet, estimatedTime) => {
+ if (!estimatedTime || !packet) {
+ return '-';
+ }
+ const now = new Date().getTime();
+ let timeTaken = estimatedTime + packet.startTime! - now;
+
+ if (timeTaken < 0) {
+ timeTaken = 0;
+ }
+
+ return formatDuration(timeTaken / 1000);
+});
+
+export const getCompletedDataString = createSelector(getCurrentPacket, (packet) => {
+ if (!packet) {
+ return '';
+ }
+
+ let str = '';
+ packet.data.forEach(({ dataStr }) => (str += dataStr));
+
+ return str;
+});
+
+export const getLastBatchGenerationDuration = createSelector(getCurrentPacket, (packet) => {
+ if (!packet) {
+ return 0;
+ }
+ return packet.stats.lastBatchGenerationDuration / 1000;
+});
diff --git a/apps/client/src/core/theme.ts b/apps/client/src/core/theme.ts
new file mode 100644
index 000000000..c20e0c85f
--- /dev/null
+++ b/apps/client/src/core/theme.ts
@@ -0,0 +1,35 @@
+/* istanbul ignore file */
+import { createTheme } from '@mui/material/styles';
+
+const theme = createTheme({
+ typography: {
+ fontFamily: '"Open Sans", serif',
+ button: {
+ fontSize: 'inherit'
+ }
+ },
+ palette: {
+ primary: {
+ main: '#275eb5'
+ }
+ },
+ overrides: {
+ MuiDialog: {
+ root: {
+ // @ts-ignore-line
+ zIndex: '5000 !important'
+ }
+ }
+ },
+ props: {
+ MuiButtonBase: {
+ disableRipple: true
+ }
+ },
+ zIndex: {
+ // @ts-ignore-line
+ tooltip: '5001 !important'
+ }
+});
+
+export default theme;
diff --git a/apps/client/src/hooks/useDidUpdate.ts b/apps/client/src/hooks/useDidUpdate.ts
new file mode 100644
index 000000000..b40bf54c1
--- /dev/null
+++ b/apps/client/src/hooks/useDidUpdate.ts
@@ -0,0 +1,15 @@
+import { useEffect, useRef } from 'react';
+
+const useDidUpdate = (callback: any, deps: any[]): void => {
+ const hasMount = useRef(false);
+
+ useEffect(() => {
+ if (hasMount.current) {
+ callback();
+ } else {
+ hasMount.current = true;
+ }
+ }, deps);
+};
+
+export default useDidUpdate;
diff --git a/apps/client/src/hooks/usePortal.tsx b/apps/client/src/hooks/usePortal.tsx
new file mode 100644
index 000000000..ea4b73ca8
--- /dev/null
+++ b/apps/client/src/hooks/usePortal.tsx
@@ -0,0 +1,44 @@
+import { useRef, useEffect } from 'react';
+
+const createRootElement = (id: string): HTMLDivElement => {
+ const rootContainer = document.createElement('div');
+ rootContainer.setAttribute('id', id);
+ return rootContainer;
+};
+
+const addRootElement = (rootEl: any): void => {
+ document.body.insertBefore(
+ rootEl,
+ // @ts-ignore-line
+ document.body.lastElementChild.nextElementSibling
+ );
+};
+
+const usePortal = (id: string): HTMLDivElement => {
+ const rootElRef = useRef(null);
+
+ useEffect((): any => {
+ const existingParent = document.querySelector(`#${id}`);
+ const parentEl = existingParent || createRootElement(id);
+ if (!existingParent) {
+ addRootElement(parentEl);
+ }
+ parentEl.appendChild(rootElRef.current);
+
+ return (): void => {
+ rootElRef.current.remove();
+ parentEl.remove();
+ };
+ }, []);
+
+ const getRootElem = (): any => {
+ if (!rootElRef.current) {
+ rootElRef.current = document.createElement('div');
+ }
+ return rootElRef.current;
+ };
+
+ return getRootElem();
+};
+
+export default usePortal;
diff --git a/apps/client/src/hooks/usePrevious.tsx b/apps/client/src/hooks/usePrevious.tsx
new file mode 100644
index 000000000..2e219d758
--- /dev/null
+++ b/apps/client/src/hooks/usePrevious.tsx
@@ -0,0 +1,13 @@
+import { useRef, useEffect } from 'react';
+
+const usePrevious = (value: any): any => {
+ const ref = useRef();
+
+ useEffect(() => {
+ ref.current = value;
+ }, [value]);
+
+ return ref.current;
+};
+
+export default usePrevious;
diff --git a/apps/client/src/hooks/useThrottle.ts b/apps/client/src/hooks/useThrottle.ts
new file mode 100644
index 000000000..ffef70af4
--- /dev/null
+++ b/apps/client/src/hooks/useThrottle.ts
@@ -0,0 +1,7 @@
+import { useState } from 'react';
+import { useThrottleCallback } from '@react-hook/throttle';
+
+export const useThrottle = (initialState: StateType, wait: number, leading = false): [StateType, any] => {
+ const [state, setState] = useState(initialState);
+ return [state, useThrottleCallback(setState, wait, leading)];
+};
diff --git a/apps/client/src/i18n/ar.json b/apps/client/src/i18n/ar.json
new file mode 100644
index 000000000..63e3de097
--- /dev/null
+++ b/apps/client/src/i18n/ar.json
@@ -0,0 +1,329 @@
+{
+ "abort": "إحباط",
+ "about": "حول",
+ "aboutInfoPara1": "generatedata.com هو مشروع مفتوح المصدر قمت بإنشائه في أواخر العصر الحجري القديم (حوالي عام 2004) وكان له العديد والعديد من المساهمين طوال فترة حياته. كل بضع سنوات أعيد كتابته باستخدام أحدث وأكبر التقنيات المتاحة: تم الانتهاء من هذا الإصدار في عام 2021 ، وتم صنعه باستخدام React و Redux و Typescript و GraphQL و Docker وعمال الويب والأشرطة المطاطية وشريط مجاري الهواء والكثير من الشتائم.",
+ "aboutInfoPara2": "يمكنك العثور على المشروع على جيثب. قم بتنزيله أو شوكة أو لا تتردد في المساهمة استمتع!",
+ "aboutThisScript": "حول هذا البرنامج النصي",
+ "accountCreated": "تم إنشاء الحساب",
+ "accountCreatedDesc": "تم إنشاء الحساب.",
+ "accountCreatedMsg": "تم إنشاء حساب لك.",
+ "accountDeleted": "تم حذف الحساب.",
+ "accountDisabled": "الحساب معطل",
+ "accountExpiredMsg": "عذرا ، لقد انتهت صلاحية حسابك.",
+ "accountExpiryDate": "تاريخ انتهاء الحساب",
+ "accountInfo": "معلومات الحساب",
+ "accountSettings": "إعدادات الحساب",
+ "accountType": "نوع الحساب",
+ "accounts": "الحسابات",
+ "add": "Add",
+ "addRowsDesc": "لإضافة المزيد من الصفوف إلى شبكة البيانات ، ما عليك سوى استخدام النموذج الموجود أسفل الصفحة. عندما يكون لديك الكثير من الحقول ، قد يكون هذا القسم مخفيًا خارج الصفحة ، لذا سيتعين عليك تمرير اللوحة إلى الأسفل لرؤية النموذج.",
+ "addSomeDataDesc": "أضف بعض الصفوف في الشبكة.",
+ "admin": "Admin",
+ "adminAccount": "حساب المسؤول",
+ "administrator": "مدير",
+ "africa": "أفريقيا",
+ "allCountries": "كل البلدان",
+ "allDataTypePlugins": "كافة المكونات الإضافية لنوع البيانات",
+ "allExportTypePlugins": "كافة المكونات الإضافية لنوع التصدير",
+ "anonymousAccess": "وصول مجهول",
+ "asia": "آسيا",
+ "back": "رجوع",
+ "backToLogin": "العودة إلى تسجيل الدخول",
+ "blog": "مدونة",
+ "cancel": "إلغاء",
+ "centralAmerica": "أمريكا الوسطى",
+ "changePassword": "غير كلمة السر",
+ "clear": "واضح",
+ "clearPage": "مسح الصفحة",
+ "clearPageConfirmation": "هل تريد بالتأكيد مسح الصفحة؟ أي تغييرات قمت بها سيتم فقدانها.",
+ "clearThePage": "مسح الصفحة",
+ "clickExportTypeBtn": "توضح الخطوة التالية ما يحدث عند النقر فوق الزر.",
+ "close": "إغلاق",
+ "closePanel": "إغلاق لوحة",
+ "columns": "الأعمدة",
+ "columnsDesc": "ستعرض الشبكة أكبر عدد ممكن من هذه الأعمدة ، اعتمادًا على حجم الشاشة المتاح لديك. عندما تكون صغيرة جدًا ، ستخفي بعض الأعمدة وتظهر أيقونة ترس تفتح تلميحًا بالمحتوى المفقود. تشرح الخطوات القليلة التالية كل عمود من الأعمدة ، باستخدام صف الأسماء الأول كتوضيح.",
+ "complete": "كاملة",
+ "confirmDeleteAccount": "هل أنت متأكد أنك تريد حذف هذا الحساب؟",
+ "confirmDeleteUserAccount": "هل أنت متأكد من أنك تريد حذف حساب المستخدم هذا؟",
+ "confirmEmptyForm": "هل أنت متأكد من أنك تريد مسح الصفحة؟",
+ "continue": "يكمل",
+ "copiedToClipboard": "نسخ إلى الحافظة",
+ "copyToClipboard": "نسخ إلى الحافظة",
+ "coreExportType": "التنسيقات الأساسية",
+ "countries": "البلدان",
+ "country": "البلد",
+ "countrySpecific": "خاص بكل بلد",
+ "cpuMeltinglyFast": "وحدة المعالجة المركزية ذوبان سريع",
+ "createAccount": "إنشاء حساب",
+ "currentPassword": "كلمة المرور الحالية",
+ "currentVersion": "الإصدار الحالي قيد التحرير",
+ "dataGenerated": "تم إنشاء البيانات.",
+ "dataSet": "مجموعة البيانات",
+ "dataSetHelp": "هذا هو المكان الذي تقوم بتعريف بالضبط أي نوع من البيانات التي تريد إنشاء. حاول ملء صف أو اثنين وانقر فوق الزر إنشاء. ستحصل على تعليق منه بسرعة كبيرة.",
+ "dataSetLoaded": "تم تحميل مجموعة البيانات.",
+ "dataSetName": "اسم مجموعة البيانات",
+ "dataSetNameDesc": "انقر فوق النص هنا لتسمية مجموعة البيانات الخاصة بك. يرجى ملاحظة أنك ستحتاج إلى حساب على الموقع وتسجيل الدخول من أجل تسمية مجموعات البيانات الخاصة بك. عند حفظ مجموعات البيانات الخاصة بك ، يمنحك هذا الاسم تسمية مناسبة لتتبعها.",
+ "dataSetNameUpdated": "تم تحديث اسم مجموعة البيانات.",
+ "dataSetOptions": "خيارات مجموعة البيانات",
+ "dataSetReverted": "تمت إعادة مجموعة البيانات الخاصة بك إلى الإصدار القديم المحدد.",
+ "dataSetSaved": "تم حفظ مجموعة البيانات الخاصة بك.",
+ "dataSets": "مجموعات البيانات",
+ "dataType": "نوع البيانات",
+ "dataTypeDesc": "هذه قائمة منسدلة تحتوي على جميع أنواع البيانات المتاحة. عند تحديد قيمة ، سيتم تحديث الأعمدة المتبقية لهذا الصف. لكل نوع بيانات إعداداته الفريدة للسماح بالتخصيص.",
+ "dataTypes": "أنواع البيانات",
+ "dateAccountCreated": "تاريخ إنشاء حساب",
+ "dateCreated": "تاريخ الإنشاء",
+ "defaultLanguage": "اللغة الافتراضية",
+ "delete": "حذف",
+ "delete1DataSet": "حذف مجموعة البيانات 1",
+ "deleteAccount": "حذف الحساب",
+ "deleteDataSet": "حذف مجموعة البيانات",
+ "deleteDataSetConfirm": "هل أنت متأكد أنك تريد حذف مجموعة البيانات هذه؟",
+ "deleteRow": "احذف صف",
+ "deleteRowDesc": "يحتوي العمود الأخير على أيقونة حذف. سيؤدي النقر فوقه إلى حذف الصف.",
+ "developer": "المطور",
+ "developerDoc": "المطور وثيقة",
+ "disabled": "معاق",
+ "documentation": "الوثائق",
+ "download": "تحميل",
+ "edit": "تحرير",
+ "editAccount": "تحرير الحساب",
+ "editExportTypePage": "عند النقر فوق الزر \"نوع التصدير\" ، يظهر عرض صفحة كاملة يحتوي على خيارات التكوين على الجانب الأيسر ولوحة المعاينة على اليمين.",
+ "editExportTypePageConfig": "لنلق نظرة على خيارات التكوين.",
+ "editExportTypeSettings": "يرجى تعديل إعدادات نوع التصدير الخاصة بك.",
+ "editingExportTypes": "تحرير أنواع التصدير",
+ "email": "البريد الإلكتروني",
+ "emailFooterDisclaimer": "إذا كنت قد تلقيت هذا البريد الإلكتروني عن طريق الخطأ ، يرجى التواصل مع مسؤول الموقع.",
+ "emailIntroLineWithName": "مرحبًا1%1,",
+ "emailLabel": "بريد إلكتروني:",
+ "emailNotSent": "لم نتمكن من إرسال إشعار البريد الإلكتروني.",
+ "emailUserLoginInfo": "البريد الإلكتروني للمستخدم معلومات تسجيل الدخول الخاصة بهم",
+ "enterEmailAddressToResetPassword": "أدخل عنوان بريدك الإلكتروني أدناه لإعادة تعيين كلمة المرور الخاصة بك.",
+ "enterUserAccountDetails": "الرجاء إدخال تفاصيل حساب المستخدم الخاص بك أدناه.",
+ "errorCreatingAccount": "حدث خطأ في إنشاء هذا الحساب.",
+ "estimatedSize": "الحجم المقدر:",
+ "estimatedTime": "الوقت المقدر:",
+ "europe": "أوروبا",
+ "example": "مثال",
+ "exampleColumn": "عمود المثال",
+ "exampleColumnDesc": "قد يحتوي هذا العمود على محتوى أو لا يحتوي على محتوى بناءً على نوع البيانات الذي حددته للصف. إنها مجرد طريقة ملائمة لبعض أنواع البيانات لتقديم أمثلة محددة مسبقًا لكيفية استخدامها. على سبيل المثال ، يوفر عمود الأسماء قائمة منسدلة بتنسيقات أسماء مختلفة. بالنسبة لجميع أنواع البيانات ، لا يزال بإمكانك تكوينها يدويًا من خلال عمود \"الخيارات\": إنه حقًا جهاز موفر للوقت لمساعدتك في السرعة بأسرع ما يمكن.",
+ "examples": "أمثلة",
+ "exit": "خروج",
+ "expired": "منتهي الصلاحية",
+ "expiryDate": "تاريخ الانتهاء",
+ "exportType": "نوع التصدير",
+ "exportTypeBtnDesc": "الزر الموجود في الجزء العلوي الأيسر من اللوحة هو المكان الذي تذهب إليه لتحديد تنسيق البيانات التي تريد إنشاؤها وتكوينها. هنا تم ضبطه على تنضيد.",
+ "exportTypeOptionsSql": "خيارات نوع التصدير: SQL",
+ "exportTypeOptionsSqlDesc": "كما ترى ، لدى SQL الكثير من الحقول الأخرى الخاصة بنوع التصدير هذا. أنواع التصدير الأخرى لها إعدادات مختلفة.",
+ "exportTypeOptionsTs": "خيارات نوع التصدير: الأنواع",
+ "exportTypeOptionsTsDesc": "بناءً على نوع التصدير المحدد ، قد يحتوي نص اللوحة على خيارات إضافية. يوجد هنا حقلين ، مما يتيح لك تخصيص اسم النوع واسم المتغير الذي تم تصديره.",
+ "exportTypeOptionsTsDesc2": "الآن دعونا نلقي نظرة على SQL.",
+ "exportTypeSelection": "تحديد نوع التصدير",
+ "exportTypeSelectionDesc": "تتيح لك القائمة المنسدلة \"تنسيق\" اختيار تنسيق المحتوى الذي تم إنشاؤه. هناك العديد من الخيارات ، بعضها من الأنواع الأساسية: SQL ، CSV ، SQL ، JSON وغيرها ، وبعضها لتوليد البيانات للغات برمجة معينة.",
+ "exportTypes": "أنواع التصدير",
+ "filterDataTypes": "أنواع بيانات التصفية",
+ "financial": "الأمور المالية",
+ "firstName": "الاسم الأول",
+ "forgottenYourPasswordQ": "نسيت كلمة المرور؟",
+ "format": "تنسيق",
+ "generate": "توليد",
+ "generateBtnDesc": "بمجرد الانتهاء من تكوين مجموعة البيانات الخاصة بك والتأكد من أنها تبدو كما تريد ، فقد حان الوقت لإنشاء بعض البيانات في الحجم! انقر فوق هذا الزر لتوليد البيانات الخاصة بك. اعتمادًا على عدد الصفوف في شبكة البيانات وعدد الصفوف التي تريد إنشاءها ، قد يستغرق ذلك بعض الوقت.",
+ "generated": "ولدت",
+ "generatedC": "ولدت:",
+ "generator": "مولد",
+ "geo": "الجغرافية",
+ "grid": "الشبكة",
+ "gridPanelTourDesc1": "لوحة الشبكة هي المكان الذي تنشئ فيه البيانات التي تريد توليدها. يمكنك هنا الاختيار بين أنواع البيانات المختلفة وكيفية تكوينها والترتيب الذي تظهر به. في المثال هنا ، لدينا خمسة حقول: الاسم والهاتف والبريد الإلكتروني وعنوان الشارع والمدينة.",
+ "gridPanelTourDesc2": "راجع الجولة على لوحة الشبكة للحصول على معلومات أكثر تفصيلاً حول هذا القسم.",
+ "gridPanelTourIntroDesc1": "لوحة الشبكة هي المكان الذي تحدد فيه البيانات التي تريد توليدها. بالنسبة لهذه الجولة ، قمنا بإخفاء لوحة المعاينة (راجع زر \"معاينة\" غير المحدد أسفل يمين الصفحة) لمنحنا مساحة أكبر قليلاً. لقد أضفنا أيضًا 10 صفوف من أنواع بيانات مختلفة ، لأغراض التوضيح فقط.",
+ "gridPanelTourIntroDesc2": "لنبدأ بأعمدة الشبكة.",
+ "help": "مساعدة",
+ "helpIcon": "رمز المساعدة",
+ "helpIconDesc": "عند تحديد نوع البيانات في العمود السابق ، ستظهر أيقونة هنا. يؤدي النقر فوقه إلى فتح مربع حوار تعليمات يحتوي على معلومات الاستخدام.",
+ "hide": "يخفي",
+ "hideShowGrid": "إخفاء / إظهار الشبكة",
+ "hideShowPreviewPanel": "إخفاء / إظهار لوحة المعاينة",
+ "history": "التاريخ",
+ "historyPanelDesc": "تعرض هذه اللوحة محفوظات التغييرات التي تم إجراؤها على مجموعة البيانات. انقر فوق الأزرار \"عرض\" لفحص الإصدارات السابقة. للعودة إلى إصدار سابق ، قم فقط بعرضه ، ثم انقر فوق الزر \"الرجوع إلى هذا الإصدار\".",
+ "hostName": "اسم المضيف",
+ "humanData": "البيانات البشرية",
+ "ifWantToReregister": "إذا كنت ترغب في إعادة التسجيل يرجى زيارة:",
+ "incomplete": "غير مكتمل",
+ "install": "تثبيت",
+ "introToGenerator": "مقدمة عن المولد",
+ "introToGeneratorDesc": "تمنحك هذه الجولة مقدمة عالية المستوى للميزات الرئيسية لمولد البيانات. هيا بنا نبدأ!",
+ "invalidSettings": "إعدادات غير صحيحة!",
+ "language": "العربية",
+ "lastEdited": "آخر تعديل",
+ "lastLoggedIn": "آخر تسجيل دخول",
+ "lastModified": "اخر تعديل",
+ "lastName": "اسم العائلة",
+ "lastSaved": "آخر حفظ",
+ "lineWrapping": "التفاف الخط",
+ "linkToDataSet": "الارتباط بمجموعة البيانات",
+ "linkToThisDataSet": "الارتباط بمجموعة البيانات هذه",
+ "live": "حي",
+ "load": "تحميل",
+ "loading": "تحميل...",
+ "login": "تسجيل الدخول",
+ "loginToSave": "من أجل حفظ مجموعات البيانات الخاصة بك ، تحتاج أولاً إلى حساب. الرجاء تسجيل الدخول أدناه.",
+ "loginOrRegisterToSave": "من أجل حفظ مجموعات البيانات الخاصة بك ، تحتاج أولاً إلى حساب. الرجاء تسجيل الدخول أو التسجيل أدناه.",
+ "loginUrlLabel": "URL تسجيل الدخول:",
+ "logout": "خروج",
+ "makeDataSetPublicAgreement": "أنا أفهم أنه لمشاركة مجموعة البيانات هذه , أحتاج إلى جعلها عامة.",
+ "metaDescription": "generatedata.com: مجاني, ومرخصة من غنو, ومولد بيانات مخصص عشوائي لاختبار البرمجيات",
+ "metaKeywords": "بيانات عشوائية, بيانات الاختبار, بيانات العينة, مولد البيانات, توليد البيانات",
+ "missingDataSetName": "الرجاء إدخال اسم مجموعة البيانات الخاصة بك",
+ "nameColumn": "عمود الاسم",
+ "nameColumnDesc": "يحصل هذا العمود على عنوان مختلف بناءً على نوع التصدير الذي حددته في لوحة المعاينة (XML ، CSV ، إلخ). الغرض من هذا العمود هو توفير معرف للصف يمكن استخدامه بعد ذلك في البيانات التي تم إنشاؤها. على سبيل المثال ، يستخدم XML هذه القيمة كاسم عقدة XML ؛ يستخدمه JSON كاسم خاصية JSON ؛ يستخدمه SQL لاسم عمود قاعدة البيانات. سيقوم التطبيق تلقائيًا بالتحقق من صحة القيمة التي أدخلتها هنا للتأكد من أنها صالحة لنوع التصدير المحدد.",
+ "newDataSet": "مجموعة بيانات جديدة",
+ "no": "لا",
+ "noAccountsCreated": "لم يتم إنشاء حسابات.",
+ "noAdditionalSettings": "لا توجد إعدادات إضافية",
+ "noDataSetsSaved": "ليس لديك أي مجموعات بيانات محفوظة.",
+ "noExamplesAvailable": "لا توجد أمثلة متاحة.",
+ "noExpiry": "لا انتهاء الصلاحية",
+ "noHistory": "لا يوجد سجل حتى الان.",
+ "noOptionsAvailable": "لا توجد خيارات متاحة.",
+ "noSafari": "آسف ، لا سفاري!",
+ "northAmerica": "أمريكا الشمالية",
+ "nowLoggedIn": "لقد قمت بتسجيل الدخول.",
+ "nowLoggedOut": "لقد تم تسجيل الخروج.",
+ "numResults": "نتائج Num",
+ "numeric": "رقمية",
+ "oceania": "أوقيانوسيا",
+ "oneTimePasswordLogin": "لقد قمت بتسجيل الدخول باستخدام كلمة مرور لمرة واحدة. الرجاء تعيين كلمة مرور جديدة الآن.",
+ "open": "افتح",
+ "options": "خيارات",
+ "optionsColumn": "عمود الخيارات",
+ "optionsColumnDesc1": "يحتوي هذا العمود على كافة خيارات التكوين لنوع البيانات لهذا الصف. تحتوي بعض أنواع البيانات على عدد كبير جدًا من إعدادات التكوين لعرضها في مثل هذه المساحة الصغيرة (على سبيل المثال ، البلد ، المنطقة) ، لذلك قد تعرض فقط زرًا في الشبكة يفتح مربع حوار يحتوي على جميع الخيارات.",
+ "optionsColumnDesc2": "بالنسبة لنوع بيانات الأسماء الموضح هنا ، يحتوي العمود على حقل واحد قابل للإنشاء. هذا نوع حقل شائع في منشئ البيانات. يتيح لك إضافة عناصر بمجرد الكتابة فيها والنقر فوق <إدخال>. ثم يقوم بتحويل هذه العناصر إلى أقراص مميزة يمكن إزالتها عبر أيقونة \"x\".",
+ "or": "أو",
+ "order": "أمر",
+ "other": "غير ذلك",
+ "overMaxAnonRows": "عذرًا ، يمكنك إنشاء 1% صف فقط. لإنشاء كميات أكبر من البيانات ، ستحتاج إلى حساب مستخدم على هذا الموقع.",
+ "panelContents": "محتويات اللوحة",
+ "panelContentsDesc": "يحتوي الجزء الأكبر من لوحة المعاينة على عينة من البيانات التي تم إنشاؤها ، وفقًا للبيانات التي حددتها في لوحة الشبكة وإعدادات نوع التصدير المحددة. هنا يتم إنشاء 5 أنواع مختلفة من البيانات (البلد ، والمنطقة ، والمدينة ، وعنوان الشارع ، والبريد / الرمز البريدي) بتنسيق الكتابة المطبوعة.",
+ "panelControls": "ضوابط لوحة",
+ "panelControlsClearIconDesc": "يتيح لك الرمز الأخير في لوحة التحكم مسح الصفحة حتى تتمكن من البدء من جديد.",
+ "panelControlsDesc": "تتيح لك أزرار التحكم في اللوحة إخفاء / إظهار لوحات الشبكة والمعاينة والتي يمكن أن تكون مفيدة إذا كنت تعمل مع عقارات محدودة الشاشة. يمكنك أيضًا تبديل موضع اللوحتين بالنسبة إلى بعضهما البعض (يسار - يمين ، أعلى - أسفل) باستخدام الرمز الثالث.",
+ "panelTabs": "علامات تبويب اللوحة",
+ "panelTabsDesc": "أخيرًا ، ربما لاحظت أن اللوحة منظمة في علامتي تبويب. بشكل افتراضي ، يعرض علامة التبويب \"الإعدادات\" ، نظرًا لأن هذا هو المكان الذي من المرجح أن تذهب إليه. ولكن هناك أيضًا علامة تبويب \"معاينة\" ثانية تحتوي على إعدادات لتغيير مظهر لوحة المعاينة: السمة وإخفاء / إظهار أرقام الأسطر وتبديل التفاف السطر وتغيير حجم النص والتحكم في عدد الصفوف التي تم إنشاؤها.",
+ "password": "كلمة السر",
+ "passwordLabel": "كلمة المرور:",
+ "passwordReset": "إعادة تعيين كلمة المرور",
+ "passwordResetAccountExpired": "إعادة تعيين كلمة المرور - انتهت صلاحية الحساب",
+ "passwordResetAccountExpiredDesc": "لقد تلقينا للتو طلبًا لإعادة تعيين كلمة المرور الخاصة بك ، ولكن حسابك قد انتهت صلاحيته بالفعل.",
+ "passwordResetComplete": "تمت إعادة تعيين كلمة المرور الخاصة بك وكنت قد عبر البريد الإلكتروني كلمة المرور الجديدة.",
+ "passwordResetEmailContent1": "تم إعادة تعيين كلمة المرور الخاصة بك. يمكنك استخدام كلمة المرور التالية لتسجيل الدخول: %1",
+ "passwordResetEmailContent2": "الرجاء تغييره بمجرد تسجيل الدخول.",
+ "passwordResetEmailDesc": "لقد تلقينا طلبًا لإعادة تعيين كلمة المرور الخاصة بك. يحتوي هذا البريد الإلكتروني على كلمة مرور مؤقتة لمرة واحدة يمكنك استخدامها لتسجيل الدخول. بعد تسجيل الدخول ، ستحتاج إلى تعيين كلمة مرور جديدة.",
+ "passwordResetMsg": "إذا كان البريد الإلكتروني موجودًا في الملف ، فقد تمت إعادة تعيين كلمة المرور الخاصة بك وتم إرسال معلومات تسجيل الدخول المؤقتة عبر البريد الإلكتروني.",
+ "passwordUpdateInvalidPassword": "كلمة المرور الحالية غير صحيحة. الرجاء إعادة الدخول.",
+ "passwordUpdated": "لقد تم تحديث كلمة السر الخاصة بك.",
+ "pause": "وقفة",
+ "pleaseConfirm": "يرجى تأكيد",
+ "pleaseEnterAll": "الرجاء إدخال جميع",
+ "pleaseEnterDataSetName": "الرجاء إدخال اسم مجموعة البيانات الجديدة.",
+ "pleaseFixErrors": "الرجاء إصلاح الأخطاء التالية وإعادة إرسال:",
+ "pleaseLogin": "الرجاء تسجيل الدخول",
+ "pleaseSelect": "الرجاء اختيار",
+ "plugins": "الإضافات",
+ "pressEnterAddItem": "اضغط على Enter لإضافة عنصر",
+ "preview": "معاينة",
+ "previewPanel": "لوحة المعاينة",
+ "previewPanelControlsDesc": "في أعلى يمين اللوحة هناك رمزان. يقوم الرمز الأول بتحديث اللوحة ، مما يؤدي إلى إنشاء بيانات عشوائية مختلفة في كل مرة - وهذا يساعد في إعطائك فكرة عن نوع البيانات التي يتم إنشاؤها. الأيقونة الثانية تغلق اللوحة تمامًا. يمكنك دائمًا إظهار اللوحة مرة أخرى باستخدام القسم الموجود أسفل يسار الصفحة (ما عليك سوى إعادة تحديد مربع الاختيار \"معاينة\").",
+ "previewPanelDesc": "تمنحك هذه اللوحة معاينة مباشرة للبيانات التي تقوم بإنشائها ، أثناء إنشائها من خلال لوحة الشبكة. يعرض الزر الموجود أعلى اليسار التنسيق المحدد للبيانات التي يتم إنشاؤها (مثل SQL و XML و CSV وما إلى ذلك). لتغيير التنسيق ، ما عليك سوى النقر فوق الزر.",
+ "previewPanelMoreInfo": "لمزيد من المعلومات حول هذه اللوحة ، تحقق من جولة لوحة المعاينة.",
+ "previewPanelNoData": "لا بيانات!",
+ "previewPanelTourDesc": "تمنحك لوحة المعاينة تمثيلاً مرئيًا حيًا للبيانات التي تقوم بإنشائها أثناء إنشائها. تقدم هذه الجولة مقدمة سريعة عن كيفية عمل اللوحة.",
+ "previewRows": "معاينة الصفوف",
+ "private": "نشر",
+ "problemLoadingTour": "آسف ، كانت هناك مشكلة في تحميل الجولة.",
+ "programmingLanguages": "لغات البرمجة",
+ "province": "مقاطعة",
+ "public": "عامة",
+ "publicQ": "الجمهور؟",
+ "reenterPassword": "إعادة إدخال كلمة المرور",
+ "refreshPage": "تحديث الصفحة",
+ "refreshPanel": "لوحة تحديث",
+ "register": "تسجيل",
+ "remainingTime": "الوقت المتبقي:",
+ "requiredField": "يتطلب حقلا",
+ "revertToVersion": "العودة إلى هذا الإصدار",
+ "row": "صف",
+ "rowLabel": "عنوان العمود",
+ "rowLabelPlural": "عناوين العمود",
+ "rowNumDesc1": "يُظهر العمود الأول في الشبكة رقم الصف. لإعادة ترتيب الصف ، ما عليك سوى النقر على هذا العنصر واسحبه لأعلى أو لأسفل.",
+ "rowNumDesc2": "تعرض قيمة الرأس لهذا العمود العدد الإجمالي للصفوف في شبكتك. يمكن أن يكون هذا مفيدًا عندما يكون لديك مجموعات بيانات كبيرة بالفعل ويكون حجمها أكبر من أن يتسع للشاشة.",
+ "rowNumber": "رقم الصف",
+ "rowSp": "صف (ق)",
+ "rows": "الصفوف",
+ "rowsGenerated": "الصفوف التي تم إنشاؤها",
+ "rowsGeneratedPerSecond": "يتم إنشاء الصفوف في الثانية",
+ "safariExplanation": "يتطلب هذا الموقع مستعرض ويب حديثًا يطبق معايير الويب الحديثة. لسوء الحظ ، تأخر Safari عن المتصفحات الأخرى ولن يقوم بتشغيل هذا البرنامج النصي. حتى نكتشف كيفية ملاءمتها ، يرجى استخدام أحد المتصفحات التالية. كل ذلك مجاني للتنزيل.",
+ "save": "حفظ",
+ "saveAs": "حفظ باسم",
+ "saveButtonDesc": "إذا كان لديك حساب مستخدم على الموقع ، فسيحفظ هذا الزر مجموعة البيانات الخاصة بك. إذا لم تقم بتسجيل الدخول ، فسيطالبك النقر فوقه بتسجيل الدخول أولاً. يحتفظ النظام بمحفوظات التغييرات %1 الأخيرة على مجموعة البيانات ، بحيث يمكنك دائمًا الرجوع وعرض الإصدارات الأقدم.",
+ "saveDataSetNewName": "حفظ مجموعة البيانات كاسم جديد",
+ "seconds": "ثواني",
+ "seeHelpDialog": "انظر مربع حوار التعليمات.",
+ "selectCountry": "اختر البلد",
+ "selectDataType": "تحديد نوع البيانات",
+ "selectEllipsis": "تحديد...",
+ "selectExpiryDate": "حدد تاريخ انتهاء الصلاحية",
+ "selectLanguage": "اختر اللغة",
+ "sendEmail": "ارسل بريد الكتروني",
+ "seriouslySlow": "بطيئة على محمل الجد",
+ "settings": "إعدادات",
+ "showGrid": "عرض الشبكة",
+ "showLineNumbers": "إظهار أرقام الأسطر",
+ "showPreview": "عرض معاينة",
+ "siteLogo": "شعار الموقع",
+ "size": "بحجم:",
+ "southAmerica": "أمريكا الجنوبية",
+ "status": "الحالة",
+ "stripWhitespace": "نزع المسافة البيضاء من المحتوى الذي تم إنشاؤه",
+ "success": "النجاح",
+ "text": "نص",
+ "textSize": "حجم الخط",
+ "theDataSetName": "اسم مجموعة البيانات",
+ "theExportTypeBtn": "زر نوع التصدير",
+ "theGenerateButton": "زر توليد",
+ "theGridPanel": "لوحة الشبكة",
+ "thePreviewPanel": "لوحة المعاينة",
+ "theSaveButton": "زر الحفظ",
+ "theme": "موضوع",
+ "togglePanelLayout": "تبديل تخطيط لوحة",
+ "totalNumGeneratedRows": "إجمالي عدد الصفوف التي تم إنشاؤها",
+ "tourComplete": "اكتملت الجولة",
+ "tourCompleteDesc": "كله تمام! اختر أحد الأزرار أدناه للمتابعة.",
+ "tourIntroPara1": "توفر هذه الأداة الكثير من الوظائف وقد تكون مربكة قليلاً في البداية. انقر فوق أحد الأزرار الموجودة على اليمين للقيام بجولة حول هذه الميزة بالذات.",
+ "tourIntroPara2": "لاحظ أنه عند بدء الجولة ، فإنها تقوم مؤقتًا بالكتابة فوق محتوى صفحة المنشئ لتوضيح أشياء معينة حول الواجهة. لكن لا تقلق! بمجرد انتهاء الجولة ، تقوم بإرجاع بياناتك إلى حالتها الأصلية.",
+ "tryDifferentTour": "جرب جولة مختلفة",
+ "update": "تحديث",
+ "updateAccount": "تحديث الحساب",
+ "user": "المستخدم",
+ "userAccount": "حساب المستخدم",
+ "userAccountNotFound": "عذرا ، لم نتمكن من العثور على حسابك.",
+ "userAccountUpdated": "تم تحديث حساب المستخدم.",
+ "userAccounts": "حسابات المستخدمين",
+ "userNotFound": "عذرا ، لم نتمكن من التعرف عليك. يرجى التحقق من بيانات الاعتماد الخاصة بك.",
+ "validationAccountAlreadyExists": "آسف, حساب مع عنوان البريد الإلكتروني هذا موجود بالفعل.",
+ "validationDifferentNewPasswords": "يرجى التأكد من تطابق كلمات المرور الجديدة",
+ "validationInvalidEmail": "الرجاء إدخال عنوان بريد إلكتروني صالح.",
+ "validationNoCurrentPassword": "الرجاء إدخال كلمة المرور الحالية الخاصة بك",
+ "validationNoEmail": "الرجاء إدخال عنوان البريد الإلكتروني الخاص بك.",
+ "validationNoFirstName": "الرجاء إدخال اسمك الأول.",
+ "validationNoLastName": "الرجاء إدخال اسم العائلة.",
+ "validationNoPassword": "الرجاء إدخال كلمة المرور الخاصة بك.",
+ "version": "الإصدار",
+ "view": "منظر",
+ "viewChangelog": "عرض سجل التغيير",
+ "viewOnGithub": "عرض على Github",
+ "welcomeToTheGenerator": "مرحبًا بك في المولد!",
+ "yes": "نعم",
+ "yourAccount": "حسابك",
+ "yourAccountUpdated": "تم تحديث حسابك",
+ "yourUserAccount": "حساب المستخدم الخاص بك"
+}
diff --git a/apps/client/src/i18n/de.json b/apps/client/src/i18n/de.json
new file mode 100644
index 000000000..513723bbf
--- /dev/null
+++ b/apps/client/src/i18n/de.json
@@ -0,0 +1,329 @@
+{
+ "abort": "Abbrechen",
+ "about": "Über",
+ "aboutInfoPara1": "generatedata.com ist ein Open-Source-Projekt, das ich in der späten Altsteinzeit (ca. 2004) erstellt habe und während seiner Lebensdauer viele, viele Mitwirkende hatte. Alle paar Jahre schreibe ich es mit den neuesten und besten verfügbaren Technologien neu: Diese Version wurde 2021 fertiggestellt, erstellt mit React, Redux, Typescript, GraphQL, Docker, Webworkern, Gummibändern, Klebeband und einer ganzen Menge Fluchen.",
+ "aboutInfoPara2": "Sie finden das Projekt auf github. Laden Sie es herunter, geben Sie es ab oder tragen Sie dazu bei. Genießen!",
+ "aboutThisScript": "Über dieses Skript",
+ "accountCreated": "Benutzerkonto erstellt",
+ "accountCreatedDesc": "Das Konto wurde erstellt.",
+ "accountCreatedMsg": "Ein Benutzerkonto wurde für Sie erstellt.",
+ "accountDeleted": "Das Konto wurde gelöscht.",
+ "accountDisabled": "Account deaktiviert",
+ "accountExpiredMsg": "Ihr Konto ist leider abgelaufen.",
+ "accountExpiryDate": "Ablaufdatum des Kontos",
+ "accountInfo": "Benutzerkonten-Daten",
+ "accountSettings": "Benutzerkonto-Einstellungen",
+ "accountType": "Kontotyp",
+ "accounts": "Konten",
+ "add": "Hinzufügen",
+ "addRowsDesc": "Um dem Datenraster weitere Zeilen hinzuzufügen, verwenden Sie einfach das Formular unten auf der Seite. Wenn Sie viele Felder haben, wird dieser Abschnitt möglicherweise außerhalb der Seite ausgeblendet, sodass Sie das Bedienfeld nach unten scrollen müssen, um das Formular anzuzeigen.",
+ "addSomeDataDesc": "Fügen Sie dem Raster einige Zeilen hinzu.",
+ "admin": "Admin",
+ "adminAccount": "Admin-Konto",
+ "administrator": "Administrator",
+ "africa": "Afrika",
+ "allCountries": "Alle Länder",
+ "allDataTypePlugins": "Alle Datentyp-Plugins",
+ "allExportTypePlugins": "Alle Exporttyp-Plugins",
+ "anonymousAccess": "Anonymer Zugriff",
+ "asia": "Asien",
+ "back": "Zurück",
+ "backToLogin": "Zurück zur Anmeldung",
+ "blog": "Blog",
+ "cancel": "abbrechen",
+ "centralAmerica": "Zentralamerika",
+ "changePassword": "Passwort ändern",
+ "clear": "klar",
+ "clearPage": "Seite löschen",
+ "clearPageConfirmation": "Möchten Sie die Seite wirklich löschen? Alle Änderungen, die Sie vorgenommen haben, gehen verloren.",
+ "clearThePage": "Seite leeren",
+ "clickExportTypeBtn": "Der nächste Schritt zeigt, was passiert, wenn Sie auf die Schaltfläche klicken.",
+ "close": "Schließen",
+ "closePanel": "Panel schließen",
+ "columns": "Säulen",
+ "columnsDesc": "Das Raster zeigt je nach verfügbarer Bildschirmgröße so viele dieser Spalten wie möglich an. Wenn es zu klein ist, werden einige Spalten ausgeblendet und ein Zahnradsymbol angezeigt, das einen Infotip mit dem fehlenden Inhalt öffnet. In den nächsten Schritten werden die einzelnen Spalten anhand der ersten Namenszeile erläutert.",
+ "complete": "Komplett",
+ "confirmDeleteAccount": "Möchten Sie dieses Konto wirklich löschen?",
+ "confirmDeleteUserAccount": "Sind Sie sicher, dass Sie dieses Benutzerkonto löschen möchten?",
+ "confirmEmptyForm": "Sind Sie sicher, dass Sie die Seite löschen möchten?",
+ "continue": "Fortsetzen",
+ "copiedToClipboard": "In die Zwischenablage kopiert",
+ "copyToClipboard": "In die Zwischenablage kopieren",
+ "coreExportType": "Kernformate",
+ "countries": "Länder",
+ "country": "Land",
+ "countrySpecific": "Länderspezifisch",
+ "cpuMeltinglyFast": "CPU-schmelzend schnell",
+ "createAccount": "Konto erstellen",
+ "currentPassword": "Derzeitiges Passwort",
+ "currentVersion": "Aktuelle Version wird bearbeitet",
+ "dataGenerated": "Daten generiert.",
+ "dataSet": "Datensatz",
+ "dataSetHelp": "Hier definieren Sie, welche Daten Sie erstellen wollen. Geben Sie einfach 2-3 Zeilen und drücken Sie auf den Button \"Erzeugen\". Sie werden den Dreh ziemlich schnell raus bekommen.",
+ "dataSetLoaded": "Der Datensatz wurde geladen.",
+ "dataSetName": "Datensatz-Name",
+ "dataSetNameDesc": "Klicken Sie hier auf den Text, um Ihren Datensatz zu benennen. Bitte beachten Sie, dass Sie ein Konto auf der Website benötigen und angemeldet sein müssen, um Ihre Datensätze zu benennen. Wenn Sie Ihre Datensätze speichern, erhalten Sie mit diesem Namen eine praktische Bezeichnung, mit der Sie sie verfolgen können.",
+ "dataSetNameUpdated": "Der Datensatzname wurde aktualisiert.",
+ "dataSetOptions": "Datensatzoptionen",
+ "dataSetReverted": "Ihr Datensatz wurde auf die ausgewählte ältere Version zurückgesetzt.",
+ "dataSetSaved": "Ihr Datensatz wurde gespeichert.",
+ "dataSets": "Datensätze",
+ "dataType": "Datentyp",
+ "dataTypeDesc": "Dies ist eine Dropdown-Liste mit allen verfügbaren Datentypen. Wenn Sie einen Wert auswählen, werden die verbleibenden Spalten für diese Zeile aktualisiert. Jeder Datentyp verfügt über eigene Einstellungen, um die Anpassung zu ermöglichen.",
+ "dataTypes": "Datentypen",
+ "dateAccountCreated": "Konto erstellt am",
+ "dateCreated": "Erstellungsdatum",
+ "defaultLanguage": "Standard Sprache",
+ "delete": "Löschen",
+ "delete1DataSet": "Datensatz löschen",
+ "deleteAccount": "Account löschen",
+ "deleteDataSet": "Datensatz löschen",
+ "deleteDataSetConfirm": "Möchten Sie diesen Datensatz wirklich löschen?",
+ "deleteRow": "Zeile löschen",
+ "deleteRowDesc": "Die letzte Spalte enthält ein Löschsymbol. Durch Klicken darauf wird die Zeile gelöscht.",
+ "developer": "Entwickler",
+ "developerDoc": "Entwickler-Dokumentation",
+ "disabled": "Behindert",
+ "documentation": "Dokumentation",
+ "download": "Herunterladen",
+ "edit": "Bearbeiten",
+ "editAccount": "Konto bearbeiten",
+ "editExportTypePage": "Wenn Sie auf die Schaltfläche Exporttyp klicken, wird eine ganzseitige Ansicht mit den Konfigurationsoptionen auf der linken Seite und dem Vorschaufenster auf der rechten Seite angezeigt.",
+ "editExportTypePageConfig": "Schauen wir uns die Konfigurationsoptionen an.",
+ "editExportTypeSettings": "Bitte bearbeiten Sie Ihre Exporttypeinstellungen.",
+ "editingExportTypes": "Exporttypen bearbeiten",
+ "email": "E-Mail",
+ "emailFooterDisclaimer": "Wenn Sie diese E-Mail irrtümlich erhalten haben, wenden Sie sich bitte an den Site-Administrator.",
+ "emailIntroLineWithName": "Hallo %1,",
+ "emailLabel": "Email:",
+ "emailNotSent": "Wir waren nicht in der Lage, die E-Mail-Benachrichtigung zu versenden.",
+ "emailUserLoginInfo": "E-Mail dem Nutzer ihre Login-Daten",
+ "enterEmailAddressToResetPassword": "Geben Sie einfach Ihre E-Mailadresse ein, um Ihr Passwort zurückzusetzen.",
+ "enterUserAccountDetails": "Bitte geben Sie Ihr Benutzerkonten-Details ein.",
+ "errorCreatingAccount": "Beim Erstellen dieses Kontos ist ein Fehler aufgetreten.",
+ "estimatedSize": "Geschätzte Größe:",
+ "estimatedTime": "Geschätzte Zeit:",
+ "europe": "Europa",
+ "example": "Beispiel",
+ "exampleColumn": "Beispielspalte",
+ "exampleColumnDesc": "Diese Spalte kann je nach dem für die Zeile ausgewählten Datentyp Inhalt enthalten oder nicht. Für einige Datentypen ist es nur eine bequeme Möglichkeit, voreingestellte Beispiele für ihre Verwendung bereitzustellen. In der Spalte \"Namen\" finden Sie beispielsweise eine Dropdown-Liste mit verschiedenen Namensformaten. Für alle Datentypen können Sie sie weiterhin manuell über die Spalte Optionen konfigurieren: Es ist wirklich ein zeitsparendes Gerät, um Sie so schnell wie möglich auf den neuesten Stand zu bringen.",
+ "examples": "Beispiele",
+ "exit": "Ausgang",
+ "expired": "Abgelaufen",
+ "expiryDate": "Verfallsdatum",
+ "exportType": "Exporttyp",
+ "exportTypeBtnDesc": "Über die Schaltfläche oben links im Bedienfeld können Sie das Format für die Daten auswählen und konfigurieren, die Sie generieren möchten. Hier ist es auf Typescript eingestellt.",
+ "exportTypeOptionsSql": "Exporttypoptionen: SQL",
+ "exportTypeOptionsSqlDesc": "Wie Sie sehen können, verfügt SQL über viele andere Felder, die für diesen Exporttyp spezifisch sind. Andere Exporttypen haben andere Einstellungen.",
+ "exportTypeOptionsTs": "Exporttypoptionen: Typoskript",
+ "exportTypeOptionsTsDesc": "Abhängig vom ausgewählten Exporttyp kann der Bedienfeldkörper zusätzliche Optionen enthalten. Hier mit Typescript gibt es zwei Felder, in denen Sie den Typnamen und den Namen der exportierten Variablen anpassen können.",
+ "exportTypeOptionsTsDesc2": "Schauen wir uns nun SQL an.",
+ "exportTypeSelection": "Typauswahl exportieren",
+ "exportTypeSelectionDesc": "In der Dropdown-Liste Format können Sie das Format für den generierten Inhalt auswählen. Es gibt viele Optionen, von denen einige die Kerntypen sind: SQL, CSV, SQL, JSON und andere, und einige dienen zum Generieren von Daten für bestimmte Programmiersprachen.",
+ "exportTypes": "Export-Typen",
+ "filterDataTypes": "Datentypen filtern",
+ "financial": "Financiera",
+ "firstName": "Vorname",
+ "forgottenYourPasswordQ": "Passwort vergessen?",
+ "format": "Format",
+ "generate": "Erzeugen",
+ "generateBtnDesc": "Sobald Sie die Konfiguration Ihres Datensatzes abgeschlossen und bestätigt haben, dass er Ihren Wünschen entspricht, ist es Zeit, einige Daten in großem Umfang zu generieren! Klicken Sie auf diese Schaltfläche, um Ihre Daten zu generieren. Abhängig von der Anzahl der Zeilen im Datenraster und der Anzahl der Zeilen, die Sie generieren möchten, kann dies einige Zeit dauern.",
+ "generated": "Generiert",
+ "generatedC": "Generiert:",
+ "generator": "Generator",
+ "geo": "Geo",
+ "grid": "Gitter",
+ "gridPanelTourDesc1": "Im Rasterfeld erstellen Sie die Daten, die Sie generieren möchten. Hier können Sie zwischen den verschiedenen Datentypen, ihrer Konfiguration und der Reihenfolge ihrer Anzeige wählen. Im Beispiel hier haben wir fünf Felder: Name, Telefon, E-Mail, Straße und Stadt.",
+ "gridPanelTourDesc2": "Weitere Informationen zu diesem Abschnitt finden Sie in der Tour im Grid Panel.",
+ "gridPanelTourIntroDesc1": "Im Rasterfeld definieren Sie, welche Daten Sie generieren möchten. Für diese Tour haben wir das Vorschaufenster ausgeblendet (siehe die deaktivierte Schaltfläche \"Vorschau\" unten rechts auf der Seite), um uns etwas mehr Platz zu geben. Wir haben auch 10 Zeilen mit verschiedenen Datentypen hinzugefügt, nur zur Veranschaulichung.",
+ "gridPanelTourIntroDesc2": "Beginnen wir mit den Rasterspalten.",
+ "help": "Hilfe",
+ "helpIcon": "Hilfesymbol",
+ "helpIconDesc": "Wenn Sie in der vorherigen Spalte einen Datentyp auswählen, wird hier ein Symbol angezeigt. Wenn Sie darauf klicken, wird ein Hilfedialogfeld mit Nutzungsinformationen geöffnet.",
+ "hide": "Ausblenden",
+ "hideShowGrid": "Raster ausblenden / anzeigen",
+ "hideShowPreviewPanel": "Vorschaufenster ausblenden / einblenden",
+ "history": "Geschichte",
+ "historyPanelDesc": "In diesem Bereich wird der Verlauf der am Datensatz vorgenommenen Änderungen angezeigt. Klicken Sie auf die Schaltflächen Anzeigen, um frühere Versionen zu untersuchen. Um zu einer früheren Version zurückzukehren, zeigen Sie sie einfach an und klicken Sie auf die Schaltfläche \"Auf diese Version zurücksetzen\".",
+ "hostName": "Host-Name",
+ "humanData": "Human-Daten",
+ "ifWantToReregister": "Wenn Sie sich erneut registrieren möchten, besuchen Sie bitte:",
+ "incomplete": "Unvollständig",
+ "install": "Installieren",
+ "introToGenerator": "Einführung in den Generator",
+ "introToGeneratorDesc": "Diese Tour bietet Ihnen eine allgemeine Einführung in die Hauptfunktionen des Datengenerators. Lass uns anfangen!",
+ "invalidSettings": "Ungültige Einstellungen!",
+ "language": "Deutsch",
+ "lastEdited": "zuletzt geändert",
+ "lastLoggedIn": "Letzter Login",
+ "lastModified": "Letzte Änderung",
+ "lastName": "Nachname",
+ "lastSaved": "zuletzt gespeichert",
+ "lineWrapping": "Zeilenumbruch",
+ "linkToDataSet": "Link zum Datensatz",
+ "linkToThisDataSet": "Link zu diesem Datensatz",
+ "live": "Wohnen",
+ "load": "Laden",
+ "loading": "Lädt...",
+ "login": "Login",
+ "loginToSave": "Um Ihre Datensätze speichern zu können, benötigen Sie zunächst ein Konto. Bitte melden Sie sich unten an.",
+ "loginOrRegisterToSave": "Um Ihre Datensätze speichern zu können, benötigen Sie zunächst ein Konto. Bitte loggen Sie sich ein oder registrieren Sie sich unten.",
+ "loginUrlLabel": "Anmelde-URL:",
+ "logout": "Abmelden",
+ "makeDataSetPublicAgreement": "Ich verstehe, dass ich diesen Datensatz öffentlich machen muss.",
+ "metaDescription": "GenerateData.com: kostenlos, GNU-lizenzierte, zufälliger benutzerdefinierter Daten-Generator zum Testen von Software",
+ "metaKeywords": "Random Data, Test Data, Sample-Daten, Daten-Generator, Daten generieren",
+ "missingDataSetName": "Bitte geben Sie den Namen Ihres Datensatzes ein",
+ "nameColumn": "Namensspalte",
+ "nameColumnDesc": "Diese Spalte erhält eine andere Überschrift, abhängig vom Exporttyp, den Sie im Vorschaufenster ausgewählt haben (XML, CSV usw.). Der Zweck dieser Spalte besteht darin, eine Kennung für die Zeile bereitzustellen, die dann in den generierten Daten verwendet werden kann. Beispielsweise verwendet XML diesen Wert als XML-Knotennamen. JSON verwendet es als JSON-Eigenschaftsnamen. SQL verwendet es für den Namen der Datenbankspalte. Die Anwendung überprüft automatisch den hier eingegebenen Wert, um sicherzustellen, dass er für den ausgewählten Exporttyp gültig ist.",
+ "newDataSet": "Neuer Datensatz",
+ "no": "Nein",
+ "noAccountsCreated": "Es wurden keine Konten erstellt.",
+ "noAdditionalSettings": "Keine zusätzlichen Einstellungen",
+ "noDataSetsSaved": "Sie haben keine Datensätze gespeichert.",
+ "noExamplesAvailable": "Keine Beispiele zur Verfügung.",
+ "noExpiry": "Kein Ablauf",
+ "noHistory": "Noch keine Geschichte.",
+ "noOptionsAvailable": "Keine Optionen verfügbar.",
+ "noSafari": "Sorry, keine Safari!",
+ "northAmerica": "Nordamerika",
+ "nowLoggedIn": "Sie wurden angemeldet.",
+ "nowLoggedOut": "Sie wurden ausgeloggt.",
+ "numResults": "Anz. Ergebnisse",
+ "numeric": "Numerisch",
+ "oceania": "Ozeanien",
+ "oneTimePasswordLogin": "Sie wurden mit einem Einmalkennwort angemeldet. Bitte legen Sie jetzt ein neues Passwort fest.",
+ "open": "öffnen",
+ "options": "Optionen",
+ "optionsColumn": "Optionsspalte",
+ "optionsColumnDesc1": "Diese Spalte enthält alle Konfigurationsoptionen für den Datentyp dieser Zeile. Einige Datentypen verfügen über zu viele Konfigurationseinstellungen, um auf so kleinem Raum angezeigt zu werden (z. B. Land, Region). Daher wird möglicherweise nur eine Schaltfläche im Raster angezeigt, die ein Dialogfeld mit allen Optionen öffnet.",
+ "optionsColumnDesc2": "Für den hier dargestellten Namensdatentyp enthält die Spalte ein einzelnes Feld \"Erstellbare Pille\". Dies ist ein allgemeiner Feldtyp im Datengenerator. Sie können Elemente hinzufügen, indem Sie sie einfach eingeben und auf klicken. Anschließend werden diese Elemente in verschiedene Pillen umgewandelt, die über das Symbol \"x\" entfernt werden können.",
+ "or": "oder",
+ "order": "Bestellen",
+ "other": "Andere",
+ "overMaxAnonRows": "Leider können Sie nur %1 Zeilen generieren. Um größere Datenmengen zu generieren, benötigen Sie ein Benutzerkonto auf dieser Site.",
+ "panelContents": "Panel-Inhalt",
+ "panelContentsDesc": "Der Großteil des Vorschaufensters enthält ein Beispiel der generierten Daten gemäß den im Rasterfenster angegebenen Daten und den ausgewählten Einstellungen für den Exporttyp. Hier werden 5 verschiedene Datentypen (Land, Region, Stadt, Straße und Postleitzahl) im Typoskriptformat generiert.",
+ "panelControls": "Bedienelemente",
+ "panelControlsClearIconDesc": "Mit dem letzten Symbol in den Bedienfeldern können Sie die Seite löschen und neu beginnen.",
+ "panelControlsDesc": "Mit den Bedienfeldschaltflächen können Sie die Raster- und Vorschaufenster ein- / ausblenden. Dies kann hilfreich sein, wenn Sie mit begrenzten Bildschirmflächen arbeiten. Sie können die Position der beiden Bedienfelder auch relativ zueinander (links-rechts, oben-unten) mithilfe des dritten Symbols umschalten.",
+ "panelTabs": "Bedienfeld-Registerkarten",
+ "panelTabsDesc": "Zuletzt haben Sie vielleicht bemerkt, dass das Bedienfeld in zwei Registerkarten unterteilt ist. Standardmäßig wird die Registerkarte Einstellungen angezeigt, da Sie dort am wahrscheinlichsten hin möchten. Es gibt aber auch eine zweite Registerkarte \"Vorschau\", die Einstellungen zum Ändern des Erscheinungsbilds des Vorschaufensters enthält: das Thema, Ausblenden / Anzeigen von Zeilennummern, Umschalten des Zeilenumbruchs, Ändern der Textgröße und Steuern der Anzahl der generierten Zeilen.",
+ "password": "Kennwort",
+ "passwordLabel": "Passwort:",
+ "passwordReset": "Passwort zurücksetzen",
+ "passwordResetAccountExpired": "Passwort zurücksetzen - Konto abgelaufen",
+ "passwordResetAccountExpiredDesc": "Wir haben gerade eine Anfrage zum Zurücksetzen Ihres Passworts erhalten, Ihr Konto ist jedoch bereits abgelaufen.",
+ "passwordResetComplete": "Ihr Passwort wurde zurückgesetzt und Ihnen wurde ein neues Passwort per E-Mail zugesendet.",
+ "passwordResetEmailContent1": "Ihr Passwort wurde zurückgesetzt. Sie können das folgenden Passwort nutzen, um sich anzumelden: %1",
+ "passwordResetEmailContent2": "Bitte ändern Sie dies, sobald Sie sich angemeldet haben.",
+ "passwordResetEmailDesc": "Wir haben eine Anfrage zum Zurücksetzen Ihres Passworts erhalten. Diese E-Mail enthält ein temporäres Einmalkennwort, mit dem Sie sich anmelden können. Nach dem Anmelden müssen Sie ein neues Kennwort festlegen.",
+ "passwordResetMsg": "Wenn die E-Mail gespeichert war, wurde Ihr Passwort zurückgesetzt und Ihnen wurden temporäre Anmeldeinformationen per E-Mail gesendet.",
+ "passwordUpdateInvalidPassword": "Ihr aktuelles Passwort ist falsch. Bitte erneut eingeben.",
+ "passwordUpdated": "Dein Passwort wurde aktualisiert.",
+ "pause": "Pause",
+ "pleaseConfirm": "Bitte bestätigen",
+ "pleaseEnterAll": "Bitte füllen Sie alle Felder aus",
+ "pleaseEnterDataSetName": "Bitte geben Sie den Namen des neuen Datensatzes ein.",
+ "pleaseFixErrors": "Bitte beheben Sie die folgenden Fehler und versuchen Sie es erneut:",
+ "pleaseLogin": "Bitte loggen Sie sich ein",
+ "pleaseSelect": "Bitte auswählen",
+ "plugins": "Plugins",
+ "pressEnterAddItem": "Drücken Sie die Eingabetaste, um ein Element hinzuzufügen",
+ "preview": "Vorschau",
+ "previewPanel": "Vorschaufenster",
+ "previewPanelControlsDesc": "Oben rechts im Bedienfeld befinden sich zwei Symbole. Das erste Symbol aktualisiert das Bedienfeld und generiert jedes Mal andere zufällige Daten. Auf diese Weise erhalten Sie eine Vorstellung davon, welche Art von Daten generiert werden. Das zweite Symbol schließt das Bedienfeld vollständig. Sie können das Bedienfeld jederzeit über den Abschnitt unten rechts auf der Seite erneut anzeigen (aktivieren Sie einfach das Kontrollkästchen \"Vorschau\" erneut).",
+ "previewPanelDesc": "Dieses Bedienfeld bietet Ihnen eine Live-Vorschau der Daten, die Sie generieren, während Sie sie über das Rasterbedienfeld erstellen. Die Schaltfläche oben links zeigt das ausgewählte Format der generierten Daten (z. B. SQL, XML, CSV usw.). Um das Format zu ändern, klicken Sie einfach auf die Schaltfläche.",
+ "previewPanelMoreInfo": "Weitere Informationen zu diesem Bereich finden Sie in der Tour zum Vorschaubereich.",
+ "previewPanelNoData": "Keine Daten!",
+ "previewPanelTourDesc": "Das Vorschaufenster bietet Ihnen eine visuelle Live-Darstellung der Daten, die Sie während der Erstellung generieren. Diese Tour bietet eine kurze Einführung in die Funktionsweise des Panels.",
+ "previewRows": "Vorschau von Zeilen",
+ "private": "Privat",
+ "problemLoadingTour": "Entschuldigung, beim Laden der Tour ist ein Problem aufgetreten.",
+ "programmingLanguages": "Programmiersprachen",
+ "province": "Provinz",
+ "public": "Öffentlichkeit",
+ "publicQ": "Öffentlich?",
+ "reenterPassword": "Passwort erneut eingeben",
+ "refreshPage": "Seite aktualisieren",
+ "refreshPanel": "Bedienfeld aktualisieren",
+ "register": "Registrieren",
+ "remainingTime": "Verbleibende Zeit:",
+ "requiredField": "Pflichtfeld",
+ "revertToVersion": "Zurück zu dieser Version",
+ "row": "reihe",
+ "rowLabel": "Spaltentitel",
+ "rowLabelPlural": "Spaltentitel",
+ "rowNumDesc1": "Die erste Spalte im Raster zeigt die Zeilennummer. Um die Zeile neu anzuordnen, klicken Sie einfach auf dieses Element und ziehen Sie es nach oben oder unten.",
+ "rowNumDesc2": "Der Kopfwert für diese Spalte zeigt die Gesamtzahl der Zeilen in Ihrem Raster. Dies kann nützlich sein, wenn Sie wirklich große Datenmengen haben und diese zu groß sind, um auf den Bildschirm zu passen.",
+ "rowNumber": "Zeilennummer",
+ "rowSp": "Zeile(n)",
+ "rows": "Zeilen",
+ "rowsGenerated": "Zeilen generiert",
+ "rowsGeneratedPerSecond": "Zeilen werden pro Sekunde generiert",
+ "safariExplanation": "Diese Website erfordert einen aktuellen Webbrowser, der moderne Webstandards implementiert. Leider ist Safari hinter den anderen Browsern zurückgefallen und führt dieses Skript nicht aus. Verwenden Sie einen der folgenden Browser, bis wir herausgefunden haben, wie wir es unterbringen können. Alles kostenlos zum Download.",
+ "save": "Speichern",
+ "saveAs": "Speichern als",
+ "saveButtonDesc": "Wenn Sie ein Benutzerkonto auf der Site haben, speichert diese Schaltfläche Ihren Datensatz. Wenn Sie nicht angemeldet sind, werden Sie durch Klicken darauf aufgefordert, sich zuerst anzumelden. Das System speichert den Verlauf der letzten %1-Änderungen an einem Datensatz, sodass Sie jederzeit zurückgehen und ältere Versionen anzeigen können.",
+ "saveDataSetNewName": "Datensatz als neuen Namen speichern",
+ "seconds": "Sekunden",
+ "seeHelpDialog": "Siehe Hilfe-Dialog.",
+ "selectCountry": "Land auswählen",
+ "selectDataType": "Daten-Typen auswählen",
+ "selectEllipsis": "Wählen...",
+ "selectExpiryDate": "Ablaufdatum auswählen",
+ "selectLanguage": "Sprache auswählen",
+ "sendEmail": "E-Mail senden",
+ "seriouslySlow": "ernsthaft langsam",
+ "settings": "Einstellungen",
+ "showGrid": "Raster anzeigen",
+ "showLineNumbers": "Vorschau von Zeilen",
+ "showPreview": "Vorschau zeigen",
+ "siteLogo": "Site-Logo",
+ "size": "Größe:",
+ "southAmerica": "Südamerika",
+ "status": "Status",
+ "stripWhitespace": "Entfernen Sie Leerzeichen von generierten Inhalten",
+ "success": "Erfolgreich",
+ "text": "Text",
+ "textSize": "Textgröße",
+ "theDataSetName": "Der Datensatzname",
+ "theExportTypeBtn": "Die Schaltfläche Typ exportieren",
+ "theGenerateButton": "Die Schaltfläche Generieren",
+ "theGridPanel": "Das Grid Panel",
+ "thePreviewPanel": "Das Vorschaufenster",
+ "theSaveButton": "Die Schaltfläche Speichern",
+ "theme": "Thema",
+ "togglePanelLayout": "Panel-Layout umschalten",
+ "totalNumGeneratedRows": "Gesamtzahl der generierten Zeilen",
+ "tourComplete": "Tour abgeschlossen",
+ "tourCompleteDesc": "Alles erledigt! Wählen Sie eine der Schaltflächen unten, um fortzufahren.",
+ "tourIntroPara1": "Dieses Tool bietet viele Funktionen und kann zunächst etwas überwältigend sein. Klicken Sie auf eine der Schaltflächen rechts, um eine Tour zu dieser bestimmten Funktion zu machen.",
+ "tourIntroPara2": "Beachten Sie, dass zu Beginn der Tour der Inhalt der Generatorseite vorübergehend überschrieben wird, um bestimmte Aspekte der Benutzeroberfläche zu veranschaulichen. Aber mach dir keine Sorgen! Sobald die Tour beendet ist, werden Ihre Daten in den ursprünglichen Zustand zurückversetzt.",
+ "tryDifferentTour": "Versuchen Sie eine andere Tour",
+ "update": "Aktualisieren",
+ "updateAccount": "Benutzerkonto aktualisieren",
+ "user": "Benutzer",
+ "userAccount": "Benutzerkonto",
+ "userAccountNotFound": "Ihr Konto konnte leider nicht gefunden werden.",
+ "userAccountUpdated": "Das Benutzerkonto wurde aktualisiert.",
+ "userAccounts": "Benutzerkonten",
+ "userNotFound": "Entschuldigung, wir konnten Sie nicht identifizieren. Bitte überprüfen Sie Ihre Anmeldeinformationen.",
+ "validationAccountAlreadyExists": "Sorry, ein Konto mit dieser E-Mailadresse ist bereits vorhanden.",
+ "validationDifferentNewPasswords": "Bitte stellen Sie sicher, dass Ihre neuen Passwörter übereinstimmen",
+ "validationInvalidEmail": "Bitte geben Sie eine gültige E-Mailadresse ein.",
+ "validationNoCurrentPassword": "Bitte geben Sie Ihr aktuelles Passwort ein",
+ "validationNoEmail": "Bitte geben Sie Ihre E-Mailadresse ein.",
+ "validationNoFirstName": "Bitte geben Sie Ihren Vornamen ein.",
+ "validationNoLastName": "Bitte geben Sie Ihren Nachnamen an.",
+ "validationNoPassword": "Bitte geben Sie Ihr Passwort ein.",
+ "version": "Version",
+ "view": "Aussicht",
+ "viewChangelog": "Änderungsprotokoll anzeigen",
+ "viewOnGithub": "Blick auf Github",
+ "welcomeToTheGenerator": "Willkommen im Generator!",
+ "yes": "Ja",
+ "yourAccount": "Ihr Konto",
+ "yourAccountUpdated": "Ihr Benutzerkonto wurde aktualisiert.",
+ "yourUserAccount": "Ihr Benutzerkonto"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/en.json b/apps/client/src/i18n/en.json
new file mode 100644
index 000000000..29063dd9f
--- /dev/null
+++ b/apps/client/src/i18n/en.json
@@ -0,0 +1,329 @@
+{
+ "abort": "Abort",
+ "about": "About",
+ "aboutInfoPara1": "generatedata.com is an open source project I created back in the late paleolithic era (circa 2004) and has had many, many contributors over its lifespan. Every few years I rewrite it using the latest and greatest available technologies: this version was completed in 2021, made with React, Redux, Typescript, GraphQL, Docker, web workers, rubber bands, duct tape and a whole lot of cursing.",
+ "aboutInfoPara2": "You can find the project on github. Download it, fork it, or feel free to contribute. Enjoy!",
+ "aboutThisScript": "About this script",
+ "accountCreated": "Account Created",
+ "accountCreatedDesc": "The account has been created.",
+ "accountCreatedMsg": "An account has been created for you.",
+ "accountDeleted": "The account has been deleted.",
+ "accountDisabled": "Account disabled",
+ "accountExpiredMsg": "Sorry, your account has expired.",
+ "accountExpiryDate": "Account expiry date",
+ "accountInfo": "Account Info",
+ "accountSettings": "Account Settings",
+ "accountType": "Account type",
+ "accounts": "Accounts",
+ "add": "Add",
+ "addRowsDesc": "To add more rows to the data grid just use the form at the bottom of the page. When you have a lot of fields, this section may be hidden off page so you'll have to scroll the panel to the bottom to see the form.",
+ "addSomeDataDesc": "Add some rows in the grid.",
+ "admin": "Admin",
+ "adminAccount": "Admin account",
+ "administrator": "Administrator",
+ "africa": "Africa",
+ "allCountries": "All countries",
+ "allDataTypePlugins": "All Data Type plugins",
+ "allExportTypePlugins": "All Export Type plugins",
+ "anonymousAccess": "Anonymous access",
+ "asia": "Asia",
+ "back": "back",
+ "backToLogin": "Back to login",
+ "blog": "Blog",
+ "cancel": "cancel",
+ "centralAmerica": "Central America",
+ "changePassword": "Change Password",
+ "clear": "Clear",
+ "clearPage": "Clear Page",
+ "clearPageConfirmation": "Are you sure you want to clear the page? Any changes you've made will be lost.",
+ "clearThePage": "Clear the Page",
+ "clickExportTypeBtn": "The next step shows what happens when you click the button.",
+ "close": "Close",
+ "closePanel": "Close panel",
+ "columns": "Columns",
+ "columnsDesc": "The grid will show as many of these columns as it can, depending on your available screen size. When it's too small, it'll hide some columns and show a cog icon that opens up an infotip with the missing content. The next few steps explain each of the columns, using the first Names row as a demonstration.",
+ "complete": "Complete",
+ "confirmDeleteAccount": "Are you sure you want to delete this account?",
+ "confirmDeleteUserAccount": "Are you sure you want to delete this user account?",
+ "confirmEmptyForm": "Are you sure you want to clear the page?",
+ "continue": "Continue",
+ "copiedToClipboard": "Copied to clipboard",
+ "copyToClipboard": "Copy to clipboard",
+ "coreExportType": "Core formats",
+ "countries": "Countries",
+ "country": "Country",
+ "countrySpecific": "Country-specific",
+ "cpuMeltinglyFast": "CPU-meltingly fast",
+ "createAccount": "Create Account",
+ "currentPassword": "Current password",
+ "currentVersion": "Current version being edited",
+ "dataGenerated": "Data generated.",
+ "dataSet": "Data Set",
+ "dataSetHelp": "This is where you define exactly what kind of data you want to generate. Try filling in a row or two and click the generate button. You'll get the hang of it pretty fast.",
+ "dataSetLoaded": "The data set has been loaded.",
+ "dataSetName": "Data Set Name",
+ "dataSetNameDesc": "Click on the text here to name your data set. Please note that you'll need an account on the site and to be logged in in order to name your data sets. When you save your data sets, this name gives you a convenient label to keep track of them.",
+ "dataSetNameUpdated": "The data set name has been updated.",
+ "dataSetOptions": "Data Set Options",
+ "dataSetReverted": "Your data set has been reverted to the selected older version.",
+ "dataSetSaved": "Your data set has been saved.",
+ "dataSets": "Data Sets",
+ "dataType": "Data Type",
+ "dataTypeDesc": "This is a dropdown containing all available Data Types. When you select a value, it'll update the remaining of the columns for that row. Each Data Type has its own unique settings to allow customization.",
+ "dataTypes": "Data Types",
+ "dateAccountCreated": "Date account created",
+ "dateCreated": "Date Created",
+ "defaultLanguage": "Default Language",
+ "delete": "Delete",
+ "delete1DataSet": "Delete 1 Data Set",
+ "deleteAccount": "Delete Account",
+ "deleteDataSet": "Delete Data Set",
+ "deleteDataSetConfirm": "Are you sure you want to delete this Data Set?",
+ "deleteRow": "Delete row",
+ "deleteRowDesc": "The last column contains a delete icon. Clicking it will delete the row.",
+ "developer": "Developer",
+ "developerDoc": "Developer doc",
+ "disabled": "Disabled",
+ "documentation": "Documentation",
+ "download": "Download",
+ "edit": "Edit",
+ "editAccount": "Edit Account",
+ "editExportTypePage": "When you click the Export Type button, a full-page view appears containing the configuration options on the left side and the preview panel on the right.",
+ "editExportTypePageConfig": "Let's look at the configuration options.",
+ "editExportTypeSettings": "Please edit your export type settings.",
+ "editingExportTypes": "Editing Export Types",
+ "email": "Email",
+ "emailFooterDisclaimer": "If you've received this email in error please reach out to the site administrator.",
+ "emailIntroLineWithName": "Hi %1,",
+ "emailLabel": "Email:",
+ "emailNotSent": "We were unable to send the email notification.",
+ "emailUserLoginInfo": "Email the user their login information",
+ "enterEmailAddressToResetPassword": "Enter your email address below to reset your password.",
+ "enterUserAccountDetails": "Please enter your user account details below.",
+ "errorCreatingAccount": "There was an error creating this account.",
+ "estimatedSize": "Estimated size:",
+ "estimatedTime": "Estimated time:",
+ "europe": "Europe",
+ "example": "Example",
+ "exampleColumn": "Example column",
+ "exampleColumnDesc": "This column may or may not have content depending on the Data Type you selected for the row. It's just a convenient way for some Data Types to provide preset examples for how they can be used. For example, the Names column provides a dropdown of different name formats. For all Data Types, you can still manually configure it through the Options column: it's really a time-saving device to get you up to speed as quickly as possible.",
+ "examples": "Examples",
+ "exit": "Exit",
+ "expired": "Expired",
+ "expiryDate": "Expiry Date",
+ "exportType": "Export Type",
+ "exportTypeBtnDesc": "The button at the top-left of the panel is where you go to select and configure the format for the data you want to generate. Here it's set to Typescript.",
+ "exportTypeOptionsSql": "Export Type Options: SQL",
+ "exportTypeOptionsSqlDesc": "As you can see, SQL has a lot of other fields that are specific to this Export Type. Other Export Types have different settings.",
+ "exportTypeOptionsTs": "Export Type Options: Typescript",
+ "exportTypeOptionsTsDesc": "Depending on the selected Export Type, the panel body may contain additional options. Here with Typescript there are two fields, letting you customize the type name and the exported variable name.",
+ "exportTypeOptionsTsDesc2": "Now let's look at SQL.",
+ "exportTypeSelection": "Export Type Selection",
+ "exportTypeSelectionDesc": "The Format dropdown lets you choose the format for the generated content. There are many options, some of them the core types: SQL, CSV, SQL, JSON and others, and some of them are for generating data for specific programming languages.",
+ "exportTypes": "Export Types",
+ "filterDataTypes": "Filter Data Types",
+ "financial": "Financial",
+ "firstName": "First Name",
+ "forgottenYourPasswordQ": "Forget password?",
+ "format": "Format",
+ "generate": "Generate",
+ "generateBtnDesc": "Once you've finished configuring your data set and confirmed that it looks how you want, it's time to generate some data in volume! Click this button to generate your data. Depending on the number of rows in the data grid and the number of rows you want to generate, this may take some time.",
+ "generated": "Generated",
+ "generatedC": "Generated:",
+ "generator": "Generator",
+ "geo": "Geo",
+ "grid": "Grid",
+ "gridPanelTourDesc1": "The grid panel is where you construct the data you want to generate. Here you can choose between the different Data Types, how they're configured and the order in which they appear. In the example here, we have five fields: Name, Phone, Email, Street address and City.",
+ "gridPanelTourDesc2": "See the tour on the Grid Panel for more detailed information on this section.",
+ "gridPanelTourIntroDesc1": "The grid panel is where you define what data you want to generate. For this tour, we've hidden the preview panel (see the unchecked \"Preview\" button at the bottom right of the page) to give us a little more space. We've also added 10 rows of different data types, just for illustration purposes.",
+ "gridPanelTourIntroDesc2": "Let's start with the grid columns.",
+ "help": "Help",
+ "helpIcon": "Help Icon",
+ "helpIconDesc": "When you select a Data Type in the previous column, an icon will appear here. Clicking it opens a help dialog containing usage information.",
+ "hide": "Hide",
+ "hideShowGrid": "Hide/show grid",
+ "hideShowPreviewPanel": "Hide/show preview panel",
+ "history": "History",
+ "historyPanelDesc": "This panel shows the history of changes made to the data set. Click the View buttons to examine earlier versions. To revert to an earlier version, just view it, then click the \"Revert to this Version\" button.",
+ "hostName": "Host Name",
+ "humanData": "Human Data",
+ "ifWantToReregister": "If you'd like to re-register please visit:",
+ "incomplete": "Incomplete",
+ "install": "Install",
+ "introToGenerator": "Intro to the Generator",
+ "introToGeneratorDesc": "This tour gives you a high-level introduction to the main features of the Data Generator. Let's get started!",
+ "invalidSettings": "Invalid settings!",
+ "language": "English",
+ "lastEdited": "last edited",
+ "lastLoggedIn": "Last Logged In",
+ "lastModified": "Last Modified",
+ "lastName": "Last Name",
+ "lastSaved": "last saved",
+ "lineWrapping": "Line wrapping",
+ "linkToDataSet": "Link to Data Set",
+ "linkToThisDataSet": "Link to this Data Set",
+ "live": "Live",
+ "load": "Load",
+ "loading": "Loading...",
+ "login": "Login",
+ "loginToSave": "In order to save your data sets you first need an account. Please login below.",
+ "loginOrRegisterToSave": "In order to save your data sets you first need an account. Please login or register below.",
+ "loginUrlLabel": "Login URL:",
+ "logout": "Logout",
+ "makeDataSetPublicAgreement": "I understand that to share this Data Set, I need to make it public.",
+ "metaDescription": "generatedata.com: free, GNU-licensed, random custom data generator for testing software",
+ "metaKeywords": "Random Data, Test Data, Sample Data, data generator, generate data",
+ "missingDataSetName": "Please enter the name of your data set",
+ "nameColumn": "Name column",
+ "nameColumnDesc": "This column gets a different heading depending on the Export Type you've selected in the preview panel (XML, CSV etc). The purpose of this column is to provide an identifier for the row which can then be used in the generated data. For example, XML uses this value as the XML node name; JSON uses it as the JSON property name; SQL uses it for the database column name. The application will automatically validate the value you'd entered here to make sure it's valid for the selected Export Type.",
+ "newDataSet": "New Data Set",
+ "no": "No",
+ "noAccountsCreated": "No accounts have been created.",
+ "noAdditionalSettings": "No additional settings.",
+ "noDataSetsSaved": "You have no data sets saved.",
+ "noExamplesAvailable": "No examples available.",
+ "noExpiry": "No expiry",
+ "noHistory": "No history yet.",
+ "noOptionsAvailable": "No options available.",
+ "noSafari": "Sorry, no Safari!",
+ "northAmerica": "North America",
+ "nowLoggedIn": "You have been logged in.",
+ "nowLoggedOut": "You have been logged out.",
+ "numResults": "Num Results",
+ "numeric": "Numeric",
+ "oceania": "Oceania",
+ "oneTimePasswordLogin": "You've logged in using a one-time password. Please set a new one now or you won't be able to log in again.",
+ "open": "Open",
+ "options": "Options",
+ "optionsColumn": "Options column",
+ "optionsColumnDesc1": "This column contains all configuration options for this row's Data Type. Some Data Types have too many configuration settings to show in such a small space (e.g. Country, Region), so they may just show a button in the grid that opens a dialog containing all the options.",
+ "optionsColumnDesc2": "For the Names Data Type illustrated here, the column contains a single Creatable Pill field. This is a common field type in the Data Generator. It lets you add items by just typing into it and clicking . It then converts those items into distinct pills which can be removed via the \"x\" icon.",
+ "or": "or",
+ "order": "Order",
+ "other": "Other",
+ "overMaxAnonRows": "Sorry, you can only generate %1 rows. To generate larger volumes of data you'll need a user account on this site.",
+ "panelContents": "Panel contents",
+ "panelContentsDesc": "The bulk of the preview panel contains a sample of the generated data, as per the data you've specified in the grid panel and the selected Export Type settings. Here it's generating 5 different data types (Country, Region, City, Street Address and Postal/Zip) in Typescript format.",
+ "panelControls": "Panel controls",
+ "panelControlsClearIconDesc": "The last icon in the panel controls lets you clear the page so you can start afresh.",
+ "panelControlsDesc": "The panel control buttons let you hide/show the Grid and Preview panels which can be helpful if you're working with limited screen real estate. You can also toggle the placement of the two panels relative to one another (left-right, top-bottom) using the third icon.",
+ "panelTabs": "Panel tabs",
+ "panelTabsDesc": "Lastly, you may have noticed that the panel is organized into two tabs. By default it shows the Settings tab, since that's where you're most likely to want to go. But there's also a second \"Preview\" tab that contains settings for changing the appearance of the preview panel: the theme, hiding/showing line numbers, toggling line wrapping, changing the text size and controlling the number of generated rows.",
+ "password": "Password",
+ "passwordLabel": "Password:",
+ "passwordReset": "Password Reset",
+ "passwordResetAccountExpired": "Password Reset - Account Expired",
+ "passwordResetAccountExpiredDesc": "We just received a request to reset your password, however your account has already expired.",
+ "passwordResetComplete": "Your password has been reset and you have been emailed the new password.",
+ "passwordResetEmailContent1": "Your password has been reset. You may use the following password to log in: %1",
+ "passwordResetEmailContent2": "Please change it once you've logged in.",
+ "passwordResetEmailDesc": "We've received a request to reset your password. This email contains a temporary one-time password you can use to log in. After logging in, you'll need to set a new password.",
+ "passwordResetMsg": "If the email was on file, your password has been reset and your have been emailed temporary login information.",
+ "passwordUpdateInvalidPassword": "Your current password is incorrect. Please re-enter.",
+ "passwordUpdated": "Your password has been updated.",
+ "pause": "Pause",
+ "pleaseConfirm": "Please confirm",
+ "pleaseEnterAll": "Please enter all",
+ "pleaseEnterDataSetName": "Please enter the name of the new Data Set.",
+ "pleaseFixErrors": "Please fix the following errors and resubmit:",
+ "pleaseLogin": "Please login",
+ "pleaseSelect": "Please Select",
+ "plugins": "Plugins",
+ "pressEnterAddItem": "Press enter to add item",
+ "preview": "Preview",
+ "previewPanel": "Preview Panel",
+ "previewPanelControlsDesc": "At the top right of the panel there are two icons. The first icon refreshes the panel, generating different random data each time - this helps to give you an idea of the sort of data that it generates. The second icon closes the panel altogether. You can always show the panel again using the section at the bottom right of the page (just re-select the \"Preview\" checkbox).",
+ "previewPanelDesc": "This panel gives you a live preview of the data you're generating, while you're constructing it through the grid panel. The button at the top-left shows the selected format of the data being generated (e.g. SQL, XML, CSV etc.). To change the format, just click on the button.",
+ "previewPanelMoreInfo": "For more info on this panel, check out the Preview Panel tour.",
+ "previewPanelNoData": "No data!",
+ "previewPanelTourDesc": "The preview panel gives you a live, visual representation of the data you're generating as you're constructing it. This tour gives a quick introduction to how the panel works.",
+ "previewRows": "Preview rows",
+ "private": "Private",
+ "problemLoadingTour": "Sorry, there was a problem loading the tour.",
+ "programmingLanguages": "Programming Languages",
+ "province": "Province",
+ "public": "Public",
+ "publicQ": "Public?",
+ "reenterPassword": "Re-enter password",
+ "refreshPage": "Refresh Page",
+ "refreshPanel": "Refresh panel",
+ "register": "Register",
+ "remainingTime": "Remaining time:",
+ "requiredField": "Required field",
+ "revertToVersion": "Revert to this version",
+ "row": "Row",
+ "rowLabel": "Column Title",
+ "rowLabelPlural": "Column Titles",
+ "rowNumDesc1": "The first column in the grid shows the row number. To reorder the row, just click and drag this element up or down.",
+ "rowNumDesc2": "The header value for this column shows the total number of rows in your grid. This can be handy when you have really large data sets and it's too large to fit on the screen.",
+ "rowNumber": "Row Number",
+ "rowSp": "Row(s)",
+ "rows": "rows",
+ "rowsGenerated": "Rows Generated",
+ "rowsGeneratedPerSecond": "Rows generated per second",
+ "safariExplanation": "This site requires an up-to-date web browser that implements modern web standards. Unfortunately Safari has fallen behind the other browsers and won't run this script. Until we figure out how to accommodate it, please use one of the following browsers. All free for download.",
+ "save": "Save",
+ "saveAs": "Save as",
+ "saveButtonDesc": "If you have a user account on the site this button will save your data set. If you're not logged in, clicking it will prompt you to login first. The system keeps a history of the last %1 changes to a Data Set, so you can always go back and view older versions.",
+ "saveDataSetNewName": "Save data set as new name",
+ "seconds": "Seconds",
+ "seeHelpDialog": "See help dialog.",
+ "selectCountry": "Select Country",
+ "selectDataType": "Select Data Type",
+ "selectEllipsis": "Select...",
+ "selectExpiryDate": "Select expiry date",
+ "selectLanguage": "Select Language",
+ "sendEmail": "Send email",
+ "seriouslySlow": "Seriously slow",
+ "settings": "Settings",
+ "showGrid": "Show Grid",
+ "showLineNumbers": "Show line numbers",
+ "showPreview": "Show Preview",
+ "siteLogo": "site logo",
+ "size": "Size:",
+ "southAmerica": "South America",
+ "status": "Status",
+ "stripWhitespace": "strip whitespace from generated content",
+ "success": "success",
+ "text": "Text",
+ "textSize": "Text size",
+ "theDataSetName": "The Data Set Name",
+ "theExportTypeBtn": "The Export Type button",
+ "theGenerateButton": "The Generate Button",
+ "theGridPanel": "The Grid Panel",
+ "thePreviewPanel": "The Preview Panel",
+ "theSaveButton": "The Save Button",
+ "theme": "Theme",
+ "togglePanelLayout": "Toggle panel layout",
+ "totalNumGeneratedRows": "Total num generated rows",
+ "tourComplete": "Tour Complete",
+ "tourCompleteDesc": "All done! Choose one of the buttons below to continue.",
+ "tourIntroPara1": "This tool provides a lot of functionality and it can be a little overwhelming at first. Click on one of the buttons to the right to take a tour about that particular feature.",
+ "tourIntroPara2": "Note that when the tour starts, it temporarily overwrites the content of the generator page to illustrate certain things about the interface. But don't worry! As soon as the tour is ended, it returns your data to its original state.",
+ "tryDifferentTour": "Try a different tour",
+ "update": "Update",
+ "updateAccount": "Update Account",
+ "user": "User",
+ "userAccount": "User Account",
+ "userAccountNotFound": "Sorry, we couldn't find your account.",
+ "userAccountUpdated": "The user account has been updated.",
+ "userAccounts": "User Accounts",
+ "userNotFound": "Sorry, we were unable to identify you. Please check your credentials.",
+ "validationAccountAlreadyExists": "Sorry, an account with that email address already exists",
+ "validationDifferentNewPasswords": "Please make sure your passwords match",
+ "validationInvalidEmail": "Please enter a valid email address",
+ "validationNoCurrentPassword": "Please enter your current password",
+ "validationNoEmail": "Please enter your email address",
+ "validationNoFirstName": "Please enter your first name",
+ "validationNoLastName": "Please enter your last name",
+ "validationNoPassword": "Please enter your password",
+ "version": "Version",
+ "view": "View",
+ "viewChangelog": "View changelog",
+ "viewOnGithub": "View on Github",
+ "welcomeToTheGenerator": "Welcome to the generator!",
+ "yes": "Yes",
+ "yourAccount": "Your Account",
+ "yourAccountUpdated": "Your account has been updated.",
+ "yourUserAccount": "Your User Account"
+}
diff --git a/apps/client/src/i18n/es.json b/apps/client/src/i18n/es.json
new file mode 100644
index 000000000..e2d516313
--- /dev/null
+++ b/apps/client/src/i18n/es.json
@@ -0,0 +1,329 @@
+{
+ "abort": "Abortar",
+ "about": "Acerca de",
+ "aboutInfoPara1": "generatedata.com es un proyecto de código abierto que creé en la era paleolítica tardía (alrededor de 2004) y ha tenido muchos, muchos contribuyentes a lo largo de su vida útil. Cada pocos años lo reescribo usando las últimas y mejores tecnologías disponibles: esta versión se completó en 2021, hecha con React, Redux, Typescript, GraphQL, Docker, web Workers, gomas, cinta adhesiva y un montón de maldiciones.",
+ "aboutInfoPara2": "Puedes encontrar el proyecto en github. Descárgalo, bifurcalo o siéntete libre de contribuir. ¡Disfrutar!",
+ "aboutThisScript": "Acerca de este guión",
+ "accountCreated": "Cuenta creada",
+ "accountCreatedDesc": "Se ha creado la cuenta.",
+ "accountCreatedMsg": "Se ha creado una cuenta para ti.",
+ "accountDeleted": "La cuenta ha sido eliminada.",
+ "accountDisabled": "Cuenta deshabilitada",
+ "accountExpiredMsg": "Lo sentimos, su cuenta ha caducado.",
+ "accountExpiryDate": "Fecha de vencimiento de la cuenta",
+ "accountInfo": "Información de cuenta",
+ "accountSettings": "Ajustes de cuenta",
+ "accountType": "Tipo de cuenta",
+ "accounts": "Cuentas",
+ "add": "Añadir",
+ "addRowsDesc": "Para agregar más filas a la cuadrícula de datos, simplemente use el formulario en la parte inferior de la página. Cuando tiene muchos campos, esta sección puede estar oculta fuera de la página, por lo que tendrá que desplazar el panel hacia la parte inferior para ver el formulario.",
+ "addSomeDataDesc": "Agrega algunas filas en la cuadrícula.",
+ "admin": "Administrador",
+ "adminAccount": "Cuenta de administración",
+ "administrator": "Administrador",
+ "africa": "África",
+ "allCountries": "Todos los países",
+ "allDataTypePlugins": "Todos los complementos de tipos de dato",
+ "allExportTypePlugins": "Todos los complementos de tipos de exportación",
+ "anonymousAccess": "Acceso anónimo",
+ "asia": "Ásia",
+ "back": "Atrás",
+ "backToLogin": "Atrás para iniciar sesión",
+ "blog": "Blog",
+ "cancel": "cancelar",
+ "centralAmerica": "América Central",
+ "changePassword": "Cambia la contraseña",
+ "clear": "Clara",
+ "clearPage": "Limpiar página",
+ "clearPageConfirmation": "¿Estás seguro de que deseas borrar la página? Se perderán todos los cambios que haya realizado.",
+ "clearThePage": "Limpiar la página",
+ "clickExportTypeBtn": "El siguiente paso muestra lo que sucede cuando hace clic en el botón.",
+ "close": "Cerrar",
+ "closePanel": "Cerrar panel",
+ "columns": "Columnas",
+ "columnsDesc": "La cuadrícula mostrará tantas de estas columnas como sea posible, según el tamaño de pantalla disponible. Cuando sea demasiado pequeño, ocultará algunas columnas y mostrará un ícono de engranaje que abre un infotip con el contenido que falta. Los siguientes pasos explican cada una de las columnas, utilizando la primera fila de Nombres como demostración.",
+ "complete": "Completa",
+ "confirmDeleteAccount": "¿Estás segura de que quieres eliminar esta cuenta?",
+ "confirmDeleteUserAccount": "¿Estás seguro de querer eliminar esta cuenta de usuario?",
+ "confirmEmptyForm": "¿Está seguro de querer borrar la página?",
+ "continue": "Continuar",
+ "copiedToClipboard": "Copiada al portapapeles",
+ "copyToClipboard": "Copiar al portapapeles",
+ "coreExportType": "Formatos principales",
+ "countries": "Países",
+ "country": "País",
+ "countrySpecific": "Específico del país",
+ "cpuMeltinglyFast": "CPU increíblemente rápido",
+ "createAccount": "Crear cuenta",
+ "currentPassword": "Contraseña actual",
+ "currentVersion": "Se está editando la versión actual",
+ "dataGenerated": "Datos generados.",
+ "dataSet": "Conjunto de datos",
+ "dataSetHelp": "Aquí es donde defines exactamente qué tipo de datos quieres generar. Prueba a rellenar una fila o dos y haz clic en el botón Generar. Te harás una idea bastante buena.",
+ "dataSetLoaded": "Se ha cargado el conjunto de datos.",
+ "dataSetName": "Nombre del conjunto de datos",
+ "dataSetNameDesc": "Haga clic en el texto aquí para nombrar su conjunto de datos. Tenga en cuenta que necesitará una cuenta en el sitio y estar conectado para nombrar sus conjuntos de datos. Cuando guarda sus conjuntos de datos, este nombre le proporciona una etiqueta conveniente para realizar un seguimiento de ellos.",
+ "dataSetNameUpdated": "Se actualizó el nombre del conjunto de datos.",
+ "dataSetOptions": "Opciones de conjunto de datos",
+ "dataSetReverted": "Su conjunto de datos se ha revertido a la versión anterior seleccionada.",
+ "dataSetSaved": "Su conjunto de datos se ha guardado.",
+ "dataSets": "Conjuntos de datos",
+ "dataType": "Tipo de dato",
+ "dataTypeDesc": "Este es un menú desplegable que contiene todos los tipos de datos disponibles. Cuando seleccione un valor, actualizará el resto de las columnas de esa fila. Cada tipo de datos tiene su propia configuración única para permitir la personalización.",
+ "dataTypes": "Tipos de datos",
+ "dateAccountCreated": "Fecha de creación de la cuenta",
+ "dateCreated": "Fecha de creación",
+ "defaultLanguage": "Idioma por defecto",
+ "delete": "Eliminar",
+ "delete1DataSet": "Eliminar 1 Conjunto de Datos",
+ "deleteAccount": "Eliminar cuenta",
+ "deleteDataSet": "Eliminar conjunto de datos",
+ "deleteDataSetConfirm": "¿Está seguro de que desea eliminar este conjunto de datos?",
+ "deleteRow": "Borrar fila",
+ "deleteRowDesc": "La última columna contiene un icono de eliminación. Al hacer clic en él, se eliminará la fila.",
+ "developer": "Desarrollador",
+ "developerDoc": "Documentación de desarrollo",
+ "disabled": "Discapacitada",
+ "documentation": "Documentación",
+ "download": "Descargar",
+ "edit": "Editar",
+ "editAccount": "Editar cuenta",
+ "editExportTypePage": "Al hacer clic en el botón Tipo de exportación, aparece una vista de página completa que contiene las opciones de configuración en el lado izquierdo y el panel de vista previa a la derecha.",
+ "editExportTypePageConfig": "Veamos las opciones de configuración.",
+ "editExportTypeSettings": "Edite la configuración del tipo de exportación.",
+ "editingExportTypes": "Editar tipos de exportación",
+ "email": "Correo electrónico",
+ "emailFooterDisclaimer": "Si recibió este correo electrónico por error, comuníquese con el administrador del sitio.",
+ "emailIntroLineWithName": "Hola %1:",
+ "emailLabel": "Correo electrónico:",
+ "emailNotSent": "No se ha podido enviar el correo de notificación.",
+ "emailUserLoginInfo": "Enviar un correo con su información de entrada",
+ "enterEmailAddressToResetPassword": "Introduce tu dirección de correo más abajo para restablecer tu contraseña.",
+ "enterUserAccountDetails": "Por favor, introduce los detalles de tu cuenta de usuario más abajo.",
+ "errorCreatingAccount": "Hubo un error al crear esta cuenta.",
+ "estimatedSize": "Tamaño estimado:",
+ "estimatedTime": "Hora prevista:",
+ "europe": "Europa",
+ "example": "Ejemplo",
+ "exampleColumn": "Columna de ejemplo",
+ "exampleColumnDesc": "Esta columna puede tener o no contenido según el tipo de datos que seleccionó para la fila. Es solo una forma conveniente para que algunos tipos de datos proporcionen ejemplos preestablecidos de cómo pueden usarse. Por ejemplo, la columna Nombres proporciona un menú desplegable de diferentes formatos de nombre. Para todos los tipos de datos, aún puede configurarlo manualmente a través de la columna Opciones: realmente es un dispositivo que ahorra tiempo y lo pone al día lo más rápido posible.",
+ "examples": "Ejemplos",
+ "exit": "Salida",
+ "expired": "Caducada",
+ "expiryDate": "Fecha de caducidad",
+ "exportType": "Tipo de exportación",
+ "exportTypeBtnDesc": "El botón en la parte superior izquierda del panel es donde va a seleccionar y configurar el formato de los datos que desea generar. Aquí está configurado en TypeScript.",
+ "exportTypeOptionsSql": "Opciones de tipo de exportación: SQL",
+ "exportTypeOptionsSqlDesc": "Como puede ver, SQL tiene muchos otros campos que son específicos de este tipo de exportación. Otros tipos de exportación tienen configuraciones diferentes.",
+ "exportTypeOptionsTs": "Opciones de tipo de exportación: mecanografiado",
+ "exportTypeOptionsTsDesc": "Dependiendo del tipo de exportación seleccionado, el cuerpo del panel puede contener opciones adicionales. Aquí con TypeScript hay dos campos, lo que le permite personalizar el nombre del tipo y el nombre de la variable exportada.",
+ "exportTypeOptionsTsDesc2": "Ahora veamos SQL.",
+ "exportTypeSelection": "Selección de tipo de exportación",
+ "exportTypeSelectionDesc": "El menú desplegable Formato le permite elegir el formato del contenido generado. Hay muchas opciones, algunas de ellas los tipos principales: SQL, CSV, SQL, JSON y otras, y algunas de ellas son para generar datos para lenguajes de programación específicos.",
+ "exportTypes": "Tipos de exportación",
+ "filterDataTypes": "Filtrar tipos de datos",
+ "financial": "Financiera",
+ "firstName": "Nombre",
+ "forgottenYourPasswordQ": "¿Contraseña olvidada?",
+ "format": "Formato",
+ "generate": "Generar",
+ "generateBtnDesc": "Una vez que haya terminado de configurar su conjunto de datos y haya confirmado que se ve como desea, ¡es hora de generar algunos datos en volumen! Haga clic en este botón para generar sus datos. Dependiendo de la cantidad de filas en la cuadrícula de datos y la cantidad de filas que desee generar, esto puede llevar algún tiempo.",
+ "generated": "Generada",
+ "generatedC": "Generada:",
+ "generator": "Generador",
+ "geo": "Geográficos",
+ "grid": "Cuadrícula",
+ "gridPanelTourDesc1": "El panel de cuadrícula es donde construye los datos que desea generar. Aquí puede elegir entre los diferentes tipos de datos, cómo están configurados y el orden en que aparecen. En el ejemplo aquí, tenemos cinco campos: Nombre, Teléfono, Correo electrónico, Dirección y Ciudad.",
+ "gridPanelTourDesc2": "Consulte el recorrido en el Panel de cuadrícula para obtener información más detallada sobre esta sección.",
+ "gridPanelTourIntroDesc1": "El panel de cuadrícula es donde usted define qué datos desea generar. Para este recorrido, hemos ocultado el panel de vista previa (vea el botón \"Vista previa\" sin marcar en la parte inferior derecha de la página) para darnos un poco más de espacio. También hemos agregado 10 filas de diferentes tipos de datos, solo con fines ilustrativos.",
+ "gridPanelTourIntroDesc2": "Comencemos con las columnas de la cuadrícula.",
+ "help": "Ayuda",
+ "helpIcon": "Icono de ayuda",
+ "helpIconDesc": "Cuando seleccione un tipo de datos en la columna anterior, aparecerá un icono aquí. Al hacer clic en él, se abre un cuadro de diálogo de ayuda que contiene información de uso.",
+ "hide": "Esconder",
+ "hideShowGrid": "Ocultar / mostrar cuadrícula",
+ "hideShowPreviewPanel": "Ocultar / mostrar panel de vista previa",
+ "history": "Historia",
+ "historyPanelDesc": "Este panel muestra el historial de cambios realizados en el conjunto de datos. Haga clic en los botones Ver para examinar versiones anteriores. Para volver a una versión anterior, simplemente visualícela y, a continuación, haga clic en el botón \"Volver a esta versión\".",
+ "hostName": "Anfitrión",
+ "humanData": "Datos humanos",
+ "ifWantToReregister": "Si desea volver a registrarse, visite:",
+ "incomplete": "Incompleta",
+ "install": "Instalar",
+ "introToGenerator": "Introducción al generador",
+ "introToGeneratorDesc": "Este recorrido le brinda una introducción de alto nivel a las características principales del Generador de datos. ¡Empecemos!",
+ "invalidSettings": "¡Configuración no válida!",
+ "language": "Español",
+ "lastEdited": "editado por último",
+ "lastLoggedIn": "Última entrada",
+ "lastModified": "Última modificación",
+ "lastName": "Apellidos",
+ "lastSaved": "salvado por último",
+ "lineWrapping": "Envoltura de línea",
+ "linkToDataSet": "Enlazar a conjunto de datos",
+ "linkToThisDataSet": "Enlazar a este conjunto de datos",
+ "live": "Vivir",
+ "load": "Cargar",
+ "loading": "Cargando...",
+ "login": "Conexión",
+ "loginToSave": "Para guardar sus conjuntos de datos, primero necesita una cuenta. Inicie sesión a continuación.",
+ "loginOrRegisterToSave": "Para guardar sus conjuntos de datos, primero necesita una cuenta. Inicie sesión o regístrese a continuación.",
+ "loginUrlLabel": "URL de inicio de sesión:",
+ "logout": "Salir",
+ "makeDataSetPublicAgreement": "Entiendo que para compartir este conjunto de datos necesit hacerlo público.",
+ "metaDescription": "GenerateData.com: generador de datos aleatorios personalizable para pruebas de software libre y licenciado como GNU",
+ "metaKeywords": "Datos aleatorios, Datos de prueba, Datos de ejemplo, generador de datos, generar datos",
+ "missingDataSetName": "Ingrese el nombre de su conjunto de datos",
+ "nameColumn": "Columna de nombre",
+ "nameColumnDesc": "Esta columna tiene un encabezado diferente según el tipo de exportación que haya seleccionado en el panel de vista previa (XML, CSV, etc.). El propósito de esta columna es proporcionar un identificador para la fila que luego se puede utilizar en los datos generados. Por ejemplo, XML utiliza este valor como nombre de nodo XML; JSON lo usa como nombre de propiedad JSON; SQL lo usa para el nombre de la columna de la base de datos. La aplicación validará automáticamente el valor que ingresó aquí para asegurarse de que sea válido para el tipo de exportación seleccionado.",
+ "newDataSet": "Nuevo conjunto de datos",
+ "no": "No",
+ "noAccountsCreated": "No se han creado cuentas.",
+ "noAdditionalSettings": "Sin configuraciones adicionales",
+ "noDataSetsSaved": "No tiene conjuntos de datos guardados.",
+ "noExamplesAvailable": "No hay ejemplos disponibles.",
+ "noExpiry": "Sin Caducidad",
+ "noHistory": "Aún no hay historial.",
+ "noOptionsAvailable": "No hay opciones disponibles.",
+ "noSafari": "¡Lo siento, no Safari!",
+ "northAmerica": "Norteamérica",
+ "nowLoggedIn": "Ha iniciado sesión.",
+ "nowLoggedOut": "Has sido desconectado.",
+ "numResults": "Núm. resultados",
+ "numeric": "Numéricos",
+ "oceania": "Oceanía",
+ "oneTimePasswordLogin": "Ha iniciado sesión con una contraseña de un solo uso. Establezca una nueva contraseña ahora.",
+ "open": "abierto",
+ "options": "Opciones",
+ "optionsColumn": "Columna de opciones",
+ "optionsColumnDesc1": "Esta columna contiene todas las opciones de configuración para el tipo de datos de esta fila. Algunos tipos de datos tienen demasiados ajustes de configuración para mostrarlos en un espacio tan pequeño (por ejemplo, país, región), por lo que es posible que solo muestren un botón en la cuadrícula que abre un cuadro de diálogo que contiene todas las opciones.",
+ "optionsColumnDesc2": "Para el tipo de datos de nombres que se ilustra aquí, la columna contiene un solo campo Píldora creable. Este es un tipo de campo común en el generador de datos. Le permite agregar elementos simplemente escribiéndolos y haciendo clic en . Luego convierte esos elementos en distintas píldoras que se pueden eliminar mediante el icono \"x\".",
+ "or": "o",
+ "order": "Orden",
+ "other": "Otros",
+ "overMaxAnonRows": "Lo sentimos, solo puede generar %1 filas. Para generar mayores volúmenes de datos, necesitará una cuenta de usuario en este sitio.",
+ "panelContents": "Contenido del panel",
+ "panelContentsDesc": "La mayor parte del panel de vista previa contiene una muestra de los datos generados, según los datos que ha especificado en el panel de cuadrícula y la configuración de Tipo de exportación seleccionada. Aquí está generando 5 tipos de datos diferentes (país, región, ciudad, dirección postal y postal) en formato mecanografiado.",
+ "panelControls": "Controles del panel",
+ "panelControlsClearIconDesc": "El último icono de los controles del panel le permite borrar la página para que pueda empezar de nuevo.",
+ "panelControlsDesc": "Los botones de control del panel le permiten ocultar / mostrar los paneles de cuadrícula y vista previa, lo que puede ser útil si trabaja con un espacio de pantalla limitado. También puede alternar la ubicación de los dos paneles entre sí (de izquierda a derecha, de arriba a abajo) utilizando el tercer icono.",
+ "panelTabs": "Pestañas del panel",
+ "panelTabsDesc": "Por último, es posible que haya notado que el panel está organizado en dos pestañas. De forma predeterminada, muestra la pestaña Configuración, ya que es allí donde es más probable que desee ir. Pero también hay una segunda pestaña \"Vista previa\" que contiene configuraciones para cambiar la apariencia del panel de vista previa: el tema, ocultar / mostrar números de línea, alternar el ajuste de línea, cambiar el tamaño del texto y controlar el número de filas generadas.",
+ "password": "Contraseña",
+ "passwordLabel": "Contraseña:",
+ "passwordReset": "Restablecimiento de contraseña",
+ "passwordResetAccountExpired": "Restablecimiento de contraseña: cuenta caducada",
+ "passwordResetAccountExpiredDesc": "Acabamos de recibir una solicitud para restablecer su contraseña, sin embargo, su cuenta ya ha caducado.",
+ "passwordResetComplete": "Tu contraseña ha sido restablecida y hemos enviado un correo electrónico con la nueva contraseña.",
+ "passwordResetEmailContent1": "Tu contraseña ha sido restablecida. Ya puedes usar la siguiente contraseña para entrar: %1",
+ "passwordResetEmailContent2": "Por favor, cámbiala una vez hayas entrado de nuevo.",
+ "passwordResetEmailDesc": "Recibimos una solicitud para restablecer su contraseña. Este correo electrónico contiene una contraseña temporal de un solo uso que puede usar para iniciar sesión. Después de iniciar sesión, deberá establecer una nueva contraseña.",
+ "passwordResetMsg": "Si el correo electrónico estaba en el archivo, su contraseña se ha restablecido y se le ha enviado por correo electrónico información de inicio de sesión temporal.",
+ "passwordUpdateInvalidPassword": "Tu contraseña actual es incorrecta. Vuelva a ingresar.",
+ "passwordUpdated": "Tu contraseña ha sido actualizada.",
+ "pause": "Pausa",
+ "pleaseConfirm": "Por favor, confirma",
+ "pleaseEnterAll": "Por favor, introduce todo",
+ "pleaseEnterDataSetName": "Por favor, introduzca el nombre del nuevo conjunto de datos.",
+ "pleaseFixErrors": "Por favor, arregle los errores y envíe de nuevo el formulario:",
+ "pleaseLogin": "Por favor Iniciar sesión",
+ "pleaseSelect": "Por favor, seleccione",
+ "plugins": "Complementos",
+ "pressEnterAddItem": "Presione enter para agregar un elemento",
+ "preview": "Avance",
+ "previewPanel": "Panel de vista previa",
+ "previewPanelControlsDesc": "En la parte superior derecha del panel hay dos iconos. El primer icono actualiza el panel, generando diferentes datos aleatorios cada vez; esto ayuda a darte una idea del tipo de datos que genera. El segundo icono cierra el panel por completo. Siempre puede volver a mostrar el panel usando la sección en la parte inferior derecha de la página (simplemente vuelva a seleccionar la casilla de verificación \"Vista previa\").",
+ "previewPanelDesc": "Este panel le brinda una vista previa en vivo de los datos que está generando, mientras los construye a través del panel de cuadrícula. El botón en la parte superior izquierda muestra el formato seleccionado de los datos que se generan (por ejemplo, SQL, XML, CSV, etc.). Para cambiar el formato, simplemente haga clic en el botón.",
+ "previewPanelMoreInfo": "Para obtener más información sobre este panel, consulte el recorrido del Panel de vista previa.",
+ "previewPanelNoData": "¡Sin datos!",
+ "previewPanelTourDesc": "El panel de vista previa le brinda una representación visual en vivo de los datos que está generando a medida que los construye. Este recorrido ofrece una introducción rápida a cómo funciona el panel.",
+ "previewRows": "Vista previa de filas",
+ "private": "Privada",
+ "problemLoadingTour": "Lo sentimos, hubo un problema al cargar el recorrido.",
+ "programmingLanguages": "Lenguajes de programación",
+ "province": "Provincia",
+ "public": "Pública",
+ "publicQ": "¿Es público?",
+ "reenterPassword": "Repita contraseña",
+ "refreshPage": "Refrescar página",
+ "refreshPanel": "Actualizar panel",
+ "register": "Registrarse",
+ "remainingTime": "Tiempo restante:",
+ "requiredField": "Campo requerido",
+ "revertToVersion": "Volver a esta versión",
+ "row": "Fila",
+ "rowLabel": "Título de columna",
+ "rowLabelPlural": "Títulos de columna",
+ "rowNumDesc1": "La primera columna de la cuadrícula muestra el número de fila. Para reordenar la fila, simplemente haga clic y arrastre este elemento hacia arriba o hacia abajo.",
+ "rowNumDesc2": "El valor del encabezado de esta columna muestra el número total de filas en su cuadrícula. Esto puede ser útil cuando tiene conjuntos de datos realmente grandes y es demasiado grande para caber en la pantalla.",
+ "rowNumber": "Numero de fila",
+ "rowSp": "Fila(s)",
+ "rows": "filas",
+ "rowsGenerated": "Filas generadas",
+ "rowsGeneratedPerSecond": "Filas generadas por segundo",
+ "safariExplanation": "Este sitio requiere un navegador web actualizado que implemente estándares web modernos. Desafortunadamente, Safari se ha quedado atrás de otros navegadores y no ejecutará este script. Hasta que averigüemos cómo adaptarlo, utilice uno de los siguientes navegadores. Todo gratis para descargar.",
+ "save": "Salvar",
+ "saveAs": "Guardar como",
+ "saveButtonDesc": "Si tiene una cuenta de usuario en el sitio, este botón guardará su conjunto de datos. Si no ha iniciado sesión, al hacer clic en él, se le pedirá que inicie sesión primero. El sistema mantiene un historial de los últimos %1 cambios en un conjunto de datos, por lo que siempre puede volver atrás y ver versiones anteriores.",
+ "saveDataSetNewName": "Guardar el conjunto de datos con un nombre nuevo",
+ "seconds": "Segundos",
+ "seeHelpDialog": " Ver el diálogo de ayuda.",
+ "selectCountry": "Seleccionar país",
+ "selectDataType": "Seleccione tipo de dato",
+ "selectEllipsis": "Seleccione...",
+ "selectExpiryDate": "Seleccione la fecha de caducidad",
+ "selectLanguage": "Elije idioma",
+ "sendEmail": "Enviar correo electrónico",
+ "seriouslySlow": "Seriamente lenta",
+ "settings": "Ajustes",
+ "showGrid": "Mostrar cuadrícula",
+ "showLineNumbers": "Mostrar números de línea",
+ "showPreview": "Mostrar vista previa",
+ "siteLogo": "logotipo del sitio",
+ "size": "Tamaño:",
+ "southAmerica": "Sudamérica",
+ "status": "Estado",
+ "stripWhitespace": "quitar los espacios en blanco del contenido generado",
+ "success": "éxito",
+ "text": "Texto",
+ "textSize": "Tamano del texto",
+ "theDataSetName": "El nombre del conjunto de datos",
+ "theExportTypeBtn": "El botón Tipo de exportación",
+ "theGenerateButton": "El botón Generar",
+ "theGridPanel": "El panel de cuadrícula",
+ "thePreviewPanel": "El panel de vista previa",
+ "theSaveButton": "El botón Guardar",
+ "theme": "Tema",
+ "togglePanelLayout": "Alternar diseño de panel",
+ "totalNumGeneratedRows": "Número total de filas generadas",
+ "tourComplete": "Tour completo",
+ "tourCompleteDesc": "¡Todo listo! Elija uno de los botones siguientes para continuar.",
+ "tourIntroPara1": "Esta herramienta proporciona mucha funcionalidad y puede resultar un poco abrumadora al principio. Haga clic en uno de los botones a la derecha para realizar un recorrido sobre esa característica en particular.",
+ "tourIntroPara2": "Tenga en cuenta que cuando comienza el recorrido, sobrescribe temporalmente el contenido de la página del generador para ilustrar ciertas cosas sobre la interfaz. ¡Pero no se preocupe! Tan pronto como finaliza el recorrido, sus datos vuelven a su estado original.",
+ "tryDifferentTour": "Prueba un recorrido diferente",
+ "update": "Actualizar",
+ "updateAccount": "Actualizar cuenta",
+ "user": "Ususario",
+ "userAccount": "Cuenta de usuario",
+ "userAccountNotFound": "Lo sentimos, no pudimos encontrar su cuenta.",
+ "userAccountUpdated": "La cuenta de usuario se ha actualizado.",
+ "userAccounts": "Cuentas de usuario",
+ "userNotFound": "Lo sentimos, no pudimos identificarte. Verifique sus credenciales.",
+ "validationAccountAlreadyExists": "Lo siento, ya existe una cuenta con esa dirección de correo electrónico.",
+ "validationDifferentNewPasswords": "Asegúrese de que sus nuevas contraseñas coincidan",
+ "validationInvalidEmail": "Por favor, introduce una dirección de correo válida.",
+ "validationNoCurrentPassword": "Ingrese su contraseña actual",
+ "validationNoEmail": "Por favor, introduce tu dirección de correo.",
+ "validationNoFirstName": "Por favor, introduce tu nombre.",
+ "validationNoLastName": "Por favor, introduce tus apellidos.",
+ "validationNoPassword": "Por favor, introduce tu contraseña.",
+ "version": "Versión",
+ "view": "Vista",
+ "viewChangelog": "Ver registro de cambios",
+ "viewOnGithub": "Ver en Github",
+ "welcomeToTheGenerator": "Bienvenido al generador!",
+ "yes": "Sí",
+ "yourAccount": "Tu cuenta",
+ "yourAccountUpdated": "Tu cuenta ha sido actualizada.",
+ "yourUserAccount": "Su cuenta de usuario"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/fr.json b/apps/client/src/i18n/fr.json
new file mode 100644
index 000000000..777b0aff4
--- /dev/null
+++ b/apps/client/src/i18n/fr.json
@@ -0,0 +1,329 @@
+{
+ "abort": "Avorter",
+ "about": "A propos",
+ "aboutInfoPara1": "generateata.com est un projet open source que j'ai créé à la fin de l'ère paléolithique (vers 2004) et a eu de très nombreux contributeurs au cours de sa vie. Toutes les quelques années, je le réécris en utilisant les dernières et les meilleures technologies disponibles : cette version a été achevée en 2021, réalisée avec React, Redux, Typescript, GraphQL, Docker, des travailleurs Web, des élastiques, du ruban adhésif et beaucoup de jurons.",
+ "aboutInfoPara2": "Vous pouvez trouver le projet sur github. Téléchargez-le, ajoutez-le ou n'hésitez pas à contribuer. Prendre plaisir!",
+ "aboutThisScript": "À propos de ce script",
+ "accountCreated": "Compte utilisateur créé",
+ "accountCreatedDesc": "Le compte a été créé.",
+ "accountCreatedMsg": "Un compte a été créé avec succès.",
+ "accountDeleted": "Le compte a été supprimé.",
+ "accountDisabled": "Compte désactivé",
+ "accountExpiredMsg": "Désolé, votre compte a expiré.",
+ "accountExpiryDate": "Date d'expiration du compte",
+ "accountInfo": "Informations sur le compte",
+ "accountSettings": "Paramètres du compte",
+ "accountType": "Type de compte",
+ "accounts": "Comptes",
+ "add": "Ajouter",
+ "addRowsDesc": "Pour ajouter plus de lignes à la grille de données, utilisez simplement le formulaire en bas de la page. Lorsque vous avez beaucoup de champs, cette section peut être masquée hors de la page, vous devrez donc faire défiler le panneau vers le bas pour voir le formulaire.",
+ "addSomeDataDesc": "Ajoutez quelques lignes dans la grille.",
+ "admin": "Administrateur",
+ "adminAccount": "Compte Administrateur",
+ "administrator": "Administrateur",
+ "africa": "Afrique",
+ "allCountries": "Tous les pays",
+ "allDataTypePlugins": "Tous les plugins de types de données",
+ "allExportTypePlugins": "Tous les plugins dexport",
+ "anonymousAccess": "Accès anonyme",
+ "asia": "Asie",
+ "back": "Retour",
+ "backToLogin": "Retour connexion",
+ "blog": "Blog",
+ "cancel": "Annuler",
+ "centralAmerica": "Amérique Centrale",
+ "changePassword": "Changer le mot de passe",
+ "clear": "Claire",
+ "clearPage": "Effacer la page",
+ "clearPageConfirmation": "Voulez-vous vraiment effacer la page? Toutes les modifications que vous avez apportées seront perdues.",
+ "clearThePage": "Vider la page",
+ "clickExportTypeBtn": "L'étape suivante montre ce qui se passe lorsque vous cliquez sur le bouton.",
+ "close": "Fermer",
+ "closePanel": "Fermer le panneau",
+ "columns": "Colonnes",
+ "columnsDesc": "La grille affichera autant de ces colonnes que possible, en fonction de la taille de votre écran disponible. Lorsqu'il est trop petit, il masquera certaines colonnes et affichera une icône de rouage qui ouvre une info-bulle avec le contenu manquant. Les étapes suivantes expliquent chacune des colonnes, en utilisant la première ligne Noms comme démonstration.",
+ "complete": "Achevé",
+ "confirmDeleteAccount": "Voulez-vous vraiment supprimer ce compte?",
+ "confirmDeleteUserAccount": "Êtes-vous sûr de vouloir supprimer ce compte utilisateur?",
+ "confirmEmptyForm": "Êtes-vous sûr de vouloir vider le formulaire?",
+ "continue": "Continuer",
+ "copiedToClipboard": "Copié dans le presse-papier",
+ "copyToClipboard": "Copier dans le presse-papier",
+ "coreExportType": "Formats de base",
+ "countries": "Pays",
+ "country": "Pays",
+ "countrySpecific": "Spécifique au pays",
+ "cpuMeltinglyFast": "Processeur ultra-rapide",
+ "createAccount": "Créer un compte",
+ "currentPassword": "Mot de passe actuel",
+ "currentVersion": "Version actuelle en cours de modification",
+ "dataGenerated": "Données générées.",
+ "dataSet": "Jeu de données",
+ "dataSetHelp": "Cest ici que vous définissez précisément le genre de données que vous souhaitez générer. Essayez de remplir une ligne ou deux et cliquez sur le bouton Générer. Vous comprendrez le fonctionnement très rapidement !",
+ "dataSetLoaded": "L'ensemble de données a été chargé.",
+ "dataSetName": "Nom du jeu de données",
+ "dataSetNameDesc": "Cliquez sur le texte ici pour nommer votre ensemble de données. Veuillez noter que vous aurez besoin d'un compte sur le site et d'être connecté pour nommer vos ensembles de données. Lorsque vous enregistrez vos ensembles de données, ce nom vous donne une étiquette pratique pour en garder une trace.",
+ "dataSetNameUpdated": "Le nom de l'ensemble de données a été mis à jour.",
+ "dataSetOptions": "Options d'ensemble de données",
+ "dataSetReverted": "Votre ensemble de données a été rétabli dans l'ancienne version sélectionnée.",
+ "dataSetSaved": "Votre ensemble de données a été enregistré.",
+ "dataSets": "Ensembles de données",
+ "dataType": "Type de données",
+ "dataTypeDesc": "Il s'agit d'une liste déroulante contenant tous les types de données disponibles. Lorsque vous sélectionnez une valeur, il met à jour le reste des colonnes pour cette ligne. Chaque type de données a ses propres paramètres uniques pour permettre la personnalisation.",
+ "dataTypes": "Types de données",
+ "dateAccountCreated": "Date de création du compte",
+ "dateCreated": "Date de création",
+ "defaultLanguage": "Langue par défaut",
+ "delete": "Supprimer",
+ "delete1DataSet": "Supprimer une Data Set",
+ "deleteAccount": "Supprimer le compte",
+ "deleteDataSet": "Supprimer l'ensemble de données",
+ "deleteDataSetConfirm": "Voulez-vous vraiment supprimer cet ensemble de données?",
+ "deleteRow": "Supprimer la ligne",
+ "deleteRowDesc": "La dernière colonne contient une icône de suppression. Cliquez dessus pour supprimer la ligne.",
+ "developer": "Développeur",
+ "developerDoc": "Doc développeur",
+ "disabled": "Désactivée",
+ "documentation": "Documentation",
+ "download": "Télécharger",
+ "edit": "Éditer",
+ "editAccount": "Modifier le compte",
+ "editExportTypePage": "Lorsque vous cliquez sur le bouton Type d'exportation, une vue pleine page apparaît, contenant les options de configuration sur le côté gauche et le panneau d'aperçu sur la droite.",
+ "editExportTypePageConfig": "Regardons les options de configuration.",
+ "editExportTypeSettings": "Veuillez modifier vos paramètres de type d'exportation.",
+ "editingExportTypes": "Modification des types d'exportation",
+ "email": "Email",
+ "emailFooterDisclaimer": "Si vous avez reçu cet e-mail par erreur, veuillez contacter l'administrateur du site.",
+ "emailIntroLineWithName": "Bonjour %1,",
+ "emailLabel": "E-mail:",
+ "emailNotSent": "Nous navons pas pu envoyer la notification email.",
+ "emailUserLoginInfo": "Envoyer à lutilisateur ses informations de connexion",
+ "enterEmailAddressToResetPassword": "Entrez votre adresse e-mail ci-dessous pour réinitialiser votre mot de passe.",
+ "enterUserAccountDetails": "Entrez vos informations de compte dutilisateur ci-dessous.",
+ "errorCreatingAccount": "Une erreur s'est produite lors de la création de ce compte.",
+ "estimatedSize": "Taille estimée :",
+ "estimatedTime": "Temps estimé:",
+ "europe": "Europe",
+ "example": "Exemple",
+ "exampleColumn": "Exemple de colonne",
+ "exampleColumnDesc": "Cette colonne peut ou non avoir du contenu en fonction du type de données que vous avez sélectionné pour la ligne. C'est juste un moyen pratique pour certains types de données de fournir des exemples prédéfinis sur la façon dont ils peuvent être utilisés. Par exemple, la colonne Noms propose une liste déroulante de différents formats de nom. Pour tous les types de données, vous pouvez toujours le configurer manuellement via la colonne Options: c'est vraiment un appareil qui vous fait gagner du temps pour vous mettre à niveau le plus rapidement possible.",
+ "examples": "Exemples",
+ "exit": "Sortie",
+ "expired": "Expiré",
+ "expiryDate": "Date d'expiration",
+ "exportType": "Export Type",
+ "exportTypeBtnDesc": "Le bouton en haut à gauche du panneau vous permet de sélectionner et de configurer le format des données que vous souhaitez générer. Ici, il est défini sur Typescript.",
+ "exportTypeOptionsSql": "Options de type d'exportation: SQL",
+ "exportTypeOptionsSqlDesc": "Comme vous pouvez le voir, SQL contient de nombreux autres champs spécifiques à ce type d'exportation. Les autres types d'exportation ont des paramètres différents.",
+ "exportTypeOptionsTs": "Options de type d'exportation: Typographie",
+ "exportTypeOptionsTsDesc": "Selon le type d'exportation sélectionné, le corps du panneau peut contenir des options supplémentaires. Ici, avec Typescript, il y a deux champs, vous permettant de personnaliser le nom du type et le nom de la variable exportée.",
+ "exportTypeOptionsTsDesc2": "Regardons maintenant SQL.",
+ "exportTypeSelection": "Sélection du type d'exportation",
+ "exportTypeSelectionDesc": "La liste déroulante Format vous permet de choisir le format du contenu généré. Il existe de nombreuses options, dont certaines sont des types de base: SQL, CSV, SQL, JSON et d'autres, et certaines d'entre elles sont destinées à générer des données pour des langages de programmation spécifiques.",
+ "exportTypes": "Types dExports",
+ "filterDataTypes": "Filtrer les types de données",
+ "financial": "Financière",
+ "firstName": "Prénom",
+ "forgottenYourPasswordQ": "Mot de passe oublié?",
+ "format": "Format",
+ "generate": "Générer",
+ "generateBtnDesc": "Une fois que vous avez terminé de configurer votre ensemble de données et confirmé qu'il vous convient, il est temps de générer des données en volume! Cliquez sur ce bouton pour générer vos données. Selon le nombre de lignes dans la grille de données et le nombre de lignes que vous souhaitez générer, cela peut prendre un certain temps.",
+ "generated": "Généré",
+ "generatedC": "Généré :",
+ "generator": "Générateur",
+ "geo": "Géo.",
+ "grid": "La grille",
+ "gridPanelTourDesc1": "Le panneau de grille est l'endroit où vous construisez les données que vous souhaitez générer. Ici, vous pouvez choisir entre les différents types de données, comment ils sont configurés et l'ordre dans lequel ils apparaissent. Dans l'exemple ici, nous avons cinq champs: Nom, Téléphone, Email, Adresse et Ville.",
+ "gridPanelTourDesc2": "Consultez la visite guidée du panneau de grille pour plus d'informations sur cette section.",
+ "gridPanelTourIntroDesc1": "Le panneau de la grille est l'endroit où vous définissez les données que vous souhaitez générer. Pour cette visite, nous avons caché le panneau de prévisualisation (voir le bouton \"Aperçu\" décoché en bas à droite de la page) pour nous donner un peu plus d'espace. Nous avons également ajouté 10 lignes de types de données différents, juste à des fins d'illustration.",
+ "gridPanelTourIntroDesc2": "Commençons par les colonnes de la grille.",
+ "help": "Aide",
+ "helpIcon": "Icône d'aide",
+ "helpIconDesc": "Lorsque vous sélectionnez un type de données dans la colonne précédente, une icône apparaît ici. Cliquez dessus pour ouvrir une boîte de dialogue d'aide contenant des informations d'utilisation.",
+ "hide": "Cacher",
+ "hideShowGrid": "Masquer / afficher la grille",
+ "hideShowPreviewPanel": "Masquer / afficher le panneau d'aperçu",
+ "history": "Histoire",
+ "historyPanelDesc": "Ce panneau affiche l'historique des modifications apportées à l'ensemble de données. Cliquez sur les boutons Afficher pour examiner les versions antérieures. Pour revenir à une version antérieure, affichez-la, puis cliquez sur le bouton \"Revenir à cette version\".",
+ "hostName": "Nom de lhôte",
+ "humanData": "Données humaines",
+ "ifWantToReregister": "Si vous souhaitez vous réinscrire, veuillez visiter:",
+ "incomplete": "Incomplet",
+ "install": "Installer",
+ "introToGenerator": "Introduction au générateur",
+ "introToGeneratorDesc": "Cette visite vous donne une introduction de haut niveau aux principales fonctionnalités du générateur de données. Commençons!",
+ "invalidSettings": "Paramètres invalides!",
+ "language": "Français",
+ "lastEdited": "Dernière modification le",
+ "lastLoggedIn": "Dernière session",
+ "lastModified": "Dernière mise à jour",
+ "lastName": "Nom de famille",
+ "lastSaved": "Dernier enregistrement",
+ "lineWrapping": "Emballage de ligne",
+ "linkToDataSet": "Lien vers le jeu de données",
+ "linkToThisDataSet": "Partager ce jeu de données",
+ "live": "Habitent",
+ "load": "Charger",
+ "loading": "Chargement...",
+ "login": "Login",
+ "loginToSave": "Afin de sauvegarder vos ensembles de données, vous avez d'abord besoin d'un compte. Veuillez vous connecter ci-dessous.",
+ "loginOrRegisterToSave": "Afin de sauvegarder vos ensembles de données, vous avez d'abord besoin d'un compte. Veuillez vous connecter ou vous inscrire ci-dessous.",
+ "loginUrlLabel": "URL de connexion:",
+ "logout": "Déconnexion",
+ "makeDataSetPublicAgreement": "Je comprends que pour partager ce jeu de données jai besoin de le rendre public.",
+ "metaDescription": "GenerateData.com: logiciel libre sous licence GNU GPL, générateur aléatoire de données personnalisé pour tester des applications.",
+ "metaKeywords": "Données aléatoires, données dessais, exemples, générateur de données, générer des données",
+ "missingDataSetName": "Veuillez saisir le nom de votre ensemble de données",
+ "nameColumn": "Colonne de nom",
+ "nameColumnDesc": "Cette colonne obtient un en-tête différent en fonction du type d'exportation que vous avez sélectionné dans le panneau d'aperçu (XML, CSV, etc.). Le but de cette colonne est de fournir un identifiant pour la ligne qui peut ensuite être utilisé dans les données générées. Par exemple, XML utilise cette valeur comme nom de noeud XML; JSON l'utilise comme nom de propriété JSON; SQL l'utilise pour le nom de la colonne de base de données. L'application validera automatiquement la valeur que vous avez saisie ici pour s'assurer qu'elle est valide pour le type d'exportation sélectionné.",
+ "newDataSet": "Nouvel ensemble de données",
+ "no": "Aucun",
+ "noAccountsCreated": "Aucun compte n'a été créé.",
+ "noAdditionalSettings": "Aucun paramètre supplémentaire",
+ "noDataSetsSaved": "Vous n'avez enregistré aucun ensemble de données.",
+ "noExamplesAvailable": "Aucun exemple disponible.",
+ "noExpiry": "Pas d'expiration",
+ "noHistory": "Pas encore d'histoire.",
+ "noOptionsAvailable": "Aucune option disponible.",
+ "noSafari": "Désolé, pas de Safari!",
+ "northAmerica": "Amérique du Nord",
+ "nowLoggedIn": "Vous êtes connecté.",
+ "nowLoggedOut": "Vous avez été déconnecté.",
+ "numResults": "Résultats",
+ "numeric": "Numérique",
+ "oceania": "Océanie",
+ "oneTimePasswordLogin": "Vous avez été connecté avec un mot de passe à usage unique. Veuillez définir un nouveau mot de passe maintenant.",
+ "open": "ouvert",
+ "options": "Options",
+ "optionsColumn": "Colonne d'options",
+ "optionsColumnDesc1": "Cette colonne contient toutes les options de configuration pour le type de données de cette ligne. Certains types de données ont trop de paramètres de configuration à afficher dans un si petit espace (par exemple, pays, région), ils peuvent donc simplement afficher un bouton dans la grille qui ouvre une boîte de dialogue contenant toutes les options.",
+ "optionsColumnDesc2": "Pour le type de données Noms illustré ici, la colonne contient un seul champ Pilule à créer. Il s'agit d'un type de champ courant dans le générateur de données. Il vous permet d'ajouter des éléments en y tapant simplement et en cliquant sur . Il convertit ensuite ces éléments en pilules distinctes qui peuvent être supprimées via l'icône «x».",
+ "or": "ou",
+ "order": "Ordre",
+ "other": "Autre",
+ "overMaxAnonRows": "Désolé, vous ne pouvez générer que %1 lignes. Pour générer de plus gros volumes de données, vous aurez besoin d'un compte utilisateur sur ce site.",
+ "panelContents": "Contenu du panneau",
+ "panelContentsDesc": "La majeure partie du panneau d'aperçu contient un échantillon des données générées, selon les données que vous avez spécifiées dans le panneau de grille et les paramètres de type d'exportation sélectionnés. Ici, il génère 5 types de données différents (Pays, Région, Ville, Adresse et Postal / Zip) au format Typescript.",
+ "panelControls": "Commandes du panneau",
+ "panelControlsClearIconDesc": "La dernière icône des commandes du panneau vous permet d'effacer la page afin de pouvoir recommencer.",
+ "panelControlsDesc": "Les boutons de contrôle du panneau vous permettent de masquer / afficher les panneaux Grille et Aperçu, ce qui peut être utile si vous travaillez avec un espace d'écran limité. Vous pouvez également basculer le placement des deux panneaux l'un par rapport à l'autre (gauche-droite, haut-bas) à l'aide de la troisième icône.",
+ "panelTabs": "Onglets du panneau",
+ "panelTabsDesc": "Enfin, vous avez peut-être remarqué que le panneau est organisé en deux onglets. Par défaut, il affiche l'onglet Paramètres, car c'est là que vous voudrez probablement aller. Mais il y a aussi un deuxième onglet «Aperçu» qui contient les paramètres pour changer l'apparence du panneau d'aperçu: le thème, masquer / afficher les numéros de ligne, basculer le retour à la ligne, changer la taille du texte et contrôler le nombre de lignes générées.",
+ "password": "Mot de passe",
+ "passwordLabel": "Mot de passe:",
+ "passwordReset": "Réinitialisation du mot de passe",
+ "passwordResetAccountExpired": "Réinitialisation du mot de passe - Compte expiré",
+ "passwordResetAccountExpiredDesc": "Nous venons de recevoir une demande de réinitialisation de votre mot de passe, mais votre compte a déjà expiré.",
+ "passwordResetComplete": "Votre mot de passe a été réinitialisé et vous a été envoyé.",
+ "passwordResetEmailContent1": "Votre mot de passe a été réinitialisé. Vous pouvez utiliser le mot de passe ci-dessous pour vous connecter: %1",
+ "passwordResetEmailContent2": "Veuillez le changer une fois connecté.",
+ "passwordResetEmailDesc": "Nous avons reçu une demande de réinitialisation de votre mot de passe. Cet e-mail contient un mot de passe temporaire à usage unique que vous pouvez utiliser pour vous connecter. Une fois connecté, vous devrez définir un nouveau mot de passe.",
+ "passwordResetMsg": "Si l'e-mail était enregistré, votre mot de passe a été réinitialisé et des informations de connexion temporaires vous ont été envoyées par e-mail.",
+ "passwordUpdateInvalidPassword": "Votre mot de passe actuel est incorrect. Veuillez entrer à nouveau.",
+ "passwordUpdated": "Votre mot de passe a été mis à jour.",
+ "pause": "Pause",
+ "pleaseConfirm": "Confirmer",
+ "pleaseEnterAll": "Veuillez renseigner tous les champs obligatoires.",
+ "pleaseEnterDataSetName": "Sil vous plaît entrez le nom du nouveau jeu de données.",
+ "pleaseFixErrors": "Corriger les erreurs suivantes et réessayez: ",
+ "pleaseLogin": "Veuillez vous connecter",
+ "pleaseSelect": "Sélection obligatoire",
+ "plugins": "Plugins",
+ "pressEnterAddItem": "Appuyez sur Entrée pour ajouter un élément",
+ "preview": "Aperçu",
+ "previewPanel": "Panneau de prévisualisation",
+ "previewPanelControlsDesc": "En haut à droite du panneau, il y a deux icônes. La première icône actualise le panneau, générant à chaque fois des données aléatoires différentes - cela vous donne une idée du type de données qu'il génère. La deuxième icône ferme complètement le panneau. Vous pouvez toujours afficher à nouveau le panneau en utilisant la section en bas à droite de la page (il suffit de re-sélectionner la case \"Aperçu\").",
+ "previewPanelDesc": "Ce panneau vous donne un aperçu en direct des données que vous générez, pendant que vous les construisez via le panneau de grille. Le bouton en haut à gauche montre le format sélectionné des données générées (par exemple SQL, XML, CSV, etc.). Pour changer le format, cliquez simplement sur le bouton.",
+ "previewPanelMoreInfo": "Pour plus d'informations sur ce panneau, consultez la visite guidée du panneau de prévisualisation.",
+ "previewPanelNoData": "Pas de données!",
+ "previewPanelTourDesc": "Le panneau d'aperçu vous donne une représentation visuelle en direct des données que vous générez au fur et à mesure que vous les construisez. Cette visite donne une introduction rapide au fonctionnement du panneau.",
+ "previewRows": "Aperçu des lignes",
+ "private": "Privé",
+ "problemLoadingTour": "Désolé, un problème est survenu lors du chargement de la visite.",
+ "programmingLanguages": "Langages de programmation",
+ "province": "Province",
+ "public": "Public",
+ "publicQ": "Publique ?",
+ "reenterPassword": "Nouveau mot de passe",
+ "refreshPage": "Actualiser la page",
+ "refreshPanel": "Refresh Panel",
+ "register": "S'inscrire",
+ "remainingTime": "Temps restant:",
+ "requiredField": "champs requis",
+ "revertToVersion": "Revenir à cette version",
+ "row": "rangée",
+ "rowLabel": "Titre de colonne",
+ "rowLabelPlural": "Titres des colonnes",
+ "rowNumDesc1": "La première colonne de la grille affiche le numéro de ligne. Pour réorganiser la ligne, cliquez et faites glisser cet élément vers le haut ou vers le bas.",
+ "rowNumDesc2": "La valeur d'en-tête de cette colonne indique le nombre total de lignes dans votre grille. Cela peut être pratique lorsque vous avez de très grands ensembles de données et qu'ils sont trop volumineux pour tenir à l'écran.",
+ "rowNumber": "Numéro de ligne",
+ "rowSp": "Ligne(s)",
+ "rows": "lignes",
+ "rowsGenerated": "Lignes générées",
+ "rowsGeneratedPerSecond": "Lignes générées par seconde",
+ "safariExplanation": "Ce site nécessite un navigateur Web à jour qui met en œuvre des normes Web modernes. Malheureusement, Safari a pris du retard sur les autres navigateurs et n'exécutera pas ce script. Jusqu'à ce que nous comprenions comment l'adapter, veuillez utiliser l'un des navigateurs suivants. Tout est gratuit à télécharger.",
+ "save": "Sauver",
+ "saveAs": "Enregistrer sous",
+ "saveButtonDesc": "Si vous avez un compte utilisateur sur le site, ce bouton enregistrera votre jeu de données. Si vous n'êtes pas connecté, cliquer dessus vous invitera à vous connecter en premier. Le système conserve un historique des %1 dernières modifications apportées à un ensemble de données, afin que vous puissiez toujours revenir en arrière et afficher les anciennes versions.",
+ "saveDataSetNewName": "Enregistrer l'ensemble de données sous un nouveau nom",
+ "seconds": "Secondes",
+ "seeHelpDialog": "Consulter laide.",
+ "selectCountry": "Choisissez le pays",
+ "selectDataType": "Choix du type de données",
+ "selectEllipsis": "Sélectionner...",
+ "selectExpiryDate": "Sélectionnez la date d'expiration",
+ "selectLanguage": "Choix de la langue",
+ "sendEmail": "Envoyer un e-mail",
+ "seriouslySlow": "sérieusement lent",
+ "settings": "Réglages",
+ "showGrid": "Afficher la grille",
+ "showLineNumbers": "Afficher les numéros de ligne",
+ "showPreview": "Afficher l'aperçu",
+ "siteLogo": "logo du site",
+ "size": "Taille:",
+ "southAmerica": "Amérique du Sud",
+ "status": "Statut",
+ "stripWhitespace": "supprimer les espaces blancs du contenu généré",
+ "success": "Succès",
+ "text": "Texte",
+ "textSize": "Taille du texte",
+ "theDataSetName": "Le nom de l'ensemble de données",
+ "theExportTypeBtn": "Le bouton Type d'exportation",
+ "theGenerateButton": "Le bouton Générer",
+ "theGridPanel": "Le panneau de grille",
+ "thePreviewPanel": "Le panneau de prévisualisation",
+ "theSaveButton": "Le bouton Enregistrer",
+ "theme": "Thème",
+ "togglePanelLayout": "Basculer la disposition du panneau",
+ "totalNumGeneratedRows": "Nombre total de lignes générées",
+ "tourComplete": "Tour terminé",
+ "tourCompleteDesc": "Terminé! Choisissez l'un des boutons ci-dessous pour continuer.",
+ "tourIntroPara1": "Esta herramienta proporciona mucha funcionalidad y puede resultar un poco abrumadora al principio. Haga clic en uno de los botones a la derecha para realizar un recorrido sobre esa característica en particular.",
+ "tourIntroPara2": "Tenga en cuenta que cuando comienza el recorrido, sobrescribe temporalmente el contenido de la página del generador para ilustrar ciertas cosas sobre la interfaz. ¡Pero no se preocupe! Tan pronto como finaliza el recorrido, sus datos vuelven a su estado original.",
+ "tryDifferentTour": "Essayez une autre visite",
+ "update": "Mettre à jour",
+ "updateAccount": "Mise à jour du compte",
+ "user": "Utilisateur",
+ "userAccount": "Compte utilisateur",
+ "userAccountNotFound": "Désolé, nous n'avons pas pu trouver votre compte.",
+ "userAccountUpdated": "Le compte utilisateur a été mis à jour.",
+ "userAccounts": "Comptes utilisateurs",
+ "userNotFound": "Désolé, nous n'avons pas pu vous identifier. Veuillez vérifier vos identifiants.",
+ "validationAccountAlreadyExists": "Désolé, un compte avec cette adresse e-mail existe déjà.",
+ "validationDifferentNewPasswords": "Veuillez vous assurer que vos nouveaux mots de passe correspondent",
+ "validationInvalidEmail": "Entrez une adresse email valide.",
+ "validationNoCurrentPassword": "Veuillez saisir votre mot de passe actuel",
+ "validationNoEmail": "Entrez votre adresse e-mail.",
+ "validationNoFirstName": "Entrez votre prénom.",
+ "validationNoLastName": "Entrez votre nom de famille.",
+ "validationNoPassword": "ntrez votre mot de passe.",
+ "version": "Version",
+ "view": "Vue",
+ "viewChangelog": "Voir la log des modifications",
+ "viewOnGithub": "Voir sur Github",
+ "welcomeToTheGenerator": "Bienvenido al generador!",
+ "yes": "Oui",
+ "yourAccount": "Votre compte",
+ "yourAccountUpdated": "Votre compte a été mis à jour.",
+ "yourUserAccount": "Su cuenta de usuario"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/hi.json b/apps/client/src/i18n/hi.json
new file mode 100644
index 000000000..b76997beb
--- /dev/null
+++ b/apps/client/src/i18n/hi.json
@@ -0,0 +1,329 @@
+{
+ "abort": "बीच में बंद करें",
+ "about": "के बारे में",
+ "aboutInfoPara1": "generatedata.com एक ओपन सोर्स प्रोजेक्ट है जिसे मैंने पुरापाषाण युग (लगभग 2004) में बनाया था और इसके जीवनकाल में कई, कई योगदानकर्ता रहे हैं। हर कुछ वर्षों में मैं इसे नवीनतम और सबसे बड़ी उपलब्ध तकनीकों का उपयोग करके फिर से लिखता हूं: यह संस्करण 2021 में पूरा हुआ, जिसे रिएक्ट, रेडक्स, टाइपस्क्रिप्ट, ग्राफक्यूएल, डॉकर, वेब वर्कर्स, रबर बैंड, डक्ट टेप और बहुत सारे कोस के साथ बनाया गया था।",
+ "aboutInfoPara2": "आप परियोजना को जीथब पर पा सकते हैं। इसे डाउनलोड करें, इसे फोर्क करें, या योगदान करने के लिए स्वतंत्र महसूस करें। आनंद लेना!",
+ "aboutThisScript": "इस स्क्रिप्ट के बारे में",
+ "accountCreated": "खाता बन गया",
+ "accountCreatedDesc": "खाता बनाया गया है।",
+ "accountCreatedMsg": "आपके लिए एक खाता बनाया गया है।",
+ "accountDeleted": "खाता हटा दिया गया है।",
+ "accountDisabled": "खाता अक्षम",
+ "accountExpiredMsg": "क्षमा करें, आपका खाता समाप्त हो गया है।",
+ "accountExpiryDate": "खाते की समाप्ति तिथि",
+ "accountInfo": "खाते की जानकारी",
+ "accountSettings": "अकाउंट सेटिंग",
+ "accountType": "खाते का प्रकार",
+ "accounts": "हिसाब किताब",
+ "add": "जोड़ें",
+ "addRowsDesc": "डेटा ग्रिड में और पंक्तियाँ जोड़ने के लिए पृष्ठ के निचले भाग में दिए गए फॉर्म का उपयोग करें। जब आपके पास बहुत सारे फ़ील्ड हों, तो यह अनुभाग पृष्ठ से छिपा हो सकता है, इसलिए आपको फ़ॉर्म देखने के लिए पैनल को नीचे तक स्क्रॉल करना होगा।",
+ "addSomeDataDesc": "ग्रिड में कुछ पंक्तियाँ जोड़ें।",
+ "admin": "व्यवस्थापक",
+ "adminAccount": "व्यवस्थापक खाता",
+ "administrator": "व्यवस्थापक खाता",
+ "africa": "अफ्रीका",
+ "allCountries": "सभी देश",
+ "allDataTypePlugins": "सभी डेटा प्रकार प्लगइन्स",
+ "allExportTypePlugins": "सभी निर्यात प्रकार प्लगइन्स",
+ "anonymousAccess": "अनाम पहुंच",
+ "asia": "एशिया",
+ "back": "वापस",
+ "backToLogin": "लॉगिन पर वापस जाएं",
+ "blog": "ब्लॉग",
+ "cancel": "रद्द करना",
+ "centralAmerica": "मध्य अमरीका",
+ "changePassword": "पासवर्ड बदलें",
+ "clear": "स्पष्ट",
+ "clearPage": "पृष्ठ साफ़ करें",
+ "clearPageConfirmation": "क्या आप वाकई पृष्ठ को साफ़ करना चाहते हैं? आपके द्वारा किया गया कोई भी परिवर्तन खो जाएगा।",
+ "clearThePage": "पेज साफ़ करें",
+ "clickExportTypeBtn": "अगला चरण दिखाता है कि जब आप बटन पर क्लिक करते हैं तो क्या होता है।",
+ "close": "बंद करे",
+ "closePanel": "पैनल बंद करें",
+ "columns": "कॉलम",
+ "columnsDesc": "आपके उपलब्ध स्क्रीन आकार के आधार पर ग्रिड इनमें से अधिक से अधिक कॉलम दिखाएगा। जब यह बहुत छोटा होता है, तो यह कुछ कॉलम छुपाएगा और एक कॉग आइकन दिखाएगा जो अनुपलब्ध सामग्री के साथ एक इंफोटिप खोलता है। अगले कुछ चरण प्रदर्शन के रूप में पहली नाम पंक्ति का उपयोग करते हुए प्रत्येक कॉलम की व्याख्या करते हैं।",
+ "complete": "पूर्ण",
+ "confirmDeleteAccount": "क्या आप वाकई इस खाते को हटाना चाहते हैं?",
+ "confirmDeleteUserAccount": "क्या आप वाकई इस उपयोगकर्ता खाते को हटाना चाहते हैं?",
+ "confirmEmptyForm": "क्या आप वाकई पृष्ठ को साफ़ करना चाहते हैं?",
+ "continue": "जारी रखना",
+ "copiedToClipboard": "क्लिपबोर्ड पर नकल",
+ "copyToClipboard": "क्लिपबोर्ड पर कॉपी करें",
+ "coreExportType": "मुख्य प्रारूप",
+ "countries": "देशों",
+ "country": "देश",
+ "countrySpecific": "देश-विशेष",
+ "cpuMeltinglyFast": "सीपीयू-पिघलने वाला तेज़",
+ "createAccount": "खाता बनाएं",
+ "currentPassword": "वर्तमान पासवर्ड",
+ "currentVersion": "वर्तमान संस्करण संपादित किया जा रहा है",
+ "dataGenerated": "डेटा जनरेट किया गया।",
+ "dataSet": "डेटा सेट",
+ "dataSetHelp": "यह वह जगह है जहां आप परिभाषित करते हैं कि आप किस प्रकार का डेटा उत्पन्न करना चाहते हैं। एक या दो पंक्ति भरने का प्रयास करें और जनरेट बटन पर क्लिक करें। आप इसे बहुत जल्दी पकड़ लेंगे।",
+ "dataSetLoaded": "डेटा सेट लोड किया गया है।",
+ "dataSetName": "डेटा सेट का नाम",
+ "dataSetNameDesc": "अपने डेटा सेट को नाम देने के लिए यहां टेक्स्ट पर क्लिक करें। कृपया ध्यान दें कि अपने डेटा सेट को नाम देने के लिए आपको साइट पर एक खाते और लॉग इन होने की आवश्यकता होगी। जब आप अपने डेटा सेट सहेजते हैं, तो यह नाम आपको उन पर नज़र रखने के लिए एक सुविधाजनक लेबल देता है।",
+ "dataSetNameUpdated": "डेटा सेट का नाम अपडेट कर दिया गया है।",
+ "dataSetOptions": "डेटा सेट विकल्प",
+ "dataSetReverted": "आपका डेटा सेट चयनित पुराने संस्करण में वापस कर दिया गया है।",
+ "dataSetSaved": "आपका डेटा सेट सहेज लिया गया है।",
+ "dataSets": "डेटा सेट",
+ "dataType": "डाटा प्रकार",
+ "dataTypeDesc": "यह एक ड्रॉपडाउन है जिसमें सभी उपलब्ध डेटा प्रकार हैं। जब आप कोई मान चुनते हैं, तो वह उस पंक्ति के शेष स्तंभों को अपडेट कर देगा। अनुकूलन की अनुमति देने के लिए प्रत्येक डेटा प्रकार की अपनी अनूठी सेटिंग्स होती हैं।",
+ "dataTypes": "जानकारी का प्रकार",
+ "dateAccountCreated": "दिनांक खाता बनाया गया",
+ "dateCreated": "निर्माण की तिथि",
+ "defaultLanguage": "डिफ़ॉल्ट भाषा",
+ "delete": "हटाएं",
+ "delete1DataSet": "1 डेटा सेट हटाएं",
+ "deleteAccount": "खाता हटा दो",
+ "deleteDataSet": "डेटा सेट हटाएं",
+ "deleteDataSetConfirm": "क्या आप वाकई इस डेटा सेट को हटाना चाहते हैं?",
+ "deleteRow": "पंक्ति को हटाएं",
+ "deleteRowDesc": "अंतिम कॉलम में एक डिलीट आइकन होता है। इसे क्लिक करने से पंक्ति हट जाएगी।",
+ "developer": "डेवलपर",
+ "developerDoc": "डेवलपर दस्तावेज़",
+ "disabled": "विकलांग",
+ "documentation": "प्रलेखन",
+ "download": "डाउनलोड",
+ "edit": "संपादित करें",
+ "editAccount": "खाता संपादित करें",
+ "editExportTypePage": "जब आप निर्यात प्रकार बटन पर क्लिक करते हैं, तो एक पूर्ण-पृष्ठ दृश्य दिखाई देता है जिसमें बाईं ओर कॉन्फ़िगरेशन विकल्प और दाईं ओर पूर्वावलोकन पैनल होता है।",
+ "editExportTypePageConfig": "आइए कॉन्फ़िगरेशन विकल्पों को देखें।",
+ "editExportTypeSettings": "कृपया अपनी निर्यात प्रकार सेटिंग संपादित करें.",
+ "editingExportTypes": "निर्यात प्रकार संपादित करना",
+ "email": "ईमेल",
+ "emailFooterDisclaimer": "यदि आपको यह ईमेल गलती से प्राप्त हुआ है तो कृपया साइट व्यवस्थापक से संपर्क करें।",
+ "emailIntroLineWithName": "नमस्ते %1,",
+ "emailLabel": "ईमेल:",
+ "emailNotSent": "हम ईमेल सूचना भेजने में असमर्थ थे।",
+ "emailUserLoginInfo": "उपयोगकर्ता को उनकी लॉगिन जानकारी ईमेल करें",
+ "enterEmailAddressToResetPassword": "अपने पासवर्ड को रीसेट करने के लिए अपना ईमेल पता नीचे दर्ज करें।",
+ "enterUserAccountDetails": "कृपया नीचे अपना उपयोगकर्ता खाता विवरण दर्ज करें।",
+ "errorCreatingAccount": "यह खाता बनाते समय एक त्रुटि हुई थी।",
+ "estimatedSize": "अनुमानित आकार:",
+ "estimatedTime": "अनुमानित समय:",
+ "europe": "यूरोप",
+ "example": "उदाहरण",
+ "exampleColumn": "उदाहरण कॉलम",
+ "exampleColumnDesc": "पंक्ति के लिए आपके द्वारा चुने गए डेटा प्रकार के आधार पर इस कॉलम में सामग्री हो भी सकती है और नहीं भी। यह कुछ डेटा प्रकारों के लिए पूर्व निर्धारित उदाहरण प्रदान करने का एक सुविधाजनक तरीका है कि उनका उपयोग कैसे किया जा सकता है। उदाहरण के लिए, नाम कॉलम विभिन्न नाम प्रारूपों का ड्रॉपडाउन प्रदान करता है। सभी डेटा प्रकारों के लिए, आप अभी भी इसे विकल्प कॉलम के माध्यम से मैन्युअल रूप से कॉन्फ़िगर कर सकते हैं: यह वास्तव में आपको जितनी जल्दी हो सके गति प्रदान करने के लिए एक समय बचाने वाला उपकरण है।",
+ "examples": "उदाहरण",
+ "exit": "बाहर जाएं",
+ "expired": "समय सीमा समाप्त",
+ "expiryDate": "समाप्ति तिथि",
+ "exportType": "निर्यात प्रकार",
+ "exportTypeBtnDesc": "पैनल के ऊपरी-बाएँ बटन वह जगह है जहाँ आप उस डेटा के प्रारूप को चुनने और कॉन्फ़िगर करने के लिए जाते हैं जिसे आप जनरेट करना चाहते हैं। यहाँ इसे टाइपस्क्रिप्ट पर सेट किया गया है।",
+ "exportTypeOptionsSql": "निर्यात प्रकार विकल्प: SQL",
+ "exportTypeOptionsSqlDesc": "जैसा कि आप देख सकते हैं, SQL में कई अन्य फ़ील्ड हैं जो इस निर्यात प्रकार के लिए विशिष्ट हैं। अन्य निर्यात प्रकारों की अलग-अलग सेटिंग्स होती हैं।",
+ "exportTypeOptionsTs": "निर्यात प्रकार विकल्प: टाइपप्रति",
+ "exportTypeOptionsTsDesc": "चयनित निर्यात प्रकार के आधार पर, पैनल के मुख्य भाग में अतिरिक्त विकल्प हो सकते हैं। यहां टाइपस्क्रिप्ट के साथ दो फ़ील्ड हैं, जिससे आप प्रकार के नाम और निर्यात किए गए चर नाम को अनुकूलित कर सकते हैं।",
+ "exportTypeOptionsTsDesc2": "अब SQL को देखते हैं।",
+ "exportTypeSelection": "निर्यात प्रकार चयन",
+ "exportTypeSelectionDesc": "प्रारूप ड्रॉपडाउन आपको जेनरेट की गई सामग्री के लिए प्रारूप चुनने देता है। कई विकल्प हैं, उनमें से कुछ मुख्य प्रकार हैं: SQL, CSV, SQL, JSON और अन्य, और उनमें से कुछ विशिष्ट प्रोग्रामिंग भाषाओं के लिए डेटा उत्पन्न करने के लिए हैं।",
+ "exportTypes": "निर्यात प्रकार",
+ "filterDataTypes": "फ़िल्टर डेटा प्रकार",
+ "financial": "वित्तीय",
+ "firstName": "पहला नाम",
+ "forgottenYourPasswordQ": "पासवर्ड भूल गया?",
+ "format": "प्रारूप",
+ "generate": "उत्पन्न",
+ "generateBtnDesc": "एक बार जब आप अपने डेटा सेट को कॉन्फ़िगर करना समाप्त कर लेते हैं और पुष्टि कर लेते हैं कि यह आपकी इच्छानुसार दिखता है, तो यह मात्रा में कुछ डेटा उत्पन्न करने का समय है! अपना डेटा जेनरेट करने के लिए इस बटन पर क्लिक करें। डेटा ग्रिड में पंक्तियों की संख्या और आपके द्वारा जेनरेट की जाने वाली पंक्तियों की संख्या के आधार पर, इसमें कुछ समय लग सकता है।",
+ "generated": "जनरेट किया गया",
+ "generatedC": "उत्पन्न:",
+ "generator": "जनक",
+ "geo": "भू",
+ "grid": "ग्रिड",
+ "gridPanelTourDesc1": "ग्रिड पैनल वह जगह है जहां आप उस डेटा का निर्माण करते हैं जिसे आप जनरेट करना चाहते हैं। यहां आप विभिन्न डेटा प्रकारों के बीच चयन कर सकते हैं कि वे कैसे कॉन्फ़िगर किए गए हैं और जिस क्रम में वे दिखाई देते हैं। यहाँ उदाहरण में, हमारे पास पाँच फ़ील्ड हैं: नाम, फ़ोन, ईमेल, सड़क का पता और शहर।",
+ "gridPanelTourDesc2": "इस खंड के बारे में अधिक विस्तृत जानकारी के लिए ग्रिड पैनल पर भ्रमण देखें।",
+ "gridPanelTourIntroDesc1": "ग्रिड पैनल वह जगह है जहां आप परिभाषित करते हैं कि आप कौन सा डेटा जेनरेट करना चाहते हैं। इस दौरे के लिए, हमने थोड़ा और स्थान देने के लिए पूर्वावलोकन पैनल (पृष्ठ के नीचे दाईं ओर स्थित अनियंत्रित \"पूर्वावलोकन\" बटन देखें) छिपा दिया है। हमने अलग-अलग डेटा प्रकारों की 10 पंक्तियाँ भी जोड़ी हैं, केवल उदाहरण के लिए।",
+ "gridPanelTourIntroDesc2": "आइए ग्रिड कॉलम से शुरू करें।",
+ "help": "मदद",
+ "helpIcon": "सहायता चिह्न",
+ "helpIconDesc": "जब आप पिछले कॉलम में डेटा प्रकार चुनते हैं, तो यहां एक आइकन दिखाई देगा। इसे क्लिक करने से उपयोग की जानकारी वाला एक सहायता संवाद खुल जाता है।",
+ "hide": "छिपाना",
+ "hideShowGrid": "ग्रिड छुपाएं/दिखाएं",
+ "hideShowPreviewPanel": "पूर्वावलोकन पैनल छुपाएं/दिखाएं",
+ "history": "इतिहास",
+ "historyPanelDesc": "यह पैनल डेटा सेट में किए गए परिवर्तनों का इतिहास दिखाता है। पुराने संस्करणों की जांच करने के लिए व्यू बटन पर क्लिक करें। किसी पुराने संस्करण पर वापस जाने के लिए, बस इसे देखें, फिर \"इस संस्करण पर वापस लौटें\" बटन पर क्लिक करें।",
+ "hostName": "होस्ट का नाम",
+ "humanData": "मानव डेटा",
+ "ifWantToReregister": "यदि आप फिर से पंजीकरण करना चाहते हैं तो कृपया यहां जाएं:",
+ "incomplete": "अधूरा",
+ "install": "इंस्टॉल",
+ "introToGenerator": "जेनरेटर का परिचय",
+ "introToGeneratorDesc": "यह टूर आपको डेटा जेनरेटर की मुख्य विशेषताओं का उच्च-स्तरीय परिचय देता है। आएँ शुरू करें!",
+ "invalidSettings": "अमान्य सेटिंग!",
+ "language": "अंग्रेज़ी",
+ "lastEdited": "अंतिम संपादित",
+ "lastLoggedIn": "पिछला लॉग इन",
+ "lastModified": "अंतिम बार संशोधित",
+ "lastName": "उपनाम",
+ "lastSaved": "पिछली बार सहेजा गया",
+ "lineWrapping": "लाइन रैपिंग",
+ "linkToDataSet": "डेटा सेट से लिंक करें",
+ "linkToThisDataSet": "इस डेटा सेट से लिंक करें",
+ "live": "रहना",
+ "load": "भार",
+ "loading": "लोड हो रहा है...",
+ "login": "लॉग इन करें",
+ "loginToSave": "अपने डेटा सेट को सहेजने के लिए आपको पहले एक खाते की आवश्यकता होगी। कृपया नीचे लॉगिन करें।",
+ "loginOrRegisterToSave": "अपने डेटा सेट को सहेजने के लिए आपको पहले एक खाते की आवश्यकता होगी। कृपया नीचे लॉग इन या रजिस्टर करें।",
+ "loginUrlLabel": "लॉगिन यूआरएल:",
+ "logout": "लॉग आउट",
+ "makeDataSetPublicAgreement": "मैं समझता हूं कि इस डेटा सेट को साझा करने के लिए, मुझे इसे सार्वजनिक करने की आवश्यकता है।",
+ "metaDescription": "Generateata.com: परीक्षण सॉफ्टवेयर के लिए मुफ़्त, GNU-लाइसेंस प्राप्त, यादृच्छिक कस्टम डेटा जनरेटर",
+ "metaKeywords": "रैंडम डेटा, टेस्ट डेटा, नमूना डेटा, डेटा जनरेटर, डेटा उत्पन्न करें",
+ "missingDataSetName": "कृपया अपने डेटा सेट का नाम दर्ज करें",
+ "nameColumn": "नाम कॉलम",
+ "nameColumnDesc": "पूर्वावलोकन पैनल (एक्सएमएल, सीएसवी इत्यादि) में आपके द्वारा चुने गए निर्यात प्रकार के आधार पर इस कॉलम को एक अलग शीर्षक मिलता है। इस कॉलम का उद्देश्य पंक्ति के लिए एक पहचानकर्ता प्रदान करना है जिसका उपयोग तब उत्पन्न डेटा में किया जा सकता है। उदाहरण के लिए, XML इस मान का उपयोग XML नोड नाम के रूप में करता है; JSON इसे JSON गुण नाम के रूप में उपयोग करता है; SQL डेटाबेस कॉलम नाम के लिए इसका उपयोग करता है। यह सुनिश्चित करने के लिए कि यह चयनित निर्यात प्रकार के लिए मान्य है, एप्लिकेशन आपके द्वारा यहां दर्ज किए गए मान को स्वचालित रूप से मान्य करेगा।",
+ "newDataSet": "नया डेटा सेट",
+ "no": "नहीं",
+ "noAccountsCreated": "कोई खाता नहीं बनाया गया है।",
+ "noAdditionalSettings": "कोई अतिरिक्त सेटिंग नहीं।",
+ "noDataSetsSaved": "आपके पास कोई डेटा सेट सहेजा नहीं गया है।",
+ "noExamplesAvailable": "कोई उदाहरण उपलब्ध नहीं है।",
+ "noExpiry": "कोई समाप्ति नहीं",
+ "noHistory": "अभी तक कोई इतिहास नहीं।",
+ "noOptionsAvailable": "कोई विकल्प उपलब्ध नहीं है।",
+ "noSafari": "क्षमा करें, सफारी नहीं!",
+ "northAmerica": "उत्तरी अमेरिका",
+ "nowLoggedIn": "आप लॉग इन हो गए हैं।",
+ "nowLoggedOut": "आप लॉग आउट हो चुके हैं।",
+ "numResults": "संख्या परिणाम",
+ "numeric": "संख्यात्मक",
+ "oceania": "ओशिनिया",
+ "oneTimePasswordLogin": "आपने वन-टाइम पासवर्ड का उपयोग करके लॉग इन किया है। कृपया अभी एक नया सेट करें अन्यथा आप फिर से लॉग इन नहीं कर पाएंगे।",
+ "open": "खोलना",
+ "options": "विकल्प",
+ "optionsColumn": "विकल्प कॉलम",
+ "optionsColumnDesc1": "इस कॉलम में इस पंक्ति के डेटा प्रकार के लिए सभी कॉन्फ़िगरेशन विकल्प हैं। कुछ डेटा प्रकारों में इतनी कम जगह (जैसे देश, क्षेत्र) में दिखाने के लिए बहुत अधिक कॉन्फ़िगरेशन सेटिंग्स होती हैं, इसलिए वे ग्रिड में केवल एक बटन दिखा सकते हैं जो सभी विकल्पों वाला एक डायलॉग खोलता है।",
+ "optionsColumnDesc2": "यहां दिखाए गए नाम डेटा प्रकार के लिए, कॉलम में एक एकल निर्माण योग्य गोली फ़ील्ड है। यह डेटा जेनरेटर में एक सामान्य फ़ील्ड प्रकार है। यह आपको केवल इसमें टाइप करके और क्लिक करके आइटम जोड़ने देता है। यह तब उन वस्तुओं को अलग-अलग गोलियों में परिवर्तित करता है जिन्हें \"x\" आइकन के माध्यम से हटाया जा सकता है।",
+ "or": "या",
+ "order": "आदेश",
+ "other": "अन्य",
+ "overMaxAnonRows": "क्षमा करें, आप केवल %1 पंक्तियाँ उत्पन्न कर सकते हैं। बड़ी मात्रा में डेटा उत्पन्न करने के लिए आपको इस साइट पर एक उपयोगकर्ता खाते की आवश्यकता होगी।",
+ "panelContents": "पैनल सामग्री",
+ "panelContentsDesc": "पूर्वावलोकन पैनल के बड़े हिस्से में आपके द्वारा ग्रिड पैनल में निर्दिष्ट डेटा और चयनित निर्यात प्रकार सेटिंग के अनुसार जनरेट किए गए डेटा का एक नमूना होता है। यहां यह टाइपस्क्रिप्ट प्रारूप में 5 अलग-अलग डेटा प्रकार (देश, क्षेत्र, शहर, सड़क का पता और डाक/ज़िप) उत्पन्न कर रहा है।",
+ "panelControls": "पैनल नियंत्रण",
+ "panelControlsClearIconDesc": "पैनल नियंत्रणों में अंतिम आइकन आपको पृष्ठ को साफ़ करने देता है ताकि आप नए सिरे से शुरुआत कर सकें।",
+ "panelControlsDesc": "पैनल नियंत्रण बटन आपको ग्रिड और पूर्वावलोकन पैनल को छिपाने/दिखाने की सुविधा देते हैं जो कि यदि आप सीमित स्क्रीन रियल एस्टेट के साथ काम कर रहे हैं तो सहायक हो सकते हैं। आप तीसरे आइकन का उपयोग करके एक दूसरे के सापेक्ष (बाएं-दाएं, ऊपर-नीचे) दो पैनलों के प्लेसमेंट को भी टॉगल कर सकते हैं।",
+ "panelTabs": "पैनल टैब",
+ "panelTabsDesc": "अंत में, आपने देखा होगा कि पैनल दो टैब में व्यवस्थित है। डिफ़ॉल्ट रूप से यह सेटिंग टैब दिखाता है, क्योंकि यही वह जगह है जहां आप जाना चाहते हैं। लेकिन एक दूसरा \"पूर्वावलोकन\" टैब भी है जिसमें पूर्वावलोकन पैनल की उपस्थिति बदलने के लिए सेटिंग्स शामिल हैं: थीम, लाइन नंबर छुपाना/दिखाना, लाइन रैपिंग टॉगल करना, टेक्स्ट आकार बदलना और जेनरेट की गई पंक्तियों की संख्या को नियंत्रित करना।",
+ "password": "पासवर्ड",
+ "passwordLabel": "पासवर्ड:",
+ "passwordReset": "पासवर्ड रीसेट",
+ "passwordResetAccountExpired": "पासवर्ड रीसेट - खाता समाप्त हो गया",
+ "passwordResetAccountExpiredDesc": "हमें अभी आपका पासवर्ड रीसेट करने का अनुरोध प्राप्त हुआ है, हालांकि आपका खाता पहले ही समाप्त हो चुका है।",
+ "passwordResetComplete": "आपका पासवर्ड रीसेट कर दिया गया है और आपको नया पासवर्ड ईमेल कर दिया गया है।",
+ "passwordResetEmailContent1": "आपका पासवर्ड रिसेट कर दिया गया है। लॉग इन करने के लिए आप निम्न पासवर्ड का उपयोग कर सकते हैं: %1",
+ "passwordResetEmailContent2": "लॉग इन करने के बाद कृपया इसे बदल दें।",
+ "passwordResetEmailDesc": "हमें आपका पासवर्ड रीसेट करने का अनुरोध प्राप्त हुआ है। इस ईमेल में एक अस्थायी वन-टाइम पासवर्ड है जिसका उपयोग आप लॉग इन करने के लिए कर सकते हैं। लॉग इन करने के बाद, आपको एक नया पासवर्ड सेट करना होगा।",
+ "passwordResetMsg": "यदि ईमेल फ़ाइल में था, तो आपका पासवर्ड रीसेट कर दिया गया है और आपको अस्थायी लॉगिन जानकारी ईमेल कर दी गई है।",
+ "passwordUpdateInvalidPassword": "आपका वर्तमान पासवर्ड गलत है। कृपया पुनः दर्ज करें।",
+ "passwordUpdated": "आपका पासवर्ड बदला जा चुका है।",
+ "pause": "ठहराव",
+ "pleaseConfirm": "कृपया पुष्टि करें",
+ "pleaseEnterAll": "कृपया सभी दर्ज करें",
+ "pleaseEnterDataSetName": "कृपया नए डेटा सेट का नाम दर्ज करें।",
+ "pleaseFixErrors": "कृपया निम्न त्रुटियों को ठीक करें और पुनः सबमिट करें:",
+ "pleaseLogin": "कृपया लॉगिन करें",
+ "pleaseSelect": "कृपया चयन कीजिए",
+ "plugins": "प्लग-इन",
+ "pressEnterAddItem": "आइटम जोड़ने के लिए एंटर दबाएं",
+ "preview": "पूर्वावलोकन",
+ "previewPanel": "पूर्वावलोकन पैनल",
+ "previewPanelControlsDesc": "पैनल के ऊपर दाईं ओर दो आइकन हैं। पहला आइकन पैनल को रीफ्रेश करता है, हर बार अलग-अलग यादृच्छिक डेटा उत्पन्न करता है - इससे आपको यह पता लगाने में मदद मिलती है कि यह किस प्रकार का डेटा उत्पन्न करता है। दूसरा आइकन पैनल को पूरी तरह से बंद कर देता है। आप पृष्ठ के नीचे दाईं ओर अनुभाग का उपयोग करके कभी भी पैनल को फिर से दिखा सकते हैं (बस \"पूर्वावलोकन\" चेकबॉक्स को फिर से चुनें)।",
+ "previewPanelDesc": "जब आप इसे ग्रिड पैनल के माध्यम से बना रहे हों, तब यह पैनल आपको आपके द्वारा जेनरेट किए जा रहे डेटा का लाइव पूर्वावलोकन देता है। ऊपर-बाईं ओर बटन जनरेट किए जा रहे डेटा के चयनित प्रारूप (जैसे SQL, XML, CSV आदि) को दिखाता है। प्रारूप बदलने के लिए, बस बटन पर क्लिक करें।",
+ "previewPanelMoreInfo": "इस पैनल के बारे में अधिक जानकारी के लिए, प्रीव्यू पैनल टूर देखें।",
+ "previewPanelNoData": "कोई आकड़ा उपलब्ध नहीं है!",
+ "previewPanelTourDesc": "पूर्वावलोकन पैनल आपको उस डेटा का एक जीवंत, दृश्य प्रतिनिधित्व देता है जिसे आप बनाते समय उत्पन्न कर रहे हैं। यह दौरा पैनल के काम करने के तरीके का एक त्वरित परिचय देता है।",
+ "previewRows": "पूर्वावलोकन पंक्तियों",
+ "private": "निजी",
+ "problemLoadingTour": "क्षमा करें, भ्रमण लोड करने में एक समस्या हुई.",
+ "programmingLanguages": "प्रोग्रामिंग की भाषाएँ",
+ "province": "प्रांत",
+ "public": "सह लोक",
+ "publicQ": "सह लोक?",
+ "reenterPassword": "पासवर्ड फिर से दर्ज करें",
+ "refreshPage": "पृष्ठ ताज़ा करें",
+ "refreshPanel": "पैनल ताज़ा करें",
+ "register": "रजिस्टर करें",
+ "remainingTime": "शेष समय:",
+ "requiredField": "आवश्यक क्षेत्र",
+ "revertToVersion": "इस संस्करण पर वापस जाएं",
+ "row": "पंक्ति",
+ "rowLabel": "कॉलम शीर्षक",
+ "rowLabelPlural": "कॉलम शीर्षक",
+ "rowNumDesc1": "ग्रिड में पहला कॉलम पंक्ति संख्या दिखाता है। पंक्ति को फिर से व्यवस्थित करने के लिए, बस इस तत्व को ऊपर या नीचे क्लिक करें और खींचें।",
+ "rowNumDesc2": "इस कॉलम का हेडर मान आपके ग्रिड में पंक्तियों की कुल संख्या दिखाता है। यह तब आसान हो सकता है जब आपके पास वास्तव में बड़े डेटा सेट हों और यह स्क्रीन पर फ़िट होने के लिए बहुत बड़ा हो।",
+ "rowNumber": "पंक्ति नंबर",
+ "rowSp": "पंक्ति",
+ "rows": "पंक्तियों",
+ "rowsGenerated": "पंक्तियाँ उत्पन्न",
+ "rowsGeneratedPerSecond": "प्रति सेकंड उत्पन्न पंक्तियाँ",
+ "safariExplanation": "इस साइट को आधुनिक वेब मानकों को लागू करने वाले अप-टू-डेट वेब ब्राउज़र की आवश्यकता है। दुर्भाग्य से सफारी अन्य ब्राउज़रों से पीछे रह गई है और इस स्क्रिप्ट को नहीं चलाएगी। जब तक हम यह नहीं समझ लेते कि इसे कैसे समायोजित किया जाए, कृपया निम्न में से किसी एक ब्राउज़र का उपयोग करें। डाउनलोड के लिए सभी मुफ्त।",
+ "save": "सहेजें",
+ "saveAs": "के रूप रक्षित करें",
+ "saveButtonDesc": "यदि आपके पास साइट पर एक उपयोगकर्ता खाता है तो यह बटन आपके डेटा सेट को सहेज लेगा। यदि आप लॉग इन नहीं हैं, तो इसे क्लिक करने से आपको पहले लॉग इन करने के लिए संकेत मिलेगा। सिस्टम डेटा सेट में पिछले %1 परिवर्तनों का इतिहास रखता है, ताकि आप हमेशा वापस जा सकें और पुराने संस्करण देख सकें।",
+ "saveDataSetNewName": "डेटा सेट को नए नाम के रूप में सहेजें",
+ "seconds": "सेकंड",
+ "seeHelpDialog": "सहायता संवाद देखें।",
+ "selectCountry": "देश चुनें",
+ "selectDataType": "डेटा प्रकार चुनें",
+ "selectEllipsis": "चुनते हैं...",
+ "selectExpiryDate": "समाप्ति तिथि चुनें",
+ "selectLanguage": "भाषा चुने",
+ "sendEmail": "ईमेल भेजें",
+ "seriouslySlow": "गंभीर रूप से धीमा",
+ "settings": "समायोजन",
+ "showGrid": "ग्रिड दिखाएं",
+ "showLineNumbers": "लाइन नंबर दिखाएं",
+ "showPreview": "पूर्वावलोकन दिखाएँ",
+ "siteLogo": "साइट लोगो",
+ "size": "आकार:",
+ "southAmerica": "दक्षिण अमेरिका",
+ "status": "स्थिति",
+ "stripWhitespace": "जेनरेट की गई सामग्री से व्हाइटस्पेस स्ट्रिप करें",
+ "success": "सफलता",
+ "text": "मूलपाठ",
+ "textSize": "शब्दों का आकर",
+ "theDataSetName": "डेटा सेट का नाम",
+ "theExportTypeBtn": "निर्यात प्रकार बटन",
+ "theGenerateButton": "जनरेट बटन",
+ "theGridPanel": "ग्रिड पैनल",
+ "thePreviewPanel": "पूर्वावलोकन पैनल",
+ "theSaveButton": "सहेजें बटन",
+ "theme": "विषय",
+ "togglePanelLayout": "पैनल लेआउट टॉगल करें",
+ "totalNumGeneratedRows": "कुल संख्या उत्पन्न पंक्तियाँ",
+ "tourComplete": "यात्रा पूर्ण",
+ "tourCompleteDesc": "सब कुछ कर दिया! जारी रखने के लिए नीचे दिए गए बटनों में से एक चुनें।",
+ "tourIntroPara1": "यह उपकरण बहुत अधिक कार्यक्षमता प्रदान करता है और यह पहली बार में थोड़ा भारी हो सकता है। उस विशेष सुविधा के बारे में भ्रमण करने के लिए दाईं ओर किसी एक बटन पर क्लिक करें।",
+ "tourIntroPara2": "ध्यान दें कि जब टूर शुरू होता है, तो यह इंटरफ़ेस के बारे में कुछ चीजों को स्पष्ट करने के लिए जनरेटर पेज की सामग्री को अस्थायी रूप से अधिलेखित कर देता है। लेकिन चिंता मत करो! जैसे ही टूर समाप्त होता है, यह आपके डेटा को उसकी मूल स्थिति में लौटा देता है।",
+ "tryDifferentTour": "कोई भिन्न भ्रमण आज़माएं",
+ "update": "अद्यतन",
+ "updateAccount": "खाता अद्यतन करें",
+ "user": "उपयोगकर्ता",
+ "userAccount": "उपभोक्ता खाता",
+ "userAccountNotFound": "क्षमा करें, हम आपका खाता नहीं ढूंढ सके।",
+ "userAccountUpdated": "उपयोगकर्ता खाता अपडेट कर दिया गया है।",
+ "userAccounts": "उपयोगकर्ता खाते",
+ "userNotFound": "क्षमा करें, हम आपको पहचानने में असमर्थ रहे। कृपया अपनी साख जांचें।",
+ "validationAccountAlreadyExists": "क्षमा करें, उस ईमेल पते वाला एक खाता पहले से मौजूद है",
+ "validationDifferentNewPasswords": "अपने पासवर्ड का मिलान सुनिश्चित करें",
+ "validationInvalidEmail": "कृपया एक वैध ई - मेल एड्रेस डालें",
+ "validationNoCurrentPassword": "कृपया अपना वर्तमान पासवर्ड दर्ज करें",
+ "validationNoEmail": "कृपया अपना पूरा ईमेल दर्ज करें",
+ "validationNoFirstName": "कृपया अपना प्रथम नाम प्रविष्ट करें",
+ "validationNoLastName": "कृपया अपना अंतिम नाम दर्ज करें",
+ "validationNoPassword": "अपना पासवर्ड दर्ज करें",
+ "version": "संस्करण",
+ "view": "राय",
+ "viewChangelog": "परिवर्तन लॉग देखें",
+ "viewOnGithub": "गीथूब पर देखें",
+ "welcomeToTheGenerator": "जनरेटर में आपका स्वागत है!",
+ "yes": "हां",
+ "yourAccount": "आपका खाता",
+ "yourAccountUpdated": "आपका खाता नवीनीकृत हो चुका है।",
+ "yourUserAccount": "आपका उपयोगकर्ता खाता"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/ja.json b/apps/client/src/i18n/ja.json
new file mode 100644
index 000000000..48a2e33af
--- /dev/null
+++ b/apps/client/src/i18n/ja.json
@@ -0,0 +1,329 @@
+{
+ "abort": "アボート",
+ "about": "約",
+ "aboutInfoPara1": "generateata.comは、旧石器時代後期(2004年頃)に私が作成したオープンソースプロジェクトであり、その存続期間にわたって多くの貢献者がいます。 数年ごとに、最新かつ最高の利用可能なテクノロジーを使用して書き直しています。このバージョンは2021年に完成し、React、Redux、Typescript、GraphQL、Docker、Webワーカー、輪ゴム、ダクトテープ、そしてたくさんの呪いで作られました。",
+ "aboutInfoPara2": "プロジェクトはgithubにあります。 ダウンロードするか、フォークするか、気軽に投稿してください。 楽しい!",
+ "aboutThisScript": "このスクリプトについて",
+ "accountCreated": "アカウントが作成されました",
+ "accountCreatedDesc": "アカウントが作成されました。",
+ "accountCreatedMsg": "アカウントが作成されました。",
+ "accountDeleted": "アカウントが削除されました。",
+ "accountDisabled": "アカウントが無効になっています",
+ "accountExpiredMsg": "申し訳ありませんが、アカウントの有効期限が切れています。",
+ "accountExpiryDate": "アカウントの有効期限",
+ "accountInfo": "アカウント情報",
+ "accountSettings": "アカウント設定",
+ "accountType": "口座の種類",
+ "accounts": "アカウント",
+ "add": "追加",
+ "addRowsDesc": "データグリッドに行を追加するには、ページの下部にあるフォームを使用するだけです。 フィールドがたくさんある場合、このセクションはページの外に隠れている可能性があるため、フォームを表示するにはパネルを一番下までスクロールする必要があります。",
+ "addSomeDataDesc": "グリッドにいくつかの行を追加します。",
+ "admin": "管理者",
+ "adminAccount": "管理者アカウント",
+ "administrator": "管理者",
+ "africa": "アフリカ",
+ "allCountries": "全ての国々",
+ "allDataTypePlugins": "すべてのデータ型プラグイン",
+ "allExportTypePlugins": "すべてのエクスポートタイププラグイン",
+ "anonymousAccess": "匿名アクセス",
+ "asia": "アジア",
+ "back": "バック",
+ "backToLogin": "ログインに戻る",
+ "blog": "ブログ",
+ "cancel": "キャンセル",
+ "centralAmerica": "中米",
+ "changePassword": "パスワードを変更する",
+ "clear": "晴れ",
+ "clearPage": "ページをクリア",
+ "clearPageConfirmation": "ページをクリアしてもよろしいですか? 行った変更はすべて失われます。",
+ "clearThePage": "ページをクリアする",
+ "clickExportTypeBtn": "次のステップは、ボタンをクリックするとどうなるかを示しています。",
+ "close": "閉じる",
+ "closePanel": "パネルを閉じる",
+ "columns": "列",
+ "columnsDesc": "グリッドには、使用可能な画面サイズに応じて、これらの列をできるだけ多く表示します。 小さすぎると、一部の列が非表示になり、コンテンツが不足している情報ヒントを開く歯車アイコンが表示されます。 次のいくつかの手順では、最初のNames行をデモンストレーションとして使用して、各列について説明します。",
+ "complete": "コンプリート",
+ "confirmDeleteAccount": "このアカウントを削除してもよろしいですか?",
+ "confirmDeleteUserAccount": "このユーザーアカウントを削除してもよろしいですか?",
+ "confirmEmptyForm": "ページをクリアしてもよろしいですか?",
+ "continue": "継続する",
+ "copiedToClipboard": "クリップボードにコピー",
+ "copyToClipboard": "クリップボードにコピー",
+ "coreExportType": "コアフォーマット",
+ "countries": "国",
+ "country": "国",
+ "countrySpecific": "国別",
+ "cpuMeltinglyFast": "CPU-とてつもなく速い",
+ "createAccount": "アカウントを作成する",
+ "currentPassword": "現在のパスワード",
+ "currentVersion": "編集中の現在のバージョン",
+ "dataGenerated": "生成されたデータ。",
+ "dataSet": "データセット",
+ "dataSetHelp": "ここで、生成するデータの種類を正確に定義します。 1行か2行入力して、[生成]ボタンをクリックしてみてください。 あなたはそれのこつをかなり速く得るでしょう。",
+ "dataSetLoaded": "データセットがロードされました。",
+ "dataSetName": "データセット名",
+ "dataSetNameDesc": "ここのテキストをクリックして、データセットに名前を付けます。 データセットに名前を付けるには、サイトにアカウントが必要であり、ログインする必要があることに注意してください。 データセットを保存するとき、この名前はそれらを追跡するための便利なラベルを提供します。",
+ "dataSetNameUpdated": "データセット名が更新されました。",
+ "dataSetOptions": "データセットオプション",
+ "dataSetReverted": "データセットは、選択した古いバージョンに戻されました。",
+ "dataSetSaved": "データセットが保存されました。",
+ "dataSets": "データセット",
+ "dataType": "データ・タイプ",
+ "dataTypeDesc": "これは、使用可能なすべてのデータ型を含むドロップダウンです。 値を選択すると、その行の残りの列が更新されます。 各データ型には、カスタマイズを可能にする独自の設定があります。",
+ "dataTypes": "データ型",
+ "dateAccountCreated": "アカウントが作成された日付",
+ "dateCreated": "作成日",
+ "defaultLanguage": "既定の言語",
+ "delete": "削除",
+ "delete1DataSet": "1つのデータセットを削除します",
+ "deleteAccount": "アカウントを削除する",
+ "deleteDataSet": "データセットの削除",
+ "deleteDataSetConfirm": "このデータセットを削除してもよろしいですか?",
+ "deleteRow": "行を削除する",
+ "deleteRowDesc": "最後の列には削除アイコンが含まれています。 クリックすると行が削除されます。",
+ "developer": "開発者",
+ "developerDoc": "開発者ドキュメント",
+ "disabled": "無効",
+ "documentation": "ドキュメンテーション",
+ "download": "ダウンロード",
+ "edit": "編集",
+ "editAccount": "アカウントの編集",
+ "editExportTypePage": "[エクスポートタイプ]ボタンをクリックすると、左側に構成オプション、右側にプレビューパネルを含む全ページビューが表示されます。",
+ "editExportTypePageConfig": "構成オプションを見てみましょう。",
+ "editExportTypeSettings": "エクスポートタイプの設定を編集してください。",
+ "editingExportTypes": "エクスポートタイプの編集",
+ "email": "Eメール",
+ "emailFooterDisclaimer": "このメールを誤って受け取った場合は、サイト管理者に連絡してください。",
+ "emailIntroLineWithName": "こんにちは%1、",
+ "emailLabel": "Eメール:",
+ "emailNotSent": "メール通知を送信できませんでした。",
+ "emailUserLoginInfo": "ログイン情報をユーザーにメールで送信する",
+ "enterEmailAddressToResetPassword": "パスワードをリセットするには、以下にメールアドレスを入力してください。",
+ "enterUserAccountDetails": "以下にユーザーアカウントの詳細を入力してください。",
+ "errorCreatingAccount": "このアカウントの作成中にエラーが発生しました。",
+ "estimatedSize": "推定サイズ:",
+ "estimatedTime": "予定時刻:",
+ "europe": "ヨーロッパ",
+ "example": "例",
+ "exampleColumn": "列の例",
+ "exampleColumnDesc": "この列には、行に選択したデータ型に応じてコンテンツが含まれる場合と含まれない場合があります。 これは、一部のデータ型がそれらの使用方法のプリセット例を提供するための便利な方法です。 たとえば、[名前]列には、さまざまな名前形式のドロップダウンがあります。 すべてのデータタイプについて、[オプション]列から手動で構成できます。これは、できるだけ早くスピードを上げるための本当に時間の節約になるデバイスです。",
+ "examples": "例",
+ "exit": "出口",
+ "expired": "期限切れ",
+ "expiryDate": "有効期限",
+ "exportType": "エクスポートタイプ",
+ "exportTypeBtnDesc": "パネルの左上にあるボタンは、生成するデータの形式を選択して構成するために移動する場所です。 ここではTypescriptに設定されています。",
+ "exportTypeOptionsSql": "エクスポートタイプオプション:SQL",
+ "exportTypeOptionsSqlDesc": "ご覧のとおり、SQLには、このエクスポートタイプに固有のフィールドが他にもたくさんあります。 他のエクスポートタイプには異なる設定があります。",
+ "exportTypeOptionsTs": "タイプオプションのエクスポート:Typescript",
+ "exportTypeOptionsTsDesc": "選択したエクスポートタイプによっては、パネル本体に追加のオプションが含まれる場合があります。 Typescriptには2つのフィールドがあり、タイプ名とエクスポートされた変数名をカスタマイズできます。",
+ "exportTypeOptionsTsDesc2": "それでは、SQLを見てみましょう。",
+ "exportTypeSelection": "エクスポートタイプの選択",
+ "exportTypeSelectionDesc": "[形式]ドロップダウンでは、生成されるコンテンツの形式を選択できます。 多くのオプションがあり、そのうちのいくつかはコアタイプです:SQL、CSV、SQL、JSONなど、そしてそれらのいくつかは特定のプログラミング言語のデータを生成するためのものです。",
+ "exportTypes": "エクスポートタイプ",
+ "filterDataTypes": "データ型をフィルタリングする",
+ "financial": "財務",
+ "firstName": "ファーストネーム",
+ "forgottenYourPasswordQ": "パスワードを忘れましたか?",
+ "format": "フォーマット",
+ "generate": "生む",
+ "generateBtnDesc": "データセットの構成が完了し、希望どおりに表示されることを確認したら、データを大量に生成します。 このボタンをクリックして、データを生成します。 データグリッドの行数と生成する行数によっては、時間がかかる場合があります。",
+ "generated": "生成された",
+ "generatedC": "生成された:",
+ "generator": "発生器",
+ "geo": "ジオ",
+ "grid": "グリッド",
+ "gridPanelTourDesc1": "グリッドパネルは、生成するデータを作成する場所です。 ここでは、さまざまなデータタイプ、それらの構成方法、およびそれらが表示される順序から選択できます。 この例では、名前、電話番号、電子メール、住所、市区町村の5つのフィールドがあります。",
+ "gridPanelTourDesc2": "このセクションの詳細については、グリッドパネルのツアーを参照してください。",
+ "gridPanelTourIntroDesc1": "グリッドパネルは、生成するデータを定義する場所です。 このツアーでは、プレビューパネルを非表示にして(ページの右下にあるチェックされていない[プレビュー]ボタンを参照)、もう少しスペースを確保しました。 また、説明のために、10行の異なるデータ型を追加しました。",
+ "gridPanelTourIntroDesc2": "グリッド列から始めましょう。",
+ "help": "助けて",
+ "helpIcon": "ヘルプアイコン",
+ "helpIconDesc": "前の列でデータ型を選択すると、ここにアイコンが表示されます。 それをクリックすると、使用法情報を含むヘルプダイアログが開きます。",
+ "hide": "隠す",
+ "hideShowGrid": "グリッドの表示/非表示",
+ "hideShowPreviewPanel": "プレビューパネルの表示/非表示",
+ "history": "歴史",
+ "historyPanelDesc": "このパネルには、データセットに加えられた変更の履歴が表示されます。 以前のバージョンを調べるには、[表示]ボタンをクリックします。 以前のバージョンに戻すには、それを表示してから、[このバージョンに戻す]ボタンをクリックします。",
+ "hostName": "ホスト名",
+ "humanData": "人間のデータ",
+ "ifWantToReregister": "再登録をご希望の場合は、以下をご覧ください。",
+ "incomplete": "不完全な",
+ "install": "インストール",
+ "introToGenerator": "ジェネレーターの紹介",
+ "introToGeneratorDesc": "このツアーでは、データジェネレーターの主な機能の概要を説明します。 始めましょう!",
+ "invalidSettings": "設定が無効です!",
+ "language": "日本語",
+ "lastEdited": "最終編集",
+ "lastLoggedIn": "最後にログインした",
+ "lastModified": "最終更新日",
+ "lastName": "苗字",
+ "lastSaved": "最後に保存された",
+ "lineWrapping": "行の折り返し",
+ "linkToDataSet": "データセットへのリンク",
+ "linkToThisDataSet": "このデータセットへのリンク",
+ "live": "住む",
+ "load": "負荷",
+ "loading": "読み込んでいます...",
+ "login": "ログインする",
+ "loginToSave": "データセットを保存するには、まずアカウントが必要です。 以下からログインしてください。",
+ "loginOrRegisterToSave": "データセットを保存するには、まずアカウントが必要です。 以下からログインまたは登録してください。",
+ "loginUrlLabel": "ログインURL:",
+ "logout": "ログアウト",
+ "makeDataSetPublicAgreement": "このデータセットを共有するには、公開する必要があることを理解しています。",
+ "metaDescription": "generatedata.com:ソフトウェアをテストするための無料のMITライセンスのランダムカスタムデータジェネレーター",
+ "metaKeywords": "ランダムデータ、テストデータ、サンプルデータ、データジェネレーター、データの生成",
+ "missingDataSetName": "データセットの名前を入力してください",
+ "nameColumn": "名前列",
+ "nameColumnDesc": "この列には、プレビューパネルで選択したエクスポートタイプ(XML、CSVなど)に応じて異なる見出しが表示されます。 この列の目的は、生成されたデータで使用できる行の識別子を提供することです。 たとえば、XMLはこの値をXMLノード名として使用します。 JSONはそれをJSONプロパティ名として使用します。 SQLはそれをデータベースの列名に使用します。 アプリケーションは、ここに入力した値を自動的に検証して、選択したエクスポートタイプに対して有効であることを確認します。",
+ "newDataSet": "新しいデータセット",
+ "no": "No",
+ "noAccountsCreated": "アカウントは作成されていません。",
+ "noAdditionalSettings": "追加設定なし",
+ "noDataSetsSaved": "データセットは保存されていません。",
+ "noExamplesAvailable": "利用可能な例はありません。",
+ "noExpiry": "有効期限なし",
+ "noHistory": "まだ歴史はありません。",
+ "noOptionsAvailable": "利用可能なオプションはありません。",
+ "noSafari": "申し訳ありませんが、Safariはありません!",
+ "northAmerica": "北米",
+ "nowLoggedIn": "ログインしました。",
+ "nowLoggedOut": "ログアウトしました。",
+ "numResults": "結果の数",
+ "numeric": "数値",
+ "oceania": "オセアニア",
+ "oneTimePasswordLogin": "ワンタイムパスワードを使用してログインしました。 今すぐ新しいパスワードを設定してください。",
+ "open": "開いた",
+ "options": "オプション",
+ "optionsColumn": "オプション列",
+ "optionsColumnDesc1": "この列には、この行のデータ型のすべての構成オプションが含まれています。 一部のデータ型では、構成設定が多すぎてこのような小さなスペース(国、地域など)に表示できないため、すべてのオプションを含むダイアログを開くボタンがグリッドに表示される場合があります。",
+ "optionsColumnDesc2": "ここに示されているNamesData Typeの場合、列には単一のCreatablePillフィールドが含まれています。 これは、データジェネレータの一般的なフィールドタイプです。 入力してをクリックするだけでアイテムを追加できます。 次に、それらのアイテムを「x」アイコンで削除できる個別のピルに変換します。",
+ "or": "または",
+ "order": "注文",
+ "other": "その他",
+ "overMaxAnonRows": "生成されたコンテンツから空白を取り除く",
+ "panelContents": "パネルの内容",
+ "panelContentsDesc": "プレビューパネルの大部分には、グリッドパネルで指定したデータと選択したエクスポートタイプの設定に従って、生成されたデータのサンプルが含まれています。 ここでは、Typescript形式で5つの異なるデータタイプ(国、地域、市、番地、郵便番号)を生成しています。",
+ "panelControls": "パネルコントロール",
+ "panelControlsClearIconDesc": "パネルコントロールの最後のアイコンを使用すると、ページをクリアして、最初からやり直すことができます。",
+ "panelControlsDesc": "パネルコントロールボタンを使用すると、グリッドパネルとプレビューパネルを表示/非表示にできます。これは、限られた画面領域で作業している場合に役立ちます。 3番目のアイコンを使用して、2つのパネルの相対的な配置(左右、上下)を切り替えることもできます。",
+ "panelTabs": "パネルタブ",
+ "panelTabsDesc": "最後に、パネルが2つのタブに編成されていることに気付いたかもしれません。 デフォルトでは、[設定]タブが表示されます。これは、このタブに移動する可能性が最も高いためです。 ただし、プレビューパネルの外観を変更するための設定を含む2番目の[プレビュー]タブもあります。テーマ、行番号の非表示/表示、行の折り返しの切り替え、テキストサイズの変更、生成される行数の制御です。",
+ "password": "パスワード",
+ "passwordLabel": "パスワード:",
+ "passwordReset": "パスワードのリセット",
+ "passwordResetAccountExpired": "パスワードのリセット-アカウントの有効期限が切れました",
+ "passwordResetAccountExpiredDesc": "パスワードのリセットリクエストを受け取りましたが、アカウントの有効期限はすでに切れています。",
+ "passwordResetComplete": "パスワードがリセットされ、新しいパスワードがメールで送信されました。",
+ "passwordResetEmailContent1": "あなたのパスワードはリセットされました。 次のパスワードを使用してログインできます:%1",
+ "passwordResetEmailContent2": "ログインしたら変更してください。",
+ "passwordResetEmailDesc": "パスワードのリセットリクエストを受け取りました。 このメールには、ログインに使用できる一時的なワンタイムパスワードが含まれています。ログイン後、新しいパスワードを設定する必要があります。",
+ "passwordResetMsg": "電子メールがファイルにある場合は、パスワードがリセットされ、一時的なログイン情報が電子メールで送信されています。",
+ "passwordUpdateInvalidPassword": "現在のパスワードが正しくありません。 再入力してください。",
+ "passwordUpdated": "パスワードが更新されました。",
+ "pause": "一時停止",
+ "pleaseConfirm": "確認してください",
+ "pleaseEnterAll": "すべて入力してください",
+ "pleaseEnterDataSetName": "新しいデータセットの名前を入力してください。",
+ "pleaseFixErrors": "次のエラーを修正して、再送信してください。",
+ "pleaseLogin": "ログインしてください",
+ "pleaseSelect": "選んでください",
+ "plugins": "プラグイン",
+ "pressEnterAddItem": "Enterキーを押してアイテムを追加します",
+ "preview": "プレビュー",
+ "previewPanel": "プレビューパネル",
+ "previewPanelControlsDesc": "パネルの右上には2つのアイコンがあります。 最初のアイコンはパネルを更新し、毎回異なるランダムデータを生成します。これは、パネルが生成するデータの種類を把握するのに役立ちます。 2番目のアイコンは、パネルを完全に閉じます。 ページの右下にあるセクションを使用して、いつでもパネルを再度表示できます([プレビュー]チェックボックスを再度選択するだけです)。",
+ "previewPanelDesc": "このパネルは、グリッドパネルを介してデータを構築しているときに、生成しているデータのライブプレビューを提供します。 左上のボタンには、生成されるデータの選択された形式(SQL、XML、CSVなど)が表示されます。 フォーマットを変更するには、ボタンをクリックするだけです。",
+ "previewPanelMoreInfo": "このパネルの詳細については、プレビューパネルツアーをご覧ください。",
+ "previewPanelNoData": "データなし!",
+ "previewPanelTourDesc": "プレビューパネルには、作成中に生成しているデータのライブの視覚的表現が表示されます。 このツアーでは、パネルの仕組みを簡単に紹介します。",
+ "previewRows": "行のプレビュー",
+ "private": "民間",
+ "problemLoadingTour": "申し訳ありませんが、ツアーの読み込みに問題がありました。",
+ "programmingLanguages": "プログラミング言語",
+ "province": "州",
+ "public": "公衆",
+ "publicQ": "公衆?",
+ "reenterPassword": "パスワード再入力",
+ "refreshPage": "ページの更新",
+ "refreshPanel": "パネルの更新",
+ "register": "登録",
+ "remainingTime": "残り時間:",
+ "requiredField": "必須フィールド",
+ "revertToVersion": "このバージョンに戻す",
+ "row": "行",
+ "rowLabel": "列のタイトル",
+ "rowLabelPlural": "列のタイトル",
+ "rowNumDesc1": "グリッドの最初の列は行番号を示しています。 行を並べ替えるには、この要素をクリックして上下にドラッグします。",
+ "rowNumDesc2": "この列のヘッダー値は、グリッド内の行の総数を示します。 これは、データセットが非常に大きく、大きすぎて画面に収まらない場合に便利です。",
+ "rowNumber": "行番号",
+ "rowSp": "行",
+ "rows": "行",
+ "rowsGenerated": "生成された行",
+ "rowsGeneratedPerSecond": "1秒あたりに生成される行",
+ "safariExplanation": "このサイトには、最新のWeb標準を実装する最新のWebブラウザが必要です。 残念ながら、Safariは他のブラウザに遅れをとっており、このスクリプトを実行しません。 どのように対応するかがわかるまで、次のいずれかのブラウザを使用してください。 すべて無料でダウンロードできます。",
+ "save": "セーブ",
+ "saveAs": "名前を付けて保存",
+ "saveButtonDesc": "サイトにユーザーアカウントがある場合、このボタンはデータセットを保存します。 ログインしていない場合は、クリックすると最初にログインするように求められます。 システムはデータセットへの最後の%1変更の履歴を保持するため、いつでも戻って古いバージョンを表示できます。",
+ "saveDataSetNewName": "データセットを新しい名前で保存する",
+ "seconds": "秒",
+ "seeHelpDialog": "ヘルプダイアログを参照してください。",
+ "selectCountry": "国を選択",
+ "selectDataType": "データ型を選択",
+ "selectEllipsis": "選択する...",
+ "selectExpiryDate": "有効期限を選択してください",
+ "selectLanguage": "言語を選択する",
+ "sendEmail": "メールを送る",
+ "seriouslySlow": "ひどく遅い",
+ "settings": "設定",
+ "showGrid": "グリッドを表示",
+ "showLineNumbers": "行のプレビュー",
+ "showPreview": "ショープレビュー",
+ "siteLogo": "サイトのロゴ",
+ "size": "サイズ:",
+ "southAmerica": "南アメリカ",
+ "status": "状態",
+ "stripWhitespace": "生成されたコンテンツから空白を取り除く",
+ "success": "成功",
+ "text": "テキスト",
+ "textSize": "文字サイズ",
+ "theDataSetName": "データセット名",
+ "theExportTypeBtn": "[タイプのエクスポート]ボタン",
+ "theGenerateButton": "生成ボタン",
+ "theGridPanel": "グリッドパネル",
+ "thePreviewPanel": "プレビューパネル",
+ "theSaveButton": "保存ボタン",
+ "theme": "テーマ",
+ "togglePanelLayout": "パネルレイアウトの切り替え",
+ "totalNumGeneratedRows": "生成された行の総数",
+ "tourComplete": "ツアー完了",
+ "tourCompleteDesc": "すべて完了! 以下のボタンのいずれかを選択して続行します。",
+ "tourIntroPara1": "このツールは多くの機能を提供し、最初は少し圧倒される可能性があります。 右側のボタンの1つをクリックして、その特定の機能に関するツアーに参加してください。",
+ "tourIntroPara2": "ツアーが開始されると、ジェネレーターページのコンテンツが一時的に上書きされ、インターフェイスに関する特定のことが説明されることに注意してください。 しかし、心配しないでください! ツアーが終了するとすぐに、データが元の状態に戻ります。",
+ "tryDifferentTour": "別のツアーをお試しください",
+ "update": "更新",
+ "updateAccount": "アカウントを更新する",
+ "user": "ユーザー",
+ "userAccount": "ユーザーアカウント",
+ "userAccountNotFound": "申し訳ありませんが、アカウントが見つかりませんでした。",
+ "userAccountUpdated": "ユーザーアカウントが更新されました。",
+ "userAccounts": "ユーザーアカウント",
+ "userNotFound": "申し訳ありませんが、お客様を特定できませんでした。 資格情報を確認してください。",
+ "validationAccountAlreadyExists": "申し訳ありませんが、そのメールアドレスのアカウントは既に存在します。",
+ "validationDifferentNewPasswords": "新しいパスワードが一致していることを確認してください",
+ "validationInvalidEmail": "有効なメールアドレスを入力してください",
+ "validationNoCurrentPassword": "現在のパスワードを入力してください",
+ "validationNoEmail": "メールアドレスを入力してください",
+ "validationNoFirstName": "名を入力してください",
+ "validationNoLastName": "あなたの姓を入力",
+ "validationNoPassword": "パスワードを入力してください",
+ "version": "バージョン",
+ "view": "見る",
+ "viewChangelog": "変更ログを表示",
+ "viewOnGithub": "Githubで表示",
+ "welcomeToTheGenerator": "ジェネレーターへようこそ!",
+ "yes": "Yes",
+ "yourAccount": "あなたのアカウント",
+ "yourAccountUpdated": "アカウントが更新されました。",
+ "yourUserAccount": "あなたのユーザーアカウント"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/nl.json b/apps/client/src/i18n/nl.json
new file mode 100644
index 000000000..25d858332
--- /dev/null
+++ b/apps/client/src/i18n/nl.json
@@ -0,0 +1,329 @@
+{
+ "abort": "Afbreken",
+ "about": "Over",
+ "aboutInfoPara1": "generatedata.com is een open source-project dat ik heb gemaakt in het late paleolithische tijdperk (circa 2004) en gedurende zijn levensduur heeft het vele, vele bijdragers gehad. Om de paar jaar herschrijf ik het met de nieuwste en beste beschikbare technologieën: deze versie werd in 2021 voltooid, gemaakt met React, Redux, Typescript, GraphQL, Docker, webworkers, elastiekjes, ducttape en een heleboel vloeken.",
+ "aboutInfoPara2": "Je kunt het project vinden op github. Download het, vork het of voel je vrij om bij te dragen. Genieten!",
+ "aboutThisScript": "Over dit script",
+ "accountCreated": "Gebruikersaccount aangemaakt",
+ "accountCreatedDesc": "Het account is aangemaakt.",
+ "accountCreatedMsg": "Er is een gebruikersaccount voor u aangemaakt.",
+ "accountDeleted": "Het account is verwijderd.",
+ "accountDisabled": "Account ongebruikelijk gemaakt",
+ "accountExpiredMsg": "Sorry, uw account is verlopen.",
+ "accountExpiryDate": "Vervaldatum account",
+ "accountInfo": "Gebruikersgegevens",
+ "accountSettings": "Gebruikersinstellingen",
+ "accountType": "Gebruikerstype",
+ "accounts": "Gebruikers",
+ "add": "Toevoegen",
+ "addRowsDesc": "Gebruik het formulier onder aan de pagina om meer rijen aan het gegevensraster toe te voegen. Als je veel velden hebt, is deze sectie mogelijk verborgen buiten de pagina, dus je moet het paneel naar beneden scrollen om het formulier te zien.",
+ "addSomeDataDesc": "Voeg enkele rijen toe aan het raster.",
+ "admin": "Admin",
+ "adminAccount": "Admin",
+ "administrator": "Beheerder",
+ "africa": "Afrika",
+ "allCountries": "Alle landen",
+ "allDataTypePlugins": "Alle datatype plugins",
+ "allExportTypePlugins": "Alle exporttype plugins",
+ "anonymousAccess": "Anonieme toegang",
+ "asia": "Azie",
+ "back": "Terug",
+ "backToLogin": "Terug naar Inloggen",
+ "blog": "Blog",
+ "cancel": "afbreken",
+ "centralAmerica": "Midden Amerika",
+ "changePassword": "Verander wachtwoord",
+ "clear": "Doorzichtig",
+ "clearPage": "Duidelijke pagina",
+ "clearPageConfirmation": "Weet u zeker dat u de pagina wilt wissen? Alle aangebrachte wijzigingen gaan verloren.",
+ "clearThePage": "Pagina wissen",
+ "clickExportTypeBtn": "De volgende stap laat zien wat er gebeurt als u op de knop klikt.",
+ "close": "Sluiten",
+ "closePanel": "Sluit het paneel",
+ "columns": "Kolommen",
+ "columnsDesc": "Het raster toont zoveel mogelijk van deze kolommen, afhankelijk van uw beschikbare schermgrootte. Als het te klein is, verbergt het enkele kolommen en wordt er een tandwielpictogram weergegeven dat een infotip opent met de ontbrekende inhoud. In de volgende stappen wordt elk van de kolommen uitgelegd, waarbij de eerste rij met namen als demonstratie wordt gebruikt.",
+ "complete": "Compleet",
+ "confirmDeleteAccount": "Weet u zeker dat u dit account wilt verwijderen?",
+ "confirmDeleteUserAccount": "Weet u zeker dat u deze gebruiker wilt verwijderen?",
+ "confirmEmptyForm": "Weet u zeker dat u deze pagina leeg wilt maken?",
+ "continue": "Doorgaan met",
+ "copiedToClipboard": "Gekopieerd naar het klembord",
+ "copyToClipboard": "Kopieer naar klembord",
+ "coreExportType": "Core formaten",
+ "countries": "Landen",
+ "country": "Land",
+ "countrySpecific": "Land specifiek",
+ "cpuMeltinglyFast": "CPU-smeltend snel",
+ "createAccount": "Maak nieuwe gebruiker aan",
+ "currentPassword": "Huidig wachtwoord",
+ "currentVersion": "Huidige versie wordt bewerkt",
+ "dataGenerated": "Gegevens gegenereerd.",
+ "dataSet": "Dataset",
+ "dataSetHelp": "Hier definieerd u precies welke data gegenereerd moet worden. Probeer een rij of 2 in te vullen en click op de genereer knop. Dan zult u snel doorhebben hoe het werkt.",
+ "dataSetLoaded": "De dataset is geladen.",
+ "dataSetName": "Datasetnaam",
+ "dataSetNameDesc": "Klik hier op de tekst om uw dataset een naam te geven. Houd er rekening mee dat u een account op de site nodig heeft en moet zijn ingelogd om uw datasets een naam te geven. Wanneer u uw datasets opslaat, geeft deze naam u een handig label om ze bij te houden.",
+ "dataSetNameUpdated": "De naam van de dataset is bijgewerkt.",
+ "dataSetOptions": "Opties voor datasets",
+ "dataSetReverted": "Uw dataset is teruggezet naar de geselecteerde oudere versie.",
+ "dataSetSaved": "Uw dataset is opgeslagen.",
+ "dataSets": "Gegevenssets",
+ "dataType": "Datatype",
+ "dataTypeDesc": "Dit is een vervolgkeuzelijst met alle beschikbare gegevenstypen. Wanneer u een waarde selecteert, wordt de resterende kolommen voor die rij bijgewerkt. Elk gegevenstype heeft zijn eigen unieke instellingen om aanpassingen mogelijk te maken.",
+ "dataTypes": "Datatypes",
+ "dateAccountCreated": "Aanmaak datum gebruiker",
+ "dateCreated": "Datum gecreeërd",
+ "defaultLanguage": "Standaardtaal",
+ "delete": "Verwijder",
+ "delete1DataSet": "Verwijder 1 dataset",
+ "deleteAccount": "Verwijder gebruiker",
+ "deleteDataSet": "Gegevensset verwijderen",
+ "deleteDataSetConfirm": "Weet u zeker dat u deze dataset wilt verwijderen?",
+ "deleteRow": "Verwijder rij",
+ "deleteRowDesc": "De laatste kolom bevat een verwijderpictogram. Als u erop klikt, wordt de rij verwijderd.",
+ "developer": "Ontwikkelaar",
+ "developerDoc": "Ontwikkeldocumentatie",
+ "disabled": "Gehandicapt",
+ "documentation": "Documentatie",
+ "download": "Download",
+ "edit": "Wijzig",
+ "editAccount": "Bewerk account",
+ "editExportTypePage": "Wanneer u op de knop Exporttype klikt, verschijnt een paginagrote weergave met de configuratie-opties aan de linkerkant en het voorbeeldvenster aan de rechterkant.",
+ "editExportTypePageConfig": "Laten we eens kijken naar de configuratie-opties.",
+ "editExportTypeSettings": "Bewerk uw exporttype-instellingen.",
+ "editingExportTypes": "Exporttypen bewerken",
+ "email": "Email",
+ "emailFooterDisclaimer": "Als u deze e-mail ten onrechte heeft ontvangen, neem dan contact op met de sitebeheerder.",
+ "emailIntroLineWithName": "Hallo %1,",
+ "emailLabel": "E-mailadres:",
+ "emailNotSent": "We zijn helaas niet instaat email te versturen.",
+ "emailUserLoginInfo": "Stuur de gebruiker een email met zijn logininformatie",
+ "enterEmailAddressToResetPassword": "Vul uw emailadres in om uw wachtwoord te herstellen.",
+ "enterUserAccountDetails": "Vul alstublieft uw gebruikersgegevens hieronder in.",
+ "errorCreatingAccount": "Er is een fout opgetreden bij het maken van dit account.",
+ "estimatedSize": "Geschatte grootte:",
+ "estimatedTime": "Geschatte tijd:",
+ "europe": "Europa",
+ "example": "Voorbeeld",
+ "exampleColumn": "Voorbeeld kolom",
+ "exampleColumnDesc": "Deze kolom kan inhoud bevatten, afhankelijk van het gegevenstype dat u voor de rij hebt geselecteerd. Voor sommige gegevenstypen is het gewoon een handige manier om vooraf ingestelde voorbeelden te geven van hoe ze kunnen worden gebruikt. De kolom Namen biedt bijvoorbeeld een vervolgkeuzelijst met verschillende naamformaten. Voor alle gegevenstypen kunt u het nog steeds handmatig configureren via de kolom Opties: het is echt een tijdbesparend apparaat om u zo snel mogelijk op de hoogte te houden.",
+ "examples": "Voorbeelden",
+ "exit": "Uitgang",
+ "expired": "Verlopen",
+ "expiryDate": "Vervaldatum",
+ "exportType": "Exporttype",
+ "exportTypeBtnDesc": "De knop linksboven in het paneel is waar u naartoe gaat om het formaat te selecteren en configureren voor de gegevens die u wilt genereren. Hier is het ingesteld op Typescript.",
+ "exportTypeOptionsSql": "Opties voor exporttype: SQL",
+ "exportTypeOptionsSqlDesc": "Zoals u kunt zien, heeft SQL veel andere velden die specifiek zijn voor dit exporttype. Andere exporttypen hebben verschillende instellingen.",
+ "exportTypeOptionsTs": "Opties voor exporttype: typoscript",
+ "exportTypeOptionsTsDesc": "Afhankelijk van het geselecteerde exporttype, kan het paneelgedeelte aanvullende opties bevatten. Hier met Typescript zijn er twee velden, waarmee u de typenaam en de geëxporteerde variabelenaam kunt aanpassen.",
+ "exportTypeOptionsTsDesc2": "Laten we nu eens kijken naar SQL.",
+ "exportTypeSelection": "Selectie van exporttype",
+ "exportTypeSelectionDesc": "Met de vervolgkeuzelijst Formaat kunt u het formaat voor de gegenereerde inhoud kiezen. Er zijn veel opties, sommige zijn de kerntypen: SQL, CSV, SQL, JSON en andere, en sommige zijn bedoeld voor het genereren van gegevens voor specifieke programmeertalen.",
+ "exportTypes": "Exporttypes",
+ "filterDataTypes": "Filter gegevenstypen",
+ "financial": "Financieel",
+ "firstName": "Voornaam",
+ "forgottenYourPasswordQ": "Wachtwoord vergeten?",
+ "format": "Formaat",
+ "generate": "Genereer",
+ "generateBtnDesc": "Als je klaar bent met het configureren van je dataset en hebt bevestigd dat het eruitziet zoals je wilt, is het tijd om wat data in volume te genereren! Klik op deze knop om uw gegevens te genereren. Afhankelijk van het aantal rijen in het gegevensraster en het aantal rijen dat u wilt genereren, kan dit even duren.",
+ "generated": "Gegenereerd",
+ "generatedC": "Gegenereerd::",
+ "generator": "Generator",
+ "geo": "Geo",
+ "grid": "rooster",
+ "gridPanelTourDesc1": "Het rasterpaneel is waar u de gegevens construeert die u wilt genereren. Hier kunt u kiezen tussen de verschillende gegevenstypen, hoe ze zijn geconfigureerd en de volgorde waarin ze verschijnen. In het voorbeeld hier hebben we vijf velden: naam, telefoonnummer, e-mail, adres en stad. ",
+ "gridPanelTourDesc2": "Zie de rondleiding op het rasterpaneel voor meer gedetailleerde informatie over deze sectie.",
+ "gridPanelTourIntroDesc1": "Het rasterpaneel is waar u definieert welke gegevens u wilt genereren. Voor deze tour hebben we het voorbeeldvenster verborgen (zie de niet-aangevinkte \"Voorbeeld\" -knop rechtsonder op de pagina) om ons wat meer ruimte te geven. We hebben ook 10 rijen met verschillende gegevenstypen toegevoegd, alleen ter illustratie.",
+ "gridPanelTourIntroDesc2": "Laten we beginnen met de rasterkolommen.",
+ "help": "Help",
+ "helpIcon": "Help-pictogram",
+ "helpIconDesc": "Als u in de vorige kolom een gegevenstype selecteert, verschijnt hier een pictogram. Als u erop klikt, wordt een helpvenster geopend met gebruiksinformatie.",
+ "hide": "Zich verstoppen",
+ "hideShowGrid": "Raster verbergen / weergeven",
+ "hideShowPreviewPanel": "Verberg / toon voorbeeldpaneel",
+ "history": "Geschiedenis",
+ "historyPanelDesc": "Dit paneel toont de geschiedenis van wijzigingen die in de dataset zijn aangebracht. Klik op de Weergaveknoppen om eerdere versies te bekijken. Om terug te keren naar een eerdere versie, hoeft u deze alleen maar te bekijken en vervolgens op de knop \"Terugkeren naar deze versie\" te klikken.",
+ "hostName": "Hostnaam",
+ "humanData": "Menselijke data",
+ "ifWantToReregister": "Als u zich opnieuw wilt registreren, gaat u naar:",
+ "incomplete": "Incompleet",
+ "install": "Installeren",
+ "introToGenerator": "Inleiding tot de generator",
+ "introToGeneratorDesc": "Deze rondleiding geeft u een introductie op hoog niveau over de belangrijkste functies van de gegevensgenerator. Laten we beginnen!",
+ "invalidSettings": "Ongeldige instellingen!",
+ "language": "Nederlands",
+ "lastEdited": "laatst gewijzigd op",
+ "lastLoggedIn": "Laatst ingelogd Op",
+ "lastModified": "Datum laatste wijziging",
+ "lastName": "Achternaam",
+ "lastSaved": "laatst opgeslagen op",
+ "lineWrapping": "Lijnomwikkeling",
+ "linkToDataSet": "Link naar dataset",
+ "linkToThisDataSet": "Link naar deze dataset",
+ "live": "Leven",
+ "load": "Laden",
+ "loading": "Laden...",
+ "login": "Inloggen",
+ "loginToSave": "Om uw datasets op te slaan heeft u eerst een account nodig. Gelieve hieronder in te loggen.",
+ "loginOrRegisterToSave": "Om uw datasets op te slaan heeft u eerst een account nodig. Gelieve hieronder in te loggen of te registreren.",
+ "loginUrlLabel": "Aanmeldings-URL:",
+ "logout": "Uitloggen",
+ "makeDataSetPublicAgreement": "Ik ben ervan op de hoogte dat als ik deze dataset wil delen met anderen ik hem openbaar moet maken.",
+ "metaDescription": "GenerateData.com: free, GNU-licensed, willekeurige klant data generator voor het testen van software",
+ "metaKeywords": "Random Data, Test Data, Sample Data, data generator, generate data",
+ "missingDataSetName": "Voer de naam van uw dataset in",
+ "nameColumn": "Naam kolom",
+ "nameColumnDesc": "Deze kolom krijgt een andere kop, afhankelijk van het exporttype dat u in het voorbeeldvenster hebt geselecteerd (XML, CSV enz.). Het doel van deze kolom is om een identificatie voor de rij te geven die vervolgens kan worden gebruikt in de gegenereerde gegevens. XML gebruikt deze waarde bijvoorbeeld als de XML-knooppuntnaam; JSON gebruikt het als de JSON-eigenschapsnaam; SQL gebruikt het voor de naam van de databasekolom. De toepassing valideert automatisch de waarde die u hier hebt ingevoerd om er zeker van te zijn dat deze geldig is voor het geselecteerde exporttype.",
+ "newDataSet": "Nieuwe dataset",
+ "no": "Nee",
+ "noAccountsCreated": "Er zijn geen accounts aangemaakt.",
+ "noAdditionalSettings": "Geen aanvullende instellingen",
+ "noDataSetsSaved": "U heeft geen gegevenssets opgeslagen.",
+ "noExamplesAvailable": "Geen voorbeelden beschikbaar.",
+ "noExpiry": "Geen vervaldatum",
+ "noHistory": "Nog geen geschiedenis.",
+ "noOptionsAvailable": "Geen opties beschikbaar.",
+ "noSafari": "Sorry, geen Safari!",
+ "northAmerica": "Noord Amerika",
+ "nowLoggedIn": "Je bent ingelogd.",
+ "nowLoggedOut": "Je bent uitgelogd.",
+ "numResults": "Aantal resultaten",
+ "numeric": "Numeriek",
+ "oceania": "Oceanie",
+ "oneTimePasswordLogin": "U bent ingelogd met een eenmalig wachtwoord. Stel nu een nieuw wachtwoord in.",
+ "open": "Open",
+ "options": "Opties",
+ "optionsColumn": "Opties kolom",
+ "optionsColumnDesc1": "Deze kolom bevat alle configuratie-opties voor het gegevenstype van deze rij. Sommige gegevenstypen hebben te veel configuratie-instellingen om in zo'n kleine ruimte weer te geven (bijvoorbeeld land, regio), dus het kan zijn dat ze gewoon een knop in het raster laten zien die een dialoogvenster opent met alle opties.",
+ "optionsColumnDesc2": "Voor het hier geïllustreerde gegevenstype Namen bevat de kolom één veld Creëerbare pil. Dit is een veelgebruikt veldtype in de gegevensgenerator. U kunt items toevoegen door er gewoon in te typen en op te klikken. Het zet die items vervolgens om in verschillende pillen die kunnen worden verwijderd via het \"x\" -pictogram.",
+ "or": "of",
+ "order": "Sorteren",
+ "other": "Anders",
+ "overMaxAnonRows": "Sorry, u kunt slechts %1 rijen genereren. Om grotere hoeveelheden gegevens te genereren, heeft u een gebruikersaccount op deze site nodig.",
+ "panelContents": "Paneel inhoud",
+ "panelContentsDesc": "Het grootste deel van het voorbeeldpaneel bevat een voorbeeld van de gegenereerde gegevens, volgens de gegevens die u hebt gespecificeerd in het rasterpaneel en de geselecteerde instellingen voor Exporttype. Hier worden 5 verschillende gegevenstypen gegenereerd (land, regio, stad, straatadres en post / postcode) in typoscript-indeling.",
+ "panelControls": "Paneelbediening",
+ "panelControlsClearIconDesc": "Met het laatste pictogram in de paneelregelaars kunt u de pagina leegmaken, zodat u opnieuw kunt beginnen.",
+ "panelControlsDesc": "Met de paneelbedieningsknoppen kunt u de raster- en voorbeeldpanelen verbergen / weergeven, wat handig kan zijn als u met beperkte schermruimte werkt. U kunt ook de plaatsing van de twee panelen ten opzichte van elkaar wijzigen (links-rechts, boven-onder) met behulp van het derde pictogram.",
+ "panelTabs": "Paneel tabbladen",
+ "panelTabsDesc": "Ten slotte is het je misschien opgevallen dat het paneel is onderverdeeld in twee tabbladen. Standaard wordt het tabblad Instellingen weergegeven, aangezien u daar waarschijnlijk naartoe wilt. Maar er is ook een tweede tabblad \"Voorbeeld\" dat instellingen bevat voor het wijzigen van het uiterlijk van het voorbeeldpaneel: het thema, regelnummers verbergen / tonen, regelterugloop wijzigen, de tekstgrootte wijzigen en het aantal gegenereerde rijen beheren.",
+ "password": "Wachtwoord",
+ "passwordLabel": "Wachtwoord:",
+ "passwordReset": "Wachtwoord reset",
+ "passwordResetAccountExpired": "Wachtwoord opnieuw instellen - Account verlopen",
+ "passwordResetAccountExpiredDesc": "We hebben zojuist een verzoek ontvangen om uw wachtwoord opnieuw in te stellen, maar uw account is al verlopen.",
+ "passwordResetComplete": "Uw wachtwoord is hersteld. Er is een email naar u verstuurd met uw nieuwe wachtwoord.",
+ "passwordResetEmailContent1": "Uw wachtwoord is hersteld. U kunt het volgende wachtwoord gebruiken om in te loggen: %1",
+ "passwordResetEmailContent2": "Wijzig uw wachtwoord nadat u ingelogd bent.",
+ "passwordResetEmailDesc": "We hebben een verzoek ontvangen om uw wachtwoord opnieuw in te stellen. Deze e-mail bevat een tijdelijk eenmalig wachtwoord dat u kunt gebruiken om in te loggen. Na het inloggen moet u een nieuw wachtwoord instellen.",
+ "passwordResetMsg": "Als de e-mail geregistreerd was, is uw wachtwoord opnieuw ingesteld en heeft u tijdelijke inloggegevens per e-mail ontvangen.",
+ "passwordUpdateInvalidPassword": "Uw huidige wachtwoord is onjuist. Voer opnieuw in.",
+ "passwordUpdated": "Uw wachtwoord is geüpdatet.",
+ "pause": "Pauze",
+ "pleaseConfirm": "Alstublieft bevestigen",
+ "pleaseEnterAll": "Graag alles invullen",
+ "pleaseEnterDataSetName": "Geef een naam voor uw nieuwe dataset.",
+ "pleaseFixErrors": "Los de volgende problemen op en probeer het opnieuw:",
+ "pleaseLogin": "Log alsjeblieft in",
+ "pleaseSelect": "Selecteer alstublieft",
+ "plugins": "Plugins",
+ "pressEnterAddItem": "Druk op enter om een item toe te voegen",
+ "preview": "voorbeeld",
+ "previewPanel": "Voorbeeldpaneel",
+ "previewPanelControlsDesc": "Rechtsboven in het paneel staan twee pictogrammen. Het eerste pictogram ververst het paneel en genereert elke keer verschillende willekeurige gegevens - dit helpt om u een idee te geven van het soort gegevens dat het genereert. Het tweede pictogram sluit het paneel helemaal. U kunt het paneel altijd opnieuw weergeven met behulp van de sectie rechtsonder op de pagina (vink gewoon het selectievakje \"Voorbeeld\" opnieuw aan).",
+ "previewPanelDesc": "Dit paneel geeft u een live voorbeeld van de gegevens die u genereert, terwijl u deze opbouwt via het rasterpaneel. De knop linksboven toont het geselecteerde formaat van de gegevens die worden gegenereerd (bijvoorbeeld SQL, XML, CSV enz.). Om het formaat te wijzigen, klikt u op de knop.",
+ "previewPanelMoreInfo": "Bekijk voor meer informatie over dit paneel de rondleiding door het voorbeeldpaneel.",
+ "previewPanelNoData": "Geen informatie!",
+ "previewPanelTourDesc": "Het voorbeeldvenster geeft u een live, visuele weergave van de gegevens die u genereert terwijl u deze aan het samenstellen bent. Deze rondleiding geeft een korte introductie over hoe het paneel werkt.",
+ "previewRows": "Voorbeeld van rijen",
+ "private": "Privaat",
+ "problemLoadingTour": "Sorry, er is een probleem opgetreden bij het laden van de tour.",
+ "programmingLanguages": "Programmeertalen",
+ "province": "Provincie",
+ "public": "Openbaar",
+ "publicQ": "Openbaar?",
+ "reenterPassword": "Wachtwoord opnieuw invoeren",
+ "refreshPage": "Pagina verversen",
+ "refreshPanel": "Vernieuw paneel",
+ "register": "Registreren",
+ "remainingTime": "Overgebleven tijd:",
+ "requiredField": "verplicht veld",
+ "revertToVersion": "Keer terug naar deze versie",
+ "row": "rij",
+ "rowLabel": "Kolomtitel",
+ "rowLabelPlural": "Kolomtitels",
+ "rowNumDesc1": "De eerste kolom in het raster toont het rijnummer. Om de rij opnieuw te ordenen, klikt en sleept u dit element omhoog of omlaag.",
+ "rowNumDesc2": "De koptekstwaarde voor deze kolom toont het totale aantal rijen in uw raster. Dit kan handig zijn als u erg grote gegevenssets heeft en deze te groot is om op het scherm te passen.",
+ "rowNumber": "Rij nummer",
+ "rowSp": "Rij(en)",
+ "rows": "rijen",
+ "rowsGenerated": "Genereerde rijen",
+ "rowsGeneratedPerSecond": "Rijen gegenereerd per seconde",
+ "safariExplanation": "Deze site vereist een up-to-date webbrowser die moderne webstandaarden implementeert. Safari is helaas achterop geraakt bij de andere browsers en zal dit script niet uitvoeren. Gebruik een van de volgende browsers totdat we erachter zijn gekomen hoe we dit kunnen aanpassen. Allemaal gratis te downloaden.",
+ "save": "Opslaan",
+ "saveAs": "Opslaan als",
+ "saveButtonDesc": "Als u een gebruikersaccount op de site heeft, slaat deze knop uw dataset op. Als u niet bent aangemeld, klikt u erop om eerst in te loggen. Het systeem houdt een geschiedenis bij van de laatste %1 wijzigingen in een dataset, zodat u altijd terug kunt gaan en oudere versies kunt bekijken.",
+ "saveDataSetNewName": "Sla dataset op als nieuwe naam",
+ "seconds": "Seconden",
+ "seeHelpDialog": " Zie help venster.",
+ "selectCountry": "Selecteer land",
+ "selectDataType": "Selecteer datatype",
+ "selectEllipsis": "Selecteer ...",
+ "selectExpiryDate": "Selecteer vervaldatum",
+ "selectLanguage": "Selecteer taal",
+ "sendEmail": "E-mail verzenden",
+ "seriouslySlow": "serieus traag",
+ "settings": "Instellingen",
+ "showGrid": "Raster tonen",
+ "showLineNumbers": "Toon regelnummers",
+ "showPreview": "Voorbeeld weergeven",
+ "siteLogo": "logo van de site",
+ "size": "Grootte:",
+ "southAmerica": "Zuid Amerika",
+ "status": "Toestand",
+ "stripWhitespace": "strip witruimte van gegenereerde inhoud",
+ "success": "success",
+ "text": "Tekst",
+ "textSize": "Lettergrootte",
+ "theDataSetName": "De naam van de dataset",
+ "theExportTypeBtn": "De Export Type knop",
+ "theGenerateButton": "De knop Genereren",
+ "theGridPanel": "Het rasterpaneel",
+ "thePreviewPanel": "Het voorbeeldvenster",
+ "theSaveButton": "De knop Opslaan",
+ "theme": "Thema",
+ "togglePanelLayout": "Schakelen tussen paneelindeling",
+ "totalNumGeneratedRows": "Totaal aantal gegenereerde rijen",
+ "tourComplete": "Tour voltooid",
+ "tourCompleteDesc": "Helemaal klaar! Kies een van de onderstaande knoppen om door te gaan.",
+ "tourIntroPara1": "Deze tool biedt veel functionaliteit en kan in het begin een beetje overweldigend zijn. Klik op een van de knoppen aan de rechterkant om een rondleiding over dat specifieke kenmerk te volgen.",
+ "tourIntroPara2": "Merk op dat wanneer de tour begint, deze tijdelijk de inhoud van de generatorpagina overschrijft om bepaalde dingen over de interface te illustreren. Maar maak je geen zorgen! Zodra de tour is beëindigd, worden uw gegevens teruggezet naar de oorspronkelijke staat.",
+ "tryDifferentTour": "Probeer een andere tour",
+ "update": "Bijwerken",
+ "updateAccount": "Update gebruiker",
+ "user": "Gebruiker",
+ "userAccount": "Gebruikersaccount",
+ "userAccountNotFound": "Sorry, we konden je account niet vinden.",
+ "userAccountUpdated": "Het gebruikersaccount is bijgewerkt.",
+ "userAccounts": "Gebruikersaccounts",
+ "userNotFound": "Sorry, we kunnen u niet identificeren.",
+ "validationAccountAlreadyExists": "Sorry, een account met dit emailadres bestaat al.",
+ "validationDifferentNewPasswords": "Zorg ervoor dat uw nieuwe wachtwoorden overeenkomen",
+ "validationInvalidEmail": "Voer alstublieft een geldig emailadres in.",
+ "validationNoCurrentPassword": "Voer uw huidige wachtwoord in",
+ "validationNoEmail": "Voer alsublieft uw emailadres in.",
+ "validationNoFirstName": "Wat is uw voornaam?",
+ "validationNoLastName": "Wat is uw achternaam?",
+ "validationNoPassword": "Wat is uw wachtwoord?",
+ "version": "Versie",
+ "view": "Visie",
+ "viewChangelog": "Bekijk changelog",
+ "viewOnGithub": "Bekijk op Github",
+ "welcomeToTheGenerator": "Welkom bij de generator!",
+ "yes": "Ja",
+ "yourAccount": "Uw gebruikersnaam",
+ "yourAccountUpdated": "Uw gebruikersaccount is bijgewerkt.",
+ "yourUserAccount": "Uw gebruikersaccount"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/pt.json b/apps/client/src/i18n/pt.json
new file mode 100644
index 000000000..791960db4
--- /dev/null
+++ b/apps/client/src/i18n/pt.json
@@ -0,0 +1,329 @@
+{
+ "abort": "Abortar",
+ "about": "Sobre",
+ "aboutInfoPara1": "generatedata.com é um projeto de código aberto que criei no final da era paleolítica (por volta de 2004) e teve muitos, muitos contribuidores ao longo de sua vida. A cada poucos anos eu o reescrevo usando as melhores e mais recentes tecnologias disponíveis: esta versão foi concluída em 2021, feita com React, Redux, Typescript, GraphQL, Docker, web workers, elásticos, fita adesiva e um monte de xingamentos.",
+ "aboutInfoPara2": "Você pode encontrar o projeto no github. Faça o download, faça um fork ou sinta-se à vontade para contribuir. Aproveitar!",
+ "aboutThisScript": "Sobre este script",
+ "accountCreated": "Conta criada",
+ "accountCreatedDesc": "A conta foi criada.",
+ "accountCreatedMsg": "Uma conta foi criada para você.",
+ "accountDeleted": "A conta foi excluída.",
+ "accountDisabled": "Conta desativada",
+ "accountExpiredMsg": "Desculpe, sua conta expirou.",
+ "accountExpiryDate": "Data de expiração da conta",
+ "accountInfo": "Informações da Conta",
+ "accountSettings": "Configurações da conta",
+ "accountType": "Tipo de conta",
+ "accounts": "Contas",
+ "add": "Adicionar",
+ "addRowsDesc": "Para adicionar mais linhas à grade de dados, basta usar o formulário na parte inferior da página. Quando você tem muitos campos, esta seção pode ficar oculta fora da página, então você terá que rolar o painel até a parte inferior para ver o formulário.",
+ "addSomeDataDesc": "Adicione algumas linhas na grade.",
+ "admin": "Admin",
+ "adminAccount": "Conta de administrador",
+ "administrator": "Administradora",
+ "africa": "África",
+ "allCountries": "Todos os países",
+ "allDataTypePlugins": "Todos os plug-ins de tipo de dados",
+ "allExportTypePlugins": "Todos os plug-ins de tipo de exportação",
+ "anonymousAccess": "Acesso anônimo",
+ "asia": "Ásia",
+ "back": "de volta",
+ "backToLogin": "Volte ao login",
+ "blog": "Blog",
+ "cancel": "cancelar",
+ "centralAmerica": "América Central",
+ "changePassword": "Mudar senha",
+ "clear": "Clara",
+ "clearPage": "Limpar página",
+ "clearPageConfirmation": "Tem certeza de que deseja limpar a página? Todas as alterações feitas serão perdidas.",
+ "clearThePage": "Limpe a página",
+ "clickExportTypeBtn": "A próxima etapa mostra o que acontece quando você clica no botão.",
+ "close": "Fechar",
+ "closePanel": "Fechar painel",
+ "columns": "Colunas",
+ "columnsDesc": "A grade mostrará quantas dessas colunas puder, dependendo do tamanho de tela disponível. Quando é muito pequeno, ele esconde algumas colunas e mostra um ícone de engrenagem que abre um infotip com o conteúdo ausente. As próximas etapas explicam cada uma das colunas, usando a primeira linha Nomes como demonstração.",
+ "complete": "Completa",
+ "confirmDeleteAccount": "Tem certeza de que deseja excluir esta conta?",
+ "confirmDeleteUserAccount": "Tem certeza de que deseja excluir esta conta de usuário?",
+ "confirmEmptyForm": "Tem certeza de que deseja limpar a página?",
+ "continue": "Prosseguir",
+ "copiedToClipboard": "Copiado para a área de transferência",
+ "copyToClipboard": "Copiar para área de transferência",
+ "coreExportType": "Formatos principais",
+ "countries": "Países",
+ "country": "País",
+ "countrySpecific": "País específico",
+ "cpuMeltinglyFast": "CPU derretedoramente rápido",
+ "createAccount": "Criar Conta",
+ "currentPassword": "Senha atual",
+ "currentVersion": "Versão atual sendo editada",
+ "dataGenerated": "Dados gerados.",
+ "dataSet": "Conjunto de Dados",
+ "dataSetHelp": "É aqui que você define exatamente que tipo de dados deseja gerar. Tente preencher uma ou duas linhas e clique no botão gerar. Você vai pegar o jeito bem rápido.",
+ "dataSetLoaded": "O conjunto de dados foi carregado.",
+ "dataSetName": "Nome do conjunto de dados",
+ "dataSetNameDesc": "Clique no texto aqui para nomear seu conjunto de dados. Observe que você precisará de uma conta no site e de estar logado para nomear seus conjuntos de dados. Quando você salva seus conjuntos de dados, este nome fornece um rótulo conveniente para controlá-los.",
+ "dataSetNameUpdated": "O nome do conjunto de dados foi atualizado.",
+ "dataSetOptions": "Opções de conjunto de dados",
+ "dataSetReverted": "Seu conjunto de dados foi revertido para a versão mais antiga selecionada.",
+ "dataSetSaved": "Seu conjunto de dados foi salvo.",
+ "dataSets": "Conjuntos de dados",
+ "dataType": "Tipo de dados",
+ "dataTypeDesc": "Este é um menu suspenso que contém todos os tipos de dados disponíveis. Quando você seleciona um valor, ele atualiza o restante das colunas dessa linha. Cada tipo de dados tem suas próprias configurações exclusivas para permitir a personalização.",
+ "dataTypes": "Tipos de dados",
+ "dateAccountCreated": "Data de criação da conta",
+ "dateCreated": "Data Criada",
+ "defaultLanguage": "Idioma padrão",
+ "delete": "Excluir",
+ "delete1DataSet": "Excluir 1 conjunto de dados",
+ "deleteAccount": "Deletar conta",
+ "deleteDataSet": "Excluir conjunto de dados",
+ "deleteDataSetConfirm": "Tem certeza de que deseja excluir este conjunto de dados?",
+ "deleteRow": "Apagar linha",
+ "deleteRowDesc": "A última coluna contém um ícone de exclusão. Clicar nele excluirá a linha.",
+ "developer": "Desenvolvedora",
+ "developerDoc": "Doc de desenvolvedor",
+ "disabled": "Desabilitada",
+ "documentation": "Documentação",
+ "download": "Download",
+ "edit": "Editar",
+ "editAccount": "Editar conta",
+ "editExportTypePage": "Quando você clica no botão Tipo de exportação, uma exibição de página inteira aparece contendo as opções de configuração à esquerda e o painel de visualização à direita.",
+ "editExportTypePageConfig": "Vejamos as opções de configuração.",
+ "editExportTypeSettings": "Edite suas configurações de tipo de exportação.",
+ "editingExportTypes": "Editando Tipos de Exportação",
+ "email": "O email",
+ "emailFooterDisclaimer": "Se você recebeu este e-mail por engano, entre em contato com o administrador do site.",
+ "emailIntroLineWithName": "Olá %1,",
+ "emailLabel": "O email:",
+ "emailNotSent": "Não foi possível enviar a notificação por e-mail.",
+ "emailUserLoginInfo": "Enviar e-mail ao usuário com suas informações de login",
+ "enterEmailAddressToResetPassword": "Digite seu email abaixo para redefinir sua senha.",
+ "enterUserAccountDetails": "Por favor, insira os detalhes da sua conta de usuário abaixo.",
+ "errorCreatingAccount": "Ocorreu um erro ao criar esta conta.",
+ "estimatedSize": "Tamanho estimado:",
+ "estimatedTime": "Tempo estimado:",
+ "europe": "Europa",
+ "example": "Exemplo",
+ "exampleColumn": "Coluna de exemplo",
+ "exampleColumnDesc": "Esta coluna pode ou não ter conteúdo, dependendo do tipo de dados que você selecionou para a linha. É apenas uma maneira conveniente para alguns tipos de dados fornecerem exemplos predefinidos de como eles podem ser usados. Por exemplo, a coluna Nomes fornece uma lista suspensa de diferentes formatos de nome. Para todos os tipos de dados, você ainda pode configurá-lo manualmente na coluna Opções: é realmente um dispositivo que economiza tempo para que você se atualize o mais rápido possível.",
+ "examples": "Exemplos",
+ "exit": "Saída",
+ "expired": "Expirada",
+ "expiryDate": "Data de validade",
+ "exportType": "Tipo de Exportação",
+ "exportTypeBtnDesc": "O botão no canto superior esquerdo do painel é onde você seleciona e configura o formato dos dados que deseja gerar. Aqui está definido como Typescript.",
+ "exportTypeOptionsSql": "Opções de tipo de exportação: SQL",
+ "exportTypeOptionsSqlDesc": "Como você pode ver, o SQL tem muitos outros campos que são específicos para este tipo de exportação. Outros tipos de exportação têm configurações diferentes.",
+ "exportTypeOptionsTs": "Opções de tipo de exportação: texto datilografado",
+ "exportTypeOptionsTsDesc": "Dependendo do tipo de exportação selecionado, o corpo do painel pode conter opções adicionais. Aqui, com Typescript, existem dois campos, permitindo que você personalize o nome do tipo e o nome da variável exportada.",
+ "exportTypeOptionsTsDesc2": "Agora vamos dar uma olhada no SQL.",
+ "exportTypeSelection": "Seleção de tipo de exportação",
+ "exportTypeSelectionDesc": "A lista suspensa Formato permite que você escolha o formato do conteúdo gerado. Existem muitas opções, algumas delas os tipos principais: SQL, CSV, SQL, JSON e outras, e algumas delas são para gerar dados para linguagens de programação específicas.",
+ "exportTypes": "Tipos de exportação",
+ "filterDataTypes": "Filtrar Tipos de Dados",
+ "financial": "Financeira",
+ "firstName": "Primeiro nome",
+ "forgottenYourPasswordQ": "Esqueceu a senha?",
+ "format": "Formato",
+ "generate": "Gerar",
+ "generateBtnDesc": "Depois de terminar de configurar seu conjunto de dados e confirmar que está com a aparência desejada, é hora de gerar alguns dados em volume! Clique neste botão para gerar seus dados. Dependendo do número de linhas na grade de dados e do número de linhas que você deseja gerar, isso pode levar algum tempo.",
+ "generated": "Gerada",
+ "generatedC": "Gerada:",
+ "generator": "Gerador",
+ "geo": "Geo",
+ "grid": "Rede",
+ "gridPanelTourDesc1": "O painel de grade é onde você constrói os dados que deseja gerar. Aqui você pode escolher entre os diferentes tipos de dados, como eles são configurados e a ordem em que aparecem. No exemplo aqui, temos cinco campos: Nome, Telefone, Email, Endereço e Cidade.",
+ "gridPanelTourDesc2": "Consulte o tour no Painel de grade para obter informações mais detalhadas sobre esta seção.",
+ "gridPanelTourIntroDesc1": "O painel de grade é onde você define quais dados deseja gerar. Para este tour, ocultamos o painel de visualização (consulte o botão \"Visualizar\" desmarcado no canto inferior direito da página) para nos dar um pouco mais de espaço. Também adicionamos 10 linhas de diferentes tipos de dados, apenas para fins de ilustração.",
+ "gridPanelTourIntroDesc2": "Vamos começar com as colunas da grade.",
+ "help": "Ajuda",
+ "helpIcon": "Ícone de ajuda",
+ "helpIconDesc": "Ao selecionar um Tipo de dados na coluna anterior, um ícone aparecerá aqui. Clicar nele abre uma caixa de diálogo de ajuda contendo informações de uso.",
+ "hide": "Esconder",
+ "hideShowGrid": "Ocultar / mostrar grade",
+ "hideShowPreviewPanel": "Ocultar / mostrar painel de visualização",
+ "history": "História",
+ "historyPanelDesc": "Este painel mostra o histórico de mudanças feitas no conjunto de dados. Clique nos botões Exibir para examinar as versões anteriores. Para reverter para uma versão anterior, basta visualizá-la e clicar no botão \"Reverter para esta versão\".",
+ "hostName": "Nome de anfitrião",
+ "humanData": "Dados Humanos",
+ "ifWantToReregister": "Se você deseja se registrar novamente, visite:",
+ "incomplete": "Incompleta",
+ "install": "Install",
+ "introToGenerator": "Introdução ao gerador",
+ "introToGeneratorDesc": "Este tour oferece uma introdução de alto nível aos principais recursos do Data Generator. Vamos começar!",
+ "invalidSettings": "Configurações inválidas!",
+ "language": "inglês",
+ "lastEdited": "última edição",
+ "lastLoggedIn": "Último login",
+ "lastModified": "Última modificação",
+ "lastName": "Último nome",
+ "lastSaved": "salvo pela última vez",
+ "lineWrapping": "Quebra de linha",
+ "linkToDataSet": "Link para o conjunto de dados",
+ "linkToThisDataSet": "Link para este conjunto de dados",
+ "live": "Ao vivo",
+ "load": "Carga",
+ "loading": "Carregando...",
+ "login": "Conecte-se",
+ "loginToSave": "Para salvar seus conjuntos de dados, primeiro você precisa de uma conta. Faça o login abaixo.",
+ "loginOrRegisterToSave": "Para salvar seus conjuntos de dados, primeiro você precisa de uma conta. Por favor, faça login ou cadastre-se abaixo.",
+ "loginUrlLabel": "URL de login:",
+ "logout": "Sair",
+ "makeDataSetPublicAgreement": "Eu entendo que, para compartilhar este conjunto de dados, preciso torná-lo público.",
+ "metaDescription": "Eu entendo que, para compartilhar este conjunto de dados, preciso torná-lo público.",
+ "metaKeywords": "Eu entendo que, para compartilhar este conjunto de dados, preciso torná-lo público.",
+ "missingDataSetName": "Por favor, insira o nome do seu conjunto de dados",
+ "nameColumn": "Coluna de nome",
+ "nameColumnDesc": "Esta coluna obtém um título diferente dependendo do Tipo de exportação selecionado no painel de visualização (XML, CSV etc.). O objetivo desta coluna é fornecer um identificador para a linha que pode então ser usado nos dados gerados. Por exemplo, XML usa esse valor como o nome do nó XML; JSON o usa como o nome da propriedade JSON; O SQL o usa para o nome da coluna do banco de dados. O aplicativo validará automaticamente o valor inserido aqui para garantir que seja válido para o tipo de exportação selecionado.",
+ "newDataSet": "Novo Conjunto de Dados",
+ "no": "Não",
+ "noAccountsCreated": "Nenhuma conta foi criada.",
+ "noAdditionalSettings": "Sem configurações adicionais.",
+ "noDataSetsSaved": "Você não tem conjuntos de dados salvos.",
+ "noExamplesAvailable": "Nenhum exemplo disponível.",
+ "noExpiry": "Sem validade",
+ "noHistory": "Sem história ainda.",
+ "noOptionsAvailable": "Sem opções disponíveis.",
+ "noSafari": "Desculpe, não há Safari!",
+ "northAmerica": "América do Norte",
+ "nowLoggedIn": "Você está logado.",
+ "nowLoggedOut": "Você foi desconectado.",
+ "numResults": "Resultados numéricos",
+ "numeric": "Numérica",
+ "oceania": "Oceânia",
+ "oneTimePasswordLogin": "Você fez login com uma senha de uso único. Defina um novo agora ou você não poderá fazer o login novamente.",
+ "open": "Abrir",
+ "options": "Opções",
+ "optionsColumn": "Coluna de opções",
+ "optionsColumnDesc1": "Esta coluna contém todas as opções de configuração para o tipo de dados desta linha. Alguns tipos de dados têm muitas definições de configuração para mostrar em um espaço tão pequeno (por exemplo, país, região), então eles podem mostrar apenas um botão na grade que abre uma caixa de diálogo contendo todas as opções.",
+ "optionsColumnDesc2": "Para o tipo de dados de nomes ilustrado aqui, a coluna contém um único campo Creatable Pill. Este é um tipo de campo comum no Data Generator. Ele permite que você adicione itens apenas digitando e clicando em . Em seguida, ele converte esses itens em pílulas distintas que podem ser removidas por meio do ícone \"x\".",
+ "or": "ou",
+ "order": "Pedida",
+ "other": "De outros",
+ "overMaxAnonRows": "Desculpe, você só pode gerar %1 linhas. Para gerar grandes volumes de dados, você precisará de uma conta de usuário neste site.",
+ "panelContents": "Conteúdo do painel",
+ "panelContentsDesc": "A maior parte do painel de visualização contém uma amostra dos dados gerados, de acordo com os dados que você especificou no painel de grade e as configurações de Tipo de exportação selecionadas. Aqui está gerando 5 tipos de dados diferentes (País, Região, Cidade, Endereço e Postal / CEP) em formato de texto digitado.",
+ "panelControls": "Controles do painel",
+ "panelControlsClearIconDesc": "O último ícone nos controles do painel permite limpar a página para que você possa começar do zero.",
+ "panelControlsDesc": "Os botões de controle do painel permitem ocultar / mostrar os painéis Grade e Visualização, o que pode ser útil se você estiver trabalhando com espaço limitado na tela. Você também pode alternar o posicionamento dos dois painéis em relação um ao outro (esquerda-direita, topo-base) usando o terceiro ícone.",
+ "panelTabs": "Abas do painel",
+ "panelTabsDesc": "Por último, você deve ter notado que o painel está organizado em duas guias. Por padrão, ele mostra a guia Configurações, já que é onde você provavelmente deseja ir. Mas também há uma segunda guia \"Visualização\" que contém configurações para alterar a aparência do painel de visualização: o tema, ocultar / mostrar os números das linhas, alternar a quebra de linha, alterar o tamanho do texto e controlar o número de linhas geradas.",
+ "password": "Senha",
+ "passwordLabel": "Senha:",
+ "passwordReset": "Redefinição de senha",
+ "passwordResetAccountExpired": "Redefinição de senha - conta expirada",
+ "passwordResetAccountExpiredDesc": "Acabamos de receber uma solicitação para redefinir sua senha, mas sua conta já expirou.",
+ "passwordResetComplete": "Sua senha foi redefinida e você recebeu um e-mail com a nova senha.",
+ "passwordResetEmailContent1": "Sua senha foi alterada. Você pode usar a seguinte senha para fazer o login: %1",
+ "passwordResetEmailContent2": "Por favor, altere-o depois de fazer login.",
+ "passwordResetEmailDesc": "Recebemos uma solicitação para redefinir sua senha. Este e-mail contém uma senha única temporária que você pode usar para fazer login. Depois de fazer login, você precisará definir uma nova senha.",
+ "passwordResetMsg": "Se o e-mail estava em arquivo, sua senha foi redefinida e você recebeu um e-mail com informações de login temporárias.",
+ "passwordUpdateInvalidPassword": "Sua senha atual está incorreta. Por favor, entre novamente.",
+ "passwordUpdated": "Sua senha foi atualizada.",
+ "pause": "Pausa",
+ "pleaseConfirm": "Por favor confirme",
+ "pleaseEnterAll": "Por favor, insira tudo",
+ "pleaseEnterDataSetName": "Insira o nome do novo conjunto de dados.",
+ "pleaseFixErrors": "Corrija os seguintes erros e reenvie:",
+ "pleaseLogin": "Por favor entre",
+ "pleaseSelect": "Por favor selecione",
+ "plugins": "Plugins",
+ "pressEnterAddItem": "Pressione Enter para adicionar o item",
+ "preview": "Antevisão",
+ "previewPanel": "Painel de visualização",
+ "previewPanelControlsDesc": "No canto superior direito do painel, existem dois ícones. O primeiro ícone atualiza o painel, gerando diferentes dados aleatórios a cada vez - isso ajuda a dar uma ideia do tipo de dados que ele gera. O segundo ícone fecha o painel completamente. Você sempre pode mostrar o painel novamente usando a seção no canto inferior direito da página (basta selecionar novamente a caixa de seleção \"Visualizar\").",
+ "previewPanelDesc": "Este painel fornece uma visualização ao vivo dos dados que você está gerando, enquanto você os constrói por meio do painel de grade. O botão no canto superior esquerdo mostra o formato selecionado dos dados sendo gerados (por exemplo, SQL, XML, CSV etc.). Para alterar o formato, basta clicar no botão.",
+ "previewPanelMoreInfo": "Para obter mais informações sobre este painel, confira o tour do Painel de visualização.",
+ "previewPanelNoData": "Sem dados!",
+ "previewPanelTourDesc": "O painel de visualização oferece uma representação visual ao vivo dos dados que você está gerando enquanto os constrói. Este tour fornece uma introdução rápida de como o painel funciona.",
+ "previewRows": "Linhas de visualização",
+ "private": "Privada",
+ "problemLoadingTour": "Ocorreu um problema ao carregar o tour.",
+ "programmingLanguages": "Linguagens de programação",
+ "province": "Província",
+ "public": "Pública",
+ "publicQ": "Pública?",
+ "reenterPassword": "Digite novamente a senha",
+ "refreshPage": "Atualizar a página",
+ "refreshPanel": "Painel de atualização",
+ "register": "Registro",
+ "remainingTime": "Tempo restante:",
+ "requiredField": "Campo obrigatório",
+ "revertToVersion": "Reverter para esta versão",
+ "row": "Linha",
+ "rowLabel": "Título da Coluna",
+ "rowLabelPlural": "Títulos de Coluna",
+ "rowNumDesc1": "A primeira coluna da grade mostra o número da linha. Para reordenar a linha, basta clicar e arrastar este elemento para cima ou para baixo.",
+ "rowNumDesc2": "O valor do cabeçalho para esta coluna mostra o número total de linhas em sua grade. Isso pode ser útil quando você tem conjuntos de dados muito grandes e eles são grandes demais para caber na tela.",
+ "rowNumber": "Número da linha",
+ "rowSp": "Linha (s)",
+ "rows": "filas",
+ "rowsGenerated": "Linhas Geradas",
+ "rowsGeneratedPerSecond": "Linhas geradas por segundo",
+ "safariExplanation": "Este site requer um navegador da web atualizado que implemente padrões da web modernos. Infelizmente, o Safari ficou para trás em relação aos outros navegadores e não executa este script. Até descobrirmos como acomodá-lo, use um dos seguintes navegadores. Tudo grátis para download.",
+ "save": "Salvar",
+ "saveAs": "Salvar como",
+ "saveButtonDesc": "Se você tiver uma conta de usuário no site, este botão salvará seu conjunto de dados. Se você não estiver conectado, clicar nele solicitará que você faça o login primeiro. O sistema mantém um histórico das últimas alterações %1 em um conjunto de dados, para que você possa sempre voltar e ver as versões anteriores.",
+ "saveDataSetNewName": "Salvar conjunto de dados com um novo nome",
+ "seconds": "Segundas",
+ "seeHelpDialog": "Veja a caixa de diálogo de ajuda.",
+ "selectCountry": "Selecione o pais",
+ "selectDataType": "Selecione o tipo de dados",
+ "selectEllipsis": "Selecione ...",
+ "selectExpiryDate": "Selecione a data de validade",
+ "selectLanguage": "Selecione o idioma",
+ "sendEmail": "Enviar email",
+ "seriouslySlow": "Muito lento",
+ "settings": "Definições",
+ "showGrid": "Mostre as grades",
+ "showLineNumbers": "Mostrar números de linha",
+ "showPreview": "Mostrar Antevisão",
+ "siteLogo": "logotipo do site",
+ "size": "Tamanho:",
+ "southAmerica": "América do Sul",
+ "status": "Status",
+ "stripWhitespace": "retire os espaços em branco do conteúdo gerado",
+ "success": "sucesso",
+ "text": "Texto",
+ "textSize": "Tamanho do texto",
+ "theDataSetName": "O nome do conjunto de dados",
+ "theExportTypeBtn": "O botão Tipo de Exportação",
+ "theGenerateButton": "O Botão Gerar",
+ "theGridPanel": "O Painel de Grade",
+ "thePreviewPanel": "O painel de visualização",
+ "theSaveButton": "O botão Salvar",
+ "theme": "Tema",
+ "togglePanelLayout": "Alternar layout do painel",
+ "totalNumGeneratedRows": "Número total de linhas geradas",
+ "tourComplete": "Tour Completo",
+ "tourCompleteDesc": "Tudo feito! Escolha um dos botões abaixo para continuar.",
+ "tourIntroPara1": "Esta ferramenta oferece muitas funcionalidades e pode ser um pouco opressora no início. Clique em um dos botões à direita para fazer um tour sobre esse recurso específico.",
+ "tourIntroPara2": "Observe que, quando o tour começa, ele substitui temporariamente o conteúdo da página do gerador para ilustrar certas coisas sobre a interface. Mas não se preocupe! Assim que o passeio termina, ele retorna seus dados ao seu estado original.",
+ "tryDifferentTour": "Experimente um passeio diferente",
+ "update": "Atualizar",
+ "updateAccount": "Atualizar conta",
+ "user": "Do utilizador",
+ "userAccount": "Conta de usuário",
+ "userAccountNotFound": "Não foi possível encontrar sua conta.",
+ "userAccountUpdated": "A conta do usuário foi atualizada.",
+ "userAccounts": "Contas de usuário",
+ "userNotFound": "Desculpe, não foi possível identificar você. Por favor, verifique suas credenciais.",
+ "validationAccountAlreadyExists": "Desculpe, já existe uma conta com esse endereço de e-mail",
+ "validationDifferentNewPasswords": "Certifique-se de que suas senhas sejam iguais",
+ "validationInvalidEmail": "Por favor insira um endereço de e-mail válido",
+ "validationNoCurrentPassword": "Por favor insira sua senha atual",
+ "validationNoEmail": "Por favor, indique o seu endereço de e-mail",
+ "validationNoFirstName": "por favor entre com seu primeiro nome",
+ "validationNoLastName": "Por favor insira seu sobrenome",
+ "validationNoPassword": "Por favor, insira sua senha",
+ "version": "Versão",
+ "view": "Visualizar",
+ "viewChangelog": "Visualizar registro de alteração",
+ "viewOnGithub": "Ver no Github",
+ "welcomeToTheGenerator": "Bem-vindo ao gerador!",
+ "yes": "sim",
+ "yourAccount": "Sua conta",
+ "yourAccountUpdated": "Sua conta foi atualizada.",
+ "yourUserAccount": "Sua conta de usuário"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/ru.json b/apps/client/src/i18n/ru.json
new file mode 100644
index 000000000..e6ae6f481
--- /dev/null
+++ b/apps/client/src/i18n/ru.json
@@ -0,0 +1,330 @@
+{
+ "abort": "Прервать",
+ "about": "О",
+ "aboutInfoPara1": "generateata.com — это проект с открытым исходным кодом, который я создал еще в эпоху позднего палеолита (около 2004 г.), и за время своего существования у него было много участников. Каждые несколько лет я переписываю его с использованием новейших и лучших доступных технологий: эта версия была завершена в 2021 году, сделана с помощью React, Redux, Typescript, GraphQL, Docker, веб-воркеров, резинок, изоленты и множества ругательств.",
+ "aboutInfoPara2": "Вы можете найти проект на github. Загрузите его, разветвите его или не стесняйтесь вносить свой вклад. Наслаждаться!",
+ "aboutThisScript": "Об этом скрипте",
+ "accountCreated": "Аккаунт создан",
+ "accountCreatedDesc": "Аккаунт создан.",
+ "accountCreatedMsg": "Для вас создана учетная запись.",
+ "accountDeleted": "Аккаунт удален.",
+ "accountDisabled": "Учетная запись отключена",
+ "accountExpiredMsg": "Извините, срок действия вашей учетной записи истек.",
+ "accountExpiryDate": "Дата истечения срока действия учетной записи",
+ "accountInfo": "Информация об аккаунте",
+ "accountSettings": "Настройки учетной записи",
+ "accountType": "Тип аккаунта",
+ "accounts": "учетные записи",
+ "add": "Добавлять",
+ "addRowsDesc": "Чтобы добавить больше строк в сетку данных, просто используйте форму внизу страницы. Если у вас много полей, этот раздел может быть скрыт за пределами страницы, поэтому вам придется прокрутить панель вниз, чтобы увидеть форму.",
+ "addSomeDataDesc": "Добавьте несколько строк в сетку.",
+ "admin": "Администратор",
+ "adminAccount": "Учетная запись администратора",
+ "administrator": "Администратор",
+ "africa": "Африка",
+ "allCountries": "Все страны",
+ "allDataTypePlugins": "Все плагины типов данных",
+ "allExportTypePlugins": "Все плагины типа экспорта",
+ "anonymousAccess": "Анонимный доступ",
+ "asia": "Азия",
+ "back": "назад",
+ "backToLogin": "Вернуться на страницу авторизации",
+ "blog": "Блог",
+ "cancel": "отменить",
+ "centralAmerica": "Центральная Америка",
+ "changePassword": "Изменить пароль",
+ "clear": "чистый",
+ "clearPage": "Очистить страницу",
+ "clearPageConfirmation": "Вы уверены, что хотите очистить страницу? Любые внесенные вами изменения будут потеряны.",
+ "clearThePage": "Очистить страницу",
+ "clickExportTypeBtn": "Следующий шаг показывает, что происходит, когда вы нажимаете кнопку.",
+ "close": "Закрывать",
+ "closePanel": "Закрыть панель",
+ "columns": "Столбцы",
+ "columnsDesc": "В сетке будет отображаться столько столбцов, сколько возможно, в зависимости от доступного размера экрана. Когда он слишком мал, он скроет некоторые столбцы и покажет значок шестеренки, который открывает информационную подсказку с отсутствующим содержимым. Следующие несколько шагов объясняют каждый из столбцов, используя первую строку Names в качестве демонстрации.",
+ "complete": "Полный",
+ "confirmDeleteAccount": "Вы уверены, что хотите удалить этот аккаунт?",
+ "confirmDeleteUserAccount": "Вы уверены, что хотите удалить эту учетную запись пользователя?",
+ "confirmEmptyForm": "Вы уверены, что хотите очистить страницу?",
+ "continue": "Продолжать",
+ "copiedToClipboard": "Скопировано в буфер обмена",
+ "copyToClipboard": "Скопировать в буфер обмена",
+ "coreExportType": "Основные форматы",
+ "countries": "Страны",
+ "country": "Страна",
+ "countrySpecific": "Для конкретной страны",
+ "cpuMeltinglyFast": "Очень быстро",
+ "createAccount": "Зарегистрироваться",
+ "currentPassword": "Текущий пароль",
+ "currentVersion": "Текущая версия редактируется",
+ "dataGenerated": "Сгенерированные данные.",
+ "dataSet": "Набор данных",
+ "dataSetHelp": "Здесь вы точно определяете, какие данные вы хотите генерировать. Попробуйте заполнить строку или две и нажмите кнопку «Создать». Вы довольно быстро освоитесь.",
+ "dataSetLoaded": "Набор данных загружен.",
+ "dataSetName": "Имя набора данных",
+ "dataSetNameDesc": "Нажмите на текст здесь, чтобы назвать свой набор данных. Обратите внимание, что вам потребуется учетная запись на сайте и вход в систему, чтобы назвать ваши наборы данных. Когда вы сохраняете свои наборы данных, это имя дает вам удобную метку для их отслеживания.",
+ "dataSetNameUpdated": "Имя набора данных обновлено.",
+ "dataSetOptions": "Параметры набора данных",
+ "dataSetReverted": "Ваш набор данных был возвращен к выбранной старой версии.",
+ "dataSetSaved": "Ваш набор данных сохранен.",
+ "dataSets": "Наборы данных",
+ "dataType": "Тип данных",
+ "dataTypeDesc": "Это раскрывающийся список, содержащий все доступные типы данных. Когда вы выберете значение, оно обновит оставшиеся столбцы для этой строки. Каждый тип данных имеет свои уникальные настройки, которые можно настроить.",
+ "dataTypes": "Типы данных",
+ "dateAccountCreated": "Дата создания учетной записи",
+ "dateCreated": "Дата создания",
+ "defaultLanguage": "Язык по умолчанию",
+ "delete": "Удалить",
+ "delete1DataSet": "Удалить 1 набор данных",
+ "deleteAccount": "Удалить аккаунт",
+ "deleteDataSet": "Удалить набор данных",
+ "deleteDataSetConfirm": "Вы уверены, что хотите удалить этот набор данных?",
+ "deleteRow": "Удалить строку",
+ "deleteRowDesc": "Последний столбец содержит значок удаления. При нажатии на нее строка будет удалена.",
+ "developer": "Разработчик",
+ "developerDoc": "Документ разработчика",
+ "disabled": "Неполноценный",
+ "documentation": "Документация",
+ "download": "Скачать",
+ "edit": "Редактировать",
+ "editAccount": "Редактировать аккаунт",
+ "editExportTypePage": "Когда вы нажимаете кнопку «Тип экспорта», появляется полностраничное представление, содержащее параметры конфигурации слева и панель предварительного просмотра справа.",
+ "editExportTypePageConfig": "Давайте посмотрим на параметры конфигурации.",
+ "editExportTypeSettings": "Измените настройки типа экспорта.",
+ "editingExportTypes": "Редактирование типов экспорта",
+ "email": "Эл. адрес",
+ "emailFooterDisclaimer": "Если вы получили это письмо по ошибке, обратитесь к администратору сайта.",
+ "emailIntroLineWithName": "Привет %1,",
+ "emailLabel": "Эл. адрес:",
+ "emailNotSent": "Нам не удалось отправить уведомление по электронной почте.",
+ "emailUserLoginInfo": "Отправьте пользователю электронное письмо с данными для входа",
+ "enterEmailAddressToResetPassword": "Введите свой адрес электронной почты ниже, чтобы сбросить пароль.",
+ "enterUserAccountDetails": "Введите данные своей учетной записи ниже.",
+ "errorCreatingAccount": "При создании этой учетной записи произошла ошибка.",
+ "estimatedSize": "Предполагаемый размер:",
+ "estimatedTime": "Расчетное время:",
+ "europe": "Европа",
+ "example": "Пример",
+ "exampleColumn": "Пример столбца",
+ "exampleColumnDesc": "Этот столбец может иметь или не иметь содержимого в зависимости от типа данных, выбранного для строки. Это просто удобный способ для некоторых типов данных предоставить предустановленные примеры того, как их можно использовать. Например, столбец «Имена» содержит раскрывающийся список различных форматов имен. Для всех типов данных вы по-прежнему можете настроить его вручную в столбце «Параметры»: это действительно способ сэкономить время, чтобы вы как можно быстрее освоились.",
+ "examples": "Примеры",
+ "exit": "Выход",
+ "expired": "Истекший",
+ "expiryDate": "Дата истечения срока",
+ "exportType": "Тип экспорта",
+ "exportTypeBtnDesc": "Кнопка в верхнем левом углу панели позволяет выбрать и настроить формат данных, которые вы хотите сгенерировать. Здесь он установлен на Typescript.",
+ "exportTypeOptionsSql": "Параметры типа экспорта: SQL",
+ "exportTypeOptionsSqlDesc": "Как видите, в SQL есть много других полей, специфичных для этого типа экспорта. Другие типы экспорта имеют другие настройки.",
+ "exportTypeOptionsTs": "Параметры типа экспорта: машинопись",
+ "exportTypeOptionsTsDesc": "В зависимости от выбранного типа экспорта тело панели может содержать дополнительные параметры. Здесь с Typescript есть два поля, позволяющие настроить имя типа и имя экспортируемой переменной.",
+ "exportTypeOptionsTsDesc2": "Теперь давайте посмотрим на SQL.",
+ "exportTypeSelection": "Выбор типа экспорта",
+ "exportTypeSelectionDesc": "Раскрывающийся список «Формат» позволяет выбрать формат для сгенерированного контента. Есть много опций, некоторые из них основные типы: SQL, CSV, SQL, JSON и другие, а некоторые из них предназначены для генерации данных для конкретных языков программирования.",
+ "exportTypes": "Типы экспорта",
+ "filterDataTypes": "Типы данных фильтра",
+ "financial": "финансовый",
+ "firstName": "Имя",
+ "forgottenYourPasswordQ": "Забыть пароль?",
+ "format": "Формат",
+ "generate": "Создать",
+ "generateBtnDesc": "После того, как вы закончили настройку набора данных и убедились, что он выглядит так, как вы хотите, пришло время создать объем данных! Нажмите эту кнопку, чтобы сгенерировать данные. В зависимости от количества строк в сетке данных и количества строк, которые вы хотите сгенерировать, это может занять некоторое время.",
+ "generated": "Сгенерировано",
+ "generatedC": "Сгенерировано:",
+ "generator": "Генератор",
+ "geo": "гео",
+ "grid": "Сетка",
+ "gridPanelTourDesc1": "Панель сетки — это место, где вы создаете данные, которые хотите сгенерировать. Здесь вы можете выбирать между различными типами данных, их настройкой и порядком отображения. В данном примере у нас есть пять полей: Имя, Телефон, Электронная почта, Адрес и Город.",
+ "gridPanelTourDesc2": "См. обзор панели сетки для получения более подробной информации по этому разделу.",
+ "gridPanelTourIntroDesc1": "Панель сетки — это место, где вы определяете, какие данные вы хотите сгенерировать. Для этого тура мы скрыли панель предварительного просмотра (см. неотмеченную кнопку «Предварительный просмотр» в правом нижнем углу страницы), чтобы дать нам немного больше места. Мы также добавили 10 строк различных типов данных только для иллюстрации.",
+ "gridPanelTourIntroDesc2": "Начнем со столбцов сетки.",
+ "help": "Помощь",
+ "helpIcon": "Значок справки",
+ "helpIconDesc": "Когда вы выбираете тип данных в предыдущем столбце, здесь появляется значок. При нажатии на нее открывается диалоговое окно справки, содержащее информацию об использовании.",
+ "hide": "Скрывать",
+ "hideShowGrid": "Скрыть/показать сетку",
+ "hideShowPreviewPanel": "Скрыть/показать панель предварительного просмотра",
+ "history": "История",
+ "historyPanelDesc": "На этой панели отображается история изменений, внесенных в набор данных. Нажмите кнопку «Просмотр», чтобы просмотреть более ранние версии. Чтобы вернуться к более ранней версии, просто просмотрите ее, затем нажмите кнопку «Вернуться к этой версии».",
+ "hostName": "Имя хоста",
+ "humanData": "Человеческие данные",
+ "ifWantToReregister": "Если вы хотите перерегистрироваться, пожалуйста, посетите:",
+ "incomplete": "Неполный",
+ "install": "Установить",
+ "introToGenerator": "Знакомство с генератором",
+ "introToGeneratorDesc": "В этом туре вы познакомитесь с основными функциями Генератора данных. Давайте начнем!",
+ "invalidSettings": "Неверные настройки!",
+ "language": "Английский",
+ "lastEdited": "последний отредактированный",
+ "lastLoggedIn": "Последний вход",
+ "lastModified": "Последнее изменение",
+ "lastName": "Фамилия",
+ "lastSaved": "последнее сохраненное",
+ "lineWrapping": "Перенос строки",
+ "linkToDataSet": "Ссылка на набор данных",
+ "linkToThisDataSet": "Ссылка на этот набор данных",
+ "live": "Жить",
+ "load": "Нагрузка",
+ "loading": "Загрузка...",
+ "login": "Авторизоваться",
+ "loginToSave": "Для того, чтобы сохранить ваши наборы данных, вам сначала нужна учетная запись. Пожалуйста, войдите ниже.",
+ "loginOrRegisterToSave": "Для того, чтобы сохранить ваши наборы данных, вам сначала нужна учетная запись. Пожалуйста, войдите или зарегистрируйтесь ниже.",
+ "loginUrlLabel": "URL-адрес входа:",
+ "logout": "Выйти",
+ "makeDataSetPublicAgreement": "Я понимаю, что для того, чтобы поделиться этим набором данных, мне нужно сделать его общедоступным.",
+ "metaDescription": "generateata.com: бесплатный генератор случайных пользовательских данных под лицензией GNU для тестирования программного обеспечения.",
+ "metaKeywords": "Случайные данные, тестовые данные, образцы данных, генератор данных, генерация данных",
+ "missingDataSetName": "Пожалуйста, введите название вашего набора данных",
+ "nameColumn": "Столбец имени",
+ "nameColumnDesc": "Этот столбец получает другой заголовок в зависимости от типа экспорта, который вы выбрали на панели предварительного просмотра (XML, CSV и т. д.). Цель этого столбца — предоставить идентификатор строки, который затем можно использовать в сгенерированных данных. Например, XML использует это значение в качестве имени узла XML; JSON использует его как имя свойства JSON; SQL использует его для имени столбца базы данных. Приложение автоматически проверит введенное здесь значение, чтобы убедиться, что оно допустимо для выбранного типа экспорта.",
+ "newDataSet": "Новый набор данных",
+ "no": "Нет",
+ "noAccountsCreated": "Аккаунты не созданы.",
+ "noAdditionalSettings": "Никаких дополнительных настроек.",
+ "noDataSetsSaved": "У вас нет сохраненных наборов данных.",
+ "noExamplesAvailable": "Нет доступных примеров.",
+ "noExpiry": "Нет срока действия",
+ "noHistory": "Еще нет истории.",
+ "noOptionsAvailable": "Нет доступных вариантов.",
+ "noSafari": "Жаль, нет сафари!",
+ "northAmerica": "Северная Америка",
+ "nowLoggedIn": "Вы вошли в систему.",
+ "nowLoggedOut": "Вы вышли из системы.",
+ "numResults": "Количество результатов",
+ "numeric": "Числовой",
+ "oceania": "Океания",
+ "oneTimePasswordLogin": "Вы вошли в систему, используя одноразовый пароль. Установите новый сейчас, иначе вы не сможете снова войти в систему.",
+ "open": "Открытым",
+ "options": "Опции",
+ "optionsColumn": "Столбец параметров",
+ "optionsColumnDesc1": "Этот столбец содержит все параметры конфигурации для типа данных этой строки. Некоторые типы данных имеют слишком много параметров конфигурации для отображения в таком маленьком пространстве (например, страна, регион), поэтому они могут просто отображать кнопку в сетке, которая открывает диалоговое окно, содержащее все параметры.",
+ "optionsColumnDesc2": "Для показанного здесь типа данных Names столбец содержит одно поле Creatable Pill. Это распространенный тип поля в генераторе данных. Он позволяет добавлять элементы, просто введя его и нажав . Затем он преобразует эти предметы в отдельные таблетки, которые можно удалить с помощью значка «x».",
+ "or": "или же",
+ "order": "Заказ",
+ "other": "Другой",
+ "overMaxAnonRows": "К сожалению, вы можете создать только %1 строк. Для создания больших объемов данных вам понадобится учетная запись пользователя на этом сайте.",
+ "panelContents": "Содержимое панели",
+ "panelContentsDesc": "Основная часть панели предварительного просмотра содержит образец сгенерированных данных в соответствии с данными, указанными на панели сетки, и выбранными настройками типа экспорта. Здесь он генерирует 5 различных типов данных (страна, регион, город, почтовый адрес и почтовый индекс) в формате машинописного текста.",
+ "panelControls": "Панель управления",
+ "panelControlsClearIconDesc": "Последний значок на панели управления позволяет очистить страницу, чтобы вы могли начать заново.",
+ "panelControlsDesc": "Кнопки управления панелью позволяют скрывать/показывать панели «Сетка» и «Предварительный просмотр», что может быть полезно, если вы работаете с ограниченным пространством экрана. Вы также можете переключать размещение двух панелей относительно друг друга (слева направо, сверху вниз) с помощью третьего значка.",
+ "panelTabs": "Вкладки панели",
+ "panelTabsDesc": "Наконец, вы могли заметить, что панель состоит из двух вкладок. По умолчанию отображается вкладка «Настройки», так как именно туда вы, скорее всего, захотите перейти. Но есть и вторая вкладка «Предварительный просмотр», которая содержит настройки для изменения внешнего вида панели предварительного просмотра: тема, скрытие/отображение номеров строк, переключение переноса строк, изменение размера текста и управление количеством сгенерированных строк.",
+ "password": "Пароль",
+ "passwordLabel": "Пароль:",
+ "passwordReset": "Восстановление пароля",
+ "passwordResetAccountExpired": "Сброс пароля - срок действия учетной записи истек",
+ "passwordResetAccountExpiredDesc": "Мы только что получили запрос на сброс вашего пароля, однако срок действия вашей учетной записи уже истек.",
+ "passwordResetComplete": "Ваш пароль был сброшен, и новый пароль был отправлен вам по электронной почте.",
+ "passwordResetEmailContent1": "Ваш пароль был сброшен. Вы можете использовать следующий пароль для входа в систему: %1",
+ "passwordResetEmailContent2": "Пожалуйста, измените его после входа в систему.",
+ "passwordResetEmailDesc": "Мы получили запрос на сброс вашего пароля. Это электронное письмо содержит временный одноразовый пароль, который вы можете использовать для входа в систему. После входа вам потребуется установить новый пароль.",
+ "passwordResetMsg": "Если электронное письмо было в файле, ваш пароль был сброшен, и вам была отправлена временная информация для входа в систему.",
+ "passwordUpdateInvalidPassword": "Ваш текущий пароль неверен. Пожалуйста, введите еще раз.",
+ "passwordUpdated": "Ваш пароль был обновлен.",
+ "pause": "Пауза",
+ "pleaseConfirm": "Пожалуйста подтвердите",
+ "pleaseEnterAll": "Пожалуйста, введите все",
+ "pleaseEnterDataSetName": "Пожалуйста, введите название нового набора данных.",
+ "pleaseFixErrors": "Пожалуйста, исправьте следующие ошибки и повторите отправку:",
+ "pleaseLogin": "Пожалуйста, войдите",
+ "pleaseSelect": "Пожалуйста выберите",
+ "plugins": "Плагины",
+ "pressEnterAddItem": "Нажмите Enter, чтобы добавить элемент",
+ "preview": "Предварительный просмотр",
+ "previewPanel": "Панель предварительного просмотра",
+ "previewPanelControlsDesc": "В правом верхнем углу панели есть две иконки. Первый значок обновляет панель, каждый раз генерируя разные случайные данные — это помогает вам понять, какие данные он генерирует. Второй значок полностью закрывает панель. Вы всегда можете отобразить панель снова, используя раздел в правом нижнем углу страницы (просто повторно установите флажок «Предварительный просмотр»).",
+ "previewPanelDesc": "Эта панель дает вам предварительный просмотр данных, которые вы генерируете, в то время как вы строите их с помощью панели сетки. Кнопка в левом верхнем углу показывает выбранный формат генерируемых данных (например, SQL, XML, CSV и т. д.). Чтобы изменить формат, просто нажмите на кнопку.",
+ "previewPanelMoreInfo": "Для получения дополнительной информации об этой панели ознакомьтесь с обзором панели предварительного просмотра.",
+ "previewPanelNoData": "Нет данных!",
+ "previewPanelTourDesc": "Панель предварительного просмотра дает вам живое визуальное представление данных, которые вы создаете по мере их построения. Этот тур дает краткое представление о том, как работает панель.",
+ "previewRows": "Предварительный просмотр строк",
+ "private": "Частный",
+ "problemLoadingTour": "Извините, возникла проблема с загрузкой тура.",
+ "programmingLanguages": "Языки программирования",
+ "province": "Провинция",
+ "public": "Общественный",
+ "publicQ": "Общественный?",
+ "reenterPassword": "Повторно введите пароль",
+ "refreshPage": "Обновить страницу",
+ "refreshPanel": "Обновить панель",
+ "register": "регистр",
+ "remainingTime": "Оставшееся время:",
+ "requiredField": "Обязательное поле",
+ "revertToVersion": "Вернуться к этой версии",
+ "row": "Строка",
+ "rowLabel": "Название столбца",
+ "rowLabelPlural": "Заголовки столбцов",
+ "rowNumDesc1": "Первый столбец в сетке показывает номер строки. Чтобы изменить порядок строк, просто нажмите и перетащите этот элемент вверх или вниз.",
+ "rowNumDesc2": "Значение заголовка для этого столбца показывает общее количество строк в сетке. Это может быть удобно, когда у вас очень большие наборы данных, и они слишком велики, чтобы поместиться на экране.",
+ "rowNumber": "Номер строки",
+ "rowSp": "Ряд (ы)",
+ "rows": "ряды",
+ "rowsGenerated": "Созданные строки",
+ "rowsGeneratedPerSecond": "Количество строк в секунду",
+ "safariExplanation": "Для этого сайта требуется современный веб-браузер, реализующий современные веб-стандарты. К сожалению, Safari отстает от других браузеров и не запускает этот скрипт. Пока мы не выясним, как это сделать, используйте один из следующих браузеров. Все бесплатно для скачивания.",
+ "save": "Сохранять",
+ "saveAs": "Сохранить как",
+ "saveButtonDesc": "Если у вас есть учетная запись пользователя на сайте, эта кнопка сохранит ваш набор данных. Если вы не вошли в систему, нажмите на нее, чтобы сначала войти в систему. Система хранит историю последних %1 изменений в наборе данных, поэтому вы всегда можете вернуться и просмотреть более ранние версии.",
+ "saveDataSetNewName": "Сохранить набор данных под новым именем",
+ "seconds": "Секунды",
+ "seeHelpDialog": "См. диалоговое окно справки.",
+ "selectCountry": "Выберите страну",
+ "selectDataType": "Выберите тип данных",
+ "selectEllipsis": "Выбирать...",
+ "selectExpiryDate": "Выберите срок действия",
+ "selectLanguage": "Выберите язык",
+ "sendEmail": "Отправить письмо",
+ "seriouslySlow": "Серьезно медленный",
+ "settings": "Настройки",
+ "showGrid": "Показать сетку",
+ "showLineNumbers": "Показать номера строк",
+ "showPreview": "Показать предварительный просмотр",
+ "siteLogo": "логотип сайта",
+ "size": "Размер:",
+ "southAmerica": "Южная Америка",
+ "status": "Статус",
+ "stripWhitespace": "удалить пробелы из сгенерированного контента",
+ "success": "успех",
+ "takeTour": "Совершите экскурсию",
+ "text": "Текст",
+ "textSize": "Размер текста",
+ "theDataSetName": "Имя набора данных",
+ "theExportTypeBtn": "Кнопка «Тип экспорта»",
+ "theGenerateButton": "Кнопка «Создать»",
+ "theGridPanel": "Панель сетки",
+ "thePreviewPanel": "Панель предварительного просмотра",
+ "theSaveButton": "Кнопка Сохранить",
+ "theme": "Тема",
+ "togglePanelLayout": "Переключить макет панели",
+ "totalNumGeneratedRows": "Общее количество сгенерированных строк",
+ "tourComplete": "Тур завершен",
+ "tourCompleteDesc": "Все сделано! Выберите одну из кнопок ниже, чтобы продолжить.",
+ "tourIntroPara1": "Этот инструмент предоставляет множество функций, и поначалу он может быть немного ошеломляющим. Нажмите на одну из кнопок справа, чтобы ознакомиться с этой конкретной функцией.",
+ "tourIntroPara2": "Обратите внимание, что при запуске тура содержимое страницы генератора временно перезаписывается, чтобы проиллюстрировать некоторые особенности интерфейса. Но не волнуйтесь! Как только тур завершен, он возвращает ваши данные в исходное состояние.",
+ "tryDifferentTour": "Попробуйте другой тур",
+ "update": "Обновлять",
+ "updateAccount": "Обновить учетную запись",
+ "user": "Пользователь",
+ "userAccount": "Учетная запись пользователя",
+ "userAccountNotFound": "Извините, мы не смогли найти ваш аккаунт.",
+ "userAccountUpdated": "Учетная запись пользователя обновлена.",
+ "userAccounts": "Учетные записи пользователей",
+ "userNotFound": "К сожалению, нам не удалось вас идентифицировать. Пожалуйста, проверьте свои учетные данные.",
+ "validationAccountAlreadyExists": "К сожалению, учетная запись с таким адресом электронной почты уже существует",
+ "validationDifferentNewPasswords": "Убедитесь, что ваши пароли совпадают",
+ "validationInvalidEmail": "Пожалуйста, введите действительный адрес электронной почты",
+ "validationNoCurrentPassword": "Пожалуйста, введите текущий пароль",
+ "validationNoEmail": "Пожалуйста, введите свой адрес электронной почты",
+ "validationNoFirstName": "Пожалуйста, введите ваше имя",
+ "validationNoLastName": "Пожалуйста, введите свою фамилию",
+ "validationNoPassword": "Пожалуйста введите ваш пароль",
+ "version": "Версия",
+ "view": "Вид",
+ "viewChangelog": "Посмотреть список изменений",
+ "viewOnGithub": "Посмотреть на Гитхабе",
+ "welcomeToTheGenerator": "Добро пожаловать в генератор!",
+ "yes": "Да",
+ "yourAccount": "Ваш счет",
+ "yourAccountUpdated": "Ваш аккаунт был обновлен.",
+ "yourUserAccount": "Ваша учетная запись пользователя"
+}
diff --git a/apps/client/src/i18n/ta.json b/apps/client/src/i18n/ta.json
new file mode 100644
index 000000000..6a21b057a
--- /dev/null
+++ b/apps/client/src/i18n/ta.json
@@ -0,0 +1,329 @@
+{
+ "abort": "நிறுத்து",
+ "about": "பற்றி",
+ "aboutInfoPara1": "generatedata.com என்பது ஒரு ஓப்பன் சோர்ஸ் ப்ராஜெக்ட் ஆகும், இது பிற்கால பேலியோலிதிக் சகாப்தத்தில் (சுமார் 2004) உருவாக்கப்பட்டது மற்றும் அதன் வாழ்நாளில் பல பங்களிப்பாளர்களைக் கொண்டுள்ளது. ஒவ்வொரு சில வருடங்களுக்கும் சமீபத்திய மற்றும் மிகப் பெரிய தொழில்நுட்பங்களைப் பயன்படுத்தி நான் அதை மீண்டும் எழுதுகிறேன்: இந்த பதிப்பு 2021 இல் நிறைவடைந்தது, இது ரியாக்ட், ரெடக்ஸ், டைப்ஸ்கிரிப்ட், கிராஃப்குஎல், டோக்கர், வலைத் தொழிலாளர்கள், ரப்பர் பேண்டுகள், டக்ட் டேப் மற்றும் நிறைய சாபங்களால் செய்யப்பட்டது.",
+ "aboutInfoPara2": "நீங்கள் திட்டத்தை கிதுப்பில் காணலாம். அதைப் பதிவிறக்குங்கள், முட்கரண்டி அல்லது பங்களிக்க தயங்க. மகிழுங்கள்!",
+ "aboutThisScript": "இந்த ஸ்கிரிப்ட் பற்றி",
+ "accountCreated": "கணக்கு உருவாக்கப்பட்டது",
+ "accountCreatedDesc": "கணக்கு உருவாக்கப்பட்டது.",
+ "accountCreatedMsg": "உங்களுக்காக ஒரு கணக்கு உருவாக்கப்பட்டது.",
+ "accountDeleted": "கணக்கு நீக்கப்பட்டது.",
+ "accountDisabled": "கணக்கு முடக்கப்பட்டது",
+ "accountExpiredMsg": "மன்னிக்கவும், உங்கள் கணக்கு காலாவதியாகிவிட்டது.",
+ "accountExpiryDate": "கணக்கு காலாவதி தேதி",
+ "accountInfo": "கணக்கு தகவல்",
+ "accountSettings": "கணக்கு அமைப்புகள்",
+ "accountType": "கணக்கு வகை",
+ "accounts": "கணக்குகள்",
+ "add": "கூட்டு",
+ "addRowsDesc": "தரவு கட்டத்தில் கூடுதல் வரிசைகளைச் சேர்க்க, பக்கத்தின் கீழே உள்ள படிவத்தைப் பயன்படுத்தவும். உங்களிடம் நிறைய புலங்கள் இருக்கும்போது, இந்த பகுதி பக்கத்திலிருந்து மறைக்கப்படலாம், எனவே படிவத்தைக் காண நீங்கள் பேனலை கீழே உருட்ட வேண்டும்.",
+ "addSomeDataDesc": "கட்டத்தில் சில வரிசைகளைச் சேர்க்கவும்.",
+ "admin": "நிர்வாகம்",
+ "adminAccount": "நிர்வாகி கணக்கு",
+ "administrator": "நிர்வாகி",
+ "africa": "ஆப்பிரிக்கா",
+ "allCountries": "அனைத்து நாடுகளும்",
+ "allDataTypePlugins": "அனைத்து தரவு வகை செருகுநிரல்களும்",
+ "allExportTypePlugins": "அனைத்து ஏற்றுமதி வகை செருகுநிரல்களும்",
+ "anonymousAccess": "அநாமதேய அணுகல்",
+ "asia": "ஆசியா",
+ "back": "மீண்டும்",
+ "backToLogin": "உள்நுழைய திரும்பவும்",
+ "blog": "வலைப்பதிவு",
+ "cancel": "ரத்துசெய்",
+ "centralAmerica": "மத்திய அமெரிக்கா",
+ "changePassword": "கடவுச்சொல்லை மாற்று",
+ "clear": "அழி",
+ "clearPage": "பக்கத்தை அழி",
+ "clearPageConfirmation": "பக்கத்தை அழிக்க விரும்புகிறீர்களா? நீங்கள் செய்த எந்த மாற்றங்களும் இழக்கப்படும்.",
+ "clearThePage": "பக்கத்தை அழிக்கவும்",
+ "clickExportTypeBtn": "அடுத்த கட்டம் நீங்கள் பொத்தானைக் கிளிக் செய்தால் என்ன நடக்கும் என்பதைக் காட்டுகிறது.",
+ "close": "நெருக்கமான",
+ "closePanel": "பேனலை மூடு",
+ "columns": "நெடுவரிசைகள்",
+ "columnsDesc": "உங்களுடைய கிடைக்கக்கூடிய திரை அளவைப் பொறுத்து, இந்த நெடுவரிசைகளில் பலவற்றைக் கட்டம் காண்பிக்கும். இது மிகச் சிறியதாக இருக்கும்போது, அது சில நெடுவரிசைகளை மறைத்து, காணாமல் போன உள்ளடக்கத்துடன் ஒரு இன்போடிப்பைத் திறக்கும் ஒரு கோக் ஐகானைக் காண்பிக்கும். அடுத்த சில படிகள் ஒவ்வொரு நெடுவரிசைகளையும் விளக்குகின்றன, முதல் பெயர்கள் வரிசையை ஒரு ஆர்ப்பாட்டமாகப் பயன்படுத்துகின்றன.",
+ "complete": "முழுமை",
+ "confirmDeleteAccount": "இந்த கணக்கை நீக்க விரும்புகிறீர்களா?",
+ "confirmDeleteUserAccount": "இந்த பயனர் கணக்கை நீக்க விரும்புகிறீர்களா?",
+ "confirmEmptyForm": "பக்கத்தை அழிக்க விரும்புகிறீர்களா?",
+ "continue": "தொடரவும்",
+ "copiedToClipboard": "கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது",
+ "copyToClipboard": "கிளிப்போர்டுக்கு நகலெடுக்கவும்",
+ "coreExportType": "கோர் வடிவங்கள்",
+ "countries": "நாடுகள்",
+ "country": "நாடு",
+ "countrySpecific": "நாடு சார்ந்த",
+ "cpuMeltinglyFast": "CPU- உருகும் வேகமாக",
+ "createAccount": "உங்கள் கணக்கை துவங்குங்கள்",
+ "currentPassword": "தற்போதைய கடவுச்சொல்",
+ "currentVersion": "தற்போதைய பதிப்பு திருத்தப்பட்டது",
+ "dataGenerated": "தரவு உருவாக்கப்பட்டது.",
+ "dataSet": "தரவு தொகுப்பு",
+ "dataSetHelp": "நீங்கள் எந்த வகையான தரவை உருவாக்க விரும்புகிறீர்கள் என்பதை நீங்கள் வரையறுக்கிறீர்கள். ஒரு வரிசையில் அல்லது இரண்டில் நிரப்ப முயற்சிக்கவும், உருவாக்கு பொத்தானைக் கிளிக் செய்யவும். நீங்கள் அதை மிக வேகமாகப் பெறுவீர்கள்.",
+ "dataSetLoaded": "தரவு தொகுப்பு ஏற்றப்பட்டது.",
+ "dataSetName": "தரவு தொகுப்பு பெயர்",
+ "dataSetNameDesc": "உங்கள் தரவு தொகுப்பிற்கு பெயரிட இங்கே உரையில் கிளிக் செய்க. தளத்தில் உங்களுக்கு ஒரு கணக்கு தேவை என்பதையும், உங்கள் தரவுத் தொகுப்புகளுக்கு பெயரிட உள்நுழைவதையும் நினைவில் கொள்க. உங்கள் தரவுத் தொகுப்புகளைச் சேமிக்கும்போது, அவற்றைக் கண்காணிக்க இந்த பெயர் உங்களுக்கு வசதியான லேபிளை வழங்குகிறது.",
+ "dataSetNameUpdated": "தரவு தொகுப்பு பெயர் புதுப்பிக்கப்பட்டது.",
+ "dataSetOptions": "தரவு தொகுப்பு விருப்பங்கள்",
+ "dataSetReverted": "உங்கள் தரவு தொகுப்பு தேர்ந்தெடுக்கப்பட்ட பழைய பதிப்பிற்கு மாற்றப்பட்டது.",
+ "dataSetSaved": "உங்கள் தரவு தொகுப்பு சேமிக்கப்பட்டது.",
+ "dataSets": "தரவு அமைக்கிறது",
+ "dataType": "தரவு வகை",
+ "dataTypeDesc": "இது கிடைக்கக்கூடிய அனைத்து தரவு வகைகளையும் கொண்ட ஒரு கீழ்தோன்றல் ஆகும். நீங்கள் ஒரு மதிப்பைத் தேர்ந்தெடுக்கும்போது, அந்த வரிசையின் மீதமுள்ள நெடுவரிசைகளை இது புதுப்பிக்கும். தனிப்பயனாக்கத்தை அனுமதிக்க ஒவ்வொரு தரவு வகைக்கும் அதன் தனித்துவமான அமைப்புகள் உள்ளன.",
+ "dataTypes": "தரவு வகைகள்",
+ "dateAccountCreated": "தேதி கணக்கு உருவாக்கப்பட்டது",
+ "dateCreated": "உருவாக்கிய தேதி",
+ "defaultLanguage": "இயல்புநிலை மொழி",
+ "delete": "அழி",
+ "delete1DataSet": "1 தரவு தொகுப்பை நீக்கு",
+ "deleteAccount": "கணக்கை நீக்குக",
+ "deleteDataSet": "தரவு தொகுப்பை நீக்கு",
+ "deleteDataSetConfirm": "இந்த தரவு தொகுப்பை நீக்க விரும்புகிறீர்களா?",
+ "deleteRow": "வரிசையை நீக்கு",
+ "deleteRowDesc": "கடைசி நெடுவரிசையில் நீக்கு ஐகான் உள்ளது. அதைக் கிளிக் செய்தால் வரிசையை நீக்கும்.",
+ "developer": "டெவலப்பர்",
+ "developerDoc": "டெவலப்பர் ஆவணம்",
+ "disabled": "முடக்கப்பட்டது",
+ "documentation": "ஆவணம்",
+ "download": "பதிவிறக்க Tamil",
+ "edit": "தொகு",
+ "editAccount": "கணக்கைத் திருத்து",
+ "editExportTypePage": "ஏற்றுமதி வகை பொத்தானைக் கிளிக் செய்யும்போது, இடது பக்கத்தில் உள்ள உள்ளமைவு விருப்பங்கள் மற்றும் வலதுபுறத்தில் மாதிரிக்காட்சி குழு ஆகியவற்றைக் கொண்ட முழு பக்கக் காட்சி தோன்றும்.",
+ "editExportTypePageConfig": "உள்ளமைவு விருப்பங்களைப் பார்ப்போம்.",
+ "editExportTypeSettings": "உங்கள் ஏற்றுமதி வகை அமைப்புகளைத் திருத்தவும்.",
+ "editingExportTypes": "ஏற்றுமதி வகைகளைத் திருத்துதல்",
+ "email": "மின்னஞ்சல்",
+ "emailFooterDisclaimer": "இந்த மின்னஞ்சலை நீங்கள் பிழையாகப் பெற்றிருந்தால், தள நிர்வாகியை அணுகவும்.",
+ "emailIntroLineWithName": "ஹாய் %1,",
+ "emailLabel": "மின்னஞ்சல்:",
+ "emailNotSent": "மின்னஞ்சல் அறிவிப்பை எங்களால் அனுப்ப முடியவில்லை.",
+ "emailUserLoginInfo": "பயனரின் உள்நுழைவு தகவலுக்கு மின்னஞ்சல் அனுப்புங்கள்",
+ "enterEmailAddressToResetPassword": "உங்கள் கடவுச்சொல்லை மீட்டமைக்க கீழே உங்கள் மின்னஞ்சல் முகவரியை உள்ளிடவும்.",
+ "enterUserAccountDetails": "உங்கள் பயனர் கணக்கு விவரங்களை கீழே உள்ளிடவும்.",
+ "errorCreatingAccount": "இந்த கணக்கை உருவாக்குவதில் பிழை ஏற்பட்டது.",
+ "estimatedSize": "மதிப்பிடப்பட்ட அளவு:",
+ "estimatedTime": "கணிக்கப்பட்ட நேரம்:",
+ "europe": "ஐரோப்பா",
+ "example": "உதாரணமாக",
+ "exampleColumn": "எடுத்துக்காட்டு நெடுவரிசை",
+ "exampleColumnDesc": "இந்த நெடுவரிசையில் நீங்கள் வரிசைக்குத் தேர்ந்தெடுத்த தரவு வகையைப் பொறுத்து உள்ளடக்கம் இருக்கலாம் அல்லது இல்லாமல் இருக்கலாம். சில தரவு வகைகள் எவ்வாறு பயன்படுத்தப்படலாம் என்பதற்கான முன்னமைக்கப்பட்ட எடுத்துக்காட்டுகளை வழங்க இது ஒரு வசதியான வழியாகும். எடுத்துக்காட்டாக, பெயர்கள் நெடுவரிசை வெவ்வேறு பெயர் வடிவங்களின் கீழ்தோன்றலை வழங்குகிறது. எல்லா தரவு வகைகளுக்கும், விருப்பத்தேர்வுகள் நெடுவரிசை மூலம் நீங்கள் அதை கைமுறையாக உள்ளமைக்க முடியும்: இது உங்களை விரைவாக விரைவாகச் சேர்ப்பதற்கான நேரத்தைச் சேமிக்கும் சாதனமாகும்.",
+ "examples": "எடுத்துக்காட்டுகள்",
+ "exit": "வெளியேறு",
+ "expired": "காலாவதியான",
+ "expiryDate": "காலாவதி தேதி",
+ "exportType": "ஏற்றுமதி வகை",
+ "exportTypeBtnDesc": "பேனலின் மேல் இடதுபுறத்தில் உள்ள பொத்தானை நீங்கள் உருவாக்க விரும்பும் தரவிற்கான வடிவமைப்பைத் தேர்ந்தெடுத்து உள்ளமைக்க நீங்கள் செல்கிறீர்கள். இங்கே இது டைப்ஸ்கிரிப்டாக அமைக்கப்பட்டுள்ளது.",
+ "exportTypeOptionsSql": "ஏற்றுமதி வகை விருப்பங்கள்: SQL",
+ "exportTypeOptionsSqlDesc": "நீங்கள் பார்க்கிறபடி, இந்த ஏற்றுமதி வகைக்கு குறிப்பிட்ட பல துறைகள் SQL இல் உள்ளன. பிற ஏற்றுமதி வகைகள் வெவ்வேறு அமைப்புகளைக் கொண்டுள்ளன.",
+ "exportTypeOptionsTs": "ஏற்றுமதி வகை விருப்பங்கள்: தட்டச்சு",
+ "exportTypeOptionsTsDesc": "தேர்ந்தெடுக்கப்பட்ட ஏற்றுமதி வகையைப் பொறுத்து, பேனல் உடலில் கூடுதல் விருப்பங்கள் இருக்கலாம். இங்கே டைப்ஸ்கிரிப்டுடன் இரண்டு புலங்கள் உள்ளன, இது வகை பெயர் மற்றும் ஏற்றுமதி செய்யப்பட்ட மாறி பெயரைத் தனிப்பயனாக்க அனுமதிக்கிறது.",
+ "exportTypeOptionsTsDesc2": "இப்போது SQL ஐப் பார்ப்போம்.",
+ "exportTypeSelection": "ஏற்றுமதி வகை தேர்வு",
+ "exportTypeSelectionDesc": "உருவாக்கப்பட்ட உள்ளடக்கத்திற்கான வடிவமைப்பைத் தேர்வுசெய்ய வடிவமைப்பு கீழ்தோன்றல் உங்களை அனுமதிக்கிறது. பல விருப்பங்கள் உள்ளன, அவற்றில் சில முக்கிய வகைகள்: SQL, CSV, SQL, JSON மற்றும் பிற, மற்றும் அவற்றில் சில குறிப்பிட்ட நிரலாக்க மொழிகளுக்கான தரவை உருவாக்குவதற்கானவை.",
+ "exportTypes": "ஏற்றுமதி வகைகள்",
+ "filterDataTypes": "தரவு வகைகளை வடிகட்டவும்",
+ "financial": "நிதி",
+ "firstName": "முதல் பெயர்",
+ "forgottenYourPasswordQ": "கடவுச்சொல்லை மறந்து?",
+ "format": "வடிவம்",
+ "generate": "உருவாக்கு",
+ "generateBtnDesc": "உங்கள் தரவு தொகுப்பை உள்ளமைத்து முடித்ததும், நீங்கள் எப்படி விரும்புகிறீர்கள் என்று உறுதிசெய்ததும், சில தரவை அளவிலேயே உருவாக்க வேண்டிய நேரம் இது! உங்கள் தரவை உருவாக்க இந்த பொத்தானைக் கிளிக் செய்க. தரவு கட்டத்தில் உள்ள வரிசைகளின் எண்ணிக்கை மற்றும் நீங்கள் உருவாக்க விரும்பும் வரிசைகளின் எண்ணிக்கையைப் பொறுத்து, இதற்கு சிறிது நேரம் ஆகலாம்.",
+ "generated": "உருவாக்கப்பட்டது",
+ "generatedC": "உருவாக்கப்பட்டது:",
+ "generator": "ஜெனரேட்டர்",
+ "geo": "புவி",
+ "grid": "கட்டம்",
+ "gridPanelTourDesc1": "நீங்கள் உருவாக்க விரும்பும் தரவை நீங்கள் கட்டமைக்கும் இடம் கட்டம் குழு. வெவ்வேறு தரவு வகைகள், அவை எவ்வாறு கட்டமைக்கப்படுகின்றன மற்றும் அவை தோன்றும் வரிசைக்கு இடையில் இங்கே நீங்கள் தேர்வு செய்யலாம். இங்கே எடுத்துக்காட்டில், எங்களிடம் ஐந்து துறைகள் உள்ளன: பெயர், தொலைபேசி, மின்னஞ்சல், தெரு முகவரி மற்றும் நகரம்.",
+ "gridPanelTourDesc2": "இந்த பகுதியைப் பற்றிய கூடுதல் தகவலுக்கு கட்டம் பேனலில் சுற்றுப்பயணத்தைப் பார்க்கவும்.",
+ "gridPanelTourIntroDesc1": "நீங்கள் உருவாக்க விரும்பும் தரவை நீங்கள் வரையறுக்கும் இடமே கட்டம் குழு. இந்த சுற்றுப்பயணத்திற்காக, எங்களுக்கு இன்னும் கொஞ்சம் இடத்தை வழங்க, முன்னோட்ட பேனலை மறைத்துள்ளோம் (பக்கத்தின் கீழ் வலதுபுறத்தில் தேர்வு செய்யப்படாத \"முன்னோட்டம்\" பொத்தானைக் காண்க). எடுத்துக்காட்டு நோக்கங்களுக்காக, வெவ்வேறு தரவு வகைகளின் 10 வரிசைகளையும் சேர்த்துள்ளோம்.",
+ "gridPanelTourIntroDesc2": "கட்டம் நெடுவரிசைகளுடன் தொடங்குவோம்.",
+ "help": "உதவி",
+ "helpIcon": "ஐகானுக்கு உதவுங்கள்",
+ "helpIconDesc": "முந்தைய நெடுவரிசையில் தரவு வகையைத் தேர்ந்தெடுக்கும்போது, ஒரு ஐகான் இங்கே தோன்றும். அதைக் கிளிக் செய்தால் பயன்பாட்டுத் தகவல் அடங்கிய உதவி உரையாடலைத் திறக்கும்.",
+ "hide": "மறை",
+ "hideShowGrid": "கட்டத்தை மறை / காட்டு",
+ "hideShowPreviewPanel": "முன்னோட்ட பேனலை மறைக்க / காண்பி",
+ "history": "வரலாறு",
+ "historyPanelDesc": "தரவுத் தொகுப்பில் செய்யப்பட்ட மாற்றங்களின் வரலாற்றை இந்த குழு காட்டுகிறது. முந்தைய பதிப்புகளை ஆராய காட்சி பொத்தான்களைக் கிளிக் செய்க. முந்தைய பதிப்பிற்கு மாற்ற, அதைப் பார்க்கவும், பின்னர் \"இந்த பதிப்பிற்குத் திரும்பு\" பொத்தானைக் கிளிக் செய்யவும்.",
+ "hostName": "புரவலன் பெயர்",
+ "humanData": "மனித தரவு",
+ "ifWantToReregister": "நீங்கள் மீண்டும் பதிவு செய்ய விரும்பினால் தயவுசெய்து பார்வையிடவும்:",
+ "incomplete": "முழுமையற்றது",
+ "install": "நிறுவு",
+ "introToGenerator": "ஜெனரேட்டருக்கு அறிமுகம்",
+ "introToGeneratorDesc": "இந்த சுற்றுப்பயணம் தரவு ஜெனரேட்டரின் முக்கிய அம்சங்களுக்கு உயர் மட்ட அறிமுகத்தை உங்களுக்கு வழங்குகிறது. தொடங்குவோம்!",
+ "invalidSettings": "தவறான அமைப்புகள்!",
+ "language": "ஆங்கிலம்",
+ "lastEdited": "கடைசியாக திருத்தப்பட்டது",
+ "lastLoggedIn": "கடைசியாக உள்நுழைந்தது",
+ "lastModified": "கடைசியாக மாற்றியமைக்கப்பட்டது",
+ "lastName": "கடைசி பெயர்",
+ "lastSaved": "கடைசியாக சேமிக்கப்பட்டது",
+ "lineWrapping": "வரி மடக்குதல்",
+ "linkToDataSet": "தரவு தொகுப்புக்கான இணைப்பு",
+ "linkToThisDataSet": "இந்த தரவு தொகுப்பிற்கான இணைப்பு",
+ "live": "வாழ்க",
+ "load": "ஏற்றவும்",
+ "loading": "ஏற்றுகிறது ...",
+ "login": "உள்நுழைய",
+ "loginToSave": "உங்கள் தரவுத் தொகுப்புகளைச் சேமிக்க முதலில் உங்களுக்கு ஒரு கணக்கு தேவை. கீழே உள்நுழையவும்.",
+ "loginOrRegisterToSave": "உங்கள் தரவுத் தொகுப்புகளைச் சேமிக்க முதலில் உங்களுக்கு ஒரு கணக்கு தேவை. உள்நுழையவும் அல்லது கீழே பதிவு செய்யவும்.",
+ "loginUrlLabel": "ஆன்மெல்டிங்ஸ்-யுஆர்எல்:",
+ "logout": "வெளியேறு",
+ "makeDataSetPublicAgreement": "இந்த தரவு தொகுப்பைப் பகிர, நான் அதைப் பகிரங்கப்படுத்த வேண்டும் என்பதை புரிந்துகொள்கிறேன்.",
+ "metaDescription": "generatedata.com: மென்பொருளைச் சோதிப்பதற்கான இலவச, குனு-உரிமம் பெற்ற, சீரற்ற தனிப்பயன் தரவு ஜெனரேட்டர்",
+ "metaKeywords": "சீரற்ற தரவு, சோதனை தரவு, மாதிரி தரவு, தரவு ஜெனரேட்டர், தரவை உருவாக்குதல்",
+ "missingDataSetName": "உங்கள் தரவு தொகுப்பின் பெயரை உள்ளிடவும்",
+ "nameColumn": "பெயர் நெடுவரிசை",
+ "nameColumnDesc": "மாதிரிக்காட்சி குழுவில் (எக்ஸ்எம்எல், சிஎஸ்வி போன்றவை) நீங்கள் தேர்ந்தெடுத்த ஏற்றுமதி வகையைப் பொறுத்து இந்த நெடுவரிசை வேறு தலைப்பைப் பெறுகிறது. இந்த நெடுவரிசையின் நோக்கம் வரிசைக்கு ஒரு அடையாளங்காட்டியை வழங்குவதாகும், பின்னர் அவை உருவாக்கப்பட்ட தரவுகளில் பயன்படுத்தப்படலாம். எடுத்துக்காட்டாக, எக்ஸ்எம்எல் இந்த மதிப்பை எக்ஸ்எம்எல் முனை பெயராகப் பயன்படுத்துகிறது; JSON இதை JSON சொத்து பெயராக பயன்படுத்துகிறது; SQL தரவுத்தள நெடுவரிசை பெயருக்கு இதைப் பயன்படுத்துகிறது. தேர்ந்தெடுக்கப்பட்ட ஏற்றுமதி வகைக்கு இது செல்லுபடியாகும் என்பதை உறுதிப்படுத்த நீங்கள் இங்கு உள்ளிட்ட மதிப்பை பயன்பாடு தானாகவே சரிபார்க்கும்.",
+ "newDataSet": "புதிய தரவு தொகுப்பு",
+ "no": "இல்லை",
+ "noAccountsCreated": "கணக்குகள் எதுவும் உருவாக்கப்படவில்லை.",
+ "noAdditionalSettings": "கூடுதல் அமைப்புகள் இல்லை",
+ "noDataSetsSaved": "உங்களிடம் தரவுத் தொகுப்புகள் எதுவும் சேமிக்கப்படவில்லை.",
+ "noExamplesAvailable": "எடுத்துக்காட்டுகள் இல்லை.",
+ "noExpiry": "காலாவதி இல்லை",
+ "noHistory": "இதுவரை வரலாறு இல்லை.",
+ "noOptionsAvailable": "விருப்பங்கள் எதுவும் கிடைக்கவில்லை.",
+ "noSafari": "மன்னிக்கவும், சஃபாரி இல்லை!",
+ "northAmerica": "வட அமெரிக்கா",
+ "nowLoggedIn": "நீங்கள் உள்நுழைந்துள்ளீர்கள்.",
+ "nowLoggedOut": "நீங்கள் வெளியேறிவிட்டீர்கள்.",
+ "numResults": "எண் முடிவுகள்",
+ "numeric": "எண்",
+ "oceania": "ஓசியானியா",
+ "oneTimePasswordLogin": "ஒரு முறை கடவுச்சொல்லைப் பயன்படுத்தி உள்நுழைந்துள்ளீர்கள். புதிய கடவுச்சொல்லை இப்போது அமைக்கவும்.",
+ "open": "திறந்திருக்கும்",
+ "options": "விருப்பங்கள்",
+ "optionsColumn": "விருப்பங்கள் நெடுவரிசை",
+ "optionsColumnDesc1": "இந்த நெடுவரிசையில் இந்த வரிசையின் தரவு வகைக்கான அனைத்து உள்ளமைவு விருப்பங்களும் உள்ளன. சில தரவு வகைகள் இவ்வளவு சிறிய இடத்தில் (எ.கா. நாடு, பிராந்தியம்) காண்பிக்க பல உள்ளமைவு அமைப்புகளைக் கொண்டுள்ளன, எனவே அவை அனைத்து விருப்பங்களையும் கொண்ட உரையாடலைத் திறக்கும் கட்டத்தில் ஒரு பொத்தானைக் காட்டக்கூடும்.",
+ "optionsColumnDesc2": "இங்கே விளக்கப்பட்டுள்ள பெயர்கள் தரவு வகைக்கு, நெடுவரிசையில் ஒரு உருவாக்கக்கூடிய மாத்திரை புலம் உள்ளது. தரவு ஜெனரேட்டரில் இது பொதுவான புலம் வகை. தட்டச்சு செய்து ஐக் கிளிக் செய்வதன் மூலம் உருப்படிகளைச் சேர்க்க இது உங்களை அனுமதிக்கிறது. அது பின்னர் அந்த பொருட்களை \"x\" ஐகான் வழியாக அகற்றக்கூடிய தனித்துவமான மாத்திரைகளாக மாற்றுகிறது.",
+ "or": "அல்லது",
+ "order": "ஆர்டர்",
+ "other": "மற்றவை",
+ "overMaxAnonRows": "மன்னிக்கவும், நீங்கள் %1 வரிசைகளை மட்டுமே உருவாக்க முடியும். பெரிய அளவிலான தரவை உருவாக்க இந்த தளத்தில் உங்களுக்கு ஒரு பயனர் கணக்கு தேவை.",
+ "panelContents": "குழு உள்ளடக்கங்கள்",
+ "panelContentsDesc": "கட்டம் பேனலில் நீங்கள் குறிப்பிட்ட தரவு மற்றும் தேர்ந்தெடுக்கப்பட்ட ஏற்றுமதி வகை அமைப்புகளின் படி, மாதிரிக்காட்சி பேனலின் பெரும்பகுதி உருவாக்கப்பட்ட தரவின் மாதிரியைக் கொண்டுள்ளது. இங்கே இது 5 வெவ்வேறு தரவு வகைகளை (நாடு, பிராந்தியம், நகரம், தெரு முகவரி மற்றும் அஞ்சல் / ஜிப்) தட்டச்சு வடிவத்தில் உருவாக்குகிறது.",
+ "panelControls": "குழு கட்டுப்பாடுகள்",
+ "panelControlsClearIconDesc": "பேனல் கட்டுப்பாடுகளின் கடைசி ஐகான் பக்கத்தை அழிக்க உங்களை அனுமதிக்கிறது, எனவே நீங்கள் புதிதாக தொடங்கலாம்.",
+ "panelControlsDesc": "குழு கட்டுப்பாட்டு பொத்தான்கள் கட்டம் மற்றும் முன்னோட்ட பேனல்களை மறைக்க / காண்பிக்க உங்களை அனுமதிக்கின்றன, அவை நீங்கள் வரையறுக்கப்பட்ட திரை ரியல் எஸ்டேட்டுடன் பணிபுரிகிறீர்கள் என்றால் உதவியாக இருக்கும். மூன்றாவது ஐகானைப் பயன்படுத்தி இரண்டு பேனல்களின் இடத்தை ஒன்றோடு ஒன்று (இடது-வலது, மேல்-கீழ்) மாற்றவும்.",
+ "panelTabs": "பேனல் தாவல்கள்",
+ "panelTabsDesc": "கடைசியாக, குழு இரண்டு தாவல்களாக ஒழுங்கமைக்கப்பட்டிருப்பதை நீங்கள் கவனித்திருக்கலாம். இயல்பாகவே இது அமைப்புகள் தாவலைக் காட்டுகிறது, ஏனென்றால் நீங்கள் செல்ல விரும்பும் இடம் இதுதான். ஆனால் முன்னோட்டக் குழுவின் தோற்றத்தை மாற்றுவதற்கான அமைப்புகளைக் கொண்ட இரண்டாவது \"முன்னோட்டம்\" தாவலும் உள்ளது: தீம், வரி எண்களை மறைத்தல் / காண்பித்தல், வரி மடக்குதலை மாற்றுதல், உரை அளவை மாற்றுவது மற்றும் உருவாக்கப்பட்ட வரிசைகளின் எண்ணிக்கையைக் கட்டுப்படுத்துதல்.",
+ "password": "கடவுச்சொல்",
+ "passwordLabel": "கடவுச்சொல்:",
+ "passwordReset": "கடவுச்சொல் மீட்டமைப்பு",
+ "passwordResetAccountExpired": "கடவுச்சொல் மீட்டமை - கணக்கு காலாவதியானது",
+ "passwordResetAccountExpiredDesc": "உங்கள் கடவுச்சொல்லை மீட்டமைக்க ஒரு கோரிக்கையை நாங்கள் பெற்றுள்ளோம், இருப்பினும் உங்கள் கணக்கு ஏற்கனவே காலாவதியானது.",
+ "passwordResetComplete": "உங்கள் கடவுச்சொல் மீட்டமைக்கப்பட்டு, புதிய கடவுச்சொல்லை உங்களுக்கு மின்னஞ்சல் செய்துள்ளீர்கள்.",
+ "passwordResetEmailContent1": "உங்களுடைய கடவுச்சொல் மாற்றப்பட்டது. உள்நுழைய பின்வரும் கடவுச்சொல்லைப் பயன்படுத்தலாம்: %1",
+ "passwordResetEmailContent2": "நீங்கள் உள்நுழைந்ததும் அதை மாற்றவும்.",
+ "passwordResetEmailDesc": "உங்கள் கடவுச்சொல்லை மீட்டமைக்க கோரிக்கை எங்களுக்கு வந்துள்ளது. இந்த மின்னஞ்சலில் நீங்கள் உள்நுழைய பயன்படுத்தக்கூடிய தற்காலிக ஒரு முறை கடவுச்சொல் உள்ளது. உள்நுழைந்த பிறகு, நீங்கள் புதிய கடவுச்சொல்லை அமைக்க வேண்டும்.",
+ "passwordResetMsg": "மின்னஞ்சல் கோப்பில் இருந்தால், உங்கள் கடவுச்சொல் மீட்டமைக்கப்பட்டு, தற்காலிக உள்நுழைவு தகவலுக்கு மின்னஞ்சல் அனுப்பப்பட்டுள்ளது.",
+ "passwordUpdateInvalidPassword": "Your current password is incorrect. Please re-enter.",
+ "passwordUpdated": "Your password has been updated.",
+ "pause": "இடைநிறுத்தம்",
+ "pleaseConfirm": "தயவுசெய்து உறுதிப்படுத்துங்கள்",
+ "pleaseEnterAll": "அனைத்தையும் உள்ளிடவும்",
+ "pleaseEnterDataSetName": "புதிய தரவு தொகுப்பின் பெயரை உள்ளிடவும்.",
+ "pleaseFixErrors": "பின்வரும் பிழைகளை சரிசெய்து மீண்டும் சமர்ப்பிக்கவும்:",
+ "pleaseLogin": "உள்நுழைக",
+ "pleaseSelect": "தயவு செய்து தேர்வு செய்யவும்",
+ "plugins": "செருகுநிரல்கள்",
+ "pressEnterAddItem": "உருப்படியைச் சேர்க்க Enter ஐ அழுத்தவும்",
+ "preview": "முன்னோட்ட",
+ "previewPanel": "முன்னோட்டம் குழு",
+ "previewPanelControlsDesc": "பேனலின் மேல் வலதுபுறத்தில் இரண்டு சின்னங்கள் உள்ளன. முதல் ஐகான் பேனலைப் புதுப்பித்து, ஒவ்வொரு முறையும் வெவ்வேறு சீரற்ற தரவை உருவாக்குகிறது - இது உருவாக்கும் தரவைப் பற்றிய ஒரு கருத்தை உங்களுக்கு வழங்க உதவுகிறது. இரண்டாவது ஐகான் பேனலை முழுவதுமாக மூடுகிறது. பக்கத்தின் கீழ் வலதுபுறத்தில் உள்ள பகுதியைப் பயன்படுத்தி நீங்கள் எப்போதும் பேனலைக் காண்பிக்கலாம் (\"முன்னோட்டம்\" தேர்வுப்பெட்டியை மீண்டும் தேர்ந்தெடுக்கவும்).",
+ "previewPanelDesc": "கட்டம் பேனல் மூலம் நீங்கள் உருவாக்கும்போது, நீங்கள் உருவாக்கும் தரவின் நேரடி முன்னோட்டத்தை இந்த குழு உங்களுக்கு வழங்குகிறது. மேல் இடதுபுறத்தில் உள்ள பொத்தான் உருவாக்கப்படும் தரவின் தேர்ந்தெடுக்கப்பட்ட வடிவமைப்பைக் காட்டுகிறது (எ.கா. SQL, XML, CSV போன்றவை). வடிவமைப்பை மாற்ற, பொத்தானைக் கிளிக் செய்க.",
+ "previewPanelMoreInfo": "இந்த பேனலில் கூடுதல் தகவலுக்கு, முன்னோட்ட பேனல் சுற்றுப்பயணத்தைப் பாருங்கள்.",
+ "previewPanelNoData": "No data!",
+ "previewPanelTourDesc": "மாதிரிக்காட்சி குழு நீங்கள் உருவாக்கும் தரவின் நேரடி, காட்சி பிரதிநிதித்துவத்தை வழங்குகிறது. இந்த சுற்றுப்பயணம் குழு எவ்வாறு செயல்படுகிறது என்பதற்கான விரைவான அறிமுகத்தை அளிக்கிறது.",
+ "previewRows": "வரிசைகளை முன்னோட்டமிடுங்கள்",
+ "private": "தனியார்",
+ "problemLoadingTour": "மன்னிக்கவும், சுற்றுப்பயணத்தை ஏற்றுவதில் சிக்கல் ஏற்பட்டது.",
+ "programmingLanguages": "கணிப்பொறி செயல்பாடு மொழி",
+ "province": "மாகாணம்",
+ "public": "பொது",
+ "publicQ": "பொது?",
+ "reenterPassword": "கடவு சொல்லை திருப்பி உள்ளிடு",
+ "refreshPage": "பக்கத்தைப் புதுப்பிக்கவும்",
+ "refreshPanel": "பேனலைப் புதுப்பிக்கவும்",
+ "register": "பதிவு",
+ "remainingTime": "மீதமுள்ள நேரம்:",
+ "requiredField": "தேவையான புலம்",
+ "revertToVersion": "இந்த பதிப்பிற்கு திரும்புக",
+ "row": "வரிசை",
+ "rowLabel": "நெடுவரிசை தலைப்பு",
+ "rowLabelPlural": "நெடுவரிசை தலைப்புகள்",
+ "rowNumDesc1": "கட்டத்தில் முதல் நெடுவரிசை வரிசை எண்ணைக் காட்டுகிறது. வரிசையை மறுவரிசைப்படுத்த, இந்த உறுப்பைக் கிளிக் செய்து மேலே அல்லது கீழ் நோக்கி இழுக்கவும்.",
+ "rowNumDesc2": "இந்த நெடுவரிசைக்கான தலைப்பு மதிப்பு உங்கள் கட்டத்தில் உள்ள மொத்த வரிசைகளின் எண்ணிக்கையைக் காட்டுகிறது. உங்களிடம் பெரிய தரவுத் தொகுப்புகள் இருக்கும்போது இது எளிது, மேலும் இது திரையில் பொருந்தும் அளவுக்கு பெரியது.",
+ "rowNumber": "வரிசை எண்",
+ "rowSp": "வரிசை (கள்)",
+ "rows": "வரிசைகள்",
+ "rowsGenerated": "வரிசைகள் உருவாக்கப்பட்டன",
+ "rowsGeneratedPerSecond": "வினாடிக்கு வரிசைகள் உருவாக்கப்படுகின்றன",
+ "safariExplanation": "இந்த தளத்திற்கு நவீன வலை தரங்களை செயல்படுத்தும் புதுப்பித்த வலை உலாவி தேவைப்படுகிறது. துரதிர்ஷ்டவசமாக சஃபாரி மற்ற உலாவிகளுக்கு பின்னால் விழுந்துவிட்டது, இந்த ஸ்கிரிப்டை இயக்காது. அதை எவ்வாறு இடமளிப்பது என்பதைக் கண்டுபிடிக்கும் வரை, பின்வரும் உலாவிகளில் ஒன்றைப் பயன்படுத்தவும். அனைத்தும் பதிவிறக்கம் செய்ய இலவசம்.",
+ "save": "சேமி",
+ "saveAs": "என சேமிக்கவும்",
+ "saveButtonDesc": "தளத்தில் உங்களிடம் பயனர் கணக்கு இருந்தால், இந்த பொத்தான் உங்கள் தரவு தொகுப்பை சேமிக்கும். நீங்கள் உள்நுழைந்திருக்கவில்லை என்றால், அதைக் கிளிக் செய்தால் முதலில் உள்நுழைய உங்களைத் தூண்டும். தரவுத் தொகுப்பிற்கான கடைசி %1 மாற்றங்களின் வரலாற்றை கணினி வைத்திருக்கிறது, எனவே நீங்கள் எப்போதும் திரும்பிச் சென்று பழைய பதிப்புகளைக் காணலாம்.",
+ "saveDataSetNewName": "தரவு புதிய பெயராக சேமிக்கவும்",
+ "seconds": "விநாடிகள்",
+ "seeHelpDialog": "உதவி உரையாடலைக் காண்க.",
+ "selectCountry": "நாட்டினை தேர்வுசெய்",
+ "selectDataType": "தரவு வகையைத் தேர்ந்தெடுக்கவும்",
+ "selectEllipsis": "தேர்ந்தெடு ...",
+ "selectExpiryDate": "காலாவதி தேதியைத் தேர்ந்தெடுக்கவும்",
+ "selectLanguage": "மொழியை தேர்ந்தெடுங்கள்",
+ "sendEmail": "மின்னஞ்சல் அனுப்பு",
+ "seriouslySlow": "தீவிரமாக மெதுவாக",
+ "settings": "அமைப்புகள்",
+ "showGrid": "கட்டத்தைக் காட்டு",
+ "showLineNumbers": "வரி எண்களைக் காட்டு",
+ "showPreview": "முன்னோட்டத்தைக் காட்டு",
+ "siteLogo": "தள லோகோ",
+ "size": "அளவு:",
+ "southAmerica": "தென் அமெரிக்கா",
+ "status": "நிலை",
+ "stripWhitespace": "உருவாக்கப்பட்ட உள்ளடக்கத்திலிருந்து இடைவெளியை அகற்றவும்",
+ "success": "வெற்றி",
+ "text": "உரை",
+ "textSize": "உரை அளவு",
+ "theDataSetName": "தரவு தொகுப்பு பெயர்",
+ "theExportTypeBtn": "ஏற்றுமதி வகை பொத்தான்",
+ "theGenerateButton": "உருவாக்கு பொத்தான்",
+ "theGridPanel": "கட்டம் குழு",
+ "thePreviewPanel": "முன்னோட்டம் குழு",
+ "theSaveButton": "சேமி பொத்தான்",
+ "theme": "தீம்",
+ "togglePanelLayout": "பேனல் தளவமைப்பை மாற்று",
+ "totalNumGeneratedRows": "மொத்த எண் உருவாக்கப்பட்ட வரிசைகள்",
+ "tourComplete": "டூர் முடிந்தது",
+ "tourCompleteDesc": "அனைத்தும் முடிந்தது! தொடர கீழே உள்ள பொத்தான்களில் ஒன்றைத் தேர்வுசெய்க.",
+ "tourIntroPara1": "இந்த கருவி நிறைய செயல்பாடுகளை வழங்குகிறது, மேலும் இது முதலில் கொஞ்சம் அதிகமாக இருக்கும். அந்த குறிப்பிட்ட அம்சத்தைப் பற்றி சுற்றுப்பயணம் செய்ய வலதுபுறத்தில் உள்ள பொத்தான்களில் ஒன்றைக் கிளிக் செய்க.",
+ "tourIntroPara2": "சுற்றுப்பயணம் தொடங்கும் போது, இடைமுகத்தைப் பற்றிய சில விஷயங்களை விளக்குவதற்கு ஜெனரேட்டர் பக்கத்தின் உள்ளடக்கத்தை தற்காலிகமாக மேலெழுதும் என்பதை நினைவில் கொள்க. ஆனால் கவலைப்பட வேண்டாம்! சுற்றுப்பயணம் முடிந்தவுடன், அது உங்கள் தரவை அதன் அசல் நிலைக்குத் தருகிறது.",
+ "tryDifferentTour": "வேறு சுற்றுப்பயணத்தை முயற்சிக்கவும்",
+ "update": "புதுப்பிப்பு",
+ "updateAccount": "கணக்கைப் புதுப்பிக்கவும்",
+ "user": "பயனர்",
+ "userAccount": "பயனர் கணக்கு",
+ "userAccountNotFound": "மன்னிக்கவும், உங்கள் கணக்கை எங்களால் கண்டுபிடிக்க முடியவில்லை.",
+ "userAccountUpdated": "பயனர் கணக்கு புதுப்பிக்கப்பட்டது.",
+ "userAccounts": "பயனர் கணக்குகள்",
+ "userNotFound": "மன்னிக்கவும், உங்களை அடையாளம் காண முடியவில்லை. உங்கள் சான்றுகளை சரிபார்க்கவும்.",
+ "validationAccountAlreadyExists": "மன்னிக்கவும், அந்த மின்னஞ்சல் முகவரியுடன் ஒரு கணக்கு ஏற்கனவே உள்ளது.",
+ "validationDifferentNewPasswords": "தயவுசெய்து உங்கள் புதிய கடவுச்சொற்கள் பொருந்துகின்றனவா என்பதை உறுதிப்படுத்தவும்",
+ "validationInvalidEmail": "செல்லுபடியாகும் மின்னஞ்சல் முகவரியை உள்ளிடவும்.",
+ "validationNoCurrentPassword": "தயவுசெய்து உங்கள் தற்போதைய கடவுச்சொல்லை உள்ளிடவும்",
+ "validationNoEmail": "உங்கள் தரவுத்தள பெயரை உள்ளிடவும்.",
+ "validationNoFirstName": "உங்கள் முதல் பெயரை உள்ளிடவும்.",
+ "validationNoLastName": "உங்கள் கடைசி பெயரை உள்ளிடவும்.",
+ "validationNoPassword": "தங்கள் கடவு சொல்லை பதிவு செய்யவும்.",
+ "version": "பதிப்பு",
+ "view": "காண்க",
+ "viewChangelog": "சேஞ்ச்லாக் காண்க",
+ "viewOnGithub": "கிதுபில் காண்க",
+ "welcomeToTheGenerator": "ஜெனரேட்டருக்கு வருக!",
+ "yes": "ஆம்",
+ "yourAccount": "உங்கள் கணக்கு",
+ "yourAccountUpdated": "உங்கள் கணக்கு புதுப்பிக்கப்பட்டுள்ளன.",
+ "yourUserAccount": "உங்கள் பயனர் கணக்கு"
+}
\ No newline at end of file
diff --git a/apps/client/src/i18n/zh.json b/apps/client/src/i18n/zh.json
new file mode 100644
index 000000000..55514480e
--- /dev/null
+++ b/apps/client/src/i18n/zh.json
@@ -0,0 +1,329 @@
+{
+ "abort": "中止",
+ "about": "关于",
+ "aboutInfoPara1": "generatedata.com 是我在旧石器时代晚期(大约 2004 年)创建的一个开源项目,并且在其生命周期内有很多很多贡献者。 每隔几年,我就会使用最新最好的可用技术重写它:这个版本于 2021 年完成,使用 React、Redux、Typescript、GraphQL、Docker、网络工作者、橡皮筋、胶带和一大堆诅咒制成。",
+ "aboutInfoPara2": "您可以在github上找到该项目。 下载,分叉或随意贡献。 请享用!",
+ "aboutThisScript": "关于此脚本",
+ "accountCreated": "账户创建",
+ "accountCreatedDesc": "该帐户已创建。",
+ "accountCreatedMsg": "该账户已创建.",
+ "accountDeleted": "该帐户已被删除。",
+ "accountDisabled": "帐户已禁用",
+ "accountExpiredMsg": "抱歉,您的帐户已过期。",
+ "accountExpiryDate": "账户到期日",
+ "accountInfo": "账户信息",
+ "accountSettings": "账户设置",
+ "accountType": "账户类型",
+ "accounts": "账号",
+ "add": "增加",
+ "addRowsDesc": "要将更多行添加到数据网格,只需使用页面底部的表单即可。 当您有很多字段时,此部分可能会隐藏在页面外,因此您必须将面板滚动到底部才能看到表单。",
+ "addSomeDataDesc": "在网格中添加一些行。",
+ "admin": "管理员",
+ "adminAccount": "管理员账户",
+ "administrator": "行政人员",
+ "africa": "非洲",
+ "allCountries": "所有城市",
+ "allDataTypePlugins": "所有的数据类型插件",
+ "allExportTypePlugins": "所有的导出类型插件",
+ "anonymousAccess": "匿名访问",
+ "asia": "亚洲",
+ "back": "回退",
+ "backToLogin": "回到登入",
+ "blog": "博客",
+ "cancel": "取消",
+ "centralAmerica": "中美洲",
+ "changePassword": "更改密码",
+ "clear": "明确",
+ "clearPage": "清除页面",
+ "clearPageConfirmation": "您确定要清除页面吗? 您所做的任何更改都将丢失。",
+ "clearThePage": "清空该页面",
+ "clickExportTypeBtn": "下一步显示单击按钮时发生的情况。",
+ "close": "关闭",
+ "closePanel": "关闭面板",
+ "columns": "列",
+ "columnsDesc": "网格将显示尽可能多的这些列,具体取决于您可用的屏幕尺寸。 当它太小时,它将隐藏一些列并显示一个齿轮图标,该图标会打开一个缺少内容的信息提示。 接下来的几个步骤以“名称”行为例,说明每个列。",
+ "complete": "完成",
+ "confirmDeleteAccount": "您确定要删除此帐户吗?",
+ "confirmDeleteUserAccount": "确定删除此账号?",
+ "confirmEmptyForm": "确定清空页面吗?",
+ "continue": "继续",
+ "copiedToClipboard": "复制到剪贴板",
+ "copyToClipboard": "复制到剪贴板",
+ "coreExportType": "核心格式",
+ "countries": "国家",
+ "country": "国家",
+ "countrySpecific": "特定国家",
+ "cpuMeltinglyFast": "CPU融化快",
+ "createAccount": "创建账号",
+ "currentPassword": "当前密码",
+ "currentVersion": "当前版本正在编辑",
+ "dataGenerated": "生成的数据。",
+ "dataSet": "数据集",
+ "dataSetHelp": "在这里你可以生成你想要的数据.尝试填充一行并且点击生成按钮.你会很快的掌握它",
+ "dataSetLoaded": "数据集已加载。",
+ "dataSetName": "数据集名称",
+ "dataSetNameDesc": "单击此处的文本以命名您的数据集。 请注意,您需要在该网站上拥有一个帐户并进行登录才能命名您的数据集。 保存数据集时,此名称为您提供了方便的标签来跟踪它们。",
+ "dataSetNameUpdated": "数据集名称已更新。",
+ "dataSetOptions": "数据集选项",
+ "dataSetReverted": "您的数据集已还原为所选的较旧版本。",
+ "dataSetSaved": "您的数据集已保存。",
+ "dataSets": "数据集",
+ "dataType": "数据类型",
+ "dataTypeDesc": "这是一个包含所有可用数据类型的下拉列表。 选择一个值时,它将更新该行的其余列。 每个数据类型都有其自己的唯一设置以允许自定义。",
+ "dataTypes": "数据类型",
+ "dateAccountCreated": "创建账户日期",
+ "dateCreated": "数据创建",
+ "defaultLanguage": "默认语言",
+ "delete": "删除",
+ "delete1DataSet": "删除1个数据集",
+ "deleteAccount": "删除账号",
+ "deleteDataSet": "删除数据集",
+ "deleteDataSetConfirm": "您确定要删除此数据集吗?",
+ "deleteRow": "删除行",
+ "deleteRowDesc": "最后一列包含一个删除图标。 单击它将删除该行。",
+ "developer": "开发者",
+ "developerDoc": "开发人员文档",
+ "disabled": "残障人士",
+ "documentation": "文档",
+ "download": "下载",
+ "edit": "编辑",
+ "editAccount": "编辑账户",
+ "editExportTypePage": "单击“导出类型”按钮时,将显示一个整页视图,其中左侧包含配置选项,而右侧则包含预览面板。",
+ "editExportTypePageConfig": "让我们看一下配置选项。",
+ "editExportTypeSettings": "请修改您的导出类型设置。",
+ "editingExportTypes": "编辑导出类型",
+ "email": "邮件地址",
+ "emailFooterDisclaimer": "如果您收到此电子邮件错误,请与站点管理员联系。",
+ "emailIntroLineWithName": "嗨,%1",
+ "emailLabel": "电子邮件:",
+ "emailNotSent": "我们不能发出邮件通知.",
+ "emailUserLoginInfo": "发送邮件包含用户登录信息",
+ "enterEmailAddressToResetPassword": "输入你的邮箱地址来重置你的密码.",
+ "enterUserAccountDetails": "请输入你的用户详细信息.",
+ "errorCreatingAccount": "创建此帐户时出错。",
+ "estimatedSize": "估计尺寸:",
+ "estimatedTime": "预计的时间:",
+ "europe": "欧洲",
+ "example": "例",
+ "exampleColumn": "示例列",
+ "exampleColumnDesc": "根据您为该行选择的数据类型,此列可能包含内容,也可能不包含内容。 这是某些数据类型提供如何使用它们的预设示例的便捷方法。 例如,“名称”列提供了不同名称格式的下拉列表。 对于所有数据类型,您仍然可以通过“选项”列手动进行配置:它确实是一种节省时间的设备,可让您尽快上手。",
+ "examples": "示例",
+ "exit": "出口",
+ "expired": "已到期",
+ "expiryDate": "到期日",
+ "exportType": "出口类型",
+ "exportTypeBtnDesc": "面板左上方的按钮是您要选择和配置要生成的数据格式的位置。 在这里将其设置为Typescript。",
+ "exportTypeOptionsSql": "导出类型选项:SQL",
+ "exportTypeOptionsSqlDesc": "如您所见,SQL还有许多其他特定于此导出类型的字段。 其他导出类型具有不同的设置。",
+ "exportTypeOptionsTs": "导出类型选项:打字稿",
+ "exportTypeOptionsTsDesc": "根据所选的“导出类型”,面板主体可能包含其他选项。 这里的Typescript有两个字段,可让您自定义类型名称和导出的变量名称。",
+ "exportTypeOptionsTsDesc2": "现在让我们看一下SQL。",
+ "exportTypeSelection": "导出类型选择",
+ "exportTypeSelectionDesc": "格式下拉菜单可让您选择所生成内容的格式。 有很多选项,其中一些是核心类型:SQL,CSV,SQL,JSON等,其中一些用于为特定编程语言生成数据。",
+ "exportTypes": "导出类型",
+ "filterDataTypes": "筛选数据类型",
+ "financial": "金融",
+ "firstName": "名字",
+ "forgottenYourPasswordQ": "忘记密码?",
+ "format": "格式",
+ "generate": "生成",
+ "generateBtnDesc": "完成数据集的配置并确认其外观后,现在该生成大量数据了! 单击此按钮生成您的数据。 根据数据网格中的行数和要生成的行数,这可能需要一些时间。",
+ "generated": "生成",
+ "generatedC": "生成:",
+ "generator": "生成器",
+ "geo": "Geo",
+ "grid": "格",
+ "gridPanelTourDesc1": "网格面板是您构造要生成的数据的地方。 在这里,您可以选择不同的数据类型,配置方式以及显示顺序。 在此处的示例中,我们有五个字段:姓名,电话,电子邮件,街道地址和城市。",
+ "gridPanelTourDesc2": "有关此部分的更多详细信息,请参见“网格面板”上的导览。",
+ "gridPanelTourIntroDesc1": "在网格面板中,您可以定义要生成的数据。 对于此游览,我们隐藏了预览面板(请参阅页面右下方的未选中的“预览”按钮),以便为我们提供更多的空间。 我们还添加了10行不同的数据类型,仅用于说明目的。",
+ "gridPanelTourIntroDesc2": "让我们从网格列开始。",
+ "help": "帮助",
+ "helpIcon": "帮助图标",
+ "helpIconDesc": "当您在上一列中选择数据类型时,此处将显示一个图标。 单击它会打开一个包含使用情况信息的帮助对话框。",
+ "hide": "隐藏",
+ "hideShowGrid": "隐藏/显示网格",
+ "hideShowPreviewPanel": "隐藏/显示预览面板",
+ "history": "历史",
+ "historyPanelDesc": "此面板显示对数据集进行更改的历史记录。 单击查看按钮以检查早期版本。 要还原为早期版本,只需查看它,然后单击“还原为该版本”按钮即可。",
+ "hostName": "主机名称",
+ "humanData": "人类数据",
+ "ifWantToReregister": "如果您想重新注册,请访问:",
+ "incomplete": "不完整",
+ "install": "安装",
+ "introToGenerator": "发电机简介",
+ "introToGeneratorDesc": "此导览向您简要介绍了数据生成器的主要功能。 让我们开始吧!",
+ "invalidSettings": "设置无效!",
+ "language": "中文",
+ "lastEdited": "最新编辑",
+ "lastLoggedIn": "上次登入",
+ "lastModified": "最新修正",
+ "lastName": "姓氏",
+ "lastSaved": "最新保存",
+ "lineWrapping": "换行",
+ "linkToDataSet": "连接数据集",
+ "linkToThisDataSet": "链接到该数据",
+ "live": "居住",
+ "load": "加载",
+ "loading": "加载...",
+ "login": "登录",
+ "loginToSave": "为了保存您的数据集,您首先需要一个帐户。 请在下方登录。",
+ "loginOrRegisterToSave": "为了保存您的数据集,您首先需要一个帐户。 请在下方登录或注册。",
+ "loginUrlLabel": "登入网址:",
+ "logout": "登出",
+ "makeDataSetPublicAgreement": "我要分享该数据集,并且需要公开它.",
+ "metaDescription": "GenerateData.com: 用于生成测试软件中自定义随机数据,采用免费GNU协议",
+ "metaKeywords": "随机数据, 测试数据, 示例数据, 数据生成器, 生成数据",
+ "missingDataSetName": "请输入您的数据集名称",
+ "nameColumn": "名称栏",
+ "nameColumnDesc": "根据您在预览面板中选择的导出类型(XML,CSV等),此列的标题会有所不同。 该列的目的是为该行提供一个标识符,然后可以在生成的数据中使用该标识符。 例如,XML使用此值作为XML节点名称。 JSON使用它作为JSON属性名称; SQL将其用作数据库列名称。 该应用程序将自动验证您在此处输入的值,以确保该值对所选的“导出类型”有效。",
+ "newDataSet": "新数据集",
+ "no": "取消",
+ "noAccountsCreated": "尚未创建任何帐户。",
+ "noAdditionalSettings": "没有其他设置",
+ "noDataSetsSaved": "您没有保存任何数据集。",
+ "noExamplesAvailable": "没有可用的示例.",
+ "noExpiry": "没有有效期",
+ "noHistory": "尚无历史。",
+ "noOptionsAvailable": "没有可用选项.",
+ "noSafari": "抱歉,没有Safari!",
+ "northAmerica": "北美州",
+ "nowLoggedIn": "您已经登录。",
+ "nowLoggedOut": "您已注销。",
+ "numResults": "结果数",
+ "numeric": "数值型",
+ "oceania": "大洋洲",
+ "oneTimePasswordLogin": "您已经使用一次性密码登录。 请立即设置一个新密码。",
+ "open": "打开",
+ "options": "选项",
+ "optionsColumn": "选项栏",
+ "optionsColumnDesc1": "此列包含此行的数据类型的所有配置选项。 某些数据类型的配置设置太多,无法在如此小的空间(例如,国家/地区)中显示,因此它们可能仅在网格中显示一个按钮即可打开包含所有选项的对话框。",
+ "optionsColumnDesc2": "对于此处说明的Names数据类型,该列包含一个Creatable Pill字段。 这是数据生成器中的常见字段类型。 它使您可以通过键入内容并单击来添加项目。 然后,将这些物品转换为不同的药丸,可以通过“ x”图标将其删除。",
+ "or": "要么",
+ "order": "顺序",
+ "other": "其他",
+ "overMaxAnonRows": "抱歉,您只能生成%1行。 要生成大量数据,您需要在此站点上有一个用户帐户。",
+ "panelContents": "面板内容",
+ "panelContentsDesc": "预览面板的大部分包含根据您在网格面板和选定的“导出类型”设置中指定的数据生成的数据的样本。 它以Typescript格式生成5种不同的数据类型(国家,地区,城市,街道地址和邮政/邮政编码)。",
+ "panelControls": "面板控制",
+ "panelControlsClearIconDesc": "面板控件中的最后一个图标使您可以清除页面,以便重新开始。",
+ "panelControlsDesc": "面板控制按钮使您可以隐藏/显示“网格”和“预览”面板,如果您使用的屏幕空间有限,这将很有帮助。 您也可以使用第三个图标,将两个面板的位置相对于彼此切换(左右,上下)。",
+ "panelTabs": "面板标签",
+ "panelTabsDesc": "最后,您可能已经注意到该面板分为两个选项卡。 默认情况下,它显示“设置”选项卡,因为这是您最想去的地方。 但是还有第二个“预览”选项卡,其中包含用于更改预览面板外观的设置:主题,隐藏/显示行号,切换换行,更改文本大小以及控制生成的行数。",
+ "password": "密码",
+ "passwordLabel": "密码:",
+ "passwordReset": "重设密码",
+ "passwordResetAccountExpired": "密码重置-帐户已过期",
+ "passwordResetAccountExpiredDesc": "我们刚收到重置密码的请求,但是您的帐户已经过期。",
+ "passwordResetComplete": "你的密码已重置,已经发出新密码邮件.",
+ "passwordResetEmailContent1": "你的密码已重置.你可以使用当前密码登录: %1",
+ "passwordResetEmailContent2": "一旦你已经登录,请修改它.",
+ "passwordResetEmailDesc": "我们已收到重设密码的请求。 该电子邮件包含一个临时的一次性密码,您可以使用该密码登录。登录后,您需要设置一个新密码。",
+ "passwordResetMsg": "如果电子邮件已归档,则您的密码已重置,并且已通过电子邮件发送给您临时登录信息。",
+ "passwordUpdateInvalidPassword": "您当前的密码错误。 请重新输入。",
+ "passwordUpdated": "您的密码已更新。",
+ "pause": "暂停",
+ "pleaseConfirm": "请确认",
+ "pleaseEnterAll": "请输入全部内容",
+ "pleaseEnterDataSetName": "请输入新数据集的名称.",
+ "pleaseFixErrors": "请修正下述错误然后重新提交:",
+ "pleaseLogin": "请登录",
+ "pleaseSelect": "请选择",
+ "plugins": "插件",
+ "pressEnterAddItem": "按Enter键添加项目",
+ "preview": "预习",
+ "previewPanel": "预览面板",
+ "previewPanelControlsDesc": "在面板的右上角有两个图标。 第一个图标刷新面板,每次生成不同的随机数据-这有助于您了解其生成的数据种类。 第二个图标完全关闭面板。 您始终可以使用页面右下角的部分再次显示面板(只需重新选择“预览”复选框)。",
+ "previewPanelDesc": "通过网格面板构造数据时,该面板为您提供了所生成数据的实时预览。 左上角的按钮显示了所生成数据的选定格式(例如SQL,XML,CSV等)。 要更改格式,只需单击按钮。",
+ "previewPanelMoreInfo": "有关此面板的更多信息,请查看“预览面板”导览。",
+ "previewPanelNoData": "没有数据!",
+ "previewPanelTourDesc": "预览面板为您在构建数据时提供了实时,可视化的表示形式。 此导览简要介绍了面板的工作原理。",
+ "previewRows": "预览行",
+ "private": "私人的",
+ "problemLoadingTour": "抱歉,加载游览时出现问题。",
+ "programmingLanguages": "编程语言",
+ "province": "省",
+ "public": "上市",
+ "publicQ": "公开?",
+ "reenterPassword": "重新输入密码",
+ "refreshPage": "刷新页面",
+ "refreshPanel": "刷新面板",
+ "register": "寄存器",
+ "remainingTime": "剩余时间:",
+ "requiredField": "必填项目",
+ "revertToVersion": "还原到此版本",
+ "row": "行",
+ "rowLabel": "列标题",
+ "rowLabelPlural": "列标题",
+ "rowNumDesc1": "网格中的第一列显示行号。 要重新排列该行,只需单击并上下拖动此元素。",
+ "rowNumDesc2": "此列的标题值显示网格中的总行数。 当您有非常大的数据集并且太大而无法容纳在屏幕上时,这可能会很方便。",
+ "rowNumber": "行号",
+ "rowSp": "行数",
+ "rows": "行",
+ "rowsGenerated": "列生成",
+ "rowsGeneratedPerSecond": "每秒产生的行",
+ "safariExplanation": "该站点需要实现现代Web标准的最新Web浏览器。 不幸的是,Safari落后于其他浏览器,并且无法运行此脚本。 在弄清楚如何容纳它之前,请使用以下浏览器之一。 全部免费下载。",
+ "save": "保存",
+ "saveAs": "另存为",
+ "saveButtonDesc": "如果您在网站上拥有一个用户帐户,则此按钮将保存您的数据集。 如果您尚未登录,则单击它会提示您先登录。 系统保留对数据集的最近%1更改的历史记录,因此您可以随时返回查看旧版本。",
+ "saveDataSetNewName": "将数据集另存为新名称",
+ "seconds": "秒",
+ "seeHelpDialog": "查看帮助对话框.",
+ "selectCountry": "选择国家",
+ "selectDataType": "选择数据类型",
+ "selectEllipsis": "选择...",
+ "selectExpiryDate": "选择有效期",
+ "selectLanguage": "选择语言",
+ "sendEmail": "发送电子邮件",
+ "seriouslySlow": "严重慢",
+ "settings": "设置",
+ "showGrid": "显示网格",
+ "showLineNumbers": "显示行号",
+ "showPreview": "显示预览",
+ "siteLogo": "网站徽标",
+ "size": "尺寸:",
+ "southAmerica": "南美洲",
+ "status": "状态",
+ "stripWhitespace": "从生成的内容中删除空格",
+ "success": "成功",
+ "text": "文本",
+ "textSize": "字体大小",
+ "theDataSetName": "数据集名称",
+ "theExportTypeBtn": "导出类型按钮",
+ "theGenerateButton": "生成按钮",
+ "theGridPanel": "网格面板",
+ "thePreviewPanel": "预览面板",
+ "theSaveButton": "保存按钮",
+ "theme": "主题",
+ "togglePanelLayout": "切换面板布局",
+ "totalNumGeneratedRows": "总生成行数",
+ "tourComplete": "游览完成",
+ "tourCompleteDesc": "全做完了! 选择下面的按钮之一继续。",
+ "tourIntroPara1": "该工具提供了很多功能,一开始可能有点让人不知所措。 单击右侧的按钮之一,以浏览该特定功能。",
+ "tourIntroPara2": "请注意,当浏览开始时,它会临时覆盖生成器页面的内容,以说明有关界面的某些内容。 但是不用担心! 游览结束后,它将使您的数据恢复为原始状态。",
+ "tryDifferentTour": "尝试不同的游览",
+ "update": "更新",
+ "updateAccount": "更新账号",
+ "user": "用户",
+ "userAccount": "用户账户",
+ "userAccountNotFound": "抱歉,我们找不到您的帐户。",
+ "userAccountUpdated": "用户帐户已更新。",
+ "userAccounts": "用户账户",
+ "userNotFound": "抱歉,我们无法识别您。 请检查您的凭据。",
+ "validationAccountAlreadyExists": "对不起,一个账户的邮件地址已存在.",
+ "validationDifferentNewPasswords": "请确保您的新密码匹配",
+ "validationInvalidEmail": "请输入一个有效的邮件地址.",
+ "validationNoCurrentPassword": "请输入您的当前密码",
+ "validationNoEmail": "请输入邮件地址.",
+ "validationNoFirstName": "请输入你的名字.",
+ "validationNoLastName": "请输入你的姓氏.",
+ "validationNoPassword": "请输入密码.",
+ "version": "版本",
+ "view": "看法",
+ "viewChangelog": "查看变更日志",
+ "viewOnGithub": "在Github上查看",
+ "welcomeToTheGenerator": "欢迎使用发电机!",
+ "yes": "确认",
+ "yourAccount": "你的账号",
+ "yourAccountUpdated": "你的账户已更新.",
+ "yourUserAccount": "您的用户帐号"
+}
\ No newline at end of file
diff --git a/apps/client/src/images/bg-pale.png b/apps/client/src/images/bg-pale.png
new file mode 100644
index 000000000..ec0f804e5
Binary files /dev/null and b/apps/client/src/images/bg-pale.png differ
diff --git a/apps/client/src/images/bg.png b/apps/client/src/images/bg.png
new file mode 100644
index 000000000..d3a0057d4
Binary files /dev/null and b/apps/client/src/images/bg.png differ
diff --git a/apps/client/src/images/btn_google_signin_dark_normal_web@2x.png b/apps/client/src/images/btn_google_signin_dark_normal_web@2x.png
new file mode 100644
index 000000000..f27bb2433
Binary files /dev/null and b/apps/client/src/images/btn_google_signin_dark_normal_web@2x.png differ
diff --git a/apps/client/src/images/chrome_256x256.png b/apps/client/src/images/chrome_256x256.png
new file mode 100644
index 000000000..a8ae85e59
Binary files /dev/null and b/apps/client/src/images/chrome_256x256.png differ
diff --git a/apps/client/src/images/dice180x180.png b/apps/client/src/images/dice180x180.png
new file mode 100644
index 000000000..e1df05f12
Binary files /dev/null and b/apps/client/src/images/dice180x180.png differ
diff --git a/apps/client/src/images/dice512x512.png b/apps/client/src/images/dice512x512.png
new file mode 100644
index 000000000..8143425fa
Binary files /dev/null and b/apps/client/src/images/dice512x512.png differ
diff --git a/apps/client/src/images/dice80.png b/apps/client/src/images/dice80.png
new file mode 100644
index 000000000..10fc03e9d
Binary files /dev/null and b/apps/client/src/images/dice80.png differ
diff --git a/apps/client/src/images/edge_256x256.png b/apps/client/src/images/edge_256x256.png
new file mode 100644
index 000000000..f80a90488
Binary files /dev/null and b/apps/client/src/images/edge_256x256.png differ
diff --git a/apps/client/src/images/favicon.ico b/apps/client/src/images/favicon.ico
new file mode 100644
index 000000000..3635c424d
Binary files /dev/null and b/apps/client/src/images/favicon.ico differ
diff --git a/apps/client/src/images/firefox_256x256.png b/apps/client/src/images/firefox_256x256.png
new file mode 100644
index 000000000..dc974fbff
Binary files /dev/null and b/apps/client/src/images/firefox_256x256.png differ
diff --git a/apps/client/src/images/flags.png b/apps/client/src/images/flags.png
new file mode 100644
index 000000000..47a559c87
Binary files /dev/null and b/apps/client/src/images/flags.png differ
diff --git a/apps/client/src/images/logo.png b/apps/client/src/images/logo.png
new file mode 100644
index 000000000..0df5386f0
Binary files /dev/null and b/apps/client/src/images/logo.png differ
diff --git a/apps/client/src/index.html b/apps/client/src/index.html
new file mode 100644
index 000000000..07b7ba63b
--- /dev/null
+++ b/apps/client/src/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ generatedata.com
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/client/src/index.tsx b/apps/client/src/index.tsx
new file mode 100644
index 000000000..dc4a85ef5
--- /dev/null
+++ b/apps/client/src/index.tsx
@@ -0,0 +1,6 @@
+/* istanbul ignore file */
+import * as React from 'react';
+import * as ReactDOM from 'react-dom';
+import App from './app';
+
+ReactDOM.render( , document.getElementById('root'));
diff --git a/apps/client/src/resources/ambience.css b/apps/client/src/resources/ambience.css
new file mode 100644
index 000000000..907dd6aea
--- /dev/null
+++ b/apps/client/src/resources/ambience.css
@@ -0,0 +1,196 @@
+.themeAmbiance .cm-s-ambiance .cm-header {
+ color: blue;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-quote {
+ color: #24C2C7;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-keyword {
+ color: #cda869;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-atom {
+ color: #CF7EA9;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-number {
+ color: #78CF8A;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-def {
+ color: #aac6e3;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-variable {
+ color: #ffb795;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-variable-2 {
+ color: #eed1b3;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-variable-3, .themeAmbiance .cm-s-ambiance .cm-type {
+ color: #faded3;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-property {
+ color: #eed1b3;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-operator {
+ color: #fa8d6a;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-comment {
+ color: #555;
+ font-style: italic;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-string {
+ color: #8f9d6a;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-string-2 {
+ color: #9d937c;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-meta {
+ color: #D2A8A1;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-qualifier {
+ color: yellow;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-builtin {
+ color: #9999cc;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-bracket {
+ color: #24C2C7;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-tag {
+ color: #fee4ff;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-attribute {
+ color: #9B859D;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-hr {
+ color: pink;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-link {
+ color: #F4C20B;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-special {
+ color: #FF9D00;
+}
+
+.themeAmbiance .cm-s-ambiance .cm-error {
+ color: #AF2018;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-matchingbracket {
+ color: #0f0;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-nonmatchingbracket {
+ color: #f22;
+}
+
+.themeAmbiance .cm-s-ambiance div.CodeMirror-selected {
+ background: rgba(255, 255, 255, 0.15);
+}
+
+.themeAmbiance .cm-s-ambiance.CodeMirror-focused div.CodeMirror-selected {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-line::selection, .themeAmbiance .cm-s-ambiance .CodeMirror-line > span::selection, .themeAmbiance .cm-s-ambiance .CodeMirror-line > span > span::selection {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-line::-moz-selection, .themeAmbiance .cm-s-ambiance .CodeMirror-line > span::-moz-selection, .themeAmbiance .cm-s-ambiance .CodeMirror-line > span > span::-moz-selection {
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.themeAmbiance .cm-s-ambiance.CodeMirror {
+ line-height: 1.4em;
+ color: #E6E1DC;
+ background-color: #202020;
+ -webkit-box-shadow: inset 0 0 10px black;
+ -moz-box-shadow: inset 0 0 10px black;
+ box-shadow: inset 0 0 10px black;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-gutters {
+ background: #3D3D3D;
+ border-right: 1px solid #4D4D4D;
+ box-shadow: 0 10px 20px black;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-linenumber {
+ text-shadow: 0px 1px 1px #4d4d4d;
+ color: #111;
+ padding: 0 5px;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-guttermarker {
+ color: #aaa;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-guttermarker-subtle {
+ color: #111;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-cursor {
+ border-left: 1px solid #7991E8;
+}
+
+.themeAmbiance .cm-s-ambiance .CodeMirror-activeline-background {
+ background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.031);
+}
+
+.themeAmbiance .cm-s-ambiance.CodeMirror,
+.themeAmbiance .cm-s-ambiance .CodeMirror-gutters {
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC");
+}
+
+/* ---------------- */
+
+body .themeAmbiance {
+ background-color: #202020;
+ color: #cccccc;
+ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAQAAAAHUWYVAABFFUlEQVQYGbzBCeDVU/74/6fj9HIcx/FRHx9JCFmzMyGRURhLZIkUsoeRfUjS2FNDtr6WkMhO9sm+S8maJfu+Jcsg+/o/c+Z4z/t97/vezy3z+z8ekGlnYICG/o7gdk+wmSHZ1z4pJItqapjoKXWahm8NmV6eOTbWUOp6/6a/XIg6GQqmenJ2lDHyvCFZ2cBDbmtHA043VFhHwXxClWmeYAdLhV00Bd85go8VmaFCkbVkzlQENzfBDZ5gtN7HwF0KDrTwJ0dypSOzpaKCMwQHKTIreYIxlmhXTzTWkVm+LTynZhiSBT3RZQ7aGfjGEd3qyXQ1FDymqbKxpspERQN2MiRjNZlFFQXfCNFm9nM1zpAsoYjmtRTc5ajwuaXc5xrWskT97RaKzAGe5ARHhVUsDbjKklziiX5WROcJwSNCNI+9w1Jwv4Zb2r7lCMZ4oq5C0EdTx+2GzNuKpJ+iFf38JEWkHJn9DNF7mmBDITrWEg0VWL3pHU20tSZnuqWu+R3BtYa8XxV1HO7GyD32UkOpL/yDloINFTmvtId+nmAjxRw40VMwVKiwrKLE4bK5UOVntYwhOcSSXKrJHKPJedocpGjVz/ZMIbnYUPB10/eKCrs5apqpgVmWzBYWpmtKHecJPjaUuEgRDDaU0oZghCJ6zNMQ5ZhDYx05r5v2muQdM0EILtXUsaKiQX9WMEUotagQzFbUNN6NUPC2nm5pxEWGCjMc3GdJHjSU2kORLK/JGSrkfGEIjncU/CYUnOipoYemwj8tST9NsJmB7TUVXtbUtXATJVZXBMvYeTXJfobgJUPmGMP/yFaWonaa6BcFO3nqcIqCozSZoZoSr1g4zJOzuyGnxTEX3lUEJ7WcZgme8ddaWvWJo2AJR9DZU3CUIbhCSG6ybSwN6qtJVnCU2svDTP2ZInOw2cBTrqtQahtNZn9NcJ4l2NaSmSkkP1noZWnVwkLmdUPOwLZEwy2Z3S3R+4rIG9hcbpPXHFVWcQdZkn2FOta3cKWQnNRC5g1LsJah4GCzSVsKnCOY5OAFRTBekyyryeyilhFKva75r4Mc0aWanGEaThcy31s439KKxTzJYY5WTHPU1FtIHjQU3Oip4xlNzj/lBw23dYZVliQa7WAXf4shetcQfatI+jWRDBPmyNeW6A1P5kdDgyYJlba0BIM8BZu1JfrFwItyjcAMR3K0BWOIrtMEXyhyrlVEx3ui5dUBjmB/Q3CXW85R4mBD0s7B+4q5tKUjOlb9qqmhi5AZ6GFIC5HXtOobdYGlVdMVbNJ8toNTFcHxnoL+muBagcctjWnbNMuR00uI7nQESwg5q2qqrKWIfrNUmeQocY6HuyxJV02wj36w00yhpmUFenv4p6fUkZYqLyuinx2RGOjhCXYyJF84oiU00YMOOhhquNdfbOB7gU88pY4xJO8LVdp6/q2voeB4R04vIdhSE40xZObx1HGGJ/ja0LBthFInKaLPPFzuCaYaoj8JjPME8yoyxo6zlBqkiUZYgq00OYMswbWO5NGmq+xhipxHLRW29ARjNKXO0wRnear8XSg4XFPLKEPUS1GqvyLwiuBUoa7zpZ0l5xxFwWmWZC1H5h5FwU8eQ7K+g8UcVY6TMQreVQT/8uQ8Z+ALIXnSEa2pYZQneE9RZbSBNYXfWYJzW/h/4j4Dp1tYVcFIC5019Vyi4ThPqSFCzjGWaHQTBU8q6vrVwgxP9Lkm840imWKpcLCjYTtrKuwvsKSnrvHCXGkSMk9p6lhckfRpIeis+N2PiszT+mFLspyGleUhDwcLrZqmyeylxwjBcKHEapqkmyangyLZRVOijwOtCY5SsG5zL0OwlCJ4y5KznF3EUNDDrinwiyLZRzOXtlBbK5ITHFGLp8Q0R6ab6mS7enI2cFrxOyHvOCFaT1HThS1krjCwqWeurCkk+willhCC+RSZnRXBiZaC5RXRIZYKp2lyfrHwiKPKR0JDzrdU2EFgpidawlFDR6FgXUMNa+g1FY3bUQh2cLCwosRdnuQTS/S+JVrGLeWIvtQUvONJxlqSQYYKpwoN2kaocLjdVsis4Mk80ESF2YpSkzwldjHkjFCUutI/r+EHDU8oCs6yzL3PhWiEooZdFMkymlas4AcI3KmoMMNSQ3tHzjGWCrcJJdYyZC7QFGwjRL9p+MrRkAGWzIaWCn9W0F3TsK01c2ZvQw0byvxuQU0r1lM0qJO7wW0kRIMdDTtXEdzi4VIh+EoIHm0mWtAtpCixlabgn83fKTI7anJe9ST7WIK1DMGpQmYeA58ImV6ezOGOzK2Kgq01pd60cKWiUi9Lievb/0vIDPHQ05Kzt4ddPckQBQtoaurjyHnek/nKzpQLrVgKPjIkh2v4uyezpv+Xoo7fPFXaGFp1vaLKxQ4uUpQQS5VuQs7BCq4xRJv7fwpVvvFEB3j+620haOuocqMhWd6TTPAEx+mdFNGHdranFe95WrWmIvlY4F1Dle2ECgc6cto7SryuqGGGha0tFQ5V53migUKmg6XKAo4qS3mik+0OZpAhOLeZKicacgaYcyx5hypYQE02ZA4xi/pNhOQxR4klNKyqacj+mpxnLTnnGSo85++3ZCZq6lrZkXlGEX3o+C9FieccJbZWVFjC0Yo1FZnJhoYMFoI1hEZ9r6hwg75HwzBNhbZCdJEfJwTPGzJvaKImw1yYX1HDAmpXR+ZJQ/SmgqMNVQb5vgamGwLtt7VwvP7Qk1xpiM5x5Cyv93E06MZmgs0Nya2azIKOYKCGBQQW97RmhKNKF02JZqHEJ4o58qp7X5EcZmc56trXEqzjCBZ1MFGR87Ql2tSTs6CGxS05PTzRQorkbw7aKoKXFDXsYW42VJih/q+FP2BdTzDTwVqOYB13liM50vG7wy28qagyuIXMeQI/Oqq8bcn5wJI50xH00CRntyfpL1T4hydYpoXgNiFzoIUTDZnLNRzh4TBHwbYGDvZkxmlyJloyr6tRihpeUG94GnKtIznREF0tzJG/OOr73JBcrSh1k6WuTprgLU+mnSGnv6Zge0NNz+kTDdH8nuAuTdJDCNb21LCiIuqlYbqGzT3RAoZofQfjFazkqeNWdYaGvYTM001EW2oKPvVk1ldUGSgUtHFwjKM1h9jnFcmy5lChoLNaQMGGDsYbKixlaMBmmsx1QjCfflwTfO/gckW0ruZ3jugKR3R5W9hGUWqCgxuFgsuaCHorotGKzGaeZB9DMsaTnKCpMtwTvOzhYk0rdrArKCqcaWmVk1+F372ur1YkKxgatI8Qfe1gIX9wE9FgS8ESmuABIXnRUbCapcKe+nO7slClSZFzpV/LkLncEb1qiO42fS3R855Su2mCLh62t1SYZZYVmKwIHjREF2uihTzB20JOkz7dkxzYQnK0UOU494wh+VWRc6Un2kpTaVgLDFEkJ/uhzRcI0YKGgpGWOlocBU/a4fKoJ/pEaNV6jip3+Es9VXY078rGnmAdf7t9ylPXS34RBSuYPs1UecZTU78WanhBCHpZ5sAoTz0LGZKjPf9TRypqWEiTvOFglL1fCEY3wY/++rbk7C8bWebA6p6om6PgOL2kp44TFJlVNBXae2rqqdZztOJpT87GQsE9jqCPIe9VReZuQ/CIgacsyZdCpIScSYqcZk8r+nsyCzhyfhOqHGOIvrLknC8wTpFcaYiGC/RU1NRbUeUpocQOnkRpGOrIOcNRx+1uA0UrzhSSt+VyS3SJpnFWkzNDqOFGIWcfR86DnmARTQ1HKIL33ExPiemeOhYSSjzlSUZZuE4TveoJLnBUOFof6KiysCbnAEcZgcUNTDOwkqWu3RWtmGpZwlHhJENdZ3miGz0lJlsKnjbwqSHQjpxnFDlTLLwqJPMZMjd7KrzkSG7VsxXBZE+F8YZkb01Oe00yyRK9psh5SYh29ySPKBo2ylNht7ZkZnsKenjKNJu9PNEyZpaCHv4Kt6RQsLvAVp7M9kIimmCUwGeWqLMmGuIotYMmWNpSahkhZw9FqZsVnKJhsjAHvtHMsTM9fCI06Dx/u3vfUXCqfsKRc4oFY2jMsoo/7DJDwZ1CsIKnJu+J9ldkpmiCxQx1rWjI+T9FwcWWzOuaYH0Hj7klNRVWEQpmaqosakiGNTFHdjS/qnUdmf0NJW5xsL0HhimCCZZSRzmSPTXJQ4aaztAwtZnoabebJ+htCaZ7Cm535ByoqXKbX1WRc4Eh2MkRXWzImVc96Cj4VdOKVxR84VdQsIUM8Psoou2byVHyZFuq7O8otbSQ2UAoeEWTudATLGSpZzVLlXVkPU2Jc+27lsw2jmg5T5VhbeE3BT083K9WsTTkFU/Osi0rC5lRlpwRHUiesNS0sOvmqGML1aRbPAxTJD9ZKtxuob+hhl8cwYGWpJ8nub7t5p6coYbMovZ1BTdaKn1jYD6h4GFDNFyT/Kqe1XCXphXHOKLZmuRSRdBPEfVUXQzJm5YGPGGJdvAEr7hHNdGZnuBvrpciGmopOLf5N0uVMy0FfYToJk90uUCbJupaVpO53UJXR2bVpoU00V2KOo4zMFrBd0Jtz2pa0clT5Q5L8IpQ177mWQejPMEJhuQjS10ref6HHjdEhy1P1EYR7GtO0uSsKJQYLiTnG1rVScj5lyazpqWGl5uBbRWl7m6ixGOOnEsMJR7z8J0n6KMnCdxhiNYQCoZ6CmYLnO8omC3MkW3bktlPmEt/VQQHejL3+dOE5FlPdK/Mq8hZxxJtLyRrepLThYKbLZxkSb5W52vYxNOaOxUF0yxMUPwBTYqCzy01XayYK0sJyWBLqX0MwU5CzoymRzV0EjjeUeLgDpTo6ij42ZAzvD01dHUUTPLU96MdLbBME8nFBn7zJCMtJcZokn8YoqU0FS5WFKyniHobguMcmW8N0XkWZjkyN3hqOMtS08r+/xTBwpZSZ3qiVRX8SzMHHjfUNFjgHEPmY9PL3ykEzxkSre/1ZD6z/NuznuB0RcE1TWTm9zRgfUWVJiG6yrzgmWPXC8EAR4Wxhlad0ZbgQyEz3pG5RVEwwDJH2mgKpjcTiCOzn1lfUWANFbZ2BA8balnEweJC9J0iuaeZoI+ippFCztEKVvckR2iice1JvhVytrQwUAZpgsubCPaU7xUe9vWnaOpaSBEspalykhC9bUlOMpT42ZHca6hyrqKmw/wMR8H5ZmdFoBVJb03O4UL0tSNnvIeRmkrLWqrs78gcrEn2tpcboh0UPOW3UUR9PMk4T4nnNKWmCjlrefhCwxRNztfmIQVdDElvS4m1/WuOujoZCs5XVOjtKPGokJzsYCtFYoWonSPT21DheU/wWhM19FcElwqNGOsp9Q8N/cwXaiND1MmeL1Q5XROtYYgGeFq1aTMsoMmcrKjQrOFQTQ1fmBYhmW6o8Jkjc7iDJRTBIo5kgJD5yMEYA3srCg7VFKwiVJkmRCc5ohGOKhsYMn/XBLdo5taZjlb9YAlGWRimqbCsoY7HFAXLa5I1HPRxMMsQDHFkWtRNniqT9UEeNjcE7RUlrCJ4R2CSJuqlKHWvJXjAUNcITYkenuBRB84TbeepcqTj3zZyFJzgYQdHnqfgI0ddUwS6GqWpsKWhjq9cV0vBAEMN2znq+EBfIWT+pClYw5xsTlJU6GeIBsjGmmANTzJZiIYpgrM0Oa8ZMjd7NP87jxhqGOhJlnQtjuQpB+8aEE00wZFznSJPyHxgH3HkPOsJFvYk8zqCHzTs1BYOa4J3PFU+UVRZxlHDM4YavlNUuMoRveiZA2d7grMNc2g+RbSCEKzmgYsUmWmazFJyoiOZ4KnyhKOGRzWJa0+moyV4TVHDzn51Awtqaphfk/lRQ08FX1iiqxTB/kLwd0VynKfEvI6cd4XMV5bMhZ7gZUWVzYQ6Nm2BYzxJbw3bGthEUUMfgbGeorae6DxHtJoZ6alhZ0+ytiVoK1R4z5PTrOECT/SugseEOlb1MMNR4VRNcJy+V1Hg9ONClSZFZjdHlc6W6FBLdJja2MC5hhpu0DBYEY1TFGwiFAxRRCsYkiM9JRb0JNMVkW6CZYT/2EiTGWmo8k+h4FhDNE7BvppoTSFnmCV5xZKzvcCdDo7VVPnIU+I+Rc68juApC90MwcFCsJ5hDqxgScYKreruyQwTqrzoqDCmhWi4IbhB0Yrt3RGa6GfDv52rKXWhh28dyZaWUvcZeMTBaZoSGyiCtRU5J8iviioHaErs7Jkj61syVzTTgOcUOQ8buFBTYWdL5g3T4qlpe0+wvD63heAXRfCCIed9RbCsp2CiI7raUOYOTU13N8PNHvpaGvayo4a3LLT1lDrVEPT2zLUlheB1R+ZTRfKWJ+dcocLJfi11vyJ51lLqJ0WD7tRwryezjiV5W28uJO9qykzX8JDe2lHl/9oyBwa2UMfOngpXCixvKdXTk3wrsKmiVYdZIqsoWEERjbcUNDuiaQomGoIbFdEHmsyWnuR+IeriKDVLnlawlyNHKwKlSU631PKep8J4Q+ayjkSLKYLhalNHlYvttb6fHm0p6OApsZ4l2VfdqZkjuysy6ysKLlckf1KUutCTs39bmCgEyyoasIWlVaMF7mgmWtBT8Kol5xpH9IGllo8cJdopcvZ2sImlDmMIbtDk3KIpeNiS08lQw11NFPTwVFlPP6pJ2gvRfI7gQUfmNAtf6Gs0wQxDsKGlVBdF8rCa3jzdwMaGHOsItrZk7hAyOzpK9VS06j5F49b0VNGOOfKs3lDToMsMBe9ZWtHFEgxTJLs7qrygKZjUnmCYoeAqeU6jqWuLJup4WghOdvCYJnrSkSzoyRkm5M2StQwVltPkfCAk58tET/CSg+8MUecmotMEnhBKfWBIZsg2ihruMJQaoIm+tkTLKEqspMh00w95gvFCQRtDwTT1gVDDSEVdlwqZfxoQRbK0g+tbiBZxzKlpnpypejdDwTaeOvorMk/IJE10h9CqRe28hhLbe0pMsdSwv4ZbhKivo2BjDWfL8UKJgeavwlwb5KlwhyE4u4XkGE2ytZCznKLCDZZq42VzT8HLCrpruFbIfOIINmh/qCdZ1ZBc65kLHR1Bkyf5zn6pN3SvGKIlFNGplhrO9QSXanLOMQTLCa0YJCRrCZm/CZmrLTm7WzCK4GJDiWUdFeYx1LCFg3NMd0XmCuF3Y5rITLDUsYS9zoHVzwnJoYpSTQoObyEzr4cFBNqYTopoaU/wkyLZ2lPhX/5Y95ulxGTV7KjhWrOZgl8MyUUafjYraNjNU1N3IWcjT5WzWqjwtoarHSUObGYO3GCJZpsBlnJGPd6ZYLyl1GdCA2625IwwJDP8GUKymbzuyPlZlvTUsaUh5zFDhRWFzPKKZLAlWdcQbObgF9tOqOsmB1dqcqYJmWstFbZRRI9poolmqiLnU0POvxScpah2iSL5UJNzgScY5+AuIbpO0YD3NCW+dLMszFSdFCWGqG6eVq2uYVNDdICGD6W7EPRWZEY5gpsE9rUkS3mijzzJnm6UpUFXG1hCUeVoS5WfNcFpblELL2qqrCvMvRfd45oalvKU2tiQ6ePJOVMRXase9iTtLJztPxJKLWpo2CRDcJwn2sWSLKIO1WQWNTCvpVUvOZhgSC40JD0dOctaSqzkCRbXsKlb11Oip6PCJ0IwSJM31j3akRxlP7Rwn6aGaUL0qiLnJkvB3xWZ2+Q1TfCwpQH3G0o92UzmX4o/oJNQMMSQc547wVHhdk+VCw01DFYEnTxzZKAm74QmeNNR1w6WzEhNK15VJzuCdxQ53dRUDws5KvwgBMOEgpcVNe0hZI6RXT1Jd0cyj5nsaEAHgVmGaJIlWdsc5Ui2ElrRR6jrRAttNMEAIWrTDFubkZaok7/AkzfIwfuWVq0jHzuCK4QabtLUMVPB3kJ0oyHTSVFlqMALilJf2Rf8k5aaHtMfayocLBS8L89oKoxpJvnAkDPa0qp5DAUTHKWmCcnthlou8iCKaFFLHWcINd1nyIwXqrSxMNmSs6KmoL2QrKuWtlQ5V0120xQ5vRyZS1rgFkWwhiOwiuQbR0OOVhQM9iS3tiXp4RawRPMp5tDletOOBL95MpM01dZTBM9pkn5qF010rIeHFcFZhmSGpYpTsI6nwhqe5C9ynhlpp5ophuRb6WcJFldkVnVEwwxVfrVkvnWUuNLCg5bgboFHPDlDPDmnK7hUrWiIbjadDclujlZcaokOFup4Ri1kacV6jmrrK1hN9bGwpKEBQ4Q6DvIUXOmo6U5LqQM6EPyiKNjVkPnJkDPNEaxhiFay5ExW1NXVUGqcpYYdPcGiCq7z/TSlbhL4pplWXKd7NZO5QQFrefhRQW/NHOsqcIglc4UhWklR8K0QzbAw08CBDnpbgqXdeD/QUsM4RZXDFBW6WJKe/mFPdH0LtBgiq57wFLzlyQzz82qYx5D5WJP5yVJDW01BfyHnS6HKO/reZqId1WGa4Hkh2kWodJ8i6KoIPlAj2hPt76CzXsVR6koPRzWTfKqIentatYpQw2me4AA3y1Kind3SwoOKZDcFXTwl9tWU6mfgRk9d71sKtlNwrjnYw5tC5n5LdKiGry3JKNlHEd3oaMCFHrazBPMp/uNJ+V7IudcSbeOIdjUEdwl0VHCOZo5t6YluEuaC9mQeMgSfOyKnYGFHcIeQ84yQWbuJYJpZw5CzglDH7gKnWqqM9ZTaXcN0TeYhR84eQtJT76JJ1lREe7WnnvsMmRc9FQ7SBBM9mV3lCUdmHk/S2RAMt0QjFNFqQpWjDPQ01DXWUdDBkXziKPjGEP3VP+zIWU2t7im41FOloyWzn/L6dkUy3VLDaZ6appgDLHPjJEsyvJngWEPUyVBiAaHCTEXwrLvSEbV1e1gKJniicWorC1MUrVjB3uDhJE/wgSOzk1DXpk0k73qCM8xw2UvD5kJmDUfOomqMpWCkJRlvKXGmoeBm18USjVIk04SClxTB6YrgLAPLWYK9HLUt5cmc0vYES8GnTeRc6skZbQkWdxRsIcyBRzx1DbTk9FbU0caTPOgJHhJKnOGIVhQqvKmo0llRw9sabrZkDtdg3PqaKi9oatjY8B+G371paMg6+mZFNNtQ04mWBq3rYLOmtWWQp8KJnpy9DdFensyjdqZ+yY40VJlH8wcdLzC8PZnvHMFUTZUrDTkLyQaGus5X5LzpYAf3i+e/ZlhqGqWhh6Ou6xTR9Z6oi5AZZtp7Mj2EEm8oSpxiYZCHU/1fbGdNNNRRoZMhmilEb2gqHOEJDtXkHK/JnG6IrvbPCwV3NhONVdS1thBMs1T4QOBcTWa2IzhMk2nW5Kyn9tXUtpv9RsG2msxk+ZsQzRQacJncpgke0+T8y5Fzj8BiGo7XlJjaTIlpQs7KFjpqGnKuoyEPeIKnFMkZHvopgh81ySxNFWvJWcKRs70j2FOT012IllEEO1n4pD1513Yg2ssQPOThOkvyrqHUdEXOSEsihmBbTbKX1kLBPWqWkLOqJbjB3GBIZmoa8qWl4CG/iZ7oiA72ZL7TJNeZUY7kFQftDcHHluBzRbCegzMtrRjVQpX2lgoPKKLJAkcbMl01XK2p7yhL8pCBbQ3BN2avJgKvttcrWDK3CiUOVxQ8ZP+pqXKyIxnmBymCg5vJjNfkPK4+c8cIfK8ocVt7kmfd/I5SR1hKvCzUtb+lhgc00ZaO6CyhIQP1Uv4yIZjload72PXX0OIJvnFU+0Zf6MhsJwTfW0r0UwQfW4LNLZl5HK261JCZ4qnBaAreVAS3WrjV0LBnNDUNNDToCEeFfwgcb4gOEqLRhirWkexrCEYKVV711DLYEE1XBEsp5tpTGjorkomKYF9FDXv7fR3BGwbettSxnyL53MBPjsxDZjMh+VUW9NRxq1DhVk+FSxQcaGjV9Pawv6eGByw5qzoy7xk4RsOShqjJwWKe/1pEEfzkobeD/dQJmpqedcyBTy2sr4nGNRH0c0SPWTLrqAc0OQcb/gemKgqucQT7ySWKCn2EUotoCvpZct7RO2sy/QW0IWcXd7pQRQyZVwT2USRO87uhjioTLKV2brpMUcMQRbKH/N2T+UlTpaMls6cmc6CCNy3JdYYSUzzJQ4oSD3oKLncULOiJvjBEC2oqnCJkJluCYy2ZQ5so9YYlZ1VLlQU1mXEW1jZERwj/MUSRc24TdexlqLKfQBtDTScJUV8FszXBEY5ktpD5Ur9hYB4Nb1iikw3JoYpkKX+RodRKFt53MMuRnKSpY31PwYaGaILh3wxJGz9TkTPEETxoCWZrgvOlmyMzxFEwVJE5xZKzvyJ4WxEc16Gd4Xe3Weq4XH2jKRikqOkGQ87hQnC7wBmGYLAnesX3M+S87eFATauuN+Qcrh7xIxXJbUIdMw3JGE3ylCWzrieaqCn4zhGM19TQ3z1oH1AX+pWEqIc7wNGAkULBo/ZxRaV9NNyh4Br3rCHZzbzmSfawBL0dNRwpW1kK9mxPXR9povcdrGSZK9c2k0xwFGzjuniCtRSZCZ6ccZ7gaktmgAOtKbG/JnOkJrjcQTdFMsxRQ2cLY3WTIrlCw1eWKn8R6pvt4GFDso3QoL4a3nLk3G6JrtME3dSenpx7PNFTmga0EaJTLQ061sEeQoWXhSo9LTXsaSjoJQRXeZLtDclbCrYzfzHHeaKjHCVOUkQHO3JeEepr56mhiyaYYKjjNU+Fed1wS5VlhWSqI/hYUdDOkaxiKehoyOnrCV5yBHtbWFqTHCCwtpDcYolesVR5yUzTZBb3RNMd0d6WP+SvhuBmRcGxnuQzT95IC285cr41cLGQ6aJJhmi4TMGempxeimBRQw1tFKV+8jd6KuzoSTqqDxzRtpZkurvKEHxlqXKRIjjfUNNXQsNOsRScoWFLT+YeRZVD3GRN0MdQcKqQjHDMrdGGVu3iYJpQx3WGUvfbmxwFfR20WBq0oYY7LMFhhgYtr8jpaEnaOzjawWWaTP8mMr0t/EPDPoqcnxTBI5o58L7uoWnMrpoqPwgVrlAUWE+V+TQl9rawoyP6QGAlQw2TPRX+YSkxyBC8Z6jhHkXBgQL7WII3DVFnRfCrBfxewv9D6xsyjys4VkhWb9pUU627JllV0YDNHMku/ldNMMXDEo4aFnAkk4U6frNEU4XgZUPmEKHUl44KrzmYamjAbh0JFvGnaTLPu1s9jPCwjFpYiN7z1DTOk/nc07CfDFzmCf7i+bfNHXhDtLeBXzTBT5rkMvWOIxpl4EMh2LGJBu2syDnAEx2naEhHDWMMzPZEhygyS1mS5RTJr5ZkoKbEUoYqr2kqdDUE8ztK7OaIntJkFrIECwv8LJTaVx5XJE86go8dFeZ3FN3rjabCAYpoYEeC9zzJVULBbmZhDyd7ko09ydpNZ3nm2Kee4FPPXHnYEF1nqOFEC08LUVcDvYXkJHW8gTaKCk9YGOeIJhqiE4ToPEepdp7IWFjdwnWaufGMwJJCMtUTTBBK9BGCOy2tGGrJTHIwyEOzp6aPzNMOtlZkDvcEWpP5SVNhfkvDxhmSazTJXYrM9U1E0xwFVwqZQwzJxw6+kGGGUj2FglGGmnb1/G51udRSMNlTw6GGnCcUwVcOpmsqTHa06o72sw1RL02p9z0VbnMLOaIX3QKaYKSCFQzBKEUNHTSc48k53RH9wxGMtpQa5KjjW0W0n6XCCCG4yxNNdhQ4R4l1Ff+2sSd6UFHiIEOyqqFgT01mEUMD+joy75jPhOA+oVVLm309FR4yVOlp4RhLiScNmSmaYF5Pw0STrOIoWMSR2UkRXOMp+M4SHW8o8Zoi6OZgjKOaFar8zZDzkWzvKOjkKBjmCXby8JahhjXULY4KlzgKLvAwxVGhvyd4zxB1d9T0piazmKLCVZY5sKiD0y2ZSYrkUEPUbIk+dlQ4SJHTR50k1DPaUWIdTZW9NJwnJMOECgd7ou/MnppMJ02O1VT4Wsh85MnZzcFTngpXGKo84qmwgKbCL/orR/SzJ2crA+t6Mp94KvxJUeIbT3CQu1uIdlQEOzlKfS3UMcrTiFmOuroocrZrT2AcmamOKg8YomeEKm/rlT2sociMaybaUlFhuqHCM2qIJ+rg4EcDFymiDSxzaHdPcpE62pD5kyM5SBMoA1PaUtfIthS85ig1VPiPPYXgYEMNk4Qq7TXBgo7oT57gPUdwgCHzhIVFPFU6OYJzHAX9m5oNrVjeE61miDrqQ4VSa1oiURTsKHC0IfjNwU2WzK6eqK8jWln4g15TVBnqmDteCJ501PGAocJhhqjZdtBEB6lnhLreFJKxmlKbeGrqLiSThVIbCdGzloasa6lpMQXHCME2boLpJgT7yWaemu6wBONbqGNVRS0PKIL7LckbjmQtR7K8I5qtqel+T/ChJTNIKLjdUMNIRyvOEko9YYl2cwQveBikCNawJKcLBbc7+JM92mysNvd/Fqp8a0k6CNEe7cnZrxlW0wQXaXjaktnRwNOGZKYiONwS7a1JVheq3WgJHlQUGKHKmp4KAxXR/ULURcNgoa4zhKSLpZR3kxRRb0NmD0OFn+UCS7CzI1nbP6+o4x47QZE5xRCt3ZagnYcvmpYQktXdk5YKXTzBC57kKEe0VVuiSYqapssMS3C9p2CKkHOg8B8Pa8p5atrIw3qezIWanMGa5HRDNF6RM9wcacl0N+Q8Z8hsIkSnaIIdHRUOEebAPy1zbCkhM062FCJtif7PU+UtoVXzWKqM1PxXO8cfdruhFQ/a6x3JKYagvVDhQEtNiyiiSQ7OsuRsZUku0CRNDs4Sog6KKjsZgk2bYJqijgsEenoKeniinRXBn/U3lgpPdyDZynQx8IiioMnCep5Ky8mjGs6Wty0l1hUQTcNWswS3WRp2kCNZwJG8omG8JphPUaFbC8lEfabwP7VtM9yoaNCAjpR41VNhrD9LkbN722v0CoZMByFzhaW+MyzRYEWFDQwN2M4/JiT76PuljT3VU/A36eaIThb+R9oZGOAJ9tewkgGvqOMNRWYjT/Cwu99Q8LqDE4TgbLWxJ1jaDDAERsFOFrobgjUsBScaguXU8kKm2RL19tRypSHnHNlHiIZqgufs4opgQdVdwxBNNFBR6kVFqb8ogimOzB6a6HTzrlDHEpYaxjiiA4TMQobkDg2vejjfwJGWmnbVFAw3H3hq2NyQfG7hz4aC+w3BbwbesG0swYayvpAs6++Ri1Vfzx93mFChvyN5xVHTS+0p9aqCAxyZ6ZacZyw5+7uuQkFPR9DDk9NOiE7X1PCYJVjVUqq7JlrHwWALF5nfHNGjApdpqgzx5OwilDhCiDYTgnc9waGW4BdLNNUQvOtpzDOWHDH8D7TR/A/85KljEQu3NREc4Pl/6B1Hhc8Umb5CsKMmGC9EPcxoT2amwHNCmeOEnOPbklnMkbOgIvO5UMOpQrS9UGVdt6iH/fURjhI/WOpaW9OKLYRod6HCUEdOX000wpDZQ6hwg6LgZfOqo1RfT/CrJzjekXOGhpc1VW71ZLbXyyp+93ILbC1kPtIEYx0FIx1VDrLoVzXRKRYWk809yYlC9ImcrinxtabKnzRJk3lAU1OLEN1j2zrYzr2myHRXJFf4h4QKT1qSTzTB5+ZNTzTRkAxX8FcLV2uS8eoQQ2aAkFzvCM72sJIcJET3WPjRk5wi32uSS9rfZajpWEvj9hW42F4o5NytSXYy8IKHay10VYdrcl4SkqscrXpMwyGOgtkajheSxdQqmpxP1L3t4R5PqasFnrQEjytq6qgp9Y09Qx9o4S1FzhUCn1kyHSzBWLemoSGvOqLNhZyBjmCaAUYpMgt4Ck7wBBMMwWKWgjsUwTaGVsxWC1mYoKiyqqeGKYqonSIRQ3KIkHO0pmAxTdBHkbOvfllfr+AA+7gnc50huVKYK393FOyg7rbPO/izI7hE4CnHHHnJ0ogNPRUGeUpsrZZTBJcrovUcJe51BPsr6GkJdhCCsZ6aTtMEb2pqWkqeVtDXE/QVggsU/Nl86d9RMF3DxvZTA58agu810RWawCiSzzXBeU3MMW9oyJUedvNEvQyNu1f10BSMddR1vaLCYpYa/mGocLSiYDcLbQz8aMn5iyF4xBNMs1P0QEOV7o5gaWGuzSeLue4tt3ro7y4Tgm4G/mopdZgl6q0o6KzJWE3mMksNr3r+a6CbT8g5wZNzT9O7fi/zpaOmnz3BRoqos+tv9zMbdpxsqDBOEewtJLt7cg5wtKKbvldpSzRRCD43VFheCI7yZLppggMVBS/KMAdHODJvOwq2NQSbKKKPLdFWQs7Fqo+mpl01JXYRgq8dnGLhTiFzqmWsUMdpllZdbKlyvSdYxhI9YghOtxR8LgSLWHK62mGGVoxzBE8LNWzqH9CUesQzFy5RQzTc56mhi6fgXEWwpKfE5Z7M05ZgZUPmo6auiv8YKzDYwWBLMErIbKHJvOwIrvEdhOBcQ9JdU1NHQ7CXn2XIDFBKU2WAgcX9UAUzDXWd5alwuyJ41Z9rjKLCL4aCp4WarhPm2rH+SaHUYE001JDZ2ZAzXPjdMpZWvC9wmqIB2lLhQ01D5jO06hghWMndbM7yRJMsoCj1vYbnFQVrW9jak3OlEJ3s/96+p33dEPRV5GxiqaGjIthUU6FFEZyqCa5qJrpBdzSw95IUnOPIrCUUjRZQFrbw5PR0R1qiYx3cb6nrWUMrBmmiBQxVHtTew5ICP/ip6g4hed/Akob/32wvBHsIOX83cI8hGeNeNPCIkPmXe8fPKx84OMSRM1MTdXSwjCZ4S30jVGhvqTRak/OVhgGazHuOCud5onEO1lJr6ecVyaOK6H7zqlBlIaHE0oroCgfvGJIdPcmfLNGLjpz7hZwZQpUbFME0A1cIJa7VNORkgfsMBatbKgwwJM9bSvQXeNOvbIjelg6WWvo5kvbKaJJNHexkKNHL9xRyFlH8Ti2riB5wVPhUk7nGkJnoCe428LR/wRGdYIlmWebCyxou1rCk4g/ShugBDX0V0ZQWkh0dOVsagkM0yV6OoLd5ye+pRlsCr0n+KiQrGuq5yJDzrTAXHtLUMduTDBVKrSm3eHL+6ijxhFDX9Z5gVU/wliHYTMiMFpKLNMEywu80wd3meoFmt6VbRMPenhrOc6DVe4pgXU8DnnHakLOIIrlF4FZPIw6R+zxBP0dyq6OOZ4Q5sLKCcz084ok+VsMMyQhNZmmBgX5xIXOEJTmi7VsGTvMTNdHHhpzdbE8Du2oKxgvBqQKdDDnTFOylCFaxR1syz2iqrOI/FEpNc3C6f11/7+ASS6l2inq2ciTrCCzgyemrCL5SVPjQkdPZUmGy2c9Sw9FtR1sS30RmsKPCS4rkIC/2U0MduwucYolGaPjKEyhzmiPYXagyWbYz8LWBDdzRimAXzxx4z8K9hpzlhLq+NiQ97HuKorMUfK/OVvC2JfiHUPCQI/q7J2gjK+tTDNxkCc4TMssqCs4TGtLVwQihyoAWgj9bosU80XGW6Ac9TJGziaUh5+hnFcHOnlaM1iRn29NaqGENTTTSUHCH2tWTeV0osUhH6psuVLjRUmGWhm6OZEshGeNowABHcJ2Bpy2ZszRcKkRXd2QuKVEeXnbfaEq825FguqfgfE2whlChSRMdron+LATTPQ2Z369t4B9C5gs/ylzv+CMmepIDPclFQl13W0rspPd1JOcbghGOEutqCv5qacURQl3dDKyvyJlqKXGPgcM9FfawJAMVmdcspcYKOZc4GjDYkFlK05olNMHyHn4zFNykyOxt99RkHlfwmiHo60l2EKI+mhreEKp080Tbug08BVPcgoqC5zWt+NLDTZ7oNSF51N1qie7Va3uCCwyZbkINf/NED6jzOsBdZjFN8oqG3wxVunqCSYYKf3EdhJyf9YWGf7tRU2oH3VHgPr1fe5J9hOgHd7xQ0y7qBwXr23aGErP0cm64JVjZwsOGqL+mhNgZmhJLW2oY4UhedsyBgzrCKrq7BmcpNVhR6jBPq64Vgi+kn6XE68pp8J5/+0wRHGOpsKenQn9DZntPzjRLZpDAdD2fnSgkG9tmIXnUwQ6WVighs7Yi2MxQ0N3CqYaCXkJ0oyOztMDJjmSSpcpvlrk0RMMOjmArQ04PRV1DO1FwhCVaUVPpKUM03JK5SxPsIWRu8/CGHi8UHChiqGFDTbSRJWeYUDDcH6vJWUxR4k1FXbMUwV6e4AJFXS8oMqsZKqzvYQ9DDQdZckY4aGsIhtlubbd2r3j4QBMoTamdPZk7O/Bf62lacZwneNjQoGcdVU7zJOd7ghsUHOkosagic6cnWc8+4gg285R6zZP5s1/LUbCKIznTwK36PkdwlOrl4U1LwfdCCa+IrvFkmgw1PCAUXKWo0sURXWcI2muKJlgyFzhynCY4RBOsqCjoI1R5zREco0n2Vt09BQtYSizgKNHfUmUrQ5UOCh51BFcLmY7umhYqXKQomOop8bUnWNNQcIiBcYaC6xzMNOS8JQQfeqKBmmglB+97ok/lfk3ygaHSyZaCRTzRxQo6GzLfa2jWBPepw+UmT7SQEJyiyRkhBLMVOfcoMjcK0eZChfUNzFAUzCsEN5vP/X1uP/n/aoMX+K+nw/Hjr/9xOo7j7Pju61tLcgvJpTWXNbfN5jLpi6VfCOviTktKlFusQixdEKWmEBUKNaIpjZRSSOXSgzaaKLdabrm1/9nZ+/f+vd/vz/v9+Xy+zZ7PRorYoZqyLrCwQdEAixxVOEXNNnjX2nUSRlkqGmWowk8lxR50JPy9Bo6qJXaXwNvREBvnThPEPrewryLhcAnj5WE15Fqi8W7R1sAuEu86S4ENikItFN4xkv9Af4nXSnUVcLiA9xzesFpivRRVeFKtsMRaKBhuSbjOELnAUtlSQUpXgdfB4Z1oSbnFEetbQ0IrAe+Y+pqnDcEJFj6S8LDZzZHwY4e3XONNlARraomNEt2bkvGsosA3ioyHm+6jCMbI59wqt4eeara28IzEmyPgoRaUOEDhTVdEJhmCoTWfC0p8aNkCp0oYqih2iqGi4yXeMkOsn4LdLLnmKfh/YogjNsPebeFGR4m9BJHLzB61XQ3BtpISfS2FugsK9FAtLWX1dCRcrCnUp44CNzuCowUZmxSRgYaE6Za0W2u/E7CVXCiI/UOR8aAm1+OSyE3mOUcwyc1zBBeoX1kiKy0Zfxck1Gsyulti11i83QTBF5Kg3pDQThFMVHiPSlK+0cSedng/VaS8bOZbtsBcTcZAR8JP5KeqQ1OYKAi20njdNNRpgnsU//K+JnaXJaGTomr7aYIphoRn9aeShJWKEq9LcozSF7QleEfDI5LYm5bgVkFkRwVDBCVu0DDIkGupo8TZBq+/pMQURYErJQmPKGKjNDkWOLx7Jd5QizdUweIaKrlP7SwJDhZvONjLkOsBBX9UpGxnydhXkfBLQ8IxgojQbLFnJf81JytSljclYYyEFyx0kVBvKWOFJmONpshGAcsduQY5giVNCV51eOdJYo/pLhbvM0uDHSevNKRcrKZIqnCtJeEsO95RoqcgGK4ocZcho1tTYtcZvH41pNQ7vA0WrhIfOSraIIntIAi+NXWCErdbkvrWwjRLrt0NKUdL6KSOscTOdMSOUtBHwL6OLA0vNSdynaWQEnCpIvKaIrJJEbvHkmuNhn6OjM8VkSGSqn1uYJCGHnq9I3aLhNME3t6GjIkO7xrNFumpyTNX/NrwX7CrIRiqqWijI9JO4d1iieykyfiposQIQ8YjjsjlBh6oHWbwRjgYJQn2NgSnNycmJAk3NiXhx44Sxykihxm8ybUwT1OVKySc7vi3OXVkdBJ4AyXBeksDXG0IhgtYY0lY5ahCD0ehborIk5aUWRJviMA7Xt5kyRjonrXENkm8yYqgs8VzgrJmClK20uMM3jRJ0FiQICQF9hdETlLQWRIb5ki6WDfWRPobvO6a4GP5mcOrNzDFELtTkONLh9dXE8xypEg7z8A9jkhrQ6Fhjlg/QVktJXxt4WXzT/03Q8IaQWSqIuEvloQ2mqC9Jfi7wRul4RX3pSPlzpoVlmCtI2jvKHCFhjcM3sN6lqF6HxnKelLjXWbwrpR4xzuCrTUZx2qq9oAh8p6ixCUGr78g8oyjRAtB5CZFwi80VerVpI0h+IeBxa6Zg6kWvpDHaioYYuEsRbDC3eOmC2JvGYLeioxGknL2UATNJN6hmtj1DlpLvDVmocYbrGCVJKOrg4X6DgddLA203BKMFngdJJFtFd7vJLm6KEpc5yjQrkk7M80SGe34X24nSex1Ra5Omgb71JKyg8SrU3i/kARKwWpH0kOGhKkObyfd0ZGjvyXlAkVZ4xRbYJ2irFMkFY1SwyWxr2oo4zlNiV+7zmaweFpT4kR3kaDAFW6xpSqzJay05FtYR4HmZhc9UxKbbfF2V8RG1MBmSaE+kmC6JnaRXK9gsiXhJHl/U0qM0WTcbyhwkYIvFGwjSbjfwhiJt8ZSQU+Bd5+marPMOkVkD0muxYLIfEuhh60x/J92itguihJSEMySVPQnTewnEm+620rTQEMsOfo4/kP/0ARvWjitlpSX7GxBgcMEsd3EEeYWvdytd+Saawi6aCIj1CkGb6Aj9rwhx16Cf3vAwFy5pyLhVonXzy51FDpdEblbkdJbUcEPDEFzQ8qNmhzzLTmmKWKbFCXeEuRabp6rxbvAtLF442QjQ+wEA9eL1xSR7Q0JXzlSHjJ4exq89yR0laScJ/FW6z4a73pFMEfDiRZvuvijIt86RaSFOl01riV2mD1UEvxGk/Geg5aWwGki1zgKPG9J2U8PEg8qYvMsZeytiTRXBMslCU8JSlxi8EabjwUldlDNLfzTUmCgxWsjqWCOHavYAqsknKFIO0yQ61VL5AVFxk6WhEaCAkdJgt9aSkzXlKNX2jEa79waYuc7gq0N3GDJGCBhoiTXUEPsdknCUE1CK0fwsiaylSF2uiDyO4XX3pFhNd7R4itFGc0k/ElBZwWvq+GC6szVeEoS/MZ+qylwpKNKv9Z469UOjqCjwlusicyTxG6VpNxcQ8IncoR4RhLbR+NdpGGmJWOcIzJGUuKPGpQg8rrG21dOMqQssJQ4RxH5jaUqnZuQ0F4Q+cjxLwPtpZbIAk3QTJHQWBE5S1BokoVtDd6lhqr9UpHSUxMcIYl9pojsb8h4SBOsMQcqvOWC2E8EVehqiJ1hrrAEbQxeK0NGZ0Gkq+guSRgniM23bIHVkqwx4hiHd7smaOyglyIyQuM978j4VS08J/A2G1KeMBRo4fBaSNhKUEZfQewVQ/C1I+MgfbEleEzCUw7mKXI0M3hd1EESVji8x5uQ41nxs1q4RMJCCXs7Iq9acpxn22oSDnQ/sJTxsCbHIYZiLyhY05TY0ZLIOQrGaSJDDN4t8pVaIrsqqFdEegtizc1iTew5Q4ayBDMUsQMkXocaYkc0hZua412siZ1rSXlR460zRJ5SlHGe5j801RLMlJTxtaOM3Q1pvxJ45zUlWFD7rsAbpfEm1JHxG0eh8w2R7QQVzBUw28FhFp5QZzq8t2rx2joqulYTWSuJdTYfWwqMFMcovFmSyJPNyLhE4E10pHzYjOC3huArRa571ZsGajQpQx38SBP5pyZB6lMU3khDnp0MBV51BE9o2E+TY5Ml2E8S7C0o6w1xvCZjf0HkVEHCzFoyNmqC+9wdcqN+Tp7jSDheE9ws8Y5V0NJCn2bk2tqSY4okdrEhx1iDN8cSudwepWmAGXKcJXK65H9to8jYQRH7SBF01ESUJdd0TayVInaWhLkOjlXE5irKGOnI6GSWGCJa482zBI9rCr0jyTVcEuzriC1vcr6mwFGSiqy5zMwxBH/TJHwjSPhL8+01kaaSUuMFKTcLEvaUePcrSmwn8DZrgikWb7CGPxkSjhQwrRk57tctmxLsb9sZvL9LSlyuSLlWkqOjwduo8b6Uv1DkmudIeFF2dHCgxVtk8dpIvHpBxhEOdhKk7OLIUSdJ+cSRY57B+0DgGUUlNfpthTfGkauzxrvTsUUaCVhlKeteTXCoJDCa2NOKhOmC4G1H8JBd4OBZReSRGkqcb/CO1PyLJTLB4j1q8JYaIutEjSLX8YKM+a6phdMsdLFUoV5RTm9JSkuDN8WcIon0NZMNZWh1q8C7SJEwV5HxrmnnTrf3KoJBlmCYI2ilSLlfEvlE4011NNgjgthzEua0oKK7JLE7HZHlEl60BLMVFewg4EWNt0ThrVNEVkkiTwpKXSWJzdRENgvKGq4IhjsiezgSFtsfCUq8qki5S1LRQeYQQ4nemmCkImWMw3tFUoUBZk4NOeZYEp4XRKTGa6wJjrWNHBVJR4m3FCnbuD6aak2WsMTh3SZImGCIPKNgsDpVwnsa70K31lCFJZYcwwSMFcQulGTsZuEaSdBXkPGZhu0FsdUO73RHjq8MPGGIfaGIbVTk6iuI3GFgucHrIQkmWSJdBd7BBu+uOryWAhY7+Lki9rK5wtEQzWwvtbqGhIMFwWRJsElsY4m9IIg9L6lCX0VklaPAYkfkZEGDnOWowlBJjtMUkcGK4Lg6EtoZInMUBVYLgn0UsdmCyCz7gIGHFfk+k1QwTh5We7A9x+IdJ6CvIkEagms0hR50eH9UnTQJ+2oiKyVlLFUE+8gBGu8MQ3CppUHesnjTHN4QB/UGPhCTHLFPHMFrCqa73gqObUJGa03wgbhHkrCfpEpzNLE7JDS25FMKhlhKKWKfCgqstLCPu1zBXy0J2ztwjtixBu8UTRn9LVtkmCN2iyFhtME70JHRQ1KVZXqKI/KNIKYMCYs1GUMEKbM1bKOI9LDXC7zbHS+bt+1MTWS9odA9DtrYtpbImQJ2VHh/lisEwaHqUk1kjKTAKknkBEXkbkdMGwq0dnhzLJF3NJH3JVwrqOB4Sca2hti75nmJN0WzxS6UxDYoEpxpa4htVlRjkYE7DZGzJVU72uC9IyhQL4i8YfGWSYLLNcHXloyz7QhNifmKSE9JgfGmuyLhc403Xm9vqcp6gXe3xuuv8F6VJNxkyTHEkHG2g0aKXL0MsXc1bGfgas2//dCONXiNLCX+5mB7eZIl1kHh7ajwpikyzlUUWOVOsjSQlsS+M0R+pPje/dzBXRZGO0rMtgQrLLG9VSu9n6CMXS3BhwYmSoIBhsjNBmZbgusE9BCPCP5triU4VhNbJfE+swSP27aayE8tuTpYYjtrYjMVGZdp2NpS1s6aBnKSHDsbKuplKbHM4a0wMFd/5/DmGyKrJSUaW4IBrqUhx0vyfzTBBLPIUcnZdrAkNsKR0sWRspumSns6Ch0v/qqIbBYUWKvPU/CFoyrDJGwSNFhbA/MlzKqjrO80hRbpKx0Jewsi/STftwGSlKc1JZyAzx05dhLEdnfQvhZOqiHWWEAHC7+30FuRcZUgaO5gpaIK+xsiHRUsqaPElTV40xQZQ107Q9BZE1nryDVGU9ZSQ47bmhBpLcYpUt7S+xuK/FiT8qKjwXYw5ypS2iuCv7q1gtgjhuBuB8LCFY5cUuCNtsQOFcT+4Ih9JX+k8Ea6v0iCIRZOtCT0Et00JW5UeC85Cg0ScK0k411HcG1zKtre3SeITBRk7WfwDhEvaYLTHP9le0m8By0JDwn4TlLW/aJOvGHxdjYUes+ScZigCkYQdNdEOhkiezgShqkx8ueKjI8lDfK2oNiOFvrZH1hS+tk7NV7nOmLHicGWEgubkXKdwdtZknCLJXaCpkrjZBtLZFsDP9CdxWsSr05Sxl6CMmoFbCOgryX40uDtamB7SVmXW4Ihlgpmq+00tBKUUa83WbjLUNkzDmY7cow1JDygyPGlhgGKYKz4vcV7QBNbJIgM11TUqZaMdwTeSguH6rOaw1JRKzaaGyxVm2EJ/uCIrVWUcZUkcp2grMsEjK+DMwS59jQk3Kd6SEq1d0S6uVmO4Bc1lDXTUcHjluCXEq+1OlBDj1pi9zgiXxnKuE0SqTXwhqbETW6RggMEnGl/q49UT2iCzgJvRwVXS2K/d6+ZkyUl7jawSVLit46EwxVljDZwoSQ20sDBihztHfk2yA8NVZghiXwrYHQdfKAOtzsayjhY9bY0yE2CWEeJ9xfzO423xhL5syS2TFJofO2pboHob0nY4GiAgRrvGQEDa/FWSsoaaYl0syRsEt3kWoH3B01shCXhTUWe9w3Bt44SC9QCh3eShQctwbaK2ApLroGCMlZrYqvlY3qYhM0aXpFkPOuoqJ3Dm6fxXrGwVF9gCWZagjPqznfkuMKQ8DPTQRO8ZqG1hPGKEm9IgpGW4DZDgTNriTxvFiq+Lz+0cKfp4wj6OCK9JSnzNSn9LFU7UhKZZMnYwcJ8s8yRsECScK4j5UOB95HFO0CzhY4xJxuCix0lDlEUeMdS6EZBkTsUkZ4K74dugyTXS7aNgL8aqjDfkCE0ZbwkCXpaWCKhl8P7VD5jxykivSyxyZrYERbe168LYu9ZYh86IkscgVLE7tWPKmJv11CgoyJltMEbrohtVAQfO4ImltiHEroYEs7RxAarVpY8AwXMcMReFOTYWe5iiLRQxJ5Q8DtJ8LQhWOhIeFESPGsILhbNDRljNbHzNRlTFbk2S3L0NOS6V1KFJYKUbSTcIIhM0wQ/s2TM0SRMNcQmSap3jCH4yhJZKSkwyRHpYYgsFeQ4U7xoCB7VVOExhXepo9ABBsYbvGWKXPME3lyH95YioZ0gssQRWWbI+FaSMkXijZXwgiTlYdPdkNLaETxlyDVIwqeaEus0aTcYcg0RVOkpR3CSJqIddK+90JCxzsDVloyrFd5ZAr4TBKfaWa6boEA7C7s6EpYaeFPjveooY72mjIccLHJ9HUwVlDhKkmutJDJBwnp1rvulJZggKDRfbXAkvC/4l3ozQOG9a8lxjx0i7nV4jSXc7vhe3OwIxjgSHjdEhhsif9YkPGlus3iLFDnWOFhtCZbJg0UbQcIaR67JjthoCyMEZRwhiXWyxO5QxI6w5NhT4U1WsJvDO60J34fW9hwzwlKij6ZAW9ne4L0s8C6XeBMEkd/LQy1VucBRot6QMlbivaBhoBgjqGiCJNhsqVp/S2SsG6DIONCR0dXhvWbJ+MRRZJkkuEjgDXJjFQW6SSL7GXK8Z2CZg7cVsbWGoKmEpzQ5elpiy8Ryg7dMkLLUEauzeO86CuwlSOlgYLojZWeJ9xM3S1PWfEfKl5ISLQ0MEKR8YOB2QfCxJBjrKPCN4f9MkaSsqoVXJBmP7EpFZ9UQfOoOFwSzBN4MQ8LsGrymlipcJQhmy0GaQjPqCHaXRwuCZwRbqK2Fg9wlClZqYicrIgMdZfxTQ0c7TBIbrChxmuzoKG8XRaSrIhhiyNFJkrC7oIAWMEOQa5aBekPCRknCo4IKPrYkvCDI8aYmY7WFtprgekcJZ3oLIqssCSMtFbQTJKwXYy3BY5oCh2iKPCpJOE+zRdpYgi6O2KmOAgvVCYaU4ySRek1sgyFhJ403QFHiVEmJHwtybO1gs8Hr5+BETQX3War0qZngYGgtVZtoqd6vFSk/UwdZElYqyjrF4HXUeFspIi9IGKf4j92pKGAdCYMVsbcV3kRF0N+R8LUd5PCsIGWoxDtBkCI0nKofdJQxT+LtZflvuc8Q3CjwWkq8KwUpHzkK/NmSsclCL0nseQdj5FRH5CNHSgtLiW80Of5HU9Hhlsga9bnBq3fEVltKfO5IaSTmGjjc4J0otcP7QsJUSQM8pEj5/wCuUuC2DWz8AAAAAElFTkSuQmCC");
+}
+
+.themeAmbiance button[disabled] svg {
+ opacity: 0.3;
+}
+
+.themeAmbiance svg {
+ fill: #cccccc;
+}
+
+body .themeAmbiance .cm-s-ambiance.CodeMirror {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+body .themeAmbiance .cm-s-ambiance .CodeMirror-gutters {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+ background-color: #202020;
+ border-right: 1px solid #202020;
+}
+
+body .themeAmbiance .cm-s-ambiance .CodeMirror-linenumber {
+ color: #717159;
+}
diff --git a/apps/client/src/resources/bespin.css b/apps/client/src/resources/bespin.css
new file mode 100644
index 000000000..8d3df7a8d
--- /dev/null
+++ b/apps/client/src/resources/bespin.css
@@ -0,0 +1,96 @@
+.themeBespin .cm-s-bespin {
+ font-size: 1em;
+ line-height: 1.5em;
+ font-family: inconsolata, monospace;
+ letter-spacing: 0.3px;
+ word-spacing: 1px;
+ background: #28211C;
+ color: #BAAE9E;
+}
+
+.themeBespin .cm-s-bespin .CodeMirror-lines {
+ padding: 8px 0;
+}
+
+.themeBespin .cm-s-bespin .CodeMirror-gutters {
+ box-shadow: 1px 0 2px 0 rgba(0, 0, 0, 0.5);
+ -webkit-box-shadow: 1px 0 2px 0 rgba(0, 0, 0, 0.5);
+ background-color: #28211C;
+ padding-right: 10px;
+ z-index: 3;
+ border: none;
+}
+
+.themeBespin .cm-s-bespin div.CodeMirror-cursor {
+ border-left: 3px solid #BAAE9E;
+}
+
+.themeBespin .cm-s-bespin .CodeMirror-activeline-background {
+ background: #FFFFFF08;
+}
+
+.themeBespin .cm-s-bespin .CodeMirror-selected {
+ background: #DDF0FF33;
+}
+
+.themeBespin .cm-s-bespin .cm-comment {
+ font-style: italic;
+ color: #666666;
+}
+
+.themeBespin .cm-s-bespin .cm-keyword {
+ color: #5EA6EA;
+}
+
+.themeBespin .cm-s-bespin .cm-string {
+ color: #54BE0D;
+}
+
+.themeBespin .cm-s-bespin .cm-property {
+ color: #DAD085;
+}
+
+.themeBespin .cm-s-bespin .cm-variable-2 {
+ color: #5EA6EA;
+}
+
+.themeBespin .cm-s-bespin .cm-atom {
+ color: #DAD085;
+}
+
+.themeBespin .cm-s-bespin .cm-number {
+ color: #DAD085;
+}
+
+.themeBespin .cm-s-bespin .cm-operator {
+ color: #5EA6EA;
+}
+
+.themeBespin {
+ background-color: #28211c;
+ color: #cccccc;
+}
+
+.themeBespin button[disabled] svg {
+ opacity: 0.3;
+}
+
+.themeBespin svg {
+ fill: #cccccc;
+}
+
+/* ----------------------------------------------- */
+
+body .themeBespin .cm-s-bespin .cm-tag {
+ color: #ca762d;
+}
+
+body .themeBespin .cm-s-bespin .CodeMirror-gutters {
+ -webkit-box-shadow: none;
+ -moz-box-shadow: none;
+ box-shadow: none;
+}
+
+body .themeBespin .cm-s-bespin .CodeMirror-linenumber {
+ color: #a27666;
+}
diff --git a/apps/client/src/resources/cobalt.css b/apps/client/src/resources/cobalt.css
new file mode 100644
index 000000000..48a3b21e0
--- /dev/null
+++ b/apps/client/src/resources/cobalt.css
@@ -0,0 +1,122 @@
+.themeCobalt .cm-s-cobalt.CodeMirror {
+ background: #002240;
+ color: white;
+}
+
+.themeCobalt .cm-s-cobalt div.CodeMirror-selected {
+ background: #b36539;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-line::selection, .themeCobalt .cm-s-cobalt .CodeMirror-line > span::selection,
+.themeCobalt .cm-s-cobalt .CodeMirror-line > span > span::selection {
+ background: rgba(179, 101, 57, 0.99);
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-line::-moz-selection, .themeCobalt .cm-s-cobalt .CodeMirror-line > span::-moz-selection,
+.themeCobalt .cm-s-cobalt .CodeMirror-line > span > span::-moz-selection {
+ background: rgba(179, 101, 57, 0.99);
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-gutters {
+ background: #002240;
+ border-right: 1px solid #aaa;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-guttermarker {
+ color: #ffee80;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-guttermarker-subtle {
+ color: #d0d0d0;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-linenumber {
+ color: #d0d0d0;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-cursor {
+ border-left: 1px solid white;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-comment {
+ color: #08f;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-atom {
+ color: #845dc4;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-number, .themeCobalt .cm-s-cobalt span.cm-attribute {
+ color: #ff80e1;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-keyword {
+ color: #ffee80;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-string {
+ color: #3ad900;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-meta {
+ color: #ff9d00;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-variable-2, .themeCobalt .cm-s-cobalt span.cm-tag {
+ color: #9effff;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-variable-3, .themeCobalt .cm-s-cobalt span.cm-def, .themeCobalt .cm-s-cobalt .cm-type {
+ color: white;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-bracket {
+ color: #d8d8d8;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-builtin, .themeCobalt .cm-s-cobalt span.cm-special {
+ color: #ff9e59;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-link {
+ color: #845dc4;
+}
+
+.themeCobalt .cm-s-cobalt span.cm-error {
+ color: #9d1e15;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-activeline-background {
+ background: #002D57;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-matchingbracket {
+ outline: 1px solid grey;
+ color: white !important;
+}
+
+/* ---------------- */
+
+.themeCobalt {
+ background-color: #002240;
+}
+
+.themeCobalt {
+ color: #cccccc;
+}
+
+.themeCobalt button[disabled] svg {
+ opacity: 0.3;
+}
+
+.themeCobalt svg {
+ fill: #cccccc;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-linenumber {
+ color: #54718b;
+}
+
+.themeCobalt .cm-s-cobalt .CodeMirror-gutters {
+ border-right: #264c6b;
+}
diff --git a/apps/client/src/resources/codemirror.css b/apps/client/src/resources/codemirror.css
new file mode 100644
index 000000000..8cccc732c
--- /dev/null
+++ b/apps/client/src/resources/codemirror.css
@@ -0,0 +1,533 @@
+.CodeMirror {
+ font-family: monospace;
+ height: 10px;
+ color: black;
+ direction: ltr;
+}
+
+/* PADDING */
+
+.CodeMirror-lines {
+ padding: 4px 0; /* Vertical padding around content */
+}
+
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
+ padding: 0 6px 0 50px; /* Horizontal padding of content */
+}
+
+.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ background-color: white; /* The little square between H and V scrollbars */
+}
+
+/* GUTTER */
+
+.CodeMirror-gutters {
+ border-right: 1px solid #ddd;
+ background-color: #f7f7f7;
+ white-space: nowrap;
+}
+
+.CodeMirror-linenumbers {
+}
+
+.CodeMirror-linenumber {
+ padding: 0 3px 0 5px;
+ min-width: 20px;
+ text-align: right;
+ color: #999;
+ white-space: nowrap;
+}
+
+.CodeMirror-guttermarker {
+ color: black;
+}
+
+.CodeMirror-guttermarker-subtle {
+ color: #999;
+}
+
+/* CURSOR */
+
+.CodeMirror-cursor {
+ border-left: 1px solid black;
+ border-right: none;
+ width: 0;
+}
+
+/* Shown when moving in bi-directional text */
+.CodeMirror div.CodeMirror-secondarycursor {
+ border-left: 1px solid silver;
+}
+
+.cm-fat-cursor .CodeMirror-cursor {
+ width: auto;
+ border: 0 !important;
+ background: #7e7;
+}
+
+.cm-fat-cursor div.CodeMirror-cursors {
+ z-index: 1;
+}
+
+.cm-fat-cursor-mark {
+ background-color: rgba(20, 255, 20, 0.5);
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+}
+
+.cm-animate-fat-cursor {
+ width: auto;
+ border: 0;
+ -webkit-animation: blink 1.06s steps(1) infinite;
+ -moz-animation: blink 1.06s steps(1) infinite;
+ animation: blink 1.06s steps(1) infinite;
+ background-color: #7e7;
+}
+
+@-moz-keyframes blink {
+ 0% {
+ }
+ 50% {
+ background-color: transparent;
+ }
+ 100% {
+ }
+}
+
+@-webkit-keyframes blink {
+ 0% {
+ }
+ 50% {
+ background-color: transparent;
+ }
+ 100% {
+ }
+}
+
+@keyframes blink {
+ 0% {
+ }
+ 50% {
+ background-color: transparent;
+ }
+ 100% {
+ }
+}
+
+/* Can style cursor different in overwrite (non-insert) mode */
+.CodeMirror-overwrite .CodeMirror-cursor {
+}
+
+.cm-tab {
+ display: inline-block;
+ text-decoration: inherit;
+}
+
+.CodeMirror-rulers {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: -50px;
+ bottom: 0;
+ overflow: hidden;
+}
+
+.CodeMirror-ruler {
+ border-left: 1px solid #ccc;
+ top: 0;
+ bottom: 0;
+ position: absolute;
+}
+
+/* DEFAULT THEME */
+
+.cm-s-default .cm-header {
+ color: blue;
+}
+
+.cm-s-default .cm-quote {
+ color: #090;
+}
+
+.cm-negative {
+ color: #d44;
+}
+
+.cm-positive {
+ color: #292;
+}
+
+.cm-header, .cm-strong {
+ font-weight: bold;
+}
+
+.cm-em {
+ font-style: italic;
+}
+
+.cm-link {
+ text-decoration: underline;
+}
+
+.cm-strikethrough {
+ text-decoration: line-through;
+}
+
+.cm-s-default .cm-keyword {
+ color: #708;
+}
+
+.cm-s-default .cm-atom {
+ color: #219;
+}
+
+.cm-s-default .cm-number {
+ color: #164;
+}
+
+.cm-s-default .cm-def {
+ color: #00f;
+}
+
+.cm-s-default .cm-variable,
+.cm-s-default .cm-punctuation,
+.cm-s-default .cm-property,
+.cm-s-default .cm-operator {
+}
+
+.cm-s-default .cm-variable-2 {
+ color: #05a;
+}
+
+.cm-s-default .cm-variable-3, .cm-s-default .cm-type {
+ color: #085;
+}
+
+.cm-s-default .cm-comment {
+ color: #a50;
+}
+
+.cm-s-default .cm-string {
+ color: #a11;
+}
+
+.cm-s-default .cm-string-2 {
+ color: #f50;
+}
+
+.cm-s-default .cm-meta {
+ color: #555;
+}
+
+.cm-s-default .cm-qualifier {
+ color: #555;
+}
+
+.cm-s-default .cm-builtin {
+ color: #30a;
+}
+
+.cm-s-default .cm-bracket {
+ color: #997;
+}
+
+.cm-s-default .cm-tag {
+ color: #170;
+}
+
+.cm-s-default .cm-attribute {
+ color: #00c;
+}
+
+.cm-s-default .cm-hr {
+ color: #999;
+}
+
+.cm-s-default .cm-link {
+ color: #00c;
+}
+
+.cm-s-default .cm-error {
+ color: #f00;
+}
+
+.cm-invalidchar {
+ color: #f00;
+}
+
+.CodeMirror-composing {
+ border-bottom: 2px solid;
+}
+
+/* Default styles for common addons */
+
+div.CodeMirror span.CodeMirror-matchingbracket {
+ color: #0b0;
+}
+
+div.CodeMirror span.CodeMirror-nonmatchingbracket {
+ color: #a22;
+}
+
+.CodeMirror-matchingtag {
+ background: rgba(255, 150, 0, .3);
+}
+
+.CodeMirror-activeline-background {
+ background: #e8f2ff;
+}
+
+/* STOP */
+
+/* The rest of this file contains styles related to the mechanics of
+ the editor. You probably shouldn't touch them. */
+
+.CodeMirror {
+ position: relative;
+ overflow: hidden;
+}
+
+.CodeMirror-scroll {
+ overflow: scroll !important; /* Things will break if this is overridden */
+ /* 30px is the magic margin used to hide the element's real scrollbars */
+ /* See overflow: hidden in .CodeMirror */
+ margin-bottom: -30px;
+ margin-right: -30px;
+ padding-bottom: 30px;
+ height: 100%;
+ outline: none; /* Prevent dragging from highlighting the element */
+ position: relative;
+}
+
+.CodeMirror-sizer {
+ position: relative;
+ border-right: 30px solid transparent;
+}
+
+/* The fake, visible scrollbars. Used to force redraw during scrolling
+ before actual scrolling happens, thus preventing shaking and
+ flickering artifacts. */
+.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
+ position: absolute;
+ z-index: 6;
+ display: none;
+}
+
+.CodeMirror-vscrollbar {
+ right: 0;
+ top: 0;
+ overflow-x: hidden;
+ overflow-y: scroll;
+}
+
+.CodeMirror-hscrollbar {
+ bottom: 0;
+ left: 0;
+ overflow-y: hidden;
+ overflow-x: scroll;
+}
+
+.CodeMirror-scrollbar-filler {
+ right: 0;
+ bottom: 0;
+}
+
+.CodeMirror-gutter-filler {
+ left: 0;
+ bottom: 0;
+}
+
+.CodeMirror-gutters {
+ position: absolute;
+ left: 0;
+ top: 0;
+ min-height: 100%;
+ z-index: 3;
+}
+
+.CodeMirror-gutter {
+ white-space: normal;
+ height: 100%;
+ display: inline-block;
+ vertical-align: top;
+ margin-bottom: -30px;
+}
+
+.CodeMirror-gutter-wrapper {
+ position: absolute;
+ z-index: 4;
+ background: none !important;
+ border: none !important;
+}
+
+.CodeMirror-gutter-background {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ z-index: 4;
+}
+
+.CodeMirror-gutter-elt {
+ position: absolute;
+ cursor: default;
+ z-index: 4;
+}
+
+.CodeMirror-gutter-wrapper ::selection {
+ background-color: transparent
+}
+
+.CodeMirror-gutter-wrapper ::-moz-selection {
+ background-color: transparent
+}
+
+.CodeMirror-lines {
+ cursor: text;
+ min-height: 1px; /* prevents collapsing before first draw */
+}
+
+.CodeMirror pre.CodeMirror-line,
+.CodeMirror pre.CodeMirror-line-like {
+ /* Reset some styles that the rest of the page might have set */
+ -moz-border-radius: 0;
+ -webkit-border-radius: 0;
+ border-radius: 0;
+ border-width: 0;
+ background: transparent;
+ font-family: inherit;
+ font-size: inherit;
+ margin: 0;
+ white-space: pre;
+ word-wrap: normal;
+ line-height: inherit;
+ color: inherit;
+ z-index: 2;
+ position: relative;
+ overflow: visible;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-font-variant-ligatures: contextual;
+ font-variant-ligatures: contextual;
+}
+
+.CodeMirror-wrap pre.CodeMirror-line,
+.CodeMirror-wrap pre.CodeMirror-line-like {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ word-break: normal;
+}
+
+.CodeMirror-linebackground {
+ position: absolute;
+ left: 0;
+ right: 0;
+ top: 0;
+ bottom: 0;
+ z-index: 0;
+}
+
+.CodeMirror-linewidget {
+ position: relative;
+ z-index: 2;
+ padding: 0.1px; /* Force widget margins to stay inside of the container */
+}
+
+.CodeMirror-widget {
+}
+
+.CodeMirror-rtl pre {
+ direction: rtl;
+}
+
+.CodeMirror-code {
+ outline: none;
+}
+
+/* Force content-box sizing for the elements where we expect it */
+.CodeMirror-scroll,
+.CodeMirror-sizer,
+.CodeMirror-gutter,
+.CodeMirror-gutters,
+.CodeMirror-linenumber {
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+.CodeMirror-measure {
+ position: absolute;
+ width: 100%;
+ height: 0;
+ overflow: hidden;
+ visibility: hidden;
+}
+
+.CodeMirror-cursor {
+ position: absolute;
+ pointer-events: none;
+}
+
+.CodeMirror-measure pre {
+ position: static;
+}
+
+div.CodeMirror-cursors {
+ visibility: hidden;
+ position: relative;
+ z-index: 3;
+}
+
+div.CodeMirror-dragcursors {
+ visibility: visible;
+}
+
+.CodeMirror-focused div.CodeMirror-cursors {
+ visibility: visible;
+}
+
+.CodeMirror-selected {
+ background: #d9d9d9;
+}
+
+.CodeMirror-focused .CodeMirror-selected {
+ background: #d7d4f0;
+}
+
+.CodeMirror-crosshair {
+ cursor: crosshair;
+}
+
+.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection {
+ background: #d7d4f0;
+}
+
+.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection {
+ background: #d7d4f0;
+}
+
+.cm-searching {
+ background-color: #ffa;
+ background-color: rgba(255, 255, 0, .4);
+}
+
+/* Used to force a border model for a node */
+.cm-force-border {
+ padding-right: .1px;
+}
+
+@media print {
+ /* Hide the cursor when printing */
+ .CodeMirror div.CodeMirror-cursors {
+ visibility: hidden;
+ }
+}
+
+/* See issue #2901 */
+.cm-tab-wrap-hack:after {
+ content: '';
+}
+
+/* Help users use markselection to safely style text background */
+span.CodeMirror-selectedtext {
+ background: none;
+}
diff --git a/apps/client/src/resources/darcula.css b/apps/client/src/resources/darcula.css
new file mode 100644
index 000000000..a03b1ee4c
--- /dev/null
+++ b/apps/client/src/resources/darcula.css
@@ -0,0 +1,188 @@
+.themeDarcula .cm-s-darcula {
+ font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
+}
+
+.themeDarcula .cm-s-darcula.CodeMirror {
+ background: #2B2B2B;
+ color: #A9B7C6;
+}
+
+.themeDarcula .cm-s-darcula span.cm-meta {
+ color: #BBB529;
+}
+
+.themeDarcula .cm-s-darcula span.cm-number {
+ color: #6897BB;
+}
+
+.themeDarcula .cm-s-darcula span.cm-keyword {
+ color: #CC7832;
+ line-height: 1em;
+ font-weight: bold;
+}
+
+.themeDarcula .cm-s-darcula span.cm-def {
+ color: #A9B7C6;
+ font-style: italic;
+}
+
+.themeDarcula .cm-s-darcula span.cm-variable {
+ color: #A9B7C6;
+}
+
+.themeDarcula .cm-s-darcula span.cm-variable-2 {
+ color: #A9B7C6;
+}
+
+.themeDarcula .cm-s-darcula span.cm-variable-3 {
+ color: #9876AA;
+}
+
+.themeDarcula .cm-s-darcula span.cm-type {
+ color: #AABBCC;
+ font-weight: bold;
+}
+
+.themeDarcula .cm-s-darcula span.cm-property {
+ color: #FFC66D;
+}
+
+.themeDarcula .cm-s-darcula span.cm-operator {
+ color: #A9B7C6;
+}
+
+.themeDarcula .cm-s-darcula span.cm-string {
+ color: #6A8759;
+}
+
+.themeDarcula .cm-s-darcula span.cm-string-2 {
+ color: #6A8759;
+}
+
+.themeDarcula .cm-s-darcula span.cm-comment {
+ color: #61A151;
+ font-style: italic;
+}
+
+.themeDarcula .cm-s-darcula span.cm-link {
+ color: #CC7832;
+}
+
+.themeDarcula .cm-s-darcula span.cm-atom {
+ color: #CC7832;
+}
+
+.themeDarcula .cm-s-darcula span.cm-error {
+ color: #BC3F3C;
+}
+
+.themeDarcula .cm-s-darcula span.cm-tag {
+ color: #629755;
+ font-weight: bold;
+ font-style: italic;
+}
+
+.themeDarcula .cm-s-darcula span.cm-attribute {
+ color: #6897bb;
+}
+
+.themeDarcula .cm-s-darcula span.cm-qualifier {
+ color: #6A8759;
+}
+
+.themeDarcula .cm-s-darcula span.cm-bracket {
+ color: #A9B7C6;
+}
+
+.themeDarcula .cm-s-darcula span.cm-builtin {
+ color: #FF9E59;
+}
+
+.themeDarcula .cm-s-darcula span.cm-special {
+ color: #FF9E59;
+}
+
+.themeDarcula .cm-s-darcula span.cm-matchhighlight {
+ color: #FFFFFF;
+ background-color: rgba(50, 89, 48, 0.7);
+ font-weight: normal;
+}
+
+.themeDarcula .cm-s-darcula span.cm-searching {
+ color: #FFFFFF;
+ background-color: rgba(61, 115, 59, 0.7);
+ font-weight: normal;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirror-cursor {
+ border-left: 1px solid #A9B7C6;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirror-activeline-background {
+ background: #323232;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirror-gutters {
+ background: #313335;
+ border-right: 1px solid #313335;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirror-guttermarker {
+ color: #FFEE80;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirror-guttermarker-subtle {
+ color: #D0D0D0;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirrir-linenumber {
+ color: #606366;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirror-matchingbracket {
+ background-color: #3B514D;
+ color: #FFEF28 !important;
+ font-weight: bold;
+}
+
+.themeDarcula .cm-s-darcula div.CodeMirror-selected {
+ background: #214283;
+}
+
+.themeDarcula .CodeMirror-hints.darcula {
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
+ color: #9C9E9E;
+ background-color: #3B3E3F !important;
+}
+
+.themeDarcula .CodeMirror-hints.darcula .CodeMirror-hint-active {
+ background-color: #494D4E !important;
+ color: #9C9E9E !important;
+}
+
+/* ---------------- */
+
+.themeDarcula {
+ background-color: #2b2b2b;
+}
+
+.themeDarcula {
+ color: #cccccc;
+}
+
+.themeDarcula svg {
+ fill: #cccccc;
+}
+
+.themeDarcula button[disabled] svg {
+ opacity: 0.3;
+}
+
+.themeDarcula .cm-s-darcula .CodeMirror-gutters {
+ background-color: #2b2b2b;
+ border-right: 1px solid #484848;
+}
+
+body .themeDarcula .cm-s-darcula .CodeMirror-gutters {
+ border-right: 0;
+}
diff --git a/apps/client/src/resources/lucario.css b/apps/client/src/resources/lucario.css
new file mode 100644
index 000000000..3ee39a452
--- /dev/null
+++ b/apps/client/src/resources/lucario.css
@@ -0,0 +1,124 @@
+.themeLucario .cm-s-lucario.CodeMirror, .themeLucario .cm-s-lucario .CodeMirror-gutters {
+ background-color: #2b3e50 !important;
+ color: #f8f8f2 !important;
+ border: none;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-gutters {
+ color: #2b3e50;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-cursor {
+ border-left: solid thin #E6C845;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-linenumber {
+ color: #f8f8f2;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-selected {
+ background: #243443;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-line::selection, .themeLucario .cm-s-lucario .CodeMirror-line > span::selection, .themeLucario .cm-s-lucario .CodeMirror-line > span > span::selection {
+ background: #243443;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-line::-moz-selection, .themeLucario .cm-s-lucario .CodeMirror-line > span::-moz-selection, .themeLucario .cm-s-lucario .CodeMirror-line > span > span::-moz-selection {
+ background: #243443;
+}
+
+.themeLucario .cm-s-lucario span.cm-comment {
+ color: #5c98cd;
+}
+
+.themeLucario .cm-s-lucario span.cm-string, .themeLucario .cm-s-lucario span.cm-string-2 {
+ color: #E6DB74;
+}
+
+.themeLucario .cm-s-lucario span.cm-number {
+ color: #ca94ff;
+}
+
+.themeLucario .cm-s-lucario span.cm-variable {
+ color: #f8f8f2;
+}
+
+.themeLucario .cm-s-lucario span.cm-variable-2 {
+ color: #f8f8f2;
+}
+
+.themeLucario .cm-s-lucario span.cm-def {
+ color: #72C05D;
+}
+
+.themeLucario .cm-s-lucario span.cm-operator {
+ color: #66D9EF;
+}
+
+.themeLucario .cm-s-lucario span.cm-keyword {
+ color: #ff6541;
+}
+
+.themeLucario .cm-s-lucario span.cm-atom {
+ color: #bd93f9;
+}
+
+.themeLucario .cm-s-lucario span.cm-meta {
+ color: #f8f8f2;
+}
+
+.themeLucario .cm-s-lucario span.cm-tag {
+ color: #ff6541;
+}
+
+.themeLucario .cm-s-lucario span.cm-attribute {
+ color: #66D9EF;
+}
+
+.themeLucario .cm-s-lucario span.cm-qualifier {
+ color: #72C05D;
+}
+
+.themeLucario .cm-s-lucario span.cm-property {
+ color: #f8f8f2;
+}
+
+.themeLucario .cm-s-lucario span.cm-builtin {
+ color: #72C05D;
+}
+
+.themeLucario .cm-s-lucario span.cm-variable-3, .themeLucario .cm-s-lucario span.cm-type {
+ color: #ffb86c;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-activeline-background {
+ background: #243443;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-matchingbracket {
+ text-decoration: underline;
+ color: white !important;
+}
+
+/* ---------------- */
+
+.themeLucario {
+ background-color: #2b3e50;
+}
+
+.themeLucario {
+ color: #cccccc;
+}
+
+.themeLucario button[disabled] svg {
+ opacity: 0.3;
+}
+
+.themeLucario svg {
+ fill: #cccccc;
+}
+
+.themeLucario .cm-s-lucario .CodeMirror-linenumber {
+ color: #697988;
+}
diff --git a/apps/client/src/styles/etShared.scss b/apps/client/src/styles/etShared.scss
new file mode 100644
index 000000000..2443869ba
--- /dev/null
+++ b/apps/client/src/styles/etShared.scss
@@ -0,0 +1,14 @@
+.settingRow {
+ margin-top: 10px;
+
+ label {
+ display: block;
+ margin-bottom: 4px;
+ color: #666666;
+ }
+
+ button {
+ margin-right: 6px;
+ width: inherit;
+ }
+}
diff --git a/apps/client/src/styles/etShared.scss.d.ts b/apps/client/src/styles/etShared.scss.d.ts
new file mode 100644
index 000000000..fe8eb156b
--- /dev/null
+++ b/apps/client/src/styles/etShared.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace EtSharedScssNamespace {
+ export interface IEtSharedScss {
+ settingRow: string;
+ }
+}
+
+declare const EtSharedScssModule: EtSharedScssNamespace.IEtSharedScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: EtSharedScssNamespace.IEtSharedScss;
+};
+
+export = EtSharedScssModule;
diff --git a/apps/client/src/styles/global.scss b/apps/client/src/styles/global.scss
new file mode 100644
index 000000000..0235e369d
--- /dev/null
+++ b/apps/client/src/styles/global.scss
@@ -0,0 +1,34 @@
+@use './variables.scss' as c;
+
+h3 {
+ margin: 10px 0 8px;
+}
+
+a {
+ text-decoration: none;
+ color: c.$primary-color;
+
+ &:hover {
+ text-decoration: underline;
+ }
+}
+
+:global(.Select-menu-outer) {
+ z-index: 10000 !important;
+}
+
+body :global(div.MuiTooltip-popper) {
+ z-index: 5005 !important; // was 5010
+}
+
+.recharts-wrapper .recharts-area {
+ transition: none;
+}
+
+body :global(.MuiAlert-filledError) {
+ background-color: #c52820;
+}
+
+ul:global(.MuiPagination-ul) li {
+ padding: 0 !important;
+}
diff --git a/apps/client/src/styles/global.scss.d.ts b/apps/client/src/styles/global.scss.d.ts
new file mode 100644
index 000000000..0c8db63f7
--- /dev/null
+++ b/apps/client/src/styles/global.scss.d.ts
@@ -0,0 +1,13 @@
+declare namespace GlobalScssNamespace {
+ export interface IGlobalScss {
+ 'recharts-area': string;
+ 'recharts-wrapper': string;
+ }
+}
+
+declare const GlobalScssModule: GlobalScssNamespace.IGlobalScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: GlobalScssNamespace.IGlobalScss;
+};
+
+export = GlobalScssModule;
diff --git a/apps/client/src/styles/shared.scss b/apps/client/src/styles/shared.scss
new file mode 100644
index 000000000..ac6f356df
--- /dev/null
+++ b/apps/client/src/styles/shared.scss
@@ -0,0 +1,160 @@
+@use './variables.scss' as c;
+
+.tip {
+ color: #999999;
+}
+
+.emptyCol {
+ padding: 7px 2px;
+ color: #999999;
+}
+
+.blank {
+ color: #cccccc;
+}
+
+.emptyText {
+ color: #666666;
+ font-style: italic;
+}
+
+input.errorField,
+textarea.errorField,
+select.errorField,
+button.errorField {
+ border: 1px solid c.$error;
+
+ &:focus {
+ border: 1px solid c.$error;
+ }
+}
+
+.pill {
+ background-color: #dfecfc;
+ border-radius: 3px;
+ padding: 1px 6px;
+}
+
+.copyCol {
+ flex: 0 0 26px;
+}
+
+:global(div.MuiAlert-root) {
+ font-size: 13px;
+}
+
+.twoColPage {
+ max-width: 1024px;
+ margin: 15px auto 0;
+ width: 100%;
+ padding: 0 10px;
+ height: 100%;
+ flex: 1;
+ display: flex;
+ flex-direction: row;
+ font-size: 12px;
+
+ input {
+ font-size: 14px;
+ }
+
+ nav {
+ flex: 0 0 240px;
+
+ ul {
+ list-style: none;
+ padding-left: 0;
+
+ li {
+ padding: 10px 10px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: color 0.2s ease-in-out;
+
+ &:hover {
+ color: c.$primary-color;
+ }
+
+ &.selected {
+ color: c.$primary-color;
+ }
+ }
+ }
+ }
+
+ .tab {
+ flex: 1;
+ padding: 20px 0;
+ overflow: scroll;
+ }
+
+ label {
+ color: #999999;
+ }
+
+ :global(.react-select__placeholder),
+ :global(.react-select__single-value) {
+ font-size: 14px;
+ }
+}
+
+.cancelLink {
+ margin-left: 15px;
+ font-size: 13px;
+ color: #999999;
+ cursor: pointer;
+ transition: visibility 200ms ease-in-out;
+
+ &:hover {
+ color: c.$primary-color;
+ }
+
+ &.hidden {
+ visibility: hidden;
+ }
+}
+
+.generatorControls {
+ flex: 1;
+ overflow: hidden;
+ padding-right: 18px;
+ opacity: 0;
+ transition: opacity 200ms ease-in-out;
+
+ &.visible {
+ opacity: 1;
+ }
+}
+
+.fadeIn {
+ opacity: 0;
+ transition: opacity 200ms ease-in-out;
+}
+
+.mainLogo {
+ opacity: 0;
+ position: absolute;
+ left: 55px;
+ top: 17px;
+ transition: opacity 200ms ease-in-out;
+ user-select: none;
+
+ img {
+ width: 200px;
+ }
+
+ &.visible {
+ opacity: 1;
+ }
+}
+
+@media (max-width: 600px) {
+ .twoColPage {
+ flex-direction: column;
+
+ nav {
+ flex: 0 0 auto;
+ border-bottom: 1px solid #efefef;
+ }
+ }
+}
diff --git a/apps/client/src/styles/shared.scss.d.ts b/apps/client/src/styles/shared.scss.d.ts
new file mode 100644
index 000000000..9b6aaa031
--- /dev/null
+++ b/apps/client/src/styles/shared.scss.d.ts
@@ -0,0 +1,27 @@
+declare namespace SharedScssNamespace {
+ export interface ISharedScss {
+ blank: string;
+ cancelLink: string;
+ copyCol: string;
+ emptyCol: string;
+ emptyText: string;
+ errorField: string;
+ fadeIn: string;
+ generatorControls: string;
+ hidden: string;
+ mainLogo: string;
+ pill: string;
+ selected: string;
+ tab: string;
+ tip: string;
+ twoColPage: string;
+ visible: string;
+ }
+}
+
+declare const SharedScssModule: SharedScssNamespace.ISharedScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: SharedScssNamespace.ISharedScss;
+};
+
+export = SharedScssModule;
diff --git a/apps/client/src/styles/variables.scss b/apps/client/src/styles/variables.scss
new file mode 100644
index 000000000..91c287570
--- /dev/null
+++ b/apps/client/src/styles/variables.scss
@@ -0,0 +1,14 @@
+$primary-color: #275eb5;
+$primary-pale-color: #e0ebfd;
+$primary-submit: #30aadc;
+$primary-submit-hover: #148ec0;
+$primary-pale-bg: #d4e6f7;
+
+$secondary-color: #006f0d;
+$secondary-pale-color: #e8fceb;
+$secondary-dark-color: #002a05;
+
+$field-focus-color: #2684FF;
+$bg-color: #f2f2f2;
+
+$error: #db0000;
diff --git a/apps/client/src/tours/Components.tour.tsx b/apps/client/src/tours/Components.tour.tsx
new file mode 100644
index 000000000..20d19f08f
--- /dev/null
+++ b/apps/client/src/tours/Components.tour.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { PrimaryButton, NullButton } from '~components/Buttons.component';
+import { getStrings } from '@generatedata/utils/lang';
+
+export const TourCompleteStep = ({ close }: any) => {
+ const { core: i18n } = getStrings();
+
+ const onExit = (): void => {
+ close(true);
+ };
+
+ return (
+ <>
+ {i18n.tourComplete}
+
+ {i18n.tourCompleteDesc}
+
+
+
+ {i18n.tryDifferentTour}
+
+
+ Exit
+
+
+ >
+ );
+};
diff --git a/apps/client/src/tours/GridPanel.tour.tsx b/apps/client/src/tours/GridPanel.tour.tsx
new file mode 100644
index 000000000..be1f4d72b
--- /dev/null
+++ b/apps/client/src/tours/GridPanel.tour.tsx
@@ -0,0 +1,354 @@
+import React from 'react';
+import Reactour, { ReactourStepPosition } from 'reactour';
+import { getStrings } from '@generatedata/utils/lang';
+import store from '~core/store';
+import * as actions from '~store/generator/generator.actions';
+import * as selectors from '~store/generator/generator.selectors';
+import { TourCompleteStep } from './Components.tour';
+import { TourProps } from '~types/general';
+import { GeneratorLayout } from '@generatedata/types';
+import { DataTypeFolder, ExportTypeFolder } from '@generatedata/plugins';
+
+const Step1 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.theGridPanel}
+ {i18n.gridPanelTourIntroDesc1}
+ {i18n.gridPanelTourIntroDesc2}
+ >
+ );
+};
+
+const Step2 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.columns}
+
+ {i18n.columnsDesc}
+ >
+ );
+};
+
+const Step3 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.rowNumber}
+ {i18n.rowNumDesc1}
+ {i18n.rowNumDesc2}
+ >
+ );
+};
+
+const Step4 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.dataType}
+ {i18n.dataTypeDesc}
+ >
+ );
+};
+
+const Step5 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.helpIcon}
+ {i18n.helpIconDesc}
+ >
+ );
+};
+
+const Step6 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.nameColumn}
+ {i18n.nameColumnDesc}
+ >
+ );
+};
+
+const Step7 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.exampleColumn}
+ {i18n.exampleColumnDesc}
+ >
+ );
+};
+
+const Step8 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.optionsColumn}
+ {i18n.optionsColumnDesc1}
+ {i18n.optionsColumnDesc2}
+ >
+ );
+};
+
+const Step9 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.deleteRow}
+ {i18n.deleteRowDesc}
+ >
+ );
+};
+
+const Step10 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.addRows}
+ {i18n.addRowsDesc}
+ >
+ );
+};
+
+const commonStyles = {
+ borderRadius: 6,
+ margin: 12
+};
+
+const steps = [
+ {
+ content: Step1,
+ style: {
+ ...commonStyles
+ },
+ position: 'center' as ReactourStepPosition,
+ action: (): void => {
+ setTimeout(() => {
+ store.dispatch(actions.clearPage(false));
+ store.dispatch(actions.addRows(10));
+
+ const state = store.getState();
+ const rows = selectors.getSortedRowsArray(state);
+
+ if (!selectors.isGridVisible(state)) {
+ store.dispatch(actions.toggleGrid());
+ }
+ if (selectors.isPreviewVisible(state)) {
+ store.dispatch(actions.togglePreview());
+ }
+
+ const layout = selectors.getGeneratorLayout(state);
+ if (layout === GeneratorLayout.vertical) {
+ store.dispatch(actions.toggleLayout());
+ }
+
+ const ids = rows.map(({ id }) => id);
+
+ const pluginsToLoad: any = {
+ Names: false,
+ Phone: false,
+ Email: false,
+ StreetAddress: false,
+ City: false,
+ Region: false,
+ Country: false,
+ LatLng: false,
+ Alphanumeric: false,
+ Boolean: false
+ };
+
+ const onLoadComplete = (plugin: DataTypeFolder | ExportTypeFolder): void => {
+ pluginsToLoad[plugin] = true;
+
+ const allLoaded = Object.keys(pluginsToLoad).reduce((allLoaded, key) => allLoaded && pluginsToLoad[key], true);
+ if (allLoaded) {
+ store.dispatch(actions.refreshPreview(ids));
+ }
+ };
+
+ const shouldRefreshPreviewPanel = false;
+ store.dispatch(
+ actions.onSelectDataType('Names', {
+ gridRowId: ids[0],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Phone', {
+ gridRowId: ids[1],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Email', {
+ gridRowId: ids[2],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('StreetAddress', {
+ gridRowId: ids[3],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('City', {
+ gridRowId: ids[4],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Region', {
+ gridRowId: ids[5],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Country', {
+ gridRowId: ids[6],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('LatLng', {
+ gridRowId: ids[7],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Alphanumeric', {
+ gridRowId: ids[8],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Boolean', {
+ gridRowId: ids[8],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+
+ document.querySelector('.tour-scrollableGridRows')!.scrollTop = 0;
+ }, 10);
+ }
+ },
+ {
+ content: Step2,
+ selector: '.tour-gridHeader',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step3,
+ selector: '.tour-gridRow div:nth-child(1)',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step4,
+ selector: '.tour-gridRow>div:nth-child(2)>div:nth-child(1) div',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step5,
+ selector: '.tour-gridRow>div:nth-child(2)>div:nth-child(2) svg',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step6,
+ selector: '.tour-gridRow div:nth-child(3) input',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step7,
+ selector: '.tour-gridRow div:nth-child(4)>*',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step8,
+ selector: '.tour-gridRow div:nth-child(5)>*',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step9,
+ selector: '.tour-gridRow div:nth-child(7) svg',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step10,
+ selector: '.tour-addRows',
+ style: {
+ ...commonStyles,
+ marginTop: -20
+ },
+ position: 'top' as ReactourStepPosition
+ },
+ {
+ content: TourCompleteStep,
+ style: {
+ ...commonStyles
+ }
+ }
+];
+
+const Tour = ({ isOpen, onClose, maskClassName, closeWithMask, disableInteraction, accentColor, className }: TourProps) => (
+
+);
+
+export default Tour;
diff --git a/apps/client/src/tours/IntroToGenerator.tour.tsx b/apps/client/src/tours/IntroToGenerator.tour.tsx
new file mode 100644
index 000000000..312acbae1
--- /dev/null
+++ b/apps/client/src/tours/IntroToGenerator.tour.tsx
@@ -0,0 +1,265 @@
+import React from 'react';
+import Reactour, { ReactourStepPosition } from 'reactour';
+import store from '~core/store';
+import { getI18nString, getStrings } from '@generatedata/utils/lang';
+import * as selectors from '~store/generator/generator.selectors';
+import * as actions from '~store/generator/generator.actions';
+import { TourCompleteStep } from './Components.tour';
+import { TourProps } from '~types/general';
+import { DataTypeFolder, ExportTypeFolder } from '@generatedata/plugins';
+import clientConfig from '@generatedata/config/clientConfig';
+
+const Step1 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.introToGenerator}
+
+ {i18n.introToGeneratorDesc}
+ >
+ );
+};
+
+const Step2 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.theGridPanel}
+
+ {i18n.gridPanelTourDesc1}
+ {i18n.gridPanelTourDesc2}
+ >
+ );
+};
+
+const Step3 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.thePreviewPanel}
+ {i18n.previewPanelDesc}
+ {i18n.previewPanelMoreInfo}
+ >
+ );
+};
+
+const Step4 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.theDataSetName}
+
+ {i18n.dataSetNameDesc}
+ >
+ );
+};
+
+const Step5 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.panelControls}
+
+ {i18n.panelControlsDesc}
+
+ {i18n.panelControlsClearIconDesc}
+ >
+ );
+};
+
+const Step6 = () => {
+ const { core: i18n } = getStrings();
+ const saveBtnDesc = getI18nString(i18n.saveButtonDesc, [clientConfig.appSettings.GD_MAX_DATASET_HISTORY_SIZE]);
+
+ return (
+ <>
+ {i18n.theSaveButton}
+
+ {saveBtnDesc}
+ >
+ );
+};
+
+const Step7 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.theGenerateButton}
+
+ {i18n.generateBtnDesc}
+ >
+ );
+};
+
+const commonStyles = {
+ borderRadius: 6,
+ margin: 12
+};
+
+const steps = [
+ {
+ content: Step1,
+ style: {
+ ...commonStyles
+ },
+ position: 'center' as ReactourStepPosition,
+ action: (): void => {
+ const pluginsToLoad: any = {
+ JSON: false,
+ Names: false,
+ Phone: false,
+ Email: false,
+ StreetAddress: false,
+ City: false
+ };
+
+ store.dispatch(actions.clearPage(false));
+ store.dispatch(actions.addRows(5));
+
+ const state = store.getState();
+ const rows = selectors.getSortedRowsArray(state);
+ const ids = rows.map(({ id }) => id);
+
+ const onLoadComplete = (plugin: DataTypeFolder | ExportTypeFolder): void => {
+ pluginsToLoad[plugin] = true;
+
+ const allLoaded = Object.keys(pluginsToLoad).reduce((allLoaded, key) => allLoaded && pluginsToLoad[key], true);
+ if (allLoaded) {
+ store.dispatch(actions.refreshPreview(ids));
+ }
+ };
+
+ const shouldRefreshPreviewPanel = false;
+ store.dispatch(
+ actions.onSelectDataType('Names', {
+ gridRowId: ids[0],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Phone', {
+ gridRowId: ids[1],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Email', {
+ gridRowId: ids[2],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('StreetAddress', {
+ gridRowId: ids[3],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('City', {
+ gridRowId: ids[4],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectExportType('JSON', {
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+
+ const layout = selectors.getGeneratorLayout(state);
+ if (layout === 'horizontal') {
+ store.dispatch(actions.toggleLayout());
+ }
+ if (!selectors.isGridVisible(state)) {
+ store.dispatch(actions.toggleGrid());
+ }
+ if (!selectors.isPreviewVisible(state)) {
+ store.dispatch(actions.togglePreview());
+ }
+ }
+ },
+ {
+ content: Step2,
+ selector: '.gdGridPanel>div:first-child',
+ style: {
+ ...commonStyles
+ }
+ },
+ {
+ content: Step3,
+ selector: '.gdGridPanel>div:nth-child(3)',
+ style: {
+ ...commonStyles,
+ marginLeft: -20
+ }
+ },
+ {
+ content: Step4,
+ selector: '.tour-dataSetName',
+ style: {
+ ...commonStyles,
+ marginLeft: 10
+ }
+ },
+ {
+ content: Step5,
+ selector: '.tour-panelControls',
+ style: {
+ ...commonStyles,
+ marginTop: -20
+ },
+ position: 'top' as ReactourStepPosition
+ },
+ {
+ content: Step6,
+ selector: '.tour-saveButton',
+ style: {
+ ...commonStyles,
+ marginTop: -20
+ },
+ position: 'top' as ReactourStepPosition
+ },
+ {
+ content: Step7,
+ selector: '.tour-generateButton',
+ style: {
+ ...commonStyles,
+ marginTop: -20
+ },
+ position: 'top' as ReactourStepPosition
+ },
+ {
+ content: TourCompleteStep,
+ style: {
+ ...commonStyles
+ }
+ }
+];
+
+const Tour = ({ isOpen, onClose, maskClassName, closeWithMask, disableInteraction, accentColor, className }: TourProps) => (
+
+);
+
+export default Tour;
diff --git a/apps/client/src/tours/PreviewPanel.tour.tsx b/apps/client/src/tours/PreviewPanel.tour.tsx
new file mode 100644
index 000000000..7b711ad29
--- /dev/null
+++ b/apps/client/src/tours/PreviewPanel.tour.tsx
@@ -0,0 +1,300 @@
+import React from 'react';
+import Reactour, { ReactourStepPosition } from 'reactour';
+import { getStrings } from '@generatedata/utils/lang';
+import store from '~core/store';
+import * as actions from '~store/generator/generator.actions';
+import * as selectors from '~store/generator/generator.selectors';
+import { TourCompleteStep } from './Components.tour';
+import { TourProps } from '~types/general';
+import { DataTypeFolder, ExportTypeFolder } from '@generatedata/plugins';
+
+const Step1 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.thePreviewPanel}
+ {i18n.previewPanelTourDesc}
+ >
+ );
+};
+
+const Step2 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.panelContents}
+ {i18n.panelContentsDesc}
+ >
+ );
+};
+
+const Step3 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.panelControls}
+ {i18n.previewPanelControlsDesc}
+ >
+ );
+};
+
+const Step4 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.theExportTypeBtn}
+ {i18n.exportTypeBtnDesc}
+ {i18n.clickExportTypeBtn}
+ >
+ );
+};
+
+const Step5 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.editingExportTypes}
+ {i18n.editExportTypePage}
+ {i18n.editExportTypePageConfig}
+ >
+ );
+};
+
+const Step6 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.exportTypeSelection}
+ {i18n.exportTypeSelectionDesc}
+ >
+ );
+};
+
+const Step7 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.exportTypeOptionsTs}
+ {i18n.exportTypeOptionsTsDesc}
+ {i18n.exportTypeOptionsTsDesc2}
+ >
+ );
+};
+
+const Step8 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.exportTypeOptionsSql}
+ {i18n.exportTypeOptionsSqlDesc}
+ >
+ );
+};
+
+const Step9 = () => {
+ const { core: i18n } = getStrings();
+
+ return (
+ <>
+ {i18n.panelTabs}
+ {i18n.panelTabsDesc}
+ >
+ );
+};
+
+const commonStyles = {
+ borderRadius: 6,
+ margin: 12
+};
+
+const steps = [
+ {
+ content: Step1,
+ style: {
+ ...commonStyles
+ },
+ position: 'center' as ReactourStepPosition,
+ action: (): void => {
+ setTimeout(() => {
+ store.dispatch(actions.clearPage(false));
+ store.dispatch(actions.addRows(5));
+
+ const state = store.getState();
+ const rows = selectors.getSortedRowsArray(state);
+
+ const layout = selectors.getGeneratorLayout(state);
+ if (layout === 'horizontal') {
+ store.dispatch(actions.toggleLayout());
+ }
+ if (!selectors.isGridVisible(state)) {
+ store.dispatch(actions.toggleGrid());
+ }
+ if (!selectors.isPreviewVisible(state)) {
+ store.dispatch(actions.togglePreview());
+ }
+
+ const ids = rows.map(({ id }) => id);
+
+ const pluginsToLoad: any = {
+ Typescript: false,
+ Country: false,
+ Region: false,
+ City: false,
+ StreetAddress: false,
+ PostalZip: false
+ };
+
+ const onLoadComplete = (plugin: DataTypeFolder | ExportTypeFolder): void => {
+ pluginsToLoad[plugin] = true;
+
+ const allLoaded = Object.keys(pluginsToLoad).reduce((allLoaded, key) => allLoaded && pluginsToLoad[key], true);
+ if (allLoaded) {
+ store.dispatch(actions.refreshPreview(ids));
+ }
+ };
+
+ const shouldRefreshPreviewPanel = false;
+ store.dispatch(
+ actions.onSelectDataType('Country', {
+ gridRowId: ids[0],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('Region', {
+ gridRowId: ids[1],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('City', {
+ gridRowId: ids[2],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('StreetAddress', {
+ gridRowId: ids[3],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(
+ actions.onSelectDataType('PostalZip', {
+ gridRowId: ids[4],
+ shouldRefreshPreviewPanel,
+ onLoadComplete
+ })
+ );
+ store.dispatch(actions.onSelectExportType('Typescript', { shouldRefreshPreviewPanel, onLoadComplete }));
+ }, 10);
+ }
+ },
+ {
+ content: Step2,
+ selector: '.gdGridPanel>div:nth-child(3)',
+ style: {
+ ...commonStyles,
+ marginLeft: -20
+ }
+ },
+
+ {
+ content: Step3,
+ selector: '.tour-previewPanelControls',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step4,
+ selector: '.tour-exportTypeBtn',
+ style: {
+ ...commonStyles
+ },
+ position: 'bottom' as ReactourStepPosition
+ },
+ {
+ content: Step5,
+ style: {
+ ...commonStyles
+ },
+ action: (): void => {
+ store.dispatch(actions.toggleExportSettings('previewPanel'));
+ }
+ },
+ {
+ content: Step6,
+ selector: '.tour-exportTypeDropdown>div',
+ style: {
+ ...commonStyles,
+ marginLeft: 20
+ },
+ position: 'right' as ReactourStepPosition
+ },
+ {
+ content: Step7,
+ selector: '.tour-exportTypePanel',
+ style: {
+ ...commonStyles,
+ marginLeft: 20
+ },
+ position: 'right' as ReactourStepPosition
+ },
+ {
+ content: Step8,
+ selector: '.tour-exportTypePanel',
+ style: {
+ ...commonStyles,
+ marginLeft: 20
+ },
+ action: (): void => {
+ store.dispatch(actions.onSelectExportType('SQL'));
+ },
+ position: 'right' as ReactourStepPosition
+ },
+ {
+ content: Step9,
+ selector: '.tour-exportTypePanelTabs',
+ style: {
+ ...commonStyles,
+ marginLeft: 20
+ },
+ position: 'left' as ReactourStepPosition
+ },
+ {
+ content: TourCompleteStep,
+ style: {
+ ...commonStyles
+ }
+ }
+];
+
+const Tour = ({ isOpen, onClose, maskClassName, closeWithMask, disableInteraction, accentColor, className }: TourProps) => (
+
+);
+
+export default Tour;
diff --git a/apps/client/src/tours/Tours.scss b/apps/client/src/tours/Tours.scss
new file mode 100644
index 000000000..72bf70cd9
--- /dev/null
+++ b/apps/client/src/tours/Tours.scss
@@ -0,0 +1,3 @@
+.mask {
+ opacity: 0.5;
+}
diff --git a/apps/client/src/tours/Tours.scss.d.ts b/apps/client/src/tours/Tours.scss.d.ts
new file mode 100644
index 000000000..9d98151c4
--- /dev/null
+++ b/apps/client/src/tours/Tours.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace ToursScssNamespace {
+ export interface IToursScss {
+ mask: string;
+ }
+}
+
+declare const ToursScssModule: ToursScssNamespace.IToursScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ToursScssNamespace.IToursScss;
+};
+
+export = ToursScssModule;
diff --git a/apps/client/src/tours/__tests__/Tours.test.tsx b/apps/client/src/tours/__tests__/Tours.test.tsx
new file mode 100644
index 000000000..ef9cf16cf
--- /dev/null
+++ b/apps/client/src/tours/__tests__/Tours.test.tsx
@@ -0,0 +1,89 @@
+import React from 'react';
+import sinon from 'sinon';
+import { render } from '@testing-library/react';
+import { TourCompleteStep } from '../Components.tour';
+import GridPanelTour from '../GridPanel.tour';
+import IntroToGenerator from '../IntroToGenerator.tour';
+import PreviewPanel from '../PreviewPanel.tour';
+import langUtils from '@generatedata/utils/lang';
+
+const i18n = require('../../i18n/en.json');
+
+describe('TourCompleteStep', () => {
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('renders', () => {
+ sinon.stub(langUtils, 'getStrings').returns({
+ core: i18n,
+ dataTypes: {}
+ });
+
+ const { container } = render( {}} />);
+
+ // there should be two buttons
+ expect(container.querySelectorAll('button').length).toEqual(2);
+ });
+});
+
+const tourProps = {
+ isOpen: true,
+ onClose: () => {},
+ maskClassName: 'test',
+ closeWithMask: true,
+ disableInteraction: true,
+ accentColor: '#444400',
+ className: 'classname'
+};
+
+describe('GriaPanel tour', () => {
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('renders', () => {
+ sinon.stub(langUtils, 'getStrings').returns({
+ core: i18n,
+ dataTypes: {}
+ });
+
+ const { baseElement } = render( );
+
+ expect(baseElement.querySelector('svg')).toBeTruthy();
+ });
+});
+
+describe('IntroToGenerator tour', () => {
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('renders', () => {
+ sinon.stub(langUtils, 'getStrings').returns({
+ core: i18n,
+ dataTypes: {}
+ });
+
+ const { baseElement } = render( );
+
+ expect(baseElement.querySelector('svg')).toBeTruthy();
+ });
+});
+
+describe('PreviewPanel tour', () => {
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('renders', () => {
+ sinon.stub(langUtils, 'getStrings').returns({
+ core: i18n,
+ dataTypes: {}
+ });
+
+ const { baseElement } = render( );
+
+ expect(baseElement.querySelector('svg')).toBeTruthy();
+ });
+});
diff --git a/apps/client/src/tours/index.tsx b/apps/client/src/tours/index.tsx
new file mode 100644
index 000000000..e77f9c912
--- /dev/null
+++ b/apps/client/src/tours/index.tsx
@@ -0,0 +1,17 @@
+import GridPanel from './GridPanel.tour';
+import IntroToGenerator from './IntroToGenerator.tour';
+import PreviewPanel from './PreviewPanel.tour';
+
+const tours = {
+ intro: {
+ component: IntroToGenerator
+ },
+ gridPanel: {
+ component: GridPanel
+ },
+ previewPanel: {
+ component: PreviewPanel
+ }
+};
+
+export default tours;
diff --git a/apps/client/src/utils/__tests__/routeUtils.test.ts b/apps/client/src/utils/__tests__/routeUtils.test.ts
new file mode 100644
index 000000000..f7a87cc41
--- /dev/null
+++ b/apps/client/src/utils/__tests__/routeUtils.test.ts
@@ -0,0 +1,41 @@
+import { getRoutes, removeLocale } from '../routeUtils';
+
+describe('getRoutes', () => {
+ it('returns all available routes by default with the generator page the last one with the root path', () => {
+ const defaultPaths = [
+ '/account',
+ '/accounts',
+ '/login',
+ '/datasets',
+ '/register',
+ '/'
+ ];
+
+ const foundPaths = getRoutes().map(({ path }) => path);
+ expect(foundPaths).toEqual(defaultPaths);
+ });
+});
+
+describe('removeLocale', () => {
+ it('removes nothing for root', () => {
+ expect(removeLocale('/')).toEqual('/');
+ expect(removeLocale('')).toEqual('');
+ });
+
+ it('removes nothing for non-locale path', () => {
+ expect(removeLocale('/accounts')).toEqual('/accounts');
+ expect(removeLocale('/blah/de/blah')).toEqual('/blah/de/blah');
+ });
+
+ it('removes valid locales from root', () => {
+ expect(removeLocale('/de')).toEqual('');
+ expect(removeLocale('/en')).toEqual('');
+ expect(removeLocale('/zz')).toEqual('/zz');
+ });
+
+ it('removes valid locales from full path', () => {
+ expect(removeLocale('/de/accounts')).toEqual('/accounts');
+ expect(removeLocale('/ta/accounts/')).toEqual('/accounts/');
+ expect(removeLocale('/zz/accounts')).toEqual('/zz/accounts');
+ });
+});
diff --git a/apps/client/src/utils/coreUtils.ts b/apps/client/src/utils/coreUtils.ts
new file mode 100644
index 000000000..f947cfe41
--- /dev/null
+++ b/apps/client/src/utils/coreUtils.ts
@@ -0,0 +1,95 @@
+// @ts-ignore
+import { nanoid } from 'nanoid';
+import webWorkers from '../../_pluginWebWorkers';
+
+import { DataTypeFolder, ExportTypeFolder } from '@generatedata/plugins';
+// import env from '../../_env';
+
+import { version as rootPackageVersion } from '../../../../package.json'; // TODO see if the bundle includes the full package.json content
+
+import { DataTypeMap, ExportTypeMap, CountryNamesMap } from '@generatedata/plugins';
+
+export const getScriptVersion = (): string => rootPackageVersion;
+
+type WorkerMap = {
+ [workerId: string]: Worker;
+};
+
+const generationWorkers: WorkerMap = {};
+
+export const createGenerationWorker = (customId: string | null = null): string => {
+ const workerId = customId ? customId : nanoid();
+ generationWorkers[workerId] = new Worker(`./workers/${webWorkers.generationWorker}`);
+ return workerId;
+};
+
+export const getGenerationWorker = (id: string): Worker => generationWorkers[id];
+export const destroyGenerationWorker = (id: string): void => {
+ delete generationWorkers[id];
+};
+
+export const getDataTypeWorkerMap = (dataTypes: DataTypeFolder[]): DataTypeMap => {
+ const map: DataTypeMap = {};
+ const dataTypeMap: any = webWorkers.dataTypes;
+ dataTypes.forEach((dataType: DataTypeFolder) => {
+ map[dataType] = dataTypeMap[dataType];
+ });
+ return map;
+};
+
+export const getExportTypeWorkerMap = (exportTypes: ExportTypeMap): ExportTypeMap => {
+ const map: ExportTypeMap = {};
+ const dataTypeMap: any = webWorkers.exportTypes;
+ Object.keys(exportTypes).forEach((exportType) => {
+ map[exportType as ExportTypeFolder] = dataTypeMap[exportType];
+ });
+ return map;
+};
+
+export const getWorkerUtilsUrl = (): string => webWorkers.workerUtils;
+
+const messageIds: any = {};
+
+// wrapper method for the worker calls. This just adds a layer to abort any previous unfinished messages that are
+// sent to the worker. It's up to the worker to handle aborting it however it sees fit, but the important part is
+// that it doesn't post back any data from stale requests
+export const performTask = (workerName: string, worker: any, postMessagePayload: any, onMessage: any): void => {
+ if (!messageIds[workerName]) {
+ messageIds[workerName] = 1;
+ } else {
+ messageIds[workerName]++;
+ }
+
+ worker.postMessage({
+ ...postMessagePayload,
+ _messageId: 1 // TODO
+ });
+
+ worker.onmessage = (data: any): void => {
+ onMessage(data);
+ };
+};
+
+export const easeInOutSine = (t: any, b: any, c: any, d: any): number => {
+ return (-c / 2) * (Math.cos((Math.PI * t) / d) - 1) + b;
+};
+
+let namesPlugins: any = null;
+export const getCountryNamesBundle = (): any => {
+ return new Promise((resolve, reject) => {
+ import(
+ /* webpackChunkName: "countryNames" */
+ /* webpackMode: "lazy" */
+ '@generatedata/plugins/dist/names'
+ )
+ .then((resp: any) => {
+ namesPlugins = resp.default;
+ resolve(resp.default);
+ })
+ .catch((e) => {
+ reject(e);
+ });
+ });
+};
+
+export const getCountryNames = (): CountryNamesMap | null => namesPlugins;
diff --git a/apps/client/src/utils/dataTypes.tsx b/apps/client/src/utils/dataTypes.tsx
new file mode 100644
index 000000000..6e126351c
--- /dev/null
+++ b/apps/client/src/utils/dataTypes.tsx
@@ -0,0 +1,231 @@
+import React from 'react';
+import { getLocale, getStrings } from '@generatedata/utils/lang';
+import { dataTypes, DataTypeFolder, blacklistedDataTypeFolders } from '@generatedata/plugins';
+import { DTBundle, DTCustomProps, DTHelpProps } from '~types/dataTypes';
+import { Store } from '~types/general';
+import C from '@generatedata/config/constants';
+
+type LoadedDataTypes = {
+ [name in DataTypeFolder]: DTBundle;
+};
+
+// this houses all Export Type code loaded async after the application starts
+const loadedDataTypes: Partial = {};
+
+export const dataTypeNames = Object.keys(dataTypes);
+
+// used for the Data Type selection dropdown
+let lastLocale: any;
+let cachedSortedGroupedDataTypes: any;
+export const getSortedGroupedDataTypes = (): any => {
+ const locale = getLocale();
+ const i18n = getStrings();
+
+ if (cachedSortedGroupedDataTypes && lastLocale == locale) {
+ return cachedSortedGroupedDataTypes;
+ }
+
+ lastLocale = locale;
+ cachedSortedGroupedDataTypes = C.DATA_TYPE_GROUPS.map((group: string) => {
+ const options = Object.keys(dataTypes)
+ .filter((dataType) => dataTypes[dataType as DataTypeFolder].fieldGroup === group)
+ .filter((dataType) => blacklistedDataTypeFolders.indexOf(dataType) === -1)
+ .map((dataType) => ({
+ dataType,
+ sortOrder: dataTypes[dataType as DataTypeFolder].fieldGroupOrder
+ }));
+
+ options.sort((a: any, b: any) => {
+ if (a.sortOrder < b.sortOrder) {
+ return -1;
+ } else if (a.sortOrder > b.sortOrder) {
+ return 1;
+ }
+ return 0;
+ });
+
+ const sortedOptions = options.map(({ dataType }: { dataType: string }) => ({
+ value: dataType,
+ label: i18n.dataTypes[dataType].NAME
+ }));
+
+ return {
+ label: i18n.core[group],
+ options: sortedOptions
+ };
+ });
+
+ return cachedSortedGroupedDataTypes;
+};
+
+export const DefaultHelpComponent = ({ i18n }: DTHelpProps) =>
;
+
+const showNothing = (): null => null;
+
+export const getDataType = (dataType: DataTypeFolder | null): any => {
+ // TODO return type is important here. Dense method!
+ if (!dataType || !loadedDataTypes[dataType]) {
+ return {
+ Example: !dataType ? showNothing : null,
+ Options: showNothing,
+ Help: null,
+ isLoaded: false
+ };
+ }
+
+ let Example = null;
+ let Options = null;
+ let Help;
+
+ if (loadedDataTypes[dataType]!.Example) {
+ Example = loadedDataTypes[dataType]!.Example;
+ }
+
+ if (loadedDataTypes[dataType]!.Options) {
+ Options = loadedDataTypes[dataType]!.Options;
+ }
+
+ if (dataType && loadedDataTypes[dataType]!.Help) {
+ Help = loadedDataTypes[dataType]!.Help;
+ } else {
+ Help = DefaultHelpComponent;
+ }
+
+ const customProps = dataType && loadedDataTypes[dataType]!.customProps ? loadedDataTypes[dataType]!.customProps : {};
+ const actionInterceptors = dataType && loadedDataTypes[dataType]!.actionInterceptors ? loadedDataTypes[dataType]!.actionInterceptors : {};
+
+ const { getMetadata, rowStateReducer } = loadedDataTypes[dataType] as DTBundle;
+ return {
+ isLoaded: true,
+ Options,
+ Help,
+ Example,
+ getMetadata,
+ rowStateReducer,
+ customProps,
+ actionInterceptors
+ };
+};
+
+export type ProcessBatches = {
+ [dt in DataTypeFolder]?: number;
+};
+
+export function RecursiveErrorException(remaining: string[]): void {
+ // @ts-ignore-line
+ this.possibleProblematicDataTypes = remaining;
+ // @ts-ignore-line
+ this.name = 'Recursive dependency';
+}
+
+/**
+ * Data Types can register dependencies on other Data Types (the `dependencies` property of their config.ts file) so
+ * that when the row data is generated, the script ensures the dependencies are generated first and available for use.
+ * For example:
+ *
+ * Country <- Region <- City
+ *
+ * Here, the City DT expects the Region DT to be generated first, so it can generate a random city within whatever
+ * random region was generated. Then the same goes for Region with Country.
+ *
+ * This method examines all the dependencies and creates a flat object of dataType => process batch. Any recursive
+ * dependencies throw an error.
+ */
+export const getProcessBatches = (dataTypes: any): ProcessBatches => {
+ let dataTypesToProcess = Object.keys(dataTypes);
+
+ const processBatches: ProcessBatches = {};
+ let previousLength = dataTypesToProcess.length;
+ let currentBatch = 1;
+
+ while (dataTypesToProcess.length > 0) {
+ const resolvedDataTypes: any = [];
+ dataTypesToProcess.forEach((dataType) => {
+ // here, we're dealing with Data Types that have dependencies. Loop through them all and figure out if all
+ // of them have already been assigned to a process batch
+ const allDependenciesPositioned =
+ !dataTypes[dataType].dependencies ||
+ dataTypes[dataType].dependencies.every((dependency: string) => !!processBatches[dependency as DataTypeFolder]);
+ if (allDependenciesPositioned) {
+ resolvedDataTypes.push(dataType);
+ }
+ });
+
+ if (resolvedDataTypes.length) {
+ resolvedDataTypes.forEach((dataType: string) => {
+ processBatches[dataType as DataTypeFolder] = currentBatch;
+ });
+ dataTypesToProcess = dataTypesToProcess.filter((dataType) => resolvedDataTypes.indexOf(dataType) === -1);
+ currentBatch++;
+ }
+
+ // simple check: every single pass of the DataTypes should be able to resolve at least one of them to a
+ // new batch number. If none got resolved we have a problem.
+ if (dataTypesToProcess.length === previousLength) {
+ // @ts-ignore-line
+ throw new RecursiveErrorException(dataTypesToProcess);
+ }
+
+ previousLength = dataTypesToProcess.length;
+ }
+
+ return processBatches;
+};
+
+export const processBatches = getProcessBatches(dataTypes);
+
+// returns a hash of { [data type] => [array of affected data types] }. This means that when the property Data Type
+// is changed, the Data Types in the key array could be affected
+export const getAffectedDataTypes = (dataTypes: any): any => {
+ const affectedDataTypes: any = {};
+
+ Object.keys(dataTypes).forEach((dataType) => {
+ if (!affectedDataTypes[dataType]) {
+ affectedDataTypes[dataType] = [];
+ }
+
+ if (dataTypes[dataType].dependencies) {
+ dataTypes[dataType].dependencies.forEach((dep: DataTypeFolder) => {
+ if (!affectedDataTypes[dep]) {
+ affectedDataTypes[dep] = [];
+ }
+ affectedDataTypes[dep].push(dataType);
+ });
+ }
+ });
+
+ return affectedDataTypes;
+};
+
+export const affectedDataTypes = getAffectedDataTypes(dataTypes);
+
+export const requestDataTypeBundle = (dataType: DataTypeFolder): any => {
+ return new Promise((resolve, reject) => {
+ import(
+ /* webpackChunkName: "DT-[request]" */
+ /* webpackMode: "lazy" */
+ `../plugins/dataTypes/${dataType}/bundle`
+ )
+ .then((definition: any) => {
+ loadedDataTypes[dataType] = definition.default;
+ resolve(definition.default);
+ })
+ .catch((e) => {
+ reject(e);
+ });
+ });
+};
+
+// TODO - this is causing a repaint on every method call, which is causing the most slowness in the UI. But using
+// noValue below doesn't help: when the DT selectors called here return different values, it doesn't get reflected in the UI
+// const noValue: any = {};
+export const getCustomProps = (customProps: DTCustomProps, state: Store): object => {
+ const values: any = {};
+ if (customProps) {
+ Object.keys(customProps).map((propName: string) => {
+ values[propName] = customProps[propName](state);
+ });
+ }
+
+ return values;
+};
diff --git a/apps/client/src/utils/exportTypes.tsx b/apps/client/src/utils/exportTypes.tsx
new file mode 100644
index 000000000..c19807359
--- /dev/null
+++ b/apps/client/src/utils/exportTypes.tsx
@@ -0,0 +1,155 @@
+import { exportTypes, ExportTypeFolder } from '@generatedata/plugins';
+import { ETBrowserBundle, ETDownloadPacketResponse, ETSettings } from '~types/exportTypes';
+import C from '@generatedata/config/constants';
+import { getLocale, getStrings } from '@generatedata/utils/lang';
+import * as React from 'react';
+
+export type LoadedExportTypes = {
+ [name in ExportTypeFolder]?: ETBrowserBundle;
+};
+
+// this houses all Export Type code loaded async after the application starts
+const loadedExportTypes: LoadedExportTypes = {};
+
+let cachedGroupedExportTypes: any;
+let lastLocale: any;
+
+export const getGroupedExportTypes = (): any => {
+ const locale = getLocale();
+ const i18n = getStrings();
+
+ if (cachedGroupedExportTypes && lastLocale == locale) {
+ return cachedGroupedExportTypes;
+ }
+
+ const groupI18nMap: any = {
+ core: 'coreExportType',
+ programmingLanguage: 'programmingLanguages'
+ };
+
+ lastLocale = locale;
+ cachedGroupedExportTypes = C.EXPORT_TYPE_GROUPS.map((group: string) => {
+ const options = Object.keys(exportTypes)
+ .filter((exportType) => exportTypes[exportType as ExportTypeFolder].fieldGroup === group)
+ .map((exportType) => ({
+ value: exportType,
+ label: i18n.exportTypes[exportType] ? i18n.exportTypes[exportType].EXPORT_TYPE_NAME : ''
+ }));
+
+ return {
+ label: i18n.core[groupI18nMap[group]],
+ options: options
+ };
+ });
+
+ return cachedGroupedExportTypes;
+};
+
+export const DefaultSettings = ({ coreI18n }: ETSettings) => {coreI18n.noAdditionalSettings}
;
+
+// TODO error scenarios
+export const loadExportTypeBundle = (exportType: ExportTypeFolder): any => {
+ return new Promise((mainResolve) => {
+ const etBundle = new Promise((resolve, reject) => {
+ import(
+ /* webpackChunkName: "ET-[request]" */
+ /* webpackMode: "lazy" */
+ `../plugins/exportTypes/${exportType}/bundle`
+ )
+ .then((resp: any) => {
+ const def = resp.default;
+ loadedExportTypes[exportType] = {
+ Settings: def.Settings || DefaultSettings,
+ initialState: def.initialState,
+ getExportTypeLabel: def.getExportTypeLabel,
+ getDownloadFileInfo: def.getDownloadFileInfo,
+ getCodeMirrorMode: def.getCodeMirrorMode,
+ validateTitleField: def.validateTitleField,
+ isValid: def.isValid
+ };
+ resolve(def);
+ })
+ .catch((e) => {
+ reject(e);
+ });
+ });
+
+ const codeMirrorModes = exportTypes[exportType].codeMirrorModes.map((mode: string) => {
+ return new Promise((resolve) => {
+ const normalizedMode = mode.replace('/', '_');
+ const id = `mode-${normalizedMode}`;
+
+ // if the codemirror mode was already inserted, don't bother doing it again
+ if (document.getElementById(id)) {
+ // @ts-ignore-line
+ resolve();
+ return;
+ }
+
+ const modeFile = document.createElement('script');
+ modeFile.src = `./codeMirrorModes/${mode}.js`;
+ modeFile.id = id;
+ modeFile.onload = (): void => {
+ // @ts-ignore-line
+ resolve();
+ };
+ document.body.appendChild(modeFile);
+ });
+ });
+
+ Promise.all([...codeMirrorModes, etBundle]).then(() => {
+ mainResolve(etBundle);
+ });
+ });
+};
+
+// *** assumes the callee knows what they're doing & that they've checked the component has been loaded
+export const getExportTypeInitialState = (exportType: ExportTypeFolder): any => loadedExportTypes[exportType]!.initialState;
+
+// *** assumes the callee knows what they're doing & that they've checked the component has been loaded
+export const getExportTypeLabel = (exportType: ExportTypeFolder, settings: any): string | null => {
+ const et = loadedExportTypes[exportType] as ETBrowserBundle;
+ return et.getExportTypeLabel ? et.getExportTypeLabel(settings) : null;
+};
+
+export const getExportTypeSettingsComponent = (exportType: ExportTypeFolder): any => {
+ if (loadedExportTypes[exportType]) {
+ const et = loadedExportTypes[exportType] as ETBrowserBundle;
+ return et.Settings;
+ }
+ return null;
+};
+
+export const getExportTypeTitleValidationFunction = (exportType: ExportTypeFolder): any => {
+ if (loadedExportTypes[exportType]?.validateTitleField) {
+ const et = loadedExportTypes[exportType] as ETBrowserBundle;
+ return et.validateTitleField;
+ }
+ return null;
+};
+
+export const getCodeMirrorMode = (exportType: ExportTypeFolder, exportTypeSettings: any): string => {
+ // @ts-ignore-line
+ return loadedExportTypes[exportType].getCodeMirrorMode(exportTypeSettings);
+};
+
+export const getDownloadFileInfo = (packetId: string, exportType: ExportTypeFolder, exportTypeSettings: any): ETDownloadPacketResponse => {
+ // @ts-ignore-line
+ return loadedExportTypes[exportType].getDownloadFileInfo({
+ packetId,
+ settings: exportTypeSettings
+ });
+};
+
+export const isExportTypeValid = (exportType: ExportTypeFolder, exportTypeSettings: any): boolean => {
+ if (!loadedExportTypes || !loadedExportTypes[exportType]) {
+ return true;
+ }
+
+ const et = loadedExportTypes![exportType]!;
+ if (et.isValid) {
+ return et.isValid(exportTypeSettings);
+ }
+
+ return true;
+};
diff --git a/apps/client/src/utils/generatorUtils.ts b/apps/client/src/utils/generatorUtils.ts
new file mode 100644
index 000000000..05223a1cf
--- /dev/null
+++ b/apps/client/src/utils/generatorUtils.ts
@@ -0,0 +1,472 @@
+import { DataTypeBatchGeneratedPayload, DataTypeWorkerInterface, UnchangedGenerationData, WorkerInterface } from '~types/generator';
+import { DataTypeFolder, CountryNamesMap } from '@generatedata/plugins';
+import { CountryDataType } from '@generatedata/types';
+import { GenerationTemplate } from '../../types/general';
+import { WorkerUtils } from './workerUtils';
+/**
+ * This utility file contains the guts of the data generation code. It farms out work to the various plugins
+ * and orchestrates the many independent tasks to gather together and return the data. It's used by both:
+ * 1. the UI, called via web workers to run performantly in browsers in separate threads off the main UI thread
+ * 2. the node script for server-side data generation
+ */
+
+let isPaused = false;
+let lastMainProcessOptions: MainProcessOptionsBrowser | MainProcessOptionsNode | null = null;
+let currentSpeed: number; // TODO possible range?
+const workerQueue: any = {};
+
+export const generate = ({
+ i18n,
+ workerUtils,
+ countryNames,
+ countryData,
+ dataTypeInterface,
+ columns,
+ template,
+ exportTypeSettings,
+ onComplete,
+ exportTypeInterface,
+ generationSettings
+}: any) => {
+ const { numResults, packetSize, stripWhitespace } = generationSettings;
+
+ const onBatchComplete = ({ completedBatchNum, numGeneratedRows, generatedData }: any): void => {
+ const isLastBatch = numGeneratedRows >= numResults;
+ const displayData = generatedData.map((row: any) => row.map((i: any) => i.display));
+
+ generateExportTypes({
+ numResults,
+ isFirstBatch: completedBatchNum === 1,
+ isLastBatch,
+ currentBatch: completedBatchNum,
+ batchSize: packetSize as number,
+ rows: displayData,
+ columns,
+ stripWhitespace: stripWhitespace as boolean, // TODO we knows it's defined here
+ settings: exportTypeSettings,
+ workerUtils,
+ onComplete: (data: string) => onComplete(data, { isLastBatch, numGeneratedRows }),
+ exportTypeInterface
+ });
+ };
+
+ generateDataTypes({
+ numResults,
+ batchSize: packetSize as number,
+ i18n,
+ template,
+ countryNames,
+ countryData,
+ onBatchComplete,
+ workerUtils,
+ dataTypeInterface
+ });
+};
+
+interface OnBatchComplete {
+ (payload: DataTypeBatchGeneratedPayload): void;
+}
+
+type BaseGenerateDataTypesProps = {
+ numResults: number;
+ batchSize: number;
+ i18n: any;
+ countryNames: CountryNamesMap;
+ dataTypeInterface: DataTypeWorkerInterface;
+ template: GenerationTemplate; // bear in mind this has been grouped by process order. Check type.
+ onBatchComplete: OnBatchComplete;
+ countryData: CountryDataType;
+
+ // used by the UI only. This allows regeneration of a subset of the data and leaves unchanged rows intact
+ unchanged?: UnchangedGenerationData;
+};
+
+type GenerateDataTypesNodeProps = BaseGenerateDataTypesProps & {
+ workerUtils: WorkerUtils;
+};
+
+type GenerateDataTypesBrowserProps = BaseGenerateDataTypesProps & {
+ workerUtilsUrl: string;
+};
+
+export interface GenerateDataTypes {
+ (options: GenerateDataTypesNodeProps | GenerateDataTypesBrowserProps): void;
+}
+
+type MainProcessBaseOptions = {
+ numResults: number;
+ numBatches: number;
+ batchSize: number;
+ batchNum: number;
+ template: GenerationTemplate; // bear in mind this has been grouped by process order. Check type.
+ i18n: any;
+ countryNames: CountryNamesMap;
+ dataTypeInterface: DataTypeWorkerInterface;
+ onBatchComplete: OnBatchComplete;
+ countryData: CountryDataType;
+ unchanged: UnchangedGenerationData;
+};
+
+type MainProcessOptionsNode = MainProcessBaseOptions & {
+ workerUtilsUrl: string;
+};
+
+type MainProcessOptionsBrowser = MainProcessBaseOptions & {
+ workerUtils: WorkerUtils;
+};
+
+type GenerateExportTypesBaseProps = {
+ numResults: number;
+ exportTypeInterface: any; // TODO
+ onComplete: any; // TODO
+ isFirstBatch: boolean;
+ isLastBatch: boolean;
+ currentBatch: number;
+ batchSize: number;
+ rows: any; // TODO
+ columns: any; // TODO
+ settings: any; // TODO
+ stripWhitespace: boolean;
+};
+
+type GenerateExportTypesNodeProps = GenerateExportTypesBaseProps & {
+ workerUtils: WorkerUtils;
+};
+
+type GenerateExportTypesBrowserProps = GenerateExportTypesBaseProps & {
+ workerUtilsUrl: string;
+};
+
+export interface GenerateExportTypes {
+ (settings: GenerateExportTypesNodeProps | GenerateExportTypesBrowserProps): void;
+}
+
+export const generateDataTypes: GenerateDataTypes = (options): void => {
+ const { numResults, batchSize, ...otherOptions } = options;
+ const numBatches = Math.ceil(numResults / batchSize);
+
+ mainProcess({
+ numResults,
+ numBatches,
+ batchSize,
+ batchNum: 1,
+ unchanged: options.unchanged || {},
+ ...otherOptions
+ });
+};
+
+export const generateExportTypes: GenerateExportTypes = (options): void => {
+ const { exportTypeInterface, onComplete, ...other } = options;
+
+ if (exportTypeInterface.context === 'node') {
+ onComplete(exportTypeInterface.send(other));
+ } else {
+ // pass off work to the Export Type worker
+ exportTypeInterface.send(other);
+
+ // listen for the response and post
+ exportTypeInterface.onSuccess((resp: MessageEvent): void => {
+ onComplete(resp.data);
+ });
+ }
+};
+
+const pauseGeneration = (): void => {
+ isPaused = true;
+};
+
+const continueGeneration = (): void => {
+ isPaused = false;
+ mainProcess(lastMainProcessOptions as MainProcessOptionsBrowser | MainProcessOptionsNode);
+};
+
+const setSpeed = (speed: number): void => {
+ currentSpeed = speed;
+};
+
+// our high-level API that this utility file offers
+export default {
+ generate,
+ generateDataTypes,
+ generateExportTypes,
+ pause: pauseGeneration,
+ continue: continueGeneration,
+ setSpeed
+};
+
+// -------------------------------------------------------------------------------------------------------------------
+// Internal methods
+
+const mainProcess = (mainProcessOptions: MainProcessOptionsBrowser | MainProcessOptionsNode): void => {
+ const { numResults, numBatches, batchSize, batchNum, ...other } = mainProcessOptions;
+ const { firstRow, lastRow } = getBatchInfo({ numResults, numBatches, batchSize, batchNum });
+ const lagTime = (100 - currentSpeed) * 50;
+
+ // if the generation has been paused, halt now and keep track of where we were at
+ if (isPaused) {
+ lastMainProcessOptions = mainProcessOptions;
+ return;
+ }
+
+ setTimeout(() => {
+ generateDataTypeBatch({ firstRow, lastRow, numResults, batchNum, ...other }).then(() => {
+ if (batchNum === numBatches) {
+ return;
+ }
+ mainProcess({
+ ...mainProcessOptions,
+ batchNum: mainProcessOptions.batchNum + 1
+ });
+ });
+ }, lagTime);
+};
+
+type GetBatchInfoProps = {
+ numResults: number;
+ numBatches: number;
+ batchSize: number;
+ batchNum: number;
+};
+
+interface GetBatchInfo {
+ (options: GetBatchInfoProps): {
+ firstRow: number;
+ lastRow: number;
+ };
+}
+
+const getBatchInfo: GetBatchInfo = ({ numResults, numBatches, batchSize, batchNum }) => {
+ const firstRow = (batchNum - 1) * batchSize + 1;
+ let lastRow = batchNum * batchSize;
+
+ if (batchNum === numBatches) {
+ lastRow = numResults;
+ }
+
+ return {
+ firstRow,
+ lastRow
+ };
+};
+
+type GenerateDataTypeBaseBatchProps = {
+ numResults: number;
+ firstRow: number;
+ lastRow: number;
+ batchNum: number;
+ i18n: any;
+ template: GenerationTemplate;
+ unchanged: UnchangedGenerationData;
+ countryData: CountryDataType;
+ countryNames: CountryNamesMap;
+ dataTypeInterface: DataTypeWorkerInterface;
+ onBatchComplete: OnBatchComplete;
+};
+
+type GenerateDataTypeNodeBatchProps = GenerateDataTypeBaseBatchProps & {
+ workerUtils: WorkerUtils;
+};
+
+type GenerateDataTypeBrowserBatchProps = GenerateDataTypeBaseBatchProps & {
+ workerUtilsUrl: string;
+};
+
+// this resolve the promise for every batch of data generated
+const generateDataTypeBatch = (options: GenerateDataTypeBrowserBatchProps | GenerateDataTypeNodeBatchProps): Promise =>
+ new Promise((resolve) => {
+ const { batchNum, numResults, firstRow, lastRow, onBatchComplete, ...other } = options;
+ const rowPromises: any = [];
+
+ // rows are independent! The only necessarily synchronous bit is between process batches. So here we just run
+ // them all in a loop
+ for (let rowNum = firstRow; rowNum <= lastRow; rowNum++) {
+ const currRowData: any[] = [];
+ rowPromises.push(
+ processDataTypeBatchGroup({
+ rowNum,
+ currRowData,
+ ...other
+ })
+ );
+ }
+
+ Promise.all(rowPromises).then((generatedData: any) => {
+ onBatchComplete({
+ completedBatchNum: batchNum,
+ numGeneratedRows: lastRow,
+ numResults,
+ generatedData
+ });
+
+ // @ts-ignore-line
+ resolve();
+ });
+ });
+
+type ProcessDataTypeBatchGroupBaseProps = {
+ currRowData: any[]; // TODO
+ template: GenerationTemplate;
+ rowNum: number;
+ i18n: any;
+ unchanged: UnchangedGenerationData;
+ countryData: CountryDataType;
+ countryNames: CountryNamesMap;
+ dataTypeInterface: DataTypeWorkerInterface;
+};
+
+type ProcessDataTypeBatchGroupNode = ProcessDataTypeBatchGroupBaseProps & {
+ workerUtils: WorkerUtils;
+};
+
+type ProcessDataTypeBatchGroupBrowser = ProcessDataTypeBatchGroupBaseProps & {
+ workerUtilsUrl: string;
+};
+
+const processDataTypeBatchGroup = (options: ProcessDataTypeBatchGroupNode | ProcessDataTypeBatchGroupBrowser): any => {
+ const { template, currRowData } = options;
+
+ const processGroups = Object.keys(template);
+
+ return new Promise((resolveAll): any => {
+ let group = Promise.resolve();
+
+ // process each batch sequentially. This ensures the data generated from one processing batch is available to any
+ // dependent children. For example, the Region data type needs the Country data being generated first so it
+ // knows what country regions to generate if a mapping had been selected in the UI
+ processGroups.forEach((processBatchNumberStr, batchIndex) => {
+ const processBatchNum = parseInt(processBatchNumberStr, 10);
+ const currBatch = template[processBatchNum];
+
+ // yup. We're mutating the currRowData param on each loop. We don't care hhahaha!!! Up yours, linter!
+ group = group
+ .then(() => processDataTypeBatch({ cells: currBatch, ...options }))
+ .then((promises) => {
+ // this bit's sneaky. It ensures that the CURRENT batch within the row being generated is fully processed
+ // before starting the next. That way, the generated data from earlier batches is available to later
+ // Data Types for generating their own data
+ return new Promise((resolveBatch) => {
+ Promise.all(promises).then((singleBatchResponses: any) => {
+ for (let i = 0; i < singleBatchResponses.length; i++) {
+ currRowData.push({
+ id: currBatch[i].id,
+ colIndex: currBatch[i].colIndex,
+ dataType: currBatch[i].dataType,
+ data: singleBatchResponses[i]
+ });
+ }
+ resolveBatch();
+
+ if (batchIndex === processGroups.length - 1) {
+ currRowData.sort((a, b) => (a.colIndex < b.colIndex ? -1 : 1));
+ resolveAll(currRowData.map((row) => row.data));
+ }
+ });
+ });
+ });
+ });
+ });
+};
+
+type ProcessDataTypeBatchBaseProps = {
+ cells: any[]; // TODO
+ rowNum: number;
+ i18n: any;
+ currRowData: any;
+ unchanged: UnchangedGenerationData;
+ countryNames: CountryNamesMap;
+ dataTypeInterface: DataTypeWorkerInterface;
+ countryData: CountryDataType;
+};
+
+type ProcessDataTypeBatchNodeProps = ProcessDataTypeBatchBaseProps & {
+ workerUtils: WorkerUtils;
+};
+
+type ProcessDataTypeBatchBrowserProps = ProcessDataTypeBatchBaseProps & {
+ workerUtilsUrl: string;
+};
+
+const processDataTypeBatch = (options: ProcessDataTypeBatchNodeProps | ProcessDataTypeBatchBrowserProps): Promise[] => {
+ const { cells, unchanged, rowNum, i18n, dataTypeInterface, currRowData, ...otherOptions } = options;
+
+ return cells.map((currCell: any) => {
+ const dataType = currCell.dataType;
+
+ return new Promise((resolve, reject) => {
+ if (unchanged[currCell.colIndex]) {
+ resolve(unchanged[currCell.colIndex][rowNum - 1]);
+ } else {
+ queueJob(
+ dataType,
+ dataTypeInterface[dataType],
+ {
+ rowNum: rowNum,
+ i18n: i18n.dataTypes[dataType],
+ countryI18n: i18n.countries,
+ rowState: currCell.rowState,
+ existingRowData: currRowData,
+ ...otherOptions
+ },
+ resolve,
+ reject
+ );
+ }
+ });
+ });
+};
+
+const queueJob = (dataType: DataTypeFolder, workerInterface: WorkerInterface, payload: any, resolve: any, reject: any): void => {
+ if (!workerQueue[dataType]) {
+ workerQueue[dataType] = {
+ processing: false,
+ queue: []
+ };
+ }
+ workerQueue[dataType].queue.push({
+ payload,
+ resolve,
+ reject
+ });
+
+ processQueue(dataType, workerInterface);
+};
+
+const processQueue = (dataType: DataTypeFolder, workerInterface: WorkerInterface): void => {
+ if (workerQueue[dataType].processing) {
+ return;
+ }
+ const queue = workerQueue[dataType].queue;
+
+ if (!queue.length) {
+ return;
+ }
+
+ workerQueue[dataType].processing = true;
+ const { payload, resolve, reject } = queue[0];
+
+ if (workerInterface.context === 'node') {
+ const resp = workerInterface.send(payload);
+ resolve(resp);
+ processNextItem(dataType, workerInterface);
+ } else {
+ workerInterface.send(payload);
+
+ // @ts-ignore
+ workerInterface.onSuccess((resp: any): void => {
+ resolve(resp.data);
+ processNextItem(dataType, workerInterface);
+ });
+
+ // @ts-ignore
+ workerInterface.onError((resp: any): void => {
+ reject(resp);
+ processNextItem(dataType, workerInterface);
+ });
+ }
+};
+
+const processNextItem = (dataType: DataTypeFolder, workerInterface: WorkerInterface): void => {
+ workerQueue[dataType].queue.shift();
+ workerQueue[dataType].processing = false;
+ processQueue(dataType, workerInterface);
+};
diff --git a/apps/client/src/utils/routeUtils.ts b/apps/client/src/utils/routeUtils.ts
new file mode 100644
index 000000000..e2e2685a0
--- /dev/null
+++ b/apps/client/src/utils/routeUtils.ts
@@ -0,0 +1,166 @@
+import Generator from '~core/generator/Generator.container';
+import AccountPage from '~core/account/Account.container';
+import DataSetsPage from '~core/account/dataSets/DataSets.container';
+import LoginPage from '~core/auth/loginPage/LoginPage.container';
+import AccountsPage from '~core/accounts/Accounts.container';
+import { GDHeaderLink, GDLocale, GDRoute } from '~types/general';
+import { AccountType } from '~types/account';
+import { PAGE_CHANGE } from '~store/main/main.actions';
+import { trimChars } from '@generatedata/utils/string';
+import clientConfig from '@generatedata/config/clientConfig';
+import C from '@generatedata/config/constants';
+
+// TODO move this under `extensionUtils`
+let customRoutes: GDRoute[] = [];
+export const registerCustomRoutes = (routes: GDRoute[]): void => {
+ customRoutes = routes;
+};
+
+// called on boot-up. Returns the list of available react-router routes plus the components they should link to. This
+// allows external customization via the `registerCustomRoutes` method above
+export const getRoutes = (): GDRoute[] => {
+ let routes: GDRoute[] = [
+ { path: '/account', component: AccountPage },
+ { path: '/accounts', component: AccountsPage },
+ { path: '/login', component: LoginPage },
+ { path: '/datasets', component: DataSetsPage },
+ { path: '/register', component: DataSetsPage }
+ ];
+
+ // react-router is a bit fussy about the order of routes; the root one has to come last. Since that is configurable
+ // (prod root = a splash intro page; local is the generator) we have to do a little work here to get them in the
+ // right order
+ const nonRootRoutes = customRoutes.filter((route) => route.path !== '/');
+ if (nonRootRoutes.length) {
+ routes.concat(nonRootRoutes);
+ }
+
+ routes.push({ path: process.env.GD_GENERATOR_PATH || '/', component: Generator });
+ const rootRoutes = customRoutes.filter((route) => route.path === '/');
+
+ // lastly, add or overwrite any existing routes (e.g. register) with the custom routes
+ customRoutes.forEach(({ path, component }: GDRoute) => {
+ let found = false;
+ for (let i = 0; i < routes.length; i++) {
+ if (routes[i].path === path) {
+ routes[i].component = component;
+ found = true;
+ }
+ }
+ if (!found) {
+ routes.push({ path, component });
+ }
+ });
+
+ if (rootRoutes.length) {
+ routes = routes.concat(rootRoutes);
+ }
+
+ return routes;
+};
+
+export const getUnlocalizedGeneratorRoute = (): string => clientConfig.appSettings.GD_GENERATOR_PATH || '';
+
+export const getGeneratorPageRoute = (locale: GDLocale): string => {
+ let path = getUnlocalizedGeneratorRoute();
+ if (locale !== 'en') {
+ path = `/${locale}${path}`;
+ }
+
+ return path;
+};
+
+// helper method to return a boolean if the user is on the generator page, regardless of locale
+export const isGeneratorPage = (currentPage: string, locale: GDLocale) => {
+ const generatorRoute = getGeneratorPageRoute(locale);
+
+ // do a comparison that ignores trailing slashes. Issue #795
+ return trimChars(currentPage, '/') === trimChars(generatorRoute, '/');
+};
+
+export interface CustomHeaderLinkGetter {
+ (isLoggedIn: boolean, accountType: AccountType): GDHeaderLink[];
+}
+
+let customHeaderLinkGetter: CustomHeaderLinkGetter;
+
+// allows external code to override the header links
+export const registerCustomHeaderLinksGetter = (getter: CustomHeaderLinkGetter): void => {
+ customHeaderLinkGetter = getter;
+};
+
+export const getHeaderLinks = (isLoggedIn: boolean, accountType: AccountType): GDHeaderLink[] => {
+ const appType = clientConfig.appSettings.GD_APP_TYPE;
+
+ if (customHeaderLinkGetter) {
+ return customHeaderLinkGetter(isLoggedIn, accountType);
+ }
+
+ let links: GDHeaderLink[] = [];
+ switch (appType) {
+ // login - allows anonymous access but without logging in they can't save their data sets or generate more than
+ // GD_MAX_DEMO_MODE_ROWS at a time. Only the admin account can create new accounts
+ case C.APP_TYPES.LOGIN:
+ if (isLoggedIn) {
+ links = ['generator'];
+ if (accountType === 'admin' || accountType === 'superuser') {
+ links.push('accounts');
+ }
+ links = links.concat(['separator', 'userAccount', 'logout']);
+ } else {
+ links = ['generator', 'separator', 'loginDialog'];
+ }
+ break;
+
+ // single - there's only ever a single account and that user is logged in by default
+ case C.APP_TYPES.SINGLE:
+ links = ['generator', 'separator', 'userAccount'];
+ break;
+
+ // open - anyone that has access to the URL can use the application anonymously or create an account
+ case C.APP_TYPES.OPEN:
+ if (isLoggedIn) {
+ links = ['generator', 'separator', 'userAccount', 'logout'];
+ } else {
+ links = ['generator', 'register', 'separator', 'loginDialog', 'logout'];
+ }
+ break;
+
+ // closed - no-one can access the script without logging in first
+ case C.APP_TYPES.CLOSED:
+ if (isLoggedIn) {
+ links = ['generator', 'separator', 'userAccount', 'logout'];
+ } else {
+ links = ['loginPage'];
+ }
+ break;
+ }
+
+ return links;
+};
+
+export const updateBodyClass = (store: any, pathname: string): void => {
+ let pageId = pathname.replace(/\//g, '');
+
+ const rootLocale = clientConfig.appSettings.GD_LOCALES.indexOf(pageId as GDLocale);
+ if (pageId === '' || rootLocale !== -1) {
+ pageId = process.env.GD_GENERATOR_PATH === '/generator' ? 'home' : 'generator';
+ }
+
+ // bit aggressive, but we're not appending any other body classes right now so this is fine. Also, this'll now
+ // include sub-pages since we're removing all the slashes
+ document.body.className = `page-${pageId}`;
+
+ store.dispatch({
+ type: PAGE_CHANGE,
+ payload: {
+ page: location.pathname
+ }
+ });
+};
+
+export const removeLocale = (path: string): string => {
+ const regexStr = `^/?(${clientConfig.appSettings.GD_LOCALES.join('|')})`;
+ const regex = new RegExp(regexStr);
+ return path.replace(regex, '');
+};
diff --git a/apps/client/src/utils/workerUtils.ts b/apps/client/src/utils/workerUtils.ts
new file mode 100644
index 000000000..041d23f9f
--- /dev/null
+++ b/apps/client/src/utils/workerUtils.ts
@@ -0,0 +1,32 @@
+/**
+ * This file contains the subset of utility methods made available to the data generation code. It defines a WorkerUtils
+ * type which is used by both the node + web worker code.
+ *
+ * Web Workers are fussy. To share these utility methods, the core script generates a worker file which is loaded
+ * via importScripts() within any plugin worker and the methods are loaded as a global in the worker scope.
+ */
+import arrayUtils from '@generatedata/utils/array';
+import countryUtils from '@generatedata/utils/country';
+import generalUtils from '@generatedata/utils/general';
+import randomUtils from '@generatedata/utils/random';
+import stringUtils from '@generatedata/utils/string';
+import numberUtils from '@generatedata/utils/number';
+
+export type WorkerUtils = {
+ arrayUtils: typeof arrayUtils;
+ countryUtils: typeof countryUtils;
+ generalUtils: typeof generalUtils;
+ randomUtils: typeof randomUtils;
+ stringUtils: typeof stringUtils;
+ numberUtils: typeof numberUtils;
+};
+
+// all utility methods are exposed to web worker generation files on the global scope under `utils`
+const utils = {
+ arrayUtils: { ...arrayUtils },
+ countryUtils: { ...countryUtils },
+ generalUtils: { ...generalUtils },
+ randomUtils: { ...randomUtils },
+ stringUtils: { ...stringUtils },
+ numberUtils: { ...numberUtils }
+};
diff --git a/apps/client/tests/jestSetup.ts b/apps/client/tests/jestSetup.ts
new file mode 100644
index 000000000..77d3e343d
--- /dev/null
+++ b/apps/client/tests/jestSetup.ts
@@ -0,0 +1,14 @@
+// @ts-ignore-line
+global.MutationObserver = class {
+ constructor() {}
+ disconnect() {}
+ observe() {}
+};
+
+let count = 1;
+jest.mock('nanoid', () => ({
+ nanoid: () => `p${count++}`
+}));
+jest.mock('@react-hook/throttle', () => ({
+ useThrottleCallback: jest.fn()
+}));
diff --git a/apps/client/tests/testHelpers.tsx b/apps/client/tests/testHelpers.tsx
new file mode 100644
index 000000000..f2b5ae9cf
--- /dev/null
+++ b/apps/client/tests/testHelpers.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import { combineReducers } from 'redux';
+import { configureStore } from '@reduxjs/toolkit';
+import { Router } from 'react-router-dom';
+import { ApolloProvider } from '@apollo/client/react';
+import { apolloClient } from '~core/apolloClient';
+import { render } from '@testing-library/react';
+import { createMemoryHistory } from 'history';
+import { getInitialState } from '~store/generator/generator.reducer';
+import { initialState as initialMainState } from '~store/main/main.reducer';
+import { initialState as initialPacketState } from '~store/packets/packets.reducer';
+import { initialState as initialAccountState } from '~store/account/account.reducer';
+import langUtils from '@generatedata/utils/lang';
+import { setLocaleFileLoaded } from '~store/main/main.actions';
+import generatorReducer from '~store/generator/generator.reducer';
+import mainReducer from '~store/main/main.reducer';
+import packetsReducer from '~store/packets/packets.reducer';
+import accountReducer from '~store/account/account.reducer';
+import actionsInterceptor from '~core/actionInterceptor';
+import { GeneratorLayout } from '@generatedata/types';
+import { ETSettings } from '~types/exportTypes';
+
+import i18n from '../src/i18n/en.json';
+import jsonI18n from '@generatedata/plugins/dist/exportTypes/JSON/i18n/en.json';
+
+export const rootReducer = combineReducers({
+ generator: generatorReducer,
+ main: mainReducer,
+ packets: packetsReducer,
+ account: accountReducer
+});
+
+export const renderWithStoreAndRouter = (component: any) => {
+ const initialState = getTestState();
+ const store = configureStore({
+ reducer: rootReducer,
+ preloadedState: initialState
+ // compose(applyMiddleware(thunk, actionsInterceptor
+ });
+ const route = '/';
+ const history = createMemoryHistory({ initialEntries: [route] });
+
+ langUtils.setLocale('en', {
+ core: i18n,
+ dataTypes: {},
+ exportTypes: {
+ JSON: jsonI18n
+ },
+ countries: {}
+ });
+ store.dispatch(setLocaleFileLoaded('en'));
+
+ return {
+ ...render(
+
+
+ {component}
+
+
+ ),
+ history
+ };
+};
+
+export const getTestState = () => ({
+ generator: getInitialState(),
+ main: { ...initialMainState },
+ packets: { ...initialPacketState },
+ account: { ...initialAccountState }
+});
+
+// requires the DT test to supply i18n and rowState (if pertinent)
+export const getBlankDTGeneratorPayload = () => ({
+ rowNum: 1,
+ rowState: null,
+ countryI18n: {},
+ existingRowData: [],
+ countryData: {},
+ workerUtilsUrl: ''
+});
+
+export const defaultETSettings: ETSettings = {
+ onUpdate: () => {},
+ data: null,
+ coreI18n: {},
+ i18n: {},
+ id: 'id',
+ layout: 'horizontal' as GeneratorLayout
+};
diff --git a/apps/client/tsconfig.json b/apps/client/tsconfig.json
new file mode 100644
index 000000000..de86e669b
--- /dev/null
+++ b/apps/client/tsconfig.json
@@ -0,0 +1,29 @@
+{
+ "compilerOptions": {
+ "lib": ["es6", "dom", "esnext", "webworker"],
+ "outDir": "./dist/",
+ "sourceMap": true,
+ "esModuleInterop": true,
+ "noImplicitAny": true,
+ "noImplicitThis": true,
+ "strict": true,
+ "strictNullChecks": true,
+ "moduleResolution": "nodenext",
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "module": "nodenext",
+ "target": "es6",
+ "jsx": "react-jsx",
+ "allowSyntheticDefaultImports": true,
+ "removeComments": false,
+ "baseUrl": "./",
+ "paths": {
+ "~components/*": ["src/components/*"],
+ "~utils/*": ["src/utils/*"],
+ "~store/*": ["src/core/store/*"],
+ "~core/*": ["src/core/*"],
+ "~types/*": ["types/*"]
+ }
+ },
+ "exclude": ["node_modules"]
+}
diff --git a/apps/client/types/account.d.ts b/apps/client/types/account.d.ts
new file mode 100644
index 000000000..5c887a018
--- /dev/null
+++ b/apps/client/types/account.d.ts
@@ -0,0 +1,20 @@
+export type AccountType = 'superuser' | 'admin' | 'user';
+export const enum SelectedAccountTab {
+ dataSets = 'dataSets',
+ dataSetHistory = 'dataSetHistory',
+ yourAccount = 'yourAccount',
+ changePassword = 'changePassword',
+ other = 'other' // mysterious!
+}
+
+export const enum SelectedAccountsTab {
+ accounts = 'accounts',
+ createAccount = 'createAccount',
+ editAccount = 'editAccount'
+}
+
+export const enum AccountStatus {
+ live = 'live',
+ disabled = 'disabled',
+ expired = 'expired'
+}
diff --git a/apps/client/types/dataTypes.d.ts b/apps/client/types/dataTypes.d.ts
new file mode 100644
index 000000000..eb8d19638
--- /dev/null
+++ b/apps/client/types/dataTypes.d.ts
@@ -0,0 +1,127 @@
+// TODO rename to dataTypePlugins.ts
+import { DatabaseTypes } from '@generatedata/types';
+import { AnyObject, GenerationTemplate } from './general.d';
+import { CountryNamesMap, CountryType, DataTypeFolder } from '@generatedata/plugins';
+
+export type DTBundle = {
+ initialState?: any;
+
+ // optional React component to show something in the UI for the "Example" column
+ Example?: any;
+
+ // optional React component. This shows up in the Options column in the UI
+ Options?: any;
+
+ // optional React component
+ Help?: any;
+
+ rowStateReducer?: (state: any) => any;
+ getMetadata?: (data: any) => DTMetadata;
+ customProps?: DTCustomProps;
+ actionInterceptors?: DTActionInterceptors;
+};
+
+export type DTActionInterceptors = {
+ [action: string]: DTActionInterceptor;
+};
+
+export interface DTActionInterceptor {
+ // TODO. generics? rowState and the `any` response here is the state type of the Data Type
+ (rowId: string, rowState: any, actionPayload: any): any | null;
+}
+
+export type DTInterceptorSingleAction = {
+ dataType: DataTypeFolder;
+ interceptor: DTActionInterceptor;
+};
+
+export type Dimensions = {
+ width: number;
+ height: number;
+};
+
+export type DTOptionsMetadata = {
+ useCountryNames?: boolean;
+};
+
+// Data Type props
+export type DTOptionsProps = {
+ data: any;
+ id: string;
+ gridPanelDimensions: Dimensions;
+ onUpdate: (data: AnyObject, metadata?: DTOptionsMetadata) => void;
+ isCountryNamesLoading: boolean;
+ isCountryNamesLoaded: boolean;
+ countryNamesMap: CountryNamesMap | null;
+ coreI18n: any;
+ countryI18n: any;
+ i18n: any;
+ throttle?: boolean; // added for testing so we can disable the default throttle behaviour
+
+ // for custom props. See DTCustomProps
+ [propName: string]: any;
+};
+
+// Data Type props
+export type DTExampleProps = {
+ data: any;
+ id: string;
+ gridPanelDimensions: Dimensions;
+ onUpdate: (data: AnyObject) => void;
+ coreI18n: any;
+ countryI18n: any;
+ i18n: any;
+};
+
+// Data Type props
+export type DTHelpProps = {
+ coreI18n: any;
+ countryI18n: any;
+ i18n: any;
+};
+
+export type DTGenerationData = {
+ rowNum: number;
+ rowState: any;
+ i18n: any;
+ countryI18n: any;
+ existingRowData: DTGenerationExistingRowData[];
+ countryData: {
+ [key in CountryType]?: any;
+ };
+ template: GenerationTemplate;
+};
+
+export type DTWorkerGenerationData = DTGenerationData & {
+ workerUtilsUrl: string; // this is the URL of the workerUtils worker file
+};
+
+interface DTWorkerOnMessage extends MessageEvent {
+ data: DTWorkerGenerationData;
+}
+
+export type DTGenerationExistingRowData = {
+ id: string;
+ colIndex: number; // bit confusing, but this is the index of the ROW in the UI.
+ dataType: DataTypeFolder;
+
+ // this contains the actual generated data from the data type
+ data: DTGenerateResult;
+};
+
+export type DTGenerateResult = {
+ display: string | number | boolean;
+ [key: string]: any;
+};
+
+export type DTCustomProps = {
+ // weird, but setting these to undefined prevents the Data Type from overriding the core prop names accidentally
+ coreI18n?: undefined;
+ countryI18n?: undefined;
+ i18n?: undefined;
+ data?: undefined;
+ id?: undefined;
+ gridPanelDimensions?: undefined;
+ onUpdate?: undefined;
+ [propName: string]: any;
+};
diff --git a/apps/client/types/exportTypes.d.ts b/apps/client/types/exportTypes.d.ts
new file mode 100644
index 000000000..c835e51d3
--- /dev/null
+++ b/apps/client/types/exportTypes.d.ts
@@ -0,0 +1,50 @@
+import { GeneratorLayout } from '@generatedata/types/generator';
+
+export interface ETValidateTitleField {
+ (title: string, i18n: any, settings?: any): string | null;
+}
+
+export type ETBundle = {
+ getCodeMirrorMode: (settings: any) => string; // TODO generics - data is same type as initialState
+ getDownloadFileInfo: (downloadPacket: ETDownloadPacket) => ETDownloadPacketResponse;
+ initialState?: any; // TODO generics
+ Settings?: any;
+ getExportTypeLabel?: (data: any) => string; // TODO generics - data is same type as initialState
+ validateTitleField?: ETValidateTitleField;
+ isValid?: (data: any) => boolean;
+};
+
+export type ETBrowserBundle = Omit;
+
+// oddity, but this is used to let the main application know when the Export Type is in an invalid state. This prevents
+// it from attempting to generate anything until it's resolved
+// TODO rename. Maybe ETRequiredState / ETCoreState?
+export interface ETState {
+ isValid: boolean;
+}
+
+export type ETFieldGroup = 'core' | 'programmingLanguage';
+
+export type ETDefinition = {
+ fieldGroup: ETFieldGroup;
+ codeMirrorModes: string[];
+};
+
+export type ETSettings = {
+ onUpdate: Function; // from container
+ data: any; // from store
+ id: string;
+ layout: GeneratorLayout;
+ i18n: any;
+ coreI18n: any;
+};
+
+export type ETDownloadPacket = {
+ packetId: string;
+ settings: any;
+};
+
+export type ETDownloadPacketResponse = {
+ filename: string;
+ fileType: string;
+};
diff --git a/apps/client/types/general.d.ts b/apps/client/types/general.d.ts
new file mode 100644
index 000000000..da87e9b9d
--- /dev/null
+++ b/apps/client/types/general.d.ts
@@ -0,0 +1,117 @@
+import { GeneratorState } from '~store/generator/generator.reducer';
+import { MainState } from '~store/main/main.reducer';
+import { PacketsState } from '~store/packets/packets.reducer';
+import { AccountState } from '~store/account/account.reducer';
+import { DataTypeFolder } from '@generatedata/plugins';
+import { availableLocales } from '../_env';
+import { DTMetadata } from '~types/dataTypes';
+
+declare global {
+ interface Window {
+ gd: any;
+ CodeMirror: any;
+ __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any;
+ clipboardData: any;
+ google: any;
+ }
+}
+
+export type Store = {
+ generator: GeneratorState;
+ main: MainState;
+ packets: PacketsState;
+ account: AccountState;
+};
+
+export const enum AuthMethod {
+ default = 'default',
+ google = 'google'
+}
+
+export type GDLocale = (typeof availableLocales)[number];
+
+export type GDLocaleMap = {
+ [locale in GDLocale]: string;
+};
+
+// these have special semantic meaning within the script. When the navigation includes them, things like the label
+// and action are preset within the core script
+export type GDPresetHeaderLinks =
+ | 'generator'
+ | 'register'
+ | 'separator'
+ | 'dataSets'
+ | 'userAccount'
+ | 'loginDialog'
+ | 'accounts'
+ | 'loginPage'
+ | 'logout';
+
+// this is for custom header links
+export type GDCustomHeaderLink = {
+ path: string;
+ labelI18nKey: string; // yup. This currently has to be baked into the applications main i18n files.
+};
+
+export type GDHeaderLink = GDPresetHeaderLinks | GDCustomHeaderLink;
+
+export type GDRoute = {
+ path: string;
+ component: any;
+};
+
+export type GDAction = {
+ type: string;
+ payload?: any;
+ triggeredByInterceptor?: boolean;
+};
+
+export type TourProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ maskClassName: string;
+ closeWithMask: boolean;
+ disableInteraction: boolean;
+ accentColor: string;
+ className: string;
+};
+
+export const enum GenerationActivityPanel {
+ small = 'small',
+ large = 'large'
+}
+
+export const enum GeneratorPanel {
+ grid = 'grid',
+ preview = 'preview'
+}
+
+export const enum LoadTimeGraphDuration {
+ all = 'all',
+ s15 = '15seconds',
+ s30 = '30seconds',
+ m1 = '1minute'
+}
+
+export const enum AccountStatusFilter {
+ all = 'all',
+ live = 'live',
+ expired = 'expired',
+ disabled = 'disabled'
+}
+
+export type AnyObject = {
+ [key: string]: any;
+};
+
+export type GenerationTemplateRow = {
+ id: string;
+ title: string;
+ dataType: DataTypeFolder;
+ rowState: any;
+ colIndex: number;
+};
+
+export type GenerationTemplate = {
+ [processOrder: number]: GenerationTemplateRow[];
+};
diff --git a/apps/client/types/generator.d.ts b/apps/client/types/generator.d.ts
new file mode 100644
index 000000000..2ec7655d7
--- /dev/null
+++ b/apps/client/types/generator.d.ts
@@ -0,0 +1,60 @@
+import { DataType, DataTemplateRow, ExportType, ExportTypeConfig } from '@generatedata/plugins';
+import { GDLocale } from '~types/general';
+import { DTGenerateResult, DTGenerationData } from './dataTypes';
+
+export { CountryNameFiles } from '../_namePlugins';
+export { DataType, DataTemplateRow, ExportType, ExportTypeConfig };
+
+/**
+ * Also the main public interface.
+ *
+ * Settings custom to the particular generation action. This is used in combination with DataSetConfig.
+ */
+export type GenerationSettings = {
+ numResults: number;
+ locale?: GDLocale;
+ stripWhitespace?: boolean;
+ target?: 'file' | 'return';
+
+ // the default behaviour for the ppm package is for the generate method to return the generated data. This option
+ // lets users generate a file instead. It's far better for larger data sets
+ filename?: string; // the filename to generate including relative path
+ packetSize?: number; // TODO needed?
+};
+
+/**
+ * This is the public type for what users supply to the generation method.
+ *
+ * It types the structure of an entire data set for being generated: the rows of Data Types with their unique
+ * options and whatever Export Type and settings have been chosen. This is the data structure generated by the
+ * UI after constructing whatever the user wants.
+ */
+export type GDTemplate = {
+ generationSettings: GenerationSettings;
+ dataTemplate: DataTemplateRow[];
+ exportSettings: ExportTypeConfig;
+};
+
+// Bad name but can't think of a better one. This is the interface required for the Data Type and Export Type
+// code for performing a unit of generation. It's a consistent interface used by both web workers and node code
+export type WorkerInterface = {
+ context: 'worker' | 'node';
+ send: (data: DTGenerationData) => void | DTGenerateResult;
+ onSuccess?: (data: any) => void;
+ onError?: (data: any) => void;
+};
+
+export type UnchangedGenerationData = {
+ [colIndex: number]: string; // colIndex => row ID (unique GUID)
+};
+
+export type DataTypeWorkerInterface = {
+ [dataType: string]: WorkerInterface;
+};
+
+export type DataTypeBatchGeneratedPayload = {
+ completedBatchNum: number;
+ numGeneratedRows: number;
+ numResults: number;
+ generatedData: any;
+};
diff --git a/apps/client/webpack.config.js b/apps/client/webpack.config.js
new file mode 100644
index 000000000..b4d84e28b
--- /dev/null
+++ b/apps/client/webpack.config.js
@@ -0,0 +1,128 @@
+const webpack = require('webpack');
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const TerserPlugin = require('terser-webpack-plugin');
+const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
+const Dotenv = require('dotenv-webpack');
+const ESLintPlugin = require('eslint-webpack-plugin');
+// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+
+// const envPath = path.resolve(__dirname, '../../.env');
+// require('dotenv').config({ path: envPath });
+
+module.exports = (env, argv) => {
+ const mode = argv.mode === 'production' ? 'production' : 'development'; // TODO not working with package.json commands
+
+ var config = {
+ mode,
+
+ entry: {
+ app: path.resolve(__dirname, 'src/index.tsx')
+ },
+
+ output: {
+ path: path.join(__dirname, 'dist'),
+ publicPath: '/',
+ chunkFilename: mode === 'development' ? '[name].js' : '[name]-[hash].js',
+ filename: mode === 'development' ? '[name].js' : '[name]-[hash].js'
+ },
+
+ module: {
+ rules: [
+ {
+ test: /\.tsx?$/,
+ use: [{ loader: 'ts-loader' }]
+ // exclude: [
+ // path.resolve(__dirname, '../cli')
+ // ]
+ },
+ {
+ test: /\.js$/,
+ use: [
+ {
+ loader: 'babel-loader'
+ }
+ ],
+ exclude: '/node_modules'
+ },
+ {
+ test: /\.scss$/,
+ use: [
+ 'style-loader',
+ {
+ loader: '@teamsupercell/typings-for-css-modules-loader'
+ },
+ {
+ loader: 'css-loader',
+ options: {
+ modules: {
+ localIdentName: '[name]__[local]--[hash:base64:3]'
+ },
+ url: false
+ }
+ },
+ {
+ loader: 'sass-loader',
+ options: {}
+ }
+ ]
+ },
+ {
+ test: /\.css$/,
+ use: ['style-loader', 'css-loader']
+ }
+ ]
+ },
+
+ plugins: [
+ new ESLintPlugin({
+ extensions: ['js', 'ts']
+ }),
+ new CaseSensitivePathsPlugin(),
+ new HtmlWebpackPlugin({
+ template: path.join(__dirname, 'src/index.html')
+ })
+ // new Dotenv({ path: envPath })
+ ],
+
+ resolve: {
+ extensions: ['.ts', '.tsx', '.js'],
+ alias: {
+ '~components': path.join(__dirname, 'src/components'),
+ '~utils': path.join(__dirname, 'src/utils'),
+ '~store': path.join(__dirname, 'src/core/store'),
+ '~core': path.join(__dirname, 'src/core'),
+ '~types': path.join(__dirname, 'types')
+ }
+ },
+
+ optimization: {
+ splitChunks: {
+ chunks: 'all'
+ }
+ },
+
+ devtool: mode === 'development' ? 'source-map' : false
+ };
+
+ if (mode === 'development') {
+ config.devServer = {
+ historyApiFallback: true,
+ static: path.join(__dirname, 'dist'),
+ // publicPath: 'http://localhost:9000',
+ port: process.env.GD_WEB_SERVER_PORT,
+ open: true
+ };
+
+ // just uncomment this & the include above to auto-generate the bundle analyzer treemap. It'll show up when
+ // running `npm run start`
+ // config.plugins.push(new BundleAnalyzerPlugin());
+ }
+
+ if (mode === 'production') {
+ config.optimization.minimizer = [new TerserPlugin()];
+ config.optimization.minimize = true;
+ }
+
+ return config;
+};
diff --git a/apps/server/.gitignore b/apps/server/.gitignore
new file mode 100644
index 000000000..5208a41c9
--- /dev/null
+++ b/apps/server/.gitignore
@@ -0,0 +1,3 @@
+database/_dbStructure.sql
+.turbo
+node_modules/*
\ No newline at end of file
diff --git a/apps/server/Dockerfile b/apps/server/Dockerfile
new file mode 100644
index 000000000..0e1d4562d
--- /dev/null
+++ b/apps/server/Dockerfile
@@ -0,0 +1,18 @@
+FROM --platform=linux/amd64 node:20 AS alpine
+
+ENV HOME=/home/app/generatedata
+
+WORKDIR $HOME
+COPY package.json pnpm-lock.yaml pnpm-workspace.yaml $HOME/
+COPY ./apps/server $HOME/apps/server/
+COPY ./apps/server/package.json $HOME/apps/server
+RUN npm install -g pnpm@9
+RUN npm install -g grunt-cli
+RUN cd $HOME && pnpm install --fetch-timeout 1000000
+RUN cd $HOME/apps/server && ls -al
+# COPY ./apps/server/node_modules $HOME/apps/server/node_modules
+
+RUN npm uninstall bcrypt
+RUN npm install bcrypt
+
+CMD ["npm", "run", "startNodeDevServer"]
diff --git a/apps/server/app.js b/apps/server/app.js
new file mode 100644
index 000000000..3ed74b80c
--- /dev/null
+++ b/apps/server/app.js
@@ -0,0 +1,52 @@
+const { ApolloServer } = require('apollo-server-express');
+const express = require('express');
+const typeDefs = require('./graphql/schema');
+const resolvers = require('./graphql/resolvers');
+const cookieParser = require('cookie-parser');
+const authUtils = require('./utils/authUtils');
+const path = require('path');
+
+require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
+
+const app = express();
+
+const protocol = process.env.GD_WEB_USE_HTTPS === 'true' ? 'https' : 'http';
+
+// see: https://github.com/expressjs/cors#configuration-options
+const corsOptions = {
+ origin: `${protocol}://${process.env.GD_WEB_DOMAIN}:${process.env.GD_WEB_SERVER_PORT}`,
+ credentials: true
+};
+
+app.use(cookieParser());
+
+const server = new ApolloServer({
+ typeDefs,
+ resolvers,
+ context: ({ req, res }) => {
+ // the token is the "live" token with a short expiry time, passed along with every request in a header while
+ // the user is logged in
+ const token = (req.headers.authorization || '').replace('Bearer ', '');
+
+ // try to retrieve a user with the token
+ const user = authUtils.getUser(token);
+
+ // provides the auth token to all resolvers, plus access to the original request + response objects for
+ // more fine-tune stuff
+ return {
+ res,
+ req,
+ token,
+ user
+ };
+ }
+});
+
+server.applyMiddleware({
+ app,
+ cors: corsOptions
+});
+
+app.listen(process.env.GD_API_SERVER_PORT, () => {
+ console.log('Server started on port ' + process.env.GD_API_SERVER_PORT);
+});
diff --git a/apps/server/database/dbStructure.template.sql b/apps/server/database/dbStructure.template.sql
new file mode 100644
index 000000000..afedc9d9f
--- /dev/null
+++ b/apps/server/database/dbStructure.template.sql
@@ -0,0 +1,73 @@
+# noinspection SqlNoDataSourceInspectionForFile
+
+CREATE DATABASE IF NOT EXISTS generatedata;
+
+CREATE USER 'gduser'@'%' IDENTIFIED BY 'gdpassword';
+GRANT CREATE, ALTER, INDEX, LOCK TABLES, REFERENCES, UPDATE, DELETE, DROP, SELECT, INSERT ON `generatedata`.* TO 'gduser'@'%';
+
+FLUSH PRIVILEGES;
+
+DROP TABLE IF EXISTS `datasets`;
+CREATE TABLE `datasets` (
+ `dataset_id` mediumint(9) NOT NULL,
+ `dataset_name` varchar(255) NOT NULL DEFAULT '',
+ `status` enum('public','private') NOT NULL,
+ `date_created` datetime NOT NULL,
+ `account_id` mediumint(9) NOT NULL,
+ `num_rows_generated` mediumint(9) DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ALTER TABLE `datasets` ADD PRIMARY KEY (`dataset_id`);
+ALTER TABLE `datasets` MODIFY `dataset_id` mediumint(9) NOT NULL AUTO_INCREMENT;
+
+
+DROP TABLE IF EXISTS `dataset_history`;
+CREATE TABLE `dataset_history` (
+ `history_id` mediumint(9) NOT NULL,
+ `dataset_id` mediumint(9) NOT NULL,
+ `date_created` datetime NOT NULL,
+ `content` text NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ALTER TABLE `dataset_history` ADD PRIMARY KEY (`history_id`);
+ALTER TABLE `dataset_history` MODIFY `history_id` mediumint(9) NOT NULL AUTO_INCREMENT;
+
+
+DROP TABLE IF EXISTS `settings`;
+CREATE TABLE `settings` (
+ `setting_name` varchar(100) NOT NULL,
+ `setting_value` text NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ALTER TABLE `settings` ADD UNIQUE KEY `setting_name` (`setting_name`);
+
+
+DROP TABLE IF EXISTS `accounts`;
+CREATE TABLE `accounts` (
+ `account_id` mediumint(8) UNSIGNED NOT NULL,
+ `created_by` mediumint(8) UNSIGNED NULL,
+ `date_created` datetime NOT NULL,
+ `last_updated` datetime NOT NULL,
+ `last_logged_in` datetime DEFAULT NULL,
+ `date_expires` datetime DEFAULT NULL,
+ `refresh_token` varchar(200) DEFAULT NULL,
+ `account_type` enum('superuser', 'admin', 'user') NOT NULL,
+ `account_status` enum('live','disabled', 'expired') NOT NULL,
+ `first_name` varchar(255) DEFAULT NULL,
+ `last_name` varchar(255) DEFAULT NULL,
+ `email` varchar(255) NOT NULL,
+ `password` varchar(100) NOT NULL,
+ `one_time_password` varchar(100) NULL,
+ `country` varchar(255) NOT NULL,
+ `region` varchar(255) NOT NULL,
+ `max_records` mediumint(9) DEFAULT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ALTER TABLE `accounts` ADD PRIMARY KEY (`account_id`);
+ALTER TABLE `accounts` MODIFY `account_id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT;
+
+/* the admin account info is populated from the values in your .env file the first time it boots up */
+INSERT INTO `accounts` (account_id, date_created, last_updated, account_type, account_status, first_name, last_name,
+ email, password, country, region)
+VALUES (1, '%DATE_CREATED%', '%LAST_UPDATED%', 'superuser', 'live', '%FIRST_NAME%', '%LAST_NAME%',
+ '%EMAIL%', '%PASSWORD%', '', '')
diff --git a/apps/server/database/index.js b/apps/server/database/index.js
new file mode 100644
index 000000000..b69cbfea9
--- /dev/null
+++ b/apps/server/database/index.js
@@ -0,0 +1,39 @@
+const Sequelize = require('sequelize');
+const accounts = require('./tables/accounts');
+const dataSets = require('./tables/dataSets');
+const dataSetHistory = require('./tables/dataSetHistory');
+const settings = require('./tables/settings');
+const path = require('path');
+
+require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') });
+
+const sequelize = new Sequelize(process.env.GD_DB_NAME, process.env.GD_MYSQL_ROOT_USER, process.env.GD_MYSQL_ROOT_PASSWORD, {
+ host: 'db',
+ port: process.env.GD_DB_PORT,
+ dialect: 'mysql',
+ define: {
+ freezeTableName: true
+ },
+ pool: {
+ max: 5,
+ min: 0,
+ acquire: 30000,
+ idle: 10000
+ }
+});
+
+const db = {};
+[accounts, dataSets, dataSetHistory, settings].forEach((model) => {
+ const seqModel = model(sequelize, Sequelize);
+ db[seqModel.name] = seqModel;
+});
+
+// define our associations
+db.dataSets.hasMany(db.dataSetHistory, { foreignKey: 'dataSetId' });
+db.dataSetHistory.belongsTo(db.dataSets);
+
+// TODO urgh...
+db.sequelize = sequelize;
+db.Sequelize = Sequelize;
+
+module.exports = db;
diff --git a/apps/server/database/tables/accounts.js b/apps/server/database/tables/accounts.js
new file mode 100644
index 000000000..133dd3365
--- /dev/null
+++ b/apps/server/database/tables/accounts.js
@@ -0,0 +1,84 @@
+module.exports = (sequelize, DataTypes) => {
+ return sequelize.define(
+ 'accounts',
+ {
+ accountId: {
+ type: DataTypes.INTEGER(8).UNSIGNED,
+ allowNull: false,
+ primaryKey: true,
+ autoIncrement: true,
+ field: 'account_id'
+ },
+ createdBy: {
+ type: DataTypes.INTEGER(8).UNSIGNED,
+ field: 'created_by'
+ },
+ dateCreated: {
+ type: DataTypes.DATE,
+ allowNull: false,
+ field: 'date_created'
+ },
+ lastUpdated: {
+ type: DataTypes.DATE,
+ allowNull: false,
+ field: 'last_updated'
+ },
+ lastLoggedIn: {
+ type: DataTypes.DATE,
+ field: 'last_logged_in'
+ },
+ expiryDate: {
+ type: DataTypes.DATE,
+ field: 'date_expires'
+ },
+ refreshToken: {
+ type: DataTypes.STRING(200),
+ field: 'refresh_token'
+ },
+ accountType: {
+ type: DataTypes.ENUM('admin', 'user'),
+ allowNull: false,
+ field: 'account_type'
+ },
+ accountStatus: {
+ type: DataTypes.ENUM('live', 'disabled', 'expired'),
+ allowNull: false,
+ field: 'account_status'
+ },
+ firstName: {
+ type: DataTypes.STRING(255),
+ field: 'first_name'
+ },
+ lastName: {
+ type: DataTypes.STRING(255),
+ field: 'last_name'
+ },
+ email: {
+ type: DataTypes.STRING(255),
+ allowNull: false
+ },
+ password: {
+ type: DataTypes.STRING(255),
+ allowNull: false
+ },
+ oneTimePassword: {
+ type: DataTypes.STRING(255),
+ field: 'one_time_password'
+ },
+ country: {
+ type: DataTypes.STRING(255)
+ },
+ region: {
+ type: DataTypes.STRING(255)
+ },
+ maxRecords: {
+ type: DataTypes.INTEGER(9).UNSIGNED,
+ field: 'max_records'
+ }
+ },
+ {
+ tableName: 'accounts',
+ timestamps: false
+ }
+ );
+};
diff --git a/apps/server/database/tables/dataSetHistory.js b/apps/server/database/tables/dataSetHistory.js
new file mode 100644
index 000000000..c394cd29b
--- /dev/null
+++ b/apps/server/database/tables/dataSetHistory.js
@@ -0,0 +1,28 @@
+module.exports = (sequelize, DataTypes) => {
+ return sequelize.define('dataSetHistory', {
+ historyId: {
+ type: DataTypes.INTEGER(9),
+ allowNull: false,
+ primaryKey: true,
+ autoIncrement: true,
+ field: 'history_id'
+ },
+ dataSetId: {
+ type: DataTypes.INTEGER(9),
+ allowNull: false,
+ field: 'dataset_id'
+ },
+ dateCreated: {
+ type: DataTypes.DATE,
+ allowNull: false,
+ field: 'date_created'
+ },
+ content: {
+ type: DataTypes.TEXT,
+ allowNull: false
+ }
+ }, {
+ tableName: 'dataset_history',
+ timestamps: false
+ });
+};
diff --git a/apps/server/database/tables/dataSets.js b/apps/server/database/tables/dataSets.js
new file mode 100644
index 000000000..ba9ea2f2b
--- /dev/null
+++ b/apps/server/database/tables/dataSets.js
@@ -0,0 +1,38 @@
+module.exports = (sequelize, DataTypes) => {
+ return sequelize.define('dataSets', {
+ dataSetId: {
+ type: DataTypes.INTEGER(9),
+ allowNull: false,
+ primaryKey: true,
+ autoIncrement: true,
+ field: 'dataset_id'
+ },
+ dataSetName: {
+ type: DataTypes.STRING(255),
+ field: 'dataset_name',
+ allowNull: false
+ },
+ status: {
+ type: DataTypes.ENUM('public', 'private'),
+ allowNull: false
+ },
+ dateCreated: {
+ type: DataTypes.DATE,
+ allowNull: false,
+ field: 'date_created'
+ },
+ accountId: {
+ type: DataTypes.INTEGER(9),
+ allowNull: false,
+ field: 'account_id'
+ },
+ numRowsGenerated: {
+ type: DataTypes.INTEGER(9),
+ field: 'num_rows_generated',
+ allowNull: false
+ }
+ }, {
+ tableName: 'datasets',
+ timestamps: false
+ });
+};
diff --git a/apps/server/database/tables/settings.js b/apps/server/database/tables/settings.js
new file mode 100644
index 000000000..2bd1d3ecc
--- /dev/null
+++ b/apps/server/database/tables/settings.js
@@ -0,0 +1,15 @@
+module.exports = (sequelize, DataTypes) => {
+ return sequelize.define('settings', {
+ settingName: {
+ type: DataTypes.STRING,
+ field: 'setting_name'
+ },
+ settingValue: {
+ type: DataTypes.STRING,
+ field: 'setting_value'
+ }
+ }, {
+ tableName: 'settings',
+ timestamps: false
+ });
+};
diff --git a/apps/server/emails/accountExpired.js b/apps/server/emails/accountExpired.js
new file mode 100644
index 000000000..d5efb4168
--- /dev/null
+++ b/apps/server/emails/accountExpired.js
@@ -0,0 +1,58 @@
+const emailUtils = require('../utils/emailUtils');
+const generalUtils = require('../utils/generalUtils');
+const langUtils = require('../utils/langUtils');
+
+/**
+ * Used when a user tries to reset their password but their account has already expired.
+ */
+const passwordResetAccountExpired = ({ firstName, i18n }) => {
+ const emailIntroLineWithName = langUtils.getI18nString(i18n.emailIntroLineWithName, [firstName]);
+ const adminEmail = emailUtils.getAdminEmail();
+ const siteUrl = generalUtils.getSiteUrl();
+
+ const text = `${emailIntroLineWithName}
+
+${i18n.passwordResetAccountExpiredDesc}
+
+${i18n.ifWantToReregister}
+${siteUrl}
+
+${i18n.emailFooterDisclaimer}
+
+- ${i18n.administrator}
+${adminEmail}
+ `;
+
+ const html = `
+
+
+
+
+
+${emailIntroLineWithName}
+${i18n.passwordResetAccountExpiredDesc}
+
+ ${i18n.ifWantToReregister}
+ ${siteUrl}
+
+
+${i18n.emailFooterDisclaimer}
+
+
+ - ${i18n.administrator}
+ ${adminEmail}
+
+
+`;
+
+ return {
+ subject: i18n.passwordResetAccountExpired,
+ text,
+ html
+ };
+};
+
+
+module.exports = {
+ passwordResetAccountExpired
+};
diff --git a/apps/server/emails/index.js b/apps/server/emails/index.js
new file mode 100644
index 000000000..e828af674
--- /dev/null
+++ b/apps/server/emails/index.js
@@ -0,0 +1,7 @@
+const { passwordReset } = require('./passwordReset');
+const { passwordResetAccountExpired } = require('./accountExpired');
+
+module.exports = {
+ passwordReset,
+ passwordResetAccountExpired
+};
diff --git a/apps/server/emails/passwordReset.js b/apps/server/emails/passwordReset.js
new file mode 100644
index 000000000..b7a58e47c
--- /dev/null
+++ b/apps/server/emails/passwordReset.js
@@ -0,0 +1,68 @@
+const emailUtils = require('../utils/emailUtils');
+const generalUtils = require('../utils/generalUtils');
+const langUtils = require('../utils/langUtils');
+
+
+const passwordReset = ({ firstName, email, tempPassword, i18n }) => {
+ const emailIntroLineWithName = langUtils.getI18nString(i18n.emailIntroLineWithName, [firstName]);
+ const adminEmail = emailUtils.getAdminEmail();
+ const siteUrl = generalUtils.getSiteUrl();
+
+ const text = `${emailIntroLineWithName}
+
+${i18n.passwordResetEmailDesc}
+
+${i18n.emailLabel} ${email}
+${i18n.passwordLabel} ${tempPassword}
+${i18n.loginUrlLabel} ${siteUrl}
+
+${i18n.emailFooterDisclaimer}
+
+- ${i18n.administrator}
+${adminEmail}
+ `;
+
+ const html = `
+
+
+
+
+
+${emailIntroLineWithName}
+${i18n.passwordResetEmailDesc}
+
+
+
+ ${i18n.emailLabel}
+ ${email}
+
+
+ ${i18n.passwordLabel}
+ ${tempPassword}
+
+
+ ${i18n.loginUrlLabel}
+ ${siteUrl}
+
+
+
+
+${i18n.emailFooterDisclaimer}
+
+
+ - ${i18n.administrator}
+ ${adminEmail}
+
+
+`;
+
+ return {
+ subject: i18n.passwordReset,
+ text,
+ html
+ };
+};
+
+module.exports = {
+ passwordReset
+};
diff --git a/apps/server/graphql/resolvers.js b/apps/server/graphql/resolvers.js
new file mode 100644
index 000000000..15526da77
--- /dev/null
+++ b/apps/server/graphql/resolvers.js
@@ -0,0 +1,269 @@
+const db = require('../database');
+const authUtils = require('../utils/authUtils');
+const authResolvers = require('./resolvers/auth');
+const accountResolvers = require('./resolvers/account');
+const dataSetResolvers = require('./resolvers/dataSets');
+
+const resolvers = {
+ Query: {
+ accounts: async (root, args, { token, user }) => {
+ authUtils.authenticate(token);
+
+ const userRecord = await db.accounts.findByPk(user.accountId);
+ if (userRecord.dataValues.accountType !== 'superuser') {
+ return {
+ success: false,
+ errorStatus: 'PermissionDenied'
+ };
+ }
+
+ const { limit, offset, sortCol, sortDir, filterStr, status } = args;
+ const { accountId } = user;
+
+ const sortColMap = {
+ lastName: 'last_name',
+ firstName: 'first_name',
+ accountStatus: 'account_status',
+ expiryDate: 'date_expires',
+ lastLoggedIn: 'last_logged_in'
+ };
+
+ let filterClause = '';
+ if (filterStr) {
+ const fields = ['first_name', 'last_name', 'email'];
+ const cleanFilter = filterStr
+ .replace(/[^a-zA-Z'\s\.@]/g, '')
+ .replace(/'/, '\\\'');
+ const clauses = fields.map(
+ (field) => `${field} LIKE '%${cleanFilter}%'`
+ );
+ filterClause = `AND (${clauses.join(' OR ')})`;
+ }
+
+ let statusClause = '';
+ if (status !== 'all') {
+ const cleanStatus = status.replace(/[^a-zA-Z'\s]/g, '');
+ statusClause = `AND account_status = '${cleanStatus}'`;
+ }
+
+ const [results] = await db.sequelize.query(`
+ SELECT *
+ FROM accounts
+ WHERE created_by = ${accountId} ${filterClause} ${statusClause}
+ ORDER BY ${sortColMap[sortCol]} ${sortDir}
+ LIMIT ${limit}
+ OFFSET ${offset}
+ `);
+
+ const [totalCountQuery] = await db.sequelize.query(
+ `
+ SELECT count(*) as c
+ FROM accounts
+ WHERE created_by = ${accountId} ${filterClause} ${statusClause}
+ `,
+ { raw: true, type: db.sequelize.QueryTypes.SELECT }
+ );
+
+ const updatedResults = results.map(async (row) => {
+ const accountId = row.account_id;
+
+ // not great, but info is needed. May be a better idea to denormalize the DB and store this on the
+ // account row
+ const numRowsGenerated =
+ await authResolvers.getAccountNumRowsGenerated(accountId);
+ let accountStatus = row.account_status;
+
+ // update any accounts that have an expiry date set and are now expired
+ if (
+ row.date_expires &&
+ row.account_status !== 'expired' &&
+ row.account_status !== 'disabled'
+ ) {
+ const accountExpired = authUtils.accountExpired(
+ new Date(row.date_expires)
+ );
+ if (accountExpired) {
+ await db.sequelize.query(
+ `
+ UPDATE accounts
+ SET account_status = 'expired'
+ WHERE account_id = ${accountId}
+ `,
+ { raw: true, type: db.sequelize.QueryTypes.UPDATE }
+ );
+
+ accountStatus = 'expired';
+ }
+ }
+
+ return {
+ accountId,
+ dateCreated: row.date_created,
+ lastUpdated: row.last_updated,
+ lastLoggedIn: row.last_logged_in,
+ expiryDate: row.date_expires,
+ accountType: row.account_type,
+ accountStatus,
+ firstName: row.first_name,
+ lastName: row.last_name,
+ email: row.email,
+ country: row.country,
+ region: row.region,
+ numRowsGenerated
+ };
+ });
+
+ return {
+ totalCount: totalCountQuery.c,
+ results: updatedResults
+ };
+ },
+
+ // retrieves any account info
+ account: async (root, args, { user, token }) => {
+ authUtils.authenticate(token);
+
+ const userRecord = await db.accounts.findByPk(user.accountId);
+ if (userRecord.dataValues.accountType !== 'superuser') {
+ return {
+ success: false,
+ errorStatus: 'PermissionDenied'
+ };
+ }
+
+ return db.accounts.findByPk(user.accountId);
+ },
+
+ // returns current user's data sets
+ dataSets: async (root, args, { token, user }) => {
+ const { limit, offset, sortDir, sortCol } = args;
+
+ authUtils.authenticate(token);
+
+ const sortColMap = {
+ dataSetName: 'd.dataset_name',
+ lastUpdated: 'dsh.date_created',
+ numRowsGenerated: 'numRowsGenerated'
+ };
+
+ const { accountId } = user;
+ const [results] = await db.sequelize.query(`
+ SELECT d.dataset_name,
+ d.dataset_id AS dataSetId,
+ d.num_rows_generated as numRowsGenerated,
+ dsh.*,
+ unix_timestamp(d.date_created) AS dateCreatedUnix,
+ unix_timestamp(dsh.date_created) AS historyDateCreatedUnix
+ FROM datasets d
+ LEFT JOIN dataset_history dsh ON dsh.dataset_id = d.dataset_id
+ AND dsh.history_id =
+ (SELECT history_id
+ FROM dataset_history dsh2
+ WHERE dsh2.dataset_id = d.dataset_id
+ ORDER BY history_id DESC
+ LIMIT 1)
+ WHERE account_id = ${accountId}
+ ORDER BY ${sortColMap[sortCol]} ${sortDir}
+ LIMIT ${limit}
+ OFFSET ${offset}
+ `);
+
+ const [totalCountQuery] = await db.sequelize.query(
+ `
+ SELECT count(*) as c
+ FROM datasets
+ WHERE account_id = ${accountId}
+ `,
+ { raw: true, type: db.sequelize.QueryTypes.SELECT }
+ );
+
+ return {
+ totalCount: totalCountQuery.c,
+ results: results.map((row) => ({
+ dataSetId: row.dataSetId,
+ status: row.status,
+ numRowsGenerated: row.numRowsGenerated,
+ historyId: row.history_id,
+ dataSetName: row.dataset_name,
+ content: row.content,
+ dataCreatedUnix: row.dateCreatedUnix,
+ historyDateCreatedUnix: row.historyDateCreatedUnix
+ }))
+ };
+ },
+
+ dataSetHistory: async (root, args, { token, user }) => {
+ const { dataSetId, limit, offset } = args;
+
+ authUtils.authenticate(token);
+
+ // confirm dataSetId belongs to current user
+ const exists = await db.dataSets.findOne({
+ where: {
+ accountId: user.accountId,
+ dataSetId
+ }
+ });
+
+ if (exists === null) {
+ return {
+ success: false,
+ errorStatus: 'PermissionDenied'
+ };
+ }
+
+ const [results] = await db.sequelize.query(`
+ SELECT *
+ FROM dataset_history dh
+ WHERE dataset_id = ${dataSetId}
+ ORDER BY history_id DESC
+ LIMIT ${limit}
+ OFFSET ${offset}
+ `);
+
+ const [totalCountQuery] = await db.sequelize.query(
+ `
+ SELECT count(*) as c
+ FROM dataset_history
+ WHERE dataset_id = ${dataSetId}
+ `,
+ { raw: true, type: db.sequelize.QueryTypes.SELECT }
+ );
+
+ return {
+ totalCount: totalCountQuery.c,
+ results: results.map((row) => ({
+ dataSetId: row.dataset_id,
+ historyId: row.history_id,
+ content: row.content,
+ dateCreated: row.date_created
+ }))
+ };
+ }
+ },
+
+ Mutation: {
+ // authentication resolvers
+ login: authResolvers.login,
+ loginWithGoogle: authResolvers.loginWithGoogle,
+ sendPasswordResetEmail: authResolvers.sendPasswordResetEmail,
+ refreshToken: authResolvers.checkAndUpdateRefreshToken,
+ logout: authResolvers.logout,
+
+ // account-related resolvers
+ updateAccount: accountResolvers.updateAccount,
+ updateCurrentAccount: accountResolvers.updateCurrentAccount,
+ updatePassword: accountResolvers.updatePassword,
+ createUserAccount: accountResolvers.createUserAccount,
+ deleteAccount: accountResolvers.deleteAccount,
+
+ // data-sets
+ saveNewDataSet: dataSetResolvers.saveNewDataSet,
+ renameDataSet: dataSetResolvers.renameDataSet,
+ saveDataSet: dataSetResolvers.saveDataSet,
+ deleteDataSet: dataSetResolvers.deleteDataSet,
+ updateDataSetGenerationCount: dataSetResolvers.updateDataSetGenerationCount
+ }
+};
+
+module.exports = resolvers;
diff --git a/apps/server/graphql/resolvers/account.js b/apps/server/graphql/resolvers/account.js
new file mode 100644
index 000000000..d1600d1b6
--- /dev/null
+++ b/apps/server/graphql/resolvers/account.js
@@ -0,0 +1,182 @@
+const dateFns = require('date-fns');
+const db = require('../../database');
+const authUtils = require('../../utils/authUtils');
+
+const updateCurrentAccount = async (_root, args, { token, user }) => {
+ if (!authUtils.authenticate(token)) {
+ return { success: false };
+ }
+
+ const { accountId } = user;
+ const userRecord = await db.accounts.findByPk(accountId);
+
+ const { firstName, lastName, email, country, region } = args;
+ userRecord.update({
+ firstName,
+ lastName,
+ email,
+ country,
+ region
+ });
+
+ return {
+ success: true
+ };
+};
+
+const updateAccount = async (_root, args, { token, user }) => {
+ if (!authUtils.authenticate(token)) {
+ return { success: false };
+ }
+
+ const { accountId, accountStatus, firstName, lastName, email, country, region, expiryDate } = args;
+ const userRecord = await db.accounts.findByPk(accountId);
+
+ const { accountId: currentAccountId } = user;
+ const currentUser = await db.accounts.findByPk(currentAccountId);
+
+ if (currentUser.dataValues.accountType !== 'superuser') {
+ return {
+ success: false,
+ errorStatus: 'PermissionDenied'
+ };
+ }
+
+ let validatedAccountStatus = accountStatus;
+
+ // "disabled" trumps "expired", otherwise the UI looks weird (you disable something but it never appears that way)
+ if (expiryDate && validatedAccountStatus !== 'disabled') {
+ const now = Number(dateFns.format(new Date(), 't'));
+
+ if (expiryDate < now) {
+ validatedAccountStatus = 'expired';
+ }
+ }
+
+ let expiryDateMs = null;
+ if (expiryDate) {
+ expiryDateMs = parseInt(expiryDate, 10);
+ }
+
+ userRecord.update({
+ accountStatus: validatedAccountStatus,
+ firstName,
+ lastName,
+ email,
+ country,
+ region,
+ expiryDate: expiryDateMs
+ });
+
+ return {
+ success: true
+ };
+};
+
+const updatePassword = async (root, args, { token, user }) => {
+ if (!authUtils.authenticate(token)) {
+ return { success: false };
+ }
+
+ const { accountId } = user;
+ const userRecord = await db.accounts.findByPk(accountId);
+ const { currentPassword, newPassword } = args;
+
+ const isCorrect = await authUtils.isValidPassword(currentPassword, userRecord.dataValues.password);
+
+ if (!isCorrect) {
+ const oneTimePasswordCorrect = await authUtils.isValidPassword(currentPassword, userRecord.dataValues.oneTimePassword);
+
+ if (!oneTimePasswordCorrect) {
+ return {
+ success: false,
+ error: 'PASSWORD_INCORRECT'
+ };
+ }
+ }
+
+ const newPasswordHash = await authUtils.getPasswordHash(newPassword);
+
+ userRecord.update({
+ password: newPasswordHash,
+ oneTimePassword: ''
+ });
+
+ return {
+ success: true
+ };
+};
+
+const createUserAccount = async (root, args, { token, user }) => {
+ if (!authUtils.authenticate(token)) {
+ return { success: false };
+ }
+
+ const userRecord = await db.accounts.findByPk(user.accountId);
+ if (userRecord.dataValues.accountType !== 'superuser') {
+ return {
+ success: false,
+ errorStatus: 'PermissionDenied'
+ };
+ }
+
+ const { accountId } = user;
+ const dateCreated = new Date().getTime();
+ const { firstName, lastName, email, country, region, accountStatus, expiryDate, oneTimePassword } = args;
+
+ let expiryDateMs = null;
+ if (expiryDate) {
+ expiryDateMs = parseInt(expiryDate, 10);
+ }
+
+ const tempPasswordHash = oneTimePassword ? await authUtils.getPasswordHash(oneTimePassword) : undefined;
+
+ await db.accounts.create({
+ createdBy: accountId,
+ accountType: 'user',
+ accountStatus,
+ dateCreated,
+ lastUpdated: dateCreated,
+ expiryDate: expiryDateMs,
+ password: '', // blank password
+ oneTimePassword: tempPasswordHash,
+ firstName,
+ lastName,
+ email,
+ country,
+ region,
+ numRowsGenerated: 0
+ });
+
+ return {
+ success: true
+ };
+};
+
+const deleteAccount = async (_root, { accountId }, { token, user }) => {
+ if (!authUtils.authenticate(token)) {
+ return { success: false };
+ }
+
+ const userRecord = await db.accounts.findByPk(user.accountId);
+ if (userRecord.dataValues.accountType !== 'superuser') {
+ return {
+ success: false,
+ errorStatus: 'PermissionDenied'
+ };
+ }
+
+ db.accounts.destroy({ where: { accountId } });
+
+ return {
+ success: true
+ };
+};
+
+module.exports = {
+ updateCurrentAccount,
+ updateAccount,
+ updatePassword,
+ createUserAccount,
+ deleteAccount
+};
diff --git a/apps/server/graphql/resolvers/auth.js b/apps/server/graphql/resolvers/auth.js
new file mode 100644
index 000000000..fb967b4f3
--- /dev/null
+++ b/apps/server/graphql/resolvers/auth.js
@@ -0,0 +1,346 @@
+const { OAuth2Client } = require('google-auth-library');
+const { nanoid } = require('nanoid');
+const db = require('../../database');
+const authUtils = require('../../utils/authUtils');
+const emailUtils = require('../../utils/emailUtils');
+const langUtils = require('../../utils/langUtils');
+const { passwordReset, passwordResetAccountExpired } = require('../../emails');
+
+const getAccountNumRowsGenerated = async (accountId) => {
+ const results = await db.dataSets.findAll({
+ where: {
+ accountId: accountId
+ },
+ attributes: [
+ [
+ db.sequelize.fn('sum', db.sequelize.col('num_rows_generated')),
+ 'totalRowsGenerated'
+ ]
+ ]
+ });
+
+ return results[0].dataValues.totalRowsGenerated || 0;
+};
+
+const login = async (_root, { email, password }, { res }) => {
+ const user = await db.accounts.findOne({
+ attributes: [
+ 'accountId',
+ 'accountType',
+ 'password',
+ 'oneTimePassword',
+ 'firstName',
+ 'lastName',
+ 'country',
+ 'region',
+ 'dateCreated',
+ 'expiryDate'
+ ],
+ where: {
+ email
+ }
+ });
+
+ if (!user) {
+ return { success: false };
+ }
+
+ const {
+ accountId,
+ password: encodedPassword,
+ oneTimePassword,
+ expiryDate
+ } = user.dataValues;
+
+ const accountExpired = authUtils.accountExpired(expiryDate);
+ if (accountExpired) {
+ await user.update({
+ accountStatus: 'expired'
+ });
+
+ return {
+ success: false,
+ error: 'accountExpired'
+ };
+ }
+
+ const isCorrect = await authUtils.isValidPassword(password, encodedPassword);
+
+ let oneTimePasswordIsCorrect = false;
+ if (oneTimePassword) {
+ oneTimePasswordIsCorrect = await authUtils.isValidPassword(
+ password,
+ oneTimePassword
+ );
+
+ // note we don't reset the password here. It's needed on the request to update the password - there we DON'T
+ // check the previous one (since it's not available). We only ever want to do that if there's a one-time password
+ // still in the DB.
+ }
+
+ if (!isCorrect && !oneTimePasswordIsCorrect) {
+ return { success: false };
+ }
+
+ const { token, tokenExpiry, refreshToken } = await getNewTokens(
+ accountId,
+ email,
+ user,
+ res
+ );
+ const numRowsGenerated = await getAccountNumRowsGenerated(accountId);
+
+ // we ignore async-ness here. No point slowing down the login just to track the last logged in date
+ user.update({
+ lastLoggedIn: new Date().getTime() / 1000
+ });
+
+ return {
+ success: true,
+ token,
+ tokenExpiry,
+ refreshToken,
+ email,
+ wasOneTimeLogin: oneTimePasswordIsCorrect,
+ numRowsGenerated,
+ ...user.dataValues
+ };
+};
+
+const sendPasswordResetEmail = async (root, { email }, { req }) => {
+ const i18n = langUtils.getStrings(req.cookies.lang || 'en');
+
+ const user = await db.accounts.findOne({
+ attributes: ['accountId', 'firstName', 'expiryDate'],
+ where: {
+ email
+ }
+ });
+
+ if (user) {
+ // if the user's account has expired, let 'em know. Sodding ORM adds a degree of confusion but expiryDate is
+ // actually a JS object
+ const { firstName, expiryDate } = user.dataValues;
+
+ const accountExpired = authUtils.accountExpired(expiryDate);
+ if (accountExpired) {
+ const { subject, text, html } = passwordResetAccountExpired({
+ firstName,
+ i18n
+ });
+ await emailUtils.sendEmail(email, subject, text, html);
+ } else {
+ const tempPassword = nanoid(14);
+ const tempPasswordHash = await authUtils.getPasswordHash(tempPassword);
+
+ // set this temporary password in the DB
+ await user.update({
+ oneTimePassword: tempPasswordHash
+ });
+
+ const { subject, text, html } = passwordReset({
+ firstName,
+ email,
+ tempPassword,
+ i18n
+ });
+ await emailUtils.sendEmail(email, subject, text, html);
+ }
+ }
+
+ // regardless of whether it was found or not, just return true. This prevents people being sneaky and finding out
+ // if people have an account or not
+ return { success: true };
+};
+
+const loginWithGoogle = async (_root, { googleToken }, { res }) => {
+ const client = new OAuth2Client(process.env.GD_GOOGLE_AUTH_CLIENT_ID);
+ let email = '';
+ let profileImage = '';
+
+ async function verify() {
+ const ticket = await client.verifyIdToken({
+ idToken: googleToken,
+ audience: process.env.GD_GOOGLE_AUTH_CLIENT_ID
+ });
+ const payload = ticket.getPayload();
+
+ email = payload.email;
+ profileImage = payload.picture;
+ }
+
+ try {
+ await verify();
+ } catch (e) {
+ return {
+ success: false
+ };
+ }
+
+ // here the authentication has passed. Now verify the account exists
+ const user = await db.accounts.findOne({
+ attributes: [
+ 'accountId',
+ 'accountType',
+ 'password',
+ 'firstName',
+ 'lastName',
+ 'country',
+ 'region',
+ 'dateCreated',
+ 'expiryDate',
+ 'country',
+ 'region'
+ ],
+ where: {
+ email
+ }
+ });
+
+ if (!user) {
+ return {
+ success: false,
+ error: 'noUserAccount'
+ };
+ }
+
+ const {
+ accountId,
+ accountType,
+ firstName,
+ lastName,
+ country,
+ region,
+ expiryDate,
+ dateCreated
+ } = user.dataValues;
+ const accountExpired = authUtils.accountExpired(expiryDate);
+
+ if (accountExpired) {
+ return {
+ success: false,
+ error: 'accountExpired'
+ };
+ }
+
+ const { token, tokenExpiry, refreshToken } = await getNewTokens(
+ accountId,
+ email,
+ user,
+ res
+ );
+ const numRowsGenerated = await getAccountNumRowsGenerated(accountId);
+
+ return {
+ success: true,
+ token,
+ tokenExpiry,
+ refreshToken,
+ firstName,
+ lastName,
+ email,
+ country,
+ region,
+ accountType,
+ expiryDate,
+ dateCreated,
+ numRowsGenerated,
+ profileImage
+ };
+};
+
+const checkAndUpdateRefreshToken = async (_root, _args, { req, res }) => {
+ if (!req.cookies.refreshToken) {
+ return { success: false };
+ }
+
+ const oldRefreshToken = req.cookies.refreshToken;
+ const user = await db.accounts.findOne({
+ attributes: [
+ 'accountId',
+ 'accountType',
+ 'firstName',
+ 'email',
+ 'lastName',
+ 'country',
+ 'region',
+ 'dateCreated',
+ 'expiryDate'
+ ],
+ where: {
+ refreshToken: oldRefreshToken
+ }
+ });
+
+ if (!user) {
+ return { success: false };
+ }
+
+ const { accountId, email } = user.dataValues;
+ const {
+ token: newToken,
+ tokenExpiry,
+ refreshToken
+ } = await getNewTokens(accountId, email, user, res);
+
+ const numRowsGenerated = await getAccountNumRowsGenerated(accountId);
+
+ return {
+ success: true,
+ token: newToken,
+ tokenExpiry,
+ refreshToken,
+ numRowsGenerated,
+ ...user.dataValues
+ };
+};
+
+const logout = async (root, args, { req }) => {
+ if (!req.cookies.refreshToken) {
+ return { success: true };
+ }
+
+ const refreshToken = req.cookies.refreshToken;
+ const user = await db.accounts.findOne({
+ attributes: ['accountId'],
+ where: {
+ refreshToken
+ }
+ });
+
+ if (user) {
+ user.update({
+ refreshToken: null,
+ oneTimePassword: null
+ });
+ }
+
+ return { success: true };
+};
+
+const getNewTokens = async (accountId, email, user) => {
+ const token = await authUtils.getJwt({ accountId, email });
+
+ // store the refresh token in a cookie and stash in the db
+ const refreshToken = nanoid();
+ await user.update({ refreshToken: refreshToken });
+
+ // ideally we'd set this cookie here on the server by passing back a Set-Cookie header. But due to the different
+ // ports, that's a no go. Instead, this is passed back to the client which sets it in a cookie. That info is then
+ // sent along with any subsequent requests to the server - including the all-important refreshToken request. This
+ // info enables the front-end code to transparently extend the lifespan of the living token (`token`) just by making
+ // requests
+ const expiryMsFromNow = process.env.GD_JWT_LIFESPAN_MINS * 60 * 1000;
+ const tokenExpiry = new Date().getTime() + expiryMsFromNow;
+
+ return { token, tokenExpiry, refreshToken };
+};
+
+module.exports = {
+ login,
+ loginWithGoogle,
+ sendPasswordResetEmail,
+ checkAndUpdateRefreshToken,
+ logout,
+ getAccountNumRowsGenerated
+};
diff --git a/apps/server/graphql/resolvers/dataSets.js b/apps/server/graphql/resolvers/dataSets.js
new file mode 100644
index 000000000..91a4b3c97
--- /dev/null
+++ b/apps/server/graphql/resolvers/dataSets.js
@@ -0,0 +1,137 @@
+const db = require('../../database');
+const authUtils = require('../../utils/authUtils');
+
+const saveNewDataSet = async (
+ _root,
+ { dataSetName, content },
+ { token, user }
+) => {
+ authUtils.authenticate(token);
+
+ const { accountId } = user;
+
+ const dateCreated = new Date().getTime();
+ const dataSet = await db.dataSets.create({
+ dataSetName,
+ status: 'private',
+ dateCreated,
+ accountId,
+ numRowsGenerated: 0
+ });
+
+ const { dataSetId } = dataSet.dataValues;
+ await db.dataSetHistory.create({
+ dataSetId,
+ dateCreated,
+ content
+ });
+
+ return {
+ success: true,
+ dataSetId,
+ savedDate: dateCreated
+ };
+};
+
+const renameDataSet = async (
+ _root,
+ { dataSetId, dataSetName },
+ { token, user }
+) => {
+ authUtils.authenticate(token);
+
+ const dataSet = await db.dataSets.findByPk(dataSetId);
+ if (dataSet.accountId !== user.accountId) {
+ return {
+ success: false
+ };
+ }
+
+ await dataSet.update({ dataSetName });
+
+ return {
+ success: true
+ };
+};
+
+const saveDataSet = async (_root, { dataSetId, content }, { token, user }) => {
+ authUtils.authenticate(token);
+
+ const dataSet = await db.dataSets.findByPk(dataSetId);
+ if (dataSet.accountId !== user.accountId) {
+ return {
+ success: false
+ };
+ }
+
+ const dateCreated = new Date().getTime();
+ await db.dataSetHistory.create({
+ dataSetId,
+ dateCreated,
+ content
+ });
+
+ return {
+ success: true,
+ dataSetId,
+ savedDate: dateCreated
+ };
+};
+
+const deleteDataSet = async (_root, { dataSetId }, { token, user }) => {
+ if (!authUtils.authenticate(token)) {
+ return { success: false };
+ }
+
+ const dataSet = await db.dataSets.findByPk(dataSetId);
+ if (dataSet.accountId !== user.accountId) {
+ return {
+ success: false
+ };
+ }
+
+ db.dataSets.destroy({ where: { dataSetId } });
+ db.dataSetHistory.destroy({ where: { dataSetId } });
+
+ return {
+ success: true
+ };
+};
+
+const updateDataSetGenerationCount = async (
+ _root,
+ { dataSetId, generatedRows },
+ { token, user }
+) => {
+ if (!authUtils.authenticate(token)) {
+ return { success: false };
+ }
+
+ let addRows = generatedRows;
+ if (/\D/.test(generatedRows)) {
+ addRows = 0;
+ }
+
+ const dataSet = await db.dataSets.findByPk(dataSetId);
+ const { accountId, numRowsGenerated } = dataSet.dataValues;
+
+ if (user.accountId !== accountId) {
+ return { success: false };
+ }
+
+ await dataSet.update({
+ numRowsGenerated: numRowsGenerated + addRows
+ });
+
+ return {
+ success: true
+ };
+};
+
+module.exports = {
+ saveNewDataSet,
+ saveDataSet,
+ renameDataSet,
+ deleteDataSet,
+ updateDataSetGenerationCount
+};
diff --git a/apps/server/graphql/schema.js b/apps/server/graphql/schema.js
new file mode 100644
index 000000000..26e542ec9
--- /dev/null
+++ b/apps/server/graphql/schema.js
@@ -0,0 +1,188 @@
+const { gql } = require('apollo-server-express');
+
+const typeDefs = gql`
+ type Query {
+ accounts(
+ limit: Int
+ offset: Int
+ sortCol: String
+ sortDir: SortDir
+ filterStr: String
+ status: String
+ ): AccountsResults
+ account: Account
+ settings: [Setting]
+ dataSets(
+ limit: Int
+ offset: Int
+ sortCol: String
+ sortDir: SortDir
+ ): DataSetResults
+ dataSet(id: ID!): DataSet
+ dataSetHistory(
+ dataSetId: ID!
+ limit: Int
+ offset: Int
+ ): DataSetHistoryResults
+ }
+ type Mutation {
+ login(email: String!, password: String!): AuthResponse
+ loginWithGoogle(googleToken: String!): AuthResponse
+ sendPasswordResetEmail(email: String!): GeneralResponse
+ refreshToken: AuthResponse
+ logout: GeneralResponse
+ updateAccount(
+ accountId: ID!
+ accountStatus: AccountStatus
+ firstName: String!
+ lastName: String!
+ email: String!
+ country: String!
+ region: String
+ expiryDate: String
+ ): GeneralResponse
+ updateCurrentAccount(
+ firstName: String!
+ lastName: String!
+ email: String!
+ country: String!
+ region: String
+ ): GeneralResponse
+ updatePassword(
+ currentPassword: String!
+ newPassword: String!
+ ): GeneralResponse
+ createUserAccount(
+ firstName: String!
+ lastName: String!
+ email: String!
+ country: String
+ region: String
+ accountStatus: AccountStatus
+ expiryDate: String
+ oneTimePassword: String
+ ): GeneralResponse
+ deleteAccount(accountId: ID!): GeneralResponse
+ saveNewDataSet(dataSetName: String!, content: String!): SavedDataSetResponse
+ saveDataSet(dataSetId: ID!, content: String!): SavedDataSetResponse
+ renameDataSet(dataSetId: ID!, dataSetName: String): GeneralResponse
+ deleteDataSet(dataSetId: ID!): GeneralResponse
+ updateDataSetGenerationCount(
+ dataSetId: ID
+ generatedRows: Int
+ ): GeneralResponse
+ }
+ type Account {
+ accountId: ID!
+ createdBy: ID
+ expiryDate: String
+ accountType: AccountType
+ accountStatus: AccountStatus
+ dateCreated: String
+ lastLoggedIn: String
+ firstName: String
+ lastName: String
+ email: String
+ country: String
+ region: String
+ numRowsGenerated: Int
+ profileImage: String
+ errorStatus: Error
+ }
+ type Setting {
+ settingName: String
+ settingValue: String
+ }
+ type DataSet {
+ dataSetId: ID
+ dataSetName: String
+ status: String
+ dateCreated: String
+ lastUpdated: String
+ accountId: ID
+ numRowsGenerated: Int
+ history: [DataSetHistory]
+ }
+ type DataSetListItem {
+ dataSetId: ID
+ status: String
+ dateCreated: String
+ numRowsGenerated: Int
+ historyId: ID
+ dataSetName: String
+ content: String
+ dataCreatedUnix: Int
+ historyDateCreatedUnix: Int
+ }
+ type DataSetHistoryResults {
+ results: [DataSetHistory]
+ totalCount: Int
+ }
+ type DataSetHistory {
+ historyId: ID!
+ dataSetId: ID!
+ dateCreated: String
+ content: String
+ }
+ type DataSetResults {
+ results: [DataSetListItem]
+ totalCount: Int
+ }
+ type AccountsResults {
+ results: [Account]
+ totalCount: Int
+ errorStatus: Error
+ }
+ type AuthResponse {
+ success: Boolean
+ token: String
+ tokenExpiry: String
+ refreshToken: String
+ error: String
+ accountId: ID
+ expiryDate: String
+ accountType: AccountType
+ accountStatus: AccountStatus
+ dateCreated: String
+ firstName: String
+ lastName: String
+ email: String
+ country: String
+ region: String
+ numRowsGenerated: Int
+ profileImage: String
+ wasOneTimeLogin: Boolean
+ }
+ type GeneralResponse {
+ success: Boolean
+ error: String
+ }
+ type Error {
+ errorStatus: ErrorStatus
+ }
+ type SavedDataSetResponse {
+ success: Boolean
+ error: String
+ dataSetId: ID
+ savedDate: String
+ }
+ enum AccountType {
+ superuser
+ admin
+ user
+ }
+ enum AccountStatus {
+ live
+ disabled
+ expired
+ }
+ enum SortDir {
+ ASC
+ DESC
+ }
+ enum ErrorStatus {
+ permissionDenied
+ }
+`;
+
+module.exports = typeDefs;
diff --git a/apps/server/nginx/nginx.conf b/apps/server/nginx/nginx.conf
new file mode 100644
index 000000000..6086dc45f
--- /dev/null
+++ b/apps/server/nginx/nginx.conf
@@ -0,0 +1,17 @@
+server {
+ listen 9000;
+ root /usr/share/nginx/html;
+ gzip on;
+ gzip_types text/plain text/javascript application/x-javascript text/css application/javascript application/json image/svg+xml;
+ gzip_comp_level 9;
+ etag on;
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+ location /index.html {
+ add_header Cache-Control no-cache;
+ }
+ location /graphql {
+ proxy_pass http://server:3001;
+ }
+}
diff --git a/apps/server/package.json b/apps/server/package.json
new file mode 100644
index 000000000..d791b85d5
--- /dev/null
+++ b/apps/server/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@generatedata/server",
+ "description": "Server-side code for generatedata app",
+ "private": true,
+ "scripts": {
+ "build": "node scripts/build.js",
+ "start": "nodemon ./app.js"
+ },
+ "dependencies": {
+ "@types/nanoid": "3.0.0",
+ "apollo-server-express": "^2.19.0",
+ "bcrypt": "^5.0.0",
+ "codemirror": "^5.58.2",
+ "cookie-parser": "^1.4.5",
+ "date-fns": "^2.17.0",
+ "express": "^4.20.2",
+ "google-auth-library": "^6.1.3",
+ "jsonwebtoken": "^9.0.2",
+ "mysql2": "^2.2.5",
+ "nanoid": "^3.3.4",
+ "nodemailer": "^6.9.9",
+ "nodemon": "^2.0.6",
+ "sequelize": "^6.29.0"
+ },
+ "devDependencies": {
+ "@generatedata/config": "workspace:*",
+ "@generatedata/utils": "workspace:*",
+ "dotenv": "^8.2.0"
+ }
+}
diff --git a/apps/server/scripts/build.js b/apps/server/scripts/build.js
new file mode 100644
index 000000000..d6eb5278f
--- /dev/null
+++ b/apps/server/scripts/build.js
@@ -0,0 +1,35 @@
+import dateFns from 'date-fns';
+import serverConfig from '@generatedata/config/serverConfig';
+import authUtils from '@generatedata/utils/auth';
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+const createDatabaseInitFile = async () => {
+ const now = Math.round(new Date().getTime() / 1000);
+ const newPasswordHash = await authUtils.getPasswordHash(serverConfig.defaultAdminAccount.GD_DEFAULT_ADMIN_PASSWORD);
+
+ const mysqlDateTime = dateFns.format(dateFns.fromUnixTime(now), 'yyyy-LL-dd HH:mm:ss');
+
+ const placeholders = {
+ '%FIRST_NAME%': serverConfig.defaultAdminAccount.GD_DEFAULT_ADMIN_FIRST_NAME,
+ '%LAST_NAME%': serverConfig.defaultAdminAccount.GD_DEFAULT_ADMIN_LAST_NAME,
+ '%EMAIL%': serverConfig.defaultAdminAccount.GD_DEFAULT_ADMIN_EMAIL,
+ '%PASSWORD%': newPasswordHash,
+ '%DATE_CREATED%': mysqlDateTime,
+ '%LAST_UPDATED%': mysqlDateTime
+ };
+
+ const dbStructureTemplate = fs.readFileSync(path.join(__dirname, '../database/dbStructure.template.sql'), 'utf8');
+ let newFile = dbStructureTemplate;
+ Object.keys(placeholders).forEach((placeholder) => {
+ newFile = newFile.replace(placeholder, placeholders[placeholder]);
+ });
+
+ fs.writeFileSync(path.join(__dirname, '../database/_dbStructure.sql'), newFile);
+};
+
+createDatabaseInitFile();
diff --git a/apps/server/utils/authUtils.js b/apps/server/utils/authUtils.js
new file mode 100644
index 000000000..320fbae31
--- /dev/null
+++ b/apps/server/utils/authUtils.js
@@ -0,0 +1,61 @@
+const jwt = require('jsonwebtoken');
+const bcrypt = require('bcrypt');
+
+const isValidPassword = async (plainTextPassword, hash) => await bcrypt.compare(plainTextPassword, hash);
+
+const getJwt = (payload) =>
+ jwt.sign(payload, process.env.GD_JWT_SECRET, {
+ expiresIn: `${process.env.GD_JWT_LIFESPAN_MINS}m`
+ });
+
+const authenticate = async (token) => {
+ if (token) {
+ try {
+ await jwt.verify(token, process.env.GD_JWT_SECRET);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ return false;
+};
+
+const decodeToken = (token) => {
+ const decodedToken = jwt.decode(token, { complete: true });
+
+ if (!decodedToken) {
+ throw new Error();
+ }
+
+ return decodedToken;
+};
+
+const getUser = (token) => {
+ if (!token) {
+ return {};
+ }
+ const decodedToken = decodeToken(token);
+ return decodedToken.payload;
+};
+
+const accountExpired = (expiryDate) => {
+ if (expiryDate === null) {
+ return false;
+ }
+
+ const now = new Date();
+ const nowMs = now.getTime();
+ const expiryDateMs = expiryDate ? expiryDate.getTime() : 0;
+
+ return expiryDateMs < nowMs;
+};
+
+module.exports = {
+ getPasswordHash,
+ isValidPassword,
+ getJwt,
+ authenticate,
+ getUser,
+ accountExpired
+};
diff --git a/apps/server/utils/dateUtils.js b/apps/server/utils/dateUtils.js
new file mode 100644
index 000000000..49eaf0679
--- /dev/null
+++ b/apps/server/utils/dateUtils.js
@@ -0,0 +1,10 @@
+const { format } = require('date-fns');
+
+const getMysqlDateTime = (date = new Date()) =>
+ format(date, 'YYYY-MM-DD HH-mm-ss');
+
+// const getUnixTimeFromMySQLDate
+
+module.exports = {
+ getMysqlDateTime
+};
diff --git a/apps/server/utils/emailUtils.js b/apps/server/utils/emailUtils.js
new file mode 100644
index 000000000..cd4173a41
--- /dev/null
+++ b/apps/server/utils/emailUtils.js
@@ -0,0 +1,41 @@
+const nodemailer = require('nodemailer');
+
+const sendEmail = async (recipientEmail, subject, textContent, htmlContent) => {
+ const transporter = nodemailer.createTransport({
+ host: 'smtp.gmail.com',
+ port: 465,
+ secure: true,
+ auth: {
+ type: 'OAuth2',
+ user: getAdminEmail(),
+ serviceClient: process.env.GD_EMAIL_OAUTH_SERVICE_CLIENT_ID,
+ privateKey: process.env.GD_EMAIL_OAUTH_PRIVATE_KEY.replace(/\\n/g, '\n')
+ }
+ });
+
+ try {
+ await transporter.verify();
+ await transporter.sendMail({
+ from: getEmailSender(),
+ to: recipientEmail,
+ subject,
+ text: textContent,
+ html: htmlContent
+ });
+ } catch (err) {
+ console.error(err);
+ }
+};
+
+const getAdminEmail = () => {
+ return process.env.GD_DEFAULT_ADMIN_EMAIL;
+};
+
+const getEmailSender = () => {
+ return `${process.env.GD_DEFAULT_ADMIN_EMAIL_SENDER_NAME} <${process.env.GD_DEFAULT_ADMIN_EMAIL}>`;
+};
+
+module.exports = {
+ sendEmail,
+ getAdminEmail
+};
diff --git a/apps/server/utils/generalUtils.js b/apps/server/utils/generalUtils.js
new file mode 100644
index 000000000..b55b4e79e
--- /dev/null
+++ b/apps/server/utils/generalUtils.js
@@ -0,0 +1,21 @@
+const getSiteUrl = () => {
+ let protocol = 'http';
+ const domain = process.env.GD_WEB_DOMAIN;
+
+ if (process.env.GD_WEB_USE_HTTPS === 'true') {
+ protocol = 'https';
+ }
+
+ // @ts-ignore-line
+ let cleanPort = '';
+ let port = process.env.GD_WEB_SERVER_PORT.trim();
+ if (port && port !== '80' && port !== 80 && port !== '443' && port !== 443) {
+ cleanPort = `:${port}`;
+ }
+
+ return `${protocol}://${domain}${cleanPort}`;
+};
+
+module.exports = {
+ getSiteUrl
+};
diff --git a/apps/server/utils/langUtils.js b/apps/server/utils/langUtils.js
new file mode 100644
index 000000000..457bfc299
--- /dev/null
+++ b/apps/server/utils/langUtils.js
@@ -0,0 +1,48 @@
+const ar = require('../../client/src/i18n/ar.json');
+const de = require('../../client/src/i18n/de.json');
+const en = require('../../client/src/i18n/en.json');
+const es = require('../../client/src/i18n/es.json');
+const fr = require('../../client/src/i18n/fr.json');
+const hi = require('../../client/src/i18n/hi.json');
+const ja = require('../../client/src/i18n/ja.json');
+const nl = require('../../client/src/i18n/nl.json');
+const pt = require('../../client/src/i18n/pt.json');
+const ta = require('../../client/src/i18n/ta.json');
+const zh = require('../../client/src/i18n/zh.json');
+
+const getStrings = (locale) => {
+ const map = { ar, de, en, es, fr, hi, ja, nl, pt, ta, zh };
+ return map[locale];
+};
+
+// this and the following method are currently duplicated here. Need to convert the BE code to use TS
+const getI18n = (i18nString, placeholders) => {
+ const parts = i18nString.split(/(%\d+)/);
+ const parsed = [];
+
+ parts.forEach((part) => {
+ if (/%\d+/.test(part)) {
+ const index = parseInt(part.replace('%', ''), 10) - 1;
+
+ if (index < placeholders.length) {
+ parsed.push(placeholders[index]);
+
+ // if there's no placeholder for this, just add the original value which looked like a placeholder
+ } else {
+ parsed.push(part);
+ }
+ } else {
+ parsed.push(part);
+ }
+ });
+
+ return parsed;
+};
+
+const getI18nString = (i18nString, placeholders) =>
+ getI18n(i18nString, placeholders).join('');
+
+module.exports = {
+ getStrings,
+ getI18nString
+};
diff --git a/apps/server/utils/stringUtils.js b/apps/server/utils/stringUtils.js
new file mode 100644
index 000000000..add3b240e
--- /dev/null
+++ b/apps/server/utils/stringUtils.js
@@ -0,0 +1,10 @@
+// JS trim() doesn't trim newlines. This does.
+const trim = (str) => str.replace(/^\s+|\s+$/g, '');
+
+const toSentenceCase = (str) =>
+ str.toLowerCase().replace(/\.\s*([a-z])|^[a-z]/gm, (s) => s.toUpperCase());
+
+module.exports = {
+ trim,
+ toSentenceCase
+};
diff --git a/cache/index.html b/cache/index.html
deleted file mode 100644
index 30e84fdd6..000000000
--- a/cache/index.html
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/composer.json b/composer.json
deleted file mode 100644
index ef69d4c2c..000000000
--- a/composer.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "name": "benkeen/generatedata",
- "type": "library",
- "description": "Free, GNU-licensed, random custom data generator for testing software",
- "keywords": ["Random Data", "Test Data", "Sample Data", "data generator", "testing"],
- "homepage": "http://github.com/benkeen/generatedata",
- "license": "GPL",
- "authors": [
- {
- "name": "Ben Keen",
- "email": "ben.keen@gmail.com",
- "homepage": "https://github.com/benkeen/",
- "role": "Developer"
- }
- ],
- "require": {
- "php": ">=5.3.0"
- }
-}
\ No newline at end of file
diff --git a/data/db/nodelete.txt b/data/db/nodelete.txt
new file mode 100644
index 000000000..9e1362993
--- /dev/null
+++ b/data/db/nodelete.txt
@@ -0,0 +1 @@
+This folder will store your database. This file is here to prevent OSs removing the empty folder.
diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml
new file mode 100644
index 000000000..648ca4376
--- /dev/null
+++ b/docker-compose-prod.yml
@@ -0,0 +1,61 @@
+version: "3"
+
+networks:
+ gd-network:
+ driver: bridge
+
+services:
+ server:
+ container_name: server
+ build:
+ context: ./
+ dockerfile: apps/server/Dockerfile
+ depends_on:
+ - db
+ networks:
+ - gd-network
+ expose:
+ - "${GD_API_SERVER_PORT}"
+ ports:
+ - "${GD_API_SERVER_PORT}:${GD_API_SERVER_PORT}"
+ restart: on-failure
+ volumes:
+ - .:/home/app/generatedata
+ - /home/app/generatedata/apps/client/node_modules
+ - /home/app/generatedata/apps/server/node_modules
+ environment:
+ - NODE_ENV=production
+
+ db:
+ container_name: db
+ image: mariadb:10.5.8
+ command: --default-authentication-plugin=mysql_native_password
+ restart: always
+ expose:
+ - "${GD_DB_PORT}"
+ ports:
+ - "${GD_DB_PORT}:${GD_DB_PORT}"
+ networks:
+ - gd-network
+ environment:
+ MYSQL_ROOT_PASSWORD: ${GD_MYSQL_ROOT_PASSWORD}
+ MYSQL_DATABASE: ${GD_DB_NAME}
+ MYSQL_ALLOW_EMPTY_PASSWORD: ok
+ volumes:
+ - ./apps/server/database/_dbStructure.sql:/docker-entrypoint-initdb.d/setup.sql
+ - ./data/db:/var/lib/mysql
+
+ # contains the webapp + starts nginx
+ webapp:
+ container_name: webapp
+ image: nginx:latest
+ ports:
+ - "${GD_WEB_SERVER_PORT}:80"
+ volumes:
+ - ./client/dist:/usr/share/nginx/html
+ depends_on:
+ - server
+ links:
+ - server
+ networks:
+ - gd-network
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 000000000..e6518492d
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,44 @@
+version: "3"
+
+networks:
+ gd-network:
+ driver: bridge
+
+services:
+ server:
+ container_name: server
+ build:
+ context: ./
+ dockerfile: ./apps/server/Dockerfile
+ depends_on:
+ - db
+ networks:
+ - gd-network
+ expose:
+ - "${GD_API_SERVER_PORT}"
+ ports:
+ - "${GD_API_SERVER_PORT}:${GD_API_SERVER_PORT}"
+ restart: on-failure
+ volumes:
+ - .:/home/app/generatedata
+ # - /home/app/generatedata/apps/client/node_modules
+ # - /home/app/generatedata/apps/server/node_modules
+
+ db:
+ container_name: db
+ image: mariadb:10.5.8
+ command: --default-authentication-plugin=mysql_native_password
+ restart: always
+ expose:
+ - "${GD_DB_PORT}"
+ ports:
+ - "${GD_DB_PORT}:${GD_DB_PORT}"
+ networks:
+ - gd-network
+ environment:
+ MYSQL_ROOT_PASSWORD: ${GD_MYSQL_ROOT_PASSWORD}
+ MYSQL_DATABASE: ${GD_DB_NAME}
+ MYSQL_ALLOW_EMPTY_PASSWORD: ok
+ volumes:
+ - ./apps/server/database/_dbStructure.sql:/docker-entrypoint-initdb.d/setup.sql
+ - ./data/db:/var/lib/mysql
diff --git a/generate.php b/generate.php
deleted file mode 100644
index 1de117ad7..000000000
--- a/generate.php
+++ /dev/null
@@ -1,47 +0,0 @@
-generate();
-
-if ($gen->getExportTarget() == "promptDownload") {
- header("Cache-Control: private, no-cache, must-revalidate");
-
- // check if user opted to zip the generated data
- if ($gen->isPromptDownloadZipped()) {
- $randNum = mt_rand(0, 100000000);
- $fileName = $randNum . "_" . $response["promptDownloadFilename"];
- $zipPath = $filePath . ".zip";
-
- if (file_put_contents($fileName, $response["content"])) {
-
- // now that we've written the file, zip it up
- $zip = new ZipArchive();
- $zipFile = $zip->open($zipPath, ZipArchive::CREATE);
- if ($zipFile && $zip->addFile($fileName, $response["promptDownloadFilename"])) {
-
- // we've got our zip file now we may set the response header
- $zip->close();
- header("Content-type: application/zip");
- header("Content-Disposition: attachment; filename=" . $response["promptDownloadFilename"] . ".zip");
- readfile($zipPath);
- unlink($zipPath);
- unlink($fileName);
- exit;
- }
- }
-
- // no compression, send the original data back
- } else {
- header("Content-Type: {$response["contentTypeHeader"]}");
-
- if (isset($response["promptDownloadFilename"]) && !empty($response["promptDownloadFilename"])) {
- header("Content-Disposition: attachment; filename={$response["promptDownloadFilename"]}");
- }
- echo $response["content"];
- }
-} else {
- echo $response["content"];
-}
diff --git a/gruntfile.js b/gruntfile.js
deleted file mode 100644
index 1aa74be90..000000000
--- a/gruntfile.js
+++ /dev/null
@@ -1,128 +0,0 @@
-module.exports = function(grunt) {
- "use strict";
-
-
- var config = {
- pkg: grunt.file.readJSON('package.json'),
-
- uglify: {
- coreJS: {
- files: {
- 'cache/core.js': [
- 'resources/libs/codemirror/lib/codemirror.js',
- 'resources/scripts/libs/jquery.min.js',
- 'resources/scripts/libs/jquery-ui.min.js',
- 'resources/scripts/libs/jquery.json-2.2.min.js',
- 'resources/scripts/libs/chosen.jquery.min.js',
- 'resources/scripts/libs/spinners.js',
-
- // pity, but now everyone gets them `client.config.ts`
+- `server.config.example.ts` ---> `server.config.ts`
+
+These contain the server- and the client-side settings for your installation. They are separate because the `client.config.ts` content **will be bundled in the front-end code** so aren't secure. The `server.config.ts` file content is only loaded by the server and won't be visible to your website visitors in any form.
+
+After you create those two files you'll be able to continue building the app. But before you do - please read over the settings to check it's configuring your application as you'd like. _Most settings can be changed later - you'll just need to do a fresh build_.
+
+### Client-side settings
+
+Alright! Let's look at the content of your newly created `client.config.ts` file. We've explained each setting with a comment.
+
+**Note: you don't need to edit any of these values** but you'll probably want to examine them all to see if there's anything you want changed. Also note that the `GD_APP_TYPE` setting is a _one off_. It's used during the initial installation to set up the database, then not afterwards.
+
+```typescript
+import type { GDClientConfig } from '../typings';
+
+const config: GDClientConfig = {
+ appSettings: {
+ // this setting controls the overall type of the installation. It's used on initial installation only. The options are:
+ // login - allows anonymous access via the browser, but unless the user has logged in they can't save their data sets
+ // or generate more rows than GD_MAX_DEMO_MODE_ROWS at a time
+ // single - there's only ever a single account and that user is logged in by default
+ // open - anyone that has access to the URL can use the application anonymously or create an account
+ // closed - no-one can access it without logging in first
+ GD_APP_TYPE: 'login',
+
+ // this allows easy extension for the prod site. On the prod site, the homepage is a splash info page to the tool. For
+ // other distributions, that isn't necessary
+ GD_GENERATOR_PATH: '/',
+
+ // the default locale for people visiting the app
+ GD_DEFAULT_LOCALE: 'en',
+
+ // the default Export Type that's show in the preview panel. The accepted values are any of the folder names of the
+ // available Export Types. See the packages/plugins package for that.
+ GD_DEFAULT_EXPORT_TYPE: 'JSON',
+
+ // this controls what locales show up in the UI. If you only enter 1, the icon to switch locales won't appear. If you
+ // want to add other locales, awesome! PRs welcome :)
+ GD_LOCALES: ['ar', 'de', 'en', 'es', 'fr', 'hi', 'ja', 'nl', 'pt', 'ru', 'ta', 'zh'],
+
+ // the default value for the number of rows to generate
+ GD_DEFAULT_NUM_ROWS: 100,
+
+ // for `login` appType, this controls how many rows can be generated by anonymous (non-logged in) users
+ GD_MAX_DEMO_MODE_ROWS: 1000,
+
+ // any time a user saves a dataset, that change is stored in the database to allow the user to backtrack and see
+ // earlier versions of the dataset. This governs the max number of history items
+ GD_MAX_DATASET_HISTORY_SIZE: 200,
+
+ // this omits specific Data Types from the application. It omits them from appearing in the UI.
+ GD_DATA_TYPE_BLACKLIST: ['BitcoinAddress', 'OrganizationNumber', 'PersonalNumber', 'SIRET'],
+
+ // this omits specific Export Types from the application. It omits them from appearing in the UI.
+ GD_EXPORT_TYPE_BLACKLIST: [],
+
+ // omits specific Country plugins from appearing in the application; comma-delimited (no spaces!)
+ GD_COUNTRY_BLACKLIST: [],
+
+ // used for extension purposes only. TODO
+ GD_IMPORT_FILES: []
+ },
+
+ auth: {
+ // controls the lifespan of the JWT. Suggest you leave this setting alone
+ GD_JWT_LIFESPAN_MINS: 15,
+
+ // if you want to enable the "Login with Google" feature, this contains the google auth client ID
+ GD_GOOGLE_AUTH_CLIENT_ID: ''
+ },
+
+ api: {
+ // the server port for the graphql server
+ GD_API_SERVER_PORT: 3001
+ },
+
+ webServer: {
+ // controls the URL you'll load up in the browser to see the app
+ GD_WEB_DOMAIN: 'localhost',
+
+ // the webserver port
+ GD_WEB_SERVER_PORT: 9000,
+
+ // https vs http. Runnign `https` locally is always fussy as heck, so leave as false unless strictly necessary
+ GD_WEB_USE_HTTPS: false
+ }
+};
+
+export default config;
+```
+
+### Server-side settings
+
+```typescript
+import type { GDServerConfig } from '../typings';
+
+const serverConfig: GDServerConfig = {
+ auth: {
+ // PLEASE UPDATE. JWT secret. This is used for the JWT generation code. It should be changed to any random string
+ GD_JWT_SECRET: 'Change this string to anything you like.',
+
+ // PLEASE UPDATE. Any random string is fine
+ GD_JWT_REFRESH_TOKEN_SECRET: 'Also change this string to something else.',
+
+ // (optional) Sign-in with Google settings for - oath2. This requires `GD_GOOGLE_AUTH_CLIENT_ID` to have been set in the
+ // client.config.ts file
+ GD_GOOGLE_AUTH_CLIENT_SECRET: ''
+ },
+
+ // (optional) this gives you the optional of tying in email functionality, to enable features like emailing for lost passwords,
+ // registering and so on
+ email: {
+ GD_EMAIL_OAUTH_SERVICE_CLIENT_ID: '',
+ GD_EMAIL_OAUTH_PRIVATE_KEY: ''
+ },
+
+ // You can either set these values before installing the app, or customize the values via the UI
+ // afterwards. This information is only used during the initial setup of the application
+ defaultAdminAccount: {
+ GD_DEFAULT_ADMIN_FIRST_NAME: 'John',
+ GD_DEFAULT_ADMIN_LAST_NAME: 'Smith',
+ GD_DEFAULT_ADMIN_EMAIL_SENDER_NAME: 'YourSite',
+ GD_DEFAULT_ADMIN_EMAIL: 'admin@youremail.net',
+ GD_DEFAULT_ADMIN_PASSWORD: 'admin123'
+ }
+};
+
+export default serverConfig;
+```
+
+## TODO
+
+Needs more documentation on:
+
+- what JWT is
+- setting up the email configuration,
+- setting up a sign-in with Google
diff --git a/packages/config/package.json b/packages/config/package.json
new file mode 100644
index 000000000..c961cbacb
--- /dev/null
+++ b/packages/config/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "@generatedata/config",
+ "version": "1.0.0",
+ "description": "This package contains the settings for your generatedata installation.",
+ "scripts": {
+ "tsc": "tsc",
+ "build": "run-s confirm-setup tsc",
+ "confirm-setup": "node ./scripts/confirm-setup.js"
+ },
+ "devDependencies": {
+ "@types/node": "^20.19.11",
+ "npm-run-all": "^4.1.5",
+ "typescript": "5.9.2"
+ },
+ "exports": {
+ ".": {
+ "default": "./dist/index.js",
+ "types": "./dist/index.d.ts"
+ },
+ "./constants": {
+ "default": "./dist/constants.js",
+ "types": "./dist/constants.d.ts"
+ },
+ "./clientConfig": {
+ "default": "./dist/client.config.js",
+ "types": "./dist/client.config.d.ts"
+ },
+ "./serverConfig": {
+ "default": "./dist/server.config.js",
+ "types": "./dist/server.config.d.ts"
+ }
+ }
+}
diff --git a/packages/config/scripts/confirm-setup.js b/packages/config/scripts/confirm-setup.js
new file mode 100644
index 000000000..446ff41ac
--- /dev/null
+++ b/packages/config/scripts/confirm-setup.js
@@ -0,0 +1,23 @@
+/**
+ * This is the base configuration package used by the rest of the application and must be configured first in order
+ * to build the application. See the instructions in this package's README on how to get set up.
+ *
+ * This script runs during the build command to verify that they've set up the config files. If not, it'll halt the build
+ * process and output a message telling them what do do.
+ */
+import fs from 'fs';
+import path from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+if (
+ !fs.existsSync(path.resolve(__dirname, '../src/client.config.ts')) ||
+ !fs.existsSync(path.resolve(__dirname, '../src/server.config.ts'))
+) {
+ console.error(
+ 'Build cancelled! Please create the two configuration files in order to proceed. See here for the documentation:\nhttps://github.com/benkeen/generatedata/tree/master/packages/config\n'
+ );
+ process.exit(1);
+}
diff --git a/packages/config/src/client.config.example.ts b/packages/config/src/client.config.example.ts
new file mode 100644
index 000000000..3b008d156
--- /dev/null
+++ b/packages/config/src/client.config.example.ts
@@ -0,0 +1,76 @@
+import type { GDClientConfig } from '../typings';
+
+const config: GDClientConfig = {
+ appSettings: {
+ // this setting controls the overall type of the installation. It's used on initial installation only. The options are:
+ // login - allows anonymous access via the browser, but unless the user has logged in they can't save their data sets
+ // or generate more rows than GD_MAX_DEMO_MODE_ROWS at a time
+ // single - there's only ever a single account and that user is logged in by default
+ // open - anyone that has access to the URL can use the application anonymously or create an account
+ // closed - no-one can access it without logging in first
+ GD_APP_TYPE: 'login',
+
+ // this allows easy extension for the prod site. On the prod site, the homepage is a splash info page to the tool. For
+ // other distributions, that isn't necessary
+ GD_GENERATOR_PATH: '/',
+
+ // the default locale for people visiting the app
+ GD_DEFAULT_LOCALE: 'en',
+
+ // the default Export Type that's show in the preview panel. The accepted values are any of the folder names of the
+ // available Export Types. See the packages/plugins package for that.
+ GD_DEFAULT_EXPORT_TYPE: 'JSON',
+
+ // this controls what locales show up in the UI. If you only enter 1, the icon to switch locales won't appear. If you
+ // want to add other locales, awesome! PRs welcome :)
+ GD_LOCALES: ['ar', 'de', 'en', 'es', 'fr', 'hi', 'ja', 'nl', 'pt', 'ru', 'ta', 'zh'],
+
+ // the default value for the number of rows to generate
+ GD_DEFAULT_NUM_ROWS: 100,
+
+ // for `login` appType, this controls how many rows can be generated by anonymous (non-logged in) users
+ GD_MAX_DEMO_MODE_ROWS: 1000,
+
+ // any time a user saves a dataset, that change is stored in the database to allow the user to backtrack and see
+ // earlier versions of the dataset. This governs the max number of history items
+ GD_MAX_DATASET_HISTORY_SIZE: 200,
+
+ // this omits specific Data Types from the application. It omits them from appearing in the UI.
+ GD_DATA_TYPE_BLACKLIST: ['BitcoinAddress', 'OrganizationNumber', 'PersonalNumber', 'SIRET'],
+
+ // this omits specific Export Types from the application. It omits them from appearing in the UI.
+ GD_EXPORT_TYPE_BLACKLIST: [],
+
+ // omits specific Country plugins from appearing in the application; comma-delimited (no spaces!)
+ GD_COUNTRY_BLACKLIST: [],
+
+ // used for extension purposes only. TODO
+ GD_IMPORT_FILES: []
+ },
+
+ auth: {
+ // controls the lifespan of the JWT. Suggest you leave this setting alone
+ GD_JWT_LIFESPAN_MINS: 15,
+
+ // if you want to enable the "Login with Google" feature, this contains the google auth client ID
+ GD_GOOGLE_AUTH_CLIENT_ID: ''
+ },
+
+ api: {
+ // the server port for the graphql server
+ GD_API_SERVER_PORT: 3001
+ },
+
+ webServer: {
+ // controls the URL you'll load up in the browser to see the app
+ GD_WEB_DOMAIN: 'localhost',
+
+ // the webserver port
+ GD_WEB_SERVER_PORT: 9000,
+
+ // https vs http. Runnign `https` locally is always fussy as heck, so leave as false unless strictly necessary
+ GD_WEB_USE_HTTPS: false
+ }
+};
+
+export default config;
diff --git a/packages/config/src/constants.ts b/packages/config/src/constants.ts
new file mode 100644
index 000000000..38a590292
--- /dev/null
+++ b/packages/config/src/constants.ts
@@ -0,0 +1,80 @@
+const constants = {
+ // any time we roll out backward incompatible redux structure changes, this number should be bumped. It causes
+ // anyone's setup to automatically reset & forget any previous saved redux store structure so there aren't
+ // incompatibility problems with the latest code
+ APP_STATE_VERSION: 8,
+
+ // the number of default empty rows that appear in the generator
+ NUM_DEFAULT_ROWS: 4,
+
+ GITHUB_URL: 'https://github.com/benkeen/generatedata',
+ CHANGELOG_URL: 'https://github.com/benkeen/generatedata/blob/master/CHANGELOG.md',
+
+ // these map to the GD_APP_TYPE type. They're the different available configurations for this GD installation
+ APP_TYPES: {
+ LOGIN: 'login',
+ SINGLE: 'single',
+ OPEN: 'open',
+ CLOSED: 'closed'
+ },
+
+ THEMES: [
+ { value: 'ambiance', label: 'Ambiance' },
+ { value: 'bespin', label: 'Bespin' },
+ { value: 'cobalt', label: 'Cobalt' },
+ { value: 'darcula', label: 'Darcula' },
+ { value: 'lucario', label: 'Lucario' }
+ ],
+
+ GD_ALL_SUPPORTED_LOCALES: {
+ ar: 'عربى',
+ de: 'Deutsch',
+ en: 'English',
+ es: 'Español',
+ fr: 'Français',
+ hi: 'हिंदी',
+ ja: '日本人',
+ nl: 'Nederlands',
+ pt: 'Português',
+ ru: 'Русский',
+ ta: 'தமிழ்',
+ zh: '中文'
+ },
+
+ MIN_PREVIEW_ROWS: 5,
+ MAX_PREVIEW_ROWS: 20,
+ HEADER_HEIGHT: 66,
+ FOOTER_HEIGHT: 66,
+ SMALL_SCREEN_WIDTH: 720,
+ GENERATION_BATCH_SIZE: 50,
+ SMALL_GENERATION_COUNT: 1000,
+ SMALL_GENERATION_COUNT_WITH_GRAPH: 5000,
+
+ GRAPH_RANGES: {
+ RANGE1: 5000,
+ RANGE2: 10000,
+ RANGE3: 100000
+ },
+
+ GRID: {
+ SMALL_BREAKPOINT: 650,
+ MEDIUM_BREAKPOINT: 780
+ },
+
+ ZINDEXES: {
+ DRAWER: 1300,
+ DIALOG: 5005
+ },
+
+ DATA_TYPE_GROUPS: ['humanData', 'geo', 'text', 'numeric', 'other', 'financial', 'countrySpecific'],
+
+ EXPORT_TYPE_GROUPS: ['core', 'programmingLanguage'],
+
+ CONTINENTS: ['africa', 'asia', 'centralAmerica', 'europe', 'northAmerica', 'oceania', 'southAmerica'],
+
+ DATE_FORMAT: 'MMM d, y',
+ DATETIME_FORMAT: 'MMM d, y h:mm a',
+ TIME_FORMAT: 'h:mm a'
+};
+
+export default constants;
diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts
new file mode 100644
index 000000000..5a0b31bf8
--- /dev/null
+++ b/packages/config/src/index.ts
@@ -0,0 +1,4 @@
+// these will be invalid until the files have been created, but the `build` command will check they exist before attempting a tsc build. See the README.
+export { default as clientConfig } from './client.config';
+export { default as serverConfig } from './server.config';
+export { default as constants } from './constants';
diff --git a/packages/config/src/server.config.example.ts b/packages/config/src/server.config.example.ts
new file mode 100644
index 000000000..cb4b73994
--- /dev/null
+++ b/packages/config/src/server.config.example.ts
@@ -0,0 +1,34 @@
+import type { GDServerConfig } from '../typings';
+
+const serverConfig: GDServerConfig = {
+ auth: {
+ // PLEASE UPDATE. JWT secret. This is used for the JWT generation code. It should be changed to any random string
+ GD_JWT_SECRET: 'Change this string to anything you like.',
+
+ // PLEASE UPDATE. Any random string is fine
+ GD_JWT_REFRESH_TOKEN_SECRET: 'Also change this string to something else.',
+
+ // (optional) Sign-in with Google settings for - oath2. This requires `GD_GOOGLE_AUTH_CLIENT_ID` to have been set in the
+ // client.config.ts file
+ GD_GOOGLE_AUTH_CLIENT_SECRET: ''
+ },
+
+ // (optional) this gives you the optional of tying in email functionality, to enable features like emailing for lost passwords,
+ // registering and so on
+ email: {
+ GD_EMAIL_OAUTH_SERVICE_CLIENT_ID: '',
+ GD_EMAIL_OAUTH_PRIVATE_KEY: ''
+ },
+
+ // You can either set these values before installing the app, or customize the values via the UI
+ // afterwards. This information is only used during the initial setup of the application
+ defaultAdminAccount: {
+ GD_DEFAULT_ADMIN_FIRST_NAME: 'John',
+ GD_DEFAULT_ADMIN_LAST_NAME: 'Smith',
+ GD_DEFAULT_ADMIN_EMAIL_SENDER_NAME: 'YourSite',
+ GD_DEFAULT_ADMIN_EMAIL: 'admin@youremail.net',
+ GD_DEFAULT_ADMIN_PASSWORD: 'admin123'
+ }
+};
+
+export default serverConfig;
diff --git a/packages/config/tsconfig.json b/packages/config/tsconfig.json
new file mode 100644
index 000000000..a7caad4e0
--- /dev/null
+++ b/packages/config/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "target": "esnext",
+ "module": "es6",
+ "moduleResolution": "node",
+ "declaration": true,
+ "skipLibCheck": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src/*.ts", "./typings"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/config/typings/index.d.ts b/packages/config/typings/index.d.ts
new file mode 100644
index 000000000..12992f0bf
--- /dev/null
+++ b/packages/config/typings/index.d.ts
@@ -0,0 +1,55 @@
+export type AppType = 'login' | 'open' | 'closed' | 'prod';
+
+export type GDLocale = 'ar' | 'de' | 'en' | 'es' | 'es' | 'fr' | 'hi' | 'ja' | 'nl' | 'pt' | 'ru' | 'ta' | 'zh';
+
+export type GDLocaleMap = {
+ [locale in GDLocale]: string;
+};
+
+export type GDClientConfig = {
+ api: {
+ GD_API_SERVER_PORT: number;
+ };
+ webServer: {
+ GD_WEB_DOMAIN: string;
+ GD_WEB_SERVER_PORT: number;
+ GD_WEB_USE_HTTPS: boolean;
+ };
+ auth: {
+ GD_JWT_LIFESPAN_MINS: number;
+ GD_GOOGLE_AUTH_CLIENT_ID: string;
+ };
+ appSettings: {
+ GD_APP_TYPE: AppType;
+ GD_GENERATOR_PATH: string;
+ GD_DEFAULT_LOCALE: GDLocale;
+ GD_DEFAULT_EXPORT_TYPE: string;
+ GD_LOCALES: GDLocale[];
+ GD_DEFAULT_NUM_ROWS: number;
+ GD_MAX_DEMO_MODE_ROWS: number;
+ GD_MAX_DATASET_HISTORY_SIZE: number;
+ GD_DATA_TYPE_BLACKLIST: string[];
+ GD_EXPORT_TYPE_BLACKLIST: string[];
+ GD_COUNTRY_BLACKLIST: string[];
+ GD_IMPORT_FILES: string[];
+ };
+};
+
+export type GDServerConfig = {
+ email: {
+ GD_EMAIL_OAUTH_SERVICE_CLIENT_ID: string;
+ GD_EMAIL_OAUTH_PRIVATE_KEY: string;
+ };
+ auth: {
+ GD_JWT_SECRET: string;
+ GD_JWT_REFRESH_TOKEN_SECRET: string;
+ GD_GOOGLE_AUTH_CLIENT_SECRET: string;
+ };
+ defaultAdminAccount: {
+ GD_DEFAULT_ADMIN_FIRST_NAME: string;
+ GD_DEFAULT_ADMIN_LAST_NAME: string;
+ GD_DEFAULT_ADMIN_EMAIL_SENDER_NAME: string;
+ GD_DEFAULT_ADMIN_EMAIL: string;
+ GD_DEFAULT_ADMIN_PASSWORD: string;
+ };
+};
diff --git a/packages/plugins/.gitignore b/packages/plugins/.gitignore
new file mode 100644
index 000000000..276ba9b59
--- /dev/null
+++ b/packages/plugins/.gitignore
@@ -0,0 +1,2 @@
+node_modules/*
+dist/
\ No newline at end of file
diff --git a/packages/plugins/README.md b/packages/plugins/README.md
new file mode 100644
index 000000000..7e22e29fb
--- /dev/null
+++ b/packages/plugins/README.md
@@ -0,0 +1,12 @@
+# [Docs](../../../docs/README.md) » Plugins
+
+The generatedata script is an _engine_ to let you generate different sorts of data. It can be extended via plugins
+to let you generate precisely whatever data you want, in whatever format you want.
+
+This folder contains the three types of plugins available. A lot of the documentation in this section is to explain how
+to configure the plugins for use by the CLI, and what options are available for each. For normal use, you'd see all this
+information via the generatedata UI.
+
+- [Data Types](./dataTypes/README.md) - types of data that can be generated (e.g. names, phone numbers, email addresses)
+- [Export Types](./exportTypes/README.md) - the format of the generate data (e.g. JSON, CSV, XML, SQL)
+- [Countries](./countries/README.md) - country-specific data to improve how realistic the regional data looks
diff --git a/packages/plugins/node_modules/@generatedata/types b/packages/plugins/node_modules/@generatedata/types
new file mode 120000
index 000000000..97c1f80bc
--- /dev/null
+++ b/packages/plugins/node_modules/@generatedata/types
@@ -0,0 +1 @@
+../../../types
\ No newline at end of file
diff --git a/packages/plugins/package.json b/packages/plugins/package.json
new file mode 100644
index 000000000..757e1e598
--- /dev/null
+++ b/packages/plugins/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "@generatedata/plugins",
+ "version": "1.0.0",
+ "description": "generatedata.com plugins",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "scripts": {
+ "tsc": "tsc",
+ "build": "run-s tsc copy-i18n-files",
+ "copy-i18n-files": "copyfiles -u 1 ./src/*/*/i18n/*.json dist"
+ },
+ "dependencies": {
+ "@mui/material": "^7.3.1",
+ "@mui/icons-material": "^7.3.1",
+ "@mui/lab": "^7.0.0-beta.16",
+ "date-fns": "^2.17.0",
+ "react": "^19.1.1"
+ },
+ "devDependencies": {
+ "@generatedata/types": "workspace:*",
+ "@generatedata/config": "workspace:*",
+ "@generatedata/utils": "workspace:*",
+ "@testing-library/react": "^10.4.7",
+ "copyfiles": "2.4.1",
+ "jest": "^29.4.3",
+ "npm-run-all": "^4.1.5",
+ "typescript": "^5.7.3"
+ }
+}
diff --git a/packages/plugins/src/countries/Australia/bundle.ts b/packages/plugins/src/countries/Australia/bundle.ts
new file mode 100644
index 000000000..a1b659137
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/bundle.ts
@@ -0,0 +1,164 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Australia: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'australia',
+ regionNames: i18n.regionNames,
+ continent: 'oceania',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['Xxxx-xxxx', '(0x) xxxx xxxx', '04xx xxx xxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Australian Capital Territory',
+ regionShort: 'ACT',
+ regionSlug: 'australian_capital_territories',
+ weight: 3,
+ cities: ['Canberra']
+ },
+ {
+ regionName: 'New South Wales',
+ regionShort: 'NSW',
+ regionSlug: 'new_south_wales',
+ weight: 69,
+ cities: [
+ 'Sydney',
+ 'Albury',
+ 'Armidale',
+ 'Bathurst',
+ 'Blue Mountains',
+ 'Broken Hill',
+ 'Campbelltown',
+ 'Cessnock',
+ 'Dubbo',
+ 'Goulburn',
+ 'Grafton',
+ 'Lithgow',
+ 'Liverpool',
+ 'Newcastle',
+ 'Orange',
+ 'Parramatta',
+ 'Penrith',
+ 'Queanbeyan',
+ 'Tamworth',
+ 'Wagga Wagga',
+ 'Wollongong'
+ ]
+ },
+ {
+ regionName: 'Northern Territory',
+ regionShort: 'NT',
+ regionSlug: 'northern_territory',
+ weight: 2,
+ cities: ['Darwin', 'Palmerston']
+ },
+ {
+ regionName: 'Queensland',
+ regionShort: 'QLD',
+ regionSlug: 'queensland',
+ weight: 42,
+ cities: [
+ 'Brisbane',
+ 'Bundaberg',
+ 'Cairns',
+ 'Caloundra',
+ 'Charters Towers',
+ 'Gladstone',
+ 'Gold Coast',
+ 'Hervey Bay',
+ 'Ipswich',
+ 'Logan City',
+ 'Mackay',
+ 'Maryborough',
+ 'Mount Isa',
+ 'Redcliffe',
+ 'Redlands',
+ 'Rockhampton',
+ 'Toowoomba',
+ 'Townsville'
+ ]
+ },
+ {
+ regionName: 'South Australia',
+ regionShort: 'SA',
+ regionSlug: 'south_australia',
+ weight: 16,
+ cities: ['Adelaide', 'Mount Gambier', 'Murray Bridge', 'Port Augusta', 'Port Pirie', 'Port Lincoln', 'Victor Harbor', 'Whyalla']
+ },
+ {
+ regionName: 'Tasmania',
+ regionShort: 'TAS',
+ regionSlug: 'tasmania',
+ weight: 5,
+ cities: ['Greater Hobart', 'Burnie', 'Devonport', 'Launceston']
+ },
+ {
+ regionName: 'Victoria',
+ regionShort: 'VIC',
+ regionSlug: 'victoria',
+ weight: 52,
+ cities: [
+ 'Melbourne',
+ 'Ararat',
+ 'Bairnsdale',
+ 'Benalla',
+ 'Ballarat',
+ 'Bendigo',
+ 'Belgrave',
+ 'Dandenong',
+ 'Frankston',
+ 'Geelong',
+ 'Hamilton',
+ 'Horsham',
+ 'Melton',
+ 'Moe',
+ 'Morwell',
+ 'Mildura',
+ 'Sale',
+ 'Shepparton',
+ 'Swan Hill',
+ 'Traralgon',
+ 'Wangaratta',
+ 'Warrnambool',
+ 'Wodonga'
+ ]
+ },
+ {
+ regionName: 'Western Australia',
+ regionShort: 'WA',
+ regionSlug: 'western_australia',
+ weight: 21,
+ cities: [
+ 'Perth',
+ 'Albany',
+ 'Armadale',
+ 'Bayswater',
+ 'Belmont',
+ 'Bunbury',
+ 'Canning',
+ 'Cockburn',
+ 'Fremantle',
+ 'Geraldton-Greenough',
+ 'Gosnells',
+ 'Joondalup',
+ 'Kalgoorlie-Boulder',
+ 'Mandurah',
+ 'Melville',
+ 'Nedlands',
+ 'Rockingham',
+ 'South Perth',
+ 'Stirling',
+ 'Subiaco',
+ 'Swan',
+ 'Wanneroo'
+ ]
+ }
+ ]
+});
+
+export default Australia;
diff --git a/packages/plugins/src/countries/Australia/i18n/ar.json b/packages/plugins/src/countries/Australia/i18n/ar.json
new file mode 100644
index 000000000..ed129df9e
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "أستراليا",
+ "regionNames": "أستراليا الولايات / الأقاليم"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/de.json b/packages/plugins/src/countries/Australia/i18n/de.json
new file mode 100644
index 000000000..467a26bac
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Australien",
+ "regionNames": "Australische Staaten/Territorien"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/en.json b/packages/plugins/src/countries/Australia/i18n/en.json
new file mode 100644
index 000000000..1ce08f4a2
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Australia",
+ "regionNames": "Australian St./Terr."
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/es.json b/packages/plugins/src/countries/Australia/i18n/es.json
new file mode 100644
index 000000000..831f22aa1
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Australia",
+ "regionNames": "Estados/Territorios de Australia"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/fr.json b/packages/plugins/src/countries/Australia/i18n/fr.json
new file mode 100644
index 000000000..b4075bf7c
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Australie",
+ "regionNames": "États/Territoires australiens"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/hi.json b/packages/plugins/src/countries/Australia/i18n/hi.json
new file mode 100644
index 000000000..326b3ad42
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ऑस्ट्रेलिया",
+ "regionNames": "ऑस्ट्रेलियाई राज्य/क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/ja.json b/packages/plugins/src/countries/Australia/i18n/ja.json
new file mode 100644
index 000000000..00c3d4e8a
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "オーストラリア",
+ "regionNames": "オーストラリアの州/準州"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/nl.json b/packages/plugins/src/countries/Australia/i18n/nl.json
new file mode 100644
index 000000000..e8955774d
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Australië",
+ "regionNames": "Australische staten/gebieden"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/pt.json b/packages/plugins/src/countries/Australia/i18n/pt.json
new file mode 100644
index 000000000..7379dab19
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Austrália",
+ "regionNames": "Est. / ter. australianos"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/ru.json b/packages/plugins/src/countries/Australia/i18n/ru.json
new file mode 100644
index 000000000..1c7e0121d
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Австралия",
+ "regionNames": "Австралийская ул./терр."
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/ta.json b/packages/plugins/src/countries/Australia/i18n/ta.json
new file mode 100644
index 000000000..99ced890e
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ஆஸ்திரேலியா",
+ "regionNames": "ஆஸ்திரேலிய மாநிலங்கள்/பிரதேசங்கள்"
+}
diff --git a/packages/plugins/src/countries/Australia/i18n/zh.json b/packages/plugins/src/countries/Australia/i18n/zh.json
new file mode 100644
index 000000000..a593603aa
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "澳大利亚",
+ "regionNames": "澳大利亚各州/地区"
+}
diff --git a/packages/plugins/src/countries/Australia/names.ts b/packages/plugins/src/countries/Australia/names.ts
new file mode 100644
index 000000000..cb283bafc
--- /dev/null
+++ b/packages/plugins/src/countries/Australia/names.ts
@@ -0,0 +1,15 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = ['Aaliyah', 'Abigail', 'Addison', 'Adeline', 'Alice', 'Amelia', 'Anna', 'Annabelle', 'Aria', 'Ariana', 'Audrey', 'Aurora', 'Ava', 'Ayla', 'Bella', 'Billie', 'Bonnie', 'Charlie', 'Charlotte', 'Chloe', 'Claire', 'Clara', 'Daisy', 'Delilah', 'Eden', 'Eleanor', 'Elena', 'Eliza', 'Elizabeth', 'Ella', 'Ellie', 'Eloise', 'Elsie', 'Emilia', 'Emily', 'Emma', 'Eva', 'Evelyn', 'Evie', 'Florence', 'Frankie', 'Freya', 'Georgia', 'Grace', 'Hallie', 'Hannah', 'Harlow', 'Harper', 'Harriet', 'Hazel', 'Imogen', 'Isabel', 'Isabella', 'Isabelle', 'Isla', 'Ivy', 'Jasmine', 'Lara', 'Layla', 'Lily', 'Lola', 'Lucy', 'Luna', 'Mackenzie', 'Maddison', 'Maeve', 'Maggie', 'Matilda', 'Maya', 'Mia', 'Mila', 'Millie', 'Molly', 'Olive', 'Olivia', 'Penelope', 'Peyton', 'Phoebe', 'Piper', 'Pippa', 'Poppy', 'Quinn', 'Remi', 'Rose', 'Rosie', 'Ruby', 'Sadie', 'Sarah', 'Savannah', 'Scarlett', 'Sienna', 'Sofia', 'Sophia', 'Sophie', 'Stella', 'Summer', 'Violet', 'Willow', 'Zara', 'Zoe'];
+
+const maleNames = ['Adam', 'Aiden', 'Alexander', 'Ali', 'Angus', 'Archer', 'Archie', 'Ari', 'Arlo', 'Arthur', 'Asher', 'Ashton', 'Austin', 'Beau', 'Benjamin', 'Billy', 'Bodhi', 'Caleb', 'Carter', 'Charles', 'Charlie', 'Connor', 'Cooper', 'Daniel', 'Darcy', 'Dylan', 'Edward', 'Eli', 'Elijah', 'Ethan', 'Ezra', 'Felix', 'Finn', 'Fletcher', 'Flynn', 'Gabriel', 'George', 'Hamish', 'Harrison', 'Harry', 'Harvey', 'Henry', 'Hudson', 'Hugo', 'Hunter', 'Isaac', 'Jack', 'Jackson', 'Jacob', 'James', 'Jasper', 'Jaxon', 'Jayden', 'Jordan', 'Joseph', 'Joshua', 'Jude', 'Kai', 'Lachlan', 'Lennox', 'Leo', 'Leon', 'Leonardo', 'Levi', 'Lewis', 'Liam', 'Lincoln', 'Logan', 'Louis', 'Luca', 'Lucas', 'Luka', 'Luke', 'Marcus', 'Mason', 'Matthew', 'Max', 'Michael', 'Muhammad', 'Noah', 'Oliver', 'Oscar', 'Owen', 'Parker', 'Patrick', 'Riley', 'River', 'Ryan', 'Ryder', 'Samuel', 'Sebastian', 'Sonny', 'Spencer', 'Theo', 'Theodore', 'Thomas', 'Vincent', 'William', 'Xavier', 'Zachary'];
+
+const lastNames = ['Adams', 'Allen', 'Anderson', 'Bailey', 'Baker', 'Bell', 'Bennett', 'Brown', 'Butler', 'Cameron', 'Campbell', 'Carter', 'Chapman', 'Clark', 'Clarke', 'Colling', 'Cook', 'Cooper', 'Cox', 'Davies', 'Davis', 'Edwards', 'Elliott', 'Ellis', 'Evans', 'Fisher', 'Gibson', 'Graham', 'Gray', 'Green', 'Hall', 'Hamilton', 'Harris', 'Harrison', 'Harvey', 'Hill', 'Hughes', 'Jackson', 'James', 'Johnson', 'Johnston', 'Jones', 'Kelly', 'Kennedy', 'King', 'Knight', 'Lee', 'Lewis', 'Marshall', 'Martin', 'Mason', 'Matthews', 'McDonald', 'Miller', 'Mills', 'Mitchell', 'Moore', 'Morgan', 'Morris', 'Murphy', 'Murray', 'Nguyen', "O'Brien", 'Parker', 'Pearce', 'Phillips', 'Price', 'Reid', 'Richards', 'Richardson', 'Roberts', 'Robertson', 'Robinson', 'Rogers', 'Ross', 'Russell', 'Ryan', 'Scott', 'Shaw', 'Simpson', 'Smith', 'Stevens', 'Stewart', 'Taylor', 'Thomas', 'Thompson', 'Thomson', 'Tran', 'Turner', 'Walker', 'Walsh', 'Ward', 'Watson', 'Webb', 'White', 'Williams', 'Wilson', 'Wood', 'Wright', 'Young'];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Austria/bundle.ts b/packages/plugins/src/countries/Austria/bundle.ts
new file mode 100644
index 000000000..8490ba8ee
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/bundle.ts
@@ -0,0 +1,180 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Austria: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'austria',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Vienna',
+ regionShort: 'Wien',
+ regionSlug: 'vienna',
+ weight: 4113,
+ cities: ['Vienna']
+ },
+ {
+ regionName: 'Vorarlberg',
+ regionShort: 'Vbg.',
+ regionSlug: 'voralberg',
+ weight: 142,
+ cities: [
+ 'Dornbirn',
+ 'Feldkirch',
+ 'Bregenz',
+ 'Lustenau',
+ 'Hohenems',
+ 'Bludenz',
+ 'Hard',
+ 'Rankweil',
+ 'Götzis',
+ 'Lauterach',
+ 'Wolfurt',
+ 'Höchst',
+ 'Altach'
+ ]
+ },
+ {
+ regionName: 'Upper Austria',
+ regionShort: 'OÖ.',
+ regionSlug: 'upper_austria',
+ weight: 117,
+ cities: [
+ 'Linz',
+ 'Wels',
+ 'Steyr',
+ 'Leonding',
+ 'Traun',
+ 'Braunau am Inn',
+ 'Ansfelden',
+ 'Bad Ischl',
+ 'Gmunden',
+ 'Marchtrenk',
+ 'Vöcklabruck',
+ 'Ried im Innkreis',
+ 'Enns',
+ 'Altmünster',
+ 'Laakirchen',
+ 'Sierning'
+ ]
+ },
+ {
+ regionName: 'Lower Austria',
+ regionShort: 'NÖ.',
+ regionSlug: 'lower_austria',
+ weight: 84,
+ cities: [
+ 'St. Pölten',
+ 'Wiener Neustadt',
+ 'Klosterneuburg',
+ 'Baden',
+ 'Krems an der Donau',
+ 'Amstetten',
+ 'Mödling',
+ 'Traiskirchen',
+ 'Schwechat',
+ 'Stockerau',
+ 'Tulln an der Donau',
+ 'Ternitz',
+ 'Perchtoldsdorf',
+ 'Korneuburg',
+ 'Neunkirchen',
+ 'Hollabrunn',
+ 'Waidhofen an der Ybbs',
+ 'Bad Vöslau',
+ 'Brunn am Gebirge',
+ 'Zwettl-Niederösterreich'
+ ]
+ },
+ {
+ regionName: 'Salzburg',
+ regionShort: 'Sbg.',
+ regionSlug: 'salzburg',
+ weight: 74,
+ cities: ['Salzburg', 'Hallein', 'Saalfelden am Steinernen Meer', 'Wals-Siezenheim', 'Sankt Johann im Pongau', 'Bischofshofen']
+ },
+ {
+ regionName: 'Styria',
+ regionShort: 'Stm.',
+ regionSlug: 'styria',
+ weight: 73,
+ cities: ['Graz', 'Leoben', 'Kapfenberg', 'Bruck an der Mur', 'Knittelfeld', 'Köflach', 'Voitsberg', 'Judenburg', 'Weiz']
+ },
+ {
+ regionName: 'Burgenland',
+ regionShort: 'Bgl.',
+ regionSlug: 'burgenland',
+ weight: 72,
+ cities: [
+ 'Eisenstadt',
+ 'Oberwart',
+ 'Neusiedl am See',
+ 'Mattersburg',
+ 'Pinkafeld',
+ 'Neudörfl',
+ 'Parndorf',
+ 'Jennersdorf',
+ 'Güssing',
+ 'Gols',
+ 'Großpetersdorf',
+ 'Neufeld an der Leitha',
+ 'Deutschkreutz',
+ 'Rechnitz',
+ 'Oberpullendorf',
+ 'Siegendorf',
+ 'Pöttsching',
+ 'Bruckneudorf',
+ 'Frauenkirchen',
+ 'Forchtenstein'
+ ]
+ },
+ {
+ regionName: 'Carinthia',
+ regionShort: 'Ktn.',
+ regionSlug: 'carinthia',
+ weight: 59,
+ cities: [
+ 'Klagenfurt',
+ 'Villach',
+ 'Wolfsberg',
+ 'Spittal an der Drau',
+ 'Feldkirchen in Kärnten',
+ 'St. Veit an der Glan',
+ 'Völkermarkt',
+ 'St. Andrä',
+ 'Velden am Wörther See',
+ 'Finkenstein am Faaker See',
+ 'Ebenthal in Kärnten',
+ 'Ferlach'
+ ]
+ },
+ {
+ regionName: 'Tyrol',
+ regionShort: 'Tirol',
+ regionSlug: 'tyrol',
+ weight: 56,
+ cities: [
+ 'Innsbruck',
+ 'Kufstein',
+ 'Telfs',
+ 'Schwaz',
+ 'Hall in Tirol',
+ 'Wörgl',
+ 'Lienz',
+ 'Imst',
+ 'Rum',
+ 'St. Johann in Tirol',
+ 'Kitzbühel',
+ 'Zirl',
+ 'Landeck'
+ ]
+ }
+ ]
+});
+
+export default Austria;
diff --git a/packages/plugins/src/countries/Austria/i18n/ar.json b/packages/plugins/src/countries/Austria/i18n/ar.json
new file mode 100644
index 000000000..65e4bca61
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "النمسا",
+ "regionNames": "الدول النمساوية"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/de.json b/packages/plugins/src/countries/Austria/i18n/de.json
new file mode 100644
index 000000000..4a051feb1
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Österreich",
+ "regionNames": "Österreichische Bundesländer"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/en.json b/packages/plugins/src/countries/Austria/i18n/en.json
new file mode 100644
index 000000000..f27072395
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Austria",
+ "regionNames": "Austrian states"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/es.json b/packages/plugins/src/countries/Austria/i18n/es.json
new file mode 100644
index 000000000..050340808
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Austria",
+ "regionNames": "estados austriacos"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/fr.json b/packages/plugins/src/countries/Austria/i18n/fr.json
new file mode 100644
index 000000000..6af11edf4
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "L'Autriche",
+ "regionNames": "États autrichiens"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/hi.json b/packages/plugins/src/countries/Austria/i18n/hi.json
new file mode 100644
index 000000000..c3988e439
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ऑस्ट्रिया",
+ "regionNames": "ऑस्ट्रियाई राज्य"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/ja.json b/packages/plugins/src/countries/Austria/i18n/ja.json
new file mode 100644
index 000000000..a33700633
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "オーストリア",
+ "regionNames": "オーストリアの諸州"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/nl.json b/packages/plugins/src/countries/Austria/i18n/nl.json
new file mode 100644
index 000000000..5e980de02
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Oostenrijk",
+ "regionNames": "Oostenrijkse staten"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/pt.json b/packages/plugins/src/countries/Austria/i18n/pt.json
new file mode 100644
index 000000000..05a282847
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Áustria",
+ "regionNames": "Estados austríacos"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/ru.json b/packages/plugins/src/countries/Austria/i18n/ru.json
new file mode 100644
index 000000000..c797fd490
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Австрия",
+ "regionNames": "Австрийские государства"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/ta.json b/packages/plugins/src/countries/Austria/i18n/ta.json
new file mode 100644
index 000000000..bcf56985b
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ஆஸ்திரியா",
+ "regionNames": "ஆஸ்திரிய நாடுகள்"
+}
diff --git a/packages/plugins/src/countries/Austria/i18n/zh.json b/packages/plugins/src/countries/Austria/i18n/zh.json
new file mode 100644
index 000000000..78d585315
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "奥地利",
+ "regionNames": "奥地利各州"
+}
diff --git a/packages/plugins/src/countries/Austria/names.ts b/packages/plugins/src/countries/Austria/names.ts
new file mode 100644
index 000000000..782a61f66
--- /dev/null
+++ b/packages/plugins/src/countries/Austria/names.ts
@@ -0,0 +1,158 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Alina',
+ 'Amelie',
+ 'Anika',
+ 'Anna',
+ 'Chiara',
+ 'Clara',
+ 'Elena',
+ 'Elisa',
+ 'Ella',
+ 'Emilia',
+ 'Emily',
+ 'Emma',
+ 'Eva',
+ 'Felicitas',
+ 'Hannah',
+ 'Helena',
+ 'Ida',
+ 'Ina',
+ 'Isabella',
+ 'Johanna',
+ 'Josephina',
+ 'Josephine',
+ 'Kristin',
+ 'Laura',
+ 'Lea',
+ 'Lena',
+ 'Leona',
+ 'Lilja',
+ 'Lilly',
+ 'Lina',
+ 'Linda',
+ 'Lorelei',
+ 'Louisa',
+ 'Magdalena',
+ 'Marie',
+ 'Marlene',
+ 'Melissa',
+ 'Mia',
+ 'Mona',
+ 'Nina',
+ 'Nora',
+ 'Paula',
+ 'Paulina',
+ 'Pia',
+ 'Ronja',
+ 'Sara',
+ 'Sophia',
+ 'Sophie',
+ 'Valentina',
+ 'Valerie'
+];
+
+const maleNames = [
+ 'Alexander',
+ 'Anton',
+ 'Ben',
+ 'Benjamin',
+ 'Daniel',
+ 'David',
+ 'Dário',
+ 'Elijas',
+ 'Felix',
+ 'Finn',
+ 'Gabriel',
+ 'Henri',
+ 'Jakob',
+ 'Jonas',
+ 'Jonathan',
+ 'Julian',
+ 'Konstantin',
+ 'Kylian',
+ 'Levi',
+ 'Lewin',
+ 'Liam',
+ 'Luis',
+ 'Lukas',
+ 'Léo',
+ 'Léon',
+ 'Mathis',
+ 'Matthias',
+ 'Max',
+ 'Maximilian',
+ 'Milo',
+ 'Milàn',
+ 'Moritz',
+ 'Nathan',
+ 'Nico, Niko',
+ 'Nicolàs',
+ 'Niklas',
+ 'Noah',
+ 'Oliver',
+ 'Oskar',
+ 'Paul',
+ 'Philipp',
+ 'Raphael',
+ 'Samuel',
+ 'Theo',
+ 'Thomas',
+ 'Valentín',
+ 'Vinzent',
+ 'Yorik',
+ 'Yoshua'
+];
+
+const lastNames = [
+ 'Aigner',
+ 'Auer',
+ 'Bauer',
+ 'Baumgartner',
+ 'Berger',
+ 'Binder',
+ 'Brunner',
+ 'Ebner',
+ 'Eder',
+ 'Egger',
+ 'Fischer',
+ 'Fuchs',
+ 'Gruber',
+ 'Haas',
+ 'Heilig',
+ 'Hofer',
+ 'Huber',
+ 'Koller',
+ 'Lang',
+ 'Lechner',
+ 'Lehner',
+ 'Leitner',
+ 'Maier',
+ 'Mayer',
+ 'Mayr',
+ 'Moser',
+ 'Müller',
+ 'Pichler',
+ 'Reiter',
+ 'Schmid',
+ 'Schmidt',
+ 'Schneider',
+ 'Schuster',
+ 'Schwarz',
+ 'Steiner',
+ 'Wagner',
+ 'Wallner',
+ 'Weber',
+ 'Wimmer',
+ 'Winkler',
+ 'Wolf'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Belgium/bundle.ts b/packages/plugins/src/countries/Belgium/bundle.ts
new file mode 100644
index 000000000..f1ee3a67b
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/bundle.ts
@@ -0,0 +1,1092 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Belgium: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'belgium',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Antwerpen',
+ regionShort: 'AN',
+ regionSlug: 'antwerpen',
+ weight: 1,
+ cities: [
+ 'Antwerpen',
+ 'Burcht',
+ 'Zwijndrecht',
+ 'Deurne',
+ 'Wijnegem',
+ 'Borgerhout',
+ 'Borsbeek',
+ 'Wommelgem',
+ 'Merksem',
+ 'Ekeren',
+ 'Herentals',
+ 'Morkhoven',
+ 'Noorderwijk',
+ 'Hallaar',
+ 'Heist-op-den-Berg',
+ 'Booischot',
+ 'Itegem',
+ 'Wiekevorst',
+ 'Schriek',
+ 'Herselt',
+ 'Ramsel',
+ 'Houtvenne',
+ 'Hulshout',
+ 'Westmeerbeek',
+ 'Massenhoven',
+ 'Viersel',
+ 'Zandhoven',
+ 'Pulderbos',
+ 'Pulle',
+ 'Olen',
+ 'Oevel',
+ 'Tongerlo',
+ 'Westerlo',
+ 'Zoerle-Parwijs',
+ 'Herenthout',
+ 'Gierle',
+ 'Lille',
+ 'Poederlee',
+ 'Wechelderzande',
+ 'Grobbendonk',
+ 'Bouwel',
+ 'Vorselaar',
+ 'Turnhout',
+ 'Rijkevorsel',
+ 'Hoogstraten',
+ 'Meer',
+ 'Minderhout',
+ 'Wortel',
+ 'Meerle',
+ 'Merksplas',
+ 'Beerse',
+ 'Vlimmeren',
+ 'Vosselaar',
+ 'Oud-Turnhout',
+ 'Arendonk',
+ 'Ravels',
+ 'Weelde',
+ 'Poppel',
+ 'Baarle-Hertog',
+ 'Malle',
+ 'Oostmalle',
+ 'Westmalle',
+ 'Mol',
+ 'Eindhout',
+ 'Laakdal',
+ 'Vorst',
+ 'Varendonk',
+ 'Veerle',
+ 'Geel',
+ 'Meerhout',
+ 'Kasterlee',
+ 'Lichtaart',
+ 'Tielen',
+ 'Retie',
+ 'Dessel',
+ 'Balen',
+ 'Olmen',
+ 'Koningshooikt',
+ 'Lier',
+ 'Broechem',
+ 'Emblem',
+ 'Oelegem',
+ 'Ranst',
+ 'Boechout',
+ 'Vremde',
+ 'Hove',
+ 'Lint',
+ 'Kontich',
+ 'Waarloos',
+ 'Bevel',
+ 'Kessel',
+ 'Nijlen',
+ 'Duffel',
+ 'Beerzel'
+ ]
+ },
+ {
+ regionName: 'Brussels Hoofdstedelijk Gewest',
+ regionShort: 'BU',
+ regionSlug: 'brussels_hoofdstedelijk_gewest',
+ weight: 1,
+ cities: [
+ 'Brussel',
+ 'Laken',
+ 'Schaarbeek',
+ 'Etterbeek',
+ 'Elsene',
+ 'Sint-Gillis',
+ 'Anderlecht',
+ 'Sint-Jans-Molenbeek',
+ 'Koekelberg',
+ 'Sint-Agatha-Berchem',
+ 'Ganshoren',
+ 'Jette',
+ 'Neder-Over-Heembeek',
+ 'Haren',
+ 'Evere',
+ 'Sint-Pieters-Woluwe',
+ 'Oudergem',
+ 'Watermaal-Bosvoorde',
+ 'Ukkel',
+ 'Vorst',
+ 'Sint-Lambrechts-Woluwe',
+ 'Sint-Joost-ten-Node'
+ ]
+ },
+ {
+ regionName: 'Waals-Brabant',
+ regionShort: 'WB',
+ regionSlug: 'waals_brabant',
+ weight: 1,
+ cities: [
+ 'Limal',
+ 'Waver',
+ 'Bierges',
+ 'La Hulpe',
+ 'Glimes',
+ 'Incourt',
+ 'Opprebais',
+ 'PiŽtrebais',
+ 'Roux-Miroir',
+ 'Beauvechain',
+ 'Hamme-Mille',
+ "l'Ecluse",
+ 'Nodebais',
+ 'Tourinnes-la-Grosse',
+ 'Bonlez',
+ 'Chaumont-Gistoux',
+ 'Corroy-le-Grand',
+ 'Dion-Valmont',
+ 'Longueville',
+ 'Rixensart',
+ 'RosiŽres',
+ 'Genval',
+ 'Ottignies',
+ 'Ottignies-Louvain-la-Neuve',
+ 'CŽroux-Mousty',
+ 'Limelette',
+ 'Louvain-la-Neuve',
+ 'Enines',
+ 'Folx-les-Caves',
+ 'Jandrain-Jandrenouille',
+ 'Jauche',
+ 'Marilles',
+ 'Noduwez',
+ 'Orp-Jauche',
+ 'Orp-le-Grand',
+ 'HŽlŽcine',
+ 'Linsmeau',
+ 'Neerheylissem',
+ 'Opheylissem',
+ 'MalŽves-Sainte-Marie-Wastines',
+ 'Orbais',
+ 'Perwez',
+ 'Thorembais-les-BŽguines',
+ 'Thorembais-Saint-Trond',
+ 'Autre-Eglise',
+ 'Bomal',
+ 'Geest-GŽrompont-Petit-RosiŽre',
+ 'GŽrompont',
+ 'Grand-RosiŽre-Hottomont',
+ 'Huppaye',
+ 'Mont-Saint-AndrŽ',
+ 'Ramillies',
+ 'Dongelberg',
+ 'Jauchelette',
+ 'Jodoigne',
+ 'Jodoigne-Souveraine',
+ 'Lathuy',
+ 'MŽlin',
+ 'PiŽtrain',
+ 'Saint-Jean-Geest',
+ 'Saint-Remy-Geest',
+ 'ZŽtrud-Lumay',
+ 'Couture-Saint-Germain',
+ 'Lasne',
+ 'Lasne-Chapelle-Saint-Lambert',
+ 'Maransart',
+ 'Ohain',
+ 'Plancenoit',
+ 'Archennes',
+ 'Graven Grez-Doiceau',
+ 'Biez',
+ 'Bossut-Gottechain',
+ 'Nethen',
+ 'Monstreux',
+ 'Nivelles',
+ 'Baulers',
+ 'Thines',
+ 'Bornival',
+ 'Waterloo',
+ 'Promo-Control',
+ 'Eigenbrakel',
+ 'Ophain-Bois-Seigneur-Isaac',
+ 'Lillois-WitterzŽe',
+ 'Bierk Bierghes',
+ 'Roosbeek',
+ 'Quenast',
+ 'Rebecq',
+ 'Corbais',
+ 'HŽvillers',
+ 'Mont-Saint-Guibert',
+ 'Kasteelbrakel',
+ 'Woutersbrakel',
+ 'Chastre',
+ 'Chastre-Villeroux-Blanmont',
+ 'Cortil-Noirmont',
+ 'Gentinnes',
+ 'Saint-GŽry',
+ 'Nil-Saint-Vincent-Saint-Martin',
+ 'Tourinnes-Saint-Lambert',
+ 'Walhain',
+ 'Walhain-Saint-Paul',
+ 'Itter',
+ 'Virginal-Samme',
+ 'Haut-Ittre',
+ 'Genappe',
+ 'Baisy-Thy',
+ 'Bousval',
+ 'Loupoigne',
+ 'Vieux-Genappe',
+ 'Glabais',
+ 'Ways',
+ 'Houtain-le-Val',
+ 'Klabbeek',
+ 'Oostkerk',
+ 'Sint-Renelde Saintes',
+ 'Tubeke Tubize',
+ 'Court-Saint-Etienne',
+ 'Marbais',
+ 'Mellery',
+ 'Sart-Dames-Avelines',
+ 'Tilly',
+ 'Villers-la-Ville'
+ ]
+ },
+ {
+ regionName: 'Vlaams-Brabant',
+ regionShort: 'VB',
+ regionSlug: 'vlaams_brabant',
+ weight: 1,
+ cities: [
+ 'Halle',
+ 'Buizingen',
+ 'Lembeek',
+ 'Herfelingen',
+ 'Herne',
+ 'Sint-Pieters-Kapelle',
+ 'Bever Bievene',
+ 'Hoeilaart',
+ 'Galmaarden',
+ 'Tollembeek',
+ 'Vollezele',
+ 'Oudenaken',
+ 'Sint-Laureins-Berchem',
+ 'Sint-Pieters-Leeuw',
+ 'Ruisbroek',
+ 'Vlezenbeek',
+ 'Drogenbos',
+ 'Linkebeek',
+ 'Sint-Genesius-Rode',
+ 'Beersel',
+ 'Lot',
+ 'Alsemberg',
+ 'Dworp',
+ 'Huizingen',
+ 'Bogaarden',
+ 'Heikruis',
+ 'Pepingen',
+ 'Elingen',
+ 'Beert',
+ 'Bellingen',
+ 'Dilbeek',
+ 'Sint-Martens-Bodegem',
+ 'Sint-Ulriks-Kapelle',
+ 'Itterbeek',
+ 'Groot-Bijgaarden',
+ 'Schepdaal',
+ 'Asse',
+ 'Bekkerzeel',
+ 'Kobbegem',
+ 'Mollem',
+ 'Relegem',
+ 'Zellik',
+ 'Ternat',
+ 'Wambeek',
+ 'Sint-Katherina-Lombeek',
+ 'Mazenzele',
+ 'Opwijk',
+ 'Gaasbeek',
+ 'Lennik',
+ 'Sint-Kwintens-Lennik',
+ 'Sint-Martens-Lennik',
+ 'Gooik',
+ 'Kester',
+ 'Leerbeek',
+ 'Oetingen',
+ 'Onze-Lieve-Vrouw-Lombeek',
+ 'Pamel',
+ 'Roosdaal',
+ 'Strijtem',
+ 'Borchtlombeek',
+ 'Liedekerke',
+ 'Wemmel',
+ 'Brussegem',
+ 'Hamme',
+ 'Merchtem',
+ 'Affligem',
+ 'Essene',
+ 'Hekelgem',
+ 'Teralfene',
+ 'Peutie',
+ 'Vilvoorde',
+ 'Cargovil',
+ 'VTM',
+ 'Melsbroek',
+ 'Perk',
+ 'Steenokkerzeel',
+ 'Machelen',
+ 'Diegem',
+ 'Londerzeel',
+ 'Malderen',
+ 'Steenhuffel',
+ 'Grimbergen',
+ 'Humbeek',
+ 'Beigem',
+ 'Strombeek-Bever',
+ 'Meise',
+ 'Wolvertem',
+ 'Kapelle-op-den-Bos',
+ 'Nieuwenrode',
+ 'Ramsdonk',
+ 'Berg',
+ 'Buken',
+ 'Kampenhout',
+ 'Nederokkerzeel',
+ 'Nossegem',
+ 'Zaventem',
+ 'Brucargo',
+ 'Sint-Stevens-Woluwe',
+ 'Sterrebeek',
+ 'Brussel X-Luchthaven Remailing'
+ ]
+ },
+ {
+ regionName: 'Luik',
+ regionShort: 'LU',
+ regionSlug: 'luik',
+ weight: 1,
+ cities: [
+ 'Glain',
+ 'Luik',
+ 'Rocourt',
+ 'Bressoux',
+ 'Jupille-sur-Meuse',
+ 'LiŽge',
+ 'Wandre',
+ 'GrivegnŽe',
+ 'LiŽge',
+ 'Angleur',
+ 'Herstal',
+ 'Milmort',
+ 'Vottem',
+ 'Liers',
+ 'Chaudfontaine',
+ 'Vaux-sous-ChŽvremont',
+ 'Beaufays',
+ 'Embourg',
+ 'B.S.D.',
+ 'Boncelles',
+ 'Seraing',
+ 'Jemeppe-sur-Meuse',
+ 'OugrŽe',
+ 'Ehein',
+ 'NeuprŽ',
+ 'Rotheux-RimiŽre',
+ 'Neuville-en-Condroz',
+ 'Plainevaux',
+ 'Esneux',
+ 'Tilff',
+ 'Dolembreux',
+ 'GomzŽ-Andoumont',
+ 'Rouvreux',
+ 'Sprimont',
+ 'LouveignŽ',
+ 'Anthisnes',
+ 'Villers-aux-Tours',
+ 'Hody',
+ 'Tavier',
+ 'Comblain-au-Pont',
+ 'Poulseur',
+ 'Comblain-Fairon',
+ 'Comblain-la-Tour',
+ 'Hamoir',
+ 'Filot',
+ 'FerriŽres',
+ 'My',
+ 'Vieuxville',
+ 'Werbomont',
+ 'Xhoris',
+ 'Burdinne',
+ 'Hannche',
+ 'LamontzŽe',
+ 'Marneffe',
+ 'Oteppe',
+ 'HŽron',
+ 'Lavoir',
+ "Waret-l'Evque",
+ 'Couthuin',
+ 'Acosse',
+ 'Ambresin',
+ 'Meeffe',
+ 'Wasseiges',
+ 'Bo‘lhe',
+ 'Geer',
+ 'Hollogne-sur-Geer',
+ 'Lens-Saint-Servais',
+ 'Omal',
+ 'Darion',
+ 'Ligney',
+ 'Berloz',
+ 'Corswarem',
+ 'Rosoux-Crenwick',
+ 'Avennes',
+ 'Braives',
+ 'Ciplet',
+ 'Fallais',
+ 'Fumal',
+ 'Ville-en-Hesbaye',
+ 'Latinne',
+ 'Tourinne',
+ 'Abolens',
+ 'Avernas-le-Bauduin',
+ 'Avin',
+ 'BertrŽe',
+ 'Blehen',
+ 'Cras-Avernas',
+ 'Crehen',
+ 'Grand-Hallet',
+ 'Hannut',
+ 'Lens-Saint-Remy',
+ 'Merdorp',
+ 'Moxhe',
+ 'Petit-Hallet',
+ 'Poucet',
+ 'Thisnes',
+ 'TrognŽe',
+ 'Villers-le-Peuplier',
+ 'Wansin'
+ ]
+ },
+ {
+ regionName: 'Namen',
+ regionShort: 'NA',
+ regionSlug: 'namen',
+ weight: 1,
+ cities: [
+ 'Beez',
+ 'Namen',
+ 'Belgrade',
+ 'Saint-Servais',
+ 'Saint-Marc',
+ 'Bouge',
+ 'Champion',
+ 'Daussoulx',
+ 'Flawinne',
+ 'Malonne',
+ 'Suarlee',
+ 'Temploux',
+ 'Vedrin',
+ 'Boninne',
+ 'Cognelee',
+ 'Gelbressee',
+ 'Marche-les-Dames',
+ 'Beuzet',
+ 'Ernage',
+ 'Gembloux',
+ 'Grand-Manil',
+ 'Lonzee',
+ 'Sauvenire',
+ 'Grand-Leez',
+ 'Bossire',
+ 'Bothey',
+ 'Corroy-le-Ch‰teau',
+ 'Isnes',
+ 'Mazy',
+ 'Arsimont',
+ 'Auvelais',
+ 'Falisolle',
+ 'Keumiee',
+ 'Moignelee',
+ 'Sambreville',
+ 'Tamines',
+ 'Velaine-sur-Sambre',
+ 'Aisemont',
+ 'Fosses-la-Ville',
+ 'Sart-Eustache',
+ 'Vitrival',
+ 'Emines',
+ 'Rhisnes',
+ 'Villers-lez-Heest',
+ 'Warisoulx',
+ 'Bovesse',
+ 'Meux',
+ 'Saint-Denis-Bovesse',
+ 'Dave',
+ 'Jambes',
+ 'Naninne',
+ 'Wepion',
+ 'Wierde',
+ 'Erpe',
+ 'Lives-sur-Meuse',
+ 'Loy',
+ 'Boignee',
+ 'Ligny',
+ 'Sombreffe',
+ 'Tongrinne',
+ 'Floreffe',
+ 'Floriffoux',
+ 'Soye',
+ 'Arbre',
+ 'Bois-de-Villers',
+ 'Lesve',
+ 'Lustin',
+ 'Profondeville',
+ 'Rivire',
+ 'Bal‰tre',
+ 'Ham-sur-Sambre',
+ 'Jemeppe-sur-Sambre',
+ 'Mornimont',
+ 'Moustier-sur-Sambre',
+ 'Onoz',
+ 'Saint-Martin',
+ 'Spy',
+ 'Andenne',
+ 'Bonneville',
+ 'Coutisse',
+ 'Landenne',
+ 'Maizeret',
+ 'Sclayn',
+ 'Seilles',
+ 'Thon',
+ 'Vezin',
+ 'Bolinne',
+ 'Boneffe',
+ 'Branchon',
+ 'Dhuy',
+ 'Eghezee',
+ 'Hanret',
+ 'Leuze',
+ 'Liernu'
+ ]
+ },
+ {
+ regionName: 'Henegouwen',
+ regionShort: 'HE',
+ regionSlug: 'henegouwen',
+ weight: 1,
+ cities: [
+ 'Charleroi',
+ 'Marcinelle',
+ 'Couillet',
+ 'Dampremy',
+ 'Goutroux',
+ 'Marchienne-au-Pont',
+ 'Monceau-sur-Sambre',
+ 'Mont-sur-Marchienne',
+ 'Jumet',
+ 'Gosselies',
+ 'Lodelinsart',
+ 'Ransart',
+ 'Roux',
+ 'Gilly',
+ 'Montignies-sur-Sambre',
+ 'Montigny-le-Tilleul',
+ 'Landelies',
+ 'Cour-sur-Heure',
+ 'Ham-sur-Heure',
+ 'Ham-sur-Heure-Nalinnes',
+ 'Jamioulx',
+ 'Marbaix',
+ 'Nalinnes',
+ "Fontaine-l'Evque",
+ 'Forchies-la-Marche',
+ 'Leernes',
+ 'Anderlues',
+ 'Courcelles',
+ 'Gouy-lez-PiŽton',
+ 'Souvret',
+ 'Trazegnies',
+ 'Bouffioulx',
+ 'Ch‰telet',
+ 'Ch‰telineau',
+ 'Frasnes-lez-Gosselies',
+ 'Les Bons Villers',
+ 'Rves',
+ 'Villers-Perwin',
+ 'Wayaux',
+ 'Mellet',
+ 'Fleurus',
+ 'Heppignies',
+ 'Lambusart',
+ 'Brye',
+ 'WagnelŽe',
+ 'WanfercŽe-Baulet',
+ 'Buzet',
+ 'Obaix',
+ 'Pont-ˆ-Celles',
+ 'ThimŽon',
+ 'Viesville',
+ 'Liberchies',
+ 'Luttre',
+ 'Farciennes',
+ 'Pironchamps',
+ 'Aiseau',
+ 'Aiseau-Presles',
+ 'Pont-de-Loup',
+ 'Presles',
+ 'Roselies',
+ 'Acoz',
+ 'Gerpinnes',
+ 'Gougnies',
+ 'Joncret',
+ 'Loverval',
+ 'Villers-Poterie',
+ 'Boussu-lez-Walcourt',
+ 'Fourbechies',
+ 'Froidchapelle',
+ 'Vergnies',
+ 'Erpion',
+ 'Bailivre',
+ 'Chimay',
+ 'Robechies',
+ 'Saint-Remy',
+ 'Salles',
+ 'Villers-la-Tour',
+ 'Virelles',
+ 'Vaulx-lez-Chimay',
+ 'Lompret',
+ 'Baileux',
+ 'Bourlers',
+ 'Forges',
+ "l'Escaillre",
+ 'Rizes',
+ 'Grandrieu',
+ 'Montbliart',
+ 'Rance',
+ 'Sautin',
+ 'Sivry',
+ 'Sivry-Rance',
+ 'Beaumont',
+ 'Leugnies',
+ 'Leval-Chaudeville',
+ 'Renlies',
+ 'Solre-Saint-GŽry',
+ 'Thirimont',
+ 'StrŽe',
+ 'Leers-et-Fosteau',
+ 'Thuin',
+ 'Biesme-sous-Thuin',
+ 'Ragnies',
+ 'BiercŽe',
+ 'GozŽe',
+ 'Donstiennes',
+ 'Thuillies',
+ 'Lobbes',
+ 'Mont-Sainte-Genevive',
+ 'Sars-la-Buissire',
+ 'Bienne-lez-Happart',
+ "Bersillies-l'Abbaye",
+ 'Erquelinnes',
+ 'Grand-Reng',
+ 'Hantes-WihŽries',
+ 'Montignies-Saint-Christophe',
+ 'Solre-sur-Sambre',
+ 'Fontaine-Valmont',
+ 'Labuissire',
+ 'Merbes-le-Ch‰teau',
+ 'Merbes-Sainte-Marie',
+ 'Momignies',
+ 'Macon',
+ 'Monceau-Imbrechies',
+ 'Macquenoise',
+ 'Beauwelz',
+ 'Forge-Philippe',
+ 'Seloignes',
+ 'Bergen Mons',
+ 'Ghlin',
+ 'FlŽnu',
+ 'Jemappes',
+ 'Maisires',
+ 'Nimy',
+ 'HavrŽ'
+ ]
+ },
+ {
+ regionName: 'Luxemburg',
+ regionShort: 'LX',
+ regionSlug: 'luxemburg',
+ weight: 1,
+ cities: [
+ 'Bastogne',
+ 'Longvilly',
+ 'Noville',
+ 'Villers-la-Bonne-Eau',
+ 'Wardin',
+ 'Martelange',
+ 'Fauvillers',
+ 'Hollange',
+ 'Tintange',
+ 'HomprŽ',
+ 'Morhet',
+ 'Nives',
+ 'Sibret',
+ 'Vaux-lez-Rosieres',
+ 'Vaux-sur-Sure',
+ 'Juseret',
+ 'Houffalize',
+ 'Nadrin',
+ 'Mont',
+ 'Tailles',
+ 'Tavigny',
+ 'MabomprŽ',
+ 'Wibrin',
+ 'Gouvy',
+ 'LimerlŽ',
+ 'Bovigny',
+ 'Beho',
+ 'Cherain',
+ 'Montleban',
+ 'Amberloup',
+ 'Sainte-Ode',
+ 'Tillet',
+ 'Lavacherie',
+ 'Flamierge',
+ 'Bertogne',
+ 'Longchamps',
+ 'Bihain',
+ 'Vielsalm',
+ 'Petit-Thier',
+ 'Grand-Halleux',
+ 'Arlon',
+ 'Bonnert',
+ 'Heinsch',
+ 'Toernich',
+ 'Guirsch',
+ 'Autelbas',
+ 'Attert',
+ 'Nobressart',
+ 'Nothomb',
+ 'Thiaumont',
+ 'Tontelange',
+ 'Habay',
+ 'Habay-la-Neuve',
+ 'Hachy',
+ 'Anlier',
+ 'Habay-la-Vieille',
+ 'Houdemont',
+ 'Rulles',
+ 'Bellefontaine',
+ 'Rossignol',
+ 'Saint-Vincent',
+ 'Tintigny',
+ 'Etalle',
+ 'Sainte-Marie-sur-Semois',
+ 'Villers-sur-Semois',
+ 'Vance',
+ 'Chantemelle',
+ 'Buzenol',
+ 'Ch‰tillon',
+ 'Meix-le-Tige',
+ 'Saint-LŽger',
+ 'Musson',
+ 'Mussy-la-Ville',
+ 'Signeulx',
+ 'Bleid',
+ 'Ethe',
+ 'Ruette',
+ 'Virton',
+ 'Latour',
+ 'Saint-Mard',
+ 'Dampicourt',
+ 'Harnoncourt',
+ 'Lamorteau',
+ 'Rouvroy',
+ 'Torgny',
+ 'GŽrouville',
+ 'Meix-Devant-Virton',
+ 'Robelmont',
+ 'Sommethonne',
+ 'Villers-la-Loue',
+ 'Hondelange',
+ 'Messancy',
+ 'Wolkrange',
+ 'SŽlange',
+ 'Habergy',
+ 'Aubange',
+ 'Athus',
+ 'Halanzy',
+ 'Rachecourt',
+ 'Bras',
+ 'Freux',
+ 'Libramont-Chevigny',
+ 'Moircy',
+ 'Recogne',
+ 'Remagne',
+ 'Sainte-Marie-Chevigny',
+ 'Saint-Pierre',
+ 'Chiny',
+ 'Izel',
+ 'Jamoigne',
+ 'Les Bulles',
+ 'Suxy',
+ 'Termes',
+ 'Florenville',
+ 'Fontenoille',
+ 'Muno'
+ ]
+ },
+ {
+ regionName: 'West-Vlaanderen',
+ regionShort: 'WV',
+ regionSlug: 'west_vlaanderen',
+ weight: 1,
+ cities: [
+ 'Brugge Bruges',
+ 'Koolkerke',
+ 'Hertsberge',
+ 'Oostkamp',
+ 'Ruddervoorde',
+ 'Waardamme',
+ 'Sint-Andries',
+ 'Sint-Michiels',
+ 'Loppem',
+ 'Veldegem',
+ 'Zedelgem',
+ 'Aartrijke',
+ 'Knokke',
+ 'Knokke-Heist',
+ 'Westkapelle',
+ 'Heist-aan-Zee',
+ 'Ramskapelle',
+ 'Assebroek',
+ 'Sint-Kruis',
+ 'Damme',
+ 'Hoeke',
+ 'Lapscheure',
+ 'Moerkerke',
+ 'Oostkerke',
+ 'Sijsele',
+ 'Blankenberge',
+ 'Uitkerke',
+ 'Houtave',
+ 'Meetkerke',
+ 'Nieuwmunster',
+ 'Zuienkerke',
+ 'Dudzele',
+ 'Lissewege',
+ 'Zeebrugge',
+ 'Oostende',
+ 'Stene',
+ 'Zandvoorde',
+ 'De Haan',
+ 'Klemskerke',
+ 'Wenduine',
+ 'Vlissegem',
+ 'Middelkerke',
+ 'Wilskerke',
+ 'Leffinge',
+ 'Mannekensvere',
+ 'Schore',
+ 'Sint-Pieters-Kapelle',
+ 'Slijpe',
+ 'Spermalie',
+ 'Lombardsijde',
+ 'Westende',
+ 'Bredene',
+ 'Ettelgem',
+ 'Oudenburg',
+ 'Roksem',
+ 'Westkerke',
+ 'Gistel',
+ 'Moere',
+ 'Snaaskerke',
+ 'Zevekote',
+ 'Bekegem',
+ 'Eernegem',
+ 'Ichtegem',
+ 'Jabbeke',
+ 'Snellegem',
+ 'Stalhille',
+ 'Varsenare',
+ 'Zerkegem',
+ 'Kortrijk',
+ 'Bissegem',
+ 'Heule',
+ 'Bellegem',
+ 'Kooigem',
+ 'Marke',
+ 'Rollegem',
+ 'Aalbeke',
+ 'Kuurne',
+ 'Harelbeke',
+ 'Bavikhove',
+ 'Hulste',
+ 'Deerlijk',
+ 'Zwevegem',
+ 'Heestert',
+ 'Moen',
+ 'Otegem',
+ 'Sint-Denijs',
+ 'Gullegem',
+ 'Moorsele',
+ 'Wevelgem',
+ 'Anzegem',
+ 'Gijzelbrechtegem',
+ 'Ingooigem',
+ 'Vichte',
+ 'Kaster',
+ 'Tiegem',
+ 'Avelgem',
+ 'Kerkhove',
+ 'Waarmaarde',
+ 'Outrijve',
+ 'Bossuit',
+ 'Helkijn',
+ 'Spiere',
+ 'Spiere-Helkijn',
+ 'Beerst',
+ 'Diksmuide',
+ 'Driekapellen',
+ 'Esen',
+ 'Kaaskerke',
+ 'Keiem',
+ 'Lampernisse',
+ 'Leke',
+ 'Nieuwkapelle',
+ 'Oostkerke',
+ 'Oudekapelle'
+ ]
+ },
+ {
+ regionName: 'Oost-Vlaanderen',
+ regionShort: 'OV',
+ regionSlug: 'oost_vlaanderen',
+ weight: 1,
+ cities: [
+ 'Gent',
+ 'Mariakerke',
+ 'Drongen',
+ 'Wondelgem',
+ 'Sint-Amandsberg',
+ 'Oostakker',
+ 'Desteldonk',
+ 'Mendonk',
+ 'Sint-Kruis-Winkel',
+ 'Gentbrugge',
+ 'Ledeberg',
+ 'Afsnee',
+ 'Sint-Denijs-Westrem',
+ 'Zwijnaarde',
+ 'Zelzate',
+ 'Destelbergen',
+ 'Heusden',
+ 'Beervelde',
+ 'Lochristi',
+ 'Zaffelare',
+ 'Zeveneken',
+ 'Gontrode',
+ 'Melle',
+ 'Nieuwkerken-Waas',
+ 'Sint-Niklaas',
+ 'Belsele',
+ 'Sinaai-Waas',
+ 'Beveren',
+ 'Haasdonk',
+ 'Kallo',
+ 'Melsele',
+ 'Vrasene',
+ 'Doel',
+ 'Kallo',
+ 'Kieldrecht',
+ 'Verrebroek',
+ 'Elversele',
+ 'Steendorp',
+ 'Temse',
+ 'Tielrode',
+ 'Bazel',
+ 'Kruibeke',
+ 'Rupelmonde',
+ 'Daknam',
+ 'Eksaarde',
+ 'Lokeren',
+ 'De Klinge',
+ 'Meerdonk',
+ 'Sint-Gillis-Waas',
+ 'Sint-Pauwels',
+ 'Moerbeke',
+ 'Wachtebeke',
+ 'Kemzeke',
+ 'Stekene',
+ 'Appels',
+ 'Baasrode',
+ 'Dendermonde',
+ 'Grembergen',
+ 'Mespelare',
+ 'Oudegem',
+ 'Schoonaarde',
+ 'Sint-Gillis-bij-Dendermonde',
+ 'Hamme',
+ 'Moerzeke',
+ 'Massemen',
+ 'Westrem',
+ 'Wetteren',
+ 'Zele',
+ 'Waasmunster',
+ 'Buggenhout',
+ 'Opdorp',
+ 'Schellebelle',
+ 'Serskamp',
+ 'Wichelen',
+ 'Kalken',
+ 'Laarne',
+ 'Denderbelle',
+ 'Lebbeke',
+ 'Wieze',
+ 'Berlare',
+ 'Overmere',
+ 'Uitbergen',
+ 'Aalst',
+ 'Gijzegem',
+ 'Hofstade',
+ 'Baardegem',
+ 'Herdersem',
+ 'Meldert',
+ 'Moorsel',
+ 'Erembodegem',
+ 'Nieuwerkerken',
+ 'Impe',
+ 'Lede',
+ 'Oordegem',
+ 'Smetlede',
+ 'Wanzele',
+ 'Appelterre-Eichem',
+ 'Denderwindeke',
+ 'Lieferinge',
+ 'Nederhasselt'
+ ]
+ }
+ ]
+});
+
+export default Belgium;
diff --git a/packages/plugins/src/countries/Belgium/i18n/ar.json b/packages/plugins/src/countries/Belgium/i18n/ar.json
new file mode 100644
index 000000000..769b02b63
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "بلجيكا",
+ "regionNames": "المقاطعات البلجيكية"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/de.json b/packages/plugins/src/countries/Belgium/i18n/de.json
new file mode 100644
index 000000000..3cf53f061
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Belgien",
+ "regionNames": "Belgische Provinzen"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/en.json b/packages/plugins/src/countries/Belgium/i18n/en.json
new file mode 100644
index 000000000..48072a265
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Belgium",
+ "regionNames": "Belgian Prov."
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/es.json b/packages/plugins/src/countries/Belgium/i18n/es.json
new file mode 100644
index 000000000..1e8eb0773
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Bélgica",
+ "regionNames": "Provincias belgas"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/fr.json b/packages/plugins/src/countries/Belgium/i18n/fr.json
new file mode 100644
index 000000000..08eed1ac1
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Belgique",
+ "regionNames": "Provinces belges"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/hi.json b/packages/plugins/src/countries/Belgium/i18n/hi.json
new file mode 100644
index 000000000..0fc2a292f
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "बेल्जियम",
+ "regionNames": "बेल्जियम प्रांत."
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/ja.json b/packages/plugins/src/countries/Belgium/i18n/ja.json
new file mode 100644
index 000000000..9132ee993
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ベルギー",
+ "regionNames": "ベルギーの州"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/nl.json b/packages/plugins/src/countries/Belgium/i18n/nl.json
new file mode 100644
index 000000000..833461700
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "België",
+ "regionNames": "Belgische provincies"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/pt.json b/packages/plugins/src/countries/Belgium/i18n/pt.json
new file mode 100644
index 000000000..9269b1929
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Bélgica",
+ "regionNames": "Províncias belgas"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/ru.json b/packages/plugins/src/countries/Belgium/i18n/ru.json
new file mode 100644
index 000000000..14d797073
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Бельгия",
+ "regionNames": "Бельгийская пров."
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/ta.json b/packages/plugins/src/countries/Belgium/i18n/ta.json
new file mode 100644
index 000000000..dc7f1780d
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "பெல்ஜியம்",
+ "regionNames": "பெல்ஜிய மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Belgium/i18n/zh.json b/packages/plugins/src/countries/Belgium/i18n/zh.json
new file mode 100644
index 000000000..f70c6b4c9
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "比利时",
+ "regionNames": "比利时省份"
+}
diff --git a/packages/plugins/src/countries/Belgium/names.ts b/packages/plugins/src/countries/Belgium/names.ts
new file mode 100644
index 000000000..43f01fa85
--- /dev/null
+++ b/packages/plugins/src/countries/Belgium/names.ts
@@ -0,0 +1,318 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Alexandra',
+ 'Alice',
+ 'Alicia',
+ 'Aline',
+ 'Alison',
+ 'Amandine',
+ 'Anaïs',
+ 'Anissa',
+ 'Anna',
+ 'Audrey',
+ 'Aurore',
+ 'Aurélie',
+ 'Carla',
+ 'Caroline',
+ 'Cassandra',
+ 'Celia',
+ 'Charline',
+ 'Charlotte',
+ 'Chloé',
+ 'Cindy',
+ 'Claire',
+ 'Clémentine',
+ 'Céline',
+ 'Déborah',
+ 'Elisa',
+ 'Elisabeth',
+ 'Elise',
+ 'Eloïse',
+ 'Emeline',
+ 'Emilie',
+ 'Emma',
+ 'Estelle',
+ 'Fanny',
+ 'Florence',
+ 'Florine',
+ 'Gaëlle',
+ 'Inès',
+ 'Jade',
+ 'Jeanne',
+ 'Jennifer',
+ 'Julie',
+ 'Juliette',
+ 'Justine',
+ 'Kelly',
+ 'Kim',
+ 'Laetitia',
+ 'Lara',
+ 'Laure',
+ 'Laurie',
+ 'Leila',
+ 'Lisa',
+ 'Lola',
+ 'Lou',
+ 'Louise',
+ 'Lucie',
+ 'Léa',
+ 'Léna',
+ 'Marine',
+ 'Maelle',
+ 'Manon',
+ 'Margaux',
+ 'Marie',
+ 'Marion',
+ 'Mathilde',
+ 'Maurine',
+ 'Morgane',
+ 'Mégane',
+ 'Mélanie',
+ 'Mélissa',
+ 'Naomi',
+ 'Nina',
+ 'Nora',
+ 'Noémie ',
+ 'Océane',
+ 'Ophélie',
+ 'Pauline',
+ 'Rachel',
+ 'Sara',
+ 'Sofia',
+ 'Sophie',
+ 'Stephanie',
+ 'Victoria',
+ 'Virginie',
+ 'Wendy',
+ 'Yasmine',
+ 'Alix',
+ 'Amelie',
+ 'Camille',
+ 'Clara',
+ 'Clémence',
+ 'Elodie',
+ 'Helene',
+ 'Jessica',
+ 'Laura',
+ 'Salomé',
+ 'Sarah',
+ 'Valentine',
+ 'Valerie',
+ 'Vanessa',
+ 'Zoe'
+];
+
+const maleNames = [
+ 'Adrien',
+ 'Alexandre',
+ 'Ali',
+ 'Andy',
+ 'Anthony',
+ 'Antonin',
+ 'Arnaud',
+ 'Arno',
+ 'Aurélien',
+ 'Axel',
+ 'Ben',
+ 'Benjamin',
+ 'Benoît',
+ 'Boris',
+ 'Bryan',
+ 'Corentin',
+ 'Cyril',
+ 'Cédric',
+ 'Danny',
+ 'David',
+ 'Dimitri',
+ 'Dorian',
+ 'Dylan',
+ 'Emile',
+ 'Florent',
+ 'François',
+ 'Gauthier',
+ 'Gaëtan',
+ 'Geoffrey',
+ 'Gilles',
+ 'Glenn',
+ 'Grégoire',
+ 'Grégory',
+ 'Guillaume',
+ 'Jason',
+ 'Jean',
+ 'John',
+ 'Jonas',
+ 'Jordan',
+ 'Julien',
+ 'Justin',
+ 'Jérémy',
+ 'Karim',
+ 'Kevin',
+ 'Killian',
+ 'Laurent',
+ 'Leo',
+ 'Loic',
+ 'Louis',
+ 'Luca',
+ 'Lucas',
+ 'Mael',
+ 'Martin',
+ 'Mathieu',
+ 'Matteo',
+ 'Matthias',
+ 'Max',
+ 'Maxence',
+ 'Maxime',
+ 'Michael',
+ 'Nicolas',
+ 'Olivier',
+ 'Pierre',
+ 'Péter',
+ 'Quentin',
+ 'Raphael',
+ 'Renaud',
+ 'Robin',
+ 'Romain',
+ 'Rémi',
+ 'Samuel',
+ 'Sebastien',
+ 'Simon',
+ 'Stéphane',
+ 'Tanguy',
+ 'Thibaut',
+ 'Thomas',
+ 'Tom',
+ 'Tony',
+ 'Tristan',
+ 'Valentin',
+ 'William',
+ 'Xavier',
+ 'Yannick',
+ 'Alexis',
+ 'Antoine',
+ 'Arthur',
+ 'Aymeric',
+ 'Charles',
+ 'Clement',
+ 'Florian',
+ 'Hugo',
+ 'Ilias',
+ 'Jerome',
+ 'Jonathan',
+ 'Marco',
+ 'Mohamed',
+ 'Nathan',
+ 'Noah',
+ 'Vincent'
+];
+
+const lastNames = [
+ 'Aakster',
+ 'Berg',
+ 'Vincent',
+ 'Bezuindenhout',
+ 'Bouwmeester',
+ 'Bunschoten',
+ 'Cruyssen',
+ 'Dam',
+ 'De Witte',
+ 'Eikenboom',
+ 'Elzinga',
+ 'Geelen',
+ 'Aaldenberg',
+ 'Haak',
+ 'Haanraads',
+ 'Hagen',
+ 'Heeren',
+ 'Hendrix',
+ 'Hoedemaker',
+ 'Holt',
+ 'Janssens',
+ 'Jonker',
+ 'Kappel',
+ 'Aarden',
+ 'Klein',
+ 'Kloet',
+ 'Koopman',
+ 'Kuiper',
+ 'Maes',
+ 'Mertens',
+ 'Offermans',
+ 'Peerenboom',
+ 'Peeters',
+ 'Prinsen',
+ 'Aarle',
+ 'Rademaker',
+ 'Rietveld',
+ 'Roggeveen',
+ 'Romeijnders',
+ 'Smet',
+ 'Spijker',
+ 'Ter Avest',
+ 'Van Aalsburg',
+ 'Van Alphen',
+ 'Van Assen',
+ 'Achterberg',
+ 'Van Der Aart',
+ 'Archambault',
+ 'Beauchene',
+ 'Beaulieu',
+ 'Bellamy',
+ 'Berger',
+ 'Blanc',
+ 'Boivin',
+ 'Borde',
+ 'Brisbois',
+ 'Achthoven',
+ 'Chaput',
+ 'Chastain',
+ 'Chevalier',
+ 'Cloutier',
+ 'Cousineau',
+ 'Deforest',
+ 'Dubois',
+ 'Dumont',
+ 'Duval',
+ 'Fabre',
+ 'Adrichem',
+ 'Fontaine',
+ 'Gagneux',
+ 'Garcon',
+ 'Janvier',
+ 'Lachapelle',
+ 'Lamar',
+ 'Lane',
+ 'Langlais',
+ 'Lavigne',
+ 'Lemaire',
+ 'Baardwijk',
+ 'Leroux',
+ 'Marchand',
+ 'Monet',
+ 'Neuville',
+ 'Petit',
+ 'Plamondon',
+ 'Plourde',
+ 'Poirier',
+ 'Poulin',
+ 'Proulx',
+ 'Bakhuizen',
+ 'Royer',
+ 'Savatier',
+ 'Segal',
+ 'Tailler',
+ 'Tasse',
+ 'Travers',
+ 'Tremblay',
+ 'Tremble',
+ 'Victor',
+ 'Villenueve'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Brazil/bundle.ts b/packages/plugins/src/countries/Brazil/bundle.ts
new file mode 100644
index 000000000..3de48d31e
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/bundle.ts
@@ -0,0 +1,245 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Brazil: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'brazil',
+ regionNames: i18n.regionNames,
+ continent: 'south_america',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxx-xxx',
+ replacements: {
+ // 'X': '123456789',
+ // 'x': '0123456789',
+ Y: '012345678',
+ W: '01234567',
+ V: '0123456',
+ U: '012345',
+ T: '01234',
+ S: '0123',
+ R: '678',
+ Q: '89',
+ P: '3456'
+ }
+ }
+ },
+ regions: [
+ {
+ regionName: 'São Paulo',
+ regionShort: 'SP',
+ regionSlug: 'sau_paulo',
+ weight: 41,
+ cities: [
+ 'Guarulhos',
+ 'Campinas',
+ 'Osasco',
+ 'Ribeirão Preto',
+ 'Mauá',
+ 'Mogi das Cruzes',
+ 'Diadema',
+ 'Jundiaí',
+ 'Carapicuíba',
+ 'Piracicaba'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '1Xxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Minas Gerais',
+ regionShort: 'MG',
+ regionSlug: 'minas_gerais',
+ weight: 20,
+ cities: [
+ 'Belo Horizonte',
+ 'Uberlândia',
+ 'Contagem',
+ 'Juiz de Fora',
+ 'Betim',
+ 'Montes Claros',
+ 'Ribeirão das Neves',
+ 'Uberaba',
+ 'Governador Valadares',
+ 'Ipatinga',
+ 'Sete Lagoas',
+ 'Divinópolis',
+ 'Santa Luzia'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '3xxxxxxx'
+ }
+ }
+ },
+ {
+ regionName: 'Rio de Janeiro',
+ regionShort: 'RJ',
+ regionSlug: 'rio',
+ weight: 16,
+ cities: [
+ 'Rio de Janeiro',
+ 'São Gonçalo',
+ 'Duque de Caxias',
+ 'Nova Iguaçu',
+ 'Niterói',
+ 'Belford Roxo',
+ 'Campos dos Goytacazes',
+ 'São João de Meriti',
+ 'Petrópolis'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '2Yxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Bahia',
+ regionShort: 'BA',
+ regionSlug: 'bahia',
+ weight: 14,
+ cities: ['Salvador', 'Feira de Santana', 'Vitória da Conquista', 'Camaçari', 'Itabuna', 'Juazeiro', 'Ilhéus', 'Lauro de Freitas'],
+ extendedData: {
+ zipFormat: {
+ format: '4Yxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Rio Grande do Sul',
+ regionShort: 'RS',
+ regionSlug: 'rio_grande',
+ weight: 11,
+ cities: ['Porto Alegre', 'Caxias do Sul', 'Pelotas', 'Canoas', 'Santa Maria', 'Gravataí', 'Novo Hamburgo', 'Rio Grande'],
+ extendedData: {
+ zipFormat: {
+ format: '9xxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Paraná',
+ regionShort: 'PR',
+ regionSlug: 'parana',
+ weight: 11,
+ cities: [
+ 'Curitiba',
+ 'Londrina',
+ 'Maringá',
+ 'Ponta Grossa',
+ 'Cascavel',
+ 'São José dos Pinhais',
+ 'Foz do Iguaçu',
+ 'Colombo',
+ 'Guarapuava',
+ 'Paranaguá'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '8Wxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Pernambuco',
+ regionShort: 'PE',
+ regionSlug: 'pernambuco',
+ weight: 9,
+ cities: ['Recife', 'Jaboatão dos Guararapes', 'Olinda', 'Caruaru', 'Paulista', 'Petrolina', 'Cabo de Santo Agostinho', 'Camaragibe'],
+ extendedData: {
+ zipFormat: {
+ format: '5Vxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Ceará',
+ regionShort: 'CE',
+ regionSlug: 'ceara',
+ weight: 9,
+ cities: ['Fortaleza', 'Caucaia', 'Juazeiro do Norte', 'Maracanaú', 'Sobral', 'Crato', 'Itapipoca', 'Maranguape'],
+ extendedData: {
+ zipFormat: {
+ format: '6Sxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Pará',
+ regionShort: 'PA',
+ regionSlug: 'para',
+ weight: 8,
+ cities: ['Belém', 'Ananindeua', 'Santarém', 'Marabá', 'Castanhal', 'Parauapebas', 'Abaetetuba', 'Cametá', 'Bragança'],
+ extendedData: {
+ zipFormat: {
+ format: '6RYxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Maranhão',
+ regionShort: 'MA',
+ regionSlug: 'maranhao',
+ weight: 7,
+ cities: [
+ 'São Luís',
+ 'Imperatriz',
+ 'Timon',
+ 'Caxias',
+ 'Codó',
+ 'Paço do Lumiar',
+ 'Açailândia',
+ 'Bacabal',
+ 'Santa Inês',
+ 'Balsas',
+ 'Chapadinha',
+ 'Barra do Corda'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '65xxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Santa Catarina',
+ regionShort: 'SC',
+ regionSlug: 'santa_catarina',
+ weight: 6,
+ cities: ['Joinville', 'Florianópolis', 'Blumenau', 'São José', 'Criciúma', 'Chapecó', 'Itajaí'],
+ extendedData: {
+ zipFormat: {
+ format: '8Qxxx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Goiás',
+ regionShort: 'GO',
+ regionSlug: 'goias',
+ weight: 6,
+ cities: ['Goiânia', 'Aparecida de Goiânia', 'Anápolis', 'Rio Verde', 'Luziânia', 'Águas Lindas de Goiás', 'Valparaíso de Goiás'],
+ extendedData: {
+ zipFormat: {
+ format: '7P7xx-xxx'
+ }
+ }
+ },
+ {
+ regionName: 'Paraíba',
+ regionShort: 'PB',
+ regionSlug: 'paraiba',
+ weight: 4,
+ cities: ['João Pessoa', 'Campina Grande', 'Santa Rita', 'Patos', 'Bayeux', 'Sousa', 'Cajazeiras'],
+ extendedData: {
+ zipFormat: {
+ format: '58xxx-xxx'
+ }
+ }
+ }
+ ]
+});
+
+export default Brazil;
diff --git a/packages/plugins/src/countries/Brazil/i18n/ar.json b/packages/plugins/src/countries/Brazil/i18n/ar.json
new file mode 100644
index 000000000..6b0c2f7da
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "البرازيل",
+ "regionNames": "الولايات البرازيلية"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/de.json b/packages/plugins/src/countries/Brazil/i18n/de.json
new file mode 100644
index 000000000..0e85f5076
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Brasilien",
+ "regionNames": "Brasilianische Staaten"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/en.json b/packages/plugins/src/countries/Brazil/i18n/en.json
new file mode 100644
index 000000000..580f0072b
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Brazil",
+ "regionNames": "Brazilian States"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/es.json b/packages/plugins/src/countries/Brazil/i18n/es.json
new file mode 100644
index 000000000..d7736fae8
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Brasil",
+ "regionNames": "Estados brasileños"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/fr.json b/packages/plugins/src/countries/Brazil/i18n/fr.json
new file mode 100644
index 000000000..05c9582d9
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Brésil",
+ "regionNames": "États brésiliens"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/hi.json b/packages/plugins/src/countries/Brazil/i18n/hi.json
new file mode 100644
index 000000000..ce1eba3a7
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ब्राज़िल",
+ "regionNames": "ब्राजील के राज्य"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/ja.json b/packages/plugins/src/countries/Brazil/i18n/ja.json
new file mode 100644
index 000000000..f3fd2e8fa
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ブラジル",
+ "regionNames": "ブラジルの州"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/nl.json b/packages/plugins/src/countries/Brazil/i18n/nl.json
new file mode 100644
index 000000000..0fef47e40
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Brazilië",
+ "regionNames": "Braziliaanse staten"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/pt.json b/packages/plugins/src/countries/Brazil/i18n/pt.json
new file mode 100644
index 000000000..903ae5161
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Brasil",
+ "regionNames": "Estados brasileiros"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/ru.json b/packages/plugins/src/countries/Brazil/i18n/ru.json
new file mode 100644
index 000000000..37113fc0d
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Бразилия",
+ "regionNames": "бразильские штаты"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/ta.json b/packages/plugins/src/countries/Brazil/i18n/ta.json
new file mode 100644
index 000000000..a7c0dc167
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "பிரேசில்",
+ "regionNames": "பிரேசிலிய நாடுகள்"
+}
diff --git a/packages/plugins/src/countries/Brazil/i18n/zh.json b/packages/plugins/src/countries/Brazil/i18n/zh.json
new file mode 100644
index 000000000..cd26a31a5
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "巴西",
+ "regionNames": "巴西各州"
+}
diff --git a/packages/plugins/src/countries/Brazil/names.ts b/packages/plugins/src/countries/Brazil/names.ts
new file mode 100644
index 000000000..b4cacc2dc
--- /dev/null
+++ b/packages/plugins/src/countries/Brazil/names.ts
@@ -0,0 +1,618 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Agatha',
+ 'Agatha Sophia',
+ 'Agnes',
+ 'Alana',
+ 'Alexia',
+ 'Alice',
+ 'Alice Vitória',
+ 'Aline',
+ 'Alícia',
+ 'Amanda',
+ 'Ana',
+ 'Ana Alice',
+ 'Ana Beatriz',
+ 'Ana Carolina',
+ 'Ana Cecília',
+ 'Ana Clara',
+ 'Ana Elisa',
+ 'Ana Helena',
+ 'Ana Júlia',
+ 'Ana Lara',
+ 'Ana Laura',
+ 'Ana Liz',
+ 'Ana Luísa',
+ 'Ana Lívia',
+ 'Ana Maria',
+ 'Ana Sophia',
+ 'Ana Vitória',
+ 'Analice',
+ 'Analu',
+ 'Angelina',
+ 'Anne',
+ 'Antonella',
+ 'Antônia',
+ 'Ariella',
+ 'Aurora',
+ 'Ayla',
+ 'Ayla Sophia',
+ 'Beatriz',
+ 'Bella',
+ 'Betina',
+ 'Bianca',
+ 'Brenda',
+ 'Bruna',
+ 'Bárbara',
+ 'Camila',
+ 'Camilly',
+ 'Carolina',
+ 'Caroline',
+ 'Catarina',
+ 'Cecília',
+ 'Celina',
+ 'Chloe',
+ 'Clara',
+ 'Clarice',
+ 'Diana',
+ 'Débora',
+ 'Eduarda',
+ 'Elis',
+ 'Elisa',
+ 'Ellen',
+ 'Eloá',
+ 'Eloá Victoria',
+ 'Emanuela',
+ 'Emanuelly',
+ 'Emanuelly Vitória',
+ 'Emilly',
+ 'Emilly Vitória',
+ 'Esther',
+ 'Eva',
+ 'Evelyn',
+ 'Fernanda',
+ 'Flora',
+ 'Gabriela',
+ 'Gabrielly',
+ 'Giovanna',
+ 'Giulia',
+ 'Hadassa',
+ 'Hannah',
+ 'Helena',
+ 'Helena Vitória',
+ 'Heloise',
+ 'Heloísa',
+ 'Heloísa Vitória',
+ 'Iris',
+ 'Isa',
+ 'Isabel',
+ 'Isabella',
+ 'Isabelly',
+ 'Isadora',
+ 'Isis',
+ 'Jade',
+ 'Joana',
+ 'Juliana',
+ 'Júlia',
+ 'Kiara',
+ 'Lara',
+ 'Lara Sophia',
+ 'Larissa',
+ 'Laura',
+ 'Laura Beatriz',
+ 'Laura Sophia',
+ 'Lauren',
+ 'Lavínia',
+ 'Laís',
+ 'Leonor',
+ 'Letícia',
+ 'Lia',
+ 'Liz',
+ 'Liz Maria',
+ 'Lorena',
+ 'Lorena Vitória',
+ 'Louise',
+ 'Luana',
+ 'Luara',
+ 'Luiza',
+ 'Luna',
+ 'Luísa Helena',
+ 'Lívia',
+ 'Lívia Maria',
+ 'Madalena',
+ 'Maitê',
+ 'Malú',
+ 'Manoela',
+ 'Manu',
+ 'Manuela',
+ 'Manuelly',
+ 'Marcela',
+ 'Maria',
+ 'Maria Alice',
+ 'Maria Antonella',
+ 'Maria Antônia',
+ 'Maria Beatriz',
+ 'Maria Cecília',
+ 'Maria Clara',
+ 'Maria Eduarda',
+ 'Maria Elisa',
+ 'Maria Eloá',
+ 'Maria Esther',
+ 'Maria Fernanda',
+ 'Maria Flor',
+ 'Maria Gabriela',
+ 'Maria Helena',
+ 'Maria Heloísa',
+ 'Maria Isabel',
+ 'Maria Isabella',
+ 'Maria Isadora',
+ 'Maria Isis',
+ 'Maria Júlia',
+ 'Maria Laura',
+ 'Maria Liz',
+ 'Maria Luiza',
+ 'Maria Rita',
+ 'Maria Sophia',
+ 'Maria Teresa',
+ 'Maria Valentina',
+ 'Maria Vitória',
+ 'Mariah',
+ 'Mariana',
+ 'Marina',
+ 'Martina',
+ 'Maya',
+ 'Maísa',
+ 'Mel',
+ 'Melina',
+ 'Melinda',
+ 'Melissa',
+ 'Micaela',
+ 'Milena',
+ 'Mirella',
+ 'Natália',
+ 'Nicole',
+ 'Nina',
+ 'Olívia',
+ 'Paola',
+ 'Pietra',
+ 'Pérola',
+ 'Rafaela',
+ 'Raquel',
+ 'Rayssa',
+ 'Rebeca',
+ 'Safira',
+ 'Sarah',
+ 'Serena',
+ 'Sophia',
+ 'Sophia Emanuelly',
+ 'Sophia Vitória',
+ 'Sophie',
+ 'Stella',
+ 'Tainá',
+ 'Teresa',
+ 'Thalita',
+ 'Thayla',
+ 'Theodora',
+ 'Valentina',
+ 'Vitória',
+ 'Yara',
+ 'Yasmin',
+ 'Yasmin Vitória',
+ 'Yohanna',
+ 'Zoe'
+];
+
+const maleNames = [
+ 'Abner',
+ 'Adrian',
+ 'Afonso',
+ 'Alexandre',
+ 'André',
+ 'Anthony',
+ 'Anthony Gabriel',
+ 'Anthony Miguel',
+ 'Antônio',
+ 'Apolo',
+ 'Aquiles',
+ 'Arthur',
+ 'Arthur Felipe',
+ 'Arthur Gabriel',
+ 'Arthur Henrique',
+ 'Arthur Miguel',
+ 'Asafe',
+ 'Augusto',
+ 'Benjamin',
+ 'Bento',
+ 'Benício',
+ 'Bernardo',
+ 'Bernardo Henrique',
+ 'Breno',
+ 'Bruno',
+ 'Bryan',
+ 'Bryan Henrique',
+ 'Caetano',
+ 'Caio',
+ 'Caleb',
+ 'Carlos Eduardo',
+ 'Carlos Henrique',
+ 'Cauã',
+ 'Christian',
+ 'Christopher',
+ 'Conrado',
+ 'Daniel',
+ 'Danilo',
+ 'Dante',
+ 'Davi',
+ 'Davi Henrique',
+ 'Davi Lucas',
+ 'Davi Lucca',
+ 'Davi Luiz',
+ 'Davi Miguel',
+ 'Derick',
+ 'Diego',
+ 'Diogo',
+ 'Dom',
+ 'Dominic',
+ 'Dylan',
+ 'Eduardo',
+ 'Elias',
+ 'Emanuel',
+ 'Enrico',
+ 'Enzo',
+ 'Enzo Gabriel',
+ 'Enzo Miguel',
+ 'Erick',
+ 'Estêvão',
+ 'Ezequiel',
+ 'Felipe',
+ 'Fernando',
+ 'Filipe',
+ 'Francisco',
+ 'Frederico',
+ 'Gabriel',
+ 'Gabriel Henrique',
+ 'Gael',
+ 'Guilherme',
+ 'Gustavo',
+ 'Gustavo Henrique',
+ 'Heitor',
+ 'Heitor Gabriel',
+ 'Heitor Henrique',
+ 'Heitor Miguel',
+ 'Henrique',
+ 'Henry',
+ 'Henry Gabriel',
+ 'Hugo',
+ 'Igor',
+ 'Inácio',
+ 'Isaac',
+ 'Israel',
+ 'Joaquim',
+ 'Jorge',
+ 'Josué',
+ 'José',
+ 'José Arthur',
+ 'José Augusto',
+ 'José Felipe',
+ 'José Henrique',
+ 'José Lucas',
+ 'José Miguel',
+ 'José Pedro',
+ 'João',
+ 'João Antônio',
+ 'João Arthur',
+ 'João Emanuel',
+ 'João Felipe',
+ 'João Gabriel',
+ 'João Guilherme',
+ 'João Henrique',
+ 'João Lucas',
+ 'João Lucca',
+ 'João Miguel',
+ 'João Paulo',
+ 'João Pedro',
+ 'João Vicente',
+ 'João Vitor',
+ 'Kaique',
+ 'Kauê',
+ 'Kevin',
+ 'Leandro',
+ 'Leonardo',
+ 'Levi',
+ 'Liam',
+ 'Lorenzo',
+ 'Lorenzo Gabriel',
+ 'Lorenzo Henrique',
+ 'Lorenzo Miguel',
+ 'Luan',
+ 'Lucas',
+ 'Lucas Gabriel',
+ 'Lucca',
+ 'Luigi',
+ 'Luiz',
+ 'Luiz Antônio',
+ 'Luiz Eduardo',
+ 'Luiz Felipe',
+ 'Luiz Fernando',
+ 'Luiz Guilherme',
+ 'Luiz Gustavo',
+ 'Luiz Henrique',
+ 'Luiz Miguel',
+ 'Luiz Otávio',
+ 'Marcelo',
+ 'Marcos',
+ 'Martin',
+ 'Matheus',
+ 'Matheus Henrique',
+ 'Mathias',
+ 'Matteo',
+ 'Miguel',
+ 'Miguel Henrique',
+ 'Moisés',
+ 'Murilo',
+ 'Nathan',
+ 'Nicolas',
+ 'Nicolas Gabriel',
+ 'Noah',
+ 'Oliver',
+ 'Otto',
+ 'Otávio',
+ 'Paulo',
+ 'Paulo Henrique',
+ 'Pedro',
+ 'Pedro Arthur',
+ 'Pedro Augusto',
+ 'Pedro Gabriel',
+ 'Pedro Henrique',
+ 'Pedro Lucas',
+ 'Pedro Lucca',
+ 'Pedro Miguel',
+ 'Pietro',
+ 'Pietro Henrique',
+ 'Rael',
+ 'Rafael',
+ 'Raul',
+ 'Ravi',
+ 'Renan',
+ 'Ricardo',
+ 'Richard',
+ 'Rodrigo',
+ 'Ruan',
+ 'Ryan',
+ 'Samuel',
+ 'Samuel Henrique',
+ 'Santiago',
+ 'Saulo',
+ 'Thales',
+ 'Theodoro',
+ 'Thiago',
+ 'Thomas',
+ 'Théo',
+ 'Théo Henrique',
+ 'Tomás',
+ 'Valentim',
+ 'Vicente',
+ 'Vinícius',
+ 'Vitor',
+ 'Vitor Gabriel',
+ 'Vitor Hugo',
+ 'William',
+ 'Yago',
+ 'Yan',
+ 'Yuri',
+ 'Álvaro',
+ 'Ícaro',
+ 'Ítalo'
+];
+
+const lastNames = [
+ 'da Silva',
+ 'dos Santos',
+ 'Pereira',
+ 'Alves',
+ 'Ferreira',
+ 'Rodrigues',
+ 'Silva',
+ 'de Oliveira',
+ 'de Souza',
+ 'Gomes',
+ 'Santos',
+ 'Oliveira',
+ 'Ribeiro',
+ 'de Jesus',
+ 'Soares',
+ 'Martins',
+ 'Barbosa',
+ 'Vieira',
+ 'Souza',
+ 'Lopes',
+ 'Lima',
+ 'Batista',
+ 'Fernandes',
+ 'Costa',
+ 'de Sousa',
+ 'Dias',
+ 'da Conceiçao',
+ 'de Lima',
+ 'do Nascimento',
+ 'Moreira',
+ 'Nunes',
+ 'da Costa',
+ 'Araujo',
+ 'Marques',
+ 'Cardoso',
+ 'de Almeida',
+ 'Mendes',
+ 'Nascimento',
+ 'Teixeira',
+ 'Ramos',
+ 'Carvalho',
+ 'Rosa',
+ 'Almeida',
+ 'Sousa',
+ 'Machado',
+ 'Rocha',
+ 'Santana',
+ 'de Araujo',
+ 'Borges',
+ 'Bezerra',
+ 'Henrique',
+ 'Pinheiro',
+ 'de Carvalho',
+ 'Monteiro',
+ 'Correa',
+ 'Aparecido',
+ 'Andrade',
+ 'Pinto',
+ 'da Cruz',
+ 'de Paula',
+ 'de Freitas',
+ 'Nogueira',
+ 'Leite',
+ 'Tavares',
+ 'Miranda',
+ 'Pires',
+ 'Garcia',
+ 'dos Reis',
+ 'Xavier',
+ 'do Carmo',
+ 'Duarte',
+ 'de Andrade',
+ 'Freitas',
+ 'Correia',
+ 'de Fatima',
+ 'Barros',
+ 'Coelho',
+ 'Gonçalves',
+ 'de Melo',
+ 'Reis',
+ 'Viana',
+ 'Campos',
+ 'Moraes',
+ 'Felix',
+ 'Brito',
+ 'Cordeiro',
+ 'Neves',
+ 'Moura',
+ 'Guimaraes',
+ 'Farias',
+ 'da Rocha',
+ 'de Castro',
+ 'Carneiro',
+ 'Silveira',
+ 'Candido',
+ 'Melo',
+ 'Medeiros',
+ 'de Assis',
+ 'Bispo',
+ 'de Lourdes',
+ 'Cruz',
+ 'Dantas',
+ 'Maciel',
+ 'Morais',
+ 'Braga',
+ 'Cavalcante',
+ 'Antunes',
+ 'Siqueira',
+ 'de Moura',
+ 'Domingos',
+ 'Macedo',
+ 'de Santana',
+ 'Fonseca',
+ 'Caetano',
+ 'Castro',
+ 'Menezes',
+ 'da Cunha',
+ 'de Moraes',
+ 'dos Anjos',
+ 'Inacio',
+ 'Matos',
+ 'Sales',
+ 'Cunha',
+ 'Chaves',
+ 'de Brito',
+ 'Barreto',
+ 'Queiroz',
+ 'Magalhaes',
+ 'Azevedo',
+ 'Mota',
+ 'Evangelista',
+ 'Bento',
+ 'Maia',
+ 'Amorim',
+ 'Cabral',
+ 'Bueno',
+ 'Mariano',
+ 'Torres',
+ 'Franca',
+ 'Marinho',
+ 'Amaral',
+ 'Guedes',
+ 'Freire',
+ 'Leal',
+ 'da Rosa',
+ 'Matias',
+ 'Paulino',
+ 'Sampaio',
+ 'Das Gracas',
+ 'Mendonca',
+ 'Camargo',
+ 'Franco',
+ 'Pacheco',
+ 'Sántos',
+ 'Aguiar',
+ 'de Barros',
+ 'de Morais',
+ 'Diniz',
+ 'Santiago',
+ 'Figueiredo',
+ 'Lemos',
+ 'Muniz',
+ 'Dutra',
+ 'Bastos',
+ 'da Silveira',
+ 'Vasconcelos',
+ 'de Azevedo',
+ 'da Luz',
+ 'Faria',
+ 'Trindade',
+ 'Gonzaga',
+ 'Domingues',
+ 'Paiva',
+ 'Feitosa',
+ 'de Abreu',
+ 'Teles',
+ 'de Matos',
+ 'Braz',
+ 'Coutinho',
+ 'Nonato',
+ 'Messias',
+ 'Chagas',
+ 'Simoes',
+ 'Fagundes',
+ 'Brandao',
+ 'Das Dores',
+ 'Teodoro',
+ 'Firmino',
+ 'Vaz',
+ 'de Campos',
+ 'Barboza',
+ 'Das Chagas',
+ 'Galdino',
+ 'Custodio',
+ 'Abreu',
+ 'Das Neves',
+ 'Peixoto',
+ 'Ferraz',
+ 'Rezende',
+ 'Furtado'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Canada/bundle.ts b/packages/plugins/src/countries/Canada/bundle.ts
new file mode 100644
index 000000000..aaeba1aca
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/bundle.ts
@@ -0,0 +1,706 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Canada: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'canada',
+ regionNames: i18n.regionNames,
+ continent: 'north_america',
+ extendedData: {
+ // the general zip format for the country. This may be optionally overridden for each region if a more
+ // specific format is desired. To prevent duplicate code, the replacements listed here cover ALL zip formats
+ // for each province
+ zipFormat: {
+ format: '%*@ *@*',
+ replacements: {
+ '%': 'ABCEGHJKLMNPRSTVXY',
+ '*': '0123456789',
+ '@': 'ABCEGHJKLMNPRSTVWXYZ',
+
+ // used in individual provinces below
+ '&': 'GHJ', // QC
+ '^': 'KLMNP' // ON
+ }
+ },
+
+ // the general phone format and area codes for the country
+ // http://cnac.ca/area_code_maps/canadian_area_codes.htm
+ phoneFormat: {
+ areaCodes: [
+ 403,
+ 587,
+ 780,
+ 825, // AB
+ 236,
+ 250,
+ 604,
+ 672,
+ 778, // BC
+ 204,
+ 431, // MB
+ 428,
+ 506, // NB
+ 709,
+ 879, // NL
+ 867, // NT, NU, YK
+ 782,
+ 902, // NS
+ 416,
+ 437,
+ 647,
+ 289,
+ 365,
+ 905,
+ 343,
+ 613,
+ 226,
+ 519,
+ 548,
+ 249,
+ 705,
+ 807, // ON
+ 782,
+ 902, // PE
+ 367,
+ 418,
+ 581,
+ 450,
+ 579,
+ 438,
+ 514,
+ 819,
+ 873, // QC
+ 306,
+ 639 // SK
+ ],
+ displayFormats: ['(AAA} Xxx-xxxx', '1 (AAA} Xxx-xxxx', '1-AAA-Xxx-xxxx']
+ }
+ },
+
+ // our country-wide data, with info separated into regions
+ regions: [
+ {
+ regionName: 'Alberta',
+ regionShort: 'AB',
+ regionSlug: 'alberta',
+ weight: 11,
+ cities: [
+ 'Airdrie',
+ 'Alix',
+ 'Banff',
+ 'Barrhead',
+ 'Bearberry',
+ 'Beaumont',
+ 'Bon Accord',
+ 'Bonnyville',
+ 'Bonnyville Municipal District',
+ 'Bowden',
+ 'Breton',
+ 'Bruderheim',
+ 'Calgary',
+ 'Calmar',
+ 'Camrose',
+ 'Canmore',
+ 'Carstairs',
+ 'Castor',
+ 'Chestermere',
+ 'Clearwater Municipal District',
+ 'Coaldale',
+ 'Coalhurst',
+ 'Cochrane',
+ 'Crowsnest Pass',
+ 'Crystal Springs',
+ 'Devon',
+ 'Drayton Valley',
+ 'Drumheller',
+ 'Eckville',
+ 'Edmonton',
+ 'Fahler',
+ 'Fort Saskatchewan',
+ 'Gibbons',
+ 'Glendon',
+ 'Grande Prairie',
+ 'Grande Cache',
+ 'High Level',
+ 'Hines Creek',
+ 'Innisfail',
+ 'Irricana',
+ 'Jasper',
+ 'Kitscoty',
+ 'Lac La Biche County',
+ 'Lac Ste. Anne',
+ 'Lacombe',
+ 'Lacombe County',
+ 'Lakeland County',
+ 'Lamont',
+ 'Leduc',
+ 'Legal',
+ 'Lloydminster',
+ 'Lethbridge',
+ 'Mayerthorpe',
+ 'Medicine Hat',
+ 'Millet',
+ 'Morinville',
+ 'Mundare',
+ 'Nanton',
+ 'New Sarepta',
+ 'Okotoks',
+ 'Oyen',
+ 'Provost',
+ 'Parkland County',
+ 'Penhold',
+ 'Picture Butte',
+ 'Pincher Creek',
+ 'Ponoka',
+ 'Raymond',
+ 'Red Deer',
+ 'Redwater',
+ 'Rimbey',
+ 'Rocky Mountain House',
+ 'Rocky View',
+ 'Rycroft',
+ 'St. Albert',
+ 'St. Paul',
+ 'Sedgewick',
+ 'Smoky Lake',
+ 'Spruce Grove',
+ 'Stirling',
+ 'Strathcona County',
+ 'Stony Plain',
+ 'Sundrie',
+ 'Sunset Point',
+ 'Swan Hills',
+ 'Sylvan Lake',
+ 'Taber',
+ 'Tofield',
+ 'Trochu',
+ 'Valleyview',
+ 'Vegreville',
+ 'Vilna',
+ 'Wabamun',
+ 'Warburg',
+ 'Warspite',
+ 'Westlock',
+ 'Wetaskiwin',
+ 'Wood Buffalo',
+ 'Woodlands County',
+ 'Yellowhead County'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: 'T*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [403, 587, 780, 825]
+ }
+ }
+ },
+ {
+ regionName: 'British Columbia',
+ regionShort: 'BC',
+ regionSlug: 'british_columbia',
+ weight: 13,
+ cities: [
+ '100 Mile House',
+ 'Abbotsford',
+ 'Alert Bay',
+ 'Armstrong',
+ 'Belcarra',
+ 'Burnaby',
+ 'Burns Lake',
+ 'Cache Creek',
+ 'Cariboo Regional District',
+ 'Castlegar',
+ 'Chetwynd',
+ 'Chilliwack',
+ 'Coldstream',
+ 'Colwood',
+ 'Comox',
+ 'Coquitlam',
+ 'Cranbrook',
+ 'Dawson Creek',
+ 'Delta',
+ 'Fernie',
+ 'Duncan',
+ 'Fort St. John',
+ 'Fraser Lake',
+ 'Fraser-Fort George',
+ 'Gibsons',
+ 'Harrison Hot Springs',
+ 'Hope',
+ 'Houston',
+ "Hudson's Hope",
+ 'Kelowna',
+ 'Kent',
+ 'Kimberly',
+ 'Kitimat',
+ 'Lake Cowichan',
+ 'Langford',
+ 'Langley',
+ 'Lions Bay',
+ 'Mission',
+ 'Maple Ridge',
+ 'Merritt',
+ 'Midway',
+ 'Nanaimo',
+ 'Nakusp',
+ 'Nelson',
+ 'New Westminster',
+ 'North Cowichan',
+ 'North Saanich',
+ 'North Vancouver',
+ 'Oliver',
+ 'Pemberton',
+ 'Penticton',
+ 'Pitt Meadows',
+ 'Port Alice',
+ 'Port Coquitlam',
+ 'Port Moody',
+ 'Prince George',
+ 'Qualicum Beach',
+ 'Richmond',
+ 'Salt Spring Island',
+ 'Silverton',
+ 'Smithers',
+ 'Sooke',
+ 'Sparwood',
+ 'Stewart',
+ 'Sunshine Coast Regional District',
+ 'Surrey',
+ 'Terrance',
+ 'Tumbler Ridge',
+ 'Ucluelet',
+ 'Vancouver',
+ 'Vanderhoof',
+ 'Victoria',
+ 'West Vancouver',
+ 'White Rock',
+ 'Williams Lake'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: 'V*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [236, 250, 604, 672, 778]
+ }
+ }
+ },
+ {
+ regionName: 'Manitoba',
+ regionShort: 'MB',
+ regionSlug: 'manitoba',
+ weight: 4,
+ cities: ['Winnipeg', 'Stonewall', 'Minitonas', 'Lourdes', 'Flin Flon', 'Daly', 'Brandon', 'Beausejour'],
+ extendedData: {
+ zipFormat: {
+ format: 'R*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [204, 431]
+ }
+ }
+ },
+ {
+ regionName: 'New Brunswick',
+ regionShort: 'NB',
+ regionSlug: 'new_brunswick',
+ weight: 2,
+ cities: ['Bathurst', 'Campbellton', 'Dieppe', 'Edmundston', 'Fredericton', 'Miramichi', 'Moncton', 'Saint John'],
+ extendedData: {
+ zipFormat: {
+ format: 'E*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [428, 506]
+ }
+ }
+ },
+ {
+ regionName: 'Newfoundland and Labrador',
+ regionShort: 'NL',
+ regionSlug: 'newfoundland_and_labrador',
+ weight: 2,
+ cities: [
+ "St. John's",
+ 'Springdale',
+ "Spaniard's Bay",
+ 'Rigolet',
+ 'Paradise',
+ 'Mount Pearl',
+ 'McCallum',
+ 'Marystown',
+ 'Harbour Grace',
+ 'Glovertown',
+ 'Gander',
+ 'Fogo',
+ 'Fortune',
+ 'Carbonear',
+ 'Burin',
+ 'Bonavista',
+ 'Bay Roberts'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: 'A*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [709, 879]
+ }
+ }
+ },
+ {
+ regionName: 'Northwest Territories',
+ regionShort: 'NT',
+ regionSlug: 'northwest_territories',
+ weight: 1,
+ cities: [
+ 'Yellowknife',
+ 'Wrigley',
+ 'Wha Ti',
+ 'Wekweti',
+ 'Tulita',
+ 'Tuktoyaktuk',
+ 'Tsiigehtchic',
+ 'Sachs Harbour',
+ 'Rae Lakes',
+ 'Rae-Edzo',
+ 'Paulatuk',
+ 'Norman Wells',
+ "Lutsel K'e",
+ 'Kakisa',
+ 'Inuvik',
+ 'Holman',
+ 'Hay River',
+ 'Fort Smith',
+ 'Fort Simpson',
+ 'Fort Resolution',
+ 'Fort Providence',
+ 'Fort McPherson',
+ 'Fort Laird',
+ 'Fort Good Hope',
+ 'Enterprise',
+ 'Deline',
+ 'Coleville Lake',
+ 'Aklavik'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: 'X*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [867]
+ }
+ }
+ },
+ {
+ regionName: 'Nova Scotia',
+ regionShort: 'NS',
+ regionSlug: 'nova_scotia',
+ weight: 1,
+ cities: [
+ 'Municipal District',
+ 'Town of Yarmouth',
+ 'Wolfville',
+ 'Pugwash',
+ 'Pictou',
+ 'New Glasgow',
+ 'Halifax',
+ 'Guysborough',
+ 'Cumberland County',
+ 'Cape Breton Island',
+ 'Berwick',
+ 'Baddeck',
+ 'Argyle',
+ 'Annapolis Royal',
+ 'Annapolis County'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: 'B*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [782, 902]
+ }
+ }
+ },
+ {
+ regionName: 'Nunavut',
+ regionShort: 'NU',
+ regionSlug: 'nunavut',
+ weight: 1,
+ cities: ['Arviat', 'Cambridge Bay', 'Gjoa Haven', 'Pangnirtung', 'Iqaluit'],
+ extendedData: {
+ zipFormat: {
+ format: 'X*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [867]
+ }
+ }
+ },
+ {
+ regionName: 'Ontario',
+ regionShort: 'ON',
+ regionSlug: 'ontario',
+ weight: 39,
+ cities: [
+ 'Ajax',
+ 'Aurora',
+ 'Ancaster Town',
+ 'Barrie',
+ 'Bath',
+ 'Blind River',
+ 'Burlington',
+ 'Caledon',
+ 'Cobourg',
+ 'Cornwall',
+ 'Cumberland',
+ 'East Gwillimbury',
+ 'Essex',
+ 'Etobicoke',
+ 'Gloucester',
+ 'Goderich',
+ 'Grey County',
+ 'Guelph',
+ 'Hamilton',
+ 'Hearst',
+ 'Kapuskasing',
+ 'Kawartha Lakes',
+ 'Kearny',
+ 'King Township',
+ 'Kingston',
+ 'Kitchener',
+ 'Lakeshore',
+ 'Lanark County',
+ 'LaSalle',
+ 'Leamington',
+ 'Malahide',
+ 'Markham',
+ 'Merrickville-Wolford',
+ 'Midlands',
+ 'Township of Minden Hills',
+ 'Minto',
+ 'Newbury',
+ 'Newmarket',
+ 'Norfolk County',
+ 'North Bay',
+ 'Northumberland',
+ 'Orangeville',
+ 'Orilla',
+ 'Osgoode',
+ 'Ottawa',
+ 'Ottawa-Carleton',
+ 'Owen Sound',
+ 'Oxford County',
+ 'Pickering',
+ 'Port Hope',
+ 'Quinte West',
+ 'Ramara',
+ 'Renfrew',
+ 'Richmond Hill',
+ 'Russell',
+ 'Scarborough',
+ 'St. Catharines',
+ 'St. Thomas',
+ 'Greater Sudbury',
+ 'Tay',
+ 'Thorold',
+ 'Thunder Bay',
+ 'Toronto',
+ 'Valley East',
+ 'Vanier',
+ 'Vaughan',
+ 'Warwick',
+ 'Welland',
+ 'Whitby',
+ 'Whitchurch-Stouffville',
+ 'Whitewater Region Township',
+ 'Wilmont',
+ 'Windsor',
+ 'Woodstock'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '^*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [416, 647, 437, 519, 226, 613, 343, 705, 249, 807, 905, 289, 365]
+ }
+ }
+ },
+ {
+ regionName: 'Prince Edward Island',
+ regionShort: 'PE',
+ regionSlug: 'prince_edward_island',
+ weight: 1,
+ cities: ['Charlottetown', 'Montague', 'Stratford'],
+ extendedData: {
+ zipFormat: {
+ format: 'C*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [782, 902]
+ }
+ }
+ },
+ {
+ regionName: 'Quebec',
+ regionShort: 'QC',
+ regionSlug: 'quebec',
+ weight: 23,
+ cities: [
+ 'Amqui',
+ 'Cabano',
+ 'Dégelis',
+ 'Price',
+ 'Rimouski',
+ 'Rivière-du-Loup',
+ 'Sainte-Flavie',
+ 'Alma',
+ 'Chambord',
+ 'Chicoutimi',
+ 'La Baie',
+ 'Labrecque',
+ 'Saguenay',
+ 'Saint-Prime',
+ 'Shipshaw',
+ 'Baie-Saint-Paul',
+ 'Beauport',
+ 'Cap-Rouge',
+ 'Deschambault',
+ 'Isle-aux-Coudres',
+ 'Lac-Serent',
+ 'Malbaie',
+ 'Neuville',
+ 'Pointe-au-Pic',
+ 'Québec City',
+ 'Saint-Hilarion',
+ 'Saint-Urbain',
+ 'Batiscan',
+ 'Cap-de-la-Madeleine',
+ 'Champlain',
+ 'Pointe-du-Lac',
+ 'Saint-Georges',
+ 'Shawinigan',
+ 'Trois-Rivières',
+ 'Asbestos',
+ 'Richmond',
+ 'Sherbrooke',
+ 'Valcourt',
+ 'Anjou',
+ "Baie-D'Urfé",
+ 'Beaconsfield',
+ 'Côte Saint-Luc',
+ 'Dollard-des-Ormeaux',
+ 'Dorval',
+ 'Hampstead',
+ 'Kirkland',
+ 'Lachine',
+ 'LaSalle',
+ 'Montreal',
+ 'Outremont',
+ 'Pierrefonds',
+ 'Pointe-aux-Trembles',
+ 'Pointe-Claire',
+ 'Roxboro',
+ 'Saint-Laurent',
+ 'Saint-Pierre',
+ 'Senneville',
+ 'Verdun',
+ 'Westmount',
+ 'Aylmer',
+ 'Buckingham',
+ 'Cantley',
+ 'Chelsea',
+ "Collines-de-l'Outaouais",
+ 'Gatineau',
+ 'Hull',
+ 'Ville de Maniwaki',
+ 'Mansfield-et-Pontefract',
+ 'Montebello',
+ 'Montpellier',
+ 'Namur',
+ 'Notre-Dame-de-la-Salette',
+ 'Shawville',
+ 'Thurso',
+ 'Dubuisson',
+ 'Malartic',
+ 'Notre-Dame-du-Nord',
+ 'Rouyn-Noranda',
+ 'Baie-Comeau',
+ 'Fermont',
+ 'Kawawachikamach',
+ 'Matagami',
+ 'Caplan',
+ 'Carleton',
+ 'Gaspé',
+ 'Gespeg',
+ 'Maria',
+ 'Murdochville',
+ 'Cap-Saint-Ignace',
+ 'Charny',
+ 'Lévis'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '&*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [418, 581, 450, 579, 514, 438, 819, 873]
+ }
+ }
+ },
+ {
+ regionName: 'Saskatchewan',
+ regionShort: 'SK',
+ regionSlug: 'saskatchewan',
+ weight: 4,
+ cities: [
+ 'Assiniboia',
+ 'Calder',
+ 'Canora',
+ 'Estevan',
+ 'Gravelbourg',
+ 'Hudson Bay',
+ 'Lang',
+ 'Langenburg',
+ 'Lloydminster',
+ 'Macklin',
+ 'Maple Creek',
+ 'Milestone',
+ 'Moose Jaw',
+ 'North Battleford',
+ 'Prince Albert',
+ 'Regina',
+ 'Saskatoon',
+ 'Weyburn',
+ 'Yorkton'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: 'S*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [306, 639]
+ }
+ }
+ },
+ {
+ regionName: 'Yukon',
+ regionShort: 'YT',
+ regionSlug: 'yukon',
+ weight: 1,
+ cities: ['Whitehorse', 'Watson Lake'],
+ extendedData: {
+ zipFormat: {
+ format: 'Y*@ *@*'
+ },
+ phoneFormat: {
+ areaCodes: [867]
+ }
+ }
+ }
+ ]
+});
+
+export default Canada;
diff --git a/packages/plugins/src/countries/Canada/i18n/ar.json b/packages/plugins/src/countries/Canada/i18n/ar.json
new file mode 100644
index 000000000..2d7822b0a
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "كندا",
+ "regionNames": "المقاطعات الكندية."
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/de.json b/packages/plugins/src/countries/Canada/i18n/de.json
new file mode 100644
index 000000000..aa2457f83
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Kanada",
+ "regionNames": "Kanadische Provinzen"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/en.json b/packages/plugins/src/countries/Canada/i18n/en.json
new file mode 100644
index 000000000..435fb973a
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Canada",
+ "regionNames": "Canadian Prov."
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/es.json b/packages/plugins/src/countries/Canada/i18n/es.json
new file mode 100644
index 000000000..22dcde017
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Canadá",
+ "regionNames": "Provincias canadienses"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/fr.json b/packages/plugins/src/countries/Canada/i18n/fr.json
new file mode 100644
index 000000000..8ece69b32
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Canada",
+ "regionNames": "Provinces canadiennes"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/hi.json b/packages/plugins/src/countries/Canada/i18n/hi.json
new file mode 100644
index 000000000..aa65159c4
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "कनाडा",
+ "regionNames": "कनाडा प्रो."
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/ja.json b/packages/plugins/src/countries/Canada/i18n/ja.json
new file mode 100644
index 000000000..1a72abfcd
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "カナダ",
+ "regionNames": "カナダの州"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/nl.json b/packages/plugins/src/countries/Canada/i18n/nl.json
new file mode 100644
index 000000000..a24b40718
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Canada",
+ "regionNames": "Canadese provincies"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/pt.json b/packages/plugins/src/countries/Canada/i18n/pt.json
new file mode 100644
index 000000000..1e15b4b4e
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Canadá",
+ "regionNames": "Prov. Canadense"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/ru.json b/packages/plugins/src/countries/Canada/i18n/ru.json
new file mode 100644
index 000000000..dba5a27d0
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Канада",
+ "regionNames": "Канадские провинции"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/ta.json b/packages/plugins/src/countries/Canada/i18n/ta.json
new file mode 100644
index 000000000..b3bf3e233
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "கனடா",
+ "regionNames": "கனடிய மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Canada/i18n/zh.json b/packages/plugins/src/countries/Canada/i18n/zh.json
new file mode 100644
index 000000000..56126e37e
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "加拿大",
+ "regionNames": "加拿大各省"
+}
diff --git a/packages/plugins/src/countries/Canada/names.ts b/packages/plugins/src/countries/Canada/names.ts
new file mode 100644
index 000000000..06c0f59d3
--- /dev/null
+++ b/packages/plugins/src/countries/Canada/names.ts
@@ -0,0 +1,468 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Aaliyah',
+ 'Abigail',
+ 'Addison',
+ 'Alexa',
+ 'Alice',
+ 'Allison',
+ 'Alyssa',
+ 'Amelia',
+ 'Anna',
+ 'Annabelle',
+ 'Aria',
+ 'Arianna',
+ 'Arielle',
+ 'Aubrey',
+ 'Audrey',
+ 'Aurora',
+ 'Autumn',
+ 'Ava',
+ 'Avery',
+ 'Ayla',
+ 'Beatrice',
+ 'Brielle',
+ 'Brooklyn',
+ 'Callie',
+ 'Charlie',
+ 'Charlotte',
+ 'Chloe',
+ 'Claire',
+ 'Clara',
+ 'Eleanor',
+ 'Elena',
+ 'Elizabeth',
+ 'Ella',
+ 'Ellie',
+ 'Emilia',
+ 'Emily',
+ 'Emma',
+ 'Eva',
+ 'Evelyn',
+ 'Freya',
+ 'Gabriella',
+ 'Georgia',
+ 'Grace',
+ 'Hailey',
+ 'Hannah',
+ 'Harper',
+ 'Hazel',
+ 'Isabella',
+ 'Isabelle',
+ 'Isla',
+ 'Ivy',
+ 'Jade',
+ 'Jordyn',
+ 'Julia',
+ 'Kaylee',
+ 'Keira',
+ 'Lauren',
+ 'Layla',
+ 'Leah',
+ 'Liliana',
+ 'Lillian',
+ 'Lily',
+ 'Lucy',
+ 'Lyla',
+ 'Mackenzie',
+ 'Madelyn',
+ 'Madison',
+ 'Maya',
+ 'Mia',
+ 'Mila',
+ 'Mya',
+ 'Natalie',
+ 'Nora',
+ 'Nova',
+ 'Olivia',
+ 'Ophelia',
+ 'Paisley',
+ 'Penelope',
+ 'Peyton',
+ 'Piper',
+ 'Quinn',
+ 'Rachel',
+ 'Riley',
+ 'Rosalie',
+ 'Rose',
+ 'Ruby',
+ 'Sadie',
+ 'Savannah',
+ 'Scarlett',
+ 'Sienna',
+ 'Sophia',
+ 'Sophie',
+ 'Stella',
+ 'Taylor',
+ 'Victoria',
+ 'Violet',
+ 'Zoey'
+];
+
+const maleNames = [
+ 'Adam',
+ 'Aiden',
+ 'Alex',
+ 'Alexander',
+ 'Andrew',
+ 'Arabia',
+ 'Arthur',
+ 'Asher',
+ 'Austin',
+ 'Australia',
+ 'Avery',
+ 'Benjamin',
+ 'Bennett',
+ 'Blake',
+ 'Brayden',
+ 'Brazil',
+ 'Brody',
+ 'Caleb',
+ 'Callum',
+ 'Carson',
+ 'Carter',
+ 'Charlie',
+ 'Christian',
+ 'Cole',
+ 'Colton',
+ 'Connor',
+ 'Daniel',
+ 'Declan',
+ 'Dominic',
+ 'Dylan',
+ 'Easton',
+ 'Edward',
+ 'Eli',
+ 'Elias',
+ 'Elijah',
+ 'Elliot',
+ 'Emmett',
+ 'Ethan',
+ 'Evan',
+ 'Everett',
+ 'Felix',
+ 'Gabriel',
+ 'Gavin',
+ 'Germany',
+ 'Grayson',
+ 'Harrison',
+ 'Hayden',
+ 'Henry',
+ 'Hudson',
+ 'Hunter',
+ 'India',
+ 'Isaac',
+ 'Isaiah',
+ 'Jace',
+ 'Jack',
+ 'Jackson',
+ 'Jacob',
+ 'James',
+ 'Jayden',
+ 'John',
+ 'Jordan',
+ 'Joseph',
+ 'Joshua',
+ 'Julian',
+ 'Kayden',
+ 'Landon',
+ 'Leo',
+ 'Levi',
+ 'Liam',
+ 'Lincoln',
+ 'Logan',
+ 'Luca',
+ 'Lucas',
+ 'Luke',
+ 'Marcus',
+ 'Mason',
+ 'Matteo',
+ 'Matthew',
+ 'Maverick',
+ 'Max',
+ 'Michael',
+ 'Nathan',
+ 'Nicholas',
+ 'Noah',
+ 'Nolan',
+ 'Oliver',
+ 'Owen',
+ 'Parker',
+ 'Riley',
+ 'Rowan',
+ 'Ryan',
+ 'Ryder',
+ 'Samuel',
+ 'Sebastian',
+ 'Theo',
+ 'Theodore',
+ 'Thomas',
+ 'Tristan',
+ 'William',
+ 'Wyatt',
+ 'Xavier',
+ 'Zachary',
+ 'Zayn'
+];
+
+const lastNames = [
+ 'Adams',
+ 'Allard',
+ 'Allen',
+ 'Anderson',
+ 'Andrews',
+ 'Armstrong',
+ 'Arsenault',
+ 'Bailey',
+ 'Baker',
+ 'Beaudoin',
+ 'Beaulieu',
+ 'Bedard',
+ 'Belanger',
+ 'Bell',
+ 'Bennett',
+ 'Benoit',
+ 'Bergeron',
+ 'Bernard',
+ 'Bernier',
+ 'Bertrand',
+ 'Berube',
+ 'Bilodeau',
+ 'Black',
+ 'Blais',
+ 'Boisvert',
+ 'Bolduc',
+ 'Bouchard',
+ 'Boucher',
+ 'Boudreau',
+ 'Brooks',
+ 'Brown',
+ 'Burns',
+ 'Butler',
+ 'Cameron',
+ 'Campbell',
+ 'Caron',
+ 'Carter',
+ 'Champagne',
+ 'Chan',
+ 'Charbonneau',
+ 'Chen',
+ 'Clark',
+ 'Clarke',
+ 'Cloutier',
+ 'Collins',
+ 'Cook',
+ 'Cooper',
+ 'Cormier',
+ 'Cote',
+ 'Couture',
+ 'Crawford',
+ 'Cyr',
+ 'Davidson',
+ 'Davies',
+ 'Davis',
+ 'Demers',
+ 'Desjardins',
+ 'Dion',
+ 'Dube',
+ 'Dubois',
+ 'Dufour',
+ 'Dupuis',
+ 'Edwards',
+ 'Elliott',
+ 'Ellis',
+ 'Evans',
+ 'Ferguson',
+ 'Fisher',
+ 'Fontaine',
+ 'Fortier',
+ 'Fortin',
+ 'Foster',
+ 'Fournier',
+ 'Fraser',
+ 'Friesen',
+ 'Gagne',
+ 'Gagnon',
+ 'Gallant',
+ 'Gauthier',
+ 'Gervais',
+ 'Gibson',
+ 'Gilbert',
+ 'Gill',
+ 'Girard',
+ 'Giroux',
+ 'Gordon',
+ 'Gosselin',
+ 'Graham',
+ 'Grant',
+ 'Gray',
+ 'Green',
+ 'Grenier',
+ 'Hall',
+ 'Hamel',
+ 'Hamilton',
+ 'Harris',
+ 'Harrison',
+ 'Harvey',
+ 'Hebert',
+ 'Henderson',
+ 'Henry',
+ 'Hill',
+ 'Ho',
+ 'Houle',
+ 'Hughes',
+ 'Hunt',
+ 'Hunter',
+ 'Jackson',
+ 'James',
+ 'Johnson',
+ 'Johnston',
+ 'Jones',
+ 'Kelly',
+ 'Kennedy',
+ 'Kerr',
+ 'Khan',
+ 'Kim',
+ 'King',
+ 'Klassen',
+ 'Labelle',
+ 'Lachance',
+ 'Lacroix',
+ 'Lalonde',
+ 'Lam',
+ 'Lambert',
+ 'Landry',
+ 'Langlois',
+ 'Lapointe',
+ 'Lavoie',
+ 'Leblanc',
+ 'Leclerc',
+ 'Leduc',
+ 'Lee',
+ 'Lefebvre',
+ 'Legault',
+ 'Lemay',
+ 'Lemieux',
+ 'Lessard',
+ 'Leung',
+ 'Levesque',
+ 'Lewis',
+ 'Li',
+ 'Liu',
+ 'Macdonald',
+ 'Mackenzie',
+ 'Maclean',
+ 'Macleod',
+ 'Marshall',
+ 'Martel',
+ 'Martin',
+ 'Mason',
+ 'Mcdonald',
+ 'Mckay',
+ 'Mclean',
+ 'Mcleod',
+ 'Menard',
+ 'Mercier',
+ 'Michaud',
+ 'Miller',
+ 'Mills',
+ 'Mitchell',
+ 'Moore',
+ 'Morgan',
+ 'Morin',
+ 'Morris',
+ 'Morrison',
+ 'Murphy',
+ 'Murray',
+ 'Nadeau',
+ 'Nelson',
+ 'Ng',
+ 'Nguyen',
+ 'Ouellet',
+ 'Ouellette',
+ 'Paquette',
+ 'Paradis',
+ 'Parent',
+ 'Park',
+ 'Parker',
+ 'Parsons',
+ 'Patel',
+ 'Patterson',
+ 'Paul',
+ 'Pelletier',
+ 'Perreault',
+ 'Perron',
+ 'Perry',
+ 'Peters',
+ 'Phillips',
+ 'Plante',
+ 'Poirier',
+ 'Poulin',
+ 'Proulx',
+ 'Raymond',
+ 'Reid',
+ 'Renaud',
+ 'Richard',
+ 'Richardson',
+ 'Robert',
+ 'Roberts',
+ 'Robertson',
+ 'Robinson',
+ 'Rogers',
+ 'Rose',
+ 'Ross',
+ 'Rousseau',
+ 'Roy',
+ 'Russell',
+ 'Ryan',
+ 'Savard',
+ 'Schmidt',
+ 'Scott',
+ 'Seguin',
+ 'Shaw',
+ 'Simard',
+ 'Simpson',
+ 'Singh',
+ 'Smith',
+ 'St-pierre',
+ 'Stevens',
+ 'Stewart',
+ 'Taylor',
+ 'Theriault',
+ 'Therrien',
+ 'Thibault',
+ 'Thomas',
+ 'Thompson',
+ 'Thomson',
+ 'Tran',
+ 'Tremblay',
+ 'Turcotte',
+ 'Turner',
+ 'Villeneuve',
+ 'Walker',
+ 'Wallace',
+ 'Walsh',
+ 'Wang',
+ 'Ward',
+ 'Watson',
+ 'White',
+ 'Wiebe',
+ 'Williams',
+ 'Wilson',
+ 'Wong',
+ 'Wood',
+ 'Wright',
+ 'Wu',
+ 'Young',
+ 'Yu',
+ 'Zhang'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Chile/bundle.ts b/packages/plugins/src/countries/Chile/bundle.ts
new file mode 100644
index 000000000..78c45d36e
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/bundle.ts
@@ -0,0 +1,455 @@
+/**
+ * Ciudades y Regiones de Chile
+ * Weight: Porcentaje de Población de la Región respecto al total nacional * 100, segun Censo 2012
+ * Source: http://datos.gob.cl/datasets/ver/29596
+ * Author: Gonzalo 'NioZero' Hidalgo
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Chile: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'chile',
+ regionNames: i18n.regionNames,
+ continent: 'south_america',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Arica y Parinacota',
+ regionShort: 'XV',
+ regionSlug: 'arica_y_parinacota',
+ weight: 132,
+ cities: ['Arica', 'Camarones', 'Putre', 'General Lagos']
+ },
+ {
+ regionName: 'Tarapacá',
+ regionShort: 'I',
+ regionSlug: 'tarapaca',
+ weight: 185,
+ cities: ['Iquique', 'Alto Hospicio', 'Pozo Almonte', 'Camiña', 'Colchane', 'Huara', 'Pica']
+ },
+ {
+ regionName: 'Antofagasta',
+ regionShort: 'II',
+ regionSlug: 'antofagasta',
+ weight: 344,
+ cities: [
+ 'Antofagasta',
+ 'Mejillones',
+ 'Sierra Gorda',
+ 'Taltal',
+ 'Calama',
+ 'Ollagüe',
+ 'San Pedro de Atacama',
+ 'Tocopilla',
+ 'María Elena'
+ ]
+ },
+ {
+ regionName: 'Atacama',
+ regionShort: 'III',
+ regionSlug: 'atacama',
+ weight: 173,
+ cities: ['Copiapó', 'Caldera', 'Tierra Amarilla', 'Chañaral', 'Diego de Almagro', 'Vallenar', 'Alto del Carmen', 'Freirina', 'Huasco']
+ },
+ {
+ regionName: 'Coquimbo',
+ regionShort: 'IV',
+ regionSlug: 'coquimbo',
+ weight: 426,
+ cities: [
+ 'La Serena',
+ 'Coquimbo',
+ 'Andacollo',
+ 'La Higuera',
+ 'Paiguano',
+ 'Vicuña',
+ 'Illapel',
+ 'Canela',
+ 'Los Vilos',
+ 'Salamanca',
+ 'Ovalle',
+ 'Combarbalá',
+ 'Monte Patria',
+ 'Punitaqui',
+ 'Río Hurtado'
+ ]
+ },
+ {
+ regionName: 'Valparaíso',
+ regionShort: 'V',
+ regionSlug: 'valparaiso',
+ weight: 1015,
+ cities: [
+ 'Valparaíso',
+ 'Casablanca',
+ 'Concón',
+ 'Juan Fernández',
+ 'Puchuncaví',
+ 'Quintero',
+ 'Viña del Mar',
+ 'Isla de Pascua',
+ 'Los Andes',
+ 'Calle Larga',
+ 'Rinconada',
+ 'San Esteban',
+ 'La Ligua',
+ 'Cabildo',
+ 'Papudo',
+ 'Petorca',
+ 'Zapallar',
+ 'Quillota',
+ 'Calera',
+ 'Hijuelas',
+ 'La Cruz',
+ 'Nogales',
+ 'San Antonio',
+ 'Algarrobo',
+ 'Cartagena',
+ 'El Quisco',
+ 'El Tabo',
+ 'Santo Domingo',
+ 'San Felipe',
+ 'Catemu',
+ 'Llaillay',
+ 'Panquehue',
+ 'Putaendo',
+ 'Santa María',
+ 'Quilpué',
+ 'Limache',
+ 'Olmué',
+ 'Villa Alemana'
+ ]
+ },
+ {
+ regionName: "O'Higgins",
+ regionShort: 'VI',
+ regionSlug: 'ohiggins',
+ weight: 511,
+ cities: [
+ 'Rancagua',
+ 'Codegua',
+ 'Coinco',
+ 'Coltauco',
+ 'Doñihue',
+ 'Graneros',
+ 'Las Cabras',
+ 'Machalí',
+ 'Malloa',
+ 'Mostazal',
+ 'Olivar',
+ 'Peumo',
+ 'Pichidegua',
+ 'Quinta de Tilcoco',
+ 'Rengo',
+ 'Requínoa',
+ 'San Vicente',
+ 'Pichilemu',
+ 'La Estrella',
+ 'Litueche',
+ 'Marchihue',
+ 'Navidad',
+ 'Paredones',
+ 'San Fernando',
+ 'Chépica',
+ 'Chimbarongo',
+ 'Lolol',
+ 'Nancagua',
+ 'Palmilla',
+ 'Peralillo',
+ 'Placilla',
+ 'Pumanque',
+ 'Santa Cruz'
+ ]
+ },
+ {
+ regionName: 'Maule',
+ regionShort: 'VII',
+ regionSlug: 'maule',
+ weight: 581,
+ cities: [
+ 'Talca',
+ 'Constitución',
+ 'Curepto',
+ 'Empedrado',
+ 'Maule',
+ 'Pelarco',
+ 'Pencahue',
+ 'Río Claro',
+ 'San Clemente',
+ 'San Rafael',
+ 'Cauquenes',
+ 'Chanco',
+ 'Pelluhue',
+ 'Curicó',
+ 'Hualañé',
+ 'Licantén',
+ 'Molina',
+ 'Rauco',
+ 'Romeral',
+ 'Sagrada Familia',
+ 'Teno',
+ 'Vichuquén',
+ 'Linares',
+ 'Colbún',
+ 'Longaví',
+ 'Parral',
+ 'Retiro',
+ 'San Javier',
+ 'Villa Alegre',
+ 'Yerbas Buenas'
+ ]
+ },
+ {
+ regionName: 'Biobío',
+ regionShort: 'VII',
+ regionSlug: 'biobio',
+ weight: 1179,
+ cities: [
+ 'Concepción',
+ 'Coronel',
+ 'Chiguayante',
+ 'Florida',
+ 'Hualqui',
+ 'Lota',
+ 'Penco',
+ 'San Pedro de la Paz',
+ 'Santa Juana',
+ 'Talcahuano',
+ 'Tomé',
+ 'Hualpén',
+ 'Lebu',
+ 'Arauco',
+ 'Cañete',
+ 'Contulmo',
+ 'Curanilahue',
+ 'Los Álamos',
+ 'Tirúa',
+ 'Los Ángeles',
+ 'Antuco',
+ 'Cabrero',
+ 'Laja',
+ 'Mulchén',
+ 'Nacimiento',
+ 'Negrete',
+ 'Quilaco',
+ 'Quilleco',
+ 'San Rosendo',
+ 'Santa Bárbara',
+ 'Tucapel',
+ 'Yumbel',
+ 'Alto Biobío',
+ 'Chillán',
+ 'Bulnes',
+ 'Cobquecura',
+ 'Coelemu',
+ 'Coihueco',
+ 'Chillán Viejo',
+ 'El Carmen',
+ 'Ninhue',
+ 'Ñiquén',
+ 'Pemuco',
+ 'Pinto',
+ 'Portezuelo',
+ 'Quillón',
+ 'Quirihue',
+ 'Ránquil',
+ 'San Carlos',
+ 'San Fabián',
+ 'San Ignacio',
+ 'San Nicolás',
+ 'Treguaco',
+ 'Yungay'
+ ]
+ },
+ {
+ regionName: 'Araucanía',
+ regionShort: 'IX',
+ regionSlug: 'araucania',
+ weight: 552,
+ cities: [
+ 'Temuco',
+ 'Carahue',
+ 'Cunco',
+ 'Curarrehue',
+ 'Freire',
+ 'Galvarino',
+ 'Gorbea',
+ 'Lautaro',
+ 'Loncoche',
+ 'Melipeuco',
+ 'Nueva Imperial',
+ 'Padre las Casas',
+ 'Perquenco',
+ 'Pitrufquén',
+ 'Pucón',
+ 'Saavedra',
+ 'Teodoro Schmidt',
+ 'Toltén',
+ 'Vilcún',
+ 'Villarrica',
+ 'Cholchol',
+ 'Angol',
+ 'Collipulli',
+ 'Curacautín',
+ 'Ercilla',
+ 'Lonquimay',
+ 'Los Sauces',
+ 'Lumaco',
+ 'Purén',
+ 'Renaico',
+ 'Traiguén',
+ 'Victoria'
+ ]
+ },
+ {
+ regionName: 'Los Ríos',
+ regionShort: 'XIV',
+ regionSlug: 'los_rios',
+ weight: 225,
+ cities: [
+ 'Valdivia',
+ 'Corral',
+ 'Lanco',
+ 'Los Lagos',
+ 'Máfil',
+ 'Mariquina',
+ 'Paillaco',
+ 'Panguipulli',
+ 'La Unión',
+ 'Futrono',
+ 'Lago Ranco',
+ 'Río Bueno'
+ ]
+ },
+ {
+ regionName: 'Los Lagos',
+ regionShort: 'X',
+ regionSlug: 'los_lagos',
+ weight: 468,
+ cities: [
+ 'Puerto Montt',
+ 'Calbuco',
+ 'Cochamó',
+ 'Fresia',
+ 'Frutillar',
+ 'Los Muermos',
+ 'Llanquihue',
+ 'Maullín',
+ 'Puerto Varas',
+ 'Castro',
+ 'Ancud',
+ 'Chonchi',
+ 'Curaco de Vélez',
+ 'Dalcahue',
+ 'Puqueldón',
+ 'Queilén',
+ 'Quellón',
+ 'Quemchi',
+ 'Quinchao',
+ 'Osorno',
+ 'Puerto Octay',
+ 'Purranque',
+ 'Puyehue',
+ 'Río Negro',
+ 'San Juan de la Costa',
+ 'San Pablo',
+ 'Chaitén',
+ 'Futaleufú',
+ 'Hualaihué',
+ 'Palena'
+ ]
+ },
+ {
+ regionName: 'Aisén',
+ regionShort: 'XIV',
+ regionSlug: 'aisen',
+ weight: 60,
+ cities: ['Coihaique', 'Lago Verde', 'Aisén', 'Cisnes', 'Guaitecas', 'Cochrane', "O'Higgins", 'Tortel', 'Chile Chico', 'Río Ibáñez']
+ },
+ {
+ regionName: 'Magallanes y Antártica Chilena',
+ regionShort: 'XII',
+ regionSlug: 'magallanes_y_antartica_chilena',
+ weight: 92,
+ cities: [
+ 'Punta Arenas',
+ 'Laguna Blanca',
+ 'Río Verde',
+ 'San Gregorio',
+ 'Cabo de Hornos',
+ 'Antártica',
+ 'Porvenir',
+ 'Primavera',
+ 'Timaukel',
+ 'Natales',
+ 'Torres del Paine'
+ ]
+ },
+ {
+ regionName: 'Metropolitana de Santiago',
+ regionShort: 'RM',
+ regionSlug: 'metropolitana_de_santiago',
+ weight: 4057,
+ cities: [
+ 'Santiago',
+ 'Cerrillos',
+ 'Cerro Navia',
+ 'Conchalí',
+ 'El Bosque',
+ 'Estación Central',
+ 'Huechuraba',
+ 'Independencia',
+ 'La Cisterna',
+ 'La Florida',
+ 'La Granja',
+ 'La Pintana',
+ 'La Reina',
+ 'Las Condes',
+ 'Lo Barnechea',
+ 'Lo Espejo',
+ 'Lo Prado',
+ 'Macul',
+ 'Maipú',
+ 'Ñuñoa',
+ 'Pedro Aguirre Cerda',
+ 'Peñalolén',
+ 'Providencia',
+ 'Pudahuel',
+ 'Quilicura',
+ 'Quinta Normal',
+ 'Recoleta',
+ 'Renca',
+ 'San Joaquín',
+ 'San Miguel',
+ 'San Ramón',
+ 'Vitacura',
+ 'Puente Alto',
+ 'Pirque',
+ 'San José de Maipo',
+ 'Colina',
+ 'Lampa',
+ 'Tiltil',
+ 'San Bernardo',
+ 'Buin',
+ 'Calera de Tango',
+ 'Paine',
+ 'Melipilla',
+ 'Alhué',
+ 'Curacaví',
+ 'María Pinto',
+ 'San Pedro',
+ 'Talagante',
+ 'El Monte',
+ 'Isla de Maipo',
+ 'Padre Hurtado',
+ 'Peñaflor'
+ ]
+ }
+ ]
+});
+
+export default Chile;
diff --git a/packages/plugins/src/countries/Chile/i18n/ar.json b/packages/plugins/src/countries/Chile/i18n/ar.json
new file mode 100644
index 000000000..ee5767c5f
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "تشيلي",
+ "regionNames": "المناطق التشيلية"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/de.json b/packages/plugins/src/countries/Chile/i18n/de.json
new file mode 100644
index 000000000..2ace48bd2
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Chile",
+ "regionNames": "Chilenische Regionen"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/en.json b/packages/plugins/src/countries/Chile/i18n/en.json
new file mode 100644
index 000000000..85ea71dc5
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Chile",
+ "regionNames": "Chilean Regions"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/es.json b/packages/plugins/src/countries/Chile/i18n/es.json
new file mode 100644
index 000000000..2c031ca99
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Chile",
+ "regionNames": "regiones chilenas"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/fr.json b/packages/plugins/src/countries/Chile/i18n/fr.json
new file mode 100644
index 000000000..ffdb0689e
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Chili",
+ "regionNames": "Régions chiliennes"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/hi.json b/packages/plugins/src/countries/Chile/i18n/hi.json
new file mode 100644
index 000000000..4b0aa6104
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "चिली",
+ "regionNames": "चिली क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/ja.json b/packages/plugins/src/countries/Chile/i18n/ja.json
new file mode 100644
index 000000000..93997c1ee
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "チリ",
+ "regionNames": "チリの地域"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/nl.json b/packages/plugins/src/countries/Chile/i18n/nl.json
new file mode 100644
index 000000000..3da9ebaf2
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Chili",
+ "regionNames": "Chileense regio's"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/pt.json b/packages/plugins/src/countries/Chile/i18n/pt.json
new file mode 100644
index 000000000..acd1aaaec
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Chile",
+ "regionNames": "Regiões chilenas"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/ru.json b/packages/plugins/src/countries/Chile/i18n/ru.json
new file mode 100644
index 000000000..b33f7d5ca
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Чили",
+ "regionNames": "Чилийские регионы"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/ta.json b/packages/plugins/src/countries/Chile/i18n/ta.json
new file mode 100644
index 000000000..fc0fb33f6
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "சிலி",
+ "regionNames": "சிலி பிராந்தியங்கள்"
+}
diff --git a/packages/plugins/src/countries/Chile/i18n/zh.json b/packages/plugins/src/countries/Chile/i18n/zh.json
new file mode 100644
index 000000000..6c3aa1810
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "智利",
+ "regionNames": "智利地区"
+}
diff --git a/packages/plugins/src/countries/Chile/names.ts b/packages/plugins/src/countries/Chile/names.ts
new file mode 100644
index 000000000..512fb2793
--- /dev/null
+++ b/packages/plugins/src/countries/Chile/names.ts
@@ -0,0 +1,319 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Abigail',
+ 'Agustina',
+ 'Ainhoa',
+ 'Alice',
+ 'Alison',
+ 'Alondra',
+ 'Amalia',
+ 'Amanda',
+ 'Amelia',
+ 'Amparo',
+ 'Amy',
+ 'Anaís',
+ 'Angela',
+ 'Antonella',
+ 'Antonia',
+ 'Ashley',
+ 'Aurora',
+ 'Aylin',
+ 'Belén',
+ 'Bárbara',
+ 'Camila',
+ 'Carla',
+ 'Carolina',
+ 'Catalina',
+ 'Celeste',
+ 'Colomba',
+ 'Constanza',
+ 'Consuelo',
+ 'Danae',
+ 'Daniela',
+ 'Dominga',
+ 'Dominique',
+ 'Elena',
+ 'Elisa',
+ 'Elizabeth',
+ 'Eloísa',
+ 'Ema',
+ 'Emilia',
+ 'Emily',
+ 'Emma',
+ 'Esperanza',
+ 'Fernanda',
+ 'Florencia',
+ 'Francisca',
+ 'Gabriela',
+ 'Génesis',
+ 'Ignacia',
+ 'Isabel',
+ 'Isabella',
+ 'Isidora',
+ 'Javiera',
+ 'Josefa',
+ 'Josefina',
+ 'Julieta',
+ 'Laura',
+ 'Leonor',
+ 'Luciana',
+ 'Lucía',
+ 'Luna',
+ 'Lía',
+ 'Magdalena',
+ 'Maite',
+ 'Mariana',
+ 'Martina',
+ 'María',
+ 'Matilda',
+ 'Matilde',
+ 'Mayra',
+ 'Mayte',
+ 'Mia',
+ 'Millaray',
+ 'Monserrat',
+ 'Montserrat',
+ 'Noelia',
+ 'Noemí',
+ 'Olivia',
+ 'Paloma',
+ 'Pascal',
+ 'Pascale',
+ 'Paula',
+ 'Paz',
+ 'Pía',
+ 'Rafaela',
+ 'Rayén',
+ 'Renata',
+ 'Rocío',
+ 'Samantha',
+ 'Sara',
+ 'Sofía',
+ 'Sophia',
+ 'Trinidad',
+ 'Valentina',
+ 'Victoria',
+ 'Violeta',
+ 'Ámbar'
+];
+
+const maleNames = [
+ 'Aaron',
+ 'Agustín',
+ 'Alan',
+ 'Alejandro',
+ 'Alex',
+ 'Alexander',
+ 'Alexis',
+ 'Alfonso',
+ 'Alonso',
+ 'Amaro',
+ 'Andrés',
+ 'Angel',
+ 'Antonio',
+ 'Arturo',
+ 'Bastián',
+ 'Benjamín',
+ 'Bruno',
+ 'Camilo',
+ 'Carlos',
+ 'Claudio',
+ 'Clemente',
+ 'Cristian',
+ 'Cristóbal',
+ 'Damián',
+ 'Daniel',
+ 'Dante',
+ 'David',
+ 'Diego',
+ 'Dylan',
+ 'Eduardo',
+ 'Elías',
+ 'Emiliano',
+ 'Emilio',
+ 'Esteban',
+ 'Fabián',
+ 'Facundo',
+ 'Felipe',
+ 'Fernando',
+ 'Francisco',
+ 'Franco',
+ 'Gabriel',
+ 'Gaspar',
+ 'Gonzalo',
+ 'Guillermo',
+ 'Gustavo',
+ 'Héctor',
+ 'Ian',
+ 'Ignacio',
+ 'Isaac',
+ 'Isaias',
+ 'Javier',
+ 'Jean',
+ 'Jesús',
+ 'Joaquín',
+ 'Jorge',
+ 'Josue',
+ 'José',
+ 'Juan',
+ 'Julián',
+ 'Kevin',
+ 'Leonardo',
+ 'León',
+ 'Liam',
+ 'Lucas',
+ 'Luciano',
+ 'Luis',
+ 'Lukas',
+ 'Manuel',
+ 'Marcelo',
+ 'Mariano',
+ 'Martín',
+ 'Mateo',
+ 'Mathias',
+ 'Matías',
+ 'Maximiliano',
+ 'Miguel',
+ 'Máximo',
+ 'Nicolás',
+ 'Oscar',
+ 'Pablo',
+ 'Patricio',
+ 'Pedro',
+ 'Rafael',
+ 'Renato',
+ 'Ricardo',
+ 'Rodrigo',
+ 'Salvador',
+ 'Samuel',
+ 'Santiago',
+ 'Sebastián',
+ 'Sergio',
+ 'Simón',
+ 'Thomas',
+ 'Tomás',
+ 'Valentín',
+ 'Vicente',
+ 'Víctor',
+ 'Álvaro'
+];
+
+const lastNames = [
+ 'Alexandra',
+ 'Alonsos',
+ 'Alvarez',
+ 'Araya',
+ 'Atlas',
+ 'Augustin',
+ 'Azizi',
+ 'Barbara',
+ 'Bastian',
+ 'Benjamin',
+ 'Bentlee',
+ 'Bravo',
+ 'Camila',
+ 'Carla',
+ 'Carolina',
+ 'Carrasco',
+ 'Castillo',
+ 'Castro',
+ 'Catalina',
+ 'Chichi',
+ 'Contreras',
+ 'Cortes',
+ 'Cristobal',
+ 'Daniel',
+ 'Diaz',
+ 'Diego',
+ 'Diem',
+ 'Emilia',
+ 'Espinoza',
+ 'Felipe',
+ 'Fernanda',
+ 'Fernandez',
+ 'Figueroa',
+ 'Florencia',
+ 'Flores',
+ 'Francisco',
+ 'Fuentes',
+ 'Gabriel',
+ 'Gabriela',
+ 'Garcia',
+ 'Gomez',
+ 'Gonzalez',
+ 'Gutierrez',
+ 'Hernandez',
+ 'Herrera',
+ 'Ignacio',
+ 'Isabella',
+ 'Jara',
+ 'Javier',
+ 'Joaquin',
+ 'Jose',
+ 'Juan',
+ 'Julieta',
+ 'Laura',
+ 'Lopez',
+ 'Luis',
+ 'Magdalena',
+ 'Maria',
+ 'Martin',
+ 'Martina',
+ 'Martinez',
+ 'Mateo',
+ 'Matias',
+ 'Matilde',
+ 'Maximiliano',
+ 'Maximo',
+ 'Miranda',
+ 'Monserrat',
+ 'Morales',
+ 'Munoz',
+ 'Nicolas',
+ 'Nunez',
+ 'Pascal',
+ 'Paula',
+ 'Paz',
+ 'Perez',
+ 'Pia',
+ 'Ramirez',
+ 'Renato',
+ 'Reyes',
+ 'Rivera',
+ 'Rocio',
+ 'Rodriguez',
+ 'Rojas',
+ 'Sanchez',
+ 'Sebastian',
+ 'Sepulveda',
+ 'Silva',
+ 'Sofia',
+ 'Soto',
+ 'Soto',
+ 'Tapia',
+ 'Testudines',
+ 'Thiarre',
+ 'Tomas',
+ 'Torres',
+ 'Trinidad',
+ 'Valentina',
+ 'Valenzuela',
+ 'Vargas',
+ 'Vasquez',
+ 'Vega',
+ 'Vera',
+ 'Vergara',
+ 'Vicente',
+ 'Victoria',
+ 'Zavala',
+ 'Zuniga'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/China/bundle.ts b/packages/plugins/src/countries/China/bundle.ts
new file mode 100644
index 000000000..839de72cd
--- /dev/null
+++ b/packages/plugins/src/countries/China/bundle.ts
@@ -0,0 +1,62 @@
+import { GetCountryData } from '@generatedata/types';
+
+const China: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'china',
+ regionNames: i18n.regionNames,
+ continent: 'asia',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['0xxx xxxx xxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: i18n.regionNorthChina,
+ regionShort: i18n.regionNorthChina,
+ regionSlug: 'north_china',
+ weight: 164,
+ cities: ['Beijing', 'Tianjin', 'Hebei', 'Shanxi', 'Inner Mongolia']
+ },
+ {
+ regionName: i18n.regionNortheastChina,
+ regionShort: i18n.regionNortheastChina,
+ regionSlug: 'northeast_china',
+ weight: 109,
+ cities: ['Liaoning', 'Jilin', 'Heilongjiang']
+ },
+ {
+ regionName: i18n.regionEastChina,
+ regionShort: i18n.regionEastChina,
+ regionSlug: 'east_china',
+ weight: 384,
+ cities: ['Shanghai', 'Jiangsu', 'Zhejiang', 'Anhui', 'Fujian', 'Jiangxi', 'Shandong']
+ },
+ {
+ regionName: i18n.regionSouthCentralChina,
+ regionShort: i18n.regionSouthCentralChina,
+ regionSlug: 'south_central_china',
+ weight: 384,
+ cities: ['Henan', 'Hubei', 'Hunan', 'Guangdong', 'Guangxi', 'Hainan', 'Hong Kong', 'Macau']
+ },
+ {
+ regionName: i18n.regionSouthwestChina,
+ regionShort: i18n.regionSouthwestChina,
+ regionSlug: 'southwest_china',
+ weight: 192,
+ cities: ['Chongqing', 'Sichuan', 'Guizhou', 'Yunnan', 'Tibet']
+ },
+ {
+ regionName: i18n.regionNorthwestChina,
+ regionShort: i18n.regionNorthwestChina,
+ regionSlug: 'northwest_china',
+ weight: 96,
+ cities: ['Shaanxi', 'Gansu', 'Qinghai', 'Ningxia', 'Xinjiang']
+ }
+ ]
+});
+
+export default China;
diff --git a/packages/plugins/src/countries/China/i18n/ar.json b/packages/plugins/src/countries/China/i18n/ar.json
new file mode 100644
index 000000000..3568d76ff
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/ar.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "الصين",
+ "regionNames": "مناطق الصين",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/de.json b/packages/plugins/src/countries/China/i18n/de.json
new file mode 100644
index 000000000..7028c9201
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/de.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "China",
+ "regionNames": "China-Regionen",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/en.json b/packages/plugins/src/countries/China/i18n/en.json
new file mode 100644
index 000000000..42b44c306
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/en.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "China",
+ "regionNames": "China regions",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/es.json b/packages/plugins/src/countries/China/i18n/es.json
new file mode 100644
index 000000000..0181a8d1c
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/es.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "China",
+ "regionNames": "Regiones de China",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/fr.json b/packages/plugins/src/countries/China/i18n/fr.json
new file mode 100644
index 000000000..079cc0408
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/fr.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "Chine",
+ "regionNames": "Régions de la Chine",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/hi.json b/packages/plugins/src/countries/China/i18n/hi.json
new file mode 100644
index 000000000..74a4545f5
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/hi.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "चीन",
+ "regionNames": "चीन क्षेत्र",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/ja.json b/packages/plugins/src/countries/China/i18n/ja.json
new file mode 100644
index 000000000..ddcfd7f00
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/ja.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "中国",
+ "regionNames": "中国地域",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/nl.json b/packages/plugins/src/countries/China/i18n/nl.json
new file mode 100644
index 000000000..129cedab4
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/nl.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "China",
+ "regionNames": "Chinese regio's:",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/pt.json b/packages/plugins/src/countries/China/i18n/pt.json
new file mode 100644
index 000000000..c449cf122
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/pt.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "China",
+ "regionNames": "Regiões da China",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/ru.json b/packages/plugins/src/countries/China/i18n/ru.json
new file mode 100644
index 000000000..9ddd2faef
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/ru.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "Китай",
+ "regionNames": "Китайские регионы",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/ta.json b/packages/plugins/src/countries/China/i18n/ta.json
new file mode 100644
index 000000000..78dd6e6a4
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/ta.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "சீனா",
+ "regionNames": "சீனா பிராந்தியங்கள்",
+ "regionNorthChina": "Huáběi",
+ "regionNortheastChina": "Dōngběi",
+ "regionEastChina": "Huádōng",
+ "regionSouthCentralChina": "Zhōngnán",
+ "regionSouthwestChina": "Xīnán",
+ "regionNorthwestChina": "Xīběi"
+}
diff --git a/packages/plugins/src/countries/China/i18n/zh.json b/packages/plugins/src/countries/China/i18n/zh.json
new file mode 100644
index 000000000..5bd0a9537
--- /dev/null
+++ b/packages/plugins/src/countries/China/i18n/zh.json
@@ -0,0 +1,10 @@
+{
+ "countryName": "中国",
+ "regionNames": "中国地区",
+ "regionNorthChina": "华北",
+ "regionNortheastChina": "东北",
+ "regionEastChina": "华东",
+ "regionSouthCentralChina": "中南",
+ "regionSouthwestChina": "西南",
+ "regionNorthwestChina": "西北"
+}
diff --git a/packages/plugins/src/countries/China/names.ts b/packages/plugins/src/countries/China/names.ts
new file mode 100644
index 000000000..cc74174b8
--- /dev/null
+++ b/packages/plugins/src/countries/China/names.ts
@@ -0,0 +1,648 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Ai',
+ 'An',
+ 'Bao',
+ 'Chen',
+ 'Chun',
+ 'Hui',
+ 'Jia',
+ 'Jun',
+ 'Kai',
+ 'Li Hua',
+ 'Li Mei',
+ 'Ling',
+ 'Mei',
+ 'Ning',
+ 'Nuan',
+ 'Qing',
+ 'Shu',
+ 'Xue',
+ 'Yan',
+ 'Zhen',
+ 'Chu',
+ 'Cong',
+ 'Genji',
+ 'Hop',
+ 'Hua',
+ 'Lai',
+ 'Mey',
+ 'Qiao',
+ 'Qing yuan',
+ 'Qinyang',
+ 'Ushi',
+ 'Wenling',
+ 'Xin-jin',
+ 'Yanay',
+ 'Ching',
+ 'Cixi',
+ 'Fang',
+ 'Fu',
+ 'Jiang',
+ 'Liang',
+ 'Lu',
+ 'Qiu',
+ 'Su',
+ 'Tai',
+ 'Wang Shu;',
+ 'Wei',
+ 'Wu',
+ 'Yun',
+ 'Zhi Rou',
+ 'Diu',
+ 'Fen',
+ 'Hanna',
+ 'Huan',
+ 'Huang',
+ 'Lian',
+ 'Ling',
+ 'Ming',
+ 'Ming Yue',
+ 'Peng',
+ 'Ping',
+ 'Xiu',
+ 'Xiulin',
+ 'Yu-yan',
+ 'Qiuyue',
+ 'Ying Yue',
+ 'Yong',
+ 'Yue',
+ 'Yuèhai',
+ 'Yuèqín',
+ 'Liena',
+ 'Lifen',
+ 'Lì húa',
+ 'Lihua',
+ 'Lijuan',
+ 'Liling',
+ 'LiMei',
+ 'Li mei',
+ 'Li ming',
+ 'Lin',
+ 'Li Na',
+ 'Linqin',
+ 'Li Qin',
+ 'Liqiu',
+ 'Li Rong',
+ 'Li Wei',
+ 'Mayleen',
+ 'Méh-è',
+ 'Méh-fùnh',
+ 'Meifen',
+ 'Meifeng',
+ 'Meihui',
+ 'Mei Lien',
+ 'Mei Xiang',
+ 'Mei Xing',
+ 'Meiyin',
+ 'Mei Zhen',
+ 'Mingmei',
+ 'Rong',
+ 'Wan',
+ 'Xin',
+ 'Qian'
+];
+
+const maleNames = [
+ 'Aiguo',
+ 'An',
+ 'Bai',
+ 'Bambang',
+ 'Bengt',
+ 'Biming',
+ 'Bingwen',
+ 'Bo',
+ 'Bojing',
+ 'Boqin',
+ 'Chang ',
+ 'Changpu',
+ 'Chao',
+ 'Chaun',
+ 'Chen',
+ 'Cheng',
+ 'Cheung',
+ 'Chi',
+ 'Chuanli',
+ 'Chun',
+ 'Chung',
+ 'Da',
+ 'Deli',
+ 'Deming',
+ 'Dingbang',
+ 'Dong',
+ 'Duyi',
+ 'Fai',
+ 'Feng',
+ 'Fu',
+ 'Gan',
+ 'Gang',
+ 'Gen',
+ 'Guang',
+ 'Gui',
+ 'Guotin',
+ 'Hai',
+ 'Hao',
+ 'He',
+ 'Heng',
+ 'Hong',
+ 'Hop',
+ 'Hou',
+ 'Hua',
+ 'Huan',
+ 'Hui',
+ 'Jia',
+ 'Jian',
+ 'Jianyu',
+ 'Jie',
+ 'Jig',
+ 'Jin',
+ 'Jinhai',
+ 'Jun',
+ 'Kang',
+ 'Kong',
+ 'Kun',
+ 'Kuo',
+ 'Lei',
+ 'Li',
+ 'Li jun',
+ 'Li qiang',
+ 'Li wei',
+ 'Liang',
+ 'Lie jie',
+ 'Ling',
+ 'Liu',
+ 'Lok',
+ 'Manchu',
+ 'Mingli',
+ 'Mingyu',
+ 'Minsheng',
+ 'Ning',
+ 'On',
+ 'Park',
+ 'Peizhi',
+ 'Ping',
+ 'Qi',
+ 'Qiang',
+ 'Qiao',
+ 'Qing',
+ 'Qiu',
+ 'Qu',
+ 'Quon',
+ 'Ru',
+ 'Shan',
+ 'Shen',
+ 'Shilin',
+ 'Shing',
+ 'Shoi-ming',
+ 'Shui',
+ 'Song',
+ 'Susu',
+ 'Sying',
+ 'Tai-hua',
+ 'Taio',
+ 'Tao',
+ 'Tengfei',
+ 'Tu',
+ 'Tung',
+ 'Wang',
+ 'Wang lei',
+ 'Wang wei',
+ 'Wang yong',
+ 'Wei',
+ 'Weiyuan',
+ 'Weizhe',
+ 'Wen',
+ 'Wenyan',
+ 'Xiang',
+ 'Xin',
+ 'Xing',
+ 'Xue',
+ 'Yang',
+ 'Yanlin',
+ 'Ye',
+ 'Yi',
+ 'Ying',
+ 'Yingjie',
+ 'Yingpei',
+ 'Yong',
+ 'Yongrui',
+ 'You',
+ 'Yuanjun',
+ 'Zhixin',
+ 'Zhong',
+ 'Zhou',
+ 'Zian',
+ 'Zihao'
+];
+
+const lastNames = [
+ '王',
+ '李',
+ '张',
+ '刘',
+ '陈',
+ '杨',
+ '黄',
+ '吴',
+ '赵',
+ '周',
+ '徐',
+ '孙',
+ '马',
+ '朱',
+ '胡',
+ '林',
+ '郭',
+ '何',
+ '高',
+ '罗',
+ '郑',
+ '梁',
+ '谢',
+ '宋',
+ '唐',
+ '许',
+ '邓',
+ '冯',
+ '韩',
+ '曹',
+ '曾',
+ '彭',
+ '萧',
+ '蔡',
+ '潘',
+ '田',
+ '董',
+ '袁',
+ '于',
+ '余',
+ '叶',
+ '蒋',
+ '杜',
+ '苏',
+ '魏',
+ '程',
+ '吕',
+ '丁',
+ '沈',
+ '任',
+ '姚',
+ '卢',
+ '傅',
+ '钟',
+ '姜',
+ '崔',
+ '谭',
+ '廖',
+ '范',
+ '汪',
+ '陆',
+ '金',
+ '石',
+ '戴',
+ '贾',
+ '韦',
+ '夏',
+ '邱',
+ '方',
+ '侯',
+ '邹',
+ '熊',
+ '孟',
+ '秦',
+ '白',
+ '江',
+ '阎',
+ '薛',
+ '尹',
+ '段',
+ '雷',
+ '黎',
+ '史',
+ '龙',
+ '陶',
+ '贺',
+ '顾',
+ '毛',
+ '郝',
+ '龚',
+ '邵',
+ '万',
+ '钱',
+ '严',
+ '赖',
+ '覃',
+ '洪',
+ '武',
+ '莫',
+ '孔',
+ '汤',
+ '向',
+ '常',
+ '温',
+ '康',
+ '施',
+ '文',
+ '牛',
+ '樊',
+ '葛',
+ '邢',
+ '安',
+ '齐',
+ '易',
+ '乔',
+ '伍',
+ '庞',
+ '颜',
+ '倪',
+ '庄',
+ '聂',
+ '章',
+ '鲁',
+ '岳',
+ '翟',
+ '殷',
+ '詹',
+ '申',
+ '欧',
+ '耿',
+ '关',
+ '兰',
+ '焦',
+ '俞',
+ '左',
+ '柳',
+ '甘',
+ '祝',
+ '包',
+ '宁',
+ '尚',
+ '符',
+ '舒',
+ '阮',
+ '柯',
+ '纪',
+ '梅',
+ '童',
+ '凌',
+ '毕',
+ '单',
+ '季',
+ '裴',
+ '霍',
+ '涂',
+ '成',
+ '苗',
+ '谷',
+ '盛',
+ '曲',
+ '翁',
+ '冉',
+ '骆',
+ '蓝',
+ '路',
+ '游',
+ '辛',
+ '靳',
+ '欧阳',
+ '管',
+ '柴',
+ '蒙',
+ '鲍',
+ '华',
+ '喻',
+ '祁',
+ '蒲',
+ '房',
+ '滕',
+ '屈',
+ '饶',
+ '解',
+ '牟',
+ '艾',
+ '尤',
+ '阳',
+ '时',
+ '穆',
+ '农',
+ '司',
+ '卓',
+ '古',
+ '吉',
+ '缪',
+ '简',
+ '车',
+ '项',
+ '连',
+ '芦',
+ '麦',
+ '褚',
+ '娄',
+ '窦',
+ '戚',
+ '岑',
+ '景',
+ '党',
+ '宫',
+ '费',
+ '卜',
+ '冷',
+ '晏',
+ '席',
+ '卫',
+ '米',
+ '柏',
+ '宗',
+ '瞿',
+ '桂',
+ '全',
+ '佟',
+ '应',
+ '臧',
+ '闵',
+ '苟',
+ '邬',
+ '边',
+ '卞',
+ '姬',
+ '师',
+ '和',
+ '仇',
+ '栾',
+ '隋',
+ '商',
+ '刁',
+ '沙',
+ '荣',
+ '巫',
+ '寇',
+ '桑',
+ '郎',
+ '甄',
+ '丛',
+ '仲',
+ '虞',
+ '敖',
+ '巩',
+ '明',
+ '佘',
+ '池',
+ '查',
+ '麻',
+ '苑',
+ '迟',
+ '邝',
+ '官',
+ '封',
+ '谈',
+ '匡',
+ '鞠',
+ '惠',
+ '荆',
+ '乐',
+ '冀',
+ '郁',
+ '胥',
+ '南',
+ '班',
+ '储',
+ '原',
+ '栗',
+ '燕',
+ '楚',
+ '鄢',
+ '劳',
+ '谌',
+ '奚',
+ '皮',
+ '粟',
+ '冼',
+ '蔺',
+ '楼',
+ '盘',
+ '满',
+ '闻',
+ '位',
+ '厉',
+ '伊',
+ '仝',
+ '区',
+ '郜',
+ '海',
+ '阚',
+ '花',
+ '权',
+ '强',
+ '帅',
+ '屠',
+ '豆',
+ '朴',
+ '盖',
+ '练',
+ '廉',
+ '禹',
+ '井',
+ '祖',
+ '漆',
+ '巴',
+ '丰',
+ '支',
+ '卿',
+ '国',
+ '狄',
+ '平',
+ '计',
+ '索',
+ '宣',
+ '晋',
+ '相',
+ '初',
+ '门',
+ '雲',
+ '容',
+ '敬',
+ '来',
+ '扈',
+ '晁',
+ '芮',
+ '都',
+ '普',
+ '阙',
+ '浦',
+ '戈',
+ '伏',
+ '鹿',
+ '薄',
+ '邸',
+ '雍',
+ '辜',
+ '羊',
+ '阿',
+ '乌',
+ '母',
+ '裘',
+ '亓',
+ '修',
+ '邰',
+ '赫',
+ '杭',
+ '况',
+ '那',
+ '宿',
+ '鲜',
+ '印',
+ '逯',
+ '隆',
+ '茹',
+ '诸',
+ '战',
+ '慕',
+ '危',
+ '玉',
+ '银',
+ '亢',
+ '嵇',
+ '公',
+ '哈',
+ '湛',
+ '宾',
+ '戎',
+ '勾',
+ '茅',
+ '利',
+ '於',
+ '呼',
+ '居',
+ '揭',
+ '干',
+ '但',
+ '尉',
+ '冶',
+ '斯',
+ '元',
+ '束',
+ '檀',
+ '衣',
+ '信',
+ '展',
+ '阴',
+ '昝',
+ '智',
+ '幸',
+ '奉',
+ '植',
+ '衡',
+ '富',
+ '尧',
+ '闭',
+ '由'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Colombia/bundle.ts b/packages/plugins/src/countries/Colombia/bundle.ts
new file mode 100644
index 000000000..eec58fc7b
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/bundle.ts
@@ -0,0 +1,257 @@
+/**
+ * @package Countries
+ *
+ * Source data:
+ * https://en.wikipedia.org/wiki/List_of_Colombian_Departments_by_population
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Colombia: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'colombia',
+ regionNames: i18n.regionNames,
+ continent: 'south_america',
+ extendedData: {
+ zipFormat: {
+ format: 'XXXXXX'
+ },
+ phoneFormat: {
+ displayFormats: ['0xx-xxx-xxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Amazonas',
+ regionShort: 'AMA',
+ regionSlug: 'amazonas',
+ weight: 5,
+ cities: ['Leticia', 'Puerto Nariño']
+ },
+ {
+ regionName: 'Antioquia',
+ regionShort: 'ANT',
+ regionSlug: 'antioquia',
+ weight: 560,
+ cities: ['Medellín', 'Bello', 'Itagüí', 'Envigado', 'Apartadó', 'Turbo', 'Rionegro']
+ },
+ {
+ regionName: 'Arauca',
+ regionShort: 'ARA',
+ regionSlug: 'arauca',
+ weight: 15,
+ cities: ['Arauca', 'Tame', 'Saravena']
+ },
+ {
+ regionName: 'Atlántico',
+ regionShort: 'ATL',
+ regionSlug: 'atlantico',
+ weight: 211,
+ cities: ['Barranquilla', 'Soledad', 'Malambo', 'Sabanalarga']
+ },
+ {
+ regionName: 'Bolívar',
+ regionShort: 'BOL',
+ regionSlug: 'bolivar',
+ weight: 210,
+ cities: ['Cartagena', 'Magangué', 'Carmen de Bolivar']
+ },
+ {
+ regionName: 'Boyacá',
+ regionShort: 'BOY',
+ regionSlug: 'boyaca',
+ weight: 3,
+ cities: ['Tunja', 'Duitama', 'Sogamoso', 'Chiquinquirá']
+ },
+ {
+ regionName: 'Caldas',
+ regionShort: 'CAL',
+ regionSlug: 'caldas',
+ weight: 3,
+ cities: ['Manizales', 'La Dorada', 'Riosucio']
+ },
+ {
+ regionName: 'Caquetá',
+ regionShort: 'CAQ',
+ regionSlug: 'caqueta',
+ weight: 3,
+ cities: ['Florencia', 'San Vicente del Caguán', 'Cartagena del Chairá']
+ },
+ {
+ regionName: 'Casanare',
+ regionShort: 'CAS',
+ regionSlug: 'casanare',
+ weight: 3,
+ cities: ['Yopal', 'Aguazul', 'Paz de Ariporo']
+ },
+ {
+ regionName: 'Cauca',
+ regionShort: 'CAU',
+ regionSlug: 'cauca',
+ weight: 3,
+ cities: ['Popayán', 'Santander de Quilichao', 'El Tambo']
+ },
+ {
+ regionName: 'Cesar',
+ regionShort: 'CES',
+ regionSlug: 'cesar',
+ weight: 3,
+ cities: ['Valledupar', 'Aguachica', 'Agustín Codazzi']
+ },
+ {
+ regionName: 'Chocó',
+ regionShort: 'CHO',
+ regionSlug: 'choco',
+ weight: 3,
+ cities: ['Quibdó', 'Alto Baudó', 'Medio Atrato', 'Istmina']
+ },
+ {
+ regionName: 'Córdoba',
+ regionShort: 'COR',
+ regionSlug: 'cordoba',
+ weight: 3,
+ cities: ['Montería', 'Santa Cruz de Lorica', 'Tierralta', 'Cereté', 'Sahagún', 'Montelíbano']
+ },
+ {
+ regionName: 'Cundinamarca',
+ regionShort: 'CUN',
+ regionSlug: 'cundinamarca',
+ weight: 3,
+ cities: ['Soacha', 'Fusagasugá', 'Facatativá', 'Chía', 'Zipaquirá', 'Girardot', 'Mosquera']
+ },
+ {
+ regionName: 'Distrito Capital',
+ regionShort: 'DC',
+ regionSlug: 'distrito_capital',
+ weight: 3,
+ cities: ['Bogotá']
+ },
+ {
+ regionName: 'Guainía',
+ regionShort: 'GUA',
+ regionSlug: 'guainia',
+ weight: 3,
+ cities: ['Inírida', 'Puerto Colombia', 'Barranco Minas', 'Mapiripana']
+ },
+ {
+ regionName: 'Guaviare',
+ regionShort: 'GUV',
+ regionSlug: 'guaviare',
+ weight: 3,
+ cities: ['San José del Guaviare', 'El Retorno', 'Miraflores', 'Calamar']
+ },
+ {
+ regionName: 'Huila',
+ regionShort: 'HUI',
+ regionSlug: 'huila',
+ weight: 3,
+ cities: ['Neiva', 'Pitalito', 'Garzón', 'La Plata']
+ },
+ {
+ regionName: 'La Guajira',
+ regionShort: 'LAG',
+ regionSlug: 'la_guajira',
+ weight: 3,
+ cities: ['Riohacha', 'Uribia', 'Maicao', 'Manaure', 'San Juan del Cesar']
+ },
+ {
+ regionName: 'Magdalena',
+ regionShort: 'MAG',
+ regionSlug: 'magdalena',
+ weight: 3,
+ cities: ['Santa Marta', 'Ciénaga', 'Zona Bananera', 'Plato', 'Fundación']
+ },
+ {
+ regionName: 'Meta',
+ regionShort: 'MET',
+ regionSlug: 'meta',
+ weight: 3,
+ cities: ['Villavicencio', 'Acacías', 'Granada', 'Puerto López']
+ },
+ {
+ regionName: 'Nariño',
+ regionShort: 'NAR',
+ regionSlug: 'narino',
+ weight: 3,
+ cities: ['San Juan de Pasto', 'Tumaco', 'Ipiales', 'Samaniego']
+ },
+ {
+ regionName: 'Norte de Santander',
+ regionShort: 'NSA',
+ regionSlug: 'norte_de_santander',
+ weight: 3,
+ cities: ['Cúcuta', 'Ocaña', 'Villa del Rosario', 'Los Patios', 'Pamplona']
+ },
+ {
+ regionName: 'Putumayo',
+ regionShort: 'PUT',
+ regionSlug: 'putumayo',
+ weight: 3,
+ cities: ['Puerto Asís', 'Orito', 'Valle del Guamuez', 'Mocoa', 'Puerto Guzmán']
+ },
+ {
+ regionName: 'Quindío',
+ regionShort: 'QUI',
+ regionSlug: 'quindio',
+ weight: 3,
+ cities: ['Armenia', 'Calarcá', 'La Tebaida', 'Montenegro', 'Quimbaya']
+ },
+ {
+ regionName: 'Risaralda',
+ regionShort: 'RIS',
+ regionSlug: 'risaralda',
+ weight: 3,
+ cities: ['Pereira', 'Dosquebradas', 'Santa Rosa de Cabal']
+ },
+ {
+ regionName: 'San Andrés y Providencia',
+ regionShort: 'SAP',
+ regionSlug: 'san_andres',
+ weight: 3,
+ cities: ['San Andrés']
+ },
+ {
+ regionName: 'Santander',
+ regionShort: 'SAN',
+ regionSlug: 'santander',
+ weight: 3,
+ cities: ['Bucaramanga', 'Floridablanca', 'San Juan de Girón', 'Barrancabermeja', 'Piedecuesta']
+ },
+ {
+ regionName: 'Sucre',
+ regionShort: 'SUC',
+ regionSlug: 'sucre',
+ weight: 3,
+ cities: ['Sincelejo', 'Corozal', 'San Marcos']
+ },
+ {
+ regionName: 'Tolima',
+ regionShort: 'TOL',
+ regionSlug: 'tolima',
+ weight: 3,
+ cities: ['Ibagué']
+ },
+ {
+ regionName: 'Valle del Cauca',
+ regionShort: 'VAC',
+ regionSlug: 'valle_del_cauca',
+ weight: 3,
+ cities: ['Cali', 'Buenaventura', 'Palmira', 'Tuluá']
+ },
+ {
+ regionName: 'Vaupés',
+ regionShort: 'VAU',
+ regionSlug: 'vaupes',
+ weight: 3,
+ cities: ['Mitú', 'Pacoa']
+ },
+ {
+ regionName: 'Vichada',
+ regionShort: 'VID',
+ regionSlug: 'vichada',
+ weight: 3,
+ cities: ['Cumaribo', 'Puerto Carreño']
+ }
+ ]
+});
+
+export default Colombia;
diff --git a/packages/plugins/src/countries/Colombia/i18n/ar.json b/packages/plugins/src/countries/Colombia/i18n/ar.json
new file mode 100644
index 000000000..65083d62e
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "كولومبيا",
+ "regionNames": "المقاطعات الكولومبية"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/de.json b/packages/plugins/src/countries/Colombia/i18n/de.json
new file mode 100644
index 000000000..0c08c7bb7
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Kolumbien",
+ "regionNames": "Kolumbianische Abteilungen"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/en.json b/packages/plugins/src/countries/Colombia/i18n/en.json
new file mode 100644
index 000000000..f8cd0fc32
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Colombia",
+ "regionNames": "Col. Departments"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/es.json b/packages/plugins/src/countries/Colombia/i18n/es.json
new file mode 100644
index 000000000..d1a52f038
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Colombia",
+ "regionNames": "departamentos colombianos"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/fr.json b/packages/plugins/src/countries/Colombia/i18n/fr.json
new file mode 100644
index 000000000..3a67c8228
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Colombie",
+ "regionNames": "Départements de Colombie"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/hi.json b/packages/plugins/src/countries/Colombia/i18n/hi.json
new file mode 100644
index 000000000..4e1fcd8c2
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "कोलंबिया",
+ "regionNames": "कोलंबिया विभाग"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/ja.json b/packages/plugins/src/countries/Colombia/i18n/ja.json
new file mode 100644
index 000000000..567365d43
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "コロンビア",
+ "regionNames": "コロンビアの県"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/nl.json b/packages/plugins/src/countries/Colombia/i18n/nl.json
new file mode 100644
index 000000000..9f01bdbeb
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Colombia",
+ "regionNames": "Colombiaanse afdelingen"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/pt.json b/packages/plugins/src/countries/Colombia/i18n/pt.json
new file mode 100644
index 000000000..9c5ac3f5d
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Colômbia",
+ "regionNames": "Departamentos colombianos"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/ru.json b/packages/plugins/src/countries/Colombia/i18n/ru.json
new file mode 100644
index 000000000..c7a3fb34c
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Колумбия",
+ "regionNames": "Департаменты Колумбии"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/ta.json b/packages/plugins/src/countries/Colombia/i18n/ta.json
new file mode 100644
index 000000000..36e56eee6
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "கொலம்பியா",
+ "regionNames": "கொலம்பிய துறைகள்"
+}
diff --git a/packages/plugins/src/countries/Colombia/i18n/zh.json b/packages/plugins/src/countries/Colombia/i18n/zh.json
new file mode 100644
index 000000000..0ea42c264
--- /dev/null
+++ b/packages/plugins/src/countries/Colombia/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "哥伦比亚",
+ "regionNames": "哥伦比亚部门"
+}
diff --git a/packages/plugins/src/countries/CostaRica/bundle.ts b/packages/plugins/src/countries/CostaRica/bundle.ts
new file mode 100644
index 000000000..897e33eee
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/bundle.ts
@@ -0,0 +1,208 @@
+/* eslint max-len:0 */
+/**
+ * @package Countries
+ *
+ * @author Andre Fortin
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const CostaRica: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'CR',
+ regionNames: i18n.regionNames,
+ continent: 'central_america',
+ extendedData: {
+ zipFormat: {
+ format: 'ZYxYx',
+ replacements: {
+ Z: '1234567',
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['xxxxxxxx', 'xxxx-xxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Alajuela',
+ regionShort: 'A',
+ regionSlug: 'alajuela',
+ weight: 20,
+ cities: ['Alajuela', 'Quesada', 'San José de Alajuela', 'San Rafael'],
+ extendedData: {
+ zipFormat: {
+ format: '2zxYx',
+ replacements: {
+ z: '01',
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['24xxxxxx']
+ }
+ }
+ },
+ {
+ regionName: 'Cartago',
+ regionShort: 'C',
+ regionSlug: 'cartago',
+ weight: 11,
+ cities: [
+ 'Aguacaliente (San Francisco]',
+ 'Carmen',
+ 'Cartago',
+ 'Paraíso',
+ 'San Diego',
+ 'San Nicolás',
+ 'San Rafael',
+ 'Tejar',
+ 'Turrialba'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '30xYx',
+ replacements: {
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['25xxxxxx']
+ }
+ }
+ },
+ {
+ regionName: 'Guanacaste',
+ regionShort: 'G',
+ regionSlug: 'guanacaste',
+ weight: 8,
+ cities: ['Cañas', 'Liberia', 'Nicoya'],
+ extendedData: {
+ zipFormat: {
+ format: '5zxYx',
+ replacements: {
+ z: '01',
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['26xxxxxx']
+ }
+ }
+ },
+ {
+ regionName: 'Heredia',
+ regionShort: 'H',
+ regionSlug: 'heredia',
+ weight: 10,
+ cities: ['Heredia', 'Mercedes', 'San Francisco', 'San Pablo', 'Ulloa (Barrial]'],
+ extendedData: {
+ zipFormat: {
+ format: '4zxYx',
+ replacements: {
+ z: '01',
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['25xxxxxx']
+ }
+ }
+ },
+ {
+ regionName: 'Limón',
+ regionShort: 'L',
+ regionSlug: 'limón',
+ weight: 2,
+ cities: ['Guápiles', 'Limón (Puerto Limón]', 'Siquirres'],
+ extendedData: {
+ zipFormat: {
+ format: '50yYx',
+ replacements: {
+ y: '12',
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['27xxxxxx']
+ }
+ }
+ },
+ {
+ regionName: 'Puntarenas',
+ regionShort: 'P',
+ regionSlug: 'puntarenas',
+ weight: 2,
+ cities: ['Barranca', 'Puntarenas'],
+ extendedData: {
+ zipFormat: {
+ format: 'ZYxYx',
+ replacements: {
+ Z: '1234567',
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['27xxxxxx']
+ }
+ }
+ },
+ {
+ regionName: 'San José',
+ regionShort: 'SJ',
+ regionSlug: 'san_josé',
+ weight: 33,
+ cities: [
+ 'Alajuelita',
+ 'Aserrí',
+ 'Calle Blancos',
+ 'Cinco Esquinas',
+ 'Concepción',
+ 'Curridabat',
+ 'Desamparados',
+ 'Gravilias',
+ 'Guadalupe',
+ 'Ipís',
+ 'Mata de Plátano',
+ 'Patalillo',
+ 'Patarrá',
+ 'Purral',
+ 'San Antonio',
+ 'San Felipe',
+ 'San Isidro',
+ 'San Isidro de El General',
+ 'San José',
+ 'San Juan (San Juan de Tibás]',
+ 'San Juan de Dios',
+ 'San Miguel',
+ 'San Pedro',
+ 'San Rafael',
+ 'San Rafael Abajo',
+ 'San Vicente',
+ 'Tirrases'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '1zxYx',
+ replacements: {
+ z: '012',
+ Y: '01',
+ x: '0123456789'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['25xxxxxx']
+ }
+ }
+ }
+ ]
+});
+
+export default CostaRica;
diff --git a/packages/plugins/src/countries/CostaRica/i18n/ar.json b/packages/plugins/src/countries/CostaRica/i18n/ar.json
new file mode 100644
index 000000000..242b54a6a
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "كوستا ريكا",
+ "regionNames": "مقاطعات كوستاريكا"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/de.json b/packages/plugins/src/countries/CostaRica/i18n/de.json
new file mode 100644
index 000000000..94ca3486b
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Costa Rica",
+ "regionNames": "Provinzen Costa Ricas"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/en.json b/packages/plugins/src/countries/CostaRica/i18n/en.json
new file mode 100644
index 000000000..8a3042cbf
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Costa Rica",
+ "regionNames": "Costa Rican Provinces"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/es.json b/packages/plugins/src/countries/CostaRica/i18n/es.json
new file mode 100644
index 000000000..4f6471b10
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Costa Rica",
+ "regionNames": "Provincias de Costa Rica"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/fr.json b/packages/plugins/src/countries/CostaRica/i18n/fr.json
new file mode 100644
index 000000000..7deddc9d4
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Costa Rica",
+ "regionNames": "Provinces du Costa Rica"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/hi.json b/packages/plugins/src/countries/CostaRica/i18n/hi.json
new file mode 100644
index 000000000..2aa12540b
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "कोस्टा रिका",
+ "regionNames": "कोस्टा रिकान प्रांत"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/ja.json b/packages/plugins/src/countries/CostaRica/i18n/ja.json
new file mode 100644
index 000000000..96b7be51f
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "コスタリカ",
+ "regionNames": "コスタリカの州"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/nl.json b/packages/plugins/src/countries/CostaRica/i18n/nl.json
new file mode 100644
index 000000000..b5c0f8805
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Costa Rica",
+ "regionNames": "Costa Ricaanse provincies"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/pt.json b/packages/plugins/src/countries/CostaRica/i18n/pt.json
new file mode 100644
index 000000000..c9439f61c
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Costa Rica",
+ "regionNames": "Províncias da Costa Rica"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/ru.json b/packages/plugins/src/countries/CostaRica/i18n/ru.json
new file mode 100644
index 000000000..4826e45c2
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Коста-Рика",
+ "regionNames": "Провинции Коста-Рики"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/ta.json b/packages/plugins/src/countries/CostaRica/i18n/ta.json
new file mode 100644
index 000000000..9c1cebc3c
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "கோஸ்ட்டா ரிக்கா",
+ "regionNames": "கோஸ்டா ரிக்கன் மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/CostaRica/i18n/zh.json b/packages/plugins/src/countries/CostaRica/i18n/zh.json
new file mode 100644
index 000000000..e600ae209
--- /dev/null
+++ b/packages/plugins/src/countries/CostaRica/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "哥斯达黎加",
+ "regionNames": "哥斯达黎加省份"
+}
diff --git a/packages/plugins/src/countries/France/bundle.ts b/packages/plugins/src/countries/France/bundle.ts
new file mode 100644
index 000000000..7c5c905b1
--- /dev/null
+++ b/packages/plugins/src/countries/France/bundle.ts
@@ -0,0 +1,278 @@
+import { GetCountryData } from '@generatedata/types';
+
+const France: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'france',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Île-de-France',
+ regionShort: 'IL',
+ regionSlug: 'ile_de_france',
+ weight: 18.3,
+ cities: [
+ 'Paris',
+ 'Boulogne-Billancourt',
+ 'Saint-Denis',
+ 'Argenteuil',
+ 'Montreuil',
+ 'Créteil',
+ 'Nanterre',
+ 'Courbevoie',
+ 'Versailles',
+ 'Vitry-sur-Seine',
+ 'Colombes',
+ 'Asnières-sur-Seine',
+ 'Aulnay-sous-Bois',
+ 'Rueil-Malmaison',
+ 'Aubervilliers',
+ 'Champigny-sur-Marne',
+ 'Saint-Maur-des-Fossés',
+ 'Drancy',
+ 'Issy-les-Moulineaux',
+ 'Levallois-Perret',
+ 'Noisy-le-Grand'
+ ]
+ },
+ {
+ regionName: "Provence-Alpes-Côte d'Azur",
+ regionShort: 'PR',
+ regionSlug: 'provence_alpes',
+ weight: 9.6,
+ cities: [
+ 'Marseille',
+ 'Nice',
+ 'Toulon',
+ 'Aix-en-Provence',
+ 'Avignon',
+ 'Antibes',
+ 'Cannes',
+ 'La Seyne-sur-Mer',
+ 'Hyères',
+ 'Arles',
+ 'Fréjus',
+ 'Grasse',
+ 'Martigues',
+ 'Cagnes-sur-Mer',
+ 'Aubagne',
+ 'Salon-de-Provence',
+ 'Istres',
+ 'Le Cannet',
+ 'Gap',
+ 'Draguignan',
+ 'Vitrolles'
+ ]
+ },
+ {
+ regionName: 'Nord-Pas-de-Calais',
+ regionShort: 'NO',
+ regionSlug: 'nord_pas_de_calais',
+ weight: 7.6,
+ cities: [
+ 'Lille',
+ 'Roubaix',
+ 'Dunkerque',
+ 'Tourcoing',
+ 'Calais',
+ "Villeneuve-d'Ascq",
+ 'Valenciennes',
+ 'Boulogne-sur-Mer',
+ 'Douai',
+ 'Arras',
+ 'Wattrelos',
+ 'Marcq-en-Baroeul',
+ 'Lens',
+ 'Cambrai',
+ 'Liévin',
+ 'Maubeuge',
+ 'Lambersart',
+ 'Hénin-Beaumont',
+ 'Béthune'
+ ]
+ },
+ {
+ regionName: 'Pays de la Loire',
+ regionShort: 'PA',
+ regionSlug: 'pays_de_la_loire',
+ weight: 6.3,
+ cities: [
+ 'Nantes',
+ 'Angers',
+ 'Le Mans',
+ 'Saint-Nazaire',
+ 'Cholet',
+ 'La Roche-sur-Yon',
+ 'Laval',
+ 'Saint-Herblain',
+ 'Rezé',
+ 'Saumur',
+ 'Saint-Sébastien-sur-Loire',
+ 'Orvault',
+ 'Vertou'
+ ]
+ },
+ {
+ regionName: 'Aquitaine',
+ regionShort: 'AQ',
+ regionSlug: 'aquitaine',
+ weight: 5.5,
+ cities: [
+ 'Bordeaux',
+ 'Pau',
+ 'Mérignac',
+ 'Pessac',
+ 'Bayonne',
+ 'Talence',
+ 'Anglet',
+ 'Agen',
+ 'Mont-de-Marsan',
+ 'Périgueux',
+ "Villenave-d'Ornon",
+ 'Saint-Médard-en-Jalles',
+ 'Bergerac',
+ 'Biarritz',
+ 'Bègles'
+ ]
+ },
+ {
+ regionName: 'Bretagne',
+ regionShort: 'BR',
+ regionSlug: 'bretagne',
+ weight: 4.9,
+ cities: ['Rennes', 'Brest', 'Quimper', 'Lorient', 'Vannes', 'Saint-Malo', 'Saint-Brieuc', 'Lanester']
+ },
+ {
+ regionName: 'Midi-Pyrénées',
+ regionShort: 'MI',
+ regionSlug: 'midi_pyrenees',
+ weight: 4.4,
+ cities: ['Toulouse', 'Montauban', 'Albi', 'Tarbes', 'Castres', 'Colomiers', 'Tournefeuille', 'Rodez']
+ },
+ {
+ regionName: 'Languedoc-Roussillon',
+ regionShort: 'LA',
+ regionSlug: 'languedoc_rousillon',
+ weight: 4.1,
+ cities: ['Montpellier', 'Nîmes', 'Perpignan', 'Béziers', 'Narbonne', 'Carcassonne', 'Sète', 'Alès', 'Lunel']
+ },
+ {
+ regionName: 'Centre',
+ regionShort: 'CE',
+ regionSlug: 'centre',
+ weight: 4,
+ cities: ['Tours', 'Orléans', 'Bourges', 'Blois', 'Châteauroux', 'Chartres', 'Joué-lès-Tours', 'Dreux', 'Vierzon']
+ },
+ {
+ regionName: 'Lorraine',
+ regionShort: 'LO',
+ regionSlug: 'lorraine',
+ weight: 3.7,
+ cities: [
+ 'Metz',
+ 'Nancy',
+ 'Thionville',
+ 'Épinal',
+ 'Vandoeuvre-lès-Nancy',
+ 'Montigny-lès-Metz',
+ 'Sarreguemines',
+ 'Forbach',
+ 'Saint-Dié-des-Vosges'
+ ]
+ },
+ {
+ regionName: 'Picardie',
+ regionShort: 'PI',
+ regionSlug: 'picardie',
+ weight: 3,
+ cities: ['Amiens', 'Saint-Quentin', 'Beauvais', 'Compiègne', 'Creil', 'Soissons', 'Laon', 'Abbeville']
+ },
+ {
+ regionName: 'Alsace',
+ regionShort: 'AL',
+ regionSlug: 'alsace',
+ weight: 2.9,
+ cities: ['Strasbourg', 'Mulhouse', 'Colmar', 'Haguenau', 'Schiltigheim', 'Illkirch-Graffenstaden', 'Saint-Louis']
+ },
+ {
+ regionName: 'Haute-Normandie',
+ regionShort: 'HA',
+ regionSlug: 'haute_normandie',
+ weight: 2.8,
+ cities: [
+ 'Le Havre',
+ 'Rouen',
+ 'Évreux',
+ 'Dieppe',
+ 'Sotteville-lès-Rouen',
+ 'Saint-Étienne-du-Rouvray',
+ 'Vernon',
+ 'Le Grand-Quevilly',
+ 'Le Petit-Quevilly'
+ ]
+ },
+ {
+ regionName: 'Poitou-Charentes',
+ regionShort: 'PO',
+ regionSlug: 'poitou_charentes',
+ weight: 2.7,
+ cities: ['Poitiers', 'La Rochelle', 'Niort', 'Angoulême', 'Châtellerault', 'Saintes']
+ },
+ {
+ regionName: 'Bourgogne',
+ regionShort: 'BO',
+ regionSlug: 'bourgogne',
+ weight: 2.6,
+ cities: ['Dijon', 'Chalon-sur-Saône', 'Nevers', 'Auxerre', 'Mâcon', 'Sens']
+ },
+ {
+ regionName: 'Basse-Normandie',
+ regionShort: 'BA',
+ regionSlug: 'basse_normandie',
+ weight: 2.3,
+ cities: ['Caen', 'Cherbourg-Octeville', 'Alençon', 'Lisieux', 'Hérouville-Saint-Clair', 'Saint-Lô']
+ },
+ {
+ regionName: 'Auvergne',
+ regionShort: 'AU',
+ regionSlug: 'auvergne',
+ weight: 2.1,
+ cities: ['Clermont-Ferrand', 'Montluçon', 'Aurillac', 'Vichy', 'Moulins', 'Le Puy-en-Velay']
+ },
+ {
+ regionName: 'Champagne-Ardenne',
+ regionShort: 'CH',
+ regionSlug: 'champagne_ardenne',
+ weight: 2.1,
+ cities: ['Reims', 'Troyes', 'Charleville-Mézières', 'Châlons-en-Champagne', 'Saint-Dizier', 'Épernay']
+ },
+ {
+ regionName: 'Franche-Comté',
+ regionShort: 'FC',
+ regionSlug: 'franche_comte',
+ weight: 1.8,
+ cities: ['Besançon', 'Belfort', 'Montbéliard', 'Dole', 'Pontarlier']
+ },
+ {
+ regionName: 'Limousin',
+ regionShort: 'LI',
+ regionSlug: 'limousin',
+ weight: 1.1,
+ cities: ['Limoges', 'Brive-la-Gaillarde']
+ },
+ {
+ regionName: 'Corse',
+ regionShort: 'CO',
+ regionSlug: 'corse',
+ weight: 0.5,
+ cities: ['Ajaccio', 'Bastia']
+ }
+ ]
+});
+
+export default France;
diff --git a/packages/plugins/src/countries/France/i18n/ar.json b/packages/plugins/src/countries/France/i18n/ar.json
new file mode 100644
index 000000000..183fdf725
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "فرنسا",
+ "regionNames": "مناطق فرنسا"
+}
diff --git a/packages/plugins/src/countries/France/i18n/de.json b/packages/plugins/src/countries/France/i18n/de.json
new file mode 100644
index 000000000..487c23582
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Frankreich",
+ "regionNames": "Frankreich-Regionen"
+}
diff --git a/packages/plugins/src/countries/France/i18n/en.json b/packages/plugins/src/countries/France/i18n/en.json
new file mode 100644
index 000000000..6557b1028
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "France",
+ "regionNames": "France regions"
+}
diff --git a/packages/plugins/src/countries/France/i18n/es.json b/packages/plugins/src/countries/France/i18n/es.json
new file mode 100644
index 000000000..360758654
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Francia",
+ "regionNames": "Francia regiones"
+}
diff --git a/packages/plugins/src/countries/France/i18n/fr.json b/packages/plugins/src/countries/France/i18n/fr.json
new file mode 100644
index 000000000..c307f3036
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "France",
+ "regionNames": "Régions Françaises"
+}
diff --git a/packages/plugins/src/countries/France/i18n/hi.json b/packages/plugins/src/countries/France/i18n/hi.json
new file mode 100644
index 000000000..b48361836
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "फ्रांस",
+ "regionNames": "फ्रांस क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/France/i18n/ja.json b/packages/plugins/src/countries/France/i18n/ja.json
new file mode 100644
index 000000000..e4cd3b274
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "フランス",
+ "regionNames": "フランスの地域"
+}
diff --git a/packages/plugins/src/countries/France/i18n/nl.json b/packages/plugins/src/countries/France/i18n/nl.json
new file mode 100644
index 000000000..5b5d78d16
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Frankrijk",
+ "regionNames": "Frankrijk regio's"
+}
diff --git a/packages/plugins/src/countries/France/i18n/pt.json b/packages/plugins/src/countries/France/i18n/pt.json
new file mode 100644
index 000000000..465ef00f4
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "França",
+ "regionNames": "Regiões da França"
+}
diff --git a/packages/plugins/src/countries/France/i18n/ru.json b/packages/plugins/src/countries/France/i18n/ru.json
new file mode 100644
index 000000000..0eec7aeb6
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Франция",
+ "regionNames": "Регионы Франции"
+}
diff --git a/packages/plugins/src/countries/France/i18n/ta.json b/packages/plugins/src/countries/France/i18n/ta.json
new file mode 100644
index 000000000..ba5c18f82
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "பிரான்ஸ்",
+ "regionNames": "பிரான்ஸ் பிராந்தியங்கள்"
+}
diff --git a/packages/plugins/src/countries/France/i18n/zh.json b/packages/plugins/src/countries/France/i18n/zh.json
new file mode 100644
index 000000000..57fdbfbff
--- /dev/null
+++ b/packages/plugins/src/countries/France/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "法国",
+ "regionNames": "法国地区"
+}
diff --git a/packages/plugins/src/countries/Germany/bundle.ts b/packages/plugins/src/countries/Germany/bundle.ts
new file mode 100644
index 000000000..ffec5d75a
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/bundle.ts
@@ -0,0 +1,429 @@
+/* eslint max-len:0 */
+import { GetCountryData } from '@generatedata/types';
+
+const Germany: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'germany',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Baden Württemberg',
+ regionShort: 'BW',
+ regionSlug: 'baden_wurttemberg',
+ weight: 301,
+ cities: [
+ 'Stuttgart',
+ 'Mannheim',
+ 'Karlsruhe',
+ 'Freiburg',
+ 'Heidelberg',
+ 'Heilbronn',
+ 'Ulm',
+ 'Pforzheim',
+ 'Reutlingen',
+ 'Esslingen',
+ 'Tübingen',
+ 'Ludwigsburg',
+ 'Konstanz',
+ 'Villingen-Schwenningen',
+ 'Aalen',
+ 'Sindelfingen',
+ 'Schwäbisch Gmünd',
+ 'Friedrichshafen',
+ 'Offenburg',
+ 'Göppingen',
+ 'Baden-Baden',
+ 'Waiblingen',
+ 'Ravensburg',
+ 'Lörrach',
+ 'Heidenheim',
+ 'Rastatt',
+ 'Böblingen'
+ ]
+ },
+ {
+ regionName: 'Bayern',
+ regionShort: 'BY',
+ regionSlug: 'bayern',
+ weight: 178,
+ cities: [
+ 'Munich',
+ 'Nuremberg',
+ 'Augsburg',
+ 'Regensburg',
+ 'Würzburg',
+ 'Ingolstadt',
+ 'Fürth',
+ 'Erlangen',
+ 'Bayreuth',
+ 'Bamberg',
+ 'Aschaffenburg',
+ 'Landshut',
+ 'Kempten',
+ 'Rosenheim',
+ 'Neu-Ulm',
+ 'Schweinfurt',
+ 'Passau',
+ 'Hof',
+ 'Freising',
+ 'Straubing'
+ ]
+ },
+ {
+ regionName: 'Berlin',
+ regionShort: 'BE',
+ regionSlug: 'berlin',
+ weight: 3890,
+ cities: ['Berlin']
+ },
+ {
+ regionName: 'Brandenburg',
+ regionShort: 'BB',
+ regionSlug: 'brandenburg',
+ weight: 85,
+ cities: [
+ 'Potsdam',
+ 'Cottbus',
+ 'Brandenburg',
+ 'Frankfurt',
+ 'Oranienburg',
+ 'Falkensee',
+ 'Eberswalde-Finow',
+ 'Bernau',
+ 'Königs Wusterhausen',
+ 'Schwedt',
+ 'Fürstenwalde',
+ 'Neuruppin',
+ 'Eisenhüttenstadt',
+ 'Senftenberg',
+ 'Strausberg',
+ 'Hennigsdorf',
+ 'Blankenfelde-Mahlow',
+ 'Rathenow',
+ 'Hohen Neuendorf',
+ 'Ludwigsfelde',
+ 'Spremberg',
+ 'Werder',
+ 'Teltow',
+ 'Wandlitz',
+ 'Luckenwalde',
+ 'Forst',
+ 'Kleinmachnow',
+ 'Prenzlau',
+ 'Panketal',
+ 'Guben'
+ ]
+ },
+ {
+ regionName: 'Bremen',
+ regionShort: 'HB',
+ regionSlug: 'bremen',
+ weight: 1577,
+ cities: ['Bremen', 'Bremerhaven']
+ },
+ {
+ regionName: 'Hamburg',
+ regionShort: 'HH',
+ regionSlug: 'hamburg',
+ weight: 2368,
+ cities: ['Hamburg']
+ },
+ {
+ regionName: 'Hessen',
+ regionShort: 'HE',
+ regionSlug: 'hessen',
+ weight: 287,
+ cities: [
+ 'Frankfurt am Main',
+ 'Wiesbaden',
+ 'Kassel',
+ 'Darmstadt',
+ 'Offenbach am Main',
+ 'Hanau',
+ 'Marburg',
+ 'Gießen',
+ 'Fulda',
+ 'Rüsselsheim',
+ 'Wetzlar',
+ 'Bad Homburg v. d. Höhe',
+ 'Rodgau',
+ 'Oberursel',
+ 'Dreieich',
+ 'Maintal',
+ 'Bensheim',
+ 'Hofheim am Taunus',
+ 'Neu-Isenburg',
+ 'Langen',
+ 'Limburg a.d. Lahn',
+ 'Dietzenbach',
+ 'Lampertheim',
+ 'Mörfelden-Walldorf',
+ 'Viernheim',
+ 'Bad Hersfeld',
+ 'Bad Nauheim',
+ 'Taunusstein',
+ 'Baunatal',
+ 'Kelkheim',
+ 'Bad Vilbel',
+ 'Friedberg',
+ 'Mühlheim am Main',
+ 'Rödermark',
+ 'Heppenheim',
+ 'Dillenburg',
+ 'Pfungstadt',
+ 'Hattersheim am Main',
+ 'Butzbach',
+ 'Friedrichsdorf',
+ 'Obertshausen',
+ 'Korbach',
+ 'Griesheim',
+ 'Groß-Gerau',
+ 'Weiterstadt',
+ 'Eschwege'
+ ]
+ },
+ {
+ regionName: 'Niedersachsen',
+ regionShort: 'NI',
+ regionSlug: 'niedersachsen',
+ weight: 166,
+ cities: [
+ 'Hannover',
+ 'Braunschweig',
+ 'Osnabrück',
+ 'Oldenburg',
+ 'Wolfsburg',
+ 'Göttingen',
+ 'Hildesheim',
+ 'Salzgitter',
+ 'Wilhelmshaven',
+ 'Delmenhorst',
+ 'Lüneburg',
+ 'Celle',
+ 'Garbsen',
+ 'Hameln',
+ 'Wolfenbüttel',
+ 'Nordhorn',
+ 'Langenhagen',
+ 'Emden',
+ 'Lingen',
+ 'Cuxhaven',
+ 'Peine',
+ 'Stade',
+ 'Melle',
+ 'Neustadt am Rübenberge',
+ 'Lehrte',
+ 'Seevetal',
+ 'Gifhorn',
+ 'Wunstorf',
+ 'Goslar'
+ ]
+ },
+ {
+ regionName: 'Mecklenburg-Vorpommern',
+ regionShort: 'MV',
+ regionSlug: 'meckleburg',
+ weight: 71,
+ cities: [
+ 'Anklam',
+ 'Bergen',
+ 'Greifswald',
+ 'Güstrow',
+ 'Neubrandenburg',
+ 'Neustrelitz',
+ 'Parchim City',
+ 'Ribnitz-Damgarten',
+ 'Rostock',
+ 'Schwerin',
+ 'Stralsund',
+ 'Waren',
+ 'Wismar'
+ ]
+ },
+ {
+ regionName: 'Nordrhein-Westphalen',
+ regionShort: 'NW',
+ regionSlug: 'nordrhein_westphalien',
+ weight: 523,
+ cities: [
+ 'Köln',
+ 'Düsseldorf',
+ 'Dortmund',
+ 'Essen',
+ 'Duisburg',
+ 'Bochum',
+ 'Wuppertal',
+ 'Bonn',
+ 'Bielefeld',
+ 'Münster',
+ 'Aachen',
+ 'Mönchengladbach',
+ 'Gelsenkirchen',
+ 'Krefeld',
+ 'Oberhausen',
+ 'Hagen',
+ 'Hamm',
+ 'Mülheim',
+ 'Herne',
+ 'Leverkusen',
+ 'Solingen',
+ 'Neuss',
+ 'Paderborn',
+ 'Recklinghausen',
+ 'Bottrop',
+ 'Remscheid',
+ 'Bergisch Gladbach'
+ ]
+ },
+ {
+ regionName: 'Rheinland-Pfalz',
+ regionShort: 'RP',
+ regionSlug: 'rheinland_pfalz',
+ weight: 202,
+ cities: [
+ 'Mainz',
+ 'Ludwigshafen',
+ 'Koblenz',
+ 'Trier',
+ 'Kaiserslauter',
+ 'Worms',
+ 'Neuwied',
+ 'Neustadt',
+ 'Speyer',
+ 'Frankenthal',
+ 'Bad Kreuznach',
+ 'Landau',
+ 'Pirmasens',
+ 'Zweibrücken',
+ 'Idar-Oberstei',
+ 'Andernach',
+ 'Bad Neuenahr-Ahrweiler',
+ 'Bingen',
+ 'Ingelheim',
+ 'Germersheim',
+ 'Haßloch',
+ 'Schifferstadt',
+ 'Bad Dürkheim'
+ ]
+ },
+ {
+ regionName: 'Saarland',
+ regionShort: 'SL',
+ regionSlug: 'saarland',
+ weight: 400,
+ cities: [
+ 'Saarbrücken',
+ 'Neunkirchen',
+ 'Homburg',
+ 'Völklingen',
+ 'Sankt Ingbert',
+ 'Saarlouis',
+ 'Merzig',
+ 'Sankt Wendel',
+ 'Blieskastel',
+ 'Dillingen',
+ 'Lebach',
+ 'Püttlingen',
+ 'Heusweiler',
+ 'Wadgassen',
+ 'Bexbach',
+ 'Schwalbach',
+ 'Sulzbach'
+ ]
+ },
+ {
+ regionName: 'Sachsen',
+ regionShort: 'SN',
+ regionSlug: 'sachsen',
+ weight: 227,
+ cities: [
+ 'Leipzig',
+ 'Dresden',
+ 'Chemnitz',
+ 'Zwickau',
+ 'Plauen',
+ 'Görlitz',
+ 'Freiberg',
+ 'Bautzen',
+ 'Freital',
+ 'Pirna',
+ 'Hoyerswerda',
+ 'Radebeul',
+ 'Riesa',
+ 'Grimma',
+ 'Zittau',
+ 'Meißen',
+ 'Delitzsch',
+ 'Limbach-Oberfrohna',
+ 'Markkleeberg',
+ 'Glauchau'
+ ]
+ },
+ {
+ regionName: 'Sachsen-Anhalt',
+ regionShort: 'ST',
+ regionSlug: 'sachsen_anhalt',
+ weight: 116,
+ cities: [
+ 'Halle',
+ 'Magdeburg',
+ 'Dessau',
+ 'Wittenberg',
+ 'Bitterfeld-Wolfen',
+ 'Stendal',
+ 'Halberstadt',
+ 'Weißenfels',
+ 'Bernburg',
+ 'Merseburg',
+ 'Wernigerode',
+ 'Naumburg',
+ 'Schönebeck',
+ 'Zeitz',
+ 'Sangerhausen',
+ 'Aschersleben',
+ 'Quedlinburg',
+ 'Staßfurt',
+ 'Köthen',
+ 'Eisleben',
+ 'Salzwedel',
+ 'Burg'
+ ]
+ },
+ {
+ regionName: 'Schleswig-Holstein',
+ regionShort: 'SH',
+ regionSlug: 'schleswig_holstein',
+ weight: 179,
+ cities: [
+ 'Kiel',
+ 'Lübeck',
+ 'Flensburg',
+ 'Neumünster',
+ 'Norderstedt',
+ 'Elmshorn',
+ 'Pinneberg',
+ 'Itzehoe',
+ 'Wedel',
+ 'Ahrensburg',
+ 'Geesthacht',
+ 'Rendsburg',
+ 'Henstedt-Ulzburg',
+ 'Reinbek',
+ 'Bad Oldesloe',
+ 'Schleswig',
+ 'Eckernförde',
+ 'Husum',
+ 'Heide',
+ 'Quickborn'
+ ]
+ }
+ ]
+});
+
+export default Germany;
diff --git a/packages/plugins/src/countries/Germany/i18n/ar.json b/packages/plugins/src/countries/Germany/i18n/ar.json
new file mode 100644
index 000000000..1bed26505
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ألمانيا",
+ "regionNames": "الدول الألمانية"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/de.json b/packages/plugins/src/countries/Germany/i18n/de.json
new file mode 100644
index 000000000..9bbca29fc
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Deutschland",
+ "regionNames": "Deutsche Staaten"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/en.json b/packages/plugins/src/countries/Germany/i18n/en.json
new file mode 100644
index 000000000..464015aa7
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Germany",
+ "regionNames": "German States"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/es.json b/packages/plugins/src/countries/Germany/i18n/es.json
new file mode 100644
index 000000000..4c88a94f5
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Alemania",
+ "regionNames": "Estados alemanes"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/fr.json b/packages/plugins/src/countries/Germany/i18n/fr.json
new file mode 100644
index 000000000..dd419af3c
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Allemagne",
+ "regionNames": "États allemands"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/hi.json b/packages/plugins/src/countries/Germany/i18n/hi.json
new file mode 100644
index 000000000..2ee2ded5b
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "जर्मनी",
+ "regionNames": "जर्मन राज्य"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/ja.json b/packages/plugins/src/countries/Germany/i18n/ja.json
new file mode 100644
index 000000000..621f3e4f4
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ドイツ",
+ "regionNames": "ドイツの諸州"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/nl.json b/packages/plugins/src/countries/Germany/i18n/nl.json
new file mode 100644
index 000000000..86716caa8
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Duitsland",
+ "regionNames": "Duitse staten"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/pt.json b/packages/plugins/src/countries/Germany/i18n/pt.json
new file mode 100644
index 000000000..c823c13f6
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Alemanha",
+ "regionNames": "Estados alemães"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/ru.json b/packages/plugins/src/countries/Germany/i18n/ru.json
new file mode 100644
index 000000000..83738aa9b
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Германия",
+ "regionNames": "Германские государства"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/ta.json b/packages/plugins/src/countries/Germany/i18n/ta.json
new file mode 100644
index 000000000..7bd5289d5
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ஜெர்மனி",
+ "regionNames": "ஜெர்மன் நாடுகள்"
+}
diff --git a/packages/plugins/src/countries/Germany/i18n/zh.json b/packages/plugins/src/countries/Germany/i18n/zh.json
new file mode 100644
index 000000000..eb379603a
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "德国",
+ "regionNames": "德国各州"
+}
diff --git a/packages/plugins/src/countries/Germany/names.ts b/packages/plugins/src/countries/Germany/names.ts
new file mode 100644
index 000000000..e00e6a36f
--- /dev/null
+++ b/packages/plugins/src/countries/Germany/names.ts
@@ -0,0 +1,454 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Alexa',
+ 'Alexandra',
+ 'Angela',
+ 'Angelika',
+ 'Anita',
+ 'Anja',
+ 'Anke',
+ 'Anna',
+ 'Anne',
+ 'Annette',
+ 'Annika',
+ 'Astrid',
+ 'Barbara',
+ 'Beate',
+ 'Beatrice',
+ 'Bettina',
+ 'Bianca',
+ 'Birgit',
+ 'Brigitte',
+ 'Britta',
+ 'Bärbel',
+ 'Carmen',
+ 'Carolin',
+ 'Chantal',
+ 'Charlotte',
+ 'Christiane',
+ 'Christina',
+ 'Cindy',
+ 'Conny',
+ 'Cordula',
+ 'Cornelia',
+ 'Dagmar',
+ 'Deborah',
+ 'Denise',
+ 'Diana',
+ 'Dora',
+ 'Doris',
+ 'Dorothea',
+ 'Edeltraud',
+ 'Eleonor',
+ 'Elfriede',
+ 'Elisabeth',
+ 'Elsa',
+ 'Else',
+ 'Elvira',
+ 'Emma',
+ 'Erika',
+ 'Erna',
+ 'Esther',
+ 'Eva',
+ 'Evelyn',
+ 'Felicia',
+ 'Flora',
+ 'Franzi',
+ 'Franziska',
+ 'Frauke',
+ 'Frieda',
+ 'Gabi',
+ 'Gabriele',
+ 'Gerda',
+ 'Gerlinde',
+ 'Gertrud',
+ 'Gisela',
+ 'Greta',
+ 'Gudrun',
+ 'Hanna',
+ 'Hannelore',
+ 'Hedwig',
+ 'Heidi',
+ 'Heike',
+ 'Helga',
+ 'Herta',
+ 'Hildegard',
+ 'Ida',
+ 'Ilse',
+ 'Ina',
+ 'Inge',
+ 'Ingeborg',
+ 'Ingrid',
+ 'Irene',
+ 'Irma',
+ 'Isa',
+ 'Isabell',
+ 'Isolde',
+ 'Ivonne',
+ 'Jacqueline',
+ 'Jana',
+ 'Janette',
+ 'Jasmin',
+ 'Jeanette',
+ 'Jennifer',
+ 'Jenny',
+ 'Jessica',
+ 'Judith',
+ 'Julia',
+ 'Juliane',
+ 'Jutta',
+ 'Karen',
+ 'Karin',
+ 'Karla',
+ 'Karola',
+ 'Karolin',
+ 'Kassandra',
+ 'Katharina',
+ 'Kathi',
+ 'Kathrin',
+ 'Kati',
+ 'Katja',
+ 'Kerstin',
+ 'Kristin',
+ 'Larissa',
+ 'Laura',
+ 'Lena',
+ 'Leonie',
+ 'Liane',
+ 'Lili',
+ 'Lily',
+ 'Lina',
+ 'Liselotte',
+ 'Louise',
+ 'Luise',
+ 'Magda',
+ 'Magdalena',
+ 'Maja',
+ 'Maria',
+ 'Marlene',
+ 'Martina',
+ 'Mechthild',
+ 'Meike',
+ 'Melanie',
+ 'Miriam',
+ 'Mona',
+ 'Monika',
+ 'Nadine',
+ 'Nadja',
+ 'Nancy',
+ 'Nicole',
+ 'Olga',
+ 'Olivia',
+ 'Patricia',
+ 'Paula',
+ 'Pauline',
+ 'Petra',
+ 'Pia',
+ 'Ramona',
+ 'Rebekka',
+ 'Regina',
+ 'Regine',
+ 'Renate',
+ 'Ricarda',
+ 'Rita',
+ 'Rosa',
+ 'Rosemarie',
+ 'Sabine',
+ 'Sabrina',
+ 'Sally',
+ 'Sarah',
+ 'Sibylle',
+ 'Siggi',
+ 'Sofia',
+ 'Sophie',
+ 'Stefanie',
+ 'Steffi',
+ 'Susanne',
+ 'Susi',
+ 'Sylvia',
+ 'Thea',
+ 'Theresa',
+ 'Tina',
+ 'Trude',
+ 'Ursel',
+ 'Ursula',
+ 'Uschi',
+ 'Uta',
+ 'Ute',
+ 'Valerie',
+ 'Vanessa',
+ 'Verena',
+ 'Vreni',
+ 'Vroni',
+ 'Waltraud',
+ 'Wiebke',
+ 'Yvonne',
+ 'Zara'
+];
+
+const maleNames = [
+ 'Aaron',
+ 'Adam',
+ 'Albrecht',
+ 'Alex',
+ 'Alexander',
+ 'Andreas',
+ 'Anselm',
+ 'Ansgar',
+ 'Anton',
+ 'Armin',
+ 'Arnold',
+ 'Bastian',
+ 'Benedikt',
+ 'Bernd',
+ 'Bernhard',
+ 'Bert',
+ 'Berthold',
+ 'Bertram',
+ 'Björn',
+ 'Bodo',
+ 'Boris',
+ 'Bruno',
+ 'Burkhardt',
+ 'Christian',
+ 'Christoph',
+ 'Christopher',
+ 'Claudius',
+ 'Clemens',
+ 'Conrad',
+ 'Corbinian',
+ 'Damian',
+ 'Daniel',
+ 'David',
+ 'Dennis',
+ 'Detlef',
+ 'Dieter',
+ 'Dietrich',
+ 'Dimitri',
+ 'Dirk',
+ 'Dominik',
+ 'Dustin',
+ 'Eberhard',
+ 'Eckart',
+ 'Edgar',
+ 'Eike',
+ 'Emil',
+ 'Ernst',
+ 'Erwin',
+ 'Eugen',
+ 'Fabian',
+ 'Ferdinand',
+ 'Florian',
+ 'Franz',
+ 'Friedrich',
+ 'Fritz',
+ 'Geralt',
+ 'Gerd',
+ 'Gerhard',
+ 'Gernot',
+ 'Gisbert',
+ 'Gregor',
+ 'Guido',
+ 'Gunnar',
+ 'Gustav',
+ 'Günther',
+ 'Hannes',
+ 'Hans',
+ 'Harald',
+ 'Heinz',
+ 'Helmut',
+ 'Hermann',
+ 'Horst',
+ 'Ingo',
+ 'Jan',
+ 'Joachim',
+ 'Johannes',
+ 'Jonas',
+ 'Josef',
+ 'Julian',
+ 'Kai',
+ 'Karl',
+ 'Klaus',
+ 'Konrad',
+ 'Kurt',
+ 'Lars',
+ 'Leo',
+ 'Leonard',
+ 'Lorenz',
+ 'Lothar',
+ 'Lukas',
+ 'Manfred',
+ 'Manuel',
+ 'Mark',
+ 'Martin',
+ 'Matthias',
+ 'Max',
+ 'Maximilian',
+ 'Michael',
+ 'Moritz',
+ 'Nico',
+ 'Nicolas',
+ 'Niko',
+ 'Nils',
+ 'Norbert',
+ 'Olaf',
+ 'Oliver',
+ 'Oswald',
+ 'Otto',
+ 'Pascal',
+ 'Patrick',
+ 'Paul',
+ 'Peter',
+ 'Philipp',
+ 'Pierre',
+ 'Raimund',
+ 'Rainer',
+ 'Reiner',
+ 'Reinhard',
+ 'Richard',
+ 'Robert',
+ 'Rolf',
+ 'Roman',
+ 'Rudolf',
+ 'Rupert',
+ 'Samuel',
+ 'Sascha',
+ 'Sebastian',
+ 'Sepp',
+ 'Siegfried',
+ 'Simon',
+ 'Stefan',
+ 'Stephan',
+ 'Sven',
+ 'Sören',
+ 'Thilo',
+ 'Thorsten',
+ 'Till',
+ 'Timo',
+ 'Ulf',
+ 'Ulrich',
+ 'Uwe',
+ 'Valentin',
+ 'Viktor',
+ 'Volker',
+ 'Waldemar',
+ 'Walther',
+ 'Wendelin',
+ 'Wilhelm',
+ 'Willi',
+ 'Xaver',
+ 'Zacharias'
+];
+
+const lastNames = [
+ 'Arnold',
+ 'Bachmann',
+ 'Bauer',
+ 'Becker',
+ 'Beyer',
+ 'Boger',
+ 'Bogner',
+ 'Braun',
+ 'Bruckmann',
+ 'Brugger',
+ 'Bäcker',
+ 'Böhm',
+ 'Claßen',
+ 'Diener',
+ 'Dietrich',
+ 'Dittrich',
+ 'Dreher',
+ 'Fiedler',
+ 'Fink',
+ 'Fischer',
+ 'Frank',
+ 'Frick',
+ 'Friedmann',
+ 'Fuchs',
+ 'Graf',
+ 'Grün',
+ 'Hahn',
+ 'Hartmann',
+ 'Hausmann',
+ 'Jacobi',
+ 'Jansen',
+ 'Jung',
+ 'Jäger',
+ 'Kaiser',
+ 'Karlmann',
+ 'Kienzle',
+ 'Kirch',
+ 'Koch',
+ 'Kowalski',
+ 'Kraus',
+ 'Krämer',
+ 'Köhler',
+ 'Lehmann',
+ 'Maier',
+ 'Maler',
+ 'Martin',
+ 'Meier',
+ 'Merkle',
+ 'Meyer',
+ 'Michaelis',
+ 'Möller',
+ 'Müller',
+ 'Neumann',
+ 'Noack',
+ 'Nowak',
+ 'Obermeyer',
+ 'Perlmann',
+ 'Peters',
+ 'Pfarrer',
+ 'Pfeifer',
+ 'Pfeiffer',
+ 'Pietsch',
+ 'Pohl',
+ 'Prinz',
+ 'Raab',
+ 'Richter',
+ 'Roßmann',
+ 'Ruf',
+ 'Sauer',
+ 'Schinacher',
+ 'Schmid',
+ 'Schmidt',
+ 'Schmitt',
+ 'Schneider',
+ 'Scholz',
+ 'Schröder',
+ 'Schubert',
+ 'Schulz',
+ 'Schulze',
+ 'Schumann',
+ 'Schuster',
+ 'Schwarz',
+ 'Schäfer',
+ 'Seidel',
+ 'Simon',
+ 'Singer',
+ 'Specht',
+ 'Strauss',
+ 'Strauß',
+ 'Vogel',
+ 'Vogt',
+ 'Wagner',
+ 'Weber',
+ 'Weiß',
+ 'Wenzler',
+ 'Werner',
+ 'Winkler',
+ 'Winter',
+ 'Wolf',
+ 'Wolff',
+ 'Zimmermann'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/India/bundle.ts b/packages/plugins/src/countries/India/bundle.ts
new file mode 100644
index 000000000..f1e1d5524
--- /dev/null
+++ b/packages/plugins/src/countries/India/bundle.ts
@@ -0,0 +1,615 @@
+/**
+ * @author Fareez Ahamed
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const India: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'india',
+ regionNames: i18n.regionNames,
+ continent: 'asia',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['+91 xxxxxxxxxx', '0xx-xxxxxxxx', '0xxx-xxxxxxx', '0xxxx-xxxxxx']
+ }
+ },
+
+ regions: [
+ {
+ regionName: 'Andhra Pradesh',
+ regionShort: 'AP',
+ regionSlug: 'andhra_pradesh',
+ weight: 1314,
+ cities: [
+ 'Adoni',
+ 'Anantapur',
+ 'Bhimavaram',
+ 'Chittoor',
+ 'Cuddapah',
+ 'Eluru',
+ 'Gudivada',
+ 'Guntakal',
+ 'Guntur',
+ 'Hindupur',
+ 'Hyderabad',
+ 'Kakinada',
+ 'Karimnagar',
+ 'Khammam',
+ 'Kukatpalle',
+ 'Kurnool',
+ 'Lalbahadur Nagar',
+ 'Machilipatnam',
+ 'Mahbubnagar',
+ 'Malkajgiri',
+ 'Nandyal',
+ 'Nellore',
+ 'Nizamabad',
+ 'Ongole',
+ 'Proddatur',
+ 'Qutubullapur',
+ 'Rajahmundry',
+ 'Ramagundam',
+ 'Secunderabad',
+ 'Tenali',
+ 'Tirupati',
+ 'Vijayawada',
+ 'Vishakhapatnam',
+ 'Vizianagaram',
+ 'Warangal'
+ ]
+ },
+ {
+ regionName: 'Arunachal Pradesh',
+ regionShort: 'AR',
+ regionSlug: 'arunachal_pradesh',
+ weight: 21,
+ cities: ['Itanagar']
+ },
+ {
+ regionName: 'Assam',
+ regionShort: 'AS',
+ regionSlug: 'assam',
+ weight: 484,
+ cities: ['Guwahati', 'Dibrugarh', 'Silchar', 'Nagaon']
+ },
+ {
+ regionName: 'Bihar',
+ regionShort: 'BR',
+ regionSlug: 'bihar',
+ weight: 1611,
+ cities: [
+ 'Patna',
+ 'Gaya',
+ 'Bhagalpur',
+ 'Muzaffarpur',
+ 'Darbhanga',
+ 'Bihar Sharif',
+ 'Arrah',
+ 'Katihar',
+ 'Munger',
+ 'Chapra',
+ 'Sasaram',
+ 'Dehri',
+ 'Bettiah'
+ ]
+ },
+ {
+ regionName: 'Chhattisgarh',
+ regionShort: 'CT',
+ regionSlug: 'chhattisgarh',
+ weight: 396,
+ cities: ['Raipur', 'Bhilai', 'Bilaspur', 'Durg', 'Raj Nandgaon', 'Korba', 'Raigarh']
+ },
+ {
+ regionName: 'Goa',
+ regionShort: 'GA',
+ regionSlug: 'goa',
+ weight: 23,
+ cities: ['Panjim']
+ },
+ {
+ regionName: 'Gujarat',
+ regionShort: 'GJ',
+ regionSlug: 'gujarat',
+ weight: 937,
+ cities: [
+ 'Ahmedabad',
+ 'Surat',
+ 'Vadodara',
+ 'Rajkot',
+ 'Bhavnagar',
+ 'Jamnagar',
+ 'Nadiad',
+ 'Bharuch',
+ 'Junagadh',
+ 'Navsari',
+ 'Gandhinagar',
+ 'Veraval',
+ 'Porbandar',
+ 'Anand',
+ 'Surendranagar',
+ 'Gandhidham',
+ 'Bhuj',
+ 'Godhra',
+ 'Patan',
+ 'Morvi',
+ 'Vejalpur'
+ ]
+ },
+ {
+ regionName: 'Haryana',
+ regionShort: 'HR',
+ regionSlug: 'haryana',
+ weight: 394,
+ cities: [
+ 'Ambala',
+ 'Ambala Sadar',
+ 'Bhiwani',
+ 'Faridabad',
+ 'Gurgaon',
+ 'Hisar',
+ 'Karnal',
+ 'Panipat',
+ 'Rohtak',
+ 'Sirsa',
+ 'Sonipat',
+ 'Yamuna Nagar'
+ ]
+ },
+ {
+ regionName: 'Himachal Pradesh',
+ regionShort: 'HP',
+ regionSlug: 'himachal_pradesh',
+ weight: 106,
+ cities: ['Shimla']
+ },
+ {
+ regionName: 'Jammu and Kashmir',
+ regionShort: 'JK',
+ regionSlug: 'jammu_and_kashmir',
+ weight: 195,
+ cities: ['Jammu', 'Srinagar']
+ },
+ {
+ regionName: 'Jharkhand',
+ regionShort: 'JH',
+ regionSlug: 'jharkhand',
+ weight: 512,
+ cities: ['Bokaro Steel City', 'Dhanbad', 'Hazaribag', 'Jamshedpur', 'Mango', 'Purnea', 'Purulia', 'Ranchi']
+ },
+ {
+ regionName: 'Karnataka',
+ regionShort: 'KA',
+ regionSlug: 'karnataka',
+ weight: 949,
+ cities: [
+ 'Bangalore',
+ 'Belgaum',
+ 'Bellary',
+ 'Bidar',
+ 'Bijapur',
+ 'Davangere',
+ 'Gadag Betigeri',
+ 'Gulbarga',
+ 'Hassan',
+ 'Hospet',
+ 'Hubli',
+ 'Mandya',
+ 'Mangalore',
+ 'Mysore',
+ 'Raichur',
+ 'Shimoga',
+ 'Timkur'
+ ]
+ },
+ {
+ regionName: 'Kerala',
+ regionShort: 'KL',
+ regionSlug: 'kerala',
+ weight: 518,
+ cities: ['Allappuzha', 'Kozhikode', 'Cochin', 'Kollam', 'Palakkad', 'Thalassery', 'Trivandrum']
+ },
+ {
+ regionName: 'Madhya Pradesh',
+ regionShort: 'MP',
+ regionSlug: 'madhya_pradesh',
+ weight: 1127,
+ cities: [
+ 'Bhind',
+ 'Bhopal',
+ 'Burhanpur',
+ 'Chhindwara',
+ 'Damoh',
+ 'Dewas',
+ 'Guna',
+ 'Gwalior',
+ 'Indore',
+ 'Jabalpur',
+ 'Khandwa',
+ 'Mandasor',
+ 'Morena',
+ 'Murwara',
+ 'Ratlam',
+ 'Rewa',
+ 'Sagar',
+ 'Satna',
+ 'Shivapuri',
+ 'Ujjain',
+ 'Vidisha'
+ ]
+ },
+ {
+ regionName: 'Maharastra',
+ regionShort: 'MH',
+ regionSlug: 'maharastra',
+ weight: 1744,
+ cities: [
+ 'Achalpur',
+ 'Ahmadnagar',
+ 'Akola',
+ 'Amravati',
+ 'Bhir',
+ 'Bhiwandi',
+ 'Bhusawal',
+ 'Chandrapur',
+ 'Dhule',
+ 'Gondiya',
+ 'Ichalkaranji',
+ 'Jalgaon',
+ 'Jalna',
+ 'Kalyan',
+ 'Kolhapur',
+ 'Latur',
+ 'Malegaon',
+ 'Mira Bhayandar',
+ 'Miraj',
+ 'Mumbai',
+ 'Nagpur',
+ 'Nanded',
+ 'Nashik',
+ 'New Bombay',
+ 'Parbhani',
+ 'Pimpri-Chinchwad',
+ 'Pune',
+ 'Sangli',
+ 'Satara',
+ 'Aurangabad',
+ 'Solapur',
+ 'Thane',
+ 'Ulhasnagar',
+ 'Wardha',
+ 'Yeotmal'
+ ]
+ },
+ {
+ regionName: 'Manipur',
+ regionShort: 'MN',
+ regionSlug: 'manipur',
+ weight: 42,
+ cities: ['Imphal']
+ },
+ {
+ regionName: 'Meghalaya',
+ regionShort: 'ML',
+ regionSlug: 'meghalaya',
+ weight: 46,
+ cities: ['Shillong']
+ },
+ {
+ regionName: 'Mizoram',
+ regionShort: 'MZ',
+ regionSlug: 'mizoram',
+ weight: 17,
+ cities: ['Aizwal']
+ },
+ {
+ regionName: 'Nagaland',
+ regionShort: 'NL',
+ regionSlug: 'nagaland',
+ weight: 31,
+ cities: ['Kohima']
+ },
+ {
+ regionName: 'Odisha',
+ regionShort: 'OR',
+ regionSlug: 'odisha',
+ weight: 651,
+ cities: ['Bhubaneswar', 'Brahmapur', 'Cuttack', 'Puri', 'Raurkela', 'Raurkela Civil Township', 'Sambalpur']
+ },
+ {
+ regionName: 'Punjab',
+ regionShort: 'PB',
+ regionSlug: 'punjab',
+ weight: 430,
+ cities: [
+ 'Abohar',
+ 'Ahmadpur East',
+ 'Amritsar',
+ 'Bahawalnagar',
+ 'Bahawalpur',
+ 'Bhatinda',
+ 'Chiniot',
+ 'Chishtian Mandi',
+ 'Daska',
+ 'Dera Ghazi Khan',
+ 'Faisalabad',
+ 'Gojra',
+ 'Gujranwala',
+ 'Gujrat',
+ 'Hafizabad',
+ 'Hoshiarpur',
+ 'Jalandhar (Jullundur}',
+ 'Jaranwala',
+ 'Jhang',
+ 'Jhelum',
+ 'Kamalia',
+ 'Kamoke',
+ 'Kasur',
+ 'Khanewal',
+ 'Khanpur',
+ 'Lahore',
+ 'Ludhiana',
+ 'Mandi Bahauddin',
+ 'Mandi Burewala',
+ 'Moga',
+ 'Multan',
+ 'Muridke',
+ 'Muzaffargarh',
+ 'Okara',
+ 'Pak Pattan',
+ 'Pathankot',
+ 'Patiala',
+ 'Rahim Yar Khan',
+ 'Rawalpindi',
+ 'Sadiqabad',
+ 'Sahiwal',
+ 'Sargodha',
+ 'Sheikhupura',
+ 'Sialkot',
+ 'Vihari',
+ 'Wah',
+ 'Wazirabad'
+ ]
+ },
+ {
+ regionName: 'Rajasthan',
+ regionShort: 'RJ',
+ regionSlug: 'rajasthan',
+ weight: 1065,
+ cities: [
+ 'Ajmer',
+ 'Alwar',
+ 'Beawar',
+ 'Bharatpur',
+ 'Bhilwara',
+ 'Bikaner',
+ 'Ganganagar',
+ 'Jaipur',
+ 'Jodhpur',
+ 'Kota',
+ 'Pali',
+ 'Sikar',
+ 'Tonk',
+ 'Udaipur'
+ ]
+ },
+ {
+ regionName: 'Sikkim',
+ regionShort: 'SK',
+ regionSlug: 'sikkim',
+ weight: 9,
+ cities: ['Gangtok']
+ },
+ {
+ regionName: 'Tamil Nadu',
+ regionShort: 'TN',
+ regionSlug: 'Tamil Nadu',
+ weight: 1120,
+ cities: [
+ 'Alandur',
+ 'Ambattur',
+ 'Avadi',
+ 'Chennai',
+ 'Coimbatore',
+ 'Cuddalore',
+ 'Dindigul',
+ 'Erode',
+ 'Kanchipuram',
+ 'Kumbakonam',
+ 'Madurai',
+ 'Nagarcoil',
+ 'Neyveli',
+ 'Palayankottai',
+ 'Pallavaram',
+ 'Pudukkottai',
+ 'Rajapalaiyam',
+ 'Salem',
+ 'Tambaram',
+ 'Thanjavur',
+ 'Tiruchirapalli',
+ 'Tirunelveli',
+ 'Tiruppur',
+ 'Tiruvannamalai',
+ 'Tiruvarur',
+ 'Tiruvottiyur',
+ 'Tuticorin',
+ 'Valparai',
+ 'Vellore'
+ ]
+ },
+ {
+ regionName: 'Tripura',
+ regionShort: 'TR',
+ regionSlug: 'tripura',
+ weight: 57,
+ cities: ['Agartala']
+ },
+ {
+ regionName: 'Uttar Pradesh',
+ regionShort: 'UP',
+ regionSlug: 'uttar_pradesh',
+ weight: 3098,
+ cities: [
+ 'Agra',
+ 'Aligarh',
+ 'Allahabad',
+ 'Amroha',
+ 'Bahraich',
+ 'Banda',
+ 'Bareilly',
+ 'Budaun',
+ 'Bulandshahr',
+ 'Etawah',
+ 'Faizabad',
+ 'Farrukhabad-cum-Fatehgarh',
+ 'Fatehpur',
+ 'Firozabad',
+ 'Ghaziabad',
+ 'Gonda',
+ 'Gorakhpur',
+ 'Hapur',
+ 'Hathras',
+ 'Jaunpur',
+ 'Jhansi',
+ 'Kanpur',
+ 'Kanpur Cantonment',
+ 'Lucknow',
+ 'Mathura',
+ 'Maunath Bhanjan',
+ 'Meerut',
+ 'Meerut Cantonment',
+ 'Mirzapur-cum-Vindhyachal',
+ 'Modinagar',
+ 'Moradabad',
+ 'Muzaffarnagar',
+ 'Noida',
+ 'Orai',
+ 'Pilibhit',
+ 'Rae Bareli',
+ 'Rampur',
+ 'Saharanpur',
+ 'Sambhal',
+ 'Shahjahanpur',
+ 'Sitapur',
+ 'Unnao',
+ 'Varanasi'
+ ]
+ },
+ {
+ regionName: 'Uttarakhand',
+ regionShort: 'UT',
+ regionSlug: 'uttarakhand',
+ weight: 157,
+ cities: ['Dehradun', 'Haridwar']
+ },
+ {
+ regionName: 'West Bengal',
+ regionShort: 'WB',
+ regionSlug: 'west_bengal',
+ weight: 1418,
+ cities: [
+ 'Asansol',
+ 'Ashoknagar-Kalyangarh',
+ 'Baidyabati',
+ 'Bally',
+ 'Balurghat',
+ 'Bankura',
+ 'Bansberia',
+ 'Barahanagar',
+ 'Barasat',
+ 'Barddhaman',
+ 'Barrackpur',
+ 'Basirhat',
+ 'Berhampore',
+ 'Bhatpara',
+ 'Burnpur',
+ 'Kolkata',
+ 'Champdani',
+ 'Chandannagar',
+ 'Dabgram',
+ 'Durgapur',
+ 'Habra',
+ 'Haldia',
+ 'Halisahar',
+ 'Howrah',
+ 'Hugli-Chinsurah',
+ 'Ingraj Bazar',
+ 'Kamarhati',
+ 'Kanchrapara',
+ 'Kharagpur',
+ 'Krishnanagar',
+ 'Kulti-Barakar',
+ 'Midnapore',
+ 'Naihati',
+ 'Navadwip',
+ 'North Barrackpur',
+ 'North Dum Dum',
+ 'Panihati',
+ 'Raiganj',
+ 'Rishra',
+ 'Santipur',
+ 'Serampore',
+ 'Siliguri',
+ 'South Dum Dum',
+ 'Titagarh',
+ 'Uluberia',
+ 'Uttarpara-Kotrung'
+ ]
+ },
+ //Union Territories
+ {
+ regionName: 'Andaman and Nicobar Islands',
+ regionShort: 'AN',
+ regionSlug: 'andaman_and_nicobar_islands',
+ weight: 6,
+ cities: ['Port Blair']
+ },
+ {
+ regionName: 'Chandigarh',
+ regionShort: 'CH',
+ regionSlug: 'Chandigarh',
+ weight: 16,
+ cities: ['Chandigarh']
+ },
+ {
+ regionName: 'Dadra and Nagar Haveli',
+ regionShort: 'DN',
+ regionSlug: 'dadra_and_nagar_haveli',
+ weight: 5,
+ cities: ['Silvassa']
+ },
+ {
+ regionName: 'Daman and Diu',
+ regionShort: 'DD',
+ regionSlug: 'Daman and Diu',
+ weight: 4,
+ cities: ['Daman']
+ },
+ {
+ regionName: 'Lakshadweep',
+ regionShort: 'LD',
+ regionSlug: 'lakshadweep',
+ weight: 1,
+ cities: ['Kavaratti']
+ },
+ {
+ regionName: 'Delhi',
+ regionShort: 'DL',
+ regionSlug: 'delhi',
+ weight: 260,
+ cities: ['Delhi']
+ },
+ {
+ regionName: 'Pondicherry',
+ regionShort: 'PY',
+ regionSlug: 'pondicherry',
+ weight: 19,
+ cities: ['Pondicherry']
+ }
+ ]
+});
+
+export default India;
diff --git a/packages/plugins/src/countries/India/i18n/ar.json b/packages/plugins/src/countries/India/i18n/ar.json
new file mode 100644
index 000000000..31e75f78b
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "الهند",
+ "regionNames": "الولايات الهندية وأقاليم الاتحاد"
+}
diff --git a/packages/plugins/src/countries/India/i18n/de.json b/packages/plugins/src/countries/India/i18n/de.json
new file mode 100644
index 000000000..dfffeeaa5
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indien",
+ "regionNames": "Indische Staaten und Unionsterritorien"
+}
diff --git a/packages/plugins/src/countries/India/i18n/en.json b/packages/plugins/src/countries/India/i18n/en.json
new file mode 100644
index 000000000..166bdfb3a
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "India",
+ "regionNames": "Indian States & Union Territories"
+}
diff --git a/packages/plugins/src/countries/India/i18n/es.json b/packages/plugins/src/countries/India/i18n/es.json
new file mode 100644
index 000000000..8cd2a78c7
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "India",
+ "regionNames": "Estados indios y territorios de la Unión"
+}
diff --git a/packages/plugins/src/countries/India/i18n/fr.json b/packages/plugins/src/countries/India/i18n/fr.json
new file mode 100644
index 000000000..bc7736108
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Inde",
+ "regionNames": "États indiens et territoires de l'Union"
+}
diff --git a/packages/plugins/src/countries/India/i18n/hi.json b/packages/plugins/src/countries/India/i18n/hi.json
new file mode 100644
index 000000000..3938a79c4
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "भारत",
+ "regionNames": "भारतीय राज्य और केंद्र शासित प्रदेश"
+}
diff --git a/packages/plugins/src/countries/India/i18n/ja.json b/packages/plugins/src/countries/India/i18n/ja.json
new file mode 100644
index 000000000..0e71fa87b
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "インド",
+ "regionNames": "インドの州と連邦直轄領"
+}
diff --git a/packages/plugins/src/countries/India/i18n/nl.json b/packages/plugins/src/countries/India/i18n/nl.json
new file mode 100644
index 000000000..81b5a9b2c
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indië",
+ "regionNames": "Indiase staten en territoria van de Unie"
+}
diff --git a/packages/plugins/src/countries/India/i18n/pt.json b/packages/plugins/src/countries/India/i18n/pt.json
new file mode 100644
index 000000000..4eef63a26
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Índia",
+ "regionNames": "Regiões indianas"
+}
diff --git a/packages/plugins/src/countries/India/i18n/ru.json b/packages/plugins/src/countries/India/i18n/ru.json
new file mode 100644
index 000000000..9ac25c151
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Индия",
+ "regionNames": "Индийские штаты и союзные территории"
+}
diff --git a/packages/plugins/src/countries/India/i18n/ta.json b/packages/plugins/src/countries/India/i18n/ta.json
new file mode 100644
index 000000000..df7650dd7
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "இந்தியா",
+ "regionNames": "இந்திய மாநிலங்கள் & யூனியன் பிரதேசங்கள்"
+}
diff --git a/packages/plugins/src/countries/India/i18n/zh.json b/packages/plugins/src/countries/India/i18n/zh.json
new file mode 100644
index 000000000..337c2b5f3
--- /dev/null
+++ b/packages/plugins/src/countries/India/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "印度",
+ "regionNames": "印度各邦和联邦直辖区"
+}
diff --git a/packages/plugins/src/countries/India/names.ts b/packages/plugins/src/countries/India/names.ts
new file mode 100644
index 000000000..eb723acea
--- /dev/null
+++ b/packages/plugins/src/countries/India/names.ts
@@ -0,0 +1,622 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Abha',
+ 'Aditi',
+ 'Aisha',
+ 'Aishwarya',
+ 'Akanksha',
+ 'Amala',
+ 'Amandeep',
+ 'Amardeep',
+ 'Amarjeet',
+ 'Anima',
+ 'Anisha',
+ 'Anjali',
+ 'Aparajita',
+ 'Aparna',
+ 'Apurva',
+ 'Aradhana',
+ 'Archana',
+ 'Aruna',
+ 'Arushi',
+ 'Arya',
+ 'Asha',
+ 'Avani',
+ 'Azra',
+ 'Bala',
+ 'Balwinder',
+ 'Bhavana',
+ 'Chanda',
+ 'Chandra',
+ 'Devi',
+ 'Devika',
+ 'Diksha',
+ 'Dipa',
+ 'Dipali',
+ 'Dipti',
+ 'Disha',
+ 'Divya',
+ 'Diya',
+ 'Drishti',
+ 'Durga',
+ 'Esha',
+ 'Ezhil',
+ 'Fariha',
+ 'Gauri',
+ 'Gita',
+ 'Grishma',
+ 'Gul',
+ 'Gulbadan',
+ 'Gulrukh',
+ 'Gurdeep',
+ 'Gurmeet',
+ 'Hema',
+ 'Ila',
+ 'Inderpal',
+ 'Indira',
+ 'Indrani',
+ 'Indu',
+ 'Indumathi',
+ 'Isha',
+ 'Ishani',
+ 'Ishita',
+ 'Jaswinder',
+ 'Jaya',
+ 'Jayashri',
+ 'Jyoti',
+ 'Jyotsna',
+ 'Kajal',
+ 'Kala',
+ 'Kali',
+ 'Kalpana',
+ 'Kalyani',
+ 'Kamakshi',
+ 'Kamala',
+ 'Kamani',
+ 'Kanchana',
+ 'Kanta',
+ 'Kanti',
+ 'Karishma',
+ 'Kashi',
+ 'Kaur',
+ 'Kavita',
+ 'Khurshid',
+ 'Khushi',
+ 'Kiran',
+ 'Kirtida',
+ 'Laboni',
+ 'Lakshmi',
+ 'Lalita',
+ 'Lata',
+ 'Lavanya',
+ 'Lila',
+ 'Lilavati',
+ 'Lina',
+ 'Madhu',
+ 'Madhur',
+ 'Madhuri',
+ 'Mala',
+ 'Malati',
+ 'Malani',
+ 'Mandeep',
+ 'Manjeet',
+ 'Manju',
+ 'Manjula',
+ 'Manjusha',
+ 'Maya',
+ 'Mina',
+ 'Minali',
+ 'Mira',
+ 'Mitra',
+ 'Mohini',
+ 'Mridula',
+ 'Mukta',
+ 'Nalini',
+ 'Namrata',
+ 'Nandita',
+ 'Nasim',
+ 'Nasrin',
+ 'Navdeep',
+ 'Navneet',
+ 'Neha',
+ 'Nida',
+ 'Nikita',
+ 'Nila',
+ 'Nirupama',
+ 'Nisha',
+ 'Nishat',
+ 'Nitika',
+ 'Nitya',
+ 'Nur',
+ 'Padma',
+ 'Padmini',
+ 'Parvati',
+ 'Prachi',
+ 'Pratibha',
+ 'Pratima',
+ 'Pritha',
+ 'Priti',
+ 'Priya',
+ 'Priyanka',
+ 'Puja',
+ 'Purnima',
+ 'Pushpa',
+ 'Rachana',
+ 'Radha',
+ 'Rajani',
+ 'Rajkumari',
+ 'Rajni',
+ 'Rani',
+ 'Rashmi',
+ 'Rati',
+ 'Ratna',
+ 'Reshmi',
+ 'Reva',
+ 'Richa',
+ 'Rina',
+ 'Ritka',
+ 'Ritu',
+ 'Riya',
+ 'Roshan',
+ 'Roshni',
+ 'Rupa',
+ 'Rupinder',
+ 'Sabeen',
+ 'Saira',
+ 'Sakshi',
+ 'Sandhya',
+ 'Sanjana',
+ 'Saraswati',
+ 'Sarita',
+ 'Savitri',
+ 'Shabnam',
+ 'Shahnaz',
+ 'Shailaja',
+ 'Shakti',
+ 'Shakuntala',
+ 'Shanta',
+ 'Shanti',
+ 'Sharmila',
+ 'Shashi',
+ 'Shikha',
+ 'Shila',
+ 'Shivali',
+ 'Shobha',
+ 'Shreya',
+ 'Shweta',
+ 'Shyama',
+ 'Siddhi',
+ 'Sima',
+ 'Sita',
+ 'Sitara',
+ 'Sneha',
+ 'Sona',
+ 'Sonal',
+ 'Sonam',
+ 'Sukhdeep',
+ 'Sulabha',
+ 'Sultana',
+ 'Suman',
+ 'Sumati',
+ 'Sunita',
+ 'Suniti',
+ 'Sushila',
+ 'Swapna',
+ 'Swarna',
+ 'Tanu',
+ 'Tanvi',
+ 'Tara',
+ 'Tejal',
+ 'Thamarai',
+ 'Trishna',
+ 'Uma',
+ 'Upasana',
+ 'Urvi',
+ 'Uttara',
+ 'Vaishnavi',
+ 'Varsha',
+ 'Vasuda',
+ 'Vasudha',
+ 'Vasundhara',
+ 'Veda',
+ 'Vidya',
+ 'Vijaya'
+];
+
+const maleNames = [
+ 'Abbas',
+ 'Abdul',
+ 'Abhay',
+ 'Abhijit',
+ 'Abhilash',
+ 'Abhinav',
+ 'Abhishek',
+ 'Adil',
+ 'Aditya',
+ 'Adnan',
+ 'Agni',
+ 'Ahmad',
+ 'Ajay',
+ 'Ajit',
+ 'Akash',
+ 'Akbar',
+ 'Akhil',
+ 'Akshay',
+ 'Ali',
+ 'Amandeep',
+ 'Amar',
+ 'Amardeep',
+ 'Amarjeet',
+ 'Amin',
+ 'Amir',
+ 'Amit',
+ 'Amitabh',
+ 'Amrit',
+ 'Anand',
+ 'Anbu',
+ 'Anik',
+ 'Aniket',
+ 'Anil',
+ 'Aniruddha',
+ 'Anish',
+ 'Ankit',
+ 'Ankur',
+ 'Anuj',
+ 'Anup',
+ 'Anupam',
+ 'Apurva',
+ 'Aravind',
+ 'Arif',
+ 'Arijit',
+ 'Aritra',
+ 'Aruna',
+ 'Arya',
+ 'Asad',
+ 'Ashwin',
+ 'Asim',
+ 'Aswathi',
+ 'Avinash',
+ 'Azad',
+ 'Azhar',
+ 'Aziz',
+ 'Babur',
+ 'Bala',
+ 'Balakrishna',
+ 'Balwinder',
+ 'Bilal',
+ 'Chanda',
+ 'Chandan',
+ 'Chandra',
+ 'Chandrakant',
+ 'Chetan',
+ 'Chiranjvi',
+ 'Darshan',
+ 'Dayaram',
+ 'Dev',
+ 'Devadas',
+ 'Dhananjay',
+ 'Dharma',
+ 'Dhaval',
+ 'Durai',
+ 'Durga',
+ 'Eshil',
+ 'Farhan',
+ 'Farid',
+ 'Ghulam',
+ 'Govinda',
+ 'Gul',
+ 'Gurdeep',
+ 'Gurmeet',
+ 'Hardeep',
+ 'Hari',
+ 'Harsha',
+ 'Harshad',
+ 'Harshal',
+ 'Hasan',
+ 'Hassan',
+ 'Imtiyaz',
+ 'Inderpal',
+ 'Indra',
+ 'Indrajit',
+ 'Isha',
+ 'Jagit',
+ 'Jahangir',
+ 'Jaswinder',
+ 'Javed',
+ 'Jaya',
+ 'Jayanta',
+ 'Jayendra',
+ 'Jayesh',
+ 'Jaywant',
+ 'Jitendra',
+ 'Jyoti',
+ 'Kailash',
+ 'Kali',
+ 'Kalyan',
+ 'Kamala',
+ 'Kanta',
+ 'Kanti',
+ 'Karan',
+ 'Kavi',
+ 'Khan',
+ 'Khurshd',
+ 'Kiran',
+ 'Kishor',
+ 'Krishna',
+ 'Kshitij',
+ 'Kuldeep',
+ 'Lakshmi',
+ 'Lal',
+ 'Lochan',
+ 'Madhu',
+ 'Madhukar',
+ 'Madhur',
+ 'Mahendra',
+ 'Mahmud',
+ 'Mamun',
+ 'Manas',
+ 'Mandeep',
+ 'Mani',
+ 'Maninder',
+ 'Manish',
+ 'Manjeet',
+ 'Manu',
+ 'Maqsud',
+ 'Maruf',
+ 'Mayur',
+ 'Mitra',
+ 'Mitul',
+ 'Mohandas',
+ 'Muhammad',
+ 'Mukul',
+ 'Murad',
+ 'Murali',
+ 'Murugan',
+ 'Nadim',
+ 'Nagendra',
+ 'Nanda',
+ 'Narayana',
+ 'Narendra',
+ 'Nasim',
+ 'Navdeep',
+ 'Navin',
+ 'Navneet',
+ 'Nikhil',
+ 'Nilam',
+ 'Ninad',
+ 'Niraj',
+ 'Nirav',
+ 'Nirmal',
+ 'Nishant',
+ 'Nishat',
+ 'Nitin',
+ 'Nitya',
+ 'Nur',
+ 'Padma',
+ 'Pallav',
+ 'Parminder',
+ 'Partha',
+ 'Prabhat',
+ 'Prabhu',
+ 'Prabodh',
+ 'Pradip',
+ 'Prakash',
+ 'Pran',
+ 'Pranay',
+ 'Prasad',
+ 'Prasanna',
+ 'Prasenjit',
+ 'Pratap',
+ 'Pratik',
+ 'Pravin',
+ 'Prem',
+ 'Punit',
+ 'Qasim',
+ 'Radha',
+ 'Rafiq',
+ 'Raghu',
+ 'Rahul',
+ 'Raj',
+ 'Raja',
+ 'Rajani',
+ 'Rajendra',
+ 'Rajesh',
+ 'Rajib',
+ 'Rajnish',
+ 'Rakesh',
+ 'Rama',
+ 'Ramachandra',
+ 'Rana',
+ 'Ranjit',
+ 'Rashmi',
+ 'Ratna',
+ 'Ravi',
+ 'Ravindra',
+ 'Rishi',
+ 'Rohan',
+ 'Rohit',
+ 'Roshan',
+ 'Rupinder',
+ 'Sachin',
+ 'Samir',
+ 'Sandip',
+ 'Sanjit',
+ 'Sanjiv',
+ 'Saral',
+ 'Sardar',
+ 'Sarvesh',
+ 'Shahid',
+ 'Shahjahan',
+ 'Shahnaz',
+ 'Shahzad',
+ 'Shakti',
+ 'Shandar',
+ 'Shantanu',
+ 'Sharif',
+ 'Sharma',
+ 'Shashi',
+ 'Shekar',
+ 'Sher',
+ 'Shiva',
+ 'Shresth',
+ 'Shrinivas',
+ 'Shrivatsa',
+ 'Shyama',
+ 'Shyamal',
+ 'Siddhartha',
+ 'Singh',
+ 'Sonam',
+ 'Subhash',
+ 'Subrahmanya',
+ 'Sudarshan',
+ 'Sudhir',
+ 'Suahil',
+ 'Sujay',
+ 'Sukhbir',
+ 'Sukhdeep',
+ 'Sultan',
+ 'Suman',
+ 'Sumantra',
+ 'Sumit',
+ 'Sunil',
+ 'Suraj',
+ 'Surendra',
+ 'Surya',
+ 'Sushila',
+ 'Swapan',
+ 'Swapnil',
+ 'Swarna',
+ 'Tamanna',
+ 'Tushar',
+ 'Uttara',
+ 'Vasu',
+ 'Vijaya',
+ 'Vimal',
+ 'Vinay',
+ 'Vipin',
+ 'Vipul',
+ 'Vishal',
+ 'Vishnu',
+ 'Vivek',
+ 'Yash',
+ 'Yasir',
+ 'Zafar',
+ 'Zahid',
+ 'Zahir',
+ 'Zaman',
+ 'Zawar'
+];
+
+const lastNames = [
+ 'Aggarwal',
+ 'Anand',
+ 'Arun',
+ 'Bhat',
+ 'Bhatt',
+ 'Chakrabarti',
+ 'Chande',
+ 'Chander',
+ 'Chandra',
+ 'Chandrasekar',
+ 'Charan',
+ 'Chaudhary',
+ 'Chauhan',
+ 'Darsha',
+ 'Dhawan',
+ 'Dutta',
+ 'Engineer',
+ 'Gandhi',
+ 'Ganesh',
+ 'Goel',
+ 'Jai',
+ 'Jana',
+ 'Jindal',
+ 'Joshi',
+ 'Kapoor',
+ 'Kishore',
+ 'Krishnamurthy',
+ 'Kumar',
+ 'Lal',
+ 'Lalit',
+ 'Lata',
+ 'Madan',
+ 'Mahajan',
+ 'Malhotra',
+ 'Malik',
+ 'Manju',
+ 'Manohar',
+ 'Mati',
+ 'Meena',
+ 'Mehra',
+ 'Mehta',
+ 'Mittal',
+ 'Muthu',
+ 'Nagpal',
+ 'Nara',
+ 'Naran',
+ 'Narang',
+ 'Narayan',
+ 'Nath',
+ 'Neel',
+ 'Neela',
+ 'Neelam',
+ 'Nigam',
+ 'Nirmal',
+ 'Nita',
+ 'Pal',
+ 'Patel',
+ 'Pawan',
+ 'Persaud',
+ 'Prasad',
+ 'Punj',
+ 'Puri',
+ 'Rai',
+ 'Rajagopal',
+ 'Rajan',
+ 'Raje',
+ 'Raji',
+ 'Raman',
+ 'Rana',
+ 'Ranga',
+ 'Rastogi',
+ 'Roy',
+ 'Sahni',
+ 'Sai',
+ 'Saini',
+ 'Samuel',
+ 'Sandeep',
+ 'Sara',
+ 'Saxena',
+ 'Sehgal',
+ 'Sen',
+ 'Sethi',
+ 'Shan',
+ 'Sharma',
+ 'Soni',
+ 'Srini',
+ 'Srivas',
+ 'Srivastav',
+ 'Srivastava',
+ 'Subram',
+ 'Subramani',
+ 'Subramanian',
+ 'Sudha',
+ 'Suri',
+ 'Swami',
+ 'Tyagi',
+ 'Uddin',
+ 'Veena',
+ 'Veer',
+ 'Verma',
+ 'Vijaya',
+ 'Vish'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Indonesia/bundle.ts b/packages/plugins/src/countries/Indonesia/bundle.ts
new file mode 100644
index 000000000..114fad5cf
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/bundle.ts
@@ -0,0 +1,265 @@
+/**
+ * Sources:
+ * https://en.wikipedia.org/wiki/Provinces_of_Indonesia
+ * https://en.wikipedia.org/wiki/List_of_Indonesian_cities_by_population
+ *
+ * @package Countries
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Indonesia: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'indonesia',
+ regionNames: i18n.regionNames,
+ continent: 'asia',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['62 Xx xxx xxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Aceh',
+ regionShort: 'AC',
+ regionSlug: 'aceh',
+ weight: 50,
+ cities: ['Banda Aceh']
+ },
+ {
+ regionName: 'Bali',
+ regionShort: 'BA',
+ regionSlug: 'bali',
+ weight: 41,
+ cities: ['Denpasar']
+ },
+ {
+ regionName: 'Bangka Belitung Islands',
+ regionShort: 'BB',
+ regionSlug: 'babel',
+ weight: 14,
+ cities: ['Pangkalpinang']
+ },
+ {
+ regionName: 'Banten',
+ regionShort: 'BT',
+ regionSlug: 'banten',
+ weight: 119,
+ cities: ['Serang']
+ },
+ {
+ regionName: 'Bengkulu',
+ regionShort: 'BE',
+ regionSlug: 'bengkulu',
+ weight: 19,
+ cities: ['Bengkulu']
+ },
+ {
+ regionName: 'Central Java',
+ regionShort: 'JT',
+ regionSlug: 'central_java',
+ weight: 338,
+ cities: ['Semarang']
+ },
+ {
+ regionName: 'Central Kalimantan',
+ regionShort: 'KT',
+ regionSlug: 'central_kalimantan',
+ weight: 25,
+ cities: ['Palangka Raya']
+ },
+ {
+ regionName: 'Central Sulawesi',
+ regionShort: 'ST',
+ regionSlug: 'central_sulawesi',
+ weight: 29,
+ cities: ['Palu']
+ },
+ {
+ regionName: 'East Java',
+ regionShort: 'JI',
+ regionSlug: 'east_java',
+ weight: 388,
+ cities: ['Surabaya', 'Malang', 'Kediri', 'Probolinggo', 'Pasuruan', 'Madiun', 'Batu', 'Blitar', 'Mojokerto']
+ },
+ {
+ regionName: 'East Kalimantan',
+ regionShort: 'KI',
+ regionSlug: 'east_kalimantan',
+ weight: 34,
+ cities: ['Samarinda', 'Balikpapan', 'Bontang']
+ },
+ {
+ regionName: 'East Nusa Tenggara',
+ regionShort: 'NT',
+ regionSlug: 'east_nusa_tenggara',
+ weight: 51,
+ cities: ['Kupang']
+ },
+ {
+ regionName: 'Gorontalo',
+ regionShort: 'GO',
+ regionSlug: 'gorontalo',
+ weight: 11,
+ cities: ['Gorontalo']
+ },
+ {
+ regionName: 'Special Capital Region of Jakarta',
+ regionShort: 'JK',
+ regionSlug: 'jakarta_region',
+ weight: 102,
+ cities: ['Jakarta', 'East Jakarta', 'West Jakarta', 'South Jakarta', 'North Jakarta', 'Central Jakarta']
+ },
+ {
+ regionName: 'Jambi',
+ regionShort: 'JA',
+ regionSlug: 'jambi',
+ weight: 34,
+ cities: ['Jambi', 'Sungai Penuh']
+ },
+ {
+ regionName: 'Lampung',
+ regionShort: 'LA',
+ regionSlug: 'lampung',
+ weight: 81,
+ cities: ['Bandar Lampung', 'Metro']
+ },
+ {
+ regionName: 'Maluku',
+ regionShort: 'MA',
+ regionSlug: 'maluku',
+ weight: 17,
+ cities: ['Ambon', 'Tual']
+ },
+ {
+ regionName: 'North Kalimantan',
+ regionShort: 'KU',
+ regionSlug: 'north_kalimantan',
+ weight: 6,
+ cities: ['Tarakan']
+ },
+ {
+ regionName: 'North Maluku',
+ regionShort: 'MU',
+ regionSlug: 'north_maluku',
+ weight: 12,
+ cities: ['Ternate', 'Tidore']
+ },
+ {
+ regionName: 'North Sulawesi',
+ regionShort: 'SA',
+ regionSlug: 'north_sulawesi',
+ weight: 24,
+ cities: ['Manado', 'Bitung', 'Kotamobagu', 'Tomohon']
+ },
+ {
+ regionName: 'North Sumatra',
+ regionShort: 'SU',
+ regionSlug: 'north_sumatra',
+ weight: 139,
+ cities: ['Medan', 'Pematangsiantar', 'Binjai', 'Padang Sidempuan', 'Tebing Tinggi', 'Tanjungbalai', 'Gunungsitoli', 'Sibolga']
+ },
+ {
+ regionName: 'Papua',
+ regionShort: 'PA',
+ regionSlug: 'papua',
+ weight: 31,
+ cities: ['Jayapura']
+ },
+ {
+ regionName: 'Riau',
+ regionShort: 'RI',
+ regionSlug: 'riau',
+ weight: 63,
+ cities: ['Pekanbaru', 'Dumai']
+ },
+ {
+ regionName: 'Riau Islands',
+ regionShort: 'KR',
+ regionSlug: 'riau_islands',
+ weight: 20,
+ cities: ['Tanjung Pinang']
+ },
+ {
+ regionName: 'Southeast Sulawesi',
+ regionShort: 'SG',
+ regionSlug: 'southeast_sulawesi',
+ weight: 25,
+ cities: ['Kendari', 'Baubau']
+ },
+ {
+ regionName: 'South Kalimantan',
+ regionShort: 'KS',
+ regionSlug: 'south_kalimantan',
+ weight: 40,
+ cities: ['Banjarmasin', 'Banjarbaru']
+ },
+ {
+ regionName: 'South Sulawesi',
+ regionShort: 'SN',
+ regionSlug: 'south_sulawesi',
+ weight: 85,
+ cities: ['Makassar', 'Palopo', 'Parepare']
+ },
+ {
+ regionName: 'South Sumatra',
+ regionShort: 'SS',
+ regionSlug: 'south_sumatra',
+ weight: 80,
+ cities: ['Palembang']
+ },
+ {
+ regionName: 'West Java',
+ regionShort: 'JB',
+ regionSlug: 'west_java',
+ weight: 467,
+ cities: ['Bandung', 'Bekasi', 'Depok', 'Bogor', 'Tasikmalaya', 'Cimahi', 'Sukabumi', 'Cirebon', 'Banjar']
+ },
+ {
+ regionName: 'West Kalimantan',
+ regionShort: 'KB',
+ regionSlug: 'west_kalimantan',
+ weight: 48,
+ cities: ['Pontianak', 'Singkawang']
+ },
+ {
+ regionName: 'West Nusa Tenggara',
+ regionShort: 'NB',
+ regionSlug: 'west_nusa_tenggara',
+ weight: 48,
+ cities: ['Mataram', 'Bima']
+ },
+ {
+ regionName: 'West Papua',
+ regionShort: 'PB',
+ regionSlug: 'west_papua',
+ weight: 9,
+ cities: ['Manokwari']
+ },
+ {
+ regionName: 'West Sulawesi',
+ regionShort: 'SR',
+ regionSlug: 'west_sulawesi',
+ weight: 13,
+ cities: ['Mamuju']
+ },
+ {
+ regionName: 'West Sumatra',
+ regionShort: 'SB',
+ regionSlug: 'west_sumatra',
+ weight: 52,
+ cities: ['Padang', 'Payakumbuh', 'Bukittinggi', 'Pariaman', 'Solok', 'Sawahlunto', 'Padang Panjang']
+ },
+ {
+ regionName: 'Special Region of Yogyakarta',
+ regionShort: 'YO',
+ regionSlug: 'yogyakarta',
+ weight: 37,
+ cities: ['Yogyakarta']
+ }
+ ]
+});
+
+export default Indonesia;
diff --git a/packages/plugins/src/countries/Indonesia/i18n/ar.json b/packages/plugins/src/countries/Indonesia/i18n/ar.json
new file mode 100644
index 000000000..2b76378fd
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "إندونيسيا",
+ "regionNames": "المقاطعات الاندونيسية"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/de.json b/packages/plugins/src/countries/Indonesia/i18n/de.json
new file mode 100644
index 000000000..4fba2e94c
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indonesien",
+ "regionNames": "Indonesische Provinzen"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/en.json b/packages/plugins/src/countries/Indonesia/i18n/en.json
new file mode 100644
index 000000000..ba6fa982c
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indonesia",
+ "regionNames": "Indonesian provinces"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/es.json b/packages/plugins/src/countries/Indonesia/i18n/es.json
new file mode 100644
index 000000000..3d81778b4
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indonesia",
+ "regionNames": "Provincias de Indonesia"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/fr.json b/packages/plugins/src/countries/Indonesia/i18n/fr.json
new file mode 100644
index 000000000..86b5bc56a
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indonésie",
+ "regionNames": "Provinces indonésiennes"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/hi.json b/packages/plugins/src/countries/Indonesia/i18n/hi.json
new file mode 100644
index 000000000..84b61de65
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "इंडोनेशिया",
+ "regionNames": "इंडोनेशियाई प्रांत"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/ja.json b/packages/plugins/src/countries/Indonesia/i18n/ja.json
new file mode 100644
index 000000000..ca11fb952
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "インドネシア",
+ "regionNames": "インドネシアの州"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/nl.json b/packages/plugins/src/countries/Indonesia/i18n/nl.json
new file mode 100644
index 000000000..f457c24ef
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indonesië",
+ "regionNames": "Indonesische provincies"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/pt.json b/packages/plugins/src/countries/Indonesia/i18n/pt.json
new file mode 100644
index 000000000..0f2a7f2ff
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Indonésia",
+ "regionNames": "Províncias indonésias"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/ru.json b/packages/plugins/src/countries/Indonesia/i18n/ru.json
new file mode 100644
index 000000000..34771b6be
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Индонезия",
+ "regionNames": "Индонезийские провинции"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/ta.json b/packages/plugins/src/countries/Indonesia/i18n/ta.json
new file mode 100644
index 000000000..005df200e
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "இந்தோனேசியா",
+ "regionNames": "இந்தோனேசிய மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Indonesia/i18n/zh.json b/packages/plugins/src/countries/Indonesia/i18n/zh.json
new file mode 100644
index 000000000..cb5bc03f9
--- /dev/null
+++ b/packages/plugins/src/countries/Indonesia/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "印度尼西亚",
+ "regionNames": "印度尼西亚各省份"
+}
diff --git a/packages/plugins/src/countries/Ireland/bundle.ts b/packages/plugins/src/countries/Ireland/bundle.ts
new file mode 100644
index 000000000..93ab5f72d
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/bundle.ts
@@ -0,0 +1,51 @@
+/**
+ * Sources:
+ * http://en.wikipedia.org/wiki/Provinces_of_Ireland
+ *
+ * @package Countries
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Ireland: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'ireland',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Leinster',
+ regionShort: 'L',
+ regionSlug: 'leinster',
+ weight: 25,
+ cities: ['Dublin']
+ },
+ {
+ regionName: 'Ulster',
+ regionShort: 'U',
+ regionSlug: 'ulster',
+ weight: 21,
+ cities: ['Belfast']
+ },
+ {
+ regionName: 'Munster',
+ regionShort: 'M',
+ regionSlug: 'munster',
+ weight: 12,
+ cities: ['Cork']
+ },
+ {
+ regionName: 'Connacht',
+ regionShort: 'C',
+ regionSlug: 'connacht',
+ weight: 25,
+ cities: ['Galway']
+ }
+ ]
+});
+
+export default Ireland;
diff --git a/packages/plugins/src/countries/Ireland/i18n/ar.json b/packages/plugins/src/countries/Ireland/i18n/ar.json
new file mode 100644
index 000000000..f8b191b60
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "أيرلندا",
+ "regionNames": "المقاطعات الأيرلندية."
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/de.json b/packages/plugins/src/countries/Ireland/i18n/de.json
new file mode 100644
index 000000000..e4b0b80c7
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Irland",
+ "regionNames": "Irische Provinzen"
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/en.json b/packages/plugins/src/countries/Ireland/i18n/en.json
new file mode 100644
index 000000000..29e536a1f
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ireland",
+ "regionNames": "Irish provinces."
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/es.json b/packages/plugins/src/countries/Ireland/i18n/es.json
new file mode 100644
index 000000000..3ca821e7e
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Irlanda",
+ "regionNames": "provincias irlandesas"
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/fr.json b/packages/plugins/src/countries/Ireland/i18n/fr.json
new file mode 100644
index 000000000..f6cebde15
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Irlande",
+ "regionNames": "Provinces irlandaises"
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/hi.json b/packages/plugins/src/countries/Ireland/i18n/hi.json
new file mode 100644
index 000000000..b6deb479b
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "आयरलैंड",
+ "regionNames": "आयरिश प्रांत।"
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/ja.json b/packages/plugins/src/countries/Ireland/i18n/ja.json
new file mode 100644
index 000000000..c235891b7
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "アイルランド",
+ "regionNames": "アイルランドの州"
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/nl.json b/packages/plugins/src/countries/Ireland/i18n/nl.json
new file mode 100644
index 000000000..94218c2c7
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ierland",
+ "regionNames": "Ierse provincies"
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/pt.json b/packages/plugins/src/countries/Ireland/i18n/pt.json
new file mode 100644
index 000000000..26339fab5
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Irlanda",
+ "regionNames": "Províncias irlandesas."
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/ru.json b/packages/plugins/src/countries/Ireland/i18n/ru.json
new file mode 100644
index 000000000..3b08c94e4
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ирландия",
+ "regionNames": "Ирландские провинции."
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/ta.json b/packages/plugins/src/countries/Ireland/i18n/ta.json
new file mode 100644
index 000000000..f6470a162
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "அயர்லாந்து",
+ "regionNames": "ஐரிஷ் மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Ireland/i18n/zh.json b/packages/plugins/src/countries/Ireland/i18n/zh.json
new file mode 100644
index 000000000..82419c385
--- /dev/null
+++ b/packages/plugins/src/countries/Ireland/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "爱尔兰",
+ "regionNames": "爱尔兰省份"
+}
diff --git a/packages/plugins/src/countries/Italy/bundle.ts b/packages/plugins/src/countries/Italy/bundle.ts
new file mode 100644
index 000000000..74b8cc619
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/bundle.ts
@@ -0,0 +1,1199 @@
+/**
+ * @package Countries
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Italy: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'italy',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Piemonte',
+ regionShort: 'PIE',
+ regionSlug: 'piemonte',
+ weight: 7,
+ cities: [
+ 'Alessandria',
+ 'Asti',
+ 'Barbania',
+ 'Bonvicino',
+ 'Borghetto di Borbera',
+ 'Borgomasino',
+ 'Borgone Susa',
+ 'Borriana',
+ 'Caprauna',
+ 'Casanova Elvo',
+ 'Cassano Spinola',
+ 'Coassolo Torinese',
+ 'Colleretto Castelnuovo',
+ 'Crescentino',
+ 'Cressa',
+ 'Cuceglio',
+ 'Ferrere',
+ 'Ghislarengo',
+ 'Marentino',
+ 'Massello',
+ 'Melazzo',
+ 'Meugliano',
+ 'Molino dei Torti',
+ 'Mombaruzzo',
+ 'Moncrivello',
+ 'Montacuto',
+ 'Montaldo Bormida',
+ 'Monteu Roero',
+ 'Olcenengo',
+ 'Olivola',
+ 'Orta San Giulio',
+ 'Ponti',
+ 'Premeno',
+ 'Priero',
+ 'Quarona',
+ "Rocca d'Arazzo",
+ 'Roccabruna',
+ 'Rueglio',
+ 'San Maurizio Canavese',
+ "Serralunga d'Alba",
+ 'Sommariva Perno',
+ 'Strona',
+ 'Torino',
+ 'Valmacca',
+ 'Vauda Canavese',
+ 'Vespolate',
+ 'Villafalletto',
+ "Villafranca d'Asti",
+ 'Villar Pellice',
+ 'Villata'
+ ]
+ },
+
+ {
+ regionName: "Valle d'Aosta",
+ regionShort: 'VDA',
+ regionSlug: 'valledaosta',
+ weight: 1,
+ cities: [
+ 'Allein',
+ 'Antey-Saint-Andrè',
+ 'Aosta',
+ 'Arvier',
+ 'Avise',
+ 'Ayas',
+ 'Bard',
+ 'Bionaz',
+ 'Brusson',
+ 'Challand-Saint-Victor',
+ 'Chambave',
+ 'Champorcher',
+ 'Chatillon',
+ 'Donnas',
+ 'Doues',
+ 'Emarèse',
+ 'Etroubles',
+ 'Gignod',
+ 'Gressan',
+ 'Gressoney-La-Trinitè',
+ 'Gressoney-Saint-Jean',
+ 'Introd',
+ 'Issime',
+ 'La Magdeleine',
+ 'La Salle',
+ 'La Thuile',
+ 'Lillianes',
+ 'Montjovet',
+ 'Morgex',
+ 'Nus',
+ 'Oyace',
+ 'Pollein',
+ 'Pont-Saint-Martin',
+ 'Pontboset',
+ 'Pontey',
+ 'Prè-Saint-Didier',
+ 'Rhemes-Notre-Dame',
+ 'Rhemes-Saint-Georges',
+ 'Saint-Denis',
+ 'Saint-Marcel',
+ 'Saint-Nicolas',
+ 'Saint-Oyen',
+ 'Saint-Pierre',
+ 'Saint-Rhémy-en-Bosses',
+ 'Sarre',
+ 'Torgnon',
+ 'Valpelline',
+ 'Valtournenche',
+ 'Verrayes',
+ 'Verrès'
+ ]
+ },
+
+ {
+ regionName: 'Lombardia',
+ regionShort: 'LOM',
+ regionSlug: 'lombardia',
+ weight: 16,
+ cities: [
+ 'Acquafredda',
+ 'Annone di Brianza',
+ 'Asso',
+ 'Barghe',
+ 'Blevio',
+ 'Borno',
+ 'Brescia',
+ 'Calco',
+ 'Cambiago',
+ 'Caprino Bergamasco',
+ 'Casnate con Bernate',
+ "Castello dell'Acqua",
+ 'Castelmarte',
+ 'Castelseprio',
+ 'Chiari',
+ 'Cisano Bergamasco',
+ 'Civo',
+ 'Colico',
+ 'Corvino San Quirico',
+ "Crotta d'Adda",
+ 'Fino Mornasco',
+ 'Galbiate',
+ 'Gambolò',
+ 'Gavirate',
+ 'Gianico',
+ 'Lasnigo',
+ 'Lenna',
+ 'Luino',
+ 'Mantova',
+ 'Malgesso',
+ 'Milano',
+ 'Motta Visconti',
+ 'Ospedaletto Lodigiano',
+ 'Pagazzano',
+ 'Pavone del Mella',
+ 'Pero',
+ 'Pogliano Milanese',
+ 'Polpenazze del Garda',
+ 'Rea',
+ 'Ripalta Guerina',
+ 'Rudiano',
+ 'Salvirola',
+ 'San Damiano al Colle',
+ 'Sannazzaro de Burgondi',
+ 'Somma Lombardo',
+ 'Sulzano',
+ 'Torno',
+ 'Val Rezzo',
+ 'Valera Fratta',
+ 'Villa Cortese'
+ ]
+ },
+
+ {
+ regionName: 'Trentino-Alto Adige',
+ regionShort: 'TAA',
+ regionSlug: 'trentinoaltoadige',
+ weight: 2,
+ cities: [
+ 'Albiano',
+ 'Andalo',
+ 'Bedollo',
+ 'Bolzano/Bozen',
+ 'Bondo',
+ 'Borgo Valsugana',
+ 'Braies/Prags',
+ 'Campitello di Fassa',
+ 'Castello Tesino',
+ 'Centa San Nicolò',
+ 'Chiusa/Klausen',
+ 'Cles',
+ 'Curon Venosta/Graun im Vinschgau',
+ 'Daiano',
+ 'Dro',
+ 'Gargazzone/Gargazon',
+ 'Giustino',
+ 'La Valle/Wengen',
+ 'Laces/Latsch',
+ 'Lagundo/Algund',
+ 'Laives/Leifers',
+ 'Lauregno/Laurein',
+ 'Livo',
+ 'Martello/Martell',
+ 'Mezzana',
+ 'Monguelfo-Tesido/Welsberg-Taisten',
+ 'Panchià',
+ 'Pellizzano',
+ 'Pomarolo',
+ 'Preore',
+ 'Rabbi',
+ 'Rodengo/Rodeneck',
+ 'Rovereto',
+ 'San Martino in Badia/St. Martin in Thurn',
+ 'Sanzeno',
+ 'Scena/Schenna',
+ 'Sfruz',
+ 'Spormaggiore',
+ 'Terlago',
+ 'Termeno sulla strada del vino/Tramin an der Weinstrasse',
+ 'Terragnolo',
+ 'Tiarno di Sopra',
+ 'Tione di Trento',
+ 'Trento',
+ 'Valda',
+ 'Vandoies/Vintl',
+ 'Varena',
+ 'Varna/Vahrn',
+ 'Villa Agnedo',
+ 'Ziano di Fiemme'
+ ]
+ },
+
+ {
+ regionName: 'Veneto',
+ regionShort: 'VEN',
+ regionSlug: 'veneto',
+ weight: 8,
+ cities: [
+ 'Adria',
+ 'Arsiè',
+ 'Asigliano Veneto',
+ 'Bevilacqua',
+ 'Bolzano Vicentino',
+ 'Bussolengo',
+ 'Camponogara',
+ 'Castelbaldo',
+ 'Castello di Godego',
+ 'Cavaion Veronese',
+ 'Cavaso del Tomba',
+ 'Codognè',
+ 'Feltre',
+ 'Ficarolo',
+ 'Follina',
+ 'Galzignano Terme',
+ 'Grezzana',
+ 'Limena',
+ 'Lozzo Atestino',
+ 'Maser',
+ 'Monfumo',
+ 'Montebelluna',
+ 'Oderzo',
+ 'Pescantina',
+ 'Pieve di Cadore',
+ 'Piovene Rocchette',
+ 'Polesella',
+ 'Ponte San Nicolò',
+ 'Ponte nelle Alpi',
+ 'Portobuffolè',
+ 'Posina',
+ 'Pramaggiore',
+ "Romano d'Ezzelino",
+ 'San Gregorio nelle Alpi',
+ 'San Pietro Mussolino',
+ 'San Zenone degli Ezzelini',
+ "Sant'Elena",
+ "Sant'Urbano",
+ 'Selva di Cadore',
+ 'Solesino',
+ 'Sorgà',
+ 'Sossano',
+ 'Soverzene',
+ 'Sovizzo',
+ 'Spresiano',
+ 'Susegana',
+ 'Tarzo',
+ 'Tezze sul Brenta',
+ 'Venezia',
+ 'Verona'
+ ]
+ },
+
+ {
+ regionName: 'Friuli-Venezia Giulia',
+ regionShort: 'FVG',
+ regionSlug: 'friuliveneziagiulia',
+ weight: 2,
+ cities: [
+ 'Amaro',
+ 'Attimis',
+ 'Bertiolo',
+ 'Bicinicco',
+ 'Castelnovo del Friuli',
+ 'Cavasso Nuovo',
+ 'Cimolais',
+ 'Clauzetto',
+ 'Colloredo di Monte Albano',
+ 'Comeglians',
+ 'Dignano',
+ 'Fogliano Redipuglia',
+ 'Fontanafredda',
+ 'Forgaria nel Friuli',
+ 'Gonars',
+ 'Grado',
+ 'Latisana',
+ 'Lauco',
+ 'Lestizza',
+ 'Ligosullo',
+ 'Lusevera',
+ 'Marano Lagunare',
+ 'Medea',
+ 'Meduno',
+ 'Paularo',
+ 'Polcenigo',
+ 'Pordenone',
+ 'Porpetto',
+ 'Pozzuolo del Friuli',
+ 'Pradamano',
+ 'Prato Carnico',
+ 'Precenicco',
+ 'Ragogna',
+ 'Reana del Rojale',
+ 'Roveredo in Piano',
+ 'Ruda',
+ 'San Floriano del Collio',
+ 'San Vito al Tagliamento',
+ 'Sauris',
+ 'Sesto al Reghena',
+ 'Sgonico',
+ 'Socchieve',
+ 'Tarcento',
+ 'Tarvisio',
+ 'Tramonti di Sopra',
+ 'Treppo Carnico',
+ 'Trieste',
+ 'Turriaco',
+ 'Verzegnis',
+ "Vito d'Asio"
+ ]
+ },
+
+ {
+ regionName: 'Liguria',
+ regionShort: 'LIG',
+ regionSlug: 'liguria',
+ weight: 3,
+ cities: [
+ 'Alassio',
+ 'Albisola Superiore',
+ "Aquila d'Arroscia",
+ 'Armo',
+ 'Bajardo',
+ 'Bargagli',
+ 'Bergeggi',
+ 'Bolano',
+ 'Borghetto di Vara',
+ 'Borgomaro',
+ 'Cairo Montenotte',
+ 'Calice al Cornoviglio',
+ 'Campomorone',
+ 'Castelbianco',
+ 'Castelnuovo Magra',
+ 'Castelvecchio di Rocca Barbena',
+ 'Ceranesi',
+ 'Chiavari',
+ 'Chiusanico',
+ 'Cicagna',
+ 'Cisano sul Neva',
+ 'Diano Arentino',
+ 'Dolceacqua',
+ 'Dolcedo',
+ 'Erli',
+ 'Fontanigorda',
+ 'Genova',
+ 'La Spezia',
+ 'Masone',
+ 'Massimino',
+ 'Mignanego',
+ 'Millesimo',
+ 'Montoggio',
+ 'Nasino',
+ 'Ortonovo',
+ 'Perinaldo',
+ 'Portofino',
+ 'Recco',
+ 'Rezzoaglio',
+ 'Savona',
+ 'Seborga',
+ 'Terzorio',
+ 'Tribogna',
+ 'Urbe',
+ 'Valbrevenna',
+ 'Vezzi Portio',
+ 'Villa Faraldi',
+ "Villanova d'Albenga",
+ 'Zignago',
+ 'Zuccarello'
+ ]
+ },
+
+ {
+ regionName: 'Emilia-Romagna',
+ regionShort: 'ERM',
+ regionSlug: 'emiliaromagna',
+ weight: 7,
+ cities: [
+ 'Baiso',
+ 'Baricella',
+ 'Bazzano',
+ 'Berceto',
+ 'Bologna',
+ 'Calestano',
+ 'Casina',
+ 'Castel Guelfo di Bologna',
+ 'Castel Maggiore',
+ 'Codigoro',
+ 'Collecchio',
+ 'Colorno',
+ 'Compiano',
+ 'Conselice',
+ 'Corte Brugnatella',
+ 'Fontanellato',
+ 'Forlì',
+ 'Fusignano',
+ 'Gaggio Montano',
+ 'Gattatico',
+ 'Jolanda di Savoia',
+ 'Luzzara',
+ 'Maranello',
+ 'Marzabotto',
+ 'Modena',
+ 'Monghidoro',
+ 'Montese',
+ "Monticelli d'Ongina",
+ 'Ostellato',
+ 'Palagano',
+ 'Palanzano',
+ 'Pievepelago',
+ 'Poggio Berni',
+ 'Porretta Terme',
+ 'Portico e San Benedetto',
+ 'Poviglio',
+ "Reggio nell'Emilia",
+ 'Rio Saliceto',
+ 'Sala Baganza',
+ 'San Clemente',
+ 'San Lazzaro di Savena',
+ "San Polo d'Enza",
+ "Sant'Agata Bolognese",
+ "Sant'Agata sul Santerno",
+ 'Santarcangelo di Romagna',
+ 'Sissa',
+ 'Travo',
+ 'Tresigallo',
+ 'Viano',
+ 'Zerba'
+ ]
+ },
+
+ {
+ regionName: 'Toscana',
+ regionShort: 'TOS',
+ regionSlug: 'toscana',
+ weight: 6,
+ cities: [
+ 'Anghiari',
+ 'Bientina',
+ 'Buti',
+ 'Capannori',
+ 'Casciana Terme',
+ "Casole d'Elsa",
+ 'Castel San Niccolò',
+ 'Castellina in Chianti',
+ 'Castiglione di Garfagnana',
+ 'Certaldo',
+ 'Comano',
+ 'Coreglia Antelminelli',
+ 'Empoli',
+ 'Fauglia',
+ 'Firenze',
+ 'Gavorrano',
+ 'Grosseto',
+ 'Livorno',
+ 'Massa e Cozzile',
+ 'Massarosa',
+ 'Minucciano',
+ 'Monte San Savino',
+ 'Montelupo Fiorentino',
+ 'Montemignaio',
+ "Monteroni d'Arbia",
+ 'Montignoso',
+ 'Orciano Pisano',
+ 'Pelago',
+ 'Piancastagnaio',
+ 'Pietrasanta',
+ 'Pisa',
+ 'Ponsacco',
+ 'Pontedera',
+ 'Pratovecchio',
+ 'Radicofani',
+ 'Rio Marina',
+ "Rio nell'Elba",
+ 'Riparbella',
+ 'Sambuca Pistoiese',
+ 'San Marcello Pistoiese',
+ 'San Piero a Sieve',
+ "Santa Croce sull'Arno",
+ 'Santa Fiora',
+ 'Santa Maria a Monte',
+ 'Siena',
+ 'Subbiano',
+ 'Vagli Sotto',
+ 'Vergemoli',
+ 'Villafranca in Lunigiana',
+ 'Vinci'
+ ]
+ },
+
+ {
+ regionName: 'Umbria',
+ regionShort: 'UMB',
+ regionSlug: 'umbria',
+ weight: 1,
+ cities: [
+ 'Acquasparta',
+ 'Allerona',
+ 'Alviano',
+ 'Amelia',
+ 'Arrone',
+ 'Attigliano',
+ 'Avigliano Umbro',
+ 'Baschi',
+ 'Bastia Umbra',
+ 'Bevagna',
+ 'Castel Giorgio',
+ 'Castel Ritaldi',
+ 'Castiglione del Lago',
+ 'Cerreto di Spoleto',
+ 'Fabro',
+ 'Ficulle',
+ 'Foligno',
+ 'Fossato di Vico',
+ 'Fratta Todina',
+ 'Giove',
+ 'Gualdo Cattaneo',
+ 'Gualdo Tadino',
+ 'Gubbio',
+ 'Lisciano Niccone',
+ 'Lugnano in Teverina',
+ 'Marsciano',
+ 'Massa Martana',
+ 'Monte Santa Maria Tiberina',
+ 'Monteleone di Spoleto',
+ 'Montone',
+ 'Nocera Umbra',
+ 'Norcia',
+ 'Orvieto',
+ 'Otricoli',
+ 'Parrano',
+ 'Penna in Teverina',
+ 'Perugia',
+ 'Piegaro',
+ 'Poggiodomo',
+ 'Polino',
+ 'San Venanzo',
+ 'Scheggino',
+ 'Sellano',
+ 'Sigillo',
+ 'Spoleto',
+ 'Terni',
+ 'Todi',
+ 'Torgiano',
+ 'Umbertide',
+ 'Valfabbrica'
+ ]
+ },
+
+ {
+ regionName: 'Marche',
+ regionShort: 'MAR',
+ regionSlug: 'marche',
+ weight: 3,
+ cities: [
+ 'Acquasanta Terme',
+ 'Altidona',
+ 'Ancona',
+ 'Arquata del Tronto',
+ 'Barchi',
+ 'Belvedere Ostrense',
+ 'Cagli',
+ 'Caldarola',
+ 'Camerino',
+ 'Camporotondo di Fiastrone',
+ 'Castel Colonna',
+ 'Cossignano',
+ 'Falerone',
+ 'Fiuminata',
+ 'Loreto',
+ 'Macerata',
+ 'Maiolati Spontini',
+ 'Maltignano',
+ 'Mogliano',
+ 'Mondolfo',
+ 'Monte Giberto',
+ 'Monte San Pietrangeli',
+ 'Monte Vidon Corrado',
+ 'Montecarotto',
+ "Montefiore dell'Aso",
+ 'Montegranaro',
+ "Morro d'Alba",
+ 'Morrovalle',
+ 'Offida',
+ 'Osimo',
+ 'Ostra Vetere',
+ 'Palmiano',
+ 'Penna San Giovanni',
+ 'Pergola',
+ 'Piagge',
+ 'Pietrarubbia',
+ 'Poggio San Marcello',
+ 'Rapagnano',
+ 'Recanati',
+ 'Saltara',
+ 'San Benedetto del Tronto',
+ 'San Marcello',
+ "Sant'Angelo in Pontano",
+ "Sant'Elpidio a Mare",
+ 'Santa Vittoria in Matenano',
+ 'Sassocorvaro',
+ 'Sefro',
+ 'Serrungarina',
+ 'Tolentino',
+ 'Visso'
+ ]
+ },
+
+ {
+ regionName: 'Lazio',
+ regionShort: 'LAZ',
+ regionSlug: 'lazio',
+ weight: 9,
+ cities: [
+ 'Acquafondata',
+ 'Allumiere',
+ 'Alvito',
+ 'Anzio',
+ 'Arsoli',
+ 'Artena',
+ 'Bassano in Teverina',
+ 'Bassiano',
+ 'Canino',
+ 'Cantalupo in Sabina',
+ 'Canterano',
+ 'Capena',
+ 'Casalvieri',
+ 'Casperia',
+ 'Castel di Tora',
+ 'Cisterna di Latina',
+ 'Filacciano',
+ 'Frascati',
+ 'Labico',
+ 'Labro',
+ 'Ladispoli',
+ 'Latera',
+ 'Mandela',
+ 'Mazzano Romano',
+ 'Minturno',
+ 'Monte San Giovanni in Sabina',
+ 'Moricone',
+ 'Morolo',
+ 'Morro Reatino',
+ 'Nemi',
+ 'Palestrina',
+ 'Pastena',
+ 'Picinisco',
+ 'Rocca Massima',
+ 'Rocca Santo Stefano',
+ "Rocca d'Arce",
+ 'Roma',
+ 'Ronciglione',
+ 'San Lorenzo Nuovo',
+ 'Scandriglia',
+ 'Segni',
+ 'Sonnino',
+ 'Torre Cajetani',
+ 'Trevignano Romano',
+ 'Vallepietra',
+ 'Vejano',
+ 'Velletri',
+ 'Vico nel Lazio',
+ 'Villa Latina',
+ 'Villa Santo Stefano'
+ ]
+ },
+
+ {
+ regionName: 'Abruzzo',
+ regionShort: 'ABR',
+ regionSlug: 'abruzzo',
+ weight: 2,
+ cities: [
+ 'Abbateggio',
+ 'Acciano',
+ 'Ancarano',
+ 'Bellante',
+ 'Campli',
+ 'Campotosto',
+ 'Capestrano',
+ 'Cappelle sul Tavo',
+ 'Caramanico Terme',
+ 'Carunchio',
+ 'Casoli',
+ 'Castellafiume',
+ 'Castelvecchio Calvisio',
+ 'Castiglione Messer Raimondo',
+ 'Castiglione a Casauria',
+ 'Cerchio',
+ 'Chieti',
+ 'Colledimacine',
+ 'Colonnella',
+ 'Crecchio',
+ 'Fallo',
+ 'Fontecchio',
+ 'Guilmi',
+ "Isola del Gran Sasso d'Italia",
+ 'Manoppello',
+ 'Montebello sul Sangro',
+ 'Montereale',
+ "Mosciano Sant'Angelo",
+ 'Nocciano',
+ 'Ofena',
+ 'Orsogna',
+ 'Paglieta',
+ 'Palombaro',
+ 'Pereto',
+ 'Pizzoferrato',
+ 'Pretoro',
+ 'Rocca di Cambio',
+ 'Roio del Sangro',
+ 'Rosciano',
+ 'San Giovanni Lipioni',
+ 'San Valentino in Abruzzo Citeriore',
+ 'San Vito Chietino',
+ "Sant'Egidio alla Vibrata",
+ "Sant'Eufemia a Maiella",
+ "Sant'Eusanio Forconese",
+ "Sant'Omero",
+ 'Serramonacesca',
+ 'Torrevecchia Teatina',
+ 'Treglio',
+ 'Vicoli'
+ ]
+ },
+
+ {
+ regionName: 'Molise',
+ regionShort: 'MOL',
+ regionSlug: 'molise',
+ weight: 1,
+ cities: [
+ 'Baranello',
+ 'Belmonte del Sannio',
+ 'Bojano',
+ 'Bonefro',
+ 'Busso',
+ 'Campochiaro',
+ 'Campolieto',
+ 'Campomarino',
+ 'Carovilli',
+ 'Casacalenda',
+ 'Castel del Giudice',
+ 'Campobasso',
+ 'Cercemaggiore',
+ 'Cercepiccola',
+ 'Chiauci',
+ 'Civitacampomarano',
+ 'Colli a Volturno',
+ 'Conca Casale',
+ 'Ferrazzano',
+ 'Filignano',
+ 'Isernia',
+ 'Longano',
+ 'Macchia Valfortore',
+ 'Miranda',
+ 'Monacilioni',
+ 'Montefalcone nel Sannio',
+ 'Montenero Val Cocchiara',
+ 'Petacciato',
+ 'Pettoranello del Molise',
+ 'Pietracatella',
+ 'Ripabottoni',
+ 'Roccasicura',
+ 'Rotello',
+ 'Salcito',
+ 'San Giacomo degli Schiavoni',
+ 'San Giovanni in Galdo',
+ 'San Giuliano di Puglia',
+ 'San Martino in Pensilis',
+ 'San Massimo',
+ 'San Pietro Avellana',
+ "Sant'Agapito",
+ "Sant'Angelo Limosano",
+ "Sant'Elia a Pianisi",
+ 'Sepino',
+ 'Sesto Campano',
+ 'Termoli',
+ 'Torella del Sannio',
+ 'Tufara',
+ 'Ururi',
+ 'Vastogirardi'
+ ]
+ },
+
+ {
+ regionName: 'Campania',
+ regionShort: 'CAM',
+ regionSlug: 'campania',
+ weight: 10,
+ cities: [
+ 'Acerra',
+ 'Altavilla Irpina',
+ 'Arzano',
+ 'Avellino',
+ 'Calvi Risorta',
+ 'Campagna',
+ 'Cannalonga',
+ 'Casalbuono',
+ 'Castel Baronia',
+ 'Castel Volturno',
+ 'Castelvetere in Val Fortore',
+ 'Ceppaloni',
+ 'Cervinara',
+ 'Cervino',
+ 'Cetara',
+ 'Cimitile',
+ 'Corbara',
+ 'Cuccaro Vetere',
+ 'Ercolano',
+ 'Falciano del Massico',
+ 'Felitto',
+ 'Forio',
+ 'Frigento',
+ 'Frignano',
+ 'Giugliano in Campania',
+ 'Guardia Sanframondi',
+ 'Napoli',
+ "Ospedaletto d'Alpinolo",
+ 'Paternopoli',
+ 'Paupisi',
+ 'Pellezzano',
+ 'Pietraroja',
+ 'Pollena Trocchia',
+ 'Portici',
+ 'Rocca San Felice',
+ 'Salerno',
+ 'San Felice a Cancello',
+ 'San Leucio del Sannio',
+ 'San Mauro Cilento',
+ 'San Pietro al Tanagro',
+ 'San Sebastiano al Vesuvio',
+ "Sant'Angelo a Cupolo",
+ "Sant'Angelo a Fasanella",
+ "Sant'Arsenio",
+ "Sant'Egidio del Monte Albino",
+ 'Santa Marina',
+ 'Santo Stefano del Sole',
+ 'Santomenna',
+ 'Sorbo Serpico',
+ 'Tufo'
+ ]
+ },
+
+ {
+ regionName: 'Puglia',
+ regionShort: 'PUG',
+ regionSlug: 'puglia',
+ weight: 7,
+ cities: [
+ 'Accadia',
+ 'Altamura',
+ 'Arnesano',
+ 'Bari',
+ 'Brindisi',
+ 'Candela',
+ 'Cannole',
+ 'Carpignano Salentino',
+ 'Castelluccio Valmaggiore',
+ 'Castri di Lecce',
+ 'Cavallino',
+ 'Cerignola',
+ 'Copertino',
+ 'Erchie',
+ 'Francavilla Fontana',
+ 'Gagliano del Capo',
+ 'Giurdignano',
+ 'Grumo Appula',
+ 'Lecce',
+ 'Maglie',
+ 'Manfredonia',
+ 'Minervino di Lecce',
+ 'Molfetta',
+ 'Montemesola',
+ 'Mottola',
+ 'Noicattaro',
+ 'Novoli',
+ 'Oria',
+ 'Orsara di Puglia',
+ 'Otranto',
+ 'Palagianello',
+ 'Palmariggi',
+ 'Poggiorsini',
+ 'Porto Cesareo',
+ 'Putignano',
+ 'Rignano Garganico',
+ 'Rutigliano',
+ 'Salice Salentino',
+ 'San Cesario di Lecce',
+ 'San Pancrazio Salentino',
+ 'Santa Cesarea Terme',
+ 'Scorrano',
+ 'Sogliano Cavour',
+ 'Surbo',
+ 'Torchiarolo',
+ 'Trani',
+ 'Tuglie',
+ 'Vernole',
+ 'Vico del Gargano',
+ 'Vieste'
+ ]
+ },
+
+ {
+ regionName: 'Basilicata',
+ regionShort: 'BAS',
+ regionSlug: 'basilicata',
+ weight: 1,
+ cities: [
+ 'Albano di Lucania',
+ 'Aliano',
+ 'Anzi',
+ 'Armento',
+ 'Balvano',
+ 'Bella',
+ 'Calvello',
+ 'Calvera',
+ 'Castelluccio Inferiore',
+ 'Castelluccio Superiore',
+ 'Castelmezzano',
+ 'Chiaromonte',
+ 'Colobraro',
+ 'Craco',
+ 'Episcopia',
+ 'Ferrandina',
+ 'Francavilla in Sinni',
+ 'Gallicchio',
+ 'Garaguso',
+ 'Genzano di Lucania',
+ 'Guardia Perticara',
+ 'Lagonegro',
+ 'Latronico',
+ 'Matera',
+ 'Moliterno',
+ 'Montemilone',
+ 'Nemoli',
+ 'Pescopagano',
+ 'Pietragalla',
+ 'Pomarico',
+ 'Potenza',
+ 'Rapone',
+ 'Rionero in Vulture',
+ 'Ripacandida',
+ 'Rivello',
+ 'Roccanova',
+ 'Ruoti',
+ 'Ruvo del Monte',
+ 'San Chirico Nuovo',
+ 'San Fele',
+ 'Satriano di Lucania',
+ 'Scanzano Jonico',
+ 'Spinoso',
+ 'Stigliano',
+ 'Tolve',
+ 'Tramutola',
+ 'Trivigno',
+ 'Vietri di Potenza',
+ 'Viggianello',
+ 'Viggiano'
+ ]
+ },
+
+ {
+ regionName: 'Calabria',
+ regionShort: 'CAL',
+ regionSlug: 'calabria',
+ weight: 3,
+ cities: [
+ 'Aiello Calabro',
+ 'Aieta',
+ 'Benestare',
+ 'Bocchigliero',
+ 'Candidoni',
+ 'Catanzaro',
+ 'Cellara',
+ 'Cittanova',
+ 'Cropalati',
+ 'Davoli',
+ 'Delianuova',
+ 'Dipignano',
+ 'Filadelfia',
+ 'Fiumara',
+ 'Fossato Serralta',
+ 'Gagliato',
+ 'Girifalco',
+ 'Grimaldi',
+ 'Isca sullo Ionio',
+ 'Isola di Capo Rizzuto',
+ 'Laino Castello',
+ 'Marcedusa',
+ 'Marzi',
+ 'Mesoraca',
+ 'Montalto Uffugo',
+ 'Oppido Mamertina',
+ 'Papasidero',
+ 'Pedace',
+ 'Placanica',
+ 'Portigliola',
+ 'Rosarno',
+ 'San Calogero',
+ 'San Costantino Calabro',
+ 'San Demetrio Corone',
+ 'San Donato di Ninea',
+ 'San Giorgio Albanese',
+ 'San Lorenzo',
+ 'San Luca',
+ 'San Mauro Marchesato',
+ 'San Sostene',
+ "Sant'Ilario dello Ionio",
+ "Sant'Onofrio",
+ 'Scala Coeli',
+ 'Sellia Marina',
+ 'Serrata',
+ 'Squillace',
+ 'Stilo',
+ 'Strongoli',
+ 'Tarsia',
+ 'Tropea'
+ ]
+ },
+
+ {
+ regionName: 'Sicilia',
+ regionShort: 'SIC',
+ regionSlug: 'sicilia',
+ weight: 8,
+ cities: [
+ 'Acireale',
+ 'Acquedolci',
+ 'Adrano',
+ 'Buccheri',
+ 'Caccamo',
+ 'Campofelice di Fitalia',
+ 'Cassaro',
+ 'Castelbuono',
+ 'Castellana Sicula',
+ 'Castiglione di Sicilia',
+ 'Cefalà Diana',
+ 'Cerami',
+ 'Chiusa Sclafani',
+ 'Cinisi',
+ 'Enna',
+ 'Francofonte',
+ 'Gallodoro',
+ 'Giardinello',
+ 'Giarratana',
+ 'Mascalucia',
+ 'Milazzo',
+ 'Milena',
+ 'Moio Alcantara',
+ 'Motta Camastra',
+ "Motta Sant'Anastasia",
+ 'Naro',
+ 'Nicolosi',
+ 'Pace del Mela',
+ 'Palermo',
+ 'Pettineo',
+ 'Piana degli Albanesi',
+ 'Priolo Gargallo',
+ 'Randazzo',
+ 'Roccalumera',
+ 'Roccamena',
+ 'Rodì Milici',
+ 'Rosolini',
+ 'San Fratello',
+ 'San Giovanni la Punta',
+ 'San Piero Patti',
+ 'Santa Caterina Villarmosa',
+ 'Santa Flavia',
+ 'Santo Stefano Quisquina',
+ 'Sciacca',
+ 'Siculiana',
+ 'Siracusa',
+ 'Sperlinga',
+ 'Termini Imerese',
+ 'Valverde',
+ 'Villafranca Tirrena'
+ ]
+ },
+
+ {
+ regionName: 'Sardegna',
+ regionShort: 'SAR',
+ regionSlug: 'sardegna',
+ weight: 3,
+ cities: [
+ 'Albagiara',
+ 'Armungia',
+ 'Birori',
+ 'Bosa',
+ 'Bottidda',
+ 'Bulzi',
+ 'Cabras',
+ 'Cagliari',
+ 'Cardedu',
+ 'Collinas',
+ 'Cuglieri',
+ 'Dorgali',
+ 'Esterzili',
+ 'Gonnosfanadiga',
+ 'Gonnosnò',
+ 'Ilbono',
+ 'Jerzu',
+ 'Lodine',
+ 'Maracalagonis',
+ 'Masullas',
+ 'Montresta',
+ 'Mores',
+ 'Narbolia',
+ 'Narcao',
+ 'Noragugume',
+ 'Nuragus',
+ 'Nurallao',
+ 'Nuraminis',
+ 'Ollolai',
+ 'Oristano',
+ 'Orosei',
+ 'Orroli',
+ 'Ortacesus',
+ 'Piscinas',
+ 'San Giovanni Suergiu',
+ 'Sanluri',
+ 'Santu Lussurgiu',
+ 'Sennariolo',
+ 'Siddi',
+ 'Silius',
+ 'Sorradile',
+ 'Stintino',
+ 'Tramatza',
+ "Trinità d'Agultu e Vignola",
+ 'Tula',
+ 'Ussassai',
+ 'Viddalba',
+ 'Villa Verde',
+ 'Villamassargia',
+ 'Villanovafranca'
+ ]
+ }
+ ]
+});
+
+export default Italy;
diff --git a/packages/plugins/src/countries/Italy/i18n/ar.json b/packages/plugins/src/countries/Italy/i18n/ar.json
new file mode 100644
index 000000000..c271e8047
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "إيطاليا",
+ "regionNames": "المناطق الإيطالية."
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/de.json b/packages/plugins/src/countries/Italy/i18n/de.json
new file mode 100644
index 000000000..ebd17bef3
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Italien",
+ "regionNames": "Italienische Regionen"
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/en.json b/packages/plugins/src/countries/Italy/i18n/en.json
new file mode 100644
index 000000000..4a452007c
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Italy",
+ "regionNames": "Italian regions."
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/es.json b/packages/plugins/src/countries/Italy/i18n/es.json
new file mode 100644
index 000000000..0f6035129
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Italia",
+ "regionNames": "regiones italianas"
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/fr.json b/packages/plugins/src/countries/Italy/i18n/fr.json
new file mode 100644
index 000000000..309350a1b
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Italie",
+ "regionNames": "Régions italiennes"
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/hi.json b/packages/plugins/src/countries/Italy/i18n/hi.json
new file mode 100644
index 000000000..242a3ac7b
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "इटली",
+ "regionNames": "इतालवी क्षेत्र।"
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/ja.json b/packages/plugins/src/countries/Italy/i18n/ja.json
new file mode 100644
index 000000000..da2888cd1
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "イタリア",
+ "regionNames": "イタリアの地域"
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/nl.json b/packages/plugins/src/countries/Italy/i18n/nl.json
new file mode 100644
index 000000000..9e32ba42c
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Italië",
+ "regionNames": "Italiaanse regio's"
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/pt.json b/packages/plugins/src/countries/Italy/i18n/pt.json
new file mode 100644
index 000000000..b6ecf700a
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Itália",
+ "regionNames": "Regiões italianas."
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/ru.json b/packages/plugins/src/countries/Italy/i18n/ru.json
new file mode 100644
index 000000000..4596afb03
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Италия",
+ "regionNames": "итальянские регионы."
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/ta.json b/packages/plugins/src/countries/Italy/i18n/ta.json
new file mode 100644
index 000000000..258e40f79
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "இத்தாலி",
+ "regionNames": "இத்தாலிய பகுதிகள்"
+}
diff --git a/packages/plugins/src/countries/Italy/i18n/zh.json b/packages/plugins/src/countries/Italy/i18n/zh.json
new file mode 100644
index 000000000..c8a023371
--- /dev/null
+++ b/packages/plugins/src/countries/Italy/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "意大利",
+ "regionNames": "意大利地区"
+}
diff --git a/packages/plugins/src/countries/Mexico/bundle.ts b/packages/plugins/src/countries/Mexico/bundle.ts
new file mode 100644
index 000000000..c8f4c2f48
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/bundle.ts
@@ -0,0 +1,190 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Mexico: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'mexico',
+ regionNames: i18n.regionNames,
+ continent: 'north_america',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxx-xxxxx'
+ },
+ phoneFormat: {
+ displayFormats: [
+ '045 Xx Xxxx xxx',
+ '045 Xx Xxxx xxxx',
+ '045 Xxx Xxxx xxx',
+ '045 Xxx Xxxx xxxx',
+ 'Xxx-Xxxx xxx',
+ '55–Xxxx xxxx',
+ '55–Xxxx xxx'
+ ]
+ }
+ },
+ regions: [
+ {
+ regionName: 'Mexico City',
+ regionShort: 'CDMX',
+ regionSlug: 'mexico_city',
+ weight: 89,
+ cities: ['Mexico City']
+ },
+ {
+ regionName: 'Veracruz',
+ regionShort: 'Ver.',
+ regionSlug: 'veracruz',
+ weight: 76,
+ cities: ['Veracruz', 'Xalapa', 'Coatzacoalcos', 'Poza Rica', 'Córdoba', 'Boca del Río', 'Orizaba', 'Minatitlán']
+ },
+ {
+ regionName: 'Jalisco',
+ regionShort: 'Jal.',
+ regionSlug: 'jalisco',
+ weight: 74,
+ cities: ['Guadalajara', 'Zapopan', 'Tlaquepaque', 'Tonalá', 'Puerto Vallarta']
+ },
+ {
+ regionName: 'Puebla',
+ regionShort: 'Pue.',
+ regionSlug: 'puebla',
+ weight: 58,
+ cities: ['Tehuacán']
+ },
+ {
+ regionName: 'Guanajuato',
+ regionShort: 'Gto.',
+ regionSlug: 'guanajuato',
+ weight: 55,
+ cities: ['León', 'Irapuato', 'Celaya', 'Salamanca']
+ },
+ {
+ regionName: 'Chiapas',
+ regionShort: 'Chis.',
+ regionSlug: 'chiapas',
+ weight: 48,
+ cities: ['Tuxtla Gutiérrez', 'Tapachula', 'San Cristóbal de las Casas']
+ },
+ {
+ regionName: 'Nuevo León',
+ regionShort: 'N.L.',
+ regionSlug: 'nuevoleon',
+ weight: 47,
+ cities: [
+ 'Monterrey',
+ 'Guadalupe',
+ 'San Nicolás de los Garza',
+ 'Ciudad Apodaca',
+ 'General Escobedo',
+ 'Ciudad Santa Catarina',
+ 'San Pedro Garza García'
+ ]
+ },
+ {
+ regionName: 'Michoacán',
+ regionShort: 'Mich.',
+ regionSlug: 'michoacan',
+ weight: 44,
+ cities: ['Morelia', 'Uruapan', 'Zamora de Hidalgo']
+ },
+ {
+ regionName: 'Oaxaca',
+ regionShort: 'Oax.',
+ regionSlug: 'oaxaca',
+ weight: 38,
+ cities: ['Oaxaca']
+ },
+ {
+ regionName: 'Chihuahua',
+ regionShort: 'Chih.',
+ regionSlug: 'chihuahua',
+ weight: 34,
+ cities: ['Juárez', 'Chihuahua', 'Delicias', 'Hidalgo del Parral']
+ },
+ {
+ regionName: 'Guerrero',
+ regionShort: 'Gro.',
+ regionSlug: 'guerrero',
+ weight: 34,
+ cities: ['Acapulco', 'Chilpancingo', 'Iguala']
+ },
+ {
+ regionName: 'Tamaulipas',
+ regionShort: 'Tamps.',
+ regionSlug: 'Tamaulipas',
+ weight: 33,
+ cities: ['Reynosa', 'Matamoros', 'Nuevo Laredo', 'Tampico', 'Ciudad Victoria', 'Ciudad Madero']
+ },
+ {
+ regionName: 'Baja California',
+ regionShort: 'B.C.',
+ regionSlug: 'baja_california',
+ weight: 32,
+ cities: ['Tijuana', 'Mexicali', 'Ensenada', 'La Paz']
+ },
+ {
+ regionName: 'Sinaloa',
+ regionShort: 'Sin.',
+ regionSlug: 'sinaloa',
+ weight: 28,
+ cities: ['Culiacán', 'Mazatlán', 'Los Mochis']
+ },
+ {
+ regionName: 'Coahuila',
+ regionShort: 'Coah.',
+ regionSlug: 'coahuila',
+ weight: 27,
+ cities: ['Saltillo', 'Torreón', 'Monclova', 'Piedras Negras', 'Acuña']
+ },
+ {
+ regionName: 'Hidalgo',
+ regionShort: 'Hgo.',
+ regionSlug: 'Hidalgo',
+ weight: 27,
+ cities: ['Pachuca']
+ },
+ {
+ regionName: 'Sonora',
+ regionShort: 'Son.',
+ regionSlug: 'Sonora',
+ weight: 27,
+ cities: ['Hermosillo', 'Ciudad Obregón', 'Nogales', 'San Luis Río Colorado', 'Navojoa', 'Guaymas']
+ },
+ {
+ regionName: 'San Luis Potosí',
+ regionShort: 'S.L.P.',
+ regionSlug: 'San Luis Potosí',
+ weight: 26,
+ cities: ['San Luis Potosí', 'Soledad de Graciano Sánchez', 'Ciudad Valles']
+ },
+ {
+ regionName: 'Tabasco',
+ regionShort: 'Tab.',
+ regionSlug: 'Tabasco',
+ weight: 22,
+ cities: ['Villahermosa']
+ },
+ {
+ regionName: 'Yucatán',
+ regionShort: 'Yuc.',
+ regionSlug: 'Yucatán',
+ weight: 20,
+ cities: ['Mérida']
+ },
+ {
+ regionName: 'Querétaro',
+ regionShort: 'Qro.',
+ regionSlug: 'Querétaro',
+ weight: 18,
+ cities: ['Querétaro', 'San Juan del Río']
+ },
+ {
+ regionName: 'Morelos',
+ regionShort: 'Mor.',
+ regionSlug: 'Morelos',
+ weight: 18,
+ cities: ['Cuernavaca', 'Jiutepec', 'Cuautla']
+ }
+ ]
+});
+
+export default Mexico;
diff --git a/packages/plugins/src/countries/Mexico/i18n/ar.json b/packages/plugins/src/countries/Mexico/i18n/ar.json
new file mode 100644
index 000000000..c2312e83a
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "المكسيك",
+ "regionNames": "الولايات المكسيكية"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/de.json b/packages/plugins/src/countries/Mexico/i18n/de.json
new file mode 100644
index 000000000..6eb7f04d2
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Mexiko",
+ "regionNames": "Mexikanische Staaten"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/en.json b/packages/plugins/src/countries/Mexico/i18n/en.json
new file mode 100644
index 000000000..15c1831db
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Mexico",
+ "regionNames": "Mexican States"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/es.json b/packages/plugins/src/countries/Mexico/i18n/es.json
new file mode 100644
index 000000000..156e23a8f
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "México",
+ "regionNames": "Estados Mexicanos"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/fr.json b/packages/plugins/src/countries/Mexico/i18n/fr.json
new file mode 100644
index 000000000..6049f805b
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Mexique",
+ "regionNames": "États mexicains"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/hi.json b/packages/plugins/src/countries/Mexico/i18n/hi.json
new file mode 100644
index 000000000..e81d0e5f3
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "मेक्सिको",
+ "regionNames": "मैक्सिकन राज्य"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/ja.json b/packages/plugins/src/countries/Mexico/i18n/ja.json
new file mode 100644
index 000000000..3bb1b92b3
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "メキシコ",
+ "regionNames": "メキシコ諸州"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/nl.json b/packages/plugins/src/countries/Mexico/i18n/nl.json
new file mode 100644
index 000000000..14f14e8b4
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Mexico",
+ "regionNames": "Mexicaanse staten"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/pt.json b/packages/plugins/src/countries/Mexico/i18n/pt.json
new file mode 100644
index 000000000..d55ca840b
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "México",
+ "regionNames": "Estados mexicanos"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/ru.json b/packages/plugins/src/countries/Mexico/i18n/ru.json
new file mode 100644
index 000000000..e54a3a10a
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Мексика",
+ "regionNames": "Мексиканские штаты"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/ta.json b/packages/plugins/src/countries/Mexico/i18n/ta.json
new file mode 100644
index 000000000..159e328f2
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "மெக்சிகோ",
+ "regionNames": "மெக்சிகன் மாநிலங்கள்"
+}
diff --git a/packages/plugins/src/countries/Mexico/i18n/zh.json b/packages/plugins/src/countries/Mexico/i18n/zh.json
new file mode 100644
index 000000000..2d9e7814e
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "墨西哥",
+ "regionNames": "墨西哥各州"
+}
diff --git a/packages/plugins/src/countries/Mexico/name.ts b/packages/plugins/src/countries/Mexico/name.ts
new file mode 100644
index 000000000..ff84519c3
--- /dev/null
+++ b/packages/plugins/src/countries/Mexico/name.ts
@@ -0,0 +1,2225 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Sofia',
+ 'Maria Jose',
+ 'Valentina',
+ 'Regina',
+ 'Ximena',
+ 'Camila',
+ 'Maria Fernanda',
+ 'Valeria',
+ 'Renata',
+ 'Victoria',
+ 'Natalia',
+ 'Daniela',
+ 'Isabella',
+ 'Romina',
+ 'Maria Guadalupe',
+ 'Fernanda',
+ 'Alexa',
+ 'Ana Sofia',
+ 'Andrea',
+ 'Samantha',
+ 'Guadalupe',
+ 'Melissa',
+ 'Elizabeth',
+ 'Mariana',
+ 'Aitana',
+ 'Yamileth',
+ 'Fatima',
+ 'Emily',
+ 'Ana Victoria',
+ 'Abigail',
+ 'Alejandra',
+ 'Julieta',
+ 'Esmeralda',
+ 'Vanessa',
+ 'Evelyn',
+ 'Estefania',
+ 'Jimena',
+ 'Ana Paula',
+ 'Alondra',
+ 'Luciana',
+ 'Miranda',
+ 'Maria',
+ 'Dulce Maria',
+ 'Kimberly',
+ 'Aranza',
+ 'Carolina',
+ 'Monserrat',
+ 'Danna Sofia',
+ 'Ivanna',
+ 'Lucia',
+ 'Sofia Guadalupe',
+ 'Paulina',
+ 'Itzayana',
+ 'Abril',
+ 'Genesis',
+ 'Frida Sofia',
+ 'Ariana',
+ 'Emma',
+ 'Dayana',
+ 'Sara',
+ 'Ana Lucia',
+ 'Gabriela',
+ 'Melany',
+ 'Samara',
+ 'Elisa',
+ 'Aylin',
+ 'Mia',
+ 'Allison',
+ 'Michelle',
+ 'Rebeca',
+ 'Angela',
+ 'Nicole',
+ 'Elena',
+ 'Diana',
+ 'Zoe',
+ 'Lia',
+ 'Paola',
+ 'Naomi',
+ 'Adriana',
+ 'Itzel',
+ 'Emilia',
+ 'Marisol',
+ 'Alisson',
+ 'Jennifer',
+ 'Yaretzi',
+ 'Lizbeth',
+ 'Karla',
+ 'Alexandra',
+ 'Estrella',
+ 'Jazmin',
+ 'Luz Maria',
+ 'Isabel',
+ 'Mia Isabella',
+ 'Alison',
+ 'Maria Victoria',
+ 'Melanie',
+ 'Aurora',
+ 'Lucero',
+ 'Ximena Guadalupe',
+ 'Arleth',
+ 'Liliana',
+ 'Maria Del Carmen',
+ 'Ana Maria',
+ 'Alexa Guadalupe',
+ 'Katherine',
+ 'Frida',
+ 'Montserrat',
+ 'Juana',
+ 'Maria Isabel',
+ 'Yoselin',
+ 'Mia Valentina',
+ 'Kenia',
+ 'Paula',
+ 'Mayte',
+ 'Emma Sofia',
+ 'Yareli',
+ 'Danna Paola',
+ 'Alicia',
+ 'Mia Guadalupe',
+ 'Catalina',
+ 'Sarahi',
+ 'Sofia Valentina',
+ 'Margarita',
+ 'Yamilet',
+ 'Angelica',
+ 'Victoria Guadalupe',
+ 'Monserrath',
+ 'Nahomi',
+ 'Denisse',
+ 'Ariadna',
+ 'Rosa',
+ 'Jade',
+ 'Tania',
+ 'Maria De Jesus',
+ 'Julia',
+ 'Camila Guadalupe',
+ 'Emily Sofia',
+ 'Evelin',
+ 'Grecia',
+ 'Ashley',
+ 'Maria Alejandra',
+ 'Barbara',
+ 'Anahi',
+ 'Cecilia',
+ 'Nataly',
+ 'Brenda',
+ 'Antonella',
+ 'Miriam',
+ 'Ana Paola',
+ 'Constanza',
+ 'Violeta',
+ 'Maria Elena',
+ 'Mariel',
+ 'Geraldine',
+ 'Rosa Maria',
+ 'Lia Victoria',
+ 'Yazmin',
+ 'Teresa',
+ 'Regina Guadalupe',
+ 'Amaya',
+ 'Luisa Fernanda',
+ 'Giselle',
+ 'Ana Karen',
+ 'Estefany',
+ 'Monica',
+ 'Lizeth',
+ 'Danna',
+ 'Valeria Guadalupe',
+ 'Milagros',
+ 'Aitana Guadalupe',
+ 'Alexa Sofia',
+ 'Hanna',
+ 'Jessica',
+ 'Lia Valentina',
+ 'Cristina',
+ 'Karina',
+ 'Karla Sofia',
+ 'Yuliana',
+ 'Helena',
+ 'Maria Paula',
+ 'Veronica',
+ 'Karen',
+ 'Sarai',
+ 'Mia Victoria',
+ 'Isabela',
+ 'Maria Regina',
+ 'Noemi',
+ 'Mia Sofia',
+ 'Fabiola',
+ 'Araceli',
+ 'Alexia',
+ 'Arely',
+ 'Grettel',
+ 'Yatziri',
+ 'Maria Luisa',
+ 'Cristal',
+ 'Sofia Victoria',
+ 'Andrea Guadalupe',
+ 'Emily Guadalupe',
+ 'Milagros Guadalupe',
+ 'Sandra',
+ 'Arantza',
+ 'Scarlett',
+ 'Melisa',
+ 'Raquel',
+ 'Daniela Guadalupe',
+ 'Ariadne',
+ 'Romina Guadalupe',
+ 'Sofia Alejandra',
+ 'Kailani',
+ 'Wendy',
+ 'Susana',
+ 'Renata Guadalupe',
+ 'Jaqueline',
+ 'Maribel',
+ 'Kimberly Guadalupe',
+ 'Vanesa',
+ 'Fatima Guadalupe',
+ 'Esther',
+ 'Berenice',
+ 'Amanda',
+ 'Esperanza',
+ 'Itzel Guadalupe',
+ 'Alessandra',
+ 'Sophia',
+ 'Maria Sofia',
+ 'Mia Fernanda',
+ 'Aitana Sofia',
+ 'Keyla',
+ 'Ana Laura',
+ 'Patricia',
+ 'Lesly',
+ 'Vania',
+ 'Leslie',
+ 'Emma Victoria',
+ 'Nayeli',
+ 'Danae',
+ 'Sofia Elizabeth',
+ 'Melani',
+ 'Amelia',
+ 'Olivia',
+ 'Martha',
+ 'Erika',
+ 'Ana Valeria',
+ 'Zoe Valentina',
+ 'Aitana Valentina',
+ 'Maria Valentina',
+ 'Lia Sofia',
+ 'Mia Nicole',
+ 'Sofia Isabella',
+ 'Citlali',
+ 'Mariela',
+ 'Roxana',
+ 'Ana',
+ 'Jacqueline',
+ 'Amairany',
+ 'Maria Belen',
+ 'Dariana',
+ 'Marlen',
+ 'Belen',
+ 'Nathalia',
+ 'Lia Fernanda',
+ 'Sofia Yamileth',
+ 'Valentina Guadalupe',
+ 'Luz Elena',
+ 'Fernanda Guadalupe',
+ 'Maritza',
+ 'Diana Sofia',
+ 'Dania',
+ 'Leticia',
+ 'Laura',
+ 'Paula Sofia',
+ 'Francisca',
+ 'Maria Del Rosario',
+ 'Carmen',
+ 'Dafne',
+ 'Johana',
+ 'Rubi',
+ 'Maria Ines',
+ 'Antonia',
+ 'Yolanda',
+ 'Amairani',
+ 'Gloria',
+ 'Danna Victoria',
+ 'Katia',
+ 'Luna',
+ 'Perla',
+ 'Natalia Guadalupe',
+ 'Estrella Guadalupe',
+ 'Yamileth Guadalupe',
+ 'Sarah',
+ 'Marina',
+ 'Paloma',
+ 'Maria De Los Angeles',
+ 'Marian',
+ 'Josefina',
+ 'Leah',
+ 'Camila Sofia',
+ 'Claudia',
+ 'Ivana',
+ 'Maria Isabella',
+ 'Hannia',
+ 'Nancy',
+ 'Keily',
+ 'Leilani',
+ 'Alexa Valentina',
+ 'Lorena',
+ 'Lia Isabella',
+ 'Renata Valentina',
+ 'Ines',
+ 'Areli',
+ 'Yaritza',
+ 'Magaly',
+ 'Evelyn Guadalupe',
+ 'Magali',
+ 'Damaris',
+ 'Karla Guadalupe',
+ 'Rocio',
+ 'Galilea',
+ 'Paulette',
+ 'Karime',
+ 'Maria Teresa',
+ 'Yesenia',
+ 'Melany Guadalupe',
+ 'Fernanda Sofia',
+ 'Angela Sofia',
+ 'Eva',
+ 'Joselin',
+ 'Rosalinda',
+ 'Dayana Guadalupe',
+ 'Maite',
+ 'Maricruz',
+ 'Rosario',
+ 'Alina',
+ 'Marcela',
+ 'Arlette',
+ 'Kendra',
+ 'Aylin Guadalupe',
+ 'Celeste',
+ 'Amayrani',
+ 'Naomi Guadalupe',
+ 'Judith',
+ 'Pamela',
+ 'Maria Renata',
+ 'Alexa Nicole',
+ 'Melina',
+ 'Ana Fernanda',
+ 'Ruth',
+ 'Lia Guadalupe',
+ 'Emma Valentina',
+ 'Hanna Sofia',
+ 'Rosa Isela',
+ 'Arisbeth',
+ 'Camila Yamileth',
+ 'Tamara',
+ 'Ana Brenda',
+ 'Reyna',
+ 'Briana',
+ 'Edith',
+ 'Luna Sofia',
+ 'Sofia Fernanda',
+ 'Luisa',
+ 'Arlet',
+ 'Xiomara',
+ 'Marlene',
+ 'Ana Luisa',
+ 'Renata Sofia',
+ 'Alexa Fernanda',
+ 'Alexa Yamileth',
+ 'Juliana',
+ 'Beatriz',
+ 'Silvia',
+ 'Nicole Guadalupe',
+ 'Janeth',
+ 'Stephanie',
+ 'Irene',
+ 'Andrea Sofia',
+ 'Alice',
+ 'Angela Guadalupe',
+ 'Zoe Guadalupe',
+ 'Sofia Abigail',
+ 'Sherlyn',
+ 'Ana Cristina',
+ 'Gianna',
+ 'Manuela',
+ 'Adilene',
+ 'Samantha Guadalupe',
+ 'Ximena Sofia',
+ 'Nicol',
+ 'Ana Regina',
+ 'Mayra',
+ 'Alejandra Guadalupe',
+ 'Nathaly',
+ 'Amaia',
+ 'Dalia',
+ 'Kenia Guadalupe',
+ 'Jocelyn',
+ 'Sofia Nicole',
+ 'Arianna',
+ 'Magdalena',
+ 'Virginia',
+ 'Lia Aitana',
+ 'Joselyn',
+ 'Cataleya',
+ 'Anayeli',
+ 'Lia Renata',
+ 'Mia Yamileth',
+ 'Maria Elizabeth',
+ 'Samanta',
+ 'Meredith',
+ 'Hannah',
+ 'Ana Camila',
+ 'Daniela Sofia',
+ 'Ingrid',
+ 'Viridiana',
+ 'Ana Guadalupe',
+ 'Ainhoa',
+ 'Carla',
+ 'Mariana Guadalupe',
+ 'Alexa Michelle',
+ 'Aranza Guadalupe',
+ 'Micaela',
+ 'Brittany',
+ 'Ivanna Sofia',
+ 'Mila',
+ 'Genesis Guadalupe',
+ 'Griselda',
+ 'Ailyn',
+ 'Lucia Fernanda',
+ 'Maricela',
+ 'Mia Alejandra',
+ 'Victoria Sofia',
+ 'Danna Guadalupe',
+ 'Maria Magdalena',
+ 'Leilany',
+ 'Victoria Elizabeth',
+ 'Daleyza',
+ 'Danna Valeria',
+ 'Sofia Michelle',
+ 'Estela',
+ 'Diana Laura',
+ 'Emily Valentina',
+ 'Belinda',
+ 'Mia Elizabeth',
+ 'Michelle Guadalupe',
+ 'Allison Guadalupe',
+ 'Camila Zoe',
+ 'Eliza',
+ 'Alisson Guadalupe',
+ 'Lia Romina',
+ 'Ariana Guadalupe',
+ 'Victoria Valentina',
+ 'Roberta',
+ 'Ana Gabriela',
+ 'Maria Ximena',
+ 'Jimena Guadalupe',
+ 'Maria Emilia',
+ 'Melanie Guadalupe',
+ 'Emma Lucia',
+ 'Angelina',
+ 'Danna Michelle',
+ 'Esmeralda Guadalupe',
+ 'Julissa',
+ 'Yuridia',
+ 'Angeles',
+ 'Victoria Alejandra',
+ 'Amalia',
+ 'Evelyn Sofia',
+ 'Alison Guadalupe',
+ 'Ana Isabel',
+ 'Alondra Guadalupe',
+ 'Martina',
+ 'Maria De La Luz',
+ 'Angela Victoria',
+ 'Dulce Guadalupe',
+ 'Ambar',
+ 'Sara Sofia',
+ 'Vianey',
+ 'Priscila',
+ 'Melissa Guadalupe',
+ 'America',
+ 'Sofia Isabel',
+ 'Andrea Valentina',
+ 'Georgina',
+ 'Yulissa',
+ 'Melany Sofia',
+ 'Yaretzi Guadalupe',
+ 'Estefani',
+ 'Yadira',
+ 'Mia Renata',
+ 'Renata Isabella',
+ 'Irma',
+ 'Diana Guadalupe',
+ 'Arleth Guadalupe',
+ 'Brianna',
+ 'Victoria Isabella',
+ 'Regina Sofia',
+ 'Lia Ximena',
+ 'Ana Belen',
+ 'Karla Daniela',
+ 'Sofia Renata'
+];
+
+const maleNames = [
+ 'Santiago',
+ 'Mateo',
+ 'Sebastian',
+ 'Leonardo',
+ 'Matias',
+ 'Emiliano',
+ 'Diego',
+ 'Miguel Angel',
+ 'Daniel',
+ 'Alexander',
+ 'Alejandro',
+ 'Gael',
+ 'Jesus',
+ 'Angel',
+ 'David',
+ 'Luis Angel',
+ 'Fernando',
+ 'Emmanuel',
+ 'Maximiliano',
+ 'Jose Luis',
+ 'Rodrigo',
+ 'Eduardo',
+ 'Jose Angel',
+ 'Juan Pablo',
+ 'Gabriel',
+ 'Dylan',
+ 'Juan Carlos',
+ 'Tadeo',
+ 'Axel',
+ 'Rafael',
+ 'Jose Manuel',
+ 'Isaac',
+ 'Alexis',
+ 'Ricardo',
+ 'Samuel',
+ 'Iker',
+ 'Jonathan',
+ 'Leonel',
+ 'Damian',
+ 'Emilio',
+ 'Alan',
+ 'Francisco',
+ 'Elias',
+ 'Nicolas',
+ 'Mauricio',
+ 'Angel Gabriel',
+ 'Cristian',
+ 'Adrian',
+ 'Carlos',
+ 'Luis Fernando',
+ 'Antonio',
+ 'Javier',
+ 'Josue',
+ 'Jose Antonio',
+ 'Jose Miguel',
+ 'Miguel',
+ 'Uriel',
+ 'Aaron',
+ 'Juan',
+ 'Andres',
+ 'Juan Diego',
+ 'Julian',
+ 'Manuel',
+ 'Brayan',
+ 'Pedro',
+ 'Kevin',
+ 'Abraham',
+ 'Gerardo',
+ 'Ismael',
+ 'Ivan',
+ 'Roberto',
+ 'Jose Maria',
+ 'Juan Jose',
+ 'Liam',
+ 'Erick',
+ 'Jose',
+ 'Ian',
+ 'Jorge',
+ 'Marco Antonio',
+ 'Juan Manuel',
+ 'Carlos Daniel',
+ 'Arturo',
+ 'Israel',
+ 'Pablo',
+ 'Alonso',
+ 'Oscar',
+ 'Oliver',
+ 'Saul',
+ 'Angel Mateo',
+ 'Omar',
+ 'Angel Daniel',
+ 'Bruno',
+ 'Angel Gael',
+ 'Jesus Antonio',
+ 'Brandon',
+ 'Ian Mateo',
+ 'Salvador',
+ 'Martin',
+ 'Hector',
+ 'Gustavo',
+ 'Francisco Javier',
+ 'Julio Cesar',
+ 'Alberto',
+ 'Esteban',
+ 'Raul',
+ 'Sergio',
+ 'Alfredo',
+ 'Enrique',
+ 'Cesar',
+ 'Angel Tadeo',
+ 'Jose Eduardo',
+ 'Victor Manuel',
+ 'Jesus Alejandro',
+ 'Moises',
+ 'Luis Antonio',
+ 'Angel David',
+ 'Ian Alexander',
+ 'Ezequiel',
+ 'Edwin',
+ 'Christopher',
+ 'Emanuel',
+ 'Elian',
+ 'Jesus Tadeo',
+ 'Angel Santiago',
+ 'Iker Gael',
+ 'Thiago',
+ 'Luis Alberto',
+ 'Angel De Jesus',
+ 'Jesus Daniel',
+ 'Luis',
+ 'Juan Antonio',
+ 'Jose Daniel',
+ 'Mario',
+ 'Joshua',
+ 'Jose De Jesus',
+ 'Edgar',
+ 'Fabian',
+ 'Armando',
+ 'Benjamin',
+ 'Ian Santiago',
+ 'Cristopher',
+ 'Jose Guadalupe',
+ 'Jesus Adrian',
+ 'Jesus Eduardo',
+ 'Jose Julian',
+ 'Luis Enrique',
+ 'Ian Gael',
+ 'Luis Daniel',
+ 'Dante',
+ 'Jose Carlos',
+ 'Ulises',
+ 'Caleb',
+ 'Isaias',
+ 'Christian',
+ 'Guillermo',
+ 'Iker Alexander',
+ 'Iker Santiago',
+ 'Jorge Luis',
+ 'Joel',
+ 'Dilan',
+ 'Dylan Gael',
+ 'Jesus Manuel',
+ 'Jose Alfredo',
+ 'Jesus Alexander',
+ 'Jesus Alberto',
+ 'Angel Eduardo',
+ 'Angel Jesus',
+ 'Jose Emiliano',
+ 'Patricio',
+ 'Jesus Santiago',
+ 'Jose Alberto',
+ 'Marcos',
+ 'Victor',
+ 'Daniel Alejandro',
+ 'Jesus Emmanuel',
+ 'Ernesto',
+ 'Axel Gael',
+ 'Dylan Alexander',
+ 'Dylan Mateo',
+ 'Valentin',
+ 'Iker Mateo',
+ 'Yahir',
+ 'Rogelio',
+ 'Luis Eduardo',
+ 'Ruben',
+ 'Jose Alejandro',
+ 'Misael',
+ 'Zaid',
+ 'Cristofer',
+ 'Jesus Gael',
+ 'Jose David',
+ 'Andre',
+ 'Abdiel',
+ 'Jesus Mateo',
+ 'Iker Daniel',
+ 'Marcelo',
+ 'Said',
+ 'Jesus Emiliano',
+ 'Yael',
+ 'Agustin',
+ 'Carlos Eduardo',
+ 'Alex',
+ 'Jose Mateo',
+ 'Diego Alejandro',
+ 'Ian Daniel',
+ 'Luis David',
+ 'Jose Armando',
+ 'Jose Santiago',
+ 'Hugo',
+ 'Angel Uriel',
+ 'Jose Francisco',
+ 'Liam Mateo',
+ 'Joaquin',
+ 'Juan Daniel',
+ 'Carlos Alberto',
+ 'Franco',
+ 'Liam Gael',
+ 'Liam Santiago',
+ 'Mariano',
+ 'Adan',
+ 'Emir',
+ 'Dario',
+ 'Alvaro',
+ 'Alfonso',
+ 'Jose Gael',
+ 'Ian Tadeo',
+ 'Jesus David',
+ 'Ian Alejandro',
+ 'Matias Alejandro',
+ 'Maximo',
+ 'Axel Mateo',
+ 'Luis Santiago',
+ 'Jacob',
+ 'Diego Alexander',
+ 'Adriel',
+ 'Ian Jesus',
+ 'Luis Mateo',
+ 'Osvaldo',
+ 'Liam Alexander',
+ 'Jaime',
+ 'Noe',
+ 'Mateo Alexander',
+ 'Jose Tadeo',
+ 'Felipe',
+ 'Angel Matias',
+ 'Mateo Alejandro',
+ 'Anthony',
+ 'Jose Fernando',
+ 'Luis Alejandro',
+ 'Giovanni',
+ 'Dylan Jesus',
+ 'Abel',
+ 'Gilberto',
+ 'Axel Daniel',
+ 'Efrain',
+ 'Eithan',
+ 'Luis Manuel',
+ 'Juan Jesus',
+ 'Aldo',
+ 'Cristobal',
+ 'Gael Alejandro',
+ 'Gonzalo',
+ 'Jared',
+ 'Octavio',
+ 'Dylan Tadeo',
+ 'Isai',
+ 'Angel Damian',
+ 'Vicente',
+ 'Leonardo Daniel',
+ 'Dylan Santiago',
+ 'Gael Alexander',
+ 'Carlos Manuel',
+ 'Carlos Santiago',
+ 'Juan Angel',
+ 'Iker Tadeo',
+ 'Angel Manuel',
+ 'David Alejandro',
+ 'Juan David',
+ 'Iker Alejandro',
+ 'Jose Juan',
+ 'Jaziel',
+ 'Angel Antonio',
+ 'Diego Armando',
+ 'Johan',
+ 'Lucas',
+ 'Carlos Mateo',
+ 'Yair',
+ 'Orlando',
+ 'Ariel',
+ 'Mario Alberto',
+ 'Carlos Gael',
+ 'Erik',
+ 'Santiago Gael',
+ 'Daniel Alexander',
+ 'Eliseo',
+ 'Roman',
+ 'Juan Luis',
+ 'Dylan Alejandro',
+ 'Jose Emmanuel',
+ 'Luis Gerardo',
+ 'Jose Pablo',
+ 'Mathias',
+ 'Jesus Gabriel',
+ 'Alan Mateo',
+ 'Iker Jesus',
+ 'Erick Daniel',
+ 'Jose Leonardo',
+ 'Mateo De Jesus',
+ 'Rodolfo',
+ 'Jorge Alberto',
+ 'Juan Francisco',
+ 'Jose Alexander',
+ 'Carlos Antonio',
+ 'Irving',
+ 'Julio',
+ 'Angel Sebastian',
+ 'Luis Gael',
+ 'Ramiro',
+ 'Ignacio',
+ 'Angel Emmanuel',
+ 'Jose Gabriel',
+ 'Jesus Alexis',
+ 'Axel Uriel',
+ 'Angel Alexander',
+ 'Liam Daniel',
+ 'Carlos David',
+ 'Jose Adrian',
+ 'Santiago De Jesus',
+ 'Jose Matias',
+ 'William',
+ 'Ethan',
+ 'Axel Santiago',
+ 'Victor Hugo',
+ 'Alan Gael',
+ 'Angel Adrian',
+ 'Ian Eduardo',
+ 'Luis Tadeo',
+ 'Juan De Dios',
+ 'Angel Emiliano',
+ 'Humberto',
+ 'Diego Gael',
+ 'Axel Tadeo',
+ 'Angel Leonardo',
+ 'Mauro',
+ 'Noah',
+ 'Ramon',
+ 'Angel Ivan',
+ 'Erick Santiago',
+ 'Bryan',
+ 'Bernardo',
+ 'Lorenzo',
+ 'Manuel Alejandro',
+ 'Ian Alexis',
+ 'Luis Miguel',
+ 'Jesus Guadalupe',
+ 'Jose Emilio',
+ 'Derek',
+ 'Rigoberto',
+ 'Tadeo De Jesus',
+ 'Luis Carlos',
+ 'Jesus Enrique',
+ 'Jose Damian',
+ 'Braulio',
+ 'Eliel',
+ 'Liam Alejandro',
+ 'Evan',
+ 'Tomas',
+ 'Kevin Alexander',
+ 'Carlos Adrian',
+ 'Adolfo',
+ 'Diego Alberto',
+ 'Jesus Angel',
+ 'Mateo Sebastian',
+ 'Carlos Tadeo',
+ 'German',
+ 'Liam Tadeo',
+ 'Jesus Ivan',
+ 'Jose Enrique',
+ 'Luciano',
+ 'Dylan Alexis',
+ 'Matias Alexander',
+ 'Matias Emiliano',
+ 'Matteo',
+ 'Alan Daniel',
+ 'Luis Adrian',
+ 'Luis Alexander',
+ 'Eleazar',
+ 'Angel Yael',
+ 'Axel Gabriel',
+ 'Angel Alexis',
+ 'Ian Emmanuel',
+ 'Alan Jesus',
+ 'Angel Alejandro',
+ 'Ian Yael',
+ 'Miguel Alejandro',
+ 'Alan Santiago',
+ 'Luis Felipe',
+ 'Dylan Eduardo',
+ 'Mateo Emiliano',
+ 'Rene',
+ 'Ian Emiliano',
+ 'Luis Armando',
+ 'Iker Antonio',
+ 'Axel Jesus',
+ 'Dariel',
+ 'Cristian Gael',
+ 'Iker Matias',
+ 'Jose Roberto',
+ 'Santiago Alexander',
+ 'Jorge Antonio',
+ 'Carlos Ivan',
+ 'Jose Andres',
+ 'Angel Alberto',
+ 'Ian David',
+ 'Carlos Damian',
+ 'Eder',
+ 'Diego Alonso',
+ 'Jesus Miguel',
+ 'Santiago Alejandro',
+ 'Santiago Daniel',
+ 'Leon',
+ 'Axel Eduardo',
+ 'Javier Alejandro',
+ 'Iker Eduardo',
+ 'Jorge Alejandro',
+ 'Jesus Emanuel',
+ 'Luca',
+ 'Liam Jesus',
+ 'Axel David',
+ 'Gabriel Alejandro',
+ 'Juan Alberto',
+ 'Jesus Sebastian',
+ 'Leonardo Gael',
+ 'Rolando',
+ 'David Alexander',
+ 'Juan Mateo',
+ 'Angel Isaac',
+ 'Carlos Alejandro',
+ 'Mateo Nicolas',
+ 'Luis Mario',
+ 'Diego Antonio',
+ 'Roberto Carlos',
+ 'Heriberto',
+ 'Danilo',
+ 'Erick Alexander',
+ 'Angel Fernando',
+ 'Jesus Alfredo',
+ 'Ian Gabriel',
+ 'Jesus Damian',
+ 'Leandro',
+ 'Damian Alejandro',
+ 'Jesus Armando',
+ 'Jesus Fernando',
+ 'Juan Miguel',
+ 'Angel Zaid',
+ 'Carlos Enrique',
+ 'Jose Martin',
+ 'Jose Ricardo',
+ 'Iker Damian',
+ 'Camilo',
+ 'Santiago Emmanuel',
+ 'Jeremy',
+ 'Angel Said',
+ 'Victor Daniel',
+ 'Cristian Daniel',
+ 'Oscar Daniel',
+ 'Iker Manuel',
+ 'Iker Alexis',
+ 'Angel Francisco',
+ 'Ian Sebastian',
+ 'Carlos Alexander',
+ 'Dylan Daniel',
+ 'Axel Yael',
+ 'Jordan',
+ 'Dilan Gael',
+ 'Alan Eduardo',
+ 'Angel Rafael',
+ 'Dylan Antonio',
+ 'Elian Gael',
+ 'Isidro',
+ 'Luis Gabriel',
+ 'Angel Yahir',
+ 'Jeronimo',
+ 'Erick Gael',
+ 'Alan Tadeo',
+ 'Iker David',
+ 'Azael',
+ 'Gael Emiliano',
+ 'Ian Fernando',
+ 'Jose Ramon',
+ 'Jose Javier',
+ 'Liam Zaid',
+ 'Pablo Emilio',
+ 'Cesar Alejandro',
+ 'Felipe De Jesus',
+ 'Jonathan Gael',
+ 'Leo',
+ 'Jesus Abraham',
+ 'Jose Ivan',
+ 'Fidel',
+ 'Oswaldo',
+ 'Luis Javier',
+ 'Felix',
+ 'Tadeo Alejandro',
+ 'Ramses',
+ 'Reynaldo',
+ 'Joan',
+ 'Ian Zaid',
+ 'Angel Leonel',
+ 'Carlos Jesus',
+ 'Henry',
+ 'Lucio',
+ 'Axel Ivan',
+ 'Liam Sebastian',
+ 'Alexis Gael',
+ 'Matias Gael',
+ 'Diego Emiliano',
+ 'Jesus Matias',
+ 'Jose Arturo',
+ 'Jose Rafael',
+ 'Luis Damian',
+ 'Dylan Yael',
+ 'Luis Alfredo',
+ 'Dominic',
+ 'Abimael',
+ 'Cristian Alexander',
+ 'Liam Eduardo',
+ 'Manuel De Jesus',
+ 'Diego Alexis',
+ 'Jonathan Alexander',
+ 'Carlos Gabriel',
+ 'Ivan Alejandro',
+ 'Victor Gael',
+ 'Dilan Alexander',
+ 'Guadalupe',
+ 'Tadeo Alexander',
+ 'Iker Yael',
+ 'Kevin Gael',
+ 'Luis Arturo',
+ 'Marcelino',
+ 'Aldair',
+ 'Brayan Alexis',
+ 'Ian Said',
+ 'Javier Alexander',
+ 'Angel Josue',
+ 'Raymundo',
+ 'Hector Manuel',
+ 'Santiago Tadeo',
+ 'Jose Sebastian',
+ 'Alexis Mateo',
+ 'Axel Damian',
+ 'Iker Emiliano',
+ 'Alexis Daniel',
+ 'Ander',
+ 'Jesus Leonardo',
+ 'Ian Josue',
+ 'Carlos Emiliano',
+ 'Cristian Alexis',
+ 'Erick Alejandro',
+ 'Luis Emiliano',
+ 'Nathan',
+ 'Fredy',
+ 'Hector Daniel',
+ 'Jose Jesus',
+ 'Jesus Francisco',
+ 'Jose Elias',
+ 'Alexis Tadeo',
+ 'Gadiel',
+ 'Gael Antonio',
+ 'Matias Sebastian',
+ 'Matias Santiago',
+ 'Pedro Damian',
+ 'Dilan Mateo',
+ 'Hernan',
+ 'Elian Mateo',
+ 'Erick Mateo',
+ 'Ian De Jesus',
+ 'Jeremias',
+ 'Axel Matias',
+ 'Natanael',
+ 'Jose Ignacio',
+ 'Vladimir',
+ 'Dylan Josue',
+ 'Ian Alberto',
+ 'Joseph',
+ 'Angel Javier',
+ 'Ian Leonardo',
+ 'Ian Antonio',
+ 'Alan David',
+ 'Jose Rodrigo',
+ 'Iker Emmanuel',
+ 'Miguel Alexander',
+ 'Cristian Alejandro',
+ 'Everardo',
+ 'David Santiago',
+ 'Jonathan Alexis',
+ 'Carlos Emilio',
+ 'Cesar Gael',
+ 'Iker Ivan',
+ 'Uziel',
+ 'Dylan De Jesus',
+ 'Dylan Alberto',
+ 'Iker Sebastian',
+ 'Leonel Alejandro',
+ 'Liam Emiliano',
+ 'Diego Ivan',
+ 'Axel Antonio',
+ 'Ian Uriel',
+ 'Karim',
+ 'Iker Alberto',
+ 'Edwin Gael',
+ 'Iker Gabriel',
+ 'Liam David',
+ 'Mateo Gael',
+ 'Eugenio',
+ 'Leonardo David',
+ 'Luis Francisco',
+ 'Axel Emiliano',
+ 'Diego Jesus',
+ 'Carlos Alexis',
+ 'Isaac Alejandro',
+ 'Jesus Uriel',
+ 'Noel',
+ 'Matias Daniel',
+ 'Francisco Gael',
+ 'Jesus Rafael',
+ 'Dylan Emmanuel',
+ 'Santiago Nicolas',
+ 'Angel Yair',
+ 'Jorge Eduardo',
+ 'Genaro',
+ 'Jose Raul',
+ 'Gregorio',
+ 'Sebastian Alejandro',
+ 'Ivan Alexander',
+ 'Carlos Miguel',
+ 'Logan',
+ 'Omar Alejandro',
+ 'Ian Manuel',
+ 'Lian',
+ 'Aurelio',
+ 'Jesus Emilio',
+ 'Renato',
+ 'Jesus Andres',
+ 'Cristian Jesus',
+ 'Mateo Daniel',
+ 'Brayan Alexander',
+ 'Carlos Emmanuel',
+ 'Gael Santiago',
+ 'Luis Roberto',
+ 'Kevin Daniel',
+ 'Brian',
+ 'Angel Guadalupe',
+ 'Carlos Javier',
+ 'Miguel Antonio',
+ 'Benito',
+ 'Iker Zaid',
+ 'Jose Abraham',
+ 'Jorge Daniel',
+ 'Fabricio',
+ 'Angel Miguel',
+ 'Dilan Jesus',
+ 'Esau',
+ 'Kevin Alexis',
+ 'Carlos Matias',
+ 'Dorian',
+ 'Luis Jesus',
+ 'Matias Eduardo',
+ 'Diego Emmanuel',
+ 'Liam Gabriel',
+ 'Iker Said',
+ 'Edwin Alexander',
+ 'Jesus Alonso',
+ 'Mateo Santiago',
+ 'Santiago Rafael',
+ 'Ian Jose',
+ 'Fernando Gael',
+ 'Hector Gael',
+ 'Edgar Gael',
+ 'Ian Caleb',
+ 'Dylan Gabriel',
+ 'Ian Leonel',
+ 'Luis Matias',
+ 'Dylan Uriel',
+ 'Cristian Mateo',
+ 'Joshua Alexander',
+ 'Oscar Gael',
+ 'Dilan Tadeo',
+ 'Liam Emmanuel',
+ 'Osmar',
+ 'Jairo',
+ 'Juan Tadeo',
+ 'Luis Emilio',
+ 'Dylan Jose',
+ 'Iker Fernando',
+ 'Axel Yahir',
+ 'Yoel',
+ 'Axel Fernando',
+ 'Yovani',
+ 'Jesus Elias',
+ 'Jesus Javier',
+ 'Jorge Gael',
+ 'Eric',
+ 'Alan Gabriel',
+ 'Cesar Daniel',
+ 'Damian Alexander'
+];
+
+const lastNames = [
+ 'Hernandez',
+ 'Garcia',
+ 'Martinez',
+ 'Lopez',
+ 'Gonzalez',
+ 'Perez',
+ 'Rodriguez',
+ 'Sanchez',
+ 'Ramirez',
+ 'Cruz',
+ 'Flores',
+ 'Gomez',
+ 'Morales',
+ 'Vazquez',
+ 'Jimenez',
+ 'Reyes',
+ 'Torres',
+ 'Diaz',
+ 'Gutierrez',
+ 'Mendoza',
+ 'Ruiz',
+ 'Aguilar',
+ 'Mendez',
+ 'Ortiz',
+ 'Moreno',
+ 'Juarez',
+ 'Castillo',
+ 'Romero',
+ 'Alvarez',
+ 'Ramos',
+ 'Rivera',
+ 'Chavez',
+ 'De La Cruz',
+ 'Dominguez',
+ 'Guzman',
+ 'Velazquez',
+ 'Santiago',
+ 'Herrera',
+ 'Vargas',
+ 'Castro',
+ 'Medina',
+ 'Rojas',
+ 'Muñoz',
+ 'Luna',
+ 'Bautista',
+ 'Contreras',
+ 'Salazar',
+ 'Ortega',
+ 'Guerrero',
+ 'Estrada',
+ 'Cortes',
+ 'Alvarado',
+ 'Soto',
+ 'Espinoza',
+ 'Lara',
+ 'Avila',
+ 'Carrillo',
+ 'Cervantes',
+ 'Rios',
+ 'Santos',
+ 'Silva',
+ 'Delgado',
+ 'Marquez',
+ 'Vega',
+ 'Mejia',
+ 'Sandoval',
+ 'Rosas',
+ 'Leon',
+ 'Solis',
+ 'Ibarra',
+ 'Nuñez',
+ 'Valdez',
+ 'Campos',
+ 'Fernandez',
+ 'Rosales',
+ 'Camacho',
+ 'Maldonado',
+ 'Nava',
+ 'Valencia',
+ 'Miranda',
+ 'Cabrera',
+ 'Pacheco',
+ 'Navarro',
+ 'Acosta',
+ 'Peña',
+ 'Trejo',
+ 'Antonio',
+ 'Huerta',
+ 'Molina',
+ 'Castañeda',
+ 'Rangel',
+ 'Robles',
+ 'De Jesus',
+ 'Salas',
+ 'Fuentes',
+ 'Meza',
+ 'Padilla',
+ 'Zuñiga',
+ 'Orozco',
+ 'Ayala',
+ 'Valenzuela',
+ 'Aguirre',
+ 'Cardenas',
+ 'Serrano',
+ 'Mora',
+ 'Salinas',
+ 'Tapia',
+ 'Duran',
+ 'Olvera',
+ 'Macias',
+ 'Espinosa',
+ 'Arellano',
+ 'Velasco',
+ 'Ochoa',
+ 'Zamora',
+ 'Suarez',
+ 'Marin',
+ 'Alonso',
+ 'Calderon',
+ 'Arias',
+ 'Gallegos',
+ 'Vasquez',
+ 'Barrera',
+ 'Esquivel',
+ 'Montes',
+ 'Zavala',
+ 'Villegas',
+ 'Galvan',
+ 'Villanueva',
+ 'Lozano',
+ 'Beltran',
+ 'Franco',
+ 'Andrade',
+ 'Trujillo',
+ 'Ponce',
+ 'Sosa',
+ 'Galindo',
+ 'Figueroa',
+ 'Palacios',
+ 'Cortez',
+ 'Pineda',
+ 'Villa',
+ 'Rocha',
+ 'Corona',
+ 'Osorio',
+ 'Cano',
+ 'Escobar',
+ 'Carmona',
+ 'Bernal',
+ 'Rubio',
+ 'Bravo',
+ 'Benitez',
+ 'Mata',
+ 'Resendiz',
+ 'Cordova',
+ 'Cuevas',
+ 'Enriquez',
+ 'Tellez',
+ 'De La Rosa',
+ 'Montoya',
+ 'Santiz',
+ 'Rivas',
+ 'Parra',
+ 'Leyva',
+ 'Francisco',
+ 'Cisneros',
+ 'Quintero',
+ 'Quiroz',
+ 'Tovar',
+ 'Avalos',
+ 'Esparza',
+ 'Becerra',
+ 'Zarate',
+ 'Peralta',
+ 'Roman',
+ 'Salgado',
+ 'Del Angel',
+ 'Felix',
+ 'Garza',
+ 'Chan',
+ 'Vera',
+ 'Barajas',
+ 'Arroyo',
+ 'De Leon',
+ 'Montiel',
+ 'Guevara',
+ 'Saucedo',
+ 'Murillo',
+ 'Olivares',
+ 'Segura',
+ 'Angeles',
+ 'Solano',
+ 'Ventura',
+ 'Reyna',
+ 'Gallardo',
+ 'Santana',
+ 'Lugo',
+ 'Castellanos',
+ 'Zapata',
+ 'Villalobos',
+ 'Paredes',
+ 'Navarrete',
+ 'Arriaga',
+ 'Escobedo',
+ 'Piña',
+ 'Guillen',
+ 'Mercado',
+ 'Davila',
+ 'Mondragon',
+ 'Villarreal',
+ 'Guerra',
+ 'De Los Santos',
+ 'Nieto',
+ 'Barrios',
+ 'Zepeda',
+ 'Saldaña',
+ 'Yañez',
+ 'Leal',
+ 'Barron',
+ 'Granados',
+ 'Magaña',
+ 'Monroy',
+ 'May',
+ 'Palma',
+ 'Balderas',
+ 'Godinez',
+ 'Bonilla',
+ 'Acevedo',
+ 'Medrano',
+ 'Carrera',
+ 'Islas',
+ 'Caballero',
+ 'Rico',
+ 'Ojeda',
+ 'Salvador',
+ 'Gil',
+ 'Galicia',
+ 'Garduño',
+ 'Coronado',
+ 'Pech',
+ 'Lorenzo',
+ 'Alfaro',
+ 'Najera',
+ 'Uribe',
+ 'Duarte',
+ 'Gamez',
+ 'Arredondo',
+ 'Gaytan',
+ 'Trinidad',
+ 'Carranza',
+ 'Carbajal',
+ 'Amador',
+ 'Escamilla',
+ 'Aguilera',
+ 'Sierra',
+ 'Barragan',
+ 'Melendez',
+ 'Blanco',
+ 'Vidal',
+ 'Arenas',
+ 'Arcos',
+ 'Escalante',
+ 'Baltazar',
+ 'Correa',
+ 'Renteria',
+ 'Hurtado',
+ 'Baez',
+ 'Aparicio',
+ 'Miguel',
+ 'Ocampo',
+ 'Tolentino',
+ 'Galvez',
+ 'Montalvo',
+ 'Romo',
+ 'Merino',
+ 'Jaramillo',
+ 'Alcantara',
+ 'Armenta',
+ 'Zaragoza',
+ 'Gaspar',
+ 'Santillan',
+ 'Arreola',
+ 'Roque',
+ 'Colin',
+ 'Lazaro',
+ 'Carrasco',
+ 'Anaya',
+ 'Quezada',
+ 'Soriano',
+ 'Cordero',
+ 'Venegas',
+ 'Valadez',
+ 'Aquino',
+ 'Alarcon',
+ 'Varela',
+ 'Valle',
+ 'Valdes',
+ 'Roblero',
+ 'Lira',
+ 'Moran',
+ 'Martin',
+ 'Aranda',
+ 'Rendon',
+ 'Nicolas',
+ 'Lucas',
+ 'Canul',
+ 'Luis',
+ 'Hidalgo',
+ 'Cazares',
+ 'Aviles',
+ 'Angel',
+ 'Cardona',
+ 'Altamirano',
+ 'Segundo',
+ 'Quintana',
+ 'Mateo',
+ 'Muñiz',
+ 'Toledo',
+ 'Sotelo',
+ 'Ordoñez',
+ 'Maya',
+ 'Portillo',
+ 'Bello',
+ 'Saavedra',
+ 'Matias',
+ 'Arteaga',
+ 'Limon',
+ 'Barrientos',
+ 'Mena',
+ 'Pedraza',
+ 'Cuellar',
+ 'Casillas',
+ 'Bustamante',
+ 'Madrigal',
+ 'Urbina',
+ 'Paz',
+ 'Ornelas',
+ 'Prado',
+ 'Ontiveros',
+ 'Vicente',
+ 'Anguiano',
+ 'De La Torre',
+ 'Covarrubias',
+ 'Nolasco',
+ 'Briones',
+ 'Arce',
+ 'Montejo',
+ 'Fonseca',
+ 'Bustos',
+ 'Montero',
+ 'Jaimes',
+ 'Melchor',
+ 'Delgadillo',
+ 'Avendaño',
+ 'Guadarrama',
+ 'Rincon',
+ 'Vallejo',
+ 'Cedillo',
+ 'Velez',
+ 'Canche',
+ 'Linares',
+ 'Carreon',
+ 'Zamudio',
+ 'Poot',
+ 'Millan',
+ 'Frias',
+ 'Becerril',
+ 'Ceron',
+ 'Puente',
+ 'Neri',
+ 'Jacobo',
+ 'Aleman',
+ 'Giron',
+ 'Pulido',
+ 'Mota',
+ 'Negrete',
+ 'Patiño',
+ 'Bolaños',
+ 'Robledo',
+ 'Olguin',
+ 'Reynoso',
+ 'Pablo',
+ 'Alejandro',
+ 'Montaño',
+ 'Cantu',
+ 'Gamboa',
+ 'Esteban',
+ 'Jose',
+ 'Alejo',
+ 'Espino',
+ 'Damian',
+ 'Jasso',
+ 'Garrido',
+ 'Munguia',
+ 'Araujo',
+ 'Mireles',
+ 'Felipe',
+ 'Valdivia',
+ 'Treviño',
+ 'Olmos',
+ 'Meneses',
+ 'Casas',
+ 'Pascual',
+ 'Ledezma',
+ 'Alcala',
+ 'Rueda',
+ 'Heredia',
+ 'Badillo',
+ 'Noriega',
+ 'Dzul',
+ 'Lemus',
+ 'Serna',
+ 'Angulo',
+ 'Barraza',
+ 'Vergara',
+ 'Chavarria',
+ 'Andres',
+ 'Cerda',
+ 'Juan',
+ 'Gaona',
+ 'Cadena',
+ 'Marcial',
+ 'Alcantar',
+ 'Caamal',
+ 'Aragon',
+ 'Barcenas',
+ 'Alanis',
+ 'Nieves',
+ 'Banda',
+ 'Mares',
+ 'Chi',
+ 'Almaraz',
+ 'Osuna',
+ 'Tenorio',
+ 'Chacon',
+ 'Chable',
+ 'Cornejo',
+ 'Prieto',
+ 'Ceballos',
+ 'Camarillo',
+ 'Adame',
+ 'Diego',
+ 'Tejeda',
+ 'Ramon',
+ 'Zacarias',
+ 'Amaro',
+ 'Amaya',
+ 'Cota',
+ 'Soria',
+ 'Jeronimo',
+ 'Ordaz',
+ 'Bermudez',
+ 'Uc',
+ 'Roldan',
+ 'Loera',
+ 'Almanza',
+ 'Hinojosa',
+ 'Manuel',
+ 'Arevalo',
+ 'Valerio',
+ 'Solorzano',
+ 'Jaime',
+ 'Alba',
+ 'Cauich',
+ 'Ceja',
+ 'Pedroza',
+ 'Elias',
+ 'Velasquez',
+ 'Landeros',
+ 'Agustin',
+ 'Olivas',
+ 'Vilchis',
+ 'Jacinto',
+ 'Gabriel',
+ 'Rojo',
+ 'Mancilla',
+ 'Clemente',
+ 'Corral',
+ 'Bañuelos',
+ 'Zambrano',
+ 'Manzano',
+ 'Pantoja',
+ 'Tinoco',
+ 'Ku',
+ 'Oliva',
+ 'Lucio',
+ 'Ruvalcaba',
+ 'Servin',
+ 'Estrella',
+ 'Betancourt',
+ 'Acuña',
+ 'Saenz',
+ 'Sarmiento',
+ 'Vela',
+ 'Razo',
+ 'De Luna',
+ 'Abarca',
+ 'Sarabia',
+ 'Sifuentes',
+ 'Balam',
+ 'Dorantes',
+ 'Ledesma',
+ 'Hilario',
+ 'Ruelas',
+ 'Fabian',
+ 'Perales',
+ 'Tomas',
+ 'Marcos',
+ 'Verdugo',
+ 'Villaseñor',
+ 'Landa',
+ 'Aldana',
+ 'Saldivar',
+ 'Quiñones',
+ 'Calixto',
+ 'Tello',
+ 'Alcaraz',
+ 'Bustillos',
+ 'Lizarraga',
+ 'Dzib',
+ 'Mosqueda',
+ 'Niño',
+ 'Tun',
+ 'Segovia',
+ 'Ascencio',
+ 'Palafox',
+ 'Gregorio',
+ 'Lozada',
+ 'Padron',
+ 'Viveros',
+ 'Camargo',
+ 'Narvaez',
+ 'Aceves',
+ 'Teran',
+ 'Oropeza',
+ 'Paez',
+ 'Cristobal',
+ 'Ibañez',
+ 'Marroquin',
+ 'Peñaloza',
+ 'Aguayo',
+ 'Gastelum',
+ 'Fierro',
+ 'Carlos',
+ 'Larios',
+ 'Santoyo',
+ 'Plascencia',
+ 'Preciado',
+ 'Armendariz',
+ 'Barbosa',
+ 'Ovando',
+ 'Tamayo',
+ 'Puc',
+ 'Bahena',
+ 'Meraz',
+ 'Tirado',
+ 'Ek',
+ 'Naranjo',
+ 'Pichardo',
+ 'Bojorquez',
+ 'Calvillo',
+ 'Conde',
+ 'Bernabe',
+ 'De La Luz',
+ 'Elizalde',
+ 'Lomeli',
+ 'Geronimo',
+ 'Chaparro',
+ 'Briseño',
+ 'Brito',
+ 'Pimentel',
+ 'Regalado',
+ 'Valentin',
+ 'Bueno',
+ 'Mendiola',
+ 'Centeno',
+ 'Bartolo',
+ 'Perea',
+ 'Izquierdo',
+ 'Canales',
+ 'Isidro',
+ 'Colorado',
+ 'Gordillo',
+ 'Valles',
+ 'Sebastian',
+ 'Loredo',
+ 'Salcedo',
+ 'Victoria',
+ 'Lima',
+ 'Pool',
+ 'Olivera',
+ 'Olmedo',
+ 'Blas',
+ 'Coronel',
+ 'Patricio',
+ 'Feliciano',
+ 'Arguello',
+ 'Camarena',
+ 'Burgos',
+ 'Solorio',
+ 'Dimas',
+ 'Cayetano',
+ 'Porras',
+ 'Carrizales',
+ 'Simon',
+ 'Javier',
+ 'Galaviz',
+ 'Alegria',
+ 'Corrales',
+ 'Moo',
+ 'Basilio',
+ 'Alvaro',
+ 'De Santiago',
+ 'Oviedo',
+ 'Garay',
+ 'Onofre',
+ 'Palomares',
+ 'Garibay',
+ 'Santamaria',
+ 'Mandujano',
+ 'Julian',
+ 'Sepulveda',
+ 'Madrid',
+ 'Victoriano',
+ 'Dueñas',
+ 'Mateos',
+ 'Ignacio',
+ 'Rafael',
+ 'Mayo',
+ 'Pedro',
+ 'Lucero',
+ 'Mauricio',
+ 'Rivero',
+ 'Mariano',
+ 'Farias',
+ 'Manriquez',
+ 'Cepeda',
+ 'Curiel',
+ 'Moctezuma',
+ 'Almazan',
+ 'Medel',
+ 'Montes De Oca',
+ 'Quiñonez',
+ 'Ulloa',
+ 'Jauregui',
+ 'Alberto',
+ 'Mendieta',
+ 'Fajardo',
+ 'Basurto',
+ 'Sauceda',
+ 'Quevedo',
+ 'Lerma',
+ 'Plata',
+ 'Falcon',
+ 'Loza',
+ 'Aburto',
+ 'Maciel',
+ 'Zavaleta',
+ 'Terrazas',
+ 'Botello',
+ 'Nevarez',
+ 'Melo',
+ 'Medellin',
+ 'Zazueta',
+ 'Puga',
+ 'Cordoba',
+ 'Araiza',
+ 'Galeana',
+ 'Moya',
+ 'Valdovinos',
+ 'Fragoso',
+ 'Llamas',
+ 'Hipolito',
+ 'Noh',
+ 'Olan',
+ 'Baeza',
+ 'Bocanegra',
+ 'Ovalle',
+ 'Orta',
+ 'Marcelino',
+ 'Lagunas',
+ 'Villagomez',
+ 'Carvajal',
+ 'De Dios',
+ 'Montelongo',
+ 'Manzo',
+ 'Gudiño',
+ 'Lechuga',
+ 'Benito',
+ 'Castañon',
+ 'Grimaldo',
+ 'Albarran',
+ 'Elizondo',
+ 'Ferrer',
+ 'Ake',
+ 'Dolores',
+ 'Ambrosio',
+ 'Lezama',
+ 'Mojica',
+ 'Barba',
+ 'Toribio',
+ 'Arvizu',
+ 'Palomo',
+ 'Can',
+ 'Gasca',
+ 'Infante',
+ 'Dionicio',
+ 'Zurita',
+ 'Calzada',
+ 'Garnica',
+ 'Paniagua',
+ 'Sevilla',
+ 'Zamarripa',
+ 'Palomino',
+ 'Alcocer',
+ 'Ambriz',
+ 'Laguna',
+ 'Almeida',
+ 'Salmeron',
+ 'Partida',
+ 'Amezcua',
+ 'Cid',
+ 'Benavides',
+ 'Mariscal',
+ 'Apolinar',
+ 'Alva',
+ 'San Juan',
+ 'Lazcano',
+ 'Alonzo',
+ 'Ocaña',
+ 'Luciano',
+ 'Arana',
+ 'Echeverria',
+ 'Rosado',
+ 'Velazco',
+ 'Calvo',
+ 'Celis',
+ 'Canseco',
+ 'Guadalupe',
+ 'Galan',
+ 'Castrejon',
+ 'Davalos',
+ 'Cornelio',
+ 'Cardoso',
+ 'Escalona',
+ 'Ballesteros',
+ 'Euan',
+ 'O',
+ 'Montañez',
+ 'Arguelles',
+ 'Catalan',
+ 'Urias',
+ 'Loya',
+ 'Arzate',
+ 'Villalba',
+ 'Reza',
+ 'Rizo',
+ 'Zamorano',
+ 'Laureano',
+ 'Uicab',
+ 'Blancas',
+ 'Jurado',
+ 'Pat',
+ 'Murrieta',
+ 'Gongora',
+ 'Licona',
+ 'De La O',
+ 'Favela',
+ 'Lujan',
+ 'Higuera',
+ 'Ugalde',
+ 'Encarnacion',
+ 'Gurrola',
+ 'Moncada',
+ 'Iñiguez',
+ 'Cobos',
+ 'Castelan',
+ 'Tuz',
+ 'Haro',
+ 'Mayorga',
+ 'Otero',
+ 'Rascon',
+ 'Aviña',
+ 'Frausto',
+ 'Bucio',
+ 'Valladares',
+ 'Calihua',
+ 'Panzo',
+ 'Silverio',
+ 'Monsivais',
+ 'Pastrana',
+ 'Macedo',
+ 'Evangelista',
+ 'Chavira',
+ 'Chuc',
+ 'Piñon',
+ 'Urbano',
+ 'Pardo',
+ 'Talavera',
+ 'Monreal',
+ 'Almaguer',
+ 'Chino',
+ 'Del Rio',
+ 'Arizmendi',
+ 'Alfonso',
+ 'Buendia',
+ 'Espindola',
+ 'Carreño',
+ 'Godoy',
+ 'Colli',
+ 'Villalpando',
+ 'Vite',
+ 'Enciso',
+ 'Carballo',
+ 'Carpio',
+ 'Guajardo',
+ 'Marmolejo',
+ 'Ahumada',
+ 'Serrato',
+ 'Holguin',
+ 'Cabello',
+ 'Magdaleno',
+ 'Jara',
+ 'Silvestre',
+ 'Celestino',
+ 'Magallanes',
+ 'Vieyra',
+ 'Luevano',
+ 'Obregon',
+ 'Barranco',
+ 'Albino',
+ 'Temoxtle',
+ 'Rosario',
+ 'Posadas',
+ 'Verduzco',
+ 'Olivo',
+ 'Caudillo',
+ 'Zermeño',
+ 'Casimiro',
+ 'Domingo',
+ 'Cancino',
+ 'Hau',
+ 'Guardado',
+ 'Yam',
+ 'Bruno',
+ 'De Anda',
+ 'Vaca',
+ 'Velarde',
+ 'Barboza',
+ 'Valtierra',
+ 'Balcazar',
+ 'Govea',
+ 'Garfias',
+ 'De La Fuente',
+ 'Puentes',
+ 'Anastacio',
+ 'Coria',
+ 'Leija',
+ 'Baca',
+ 'Colunga',
+ 'Cen',
+ 'Vizcarra',
+ 'Raya',
+ 'Miramontes',
+ 'Chairez',
+ 'Candelario',
+ 'Peraza',
+ 'Pozos',
+ 'Bedolla',
+ 'Cavazos',
+ 'Crisostomo',
+ 'Salcido',
+ 'Aguila',
+ 'Duron',
+ 'Marcelo',
+ 'Lobato',
+ 'Zenteno',
+ 'Tinajero',
+ 'Bernardino',
+ 'Tadeo',
+ 'Villeda',
+ 'Guillermo',
+ 'Bailon',
+ 'Santes',
+ 'Olea',
+ 'Nuño',
+ 'Avilez',
+ 'Barreto',
+ 'Sanabria',
+ 'Del Valle',
+ 'Bazan',
+ 'Zetina',
+ 'Arreguin',
+ 'Bejarano',
+ 'Casanova',
+ 'Ambrocio',
+ 'Mar',
+ 'Merida',
+ 'Parada',
+ 'Caro',
+ 'Manjarrez',
+ 'Morelos',
+ 'Patishtan',
+ 'Grande',
+ 'Zeferino',
+ 'Hermosillo',
+ 'Muro',
+ 'Pelayo',
+ 'Payan',
+ 'Buenrostro',
+ 'Gaxiola',
+ 'Avelino',
+ 'Gerardo',
+ 'Lagunes',
+ 'Pinto',
+ 'Antunez',
+ 'Callejas',
+ 'Joaquin',
+ 'Leos',
+ 'Briceño',
+ 'Yepez',
+ 'Arrieta',
+ 'Romano',
+ 'Bobadilla',
+ 'Couoh',
+ 'Virgen',
+ 'Alatorre',
+ 'Landero',
+ 'Valverde',
+ 'Campuzano',
+ 'Candia',
+ 'Samano',
+ 'Bastida',
+ 'Cuautle',
+ 'Venancio',
+ 'Daniel',
+ 'Rea',
+ 'Samaniego',
+ 'Alejandre',
+ 'Aguillon',
+ 'Constantino',
+ 'Baños',
+ 'Florentino',
+ 'Crespo',
+ 'Balbuena',
+ 'Valente',
+ 'Alamilla',
+ 'Cocom',
+ 'Caldera',
+ 'Collazo',
+ 'Noyola',
+ 'Gatica',
+ 'Jarquin',
+ 'Justo',
+ 'Chimal',
+ 'Cabañas',
+ 'Grijalva',
+ 'Aguero',
+ 'Orduña',
+ 'Quijano',
+ 'Gama',
+ 'Quirino',
+ 'Calva',
+ 'Abad',
+ 'Polanco',
+ 'Tiburcio',
+ 'Galarza',
+ 'Paulino',
+ 'Osornio',
+ 'Huitron',
+ 'Astorga',
+ 'Toscano',
+ 'Armas',
+ 'Atilano',
+ 'Zarco',
+ 'Escudero',
+ 'Amado',
+ 'Calvario',
+ 'Roa',
+ 'Rodarte',
+ 'Machuca',
+ 'Escalera',
+ 'Gayosso',
+ 'Remigio',
+ 'Cosme',
+ 'Picazo',
+ 'Quintanilla',
+ 'Iglesias',
+ 'Machado',
+ 'Arciniega',
+ 'Rebollar',
+ 'Villavicencio',
+ 'Borja',
+ 'Celaya',
+ 'Ascencion',
+ 'Casarrubias',
+ 'Alavez',
+ 'Gracia',
+ 'Pelaez',
+ 'Garces'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Netherlands/bundle.ts b/packages/plugins/src/countries/Netherlands/bundle.ts
new file mode 100644
index 000000000..f706ad47e
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/bundle.ts
@@ -0,0 +1,285 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Netherlands: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'netherlands',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxx LL'
+ },
+ phoneFormat: {
+ displayFormats: [
+ '0Xxx-xxxxxx',
+ '0Xx-xxxxxxx',
+ '06-Xxxxxxxxx',
+ '0Xxx xxxxxx',
+ '0Xx xxxxxxx',
+ '06 Xxxxxxxxx',
+ '+31 Xx xxxxxxx',
+ '+31 Xxx xxxxxx',
+ '+31 6 Xxxxxxxxx'
+ ]
+ }
+ },
+ regions: [
+ {
+ regionName: 'Drenthe',
+ regionShort: 'Dr',
+ regionSlug: 'drenthe',
+ weight: 5,
+ cities: ['Assen', 'Coevorden', 'Emmen', 'Hoogeveen', 'Meppel']
+ },
+ {
+ regionName: 'Flevoland',
+ regionShort: 'Fl',
+ regionSlug: 'flevoland',
+ weight: 1,
+ cities: ['Almere', 'Lelystad']
+ },
+ {
+ regionName: 'Friesland',
+ regionShort: 'Fr',
+ regionSlug: 'friesland',
+ weight: 6,
+ cities: [
+ 'Bolsward',
+ 'Dokkum',
+ 'Drachten',
+ 'Franeker',
+ 'Harlingen',
+ 'Heerenveen',
+ 'Hindeloopen',
+ 'IJlst',
+ 'Leeuwarden',
+ 'Sloten',
+ 'Sneek',
+ 'Stavoren',
+ 'Workum'
+ ]
+ },
+ {
+ regionName: 'Gelderland',
+ regionShort: 'Gl',
+ regionSlug: 'gelderland',
+ weight: 20,
+ cities: [
+ 'Apeldoorn',
+ 'Arnhem',
+ 'Buren',
+ 'Culemborg',
+ 'Doetinchem',
+ 'Ede',
+ 'Groenlo',
+ 'Harderwijk',
+ 'Hattem',
+ 'Huissen',
+ 'Nijkerk',
+ 'Nijmegen',
+ 'Tiel',
+ 'Wageningen',
+ 'Winterswijk',
+ 'Zaltbommel',
+ 'Zutphen'
+ ]
+ },
+ {
+ regionName: 'Limburg',
+ regionShort: 'L.',
+ regionSlug: 'limburg',
+ weight: 11,
+ cities: [
+ 'Geleen',
+ 'Heerlen',
+ 'Kerkrade',
+ 'Maastricht',
+ 'Roermond',
+ 'Sittard',
+ 'Thorn',
+ 'Valkenburg aan de Geul',
+ 'Venlo',
+ 'Weert',
+ 'Hasselt',
+ 'Sint-Lambrechts-Herk',
+ 'Wimmertingen',
+ 'Kermt',
+ 'Spalbeek',
+ 'Kuringen',
+ 'Stokrooie',
+ 'Stevoort',
+ 'Zonhoven',
+ 'Helchteren',
+ 'Houthalen',
+ 'Houthalen-Helchteren',
+ 'Berbroek',
+ 'Donk',
+ 'Herk-de-Stad',
+ 'Schulen',
+ 'Halen',
+ 'Loksbergen',
+ 'Zelem',
+ 'Heusden',
+ 'Heusden-Zolder',
+ 'Zolder',
+ 'Linkhout',
+ 'Lummen',
+ 'Meldert',
+ 'Alken',
+ 'Beringen',
+ 'Beverlo',
+ 'Koersel',
+ 'Paal',
+ 'Diepenbeek',
+ 'Genk',
+ 'Gellik',
+ 'Lanaken',
+ 'Neerharen',
+ 'Veldwezelt',
+ 'Rekem',
+ 'Eisden',
+ 'Leut',
+ 'Maasmechelen',
+ 'Mechelen-aan-de-Maas',
+ 'Meeswijk',
+ 'Opgrimbie',
+ 'Vucht',
+ 'Boorsem',
+ 'Uikhoven',
+ 'Kessenich',
+ 'Kinrooi',
+ 'Molenbeersel',
+ 'Ophoven',
+ 'Dilsen-Stokkem',
+ 'Elen',
+ 'Lanklaar',
+ 'Rotem',
+ 'Stokkem',
+ 'Opglabbeek',
+ 'As',
+ 'Niel-bij-As',
+ 'Ellikom',
+ 'Gruitrode',
+ 'Meeuwen',
+ 'Meeuwen-Gruitrode',
+ 'Neerglabbeek',
+ 'Wijshagen',
+ 'Maaseik',
+ 'Neeroeteren',
+ 'Opoeteren',
+ 'Zutendaal',
+ 'Berg',
+ 'Diets-Heur',
+ 'Haren',
+ 'Henis',
+ 'Kolmont',
+ 'Koninksem',
+ 'Lauw',
+ 'Mal',
+ 'Neerrepen',
+ 'Nerem',
+ 'Overrepen',
+ 'Piringen',
+ 'Riksingen',
+ 'Rutten',
+ 's Herenelderen',
+ 'Sluizen',
+ 'Tongeren',
+ 'Vreren',
+ 'Widooie',
+ 'Herstappe',
+ 'Kortessem',
+ 'Vliermaalroot'
+ ]
+ },
+ {
+ regionName: 'Noord Brabant',
+ regionShort: 'N.',
+ regionSlug: 'noord_brabant',
+ weight: 24,
+ cities: [
+ 'Bergen op Zoom',
+ 'Breda',
+ 'Eindhoven',
+ 'Geertruidenberg',
+ 'Grave',
+ 'Helmond',
+ 'Heusden',
+ 'Oosterhout',
+ 'Oss',
+ 'Ravenstein',
+ 'Roosendaal',
+ 'Tilburg',
+ 'Waalwijk'
+ ]
+ },
+ {
+ regionName: 'Noord Holland',
+ regionShort: 'N.',
+ regionSlug: 'noord_holland',
+ weight: 26,
+ cities: [
+ 'Alkmaar',
+ 'Amstelveen',
+ 'Amsterdam',
+ 'Den Helder',
+ 'Edam',
+ 'Enkhuizen',
+ 'Haarlem',
+ 'Heerhugowaard',
+ 'Hilversum',
+ 'Hoofddorp',
+ 'Hoorn',
+ 'Laren',
+ 'Purmerend',
+ 'Medemblik',
+ 'Muiden',
+ 'Naarden',
+ 'Schagen',
+ 'Weesp',
+ 'Zaanstad'
+ ]
+ },
+ {
+ regionName: 'Overijssel',
+ regionShort: 'Ov',
+ regionSlug: 'overijssel',
+ weight: 11,
+ cities: ['Almelo', 'Deventer', 'Enschede', 'Hengelo', 'Oldenzaal', 'Zwolle']
+ },
+ {
+ regionName: 'Zuid Holland',
+ regionShort: 'Z.',
+ regionSlug: 'zuid_holland',
+ weight: 12,
+ cities: [
+ 'Alphen aan den Rijn',
+ 'Delft',
+ 'Dordrecht',
+ 'Gorinchem',
+ 'Gouda',
+ 'Leiden',
+ 'Rotterdam',
+ 'Spijkenisse',
+ 'The Hague',
+ 'Zoetermeer'
+ ]
+ },
+ {
+ regionName: 'Utrecht',
+ regionShort: 'U.',
+ regionSlug: 'utrecht',
+ weight: 4,
+ cities: ['Amersfoort', 'Leersum', 'Nieuwegein', 'Utrecht', 'Veenendaal', 'Woerden', 'Zeist']
+ },
+ {
+ regionName: 'Zeeland',
+ regionShort: 'Zl',
+ regionSlug: 'zeeland',
+ weight: 35,
+ cities: ['Flushing', 'Goes', 'Hulst', 'Middelburg', 'Sluis', 'Terneuzen', 'Veere', 'Zierikzee']
+ }
+ ]
+});
+
+export default Netherlands;
diff --git a/packages/plugins/src/countries/Netherlands/i18n/ar.json b/packages/plugins/src/countries/Netherlands/i18n/ar.json
new file mode 100644
index 000000000..3fe464b89
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "هولندا",
+ "regionNames": "مقاطعات هولندا"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/de.json b/packages/plugins/src/countries/Netherlands/i18n/de.json
new file mode 100644
index 000000000..9f5e15ee6
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Niederlande",
+ "regionNames": "Niederländische Provinzen"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/en.json b/packages/plugins/src/countries/Netherlands/i18n/en.json
new file mode 100644
index 000000000..9510ffbc3
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Netherlands",
+ "regionNames": "Netherlands Prov."
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/es.json b/packages/plugins/src/countries/Netherlands/i18n/es.json
new file mode 100644
index 000000000..bc5c788ad
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Países Bajos",
+ "regionNames": "Provincias holandesas"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/fr.json b/packages/plugins/src/countries/Netherlands/i18n/fr.json
new file mode 100644
index 000000000..80e19186a
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pays-Bas",
+ "regionNames": "Provinces néerlandaises"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/hi.json b/packages/plugins/src/countries/Netherlands/i18n/hi.json
new file mode 100644
index 000000000..88d55efe4
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "नीदरलैंड",
+ "regionNames": "नीदरलैंड प्रो."
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/ja.json b/packages/plugins/src/countries/Netherlands/i18n/ja.json
new file mode 100644
index 000000000..4e0df3101
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "オランダ",
+ "regionNames": "オランダの州"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/nl.json b/packages/plugins/src/countries/Netherlands/i18n/nl.json
new file mode 100644
index 000000000..b7d70a0fe
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nederland",
+ "regionNames": "Nederlandse provincies"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/pt.json b/packages/plugins/src/countries/Netherlands/i18n/pt.json
new file mode 100644
index 000000000..6ea3549b5
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Holanda",
+ "regionNames": "Províncias da Holanda"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/ru.json b/packages/plugins/src/countries/Netherlands/i18n/ru.json
new file mode 100644
index 000000000..7892759ee
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Нидерланды",
+ "regionNames": "Нидерланды пров."
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/ta.json b/packages/plugins/src/countries/Netherlands/i18n/ta.json
new file mode 100644
index 000000000..248d29b00
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "நெதர்லாந்து",
+ "regionNames": "நெதர்லாந்து மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Netherlands/i18n/zh.json b/packages/plugins/src/countries/Netherlands/i18n/zh.json
new file mode 100644
index 000000000..f1baebf26
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "荷兰",
+ "regionNames": "荷兰省份"
+}
diff --git a/packages/plugins/src/countries/Netherlands/names.ts b/packages/plugins/src/countries/Netherlands/names.ts
new file mode 100644
index 000000000..3a6462c66
--- /dev/null
+++ b/packages/plugins/src/countries/Netherlands/names.ts
@@ -0,0 +1,618 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Lara',
+ 'Aaltje',
+ 'Saar',
+ 'Tess',
+ 'Iris',
+ 'Anne',
+ 'Eline',
+ 'Elisabeth',
+ 'Nora',
+ 'Jill',
+ 'Fleur',
+ 'Anne',
+ 'Jannetje',
+ 'Iris',
+ 'Laura',
+ 'Lily',
+ 'Romy',
+ 'Noëlle',
+ 'Yara',
+ 'Tessa',
+ 'Lina',
+ 'Amira',
+ 'Kaylee',
+ 'Amy',
+ 'Isabella',
+ 'Indy',
+ 'Isabel',
+ 'Elijah',
+ 'Anouk',
+ 'Dewi',
+ 'Roos',
+ 'Sophia',
+ 'Quinty',
+ 'Jamie',
+ 'Lotte',
+ 'Hailey',
+ 'Evi',
+ 'Dewi',
+ 'Elin',
+ 'Loïs',
+ 'Olivia',
+ 'Elin',
+ 'Jasmijn',
+ 'Feline',
+ 'Sanne',
+ 'Lisa',
+ 'Eline',
+ 'Sem',
+ 'Lana',
+ 'Amira',
+ 'Dani',
+ 'Ashley',
+ 'Isabella',
+ 'Lina',
+ 'Samuel',
+ 'Luna',
+ 'Marretje',
+ 'Alyssa',
+ 'Emma',
+ 'Johanna',
+ 'Floor',
+ 'Aaltje',
+ 'Ashley',
+ 'Emily',
+ 'Lily',
+ 'Elise',
+ 'Sofia',
+ 'Fleur',
+ 'Zoë',
+ 'Evi',
+ 'Faye',
+ 'Jannetje',
+ 'Lynn',
+ 'Charlotte',
+ 'Anna',
+ 'Megan',
+ 'Jaylinn',
+ 'Mason',
+ 'Nikki',
+ 'Julia',
+ 'Johanna',
+ 'Floor',
+ 'Alyssa',
+ 'Isa',
+ 'Elise',
+ 'Lizzy',
+ 'Jaylinn',
+ 'Indy',
+ 'Fenna',
+ 'Femke',
+ 'Luna',
+ 'Lotte',
+ 'Marit',
+ 'Demi',
+ 'Esmee',
+ 'Elena',
+ 'Norah',
+ 'Isabel',
+ 'Vera',
+ 'Lana',
+ 'Lieke',
+ 'Naomi',
+ 'Elisa',
+ 'Rosalie',
+ 'Demi',
+ 'Esmee',
+ 'Elena',
+ 'Amber',
+ 'Nina',
+ 'Liv',
+ 'Elisabeth',
+ 'Kaylee',
+ 'Jamie',
+ 'Charlotte',
+ 'Feline',
+ 'Senna',
+ 'Noa',
+ 'Maria',
+ 'Laura',
+ 'Elisa',
+ 'Nienke',
+ 'Anouk',
+ 'Sofie',
+ 'Mila',
+ 'Lieke',
+ 'Amy',
+ 'Eva',
+ 'Liv',
+ 'Jill',
+ 'Faye',
+ 'Elize',
+ 'Julia',
+ 'Loïs',
+ 'Lynn',
+ 'Sara',
+ 'Nova',
+ 'Jasmijn',
+ 'Fay',
+ 'Noor',
+ 'Lara',
+ 'Sterre',
+ 'Eva',
+ 'Hailey',
+ 'Anna',
+ 'Isa',
+ 'Evy',
+ 'Femke',
+ 'Emma',
+ 'Emily',
+ 'Sophie',
+ 'Elize',
+ 'Fay',
+ 'Lizzy',
+ 'Fenna',
+ 'Amber',
+ 'Maud',
+ 'Evy',
+ 'Lisa',
+ 'Milou',
+ 'Sarah'
+];
+
+const maleNames = [
+ 'Pieter',
+ 'Damian',
+ 'Sven',
+ 'Floris',
+ 'Timo',
+ 'Jasper',
+ 'Jay',
+ 'David',
+ 'Tim',
+ 'Albert',
+ 'Julian',
+ 'Daan',
+ 'Quinten',
+ 'Nathan',
+ 'Luuk',
+ 'Mika',
+ 'Luuk',
+ 'Thijmen',
+ 'Hugo',
+ 'Elijah',
+ 'Kyan',
+ 'Julian',
+ 'Jacob',
+ 'Thijs',
+ 'Aron',
+ 'Jack',
+ 'Benjamin',
+ 'Liam',
+ 'Robin',
+ 'Jack',
+ 'Niels',
+ 'Ruben',
+ 'Aiden',
+ 'Daan',
+ 'Daley',
+ 'Dex',
+ 'Dion',
+ 'Adam',
+ 'Quinn',
+ 'Jacob',
+ 'Rayan',
+ 'Amir',
+ 'Lucas',
+ 'Finn',
+ 'Max',
+ 'Alexander',
+ 'Justin',
+ 'Tygo',
+ 'Lars',
+ 'Jelle',
+ 'Johannes',
+ 'Justin',
+ 'Levi',
+ 'Jayden',
+ 'Dylan',
+ 'Jan',
+ 'Jesse',
+ 'Jason',
+ 'David',
+ 'Noah',
+ 'Gerrit',
+ 'Stijn',
+ 'Jason',
+ 'Roan',
+ 'Hendrik',
+ 'Vince',
+ 'Owen',
+ 'Floris',
+ 'Klaas',
+ 'Alexander',
+ 'Levi',
+ 'Boaz',
+ 'Dani',
+ 'Aiden',
+ 'Johannes',
+ 'Jesse',
+ 'Daniël',
+ 'Riley',
+ 'Bram',
+ 'Damian',
+ 'Cornelis',
+ 'Jayson',
+ 'Kyan',
+ 'Jayden',
+ 'Joey',
+ 'Lucas',
+ 'Hugo',
+ 'Dean',
+ 'Amir',
+ 'Joey',
+ 'Dex',
+ 'Klaas',
+ 'Joshua',
+ 'Lars',
+ 'Jelle',
+ 'Daley',
+ 'Wesley',
+ 'Daniël',
+ 'Dylan',
+ 'Maarten',
+ 'Boaz',
+ 'Luca',
+ 'Milan',
+ 'Dean',
+ 'Bram',
+ 'Jasper',
+ 'Cornelis',
+ 'Mohammed',
+ 'Luca',
+ 'Liam',
+ 'Hendrik',
+ 'Jan',
+ 'Aron',
+ 'Gerrit',
+ 'Thomas',
+ 'Ryan',
+ 'Finn',
+ 'Sam',
+ 'Bas',
+ 'Willem',
+ 'Jayson',
+ 'Kyano',
+ 'Joshua',
+ 'Casper',
+ 'Sander',
+ 'Mohamed',
+ 'Jens',
+ 'Sami',
+ 'Bas',
+ 'Albert',
+ 'Nick',
+ 'Casper',
+ 'Adam',
+ 'Jens',
+ 'Kyano',
+ 'Shane',
+ 'Benjamin',
+ 'Jay',
+ 'Dion',
+ 'Mees'
+];
+
+const lastNames = [
+ 'Aarts',
+ 'van den Akker',
+ 'Albers',
+ 'Arends',
+ 'Arts',
+ 'Baars',
+ 'Baas',
+ 'Bakker',
+ 'Beckers',
+ 'van Beek',
+ 'Berends',
+ 'Berg',
+ 'van den Berg',
+ 'van der Berg',
+ 'Bergsma',
+ 'van Berkel',
+ 'Bijl',
+ 'Blok',
+ 'Blom',
+ 'de Boer',
+ 'Boer',
+ 'Boers',
+ 'Boersma',
+ 'Boom',
+ 'Boon',
+ 'Boonstra',
+ 'Boot',
+ 'Bos',
+ 'Bosch',
+ 'van den Bosch',
+ 'van der Bosch',
+ 'Bosma',
+ 'Bosman',
+ 'Bouma',
+ 'Bouman',
+ 'Bouwman',
+ 'Bouwmeester',
+ 'Brand',
+ 'Brands',
+ 'Brink',
+ 'van den Brink',
+ 'van der Brink',
+ 'Brinkman',
+ 'van der Broek',
+ 'van den Broek',
+ 'Brouwer',
+ 'Brouwers',
+ 'de Bruijn',
+ 'de Bruin',
+ 'Bruin',
+ 'Bruinsma',
+ 'Buijs',
+ 'van der Burg',
+ 'Burger',
+ 'Coenen',
+ 'Cornelissen',
+ 'van Dalen',
+ 'van Dam',
+ 'Dam',
+ 'Damen',
+ 'Dekker',
+ 'Dekkers',
+ 'Derks',
+ 'Derksen',
+ 'van Dijk',
+ 'Dijk',
+ 'Dijkstra',
+ 'van Dongen',
+ 'van Doorn',
+ 'van Driel',
+ 'Driessen',
+ 'Drost',
+ 'van Eck',
+ 'van Egmond',
+ 'van Eijk',
+ 'van Essen',
+ 'Evers',
+ 'Faber',
+ 'Feenstra',
+ 'Franken',
+ 'Fransen',
+ 'Franssen',
+ 'Geerts',
+ 'van Gelder',
+ 'Gerrits',
+ 'Gerritsen',
+ 'Geurts',
+ 'van Gils',
+ 'Goossens',
+ 'de Graaf',
+ 'van der Graaf',
+ 'de Graaff',
+ 'Groen',
+ 'Groeneveld',
+ 'de Groot',
+ 'Groot',
+ 'de Haan',
+ 'de Haas',
+ 'Hartman',
+ 'Heemskerk',
+ 'van der Heide',
+ 'van der Heijden',
+ 'Hendriks',
+ 'Hermans',
+ 'van der Heuvel',
+ 'van den Heuvel',
+ 'Hoek',
+ 'van dn Hoek',
+ 'Hoekstra',
+ 'van den Hoeven',
+ 'Hof',
+ 'Hofman',
+ 'Hoogendoorn',
+ 'Hoogeveen',
+ 'van der Horst',
+ 'Houben',
+ 'Huisman',
+ 'Jacobs',
+ 'de Jager',
+ 'Jager',
+ 'Jansen',
+ 'Janssen',
+ 'de Jong',
+ 'Jong',
+ 'de Jonge',
+ 'de Jongh',
+ 'Jonker',
+ 'Jonkers',
+ 'Joosten',
+ 'Kamp',
+ 'Kamphuis',
+ 'Keijzer',
+ 'Keizer',
+ 'van Kempen',
+ 'Kersten',
+ 'Klaassen',
+ 'Klaver',
+ 'Klein',
+ 'Klomp',
+ 'Knol',
+ 'de Kok',
+ 'Kok',
+ 'van de Kolk',
+ 'de Koning',
+ 'Koning',
+ 'Konings',
+ 'Kooistra',
+ 'Kool',
+ 'Koopman',
+ 'Koopmans',
+ 'Koster',
+ 'Kramer',
+ 'Kroon',
+ 'Kuijpers',
+ 'Kuiper',
+ 'Kuipers',
+ 'van der Laan',
+ 'Lamers',
+ 'Lammers',
+ 'de Lange',
+ 'Leenders',
+ 'de Leeuw',
+ 'van Leeuwen',
+ 'Lemmens',
+ 'van Lieshout',
+ 'van den Linde',
+ 'van der Linden',
+ 'van Loon',
+ 'Maas',
+ 'Martens',
+ 'van der Meer',
+ 'Meijer',
+ 'Meijers',
+ 'van der Meulen',
+ 'Meyer',
+ 'Mol',
+ 'van der Molen',
+ 'Molenaar',
+ 'Mulder',
+ 'Mulders',
+ 'Muller',
+ 'Nieuwenhuis',
+ 'Nijenhuis',
+ 'Nijhuis',
+ 'Nijland',
+ 'Otten',
+ 'Peeters',
+ 'Peters',
+ 'Pieters',
+ 'van der Plas',
+ 'van der Ploeg',
+ 'Pol',
+ 'van den Pol',
+ 'Post',
+ 'Postma',
+ 'Prins',
+ 'Pronk',
+ 'Reinders',
+ 'de Ridder',
+ 'Rietveld',
+ 'van Rijn',
+ 'Roelofs',
+ 'de Rooij',
+ 'van Rooij',
+ 'van Rooijen',
+ 'Roos',
+ 'van Rossum',
+ 'de Ruiter',
+ 'Ruiter',
+ 'Rutten',
+ 'Sanders',
+ 'Schaap',
+ 'van Schaik',
+ 'Schepers',
+ 'Schipper',
+ 'Schippers',
+ 'Scholten',
+ 'Schouten',
+ 'Schreuder',
+ 'Schreurs',
+ 'Schut',
+ 'Schutte',
+ 'Schuurman',
+ 'Simons',
+ 'van de Sluis',
+ 'Smeets',
+ 'Smit',
+ 'Smits',
+ 'Smulders',
+ 'Snijders',
+ 'Stam',
+ 'van den Steen',
+ 'Steenbergen',
+ 'Stevens',
+ 'Stolk',
+ 'Swart',
+ 'Terpstra',
+ 'Teunissen',
+ 'Theunissen',
+ 'Thijssen',
+ 'Timmer',
+ 'Timmerman',
+ 'Timmermans',
+ 'Tromp',
+ 'Valk',
+ 'van Veen',
+ 'Veen',
+ 'van der Veen',
+ 'Veenstra',
+ 'van der Veer',
+ 'van der Velde',
+ 'van der Velden',
+ 'Veldhuis',
+ 'Veldman',
+ 'van Velzen',
+ 'van der Ven',
+ 'van de Ven',
+ 'Venema',
+ 'Verbeek',
+ 'Verhagen',
+ 'Verhoef',
+ 'Verhoeven',
+ 'Vermeer',
+ 'Vermeulen',
+ 'Verschoor',
+ 'Versteeg',
+ 'Verweij',
+ 'Vink',
+ 'Vis',
+ 'Visscher',
+ 'de Visser',
+ 'Visser',
+ 'Vissers',
+ 'van Vliet',
+ 'Vonk',
+ 'de Vos',
+ 'Vos',
+ 'de Vries',
+ 'Vries',
+ 'van Vugt',
+ 'de Waal',
+ 'Wagenaar',
+ 'van der Wal',
+ 'van den Werf',
+ 'Wessels',
+ 'Wiersma',
+ 'van Wijk',
+ 'Wijnen',
+ 'de Wilde',
+ 'Willems',
+ 'Willemse',
+ 'Willemsen',
+ 'de Winter',
+ 'de Wit',
+ 'Wolters',
+ 'Wouters',
+ 'van Zanten',
+ 'van der Zee',
+ 'Zijlstra',
+ 'Zwart'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/NewZealand/bundle.ts b/packages/plugins/src/countries/NewZealand/bundle.ts
new file mode 100644
index 000000000..4d312d2e7
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/bundle.ts
@@ -0,0 +1,95 @@
+/**
+ * @package Countries
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const NewZealand: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'newzealand',
+ regionNames: i18n.regionNames,
+ continent: 'oceania',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'North Island',
+ regionShort: 'NI',
+ regionSlug: 'north_island',
+ weight: 3,
+ cities: [
+ 'Auckland',
+ 'Manukau',
+ 'North Shore',
+ 'Waitakere',
+ 'Wellington',
+ 'Hamilton',
+ 'Tauranga',
+ 'Lower Hutt',
+ 'Palmerston North',
+ 'Hastings',
+ 'Napier',
+ 'Rotorua',
+ 'New Plymouth',
+ 'Whangarei',
+ 'Porirua',
+ 'Wanganui',
+ 'Kapiti',
+ 'Upper Hutt',
+ 'Gisborne',
+ 'Pukekohe',
+ 'Taupo',
+ 'Masterton',
+ 'Levin',
+ 'Whakatane',
+ 'Cambridge',
+ 'Te Awamutu',
+ 'Feilding',
+ 'Tokoroa',
+ 'Hawera',
+ 'Waiuku',
+ 'Waiheke Island',
+ 'Te Puke',
+ 'Kawerau',
+ 'Huntly',
+ 'Thames',
+ 'Morrinsville',
+ 'Matamata',
+ 'Waitara',
+ 'Kerikeri',
+ 'Dannevirke'
+ ]
+ },
+ {
+ regionName: 'South Island',
+ regionShort: 'SI',
+ regionSlug: 'south_island',
+ weight: 1,
+ cities: [
+ 'Christchurch',
+ 'Dunedin',
+ 'Nelson',
+ 'Invercargill',
+ 'Blenheim',
+ 'Timaru',
+ 'Ashburton',
+ 'Oamaru',
+ 'Rangiora',
+ 'Queenstown',
+ 'Greymouth',
+ 'Gore',
+ 'Motueka',
+ 'Wanaka',
+ 'Alexandra',
+ 'Picton',
+ 'Balclutha',
+ 'Temuka',
+ 'Westport'
+ ]
+ }
+ ]
+});
+
+export default NewZealand;
diff --git a/packages/plugins/src/countries/NewZealand/i18n/ar.json b/packages/plugins/src/countries/NewZealand/i18n/ar.json
new file mode 100644
index 000000000..84bcbc7eb
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "نيوزيلندا",
+ "regionNames": "مناطق نيوزيلندا"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/de.json b/packages/plugins/src/countries/NewZealand/i18n/de.json
new file mode 100644
index 000000000..8f8d09564
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Neuseeland",
+ "regionNames": "Regionen Neuseelands"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/en.json b/packages/plugins/src/countries/NewZealand/i18n/en.json
new file mode 100644
index 000000000..d91e47a0e
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "New Zealand",
+ "regionNames": "NZ Regions"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/es.json b/packages/plugins/src/countries/NewZealand/i18n/es.json
new file mode 100644
index 000000000..9fb18e311
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nueva Zelanda",
+ "regionNames": "Regiones de Nueva Zelanda"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/fr.json b/packages/plugins/src/countries/NewZealand/i18n/fr.json
new file mode 100644
index 000000000..9a26a932b
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nouvelle-Zélande",
+ "regionNames": "Régions de Nouvelle-Zélande"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/hi.json b/packages/plugins/src/countries/NewZealand/i18n/hi.json
new file mode 100644
index 000000000..b156c6458
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "न्यूजीलैंड",
+ "regionNames": "न्यूजीलैंड क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/ja.json b/packages/plugins/src/countries/NewZealand/i18n/ja.json
new file mode 100644
index 000000000..afd514799
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ニュージーランド",
+ "regionNames": "ニュージーランドの地域"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/nl.json b/packages/plugins/src/countries/NewZealand/i18n/nl.json
new file mode 100644
index 000000000..73eb4824f
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nieuw-Zeeland",
+ "regionNames": "Regio's in Nieuw-Zeeland"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/pt.json b/packages/plugins/src/countries/NewZealand/i18n/pt.json
new file mode 100644
index 000000000..3d5792a81
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nova Zelândia",
+ "regionNames": "Regiões da NZ"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/ru.json b/packages/plugins/src/countries/NewZealand/i18n/ru.json
new file mode 100644
index 000000000..6e94b8275
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Новая Зеландия",
+ "regionNames": "Регионы Новой Зеландии"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/ta.json b/packages/plugins/src/countries/NewZealand/i18n/ta.json
new file mode 100644
index 000000000..51b919a74
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "நியூசிலாந்து",
+ "regionNames": "நியூசிலாந்து பிராந்தியங்கள்"
+}
diff --git a/packages/plugins/src/countries/NewZealand/i18n/zh.json b/packages/plugins/src/countries/NewZealand/i18n/zh.json
new file mode 100644
index 000000000..c2aead3bc
--- /dev/null
+++ b/packages/plugins/src/countries/NewZealand/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "新西兰",
+ "regionNames": "新西兰地区"
+}
diff --git a/packages/plugins/src/countries/Nigeria/bundle.ts b/packages/plugins/src/countries/Nigeria/bundle.ts
new file mode 100644
index 000000000..f5f5ba057
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/bundle.ts
@@ -0,0 +1,153 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Nigeria: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'nigeria',
+ regionNames: i18n.regionNames,
+ continent: 'africa',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['xxx xxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Kano',
+ regionShort: 'KN',
+ regionSlug: 'kano',
+ weight: 94,
+ cities: ['Kano']
+ },
+ {
+ regionName: 'Lagos',
+ regionShort: 'LA',
+ regionSlug: 'lagos',
+ weight: 91,
+ cities: ['Lagos']
+ },
+ {
+ regionName: 'Kaduna',
+ regionShort: 'KD',
+ regionSlug: 'kaduna',
+ weight: 61,
+ cities: ['Kaduna', 'Zaria']
+ },
+ {
+ regionName: 'Katsina',
+ regionShort: 'KT',
+ regionSlug: 'katsina',
+ weight: 58,
+ cities: ['Katsina', 'Funtua']
+ },
+ {
+ regionName: 'Oyo',
+ regionShort: 'OY',
+ regionSlug: 'oyo',
+ weight: 56,
+ cities: ['Ibadan', 'Ogbomosho', 'Oyo', 'Iseyin', 'Shaki', 'Kisi', 'Igboho']
+ },
+ {
+ regionName: 'Rivers',
+ regionShort: 'RI',
+ regionSlug: 'rivers',
+ weight: 52,
+ cities: ['Port Harcourt', 'Buguma']
+ },
+ {
+ regionName: 'Bauchi',
+ regionShort: 'BA',
+ regionSlug: 'bauchi',
+ weight: 46,
+ cities: ['Bauchi']
+ },
+ {
+ regionName: 'Jigawa',
+ regionShort: 'JI',
+ regionSlug: 'jigawa',
+ weight: 43,
+ cities: ['Dutse']
+ },
+ {
+ regionName: 'Benue',
+ regionShort: 'BE',
+ regionSlug: 'benue',
+ weight: 42,
+ cities: ['Makurdi', 'Gboko', 'Otukpo']
+ },
+ {
+ regionName: 'Anambra',
+ regionShort: 'AN',
+ regionSlug: 'anambra',
+ weight: 42,
+ cities: ['Onitsha', 'Okpoko', 'Awka']
+ },
+ {
+ regionName: 'Borno',
+ regionShort: 'BO',
+ regionSlug: 'borno',
+ weight: 42,
+ cities: ['Maiduguri', 'Bama']
+ },
+ {
+ regionName: 'Delta',
+ regionShort: 'DE',
+ regionSlug: 'delta',
+ weight: 41,
+ cities: ['Warri', 'Sapele']
+ },
+ {
+ regionName: 'Niger',
+ regionShort: 'NI',
+ regionSlug: 'niger',
+ weight: 40,
+ cities: ['Minna', 'Bida']
+ },
+ {
+ regionName: 'Imo',
+ regionShort: 'IM',
+ regionSlug: 'imo',
+ weight: 39,
+ cities: ['Owerri', 'Okigwe']
+ },
+ {
+ regionName: 'Akwa Ibom',
+ regionShort: 'AK',
+ regionSlug: 'akwaibom',
+ weight: 39,
+ cities: ['Uyo', 'Ikot Ekpene']
+ },
+ {
+ regionName: 'Ogun',
+ regionShort: 'OG',
+ regionSlug: 'ogun',
+ weight: 38,
+ cities: ['Abeokuta', 'Sagamu', 'Ijebu Ode']
+ },
+ {
+ regionName: 'Sokoto',
+ regionShort: 'SO',
+ regionSlug: 'sokoto',
+ weight: 37,
+ cities: ['Sokoto']
+ },
+ {
+ regionName: 'Osun',
+ regionShort: 'OS',
+ regionSlug: 'osun',
+ weight: 34,
+ cities: ['Osogbo', 'Ife', 'Ilesa', 'Ila', 'Gbongan', 'Modakeke']
+ },
+ {
+ regionName: 'Kogi',
+ regionShort: 'KO',
+ regionSlug: 'kogi',
+ weight: 33,
+ cities: ['Okene']
+ }
+ ]
+});
+
+export default Nigeria;
diff --git a/packages/plugins/src/countries/Nigeria/i18n/ar.json b/packages/plugins/src/countries/Nigeria/i18n/ar.json
new file mode 100644
index 000000000..87dc6abb2
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "نيجيريا",
+ "regionNames": "الدول النيجيرية"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/de.json b/packages/plugins/src/countries/Nigeria/i18n/de.json
new file mode 100644
index 000000000..9ebed4ec5
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nigeria",
+ "regionNames": "Nigerianische Staaten"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/en.json b/packages/plugins/src/countries/Nigeria/i18n/en.json
new file mode 100644
index 000000000..d52abbb5b
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nigeria",
+ "regionNames": "Nigerian States"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/es.json b/packages/plugins/src/countries/Nigeria/i18n/es.json
new file mode 100644
index 000000000..36526aba0
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nigeria",
+ "regionNames": "Estados de Nigeria"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/fr.json b/packages/plugins/src/countries/Nigeria/i18n/fr.json
new file mode 100644
index 000000000..e2037cfe6
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nigeria",
+ "regionNames": "États nigérians"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/hi.json b/packages/plugins/src/countries/Nigeria/i18n/hi.json
new file mode 100644
index 000000000..44db8925c
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "नाइजीरिया",
+ "regionNames": "नाइजीरियाई राज्य"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/ja.json b/packages/plugins/src/countries/Nigeria/i18n/ja.json
new file mode 100644
index 000000000..209f68f80
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ナイジェリア",
+ "regionNames": "ナイジェリア諸国"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/nl.json b/packages/plugins/src/countries/Nigeria/i18n/nl.json
new file mode 100644
index 000000000..9c793d328
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Niger",
+ "regionNames": "Nigeriaanse staten"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/pt.json b/packages/plugins/src/countries/Nigeria/i18n/pt.json
new file mode 100644
index 000000000..7bde1bb9d
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Nigéria",
+ "regionNames": "Estados nigerianos"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/ru.json b/packages/plugins/src/countries/Nigeria/i18n/ru.json
new file mode 100644
index 000000000..2ae8d8f5a
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Нигерия",
+ "regionNames": "нигерийские штаты"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/ta.json b/packages/plugins/src/countries/Nigeria/i18n/ta.json
new file mode 100644
index 000000000..18e49ed09
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "நைஜீரியா",
+ "regionNames": "நைஜீரிய நாடுகள்"
+}
diff --git a/packages/plugins/src/countries/Nigeria/i18n/zh.json b/packages/plugins/src/countries/Nigeria/i18n/zh.json
new file mode 100644
index 000000000..c95c6630e
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "尼日利亚",
+ "regionNames": "尼日利亚各州"
+}
diff --git a/packages/plugins/src/countries/Nigeria/names.ts b/packages/plugins/src/countries/Nigeria/names.ts
new file mode 100644
index 000000000..505586f77
--- /dev/null
+++ b/packages/plugins/src/countries/Nigeria/names.ts
@@ -0,0 +1,192 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Abeni',
+ 'Abiola',
+ 'Adaeze',
+ 'Adama',
+ 'Adanna',
+ 'Adaora',
+ 'Adenike',
+ 'Adeola',
+ 'Ajah',
+ 'Amara',
+ 'Amma',
+ 'Ashani',
+ 'Chiamaka',
+ 'Chidera',
+ 'Chidinma',
+ 'Chika',
+ 'Chimamanda',
+ 'Chinenye',
+ 'Chinyere',
+ 'Chioma',
+ 'Chisom',
+ 'Chizaram',
+ 'Deola',
+ 'Dera',
+ 'Dola',
+ 'Favour',
+ 'Folasade',
+ 'Fumi',
+ 'Honour',
+ 'Ifeoluwa',
+ 'Ifeoma',
+ 'Ijeoma',
+ 'Ima',
+ 'Kemi',
+ 'Nene',
+ 'Ngozi',
+ 'Nneka',
+ 'Nneoma',
+ 'Ogechi',
+ 'Ola',
+ 'Olamide',
+ 'Oluwadamilola',
+ 'Oluwadarasimi',
+ 'Oni',
+ 'Oreoluwa',
+ 'Osa',
+ 'Ovie',
+ 'Sade',
+ 'Shade',
+ 'Shakara',
+ 'Sola',
+ 'Stephenia',
+ 'Tari',
+ 'Timi',
+ 'Toba',
+ 'Tobi',
+ 'Tomi',
+ 'Tonna',
+ 'Toye',
+ 'Uchenna',
+ 'Yetunde',
+ 'Zelie'
+];
+
+const maleNames = [
+ 'Abayomi',
+ 'Adama',
+ 'Ade',
+ 'Adewale',
+ 'Akin',
+ 'Amadi',
+ 'Atiba',
+ 'Ayinde',
+ 'Ayodeji',
+ 'Ayodele',
+ 'Ayomide',
+ 'Babatunde',
+ 'Chibueze',
+ 'Chidi',
+ 'Chidubem',
+ 'Chigozie',
+ 'Chike',
+ 'Chima',
+ 'Chinedu',
+ 'Chukwudi',
+ 'Chukwuemeka',
+ 'Efe',
+ 'Emeka',
+ 'Geofrey',
+ 'Ike',
+ 'Ikechukwu',
+ 'Ikenna',
+ 'Juwon',
+ 'Kayin',
+ 'Kayode',
+ 'Kelechi',
+ 'Kenechukwu',
+ 'Nnamdi',
+ 'Obie',
+ 'Obinna',
+ 'Okey',
+ 'Olajuwon',
+ 'Olamide',
+ 'Olufemi',
+ 'Olumide',
+ 'Oluwafemi',
+ 'Oluwaseun',
+ 'Oluwaseyi',
+ 'Oluwatobi',
+ 'Oluwatobiloba',
+ 'Oreoluwa',
+ 'Oshay',
+ 'Ovie',
+ 'Praise',
+ 'Rapheal',
+ 'Saheed',
+ 'Shade',
+ 'Taiwo',
+ 'Taye',
+ 'Temitope',
+ 'Tobe',
+ 'Tobenna',
+ 'Tobi',
+ 'Uchenna',
+ 'Ugo',
+ 'Ugochukwu',
+ 'Ugonna'
+];
+
+const lastNames = [
+ 'Abadaki',
+ 'Abagun',
+ 'Abaribe',
+ 'Adarabioyo',
+ 'Adejuyigbe',
+ 'Adekugbe',
+ 'Adeniran',
+ 'Aderemi',
+ 'Adewumi',
+ 'Adewusi',
+ 'Ajudua',
+ 'Akinde',
+ 'Aladefa',
+ 'Ameobi',
+ 'Aneke',
+ 'Anjorin',
+ 'Aremu',
+ 'Ayanbadejo',
+ 'Bakare',
+ 'Balarabe',
+ 'Buraimoh',
+ 'Coker',
+ 'Chibuzor',
+ 'Daramola',
+ 'Fuwape',
+ 'Gwarzo',
+ 'Ibekwe',
+ 'Idonije',
+ 'Igbinoghene',
+ 'Ihemeje',
+ 'Laouali',
+ 'Maikori',
+ 'Makinde',
+ 'Nwakaeme',
+ 'Ofoedu',
+ 'Ogedegbe',
+ 'Ogunode',
+ 'Okojie',
+ 'Olaoye',
+ 'Oliha',
+ 'Olonisakin',
+ 'Olunloyo',
+ 'Onoja',
+ 'Onyeama',
+ 'Osakwe',
+ 'Oteh',
+ 'Sofoluwe',
+ 'Udoka',
+ 'Ujah',
+ 'Umelo'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Norway/bundle.ts b/packages/plugins/src/countries/Norway/bundle.ts
new file mode 100644
index 000000000..fa529ddce
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/bundle.ts
@@ -0,0 +1,94 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Norway: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'norway',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Oslo',
+ regionShort: '03',
+ regionSlug: 'oslo',
+ weight: 5,
+ cities: ['Oslo']
+ },
+ {
+ regionName: 'Rogaland',
+ regionShort: '11',
+ regionSlug: 'rogaland',
+ weight: 5,
+ cities: ['Stavanger', 'Sandnes', 'Haugesund', 'Bryne', 'Egersund', 'Koppervik', 'Åkrehamn', 'Jørpeland', 'Sauda', 'Skudeneshavn']
+ },
+ {
+ regionName: 'Møre og Romsdal',
+ regionShort: '15',
+ regionSlug: 'more_og_romsdal',
+ weight: 5,
+ cities: ['Molde', 'Ålesund', 'Kristiansund', 'Ørsta', 'Volda']
+ },
+ {
+ regionName: 'Nordland',
+ regionShort: '18',
+ regionSlug: 'nordland',
+ weight: 5,
+ cities: ['Bodø', 'Mo i Rana', 'Narvik', 'Mosjøen', 'Fauske', 'Sandnessjøen', 'Sortland', 'Brønnøysund']
+ },
+ {
+ regionName: 'Viken',
+ regionShort: '30',
+ regionSlug: 'viken',
+ weight: 5,
+ cities: ['Drammen', 'Frederikstad', 'Sarpsborg', 'Moss']
+ },
+ {
+ regionName: 'Innlandet',
+ regionShort: '34',
+ regionSlug: 'innlandet',
+ weight: 5,
+ cities: ['Hamar', 'Lillehammer', 'Gjøvik', 'Elverum', 'Kongsvinger', 'Brummunddal', 'Raufoss', 'Moelv']
+ },
+ {
+ regionName: 'Vestfold og Telemark',
+ regionShort: '38',
+ regionSlug: 'vestfold_og_telemark',
+ weight: 5,
+ cities: ['Skien', 'Porsgrunn', 'Brevik', 'Langesund', 'Tønsberg', 'Sandefjord', 'Larvik', 'Horten', 'Notodden', 'Holmestrand']
+ },
+ {
+ regionName: 'Agder',
+ regionShort: '42',
+ regionSlug: 'agder',
+ weight: 5,
+ cities: ['Kristiansand', 'Arendal', 'Grimstad', 'Farsund', 'Flekkefjord', 'Lillesand', 'Mandal', 'Risør', 'Tvedestrand']
+ },
+ {
+ regionName: 'Vestland',
+ regionShort: '46',
+ regionSlug: 'vestland',
+ weight: 5,
+ cities: ['Bergen', 'Leirvik', 'Førde', 'Florø', 'Odda']
+ },
+ {
+ regionName: 'Trøndelag',
+ regionShort: '50',
+ regionSlug: 'trondelag',
+ weight: 5,
+ cities: ['Trondheim', 'Stjørdalshalsen', 'Steinkjer', 'Levanger', 'Namsos', 'Verdalsøra', 'Orkanger']
+ },
+ {
+ regionName: 'Troms og Finnmark',
+ regionShort: '54',
+ regionSlug: 'troms_og_finnmark',
+ weight: 5,
+ cities: ['Tromsø', 'Harstad', 'Alta', 'Hammerfest', 'Vadsø']
+ }
+ ]
+});
+
+export default Norway;
diff --git a/packages/plugins/src/countries/Norway/i18n/ar.json b/packages/plugins/src/countries/Norway/i18n/ar.json
new file mode 100644
index 000000000..13b7640fe
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "النرويج",
+ "regionNames": "المقاطعات النرويجية"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/de.json b/packages/plugins/src/countries/Norway/i18n/de.json
new file mode 100644
index 000000000..f46226048
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Norwegen",
+ "regionNames": "Norwegische Bezirke"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/en.json b/packages/plugins/src/countries/Norway/i18n/en.json
new file mode 100644
index 000000000..98cec70b7
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Norway",
+ "regionNames": "Norwegian counties"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/es.json b/packages/plugins/src/countries/Norway/i18n/es.json
new file mode 100644
index 000000000..8fec14074
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Noruega",
+ "regionNames": "condados noruegos"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/fr.json b/packages/plugins/src/countries/Norway/i18n/fr.json
new file mode 100644
index 000000000..330d88e62
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Norvège",
+ "regionNames": "Comtés norvégiens"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/hi.json b/packages/plugins/src/countries/Norway/i18n/hi.json
new file mode 100644
index 000000000..c055ee9a7
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "नॉर्वे",
+ "regionNames": "नॉर्वेजियन काउंटी"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/ja.json b/packages/plugins/src/countries/Norway/i18n/ja.json
new file mode 100644
index 000000000..476d6cf1f
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ノルウェー",
+ "regionNames": "ノルウェーの郡"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/nl.json b/packages/plugins/src/countries/Norway/i18n/nl.json
new file mode 100644
index 000000000..74e0bc683
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Noorwegen",
+ "regionNames": "Noorse provincies"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/pt.json b/packages/plugins/src/countries/Norway/i18n/pt.json
new file mode 100644
index 000000000..410c2776f
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Noruega",
+ "regionNames": "condados noruegueses"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/ru.json b/packages/plugins/src/countries/Norway/i18n/ru.json
new file mode 100644
index 000000000..2c5533270
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Норвегия",
+ "regionNames": "Норвежские округа"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/ta.json b/packages/plugins/src/countries/Norway/i18n/ta.json
new file mode 100644
index 000000000..8e369f55d
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "நார்வே",
+ "regionNames": "நோர்வே மாவட்டங்கள்"
+}
diff --git a/packages/plugins/src/countries/Norway/i18n/zh.json b/packages/plugins/src/countries/Norway/i18n/zh.json
new file mode 100644
index 000000000..3aabbe5f7
--- /dev/null
+++ b/packages/plugins/src/countries/Norway/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "挪威",
+ "regionNames": "挪威县"
+}
diff --git a/packages/plugins/src/countries/Pakistan/bundle.ts b/packages/plugins/src/countries/Pakistan/bundle.ts
new file mode 100644
index 000000000..b3c4e4f0c
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/bundle.ts
@@ -0,0 +1,190 @@
+/* eslint max-len:0 */
+/**
+ * @author Fareez Ahamed
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Pakistan: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'pakistan',
+ regionNames: i18n.regionNames,
+ continent: 'asia',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['+92 xxxxxxxxx', '0xx-xxxxxxx', '0xxx-xxxxxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Punjab',
+ regionShort: 'PU',
+ regionSlug: 'sindh',
+ weight: 30,
+ cities: [
+ 'Attock',
+ 'Bahawalnagar',
+ 'Bahawalpur',
+ 'Bhakkar',
+ 'Chakwal',
+ 'Chiniot',
+ 'Dera Ghazi Khan',
+ 'Faisalabad',
+ 'Gujranwala',
+ 'Hafizabad',
+ 'Jhang',
+ 'Jhelum',
+ 'Kasur',
+ 'Khanewal',
+ 'Khushab',
+ 'Lahore',
+ 'Lodhran',
+ 'Mandi Bahauddin',
+ 'Mianwali',
+ 'Multan',
+ 'Murree',
+ 'Muzaffargarh',
+ 'Nankana Sahib',
+ 'Narowal',
+ 'Okara',
+ 'Pakpatan',
+ 'Rahimyar Khan',
+ 'Rajanpur',
+ 'Rawalpindi',
+ 'Sahiwal',
+ 'Sargodha',
+ 'Sheikhupura',
+ 'Sialkot',
+ 'Toba Tek Singh',
+ 'Vehari'
+ ]
+ },
+ {
+ regionName: 'Sindh',
+ regionShort: 'SI',
+ regionSlug: 'sindh',
+ weight: 25,
+ cities: [
+ 'Badin',
+ 'Dadu',
+ 'Ghotki',
+ 'Hyderabad',
+ 'Jacobabad',
+ 'Jamshoro',
+ 'Karachi',
+ 'Kashmore',
+ 'Khairpur',
+ 'Larkana',
+ 'Matiari',
+ 'Naushahro Firoze',
+ 'Qambar Shahdadkot',
+ 'Sanghar',
+ 'Shaheed Benazirabad',
+ 'Shikarpur',
+ 'Sujawal',
+ 'Sukkur',
+ 'Tando Allahyar',
+ 'Tando Muhammad Khan',
+ 'Tharparkar',
+ 'Thatta',
+ 'Umerkot'
+ ]
+ },
+ {
+ regionName: 'Balochistan',
+ regionShort: 'BL',
+ regionSlug: 'balochistan',
+ weight: 20,
+ cities: [
+ 'Awaran',
+ 'Barkhan',
+ 'Chagai',
+ 'Dera Bugti',
+ 'Gwadar',
+ 'Harnai',
+ 'Jafarabad',
+ 'Kacchi',
+ 'Kalat',
+ 'Kech',
+ 'Kharan',
+ 'Khuzdar',
+ 'Killa Abdullah',
+ 'Killa Saifullah',
+ 'Kohlu',
+ 'Lasbela',
+ 'Lehri',
+ 'Loralai',
+ 'Mastung',
+ 'Musakhel',
+ 'Nasirabad',
+ 'Nushki',
+ 'Panjgur',
+ 'Pishin Valley',
+ 'Quetta',
+ 'Sherani',
+ 'Sibi',
+ 'Sohbatpur',
+ 'Washuk',
+ 'Zhob',
+ 'Ziarat'
+ ]
+ },
+ {
+ regionName: 'Khyber Pakhtoonkhwa',
+ regionShort: 'KPK',
+ regionSlug: 'khyber_pakhtoonkhwa',
+ weight: 20,
+ cities: [
+ 'Abbottabad',
+ 'Bannu',
+ 'Battagram',
+ 'Buner',
+ 'Charsadda',
+ 'Chitral',
+ 'Dera Ismail Khan',
+ 'Dir',
+ 'Hangu',
+ 'Haripur',
+ 'Karak',
+ 'Kohat',
+ 'Kohistan',
+ 'Lakki Marwat',
+ 'Malakand',
+ 'Mansehra',
+ 'Mardan',
+ 'Nowshera',
+ 'Peshawar',
+ 'Shangla',
+ 'Swabi',
+ 'Swat',
+ 'Tank',
+ 'Torghar'
+ ]
+ },
+ {
+ regionName: 'Gilgit Baltistan',
+ regionShort: 'GB',
+ regionSlug: 'gilgit_baltistan',
+ weight: 15,
+ cities: ['Astore', 'Diamer', 'Ghanche', 'Ghizer', 'Gilgit', 'Gojal Upper Hunza', 'Kharmang', 'Nagar', 'Shigar', 'Skardu']
+ },
+ {
+ regionName: 'Azad Kashmir',
+ regionShort: 'AK',
+ regionSlug: 'azad_kashmir',
+ weight: 10,
+ cities: ['Bagh', 'Bhimber', 'Hattian Bala', 'Haveli', 'Kotli', 'Mirpur', 'Muzzafarabad', 'Neelum Valley', 'Rawalakot', 'Sudhanoti']
+ },
+ {
+ regionName: 'FATA',
+ regionShort: 'FA',
+ regionSlug: 'fata',
+ weight: 5,
+ cities: ['Bajaur Agency', 'Khyber Agency', 'Kurram Agency', 'Mohmand Agency', 'North Waziristan']
+ }
+ ]
+});
+
+export default Pakistan;
diff --git a/packages/plugins/src/countries/Pakistan/i18n/ar.json b/packages/plugins/src/countries/Pakistan/i18n/ar.json
new file mode 100644
index 000000000..1b1089bb2
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "باكستان",
+ "regionNames": "دولة باكستان"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/de.json b/packages/plugins/src/countries/Pakistan/i18n/de.json
new file mode 100644
index 000000000..8c3572379
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pakistan",
+ "regionNames": "Pakistanische Staaten"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/en.json b/packages/plugins/src/countries/Pakistan/i18n/en.json
new file mode 100644
index 000000000..0d40519da
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pakistan",
+ "regionNames": "Pakistan States"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/es.json b/packages/plugins/src/countries/Pakistan/i18n/es.json
new file mode 100644
index 000000000..27d57dd07
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pakistán",
+ "regionNames": "Estados de Pakistán"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/fr.json b/packages/plugins/src/countries/Pakistan/i18n/fr.json
new file mode 100644
index 000000000..e0a823030
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pakistan",
+ "regionNames": "États pakistanais"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/hi.json b/packages/plugins/src/countries/Pakistan/i18n/hi.json
new file mode 100644
index 000000000..3aa225f65
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "पाकिस्तान",
+ "regionNames": "पाकिस्तान राज्य"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/ja.json b/packages/plugins/src/countries/Pakistan/i18n/ja.json
new file mode 100644
index 000000000..14e4fc4ef
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "パキスタン",
+ "regionNames": "パキスタン諸国"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/nl.json b/packages/plugins/src/countries/Pakistan/i18n/nl.json
new file mode 100644
index 000000000..53c9d5dc5
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pakistan",
+ "regionNames": "Pakistaanse staten"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/pt.json b/packages/plugins/src/countries/Pakistan/i18n/pt.json
new file mode 100644
index 000000000..76e334c9f
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Paquistão",
+ "regionNames": "Estados do Paquistão"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/ru.json b/packages/plugins/src/countries/Pakistan/i18n/ru.json
new file mode 100644
index 000000000..c69b4472e
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Пакистан",
+ "regionNames": "Пакистанские штаты"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/ta.json b/packages/plugins/src/countries/Pakistan/i18n/ta.json
new file mode 100644
index 000000000..fe9bd48e7
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "பாகிஸ்தான்",
+ "regionNames": "பாகிஸ்தான் மாநிலம்"
+}
diff --git a/packages/plugins/src/countries/Pakistan/i18n/zh.json b/packages/plugins/src/countries/Pakistan/i18n/zh.json
new file mode 100644
index 000000000..b82cd0013
--- /dev/null
+++ b/packages/plugins/src/countries/Pakistan/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "巴基斯坦",
+ "regionNames": "巴基斯坦国"
+}
diff --git a/packages/plugins/src/countries/Peru/bundle.ts b/packages/plugins/src/countries/Peru/bundle.ts
new file mode 100644
index 000000000..fa9229b04
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/bundle.ts
@@ -0,0 +1,100 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Peru: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'peru',
+ regionNames: i18n.regionNames,
+ continent: 'south_america',
+ extendedData: {
+ zipFormat: {
+ format: 'Axxxx',
+ replacements: {
+ A: '1234'
+ }
+ },
+ phoneFormat: {
+ displayFormats: ['Xxx xxx xxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Piura',
+ regionShort: 'PIU',
+ regionSlug: 'piura',
+ weight: 16,
+ cities: ['Piura', 'Sullana', 'Paita', 'Catacaos', 'Talara', 'Chulucanas', 'Sechura']
+ },
+ {
+ regionName: 'La Libertad',
+ regionShort: 'LAL',
+ regionSlug: 'lalibertad',
+ weight: 15,
+ cities: ['Trujillo', 'Chepén', 'Pacasmayo', 'Guadalupe']
+ },
+ {
+ regionName: 'Cajamarca',
+ regionShort: 'CAJ',
+ regionSlug: 'cajamarca',
+ weight: 14,
+ cities: ['Cajamarca', 'Jaén']
+ },
+ {
+ regionName: 'Puno',
+ regionShort: 'PUN',
+ regionSlug: 'puno',
+ weight: 12,
+ cities: ['Juliaca', 'Puno']
+ },
+ {
+ regionName: 'Cusco',
+ regionShort: 'CUS',
+ regionSlug: 'cusco',
+ weight: 12,
+ cities: ['Cusco', 'Sicuani']
+ },
+ {
+ regionName: 'Arequipa',
+ regionShort: 'ARE',
+ regionSlug: 'arequipa',
+ weight: 11,
+ cities: ['Arequipa']
+ },
+ {
+ regionName: 'Junín',
+ regionShort: 'JUN',
+ regionSlug: 'junín',
+ weight: 11,
+ cities: ['Huancayo', 'Tarma']
+ },
+ {
+ regionName: 'Lambayeque',
+ regionShort: 'LAM',
+ regionSlug: 'lambayeque',
+ weight: 11,
+ cities: ['Chiclayo', 'Lambayeque']
+ },
+ {
+ regionName: 'Ancash',
+ regionShort: 'ANC',
+ regionSlug: 'ancash',
+ weight: 10,
+ cities: ['Chimbote', 'Huaraz']
+ },
+ {
+ regionName: 'Loreto',
+ regionShort: 'LOR',
+ regionSlug: 'loreto',
+ weight: 9,
+ cities: ['Iquitos', 'Yurimaguas']
+ },
+ {
+ regionName: 'Lima',
+ regionShort: 'LIM',
+ regionSlug: 'Lima',
+ weight: 9,
+ cities: ['Lima', 'Huacho', 'Huaral', 'San Vicente de Cañete', 'Barranca', 'Chancay', 'Mala']
+ }
+ ]
+});
+
+export default Peru;
diff --git a/packages/plugins/src/countries/Peru/i18n/ar.json b/packages/plugins/src/countries/Peru/i18n/ar.json
new file mode 100644
index 000000000..c3236b0b5
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "بيرو",
+ "regionNames": "مناطق بيرو"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/de.json b/packages/plugins/src/countries/Peru/i18n/de.json
new file mode 100644
index 000000000..9fb4a8c27
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Peru",
+ "regionNames": "Peruanische Regionen"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/en.json b/packages/plugins/src/countries/Peru/i18n/en.json
new file mode 100644
index 000000000..f7a9bdfff
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Peru",
+ "regionNames": "Peruvian regions"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/es.json b/packages/plugins/src/countries/Peru/i18n/es.json
new file mode 100644
index 000000000..4edb10a72
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Perú",
+ "regionNames": "regiones peruanas"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/fr.json b/packages/plugins/src/countries/Peru/i18n/fr.json
new file mode 100644
index 000000000..193f34d65
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pérou",
+ "regionNames": "Régions péruviennes"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/hi.json b/packages/plugins/src/countries/Peru/i18n/hi.json
new file mode 100644
index 000000000..55b3dfd07
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "पेरू",
+ "regionNames": "पेरू क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/ja.json b/packages/plugins/src/countries/Peru/i18n/ja.json
new file mode 100644
index 000000000..787547275
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ペルー",
+ "regionNames": "ペルー地域"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/nl.json b/packages/plugins/src/countries/Peru/i18n/nl.json
new file mode 100644
index 000000000..8e3b7370e
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Peru",
+ "regionNames": "Peruaanse regio's"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/pt.json b/packages/plugins/src/countries/Peru/i18n/pt.json
new file mode 100644
index 000000000..8874767fb
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Peru",
+ "regionNames": "Regiões peruanas"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/ru.json b/packages/plugins/src/countries/Peru/i18n/ru.json
new file mode 100644
index 000000000..38558de3e
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Перу",
+ "regionNames": "Перуанские регионы"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/ta.json b/packages/plugins/src/countries/Peru/i18n/ta.json
new file mode 100644
index 000000000..54dd3d065
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "பெரு",
+ "regionNames": "பெருவியன் பகுதிகள்"
+}
diff --git a/packages/plugins/src/countries/Peru/i18n/zh.json b/packages/plugins/src/countries/Peru/i18n/zh.json
new file mode 100644
index 000000000..9baa69b34
--- /dev/null
+++ b/packages/plugins/src/countries/Peru/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "秘鲁",
+ "regionNames": "秘鲁地区"
+}
diff --git a/packages/plugins/src/countries/Philippines/bundle.ts b/packages/plugins/src/countries/Philippines/bundle.ts
new file mode 100644
index 000000000..bae2aead3
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/bundle.ts
@@ -0,0 +1,213 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Philippines: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'philippines',
+ regionNames: i18n.regionNames,
+ continent: 'asia',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['63 (xxx) xxx xxxx', '0 (xxx) xxx xxxx', '(xxx) xxx xxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'National Capital Region',
+ regionShort: 'NCR',
+ regionSlug: 'national_capital_region',
+ weight: 139,
+ cities: [
+ 'Quezon City',
+ 'Manila',
+ 'Caloocan',
+ 'Taguig',
+ 'Pasig',
+ 'Valenzuela',
+ 'Parañaque',
+ 'Makati',
+ 'Las Piñas',
+ 'Muntinlupa',
+ 'Marikina'
+ ]
+ },
+ {
+ regionName: 'Cordillera Administrative Region',
+ regionShort: 'CAR',
+ regionSlug: 'cordillera_administrative_region',
+ weight: 18,
+ cities: ['Baguio', 'Tabuk']
+ },
+ {
+ regionName: 'Ilocos Region',
+ regionShort: 'regionI',
+ regionSlug: 'ilocos_region',
+ weight: 52,
+ cities: ['Dagupan City', 'Alaminos', 'Batac', 'Candon', 'Laoag', 'San Carlos', 'San Fernando', 'Urdaneta', 'Vigan']
+ },
+ {
+ regionName: 'Cagayan Valley',
+ regionShort: 'regionII',
+ regionSlug: 'cagayan_valley',
+ weight: 37,
+ cities: ['Cauayan', 'Ilagan', 'Santiago', 'Tuguegarao']
+ },
+ {
+ regionName: 'Central Luzon',
+ regionShort: 'regionIII',
+ regionSlug: 'central_luzon',
+ weight: 123,
+ cities: [
+ 'Angeles City',
+ 'Balanga',
+ 'Cabanatuan',
+ 'Gapan',
+ 'Mabalacat',
+ 'Malolos',
+ 'Meycauayan',
+ 'Muñoz',
+ 'Olongapo',
+ 'Palayan',
+ 'San Fernando',
+ 'San Jose',
+ 'San Jose del Monte',
+ 'Tarlac City'
+ ]
+ },
+ {
+ regionName: 'Calabarzon',
+ regionShort: 'regionIV-A',
+ regionSlug: 'calabarzon',
+ weight: 161,
+ cities: [
+ 'Antipolo',
+ 'Dasmariñas',
+ 'Bacoor',
+ 'Calamba',
+ 'Imus',
+ 'General Trias',
+ 'Santa Rosa',
+ 'Biñan',
+ 'Lipa',
+ 'Cabuyao',
+ 'Batangas City',
+ 'San Pedro'
+ ]
+ },
+ {
+ regionName: 'Southwestern Tagalog Region',
+ regionShort: 'mimaropa',
+ regionSlug: 'southwestern_tagalog_region',
+ weight: 32,
+ cities: ['Calapan', 'Puerto Princesa']
+ },
+ {
+ regionName: 'Bicol Region',
+ regionShort: 'regionV',
+ regionSlug: 'bicol_region',
+ weight: 61,
+ cities: ['Iriga', 'Legazpi', 'Ligao', 'Masbate City', 'Naga', 'Sorsogon City', 'Tabaco']
+ },
+ {
+ regionName: 'Western Visayas',
+ regionShort: 'regionVI',
+ regionSlug: 'western_visayas',
+ weight: 80,
+ cities: [
+ 'Bacolod',
+ 'Bago',
+ 'Cadiz',
+ 'Escalante',
+ 'Himamaylan',
+ 'Iloilo City',
+ 'Kabankalan',
+ 'La Carlota',
+ 'Passi',
+ 'Roxas',
+ 'Sagay',
+ 'San Carlos',
+ 'Silay',
+ 'Sipalay',
+ 'Talisay',
+ 'Victorias'
+ ]
+ },
+ {
+ regionName: 'Central Visayas',
+ regionShort: 'regionVII',
+ regionSlug: 'central_visayas',
+ weight: 74,
+ cities: [
+ 'Bais',
+ 'Bayawan',
+ 'Bogo',
+ 'Canlaon',
+ 'Carcar',
+ 'Cebu City',
+ 'Danao',
+ 'Dumaguete',
+ 'Guihulngan',
+ 'Lapu-Lapu City',
+ 'Mandaue',
+ 'Naga',
+ 'Tagbilaran',
+ 'Talisay',
+ 'Tanjay',
+ 'Toledo'
+ ]
+ },
+ {
+ regionName: 'Eastern Visayas',
+ regionShort: 'regionVIII',
+ regionSlug: 'eastern_visayas',
+ weight: 48,
+ cities: ['Baybay', 'Borongan', 'Calbayog', 'Catbalogan', 'Maasin', 'Ormoc', 'Tacloban']
+ },
+ {
+ regionName: 'Zamboanga Peninsula',
+ regionShort: 'regionIX',
+ regionSlug: 'zamboanga_peninsula',
+ weight: 38,
+ cities: ['Dapitan', 'Dipolog', 'Isabela City', 'Pagadian', 'Zamboanga City']
+ },
+ {
+ regionName: 'Northern Mindanao',
+ regionShort: 'regionX',
+ regionSlug: 'northern_mindanao',
+ weight: 50,
+ cities: ['Cagayan de Oro', 'El Salvador', 'Gingoog', 'Iligan', 'Malaybalay', 'Oroquieta', 'Ozamiz', 'Tangub', 'Valencia']
+ },
+ {
+ regionName: 'Davao Region',
+ regionShort: 'regionXI',
+ regionSlug: 'davao_region',
+ weight: 53,
+ cities: ['Davao City', 'Digos', 'Mati', 'Panabo', 'Samal', 'Tagum']
+ },
+ {
+ regionName: 'Soccsksargen',
+ regionShort: 'regionXII',
+ regionSlug: 'national_capital_region',
+ weight: 139,
+ cities: ['General Santos', 'Kidapawan', 'Koronadal', 'Tacurong']
+ },
+ {
+ regionName: 'Caraga',
+ regionShort: 'regionXIII',
+ regionSlug: 'national_capital_region',
+ weight: 27,
+ cities: ['Bayugan', 'Bislig', 'Butuan', 'Cabadbaran', 'Surigao City', 'Tandag']
+ },
+ {
+ regionName: 'Bangsamoro',
+ regionShort: 'BARMM',
+ regionSlug: 'bangsamoro',
+ weight: 42,
+ cities: ['Cotabato City', 'Lamitan', 'Marawi']
+ }
+ ]
+});
+
+export default Philippines;
diff --git a/packages/plugins/src/countries/Philippines/i18n/ar.json b/packages/plugins/src/countries/Philippines/i18n/ar.json
new file mode 100644
index 000000000..d4faba528
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "فيلبيني",
+ "regionNames": "مناطق الفلبين"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/de.json b/packages/plugins/src/countries/Philippines/i18n/de.json
new file mode 100644
index 000000000..2d3e9f157
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Philippinen",
+ "regionNames": "Philippinische Regionen"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/en.json b/packages/plugins/src/countries/Philippines/i18n/en.json
new file mode 100644
index 000000000..0627478d8
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Philippines",
+ "regionNames": "Philippine Regions"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/es.json b/packages/plugins/src/countries/Philippines/i18n/es.json
new file mode 100644
index 000000000..6c92240ad
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Filipinas",
+ "regionNames": "Regiones de Filipinas"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/fr.json b/packages/plugins/src/countries/Philippines/i18n/fr.json
new file mode 100644
index 000000000..ad18a6696
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Philippines",
+ "regionNames": "Régions des Philippines"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/hi.json b/packages/plugins/src/countries/Philippines/i18n/hi.json
new file mode 100644
index 000000000..81d3ba1f1
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "फिलिपींस",
+ "regionNames": "फिलीपीन क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/ja.json b/packages/plugins/src/countries/Philippines/i18n/ja.json
new file mode 100644
index 000000000..676775288
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "フィリピン",
+ "regionNames": "フィリピンの地域"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/nl.json b/packages/plugins/src/countries/Philippines/i18n/nl.json
new file mode 100644
index 000000000..64c6edef5
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Filippijnen",
+ "regionNames": "Filippijnse regio's"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/pt.json b/packages/plugins/src/countries/Philippines/i18n/pt.json
new file mode 100644
index 000000000..f115c3fbc
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Filipinas",
+ "regionNames": "Regiões filipinas"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/ru.json b/packages/plugins/src/countries/Philippines/i18n/ru.json
new file mode 100644
index 000000000..75b148b9f
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Филиппины",
+ "regionNames": "Филиппинские регионы"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/ta.json b/packages/plugins/src/countries/Philippines/i18n/ta.json
new file mode 100644
index 000000000..fc78c17a8
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "பிலிப்பைன்ஸ்",
+ "regionNames": "பிலிப்பைன்ஸ் பகுதிகள்"
+}
diff --git a/packages/plugins/src/countries/Philippines/i18n/zh.json b/packages/plugins/src/countries/Philippines/i18n/zh.json
new file mode 100644
index 000000000..d2d136e0c
--- /dev/null
+++ b/packages/plugins/src/countries/Philippines/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "菲律宾",
+ "regionNames": "菲律宾地区"
+}
diff --git a/packages/plugins/src/countries/Poland/bundle.ts b/packages/plugins/src/countries/Poland/bundle.ts
new file mode 100644
index 000000000..87846f603
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/bundle.ts
@@ -0,0 +1,132 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Poland: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'poland',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xx-xxx'
+ },
+ phoneFormat: {
+ displayFormats: ['Xx xxx xx xx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Dolnośląskie',
+ regionShort: 'DS',
+ regionSlug: 'dolnoslaskie',
+ weight: 291,
+ cities: ['Wrocław', 'Wałbrzych', 'Legnica', 'Jelenia Góra']
+ },
+ {
+ regionName: 'Kujawsko-pomorskie',
+ regionShort: 'KP',
+ regionSlug: 'kujawskopomorskie',
+ weight: 210,
+ cities: ['Bydgoszcz', 'Toruń']
+ },
+ {
+ regionName: 'Lubelskie',
+ regionShort: 'LU',
+ regionSlug: 'lubelskie',
+ weight: 217,
+ cities: ['Lublin', 'Chełm', 'Zamość', 'Biała Podlaska']
+ },
+ {
+ regionName: 'Lubuskie',
+ regionShort: 'LB',
+ regionSlug: 'lubuskie',
+ weight: 102,
+ cities: ['Gorzów Wielkopolski', 'Zielona Góra']
+ },
+ {
+ regionName: 'łódzkie',
+ regionShort: 'LD',
+ regionSlug: 'lodzkie',
+ weight: 252,
+ cities: ['Łódź', 'Piotrków Trybunalski', 'Pabianice', 'Tomaszów Mazowiecki']
+ },
+ {
+ regionName: 'Małopolskie',
+ regionShort: 'MP',
+ regionSlug: 'malopolskie',
+ weight: 335,
+ cities: ['Kraków', 'Tarnów']
+ },
+ {
+ regionName: 'Mazowieckie',
+ regionShort: 'MA',
+ regionSlug: 'mazowieckie',
+ weight: 530,
+ cities: ['Warszawa', 'Radom', 'Płock', 'Siedlce']
+ },
+ {
+ regionName: 'Opolskie',
+ regionShort: 'OP',
+ regionSlug: 'opolskie',
+ weight: 101,
+ cities: ['Opole', 'Kędzierzyn-Koźle']
+ },
+ {
+ regionName: 'Podkarpackie',
+ regionShort: 'PK',
+ regionSlug: 'podkarpackie',
+ weight: 213,
+ cities: ['Rzeszów', 'Przemyśl', 'Stalowa Wola', 'Mielec']
+ },
+ {
+ regionName: 'Podlaskie',
+ regionShort: 'PD',
+ regionSlug: 'podlaskie',
+ weight: 120,
+ cities: ['Białystok', 'Suwałki', 'Łomża']
+ },
+ {
+ regionName: 'Pomorskie',
+ regionShort: 'PM',
+ regionSlug: 'pomorskie',
+ weight: 229,
+ cities: ['Gdańsk', 'Gdynia', 'Słupsk', 'Tczew']
+ },
+ {
+ regionName: 'Sląskie',
+ regionShort: 'SL',
+ regionSlug: 'slaskie',
+ weight: 462,
+ cities: ['Katowice', 'Częstochowa', 'Sosnowiec', 'Gliwice']
+ },
+ {
+ regionName: 'Swiętokrzyskie',
+ regionShort: 'SK',
+ regionSlug: 'swietokrzyskie',
+ weight: 127,
+ cities: ['Kielce', 'Ostrowiec Świętokrzyski', 'Starachowice']
+ },
+ {
+ regionName: 'Warmińsko-mazurskie',
+ regionShort: 'WM',
+ regionSlug: 'warminskomazurskie',
+ weight: 145,
+ cities: ['Olsztyn', 'Elbląg', 'Ełk']
+ },
+ {
+ regionName: 'Wielkopolskie',
+ regionShort: 'WP',
+ regionSlug: 'wielkopolskie',
+ weight: 342,
+ cities: ['Poznań', 'Kalisz', 'Konin', 'Piła']
+ },
+ {
+ regionName: 'Zachodniopomorskie',
+ regionShort: 'ZP',
+ regionSlug: 'zachodniopomorskie',
+ weight: 172,
+ cities: ['Szczecin', 'Koszalin', 'Stargard Szczeciński']
+ }
+ ]
+});
+
+export default Poland;
diff --git a/packages/plugins/src/countries/Poland/i18n/ar.json b/packages/plugins/src/countries/Poland/i18n/ar.json
new file mode 100644
index 000000000..6c43cdac1
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "بولندا",
+ "regionNames": "الدول البولندية"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/de.json b/packages/plugins/src/countries/Poland/i18n/de.json
new file mode 100644
index 000000000..701878800
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Polen",
+ "regionNames": "Polnische Staaten"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/en.json b/packages/plugins/src/countries/Poland/i18n/en.json
new file mode 100644
index 000000000..2e43e8ad3
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Poland",
+ "regionNames": "Polish States"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/es.json b/packages/plugins/src/countries/Poland/i18n/es.json
new file mode 100644
index 000000000..69f9176ce
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Polonia",
+ "regionNames": "Estados polacos"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/fr.json b/packages/plugins/src/countries/Poland/i18n/fr.json
new file mode 100644
index 000000000..08942ee5a
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Pologne",
+ "regionNames": "États polonais"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/hi.json b/packages/plugins/src/countries/Poland/i18n/hi.json
new file mode 100644
index 000000000..97747515b
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "पोलैंड",
+ "regionNames": "पोलिश राज्य"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/ja.json b/packages/plugins/src/countries/Poland/i18n/ja.json
new file mode 100644
index 000000000..d1f59106a
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ポーランド",
+ "regionNames": "ポーランド諸国"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/nl.json b/packages/plugins/src/countries/Poland/i18n/nl.json
new file mode 100644
index 000000000..ba08d82b8
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Polen",
+ "regionNames": "Poolse staten"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/pt.json b/packages/plugins/src/countries/Poland/i18n/pt.json
new file mode 100644
index 000000000..d997ace76
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Polônia",
+ "regionNames": "Estados poloneses"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/ru.json b/packages/plugins/src/countries/Poland/i18n/ru.json
new file mode 100644
index 000000000..ea66a520a
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Польша",
+ "regionNames": "Польские государства"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/ta.json b/packages/plugins/src/countries/Poland/i18n/ta.json
new file mode 100644
index 000000000..fb68ff925
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "போலந்து",
+ "regionNames": "போலந்து மாநிலங்கள்"
+}
diff --git a/packages/plugins/src/countries/Poland/i18n/zh.json b/packages/plugins/src/countries/Poland/i18n/zh.json
new file mode 100644
index 000000000..7451b4c99
--- /dev/null
+++ b/packages/plugins/src/countries/Poland/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "波兰",
+ "regionNames": "波兰国家"
+}
diff --git a/packages/plugins/src/countries/README.md b/packages/plugins/src/countries/README.md
new file mode 100644
index 000000000..2cd07706b
--- /dev/null
+++ b/packages/plugins/src/countries/README.md
@@ -0,0 +1,234 @@
+# [CLI](../../../../../cli/README.md) » [Plugins](../PLUGINS.md) » Countries
+
+The Country plugins are used in a number of different ways in the data generator. They each contain a rich set of
+data for a particular country, namely:
+
+- Country name
+- Region list
+- Cities within each region
+- phone number formats (optional)
+- postal/zip code formats (optional)
+- list of names (first and last) that are common to the country (optional)
+
+This enables different Data Types in the generator to create more realistic looking data sets.
+
+The goal of the country plugins is **not to be exhaustive** in terms of the data they contain. The overriding goal is
+to be able to generate realistic-looking regional data, but that doesn't mean having 100% of all regions and cities
+etc. to be provided. In fact, it's better to keep the data somewhat limited to ensure decent performance.
+
+# Available Countries & Functionality
+
+This lists the available country plugins and what they currently provide. The global phone and zip formats are so that
+the generator can at least provide somewhat realistic looking information for a mapped Data Type (i.e. a phone or zip
+Data Type that's been explicitly mapped to a higher region).
+
+- **Australia**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **Austria**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **Belgium**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **Brazil**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [x] regional zip format
+ - [x] names
+- **Canada**
+ - [x] global phone format
+ - [x] region-specific phone format
+ - [x] global zip format
+ - [x] regional zip format
+ - [x] names
+- **Chile**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **China**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **Colombia**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Costa Rica**
+ - [x] global phone format
+ - [x] region-specific phone format
+ - [x] global zip format
+ - [x] regional zip format
+ - [ ] names
+- **France**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Germany**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **India**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **Indonesia**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Ireland**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Italy**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Mexico**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Netherlands**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **New Zealand**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Nigeria**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **Norway**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Pakistan**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Peru**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Philippines**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Poland**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Russia**
+ - [x] global phone format
+ - [x] region-specific phone format
+ - [x] global zip format
+ - [x] regional zip format
+ - [ ] names
+- **Singapore**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **South Africa**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **South Korea**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Spain**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **Sweden**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Turkey**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
+- **UK**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **Ukraine**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [ ] names
+- **US**
+ - [ ] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [x] regional zip format
+ - [x] names
+- **Vietnam**
+ - [x] global phone format
+ - [ ] region-specific phone format
+ - [x] global zip format
+ - [ ] regional zip format
+ - [x] names
diff --git a/packages/plugins/src/countries/Russia/bundle.ts b/packages/plugins/src/countries/Russia/bundle.ts
new file mode 100644
index 000000000..592e39249
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/bundle.ts
@@ -0,0 +1,1158 @@
+// https://en.wikipedia.org/wiki/Category:Cities_and_towns_in_Russia_by_federal_subject
+import { GetCountryData } from '@generatedata/types';
+
+const Russia: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'RU',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+
+ // Federal Cities (3} = Moscow, Saint Petersburg, Sevastopol
+ // Oblast (46}= Province or Region
+ // Krai = Territory (Same as Oblast, but designation is historic}
+ // Republic = Home to specific Ethinic Minorities
+
+ extendedData: {
+ // the general zip format for the country. This may be optionally overridden for each region if a more
+ // specific format is desired. To prevent duplicate code, the replacements listed here cover ALL zip formats
+ // for each province
+ zipFormat: {
+ format: '%*****',
+ replacements: {
+ '%': '123456789',
+ '*': '0123456789',
+
+ // used in individual federal subjects
+ '-': '01', // (Kurgan, Ryazan, Tula} Oblast
+ '&': '012', // Moscow City, (Pskov, Tver, Vologda, Orenburg, Penza, Vladimir, Yaroslavl} Oblast
+ '+': '0123', // (Kirov, Novosibirsk, Saratov} Oblast
+ U: '01234', // (Kemerovo, Moscow, Sverdlovsk, Volgograd} Oblast
+ X: '012345', // Moscow City
+ P: '123', // Bryansk Oblast
+ Z: '23', // (Oryol, Tambov, Ulyanovsk} Oblast
+ '}': '34', // (Sakhalin, Murmansk} Oblast
+ T: '345', // (Ivanovo, Novgorod} Oblast
+ '^': '3456', // (Arkhangelsk, Samara} Oblast
+ Y: '34567', // Nizhny Novgorod Oblast
+ S: '456', // (Astrakhan, Smolensk, Omsk, Tomsk} Oblast
+ R: '4567', // (Chelyabinsk, Rostov, Voronezh} Oblast
+ '#': '4569', // Irkutsk Oblast
+ '(': '56', // (Amur, Magadan} Oblast
+ Q: '567', // (Kursk, Tyumen} Oblast
+ V: '67', // Kostroma Oblast
+ '?': '678', // Kalingrad Oblast
+ W: '78', // Leningrad Oblast
+ '@': '89' // (Belgorod, Kaluga, Lipetsk} Oblast
+ }
+ },
+
+ // the general phone format and area codes for the country
+ // https://en.wikipedia.org/wiki/Telephone_numbers_in_Russia
+ // https://www.itu.int/oth/T02020000AD/en
+ phoneFormat: {
+ areaCodes: [
+ 301, // Republic of Buryatia (Ulan – Ude}
+ 302, // Zabaykalsky Krai, Chita region together with the Agin-Buryat autonomous region (Chita}
+ 336, // Baikonur
+ 341, // Republic of Udmurtia (Izhevsk}
+ 342, // Perm Krai (Perm}
+ 343, // Sverdlovsk Oblast (Ekaterinburg}
+ 345, // Tyumen Oblast (Tyumen}
+ 346, // Khanty–Mansi Autonomous Okrug – Yugra (Surgut}
+ 347, // Republic of Bashkortostan (Ufa}
+ 349, // Yamalo-Nenets Autonomous Okrug (Salekhard}
+ 351, // Chelyabinsk Oblast (Chelyabinsk}
+ 352, // Kurgan Oblast (Kurgan}
+ 353, // Orenburg Oblast (Orenburg}
+ 365, // Republic of Crimea* (Simferopol}
+ 381, // Omsk Oblast (Omsk}
+ 382, // Tomsk Oblast (Tomsk}
+ 383, // Novosibirsk Oblast (Novosibirsk}
+ 384, // Kemerovo Oblast (Kemerovo}
+ 385, // Altai Krai (Barnaul}
+ 387, // Altai Krai
+ 388, // Altai Krai (Gorno-Altaisk}
+ 390, // Republic of Khakassia (Abakan}
+ 391, // Krasnoyarsk Krai in conjunction with the Evenk Oblast and Taimyr (Dolgan-Nenets} Oblast (Krasnoyarsk}
+ 394, // Republic of Tyva (Kyzyl}
+ 395, // Irkutsk Oblast (Irkutsk}
+ 401, // Kaliningrad Oblast (Kaliningrad}
+ 411, // Sakha Republic (Yakutia} (Yakutsk}
+ 413, // Magadan Oblast (Magadan}
+ 415, // Kamchatka Krai and Koryak autonomous region (Petropavlovsk-Kamchatsky}
+ 416, // Amur Oblast (Blagoveshchensk}
+ 421, // Khabarovsk Krai (Khabarovsk}
+ 423, // Primorsky Krai (Vladivostok}
+ 424, // Sakhalin Oblast (Yuzhno-Sakhalinsk}
+ 426, // Jewish Autonomous Oblast (Birobidzhan}
+ 427, // Chukotka Autonomous Okrug (Anadyr}
+ 471, // Kursk Oblast (Kursk}
+ 472, // Belgorod Oblast (Belgorod}
+ 473, // Voronezh Oblast (Voronezh}
+ 474, // Lipetsk Oblast (Lipetsk}
+ 475, // Tambov Oblast (Tambov}
+ 481, // Smolensk Oblast (Smolensk}
+ 482, // Tver Oblast (Tver}
+ 483, // Bryansk Oblast (Bryansk}
+ 484, // Kaluga Oblast
+ 485, // Yaroslavl Oblast (Yaroslavl}
+ 486, // Oryol Oblast (Orel}
+ 487, // Tula Oblast (Tula}
+ 491, // Ryazan Oblast (Ryazan}
+ 492, // Vladimir Oblast (Vladimir}
+ 493, // Ivanovo Oblast (Ivanovo}
+ 494, // Kostroma Oblast (Kostroma}
+ 495,
+ 499, // Moscow City
+ 496,
+ 498, // Moscow Oblast
+ 811, // Pskov Oblast (Pskov}
+ 812, // Saint Petersburg
+ 813,
+ 820, // Volgograd Oblast
+ 813, // Leningrad Oblast
+ 814, // Republic of Karelia (Petrozavodsk}
+ 815, // Murmansk Oblast (Murmansk}
+ 816, // Novgorod Oblast (Velikiy Novgorod}
+ 817, // Vologda Oblast (Vologda}
+ 818, // Arkhangelsk Oblast (Arkhangelsk}
+ 821, // Komi Republic (Syktyvkar}
+ 831, // Nizhny Novgorod Oblast (Nizhny Novgorod}
+ 833, // Kirov Oblast (Kirov}
+ 834, // Republic of Mordovia (Saransk}
+ 835, // Republic of Chuvashia (Cheboksary}
+ 836, // Republic of Mari El (Yoshkar-Ola}
+ 841, // Penza Oblast (Penza}
+ 842, // Ulyanovsk Oblast (Ulyanovsk}
+ 843, // Republic of Tatarstan (Kazan}
+ 844, // Volgograd Oblast (Volgograd}
+ 845, // Saratov Oblast (Saratov}
+ 846, // Samara Oblast (Samara}
+ 847, // Republic of Kalmykia (Elista}
+ 848, // Samara Oblast (Tolyatti}
+ 851, // Astrakhan Oblast (Astrakhan}
+ 855, // Republic of Tatarstan (Naberezhnye Chelny}
+ 861, // Krasnodar Krai (Krasnodar}
+ 862, // Krasnodar Krai (Sochi}
+ 863, // Rostov Oblast (Rostov-on Don}
+ 865, // Stavropol Krai (Stavropol}
+ 866, // Republic of Kabardino-Balkaria (Nalchik}
+ 867, // Republic of North Ossetia–Alania (Vladikavkaz}
+ 869, // Sevastopol City
+ 871, // Republic of Chechnya (Grozny}
+ 872, // Republic of Dagestan (Makhachkala}
+ 873, // Republic of Ingushetia (Nazran}
+ 877, // Republic of Adygea (Maikop}
+ 878, // Republic of Karachayevo-Cherkessia (Cherkessk}
+ 879, // Stavropol Krai (Mineralnye Vody}
+ 900,
+ 901,
+ 902,
+ 903,
+ 904,
+ 905,
+ 906,
+ 907,
+ 908,
+ 909, // Mobile networks
+ 910,
+ 911,
+ 912,
+ 913,
+ 914,
+ 915,
+ 916,
+ 917,
+ 918,
+ 919, // Mobile networks
+ 920,
+ 921,
+ 922,
+ 923,
+ 924,
+ 925,
+ 926,
+ 927,
+ 928,
+ 929, // Mobile networks
+ 930,
+ 931,
+ 932,
+ 933,
+ 934,
+ 935,
+ 936,
+ 937,
+ 938,
+ 939, // Mobile networks
+ 940,
+ 941,
+ 942,
+ 943,
+ 944,
+ 945,
+ 946,
+ 947,
+ 948,
+ 949, // Mobile networks
+ 950,
+ 951,
+ 952,
+ 953,
+ 955,
+ 956,
+ 957,
+ 958,
+ 959, // Mobile networks
+ 960,
+ 961,
+ 962,
+ 963,
+ 964,
+ 965,
+ 966,
+ 967,
+ 968,
+ 969, // Mobile networks
+ 972,
+ 973,
+ 974,
+ 975,
+ 976,
+ 977,
+ 978,
+ 979, // Mobile networks
+ 980,
+ 981,
+ 982,
+ 983,
+ 984,
+ 985,
+ 986,
+ 987,
+ 988,
+ 989, // Mobile networks
+ 990,
+ 991,
+ 992,
+ 993,
+ 994,
+ 995,
+ 996,
+ 997,
+ 998,
+ 999 // Mobile networks
+ ],
+ displayFormats: [
+ '(AAA} Xxx-xx-xx',
+ '+7 AAAXxxxxxx',
+ '+7 (AAA} Xxx-xx-xx', // Outside Russia use 7 as Country Calling Code
+ '+7-AAA-Xxx-xx-xx'
+ ]
+ }
+ },
+ regions: [
+ // Federal Cities
+ {
+ regionName: 'Moscow City',
+ regionShort: 'Moscow',
+ regionSlug: 'moscow-city',
+ weight: 11,
+ cities: ['Moscow'],
+ extendedData: {
+ zipFormat: {
+ format: '1+X***'
+ },
+ phoneFormat: {
+ areaCodes: [495, 499]
+ }
+ }
+ },
+ {
+ regionName: 'Saint Petersburg City',
+ regionShort: 'Saint Petersburg', // ISO 3166 code
+ regionSlug: 'saint_petersburg-city',
+ weight: 11,
+ cities: ['Saint Petersburg'],
+ extendedData: {
+ zipFormat: {
+ format: '19****'
+ },
+ phoneFormat: {
+ areaCodes: [812]
+ }
+ }
+ },
+
+ // TODO: Need zipFormat for Sevastopol City
+ {
+ regionName: 'Sevastopol City',
+ regionShort: 'Sevastopol', // ISO 3166 code
+ regionSlug: 'sevastopol-city',
+ weight: 11,
+ cities: ['Sevastopol'],
+ extendedData: {
+ zipFormat: {
+ format: '%*****'
+ },
+ phoneFormat: {
+ areaCodes: [869]
+ }
+ }
+ },
+
+ // Oblasts
+ {
+ regionName: 'Amur Oblast',
+ regionShort: 'AMU', // 'RU-AMU', // ISO 3166 code
+ regionSlug: 'amur-oblast',
+ weight: 50,
+ cities: ['Belogorsk', 'Blagoveshchensk', 'Raychikhinsk', 'Shimanovsk', 'Skovorodino', 'Svobodny', 'Tynda', 'Zavitinsk', 'Zeya'],
+ extendedData: {
+ zipFormat: {
+ format: '67(***'
+ },
+ phoneFormat: {
+ areaCodes: [416]
+ }
+ }
+ },
+ {
+ regionName: 'Arkhangelsk Oblast',
+ regionShort: 'ARK', // 'RU-ARK', // ISO 3166 code
+ regionSlug: 'arkhangelsk-oblast',
+ weight: 50,
+ cities: [
+ 'Arkhangelsk',
+ 'Brin-Navolok',
+ 'Kargopol',
+ 'Koryazhma',
+ 'Kotlas',
+ 'Mezen',
+ 'Mirny',
+ 'Novodvinsk',
+ 'Nyandoma',
+ 'Onega',
+ 'Severodvinsk',
+ 'Shenkursk',
+ 'Solvychegodsk',
+ 'Velsk'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '16^***'
+ },
+ phoneFormat: {
+ areaCodes: [818]
+ }
+ }
+ },
+ {
+ regionName: 'Astrakhan Oblast',
+ regionShort: 'AST', // 'RU-AST', // ISO 3166 code
+ regionSlug: 'astrakhan-oblast',
+ weight: 50,
+ cities: ['Astrakhan', 'Akhtubinsk', 'Kamyzyak', 'Kharabali', 'Narimanov', 'Znamensk'],
+ extendedData: {
+ zipFormat: {
+ format: '41S***'
+ },
+ phoneFormat: {
+ areaCodes: [851]
+ }
+ }
+ },
+ {
+ regionName: 'Belgorod Oblast',
+ regionShort: 'BEL', // 'RU-BEL', // ISO 3166 code
+ regionSlug: 'belgorod-oblast',
+ weight: 50,
+ cities: [
+ 'Alexeyevka',
+ 'Belgorod',
+ 'Biryuch',
+ 'Borisovka',
+ 'Grayvoron',
+ 'Gubkin',
+ 'Korocha',
+ 'Krasnaya Yaruga',
+ 'Novy Oskol',
+ 'Rakitnoye',
+ 'Shebekino',
+ 'Stary Oskol',
+ 'Stroitel',
+ 'Valuyki',
+ 'Volokonovka'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '30@***'
+ },
+ phoneFormat: {
+ areaCodes: [472]
+ }
+ }
+ },
+ {
+ regionName: 'Bryansk Oblast',
+ regionShort: 'BRY', // 'RU-BRY', // ISO 3166 code
+ regionSlug: 'bryansk-oblast',
+ weight: 50,
+ cities: [
+ 'Bryansk',
+ 'Dyatkovo',
+ 'Fokino',
+ 'Karachev',
+ 'Klintsy',
+ 'Mglin',
+ 'Novozybkov',
+ 'Pochep',
+ 'Seltso',
+ 'Sevsk',
+ 'Starodub',
+ 'Surazh',
+ 'Trubchevsk',
+ 'Unecha',
+ 'Vialky',
+ 'Zhukovka',
+ 'Zlynka'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '24P***'
+ },
+ phoneFormat: {
+ areaCodes: [483]
+ }
+ }
+ },
+ {
+ regionName: 'Chelyabinsk Oblast',
+ regionShort: 'CHE', // 'RU-CHE', // ISO 3166 code
+ regionSlug: 'chelyabinsk-oblast',
+ weight: 50,
+ cities: [
+ 'Asha',
+ 'Bakal',
+ 'Chelyabinsk',
+ 'Karabash',
+ 'Kartaly',
+ 'Kasli',
+ 'Katav-Ivanovsk',
+ 'Kopeysk',
+ 'Korkino',
+ 'Kusa',
+ 'Kyshtym',
+ 'Magnitogorsk',
+ 'Miass',
+ 'Minyar',
+ 'Nyazepetrovsk',
+ 'Ozyorsk',
+ 'Plast',
+ 'Satka',
+ 'Sim',
+ 'Snezhinsk',
+ 'Troitsk',
+ 'Tryokhgorny',
+ 'Ust-Katav',
+ 'Verkhneuralsk',
+ 'Verkhny Ufaley',
+ 'Yemanzhelinsk',
+ 'Yuryuzan',
+ 'Yurzhnouralsk',
+ 'Zlatoust'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '45R***'
+ },
+ phoneFormat: {
+ areaCodes: [351]
+ }
+ }
+ },
+ {
+ regionName: 'Irkutsk Oblast',
+ regionShort: 'IRK', // 'RU-IRK', // ISO 3166 code
+ regionSlug: 'irkutsk-oblast',
+ weight: 50,
+ cities: ['Irkutsk'],
+ extendedData: {
+ zipFormat: {
+ format: '66#***'
+ },
+ phoneFormat: {
+ areaCodes: [395]
+ }
+ }
+ },
+ {
+ regionName: 'Ivanovo Oblast',
+ regionShort: 'IVA', // 'RU-IVA', // ISO 3166 code
+ regionSlug: 'ivanovo-oblast',
+ weight: 50,
+ cities: ['Ivanovo'],
+ extendedData: {
+ zipFormat: {
+ format: '15T***'
+ },
+ phoneFormat: {
+ areaCodes: [493]
+ }
+ }
+ },
+ {
+ regionName: 'Kaliningrad Oblast',
+ regionShort: 'KGD', // 'RU-KGD', // ISO 3166 code
+ regionSlug: 'kaliningrad-oblast',
+ weight: 50,
+ cities: ['Kaliningrad'],
+ extendedData: {
+ zipFormat: {
+ format: '23?***'
+ },
+ phoneFormat: {
+ areaCodes: [401]
+ }
+ }
+ },
+ {
+ regionName: 'Kaluga Oblast',
+ regionShort: 'KLU', // 'RU-KLU', // ISO 3166 code
+ regionSlug: 'kaluga-oblast',
+ weight: 50,
+ cities: ['Kaluga'],
+ extendedData: {
+ zipFormat: {
+ format: '24@***'
+ },
+ phoneFormat: {
+ areaCodes: [484]
+ }
+ }
+ },
+ {
+ regionName: 'Kemerovo Oblast',
+ regionShort: 'KEM', // 'RU-KEM', // ISO 3166 code
+ regionSlug: 'kemerovo-oblast',
+ weight: 50,
+ cities: ['Kemerovo'],
+ extendedData: {
+ zipFormat: {
+ format: '65U***'
+ },
+ phoneFormat: {
+ areaCodes: [384]
+ }
+ }
+ },
+ {
+ regionName: 'Kirov Oblast',
+ regionShort: 'KIR', // 'RU-KIR', // ISO 3166 code
+ regionSlug: 'kirov-oblast',
+ weight: 50,
+ cities: ['Kirov'],
+ extendedData: {
+ zipFormat: {
+ format: '61+***'
+ },
+ phoneFormat: {
+ areaCodes: [833]
+ }
+ }
+ },
+ {
+ regionName: 'Kostroma Oblast',
+ regionShort: 'KOS', // 'RU-KOS', // ISO 3166 code
+ regionSlug: 'kostroma-oblast',
+ weight: 50,
+ cities: ['Kostroma'],
+ extendedData: {
+ zipFormat: {
+ format: '15V***'
+ },
+ phoneFormat: {
+ areaCodes: [494]
+ }
+ }
+ },
+ {
+ regionName: 'Kurgan Oblast',
+ regionShort: 'KGN', // 'RU-KGN', // ISO 3166 code
+ regionSlug: 'kurgan-oblast',
+ weight: 50,
+ cities: ['Kurgan'],
+ extendedData: {
+ zipFormat: {
+ format: '64-***'
+ },
+ phoneFormat: {
+ areaCodes: [352]
+ }
+ }
+ },
+ {
+ regionName: 'Kursk Oblast',
+ regionShort: 'KRS', // 'RU-KRS', // ISO 3166 code
+ regionSlug: 'kursk-oblast',
+ weight: 50,
+ cities: ['Kursk'],
+ extendedData: {
+ zipFormat: {
+ format: '30Q***'
+ },
+ phoneFormat: {
+ areaCodes: [471]
+ }
+ }
+ },
+ {
+ regionName: 'Leningrad Oblast',
+ regionShort: 'LEN', // 'RU-LEN', // ISO 3166 code
+ regionSlug: 'leningrad-oblast',
+ weight: 50,
+ cities: ['Gatchina', 'Ivangorod'],
+ extendedData: {
+ zipFormat: {
+ format: '18W***'
+ },
+ phoneFormat: {
+ areaCodes: [813]
+ }
+ }
+ },
+ {
+ regionName: 'Lipetsk Oblast',
+ regionShort: 'LIP', // 'RU-LIP', // ISO 3166 code
+ regionSlug: 'lipetsk-oblast',
+ weight: 50,
+ cities: ['Lipetsk'],
+ extendedData: {
+ zipFormat: {
+ format: '39@***'
+ },
+ phoneFormat: {
+ areaCodes: [474]
+ }
+ }
+ },
+ {
+ regionName: 'Magadan Oblast',
+ regionShort: 'MAG', // 'RU-MAG', // ISO 3166 code
+ regionSlug: 'magadan-oblast',
+ weight: 50,
+ cities: ['Magadan'],
+ extendedData: {
+ zipFormat: {
+ format: '68(***'
+ },
+ phoneFormat: {
+ areaCodes: [413]
+ }
+ }
+ },
+ {
+ regionName: 'Moscow Oblast',
+ regionShort: 'MOS', // 'RU-MOS', // ISO 3166 code
+ regionSlug: 'moscow-oblast',
+ weight: 11,
+
+ // https://en.wikipedia.org/wiki/Category:Cities_and_towns_in_Moscow_Oblast
+ cities: [
+ 'Aprelevka',
+ 'Balashikha',
+ 'Bolshevo',
+ 'Bronnitsy',
+ 'Chekhov',
+ 'Chernogolovka',
+ 'Dedovsk',
+ 'Dmitrov',
+ 'Dolgoprudny',
+ 'Domodedovo',
+ 'Drezna',
+ 'Dubna',
+ 'Dzerzhinsky',
+ 'Elektrogorsk',
+ 'Elektrostal',
+ 'Elektrougli',
+ 'Fryazino',
+ 'Golitsyno',
+ 'Istra',
+ 'Ivanteyevka',
+ 'Kalininets',
+ 'Kashira',
+ 'Khimki',
+ 'Khotkovo',
+ 'Klimovsk',
+ 'Klin',
+ 'Kolomna',
+ 'Korolyov',
+ 'Kotelniki',
+ 'Krasnoarmeysk',
+ 'Krasnogorsk',
+ 'Krasnozavodsk',
+ 'Krasnoznamensk',
+ 'Kubinka',
+ 'Kurovskoye',
+ 'Likino-Dulyovo',
+ 'Lobnya',
+ 'Losino-Petrovsky',
+ 'Lukhovitsy',
+ 'Lytkarino',
+ 'Lyubertsy',
+ 'Mozhaysk',
+ 'Mytishchi',
+ 'Naro-Fominsk',
+ 'Noginsk',
+ 'Odintsovo',
+ 'Orekhovo-Zuyevo',
+ 'Ozherelye',
+ 'Ozyory',
+ 'Pavlovsky Posad',
+ 'Peresvet',
+ 'Podolsk',
+ 'Protvino',
+ 'Pushchino',
+ 'Pushkino',
+ 'Ramenskoye',
+ 'Reutov',
+ 'Roshal',
+ 'Ruza',
+ 'Sergiyev Posad',
+ 'Serpukhov',
+ 'Shatura',
+ 'Shchyolkovo',
+ 'Solnechnogorsk',
+ 'Staraya Kupavna',
+ 'Stupino',
+ 'Svitino',
+ 'Taldom',
+ 'Vereya',
+ 'Vidnoye',
+ 'Volokolamsk',
+ 'Voskresensk',
+ 'Vysokovsk',
+ 'Yakhroma',
+ 'Yegoryevsk',
+ 'Zaraysk',
+ 'Zhukovsky',
+ 'Zvenigorod'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: '14U***'
+ },
+ phoneFormat: {
+ areaCodes: [496, 498]
+ }
+ }
+ },
+ {
+ regionName: 'Murmansk Oblast',
+ regionShort: 'MUR', // 'RU-MUR', // ISO 3166 code
+ regionSlug: 'murmansk-oblast',
+ weight: 50,
+ cities: ['Murmansk'],
+ extendedData: {
+ zipFormat: {
+ format: '18}***'
+ },
+ phoneFormat: {
+ areaCodes: [815]
+ }
+ }
+ },
+ {
+ regionName: 'Nizhny Novgorod Oblast',
+ regionShort: 'NIZ', // 'RU-NIZ', // ISO 3166 code
+ regionSlug: 'nizhny_novgorod-oblast',
+ weight: 50,
+ cities: ['Nizhny'],
+ extendedData: {
+ zipFormat: {
+ format: '60Y***'
+ },
+ phoneFormat: {
+ areaCodes: [831]
+ }
+ }
+ },
+ {
+ regionName: 'Novgorod Oblast',
+ regionShort: 'NGR', // 'RU-NGR', // ISO 3166 code
+ regionSlug: 'novgorod-oblast',
+ weight: 50,
+ cities: ['Novgorod'],
+ extendedData: {
+ zipFormat: {
+ format: '17T***'
+ },
+ phoneFormat: {
+ areaCodes: [816]
+ }
+ }
+ },
+ {
+ regionName: 'Novosibirsk Oblast',
+ regionShort: 'NVS', // 'RU-NVS', // ISO 3166 code
+ regionSlug: 'novosibirsk-oblast',
+ weight: 50,
+ cities: ['Novosibirsk'],
+ extendedData: {
+ zipFormat: {
+ format: '63+***'
+ },
+ phoneFormat: {
+ areaCodes: [383]
+ }
+ }
+ },
+ {
+ regionName: 'Omsk Oblast',
+ regionShort: 'OMS', // 'RU-OMS', // ISO 3166 code
+ regionSlug: 'omsk-oblast',
+ weight: 50,
+ cities: ['Omsk'],
+ extendedData: {
+ zipFormat: {
+ format: '64S***'
+ },
+ phoneFormat: {
+ areaCodes: [381]
+ }
+ }
+ },
+ {
+ regionName: 'Orenburg Oblast',
+ regionShort: 'ORE', // 'RU-ORE', // ISO 3166 code
+ regionSlug: 'orenburg-oblast',
+ weight: 50,
+ cities: ['Orenburg'],
+ extendedData: {
+ zipFormat: {
+ format: '46&***'
+ },
+ phoneFormat: {
+ areaCodes: [353]
+ }
+ }
+ },
+ {
+ regionName: 'Oryol Oblast',
+ regionShort: 'ORL', // 'RU-ORL', // ISO 3166 code
+ regionSlug: 'oryol-oblast',
+ weight: 50,
+ cities: ['Oryol'],
+ extendedData: {
+ zipFormat: {
+ format: '30Z***'
+ },
+ phoneFormat: {
+ areaCodes: [486]
+ }
+ }
+ },
+ {
+ regionName: 'Penza Oblast',
+ regionShort: 'PNZ', // 'RU-PNZ', // ISO 3166 code
+ regionSlug: 'penza-oblast',
+ weight: 50,
+ cities: ['Penza'],
+ extendedData: {
+ zipFormat: {
+ format: '44&***'
+ },
+ phoneFormat: {
+ areaCodes: [841]
+ }
+ }
+ },
+ {
+ regionName: 'Pskov Oblast',
+ regionShort: 'PSK', // 'RU-PSK', // ISO 3166 code
+ regionSlug: 'pskov-oblast',
+ weight: 50,
+ cities: ['Pskov'],
+ extendedData: {
+ zipFormat: {
+ format: '18&***'
+ },
+ phoneFormat: {
+ areaCodes: [811]
+ }
+ }
+ },
+ {
+ regionName: 'Rostov Oblast',
+ regionShort: 'ROS', // 'RU-ROS', // ISO 3166 code
+ regionSlug: 'rostov-oblast',
+ weight: 50,
+ cities: ['Rostov'],
+ extendedData: {
+ zipFormat: {
+ format: '34R***'
+ },
+ phoneFormat: {
+ areaCodes: [863]
+ }
+ }
+ },
+ {
+ regionName: 'Ryazan Oblast',
+ regionShort: 'RYA', // 'RU-RYA', // ISO 3166 code
+ regionSlug: 'ryazan-oblast',
+ weight: 50,
+ cities: ['Ryazan'],
+ extendedData: {
+ zipFormat: {
+ format: '39-***'
+ },
+ phoneFormat: {
+ areaCodes: [491]
+ }
+ }
+ },
+ {
+ regionName: 'Sakhalin Oblast',
+ regionShort: 'SAK', // 'RU-SAK', // ISO 3166 code
+ regionSlug: 'sakhalin-oblast',
+ weight: 50,
+ cities: ['Sakhalin'],
+ extendedData: {
+ zipFormat: {
+ format: '69}***'
+ },
+ phoneFormat: {
+ areaCodes: [424]
+ }
+ }
+ },
+ {
+ regionName: 'Samara Oblast',
+ regionShort: 'SAM', // 'RU-SAM', // ISO 3166 code
+ regionSlug: 'samara-oblast',
+ weight: 50,
+ cities: ['Samara', 'Tolyatti'],
+ extendedData: {
+ zipFormat: {
+ format: '44^***'
+ },
+ phoneFormat: {
+ areaCodes: [846, 848]
+ }
+ }
+ },
+ {
+ regionName: 'Saratov Oblast',
+ regionShort: 'SAR', // 'RU-SAR', // ISO 3166 code
+ regionSlug: 'saratov-oblast',
+ weight: 50,
+ cities: ['Saratov'],
+ extendedData: {
+ zipFormat: {
+ format: '41+***'
+ },
+ phoneFormat: {
+ areaCodes: [845]
+ }
+ }
+ },
+ {
+ regionName: 'Smolensk Oblast',
+ regionShort: 'SMO', // 'RU-SMO', // ISO 3166 code
+ regionSlug: 'smolensk-oblast',
+ weight: 50,
+ cities: ['Smolensk'],
+ extendedData: {
+ zipFormat: {
+ format: '21S***'
+ },
+ phoneFormat: {
+ areaCodes: [481]
+ }
+ }
+ },
+ {
+ regionName: 'Sverdlovsk Oblast',
+ regionShort: 'SVE', // 'RU-SVE', // ISO 3166 code
+ regionSlug: 'sverdlovsk-oblast',
+ weight: 50,
+ cities: ['Yekaterinburg'],
+ extendedData: {
+ zipFormat: {
+ format: '62U***'
+ },
+ phoneFormat: {
+ areaCodes: [343]
+ }
+ }
+ },
+ {
+ regionName: 'Tambov Oblast',
+ regionShort: 'TAM', // 'RU-TAM', // ISO 3166 code
+ regionSlug: 'tambov-oblast',
+ weight: 50,
+ cities: ['Tambov'],
+ extendedData: {
+ zipFormat: {
+ format: '39Z***'
+ },
+ phoneFormat: {
+ areaCodes: [475]
+ }
+ }
+ },
+ {
+ regionName: 'Tomsk Oblast',
+ regionShort: 'TOM', // 'RU-TOM', // ISO 3166 code
+ regionSlug: 'tomsk-oblast',
+ weight: 50,
+ cities: ['Tomsk'],
+ extendedData: {
+ zipFormat: {
+ format: '63S***'
+ },
+ phoneFormat: {
+ areaCodes: [382]
+ }
+ }
+ },
+ {
+ regionName: 'Tver Oblast',
+ regionShort: 'TVE', // 'RU-TVE', // ISO 3166 code
+ regionSlug: 'tver-oblast',
+ weight: 50,
+ cities: ['Tver'],
+ extendedData: {
+ zipFormat: {
+ format: '17&***'
+ },
+ phoneFormat: {
+ areaCodes: [482]
+ }
+ }
+ },
+ {
+ regionName: 'Tula Oblast',
+ regionShort: 'TUL', // 'RU-TUL', // ISO 3166 code
+ regionSlug: 'tula-oblast',
+ weight: 50,
+ cities: ['Tula'],
+ extendedData: {
+ zipFormat: {
+ format: '30-***'
+ },
+ phoneFormat: {
+ areaCodes: [487]
+ }
+ }
+ },
+ {
+ regionName: 'Tyumen Oblast',
+ regionShort: 'TYU', // 'RU-TYU', // ISO 3166 code
+ regionSlug: 'tyumen-oblast',
+ weight: 50,
+ cities: ['Tyumen'],
+ extendedData: {
+ zipFormat: {
+ format: '62Q***'
+ },
+ phoneFormat: {
+ areaCodes: [345]
+ }
+ }
+ },
+ {
+ regionName: 'Ulyanovsk Oblast',
+ regionShort: 'ULY', // 'RU-ULY', // ISO 3166 code
+ regionSlug: 'ulyanovsk-oblast',
+ weight: 50,
+ cities: ['Ulyanovsk'],
+ extendedData: {
+ zipFormat: {
+ format: '43Z***'
+ },
+ phoneFormat: {
+ areaCodes: [842]
+ }
+ }
+ },
+ {
+ regionName: 'Vladimir Oblast',
+ regionShort: 'VLA', // 'RU-VLA', // ISO 3166 code
+ regionSlug: 'vladimir-oblast',
+ weight: 50,
+ cities: ['Vladimir'],
+ extendedData: {
+ zipFormat: {
+ format: '60&***'
+ },
+ phoneFormat: {
+ areaCodes: [492]
+ }
+ }
+ },
+ {
+ regionName: 'Volgograd Oblast',
+ regionShort: 'VGG', // 'RU-VGG', // ISO 3166 code
+ regionSlug: 'volgograd-oblast',
+ weight: 50,
+ cities: ['Volgograd'],
+ extendedData: {
+ zipFormat: {
+ format: '40U***'
+ },
+ phoneFormat: {
+ areaCodes: [844]
+ }
+ }
+ },
+ {
+ regionName: 'Vologda Oblast',
+ regionShort: 'VLG', // 'RU-VLG', // ISO 3166 code
+ regionSlug: 'vologda-oblast',
+ weight: 50,
+ cities: ['Cherepovets', 'Vologda'],
+ extendedData: {
+ zipFormat: {
+ format: '16&***'
+ },
+ phoneFormat: {
+ areaCodes: [817, 820]
+ }
+ }
+ },
+ {
+ regionName: 'Voronezh Oblast',
+ regionShort: 'VOR', // 'RU-VOR', // ISO 3166 code
+ regionSlug: 'voronezh-oblast',
+ weight: 50,
+ cities: ['Voronezh'],
+ extendedData: {
+ zipFormat: {
+ format: '39R****'
+ },
+ phoneFormat: {
+ areaCodes: [473]
+ }
+ }
+ },
+ {
+ regionName: 'Yaroslavl Oblast',
+ regionShort: 'YAR', // 'RU-YAR', // ISO 3166 code
+ regionSlug: 'yaroslavl-oblast',
+ weight: 50,
+ cities: ['Yaroslavl'],
+ extendedData: {
+ zipFormat: {
+ format: '15&***'
+ },
+ phoneFormat: {
+ areaCodes: [485]
+ }
+ }
+ }
+ ]
+});
+
+export default Russia;
diff --git a/packages/plugins/src/countries/Russia/i18n/ar.json b/packages/plugins/src/countries/Russia/i18n/ar.json
new file mode 100644
index 000000000..4246e75bc
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "الاتحاد الروسي",
+ "regionNames": "الموضوعات الفيدرالية"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/de.json b/packages/plugins/src/countries/Russia/i18n/de.json
new file mode 100644
index 000000000..35e0b9eb2
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Russische Föderation",
+ "regionNames": "Russische Bundessubjekte"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/en.json b/packages/plugins/src/countries/Russia/i18n/en.json
new file mode 100644
index 000000000..e5ee77dcd
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Russian Federation",
+ "regionNames": "Federal Subjects"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/es.json b/packages/plugins/src/countries/Russia/i18n/es.json
new file mode 100644
index 000000000..9239d6682
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Federación Rusa",
+ "regionNames": "Sujetos federales rusos"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/fr.json b/packages/plugins/src/countries/Russia/i18n/fr.json
new file mode 100644
index 000000000..3e3b963a4
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Fédération Russe",
+ "regionNames": "Sujets fédéraux russes"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/hi.json b/packages/plugins/src/countries/Russia/i18n/hi.json
new file mode 100644
index 000000000..912239b79
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "रूसी संघ",
+ "regionNames": "संघीय विषय"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/ja.json b/packages/plugins/src/countries/Russia/i18n/ja.json
new file mode 100644
index 000000000..6b61e5955
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ポーランド諸国",
+ "regionNames": "ロシア連邦臣民"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/nl.json b/packages/plugins/src/countries/Russia/i18n/nl.json
new file mode 100644
index 000000000..547b20651
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Russische Federatie",
+ "regionNames": "Federale onderwerpen"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/pt.json b/packages/plugins/src/countries/Russia/i18n/pt.json
new file mode 100644
index 000000000..e09247882
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Federação Russa",
+ "regionNames": "Assuntos Federais"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/ru.json b/packages/plugins/src/countries/Russia/i18n/ru.json
new file mode 100644
index 000000000..c498cdad1
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Российская Федерация",
+ "regionNames": "Субъекты Федерации"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/ta.json b/packages/plugins/src/countries/Russia/i18n/ta.json
new file mode 100644
index 000000000..7f0c1f5e2
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "இரஷ்ய கூட்டமைப்பு",
+ "regionNames": "ரஷ்ய கூட்டாட்சி பாடங்கள்"
+}
diff --git a/packages/plugins/src/countries/Russia/i18n/zh.json b/packages/plugins/src/countries/Russia/i18n/zh.json
new file mode 100644
index 000000000..6b2ea9dac
--- /dev/null
+++ b/packages/plugins/src/countries/Russia/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "俄罗斯联邦",
+ "regionNames": "俄罗斯联邦主体"
+}
diff --git a/packages/plugins/src/countries/Singapore/bundle.ts b/packages/plugins/src/countries/Singapore/bundle.ts
new file mode 100644
index 000000000..c80e8beb3
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/bundle.ts
@@ -0,0 +1,91 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Singapore: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'singapore',
+ regionNames: i18n.regionNames,
+ continent: 'asia',
+ extendedData: {
+ zipFormat: {
+ format: 'XXXXXX'
+ },
+ phoneFormat: {
+ displayFormats: ['XXXX XXXX']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Central Region',
+ regionShort: 'SG-01',
+ regionSlug: 'centralregion',
+ weight: 92,
+ cities: [
+ 'Bishan',
+ 'Bukit Merah',
+ 'Bukit Timah',
+ 'Downtown Core',
+ 'Geylang',
+ 'Kallang',
+ 'Marina East',
+ 'Marina South',
+ 'Marine Parade',
+ 'Museum',
+ 'Newton',
+ 'Novena',
+ 'Orchard',
+ 'Outram',
+ 'Queenstown',
+ 'River Valley',
+ 'Rochor',
+ 'Singapore River',
+ 'Southern Islands',
+ 'Straits View',
+ 'Tanglin',
+ 'Toa Payoh'
+ ]
+ },
+ {
+ regionName: 'East Region',
+ regionShort: 'ER',
+ regionSlug: 'eastregion',
+ weight: 69,
+ cities: ['Bedok', 'Changi', 'Changi Bay', 'Pasir Ris', 'Paya Lebar', 'Tampines']
+ },
+ {
+ regionName: 'North Region',
+ regionShort: 'CR',
+ regionSlug: 'northregion',
+ weight: 58,
+ cities: ['Central Water Catchment', 'Lim Chu Kang', 'Mandai', 'Sembawang', 'Simpang', 'Sungei Kadut', 'Woodlands', 'Yishun']
+ },
+ {
+ regionName: 'North-East Region',
+ regionShort: 'SG-02',
+ regionSlug: 'northeastregion',
+ weight: 93,
+ cities: ['Ang Mo Kio', 'Hougang', 'North-Eastern Islands', 'Punggol', 'Seletar', 'Sengkang', 'Serangoon']
+ },
+ {
+ regionName: 'West Region',
+ regionShort: 'CR',
+ regionSlug: 'westregion',
+ weight: 92,
+ cities: [
+ 'Boon Lay',
+ 'Bukit Batok',
+ 'Bukit Panjang',
+ 'Choa Chu Kang',
+ 'Clementi',
+ 'Jurong East',
+ 'Jurong West',
+ 'Pioneer',
+ 'Tengah',
+ 'Tuas',
+ 'Western Islands',
+ 'Western Water Catchment'
+ ]
+ }
+ ]
+});
+
+export default Singapore;
diff --git a/packages/plugins/src/countries/Singapore/i18n/ar.json b/packages/plugins/src/countries/Singapore/i18n/ar.json
new file mode 100644
index 000000000..cac40504d
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "سنغافورة",
+ "regionNames": "مناطق سنغافورة"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/de.json b/packages/plugins/src/countries/Singapore/i18n/de.json
new file mode 100644
index 000000000..e60bd7e86
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Singapur",
+ "regionNames": "Regionen in Singapur"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/en.json b/packages/plugins/src/countries/Singapore/i18n/en.json
new file mode 100644
index 000000000..9a1c78f33
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Singapore",
+ "regionNames": "Singapore regions"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/es.json b/packages/plugins/src/countries/Singapore/i18n/es.json
new file mode 100644
index 000000000..d3cebbc79
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Singapur",
+ "regionNames": "Regiones de Singapur"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/fr.json b/packages/plugins/src/countries/Singapore/i18n/fr.json
new file mode 100644
index 000000000..2966c329c
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Singapour",
+ "regionNames": "Régions de Singapour"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/hi.json b/packages/plugins/src/countries/Singapore/i18n/hi.json
new file mode 100644
index 000000000..20d432fc5
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "सिंगापुर",
+ "regionNames": "सिंगापुर क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/ja.json b/packages/plugins/src/countries/Singapore/i18n/ja.json
new file mode 100644
index 000000000..4234ab3c3
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "シンガポール",
+ "regionNames": "シンガポール地域"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/nl.json b/packages/plugins/src/countries/Singapore/i18n/nl.json
new file mode 100644
index 000000000..7d72bacf7
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Singapore",
+ "regionNames": "Regio's in Singapore"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/pt.json b/packages/plugins/src/countries/Singapore/i18n/pt.json
new file mode 100644
index 000000000..862415e3f
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Cingapura",
+ "regionNames": "Regiões de Cingapura"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/ru.json b/packages/plugins/src/countries/Singapore/i18n/ru.json
new file mode 100644
index 000000000..8ba91c0e4
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Сингапур",
+ "regionNames": "Сингапурские регионы"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/ta.json b/packages/plugins/src/countries/Singapore/i18n/ta.json
new file mode 100644
index 000000000..dab595804
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "சிங்கப்பூர்",
+ "regionNames": "சிங்கப்பூர் பிராந்தியங்கள்"
+}
diff --git a/packages/plugins/src/countries/Singapore/i18n/zh.json b/packages/plugins/src/countries/Singapore/i18n/zh.json
new file mode 100644
index 000000000..f10ef3b9f
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "新加坡",
+ "regionNames": "新加坡地区"
+}
diff --git a/packages/plugins/src/countries/Singapore/names.ts b/packages/plugins/src/countries/Singapore/names.ts
new file mode 100644
index 000000000..12b4c217f
--- /dev/null
+++ b/packages/plugins/src/countries/Singapore/names.ts
@@ -0,0 +1,518 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Alex',
+ 'Alexis',
+ 'Alicia',
+ 'Alisa',
+ 'Amanda',
+ 'Amelia ',
+ 'Amoleka',
+ 'Anastacia',
+ 'Angelina',
+ 'Ashley',
+ 'Belle',
+ 'Beth',
+ 'Chang',
+ 'Charmaine',
+ 'Cherie kok',
+ 'Cherilyn',
+ 'Cheryl',
+ 'Chloe',
+ 'Claire',
+ 'Claris',
+ 'Danielle',
+ 'Dee',
+ 'Dina',
+ 'Dionne',
+ 'Elizabeth',
+ 'Emily',
+ 'Eunice',
+ 'Felicia',
+ 'Gen',
+ 'Giselle',
+ 'Gladys',
+ 'Hannah',
+ 'Hanni',
+ 'Hazel',
+ 'Iris',
+ 'Isabel',
+ 'Isabelle',
+ 'Jaime',
+ 'Jaslyn',
+ 'Jasmine',
+ 'Jazreen',
+ 'Jean',
+ 'Jiemin',
+ 'Jing Yi',
+ 'Joanne',
+ 'Joyce',
+ 'Kelly',
+ 'Kim',
+ 'Kristine',
+ 'Leisrin',
+ 'Lucianna',
+ 'Lydiah',
+ 'Mai',
+ 'Marjorie',
+ 'Michele',
+ 'Michelle',
+ 'Min',
+ 'Mitchie',
+ 'Natasha',
+ 'Nicole',
+ 'Nina',
+ 'Paula',
+ 'Peggy',
+ 'Qistina',
+ 'Rachael',
+ 'Rachel',
+ 'Radiance',
+ 'Regina',
+ 'Renee',
+ 'Ria',
+ 'Sabrina',
+ 'Samantha',
+ 'Sarah',
+ 'Sofia',
+ 'Sophie',
+ 'Star',
+ 'Su',
+ 'Tara',
+ 'Tarini',
+ 'Therese',
+ 'Tuti',
+ 'Val',
+ 'Valerie',
+ 'Vanessa',
+ 'Vivian ',
+ 'Xin',
+ 'Xin Jie',
+ 'Yi Jia',
+ 'Yuna',
+ 'Yuri'
+];
+
+const maleNames = [
+ 'Ace',
+ 'Adam',
+ 'Alan',
+ 'Alex',
+ 'Aloy',
+ 'Alv',
+ 'Andrew',
+ 'Aniq',
+ 'Aqmal',
+ 'Austin',
+ 'Ayden',
+ 'Ben',
+ 'Benjamin',
+ 'Chen',
+ 'Cheong',
+ 'Christopher',
+ 'Chua',
+ 'Chun Hwee',
+ 'Clemens',
+ 'Damien',
+ 'Danial',
+ 'Daniel',
+ 'Darren',
+ 'Darryl',
+ 'Derrick',
+ 'Derrik',
+ 'Dominic',
+ 'Drew',
+ 'Edison',
+ 'Elroy',
+ 'Ethan',
+ 'Eugene',
+ 'Fuzz',
+ 'Gabriel',
+ 'Grantdy',
+ 'Haruka',
+ 'Hong wei',
+ 'Irfan',
+ 'Imran',
+ 'Isaac',
+ 'J',
+ 'Jackson',
+ 'Jareth',
+ 'Jason',
+ 'Javier',
+ 'Jess',
+ 'Jolly',
+ 'Jonathan',
+ 'Jordan',
+ 'Joseph',
+ 'Joshua',
+ 'Jun Wei',
+ 'Justin',
+ 'Ke Wei',
+ 'Keefe',
+ 'Ken',
+ 'Kevan',
+ 'Khey',
+ 'Koh',
+ 'Lance',
+ 'Lee',
+ 'Lim',
+ 'Low',
+ 'Lyon',
+ 'Marcus',
+ 'Martin',
+ 'Max',
+ 'Michael',
+ 'Morgan',
+ 'Nathaniel',
+ 'Nicholas',
+ 'Nish',
+ 'Norman',
+ 'Orion',
+ 'Peisen',
+ 'Richard',
+ 'Roy',
+ 'Ryan',
+ 'Sean',
+ 'Shahir',
+ 'Shane',
+ 'Sharan',
+ 'Siegfried',
+ 'Swami',
+ 'Thane',
+ 'Tony',
+ 'Trivikram',
+ 'Tyris',
+ 'Wayne',
+ 'Wei',
+ 'Xavier',
+ 'YongJae',
+ 'Zack',
+ 'Zander'
+];
+
+const lastNames = [
+ 'Ahmad',
+ 'Ali',
+ 'Alsagoff',
+ 'An',
+ 'Ang',
+ 'Au',
+ 'Aung',
+ 'Ban',
+ 'Bao',
+ 'Bi',
+ 'Boon',
+ 'Cai',
+ 'Cao',
+ 'Cha',
+ 'Chai',
+ 'Chan',
+ 'Chang',
+ 'Chao',
+ 'Che',
+ 'Cheah',
+ 'Cheang',
+ 'Chee',
+ 'Chen',
+ 'Cheng',
+ 'Cheong',
+ 'Cheung',
+ 'Chew',
+ 'Chi',
+ 'Chia',
+ 'Chin',
+ 'Ching',
+ 'Chionh',
+ 'Chiu',
+ 'Chng',
+ 'Cho',
+ 'Choi',
+ 'Chong',
+ 'Choo',
+ 'Choong',
+ 'Chou',
+ 'Chow',
+ 'Choy',
+ 'Chu',
+ 'Chua',
+ 'Chui',
+ 'Chung',
+ 'Cui',
+ 'Dai',
+ 'Deng',
+ 'Ding',
+ 'Dong',
+ 'Duan',
+ 'Ee',
+ 'Fan',
+ 'Fang',
+ 'Feng',
+ 'Fok',
+ 'Fong',
+ 'Foo',
+ 'Foong',
+ 'Fu',
+ 'Fung',
+ 'Gan',
+ 'Gao',
+ 'Ge',
+ 'Goh',
+ 'Gong',
+ 'Gu',
+ 'Guan',
+ 'Guo',
+ 'Gupta',
+ 'Han',
+ 'Hang',
+ 'Hao',
+ 'He',
+ 'Heng',
+ 'Ho',
+ 'Hong',
+ 'Hou',
+ 'Hu',
+ 'Hua',
+ 'Huang',
+ 'Hui',
+ 'Hwang',
+ 'Hwee',
+ 'Jain',
+ 'Jang',
+ 'Jia',
+ 'Jiang',
+ 'Jie',
+ 'Jin',
+ 'Jing',
+ 'Jong',
+ 'Jung',
+ 'Kai',
+ 'Kan',
+ 'Kang',
+ 'Kannan',
+ 'Kaur',
+ 'Ke',
+ 'Kee',
+ 'Khan',
+ 'Khoo',
+ 'Kim',
+ 'Ko',
+ 'Koh',
+ 'Kong',
+ 'Koo',
+ 'Krishnan',
+ 'Ku',
+ 'Kuang',
+ 'Kum',
+ 'Kumar',
+ 'Kung',
+ 'Kwa',
+ 'Kwan',
+ 'Kwee',
+ 'Kwek',
+ 'Kwok',
+ 'Kwon',
+ 'Kwong',
+ 'Lai',
+ 'Lam',
+ 'Lan',
+ 'Lang',
+ 'Lao',
+ 'Lau',
+ 'Law',
+ 'Lay',
+ 'Lee',
+ 'Lei',
+ 'Leng',
+ 'Leong',
+ 'Leow',
+ 'Leung',
+ 'Lew',
+ 'Li',
+ 'Lian',
+ 'Liang',
+ 'Liao',
+ 'Liew',
+ 'Lim',
+ 'Lin',
+ 'Ling',
+ 'Liu',
+ 'Loh',
+ 'Loke',
+ 'Loo',
+ 'Lou',
+ 'Lu',
+ 'Lui',
+ 'Luo',
+ 'Lwin',
+ 'Lye',
+ 'Ma',
+ 'Mai',
+ 'Man',
+ 'Mani',
+ 'Mao',
+ 'Mei',
+ 'Meng',
+ 'Miao',
+ 'Min',
+ 'Ming',
+ 'Mo',
+ 'Mohan',
+ 'Mok',
+ 'Mu',
+ 'Mun',
+ 'Myint',
+ 'Nai',
+ 'Naing',
+ 'Nair',
+ 'Neo',
+ 'Ng',
+ 'Ngai',
+ 'Nguyen',
+ 'Ni',
+ 'Ong',
+ 'Oo',
+ 'Ooi',
+ 'Ou',
+ 'Ow',
+ 'Pak',
+ 'Pan',
+ 'Pang',
+ 'Peh',
+ 'Pei',
+ 'Peng',
+ 'Phang',
+ 'Phua',
+ 'Ping',
+ 'Png',
+ 'Poh',
+ 'Pok',
+ 'Pong',
+ 'Poon',
+ 'Qi',
+ 'Qin',
+ 'Qiu',
+ 'Qu',
+ 'Quan',
+ 'Quek',
+ 'Rahman',
+ 'Raj',
+ 'Reddy',
+ 'Ren',
+ 'Sato',
+ 'Seah',
+ 'Seet',
+ 'Seetoh',
+ 'Seng',
+ 'Seo',
+ 'Seow',
+ 'Sha',
+ 'Shah',
+ 'Shang',
+ 'Shao',
+ 'Sharma',
+ 'Shen',
+ 'Sheng',
+ 'Shi',
+ 'Shin',
+ 'Shu',
+ 'Shum',
+ 'Si',
+ 'Sim',
+ 'Sin',
+ 'Singh',
+ 'Siu',
+ 'So',
+ 'Soe',
+ 'Soh',
+ 'Son',
+ 'Song',
+ 'Soo',
+ 'Soon',
+ 'Su',
+ 'Subramanian',
+ 'Sui',
+ 'Sun',
+ 'Sze',
+ 'Tai',
+ 'Tan',
+ 'Tanaka',
+ 'Tang',
+ 'Tao',
+ 'Tay',
+ 'Tee',
+ 'Teh',
+ 'Teng',
+ 'Teo',
+ 'Teoh',
+ 'Tham',
+ 'Thia',
+ 'Tian',
+ 'Ting',
+ 'Toh',
+ 'Tong',
+ 'Tsang',
+ 'Tse',
+ 'Tsui',
+ 'Tu',
+ 'Tun',
+ 'Wang',
+ 'Wee',
+ 'Wen',
+ 'Win',
+ 'Won',
+ 'Wong',
+ 'Woo',
+ 'Woon',
+ 'Wu',
+ 'Xia',
+ 'Xiang',
+ 'Xiao',
+ 'Xie',
+ 'Xin',
+ 'Xing',
+ 'Xiong',
+ 'Xu',
+ 'Xue',
+ 'Yan',
+ 'Yang',
+ 'Yao',
+ 'Yap',
+ 'Ye',
+ 'Yee',
+ 'Yeoh',
+ 'Yeow',
+ 'Yeung',
+ 'Yew',
+ 'Yi',
+ 'Yim',
+ 'Yin',
+ 'Ying',
+ 'Yip',
+ 'Yong',
+ 'You',
+ 'Yu',
+ 'Yuan',
+ 'Yue',
+ 'Yuen',
+ 'Yun',
+ 'Yung',
+ 'Zhai',
+ 'Zhan',
+ 'Zhang',
+ 'Zhao',
+ 'Zheng',
+ 'Zhong',
+ 'Zhou',
+ 'Zhu',
+ 'Zhuang',
+ 'Zhuo',
+ 'Zou'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/SouthAfrica/bundle.ts b/packages/plugins/src/countries/SouthAfrica/bundle.ts
new file mode 100644
index 000000000..90bec7eae
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/bundle.ts
@@ -0,0 +1,134 @@
+import { GetCountryData } from '@generatedata/types';
+
+const SouthAfrica: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'southafrica',
+ regionNames: i18n.regionNames,
+ continent: 'africa',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxx-xxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['XXX XXX XXXX', '0XX XXX XXXX']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Eastern Cape',
+ regionShort: 'EC',
+ regionSlug: 'easterncape',
+ weight: 6,
+ cities: [
+ 'Alice',
+ 'Butterworth',
+ 'East London',
+ 'Graaff-Reinet',
+ 'Grahamstown',
+ "King William's Town",
+ 'Mthatha',
+ 'Port Elizabeth',
+ 'Queenstown',
+ 'Uitenhage',
+ 'Zwelitsha'
+ ]
+ },
+ {
+ regionName: 'Free State',
+ regionShort: 'FS',
+ regionSlug: 'freestate',
+ weight: 3,
+ cities: [
+ 'Bethlehem',
+ 'Bloemfontein',
+ 'Jagersfontein',
+ 'Kroonstad',
+ 'Odendaalsrus',
+ 'Parys',
+ 'Phuthaditjhaba',
+ 'Sasolburg',
+ 'Virginia',
+ 'Welkom'
+ ]
+ },
+ {
+ regionName: 'Gauteng',
+ regionShort: 'GP',
+ regionSlug: 'gauteng',
+ weight: 12,
+ cities: [
+ 'Benoni',
+ 'Boksburg',
+ 'Brakpan',
+ 'Carletonville',
+ 'Germiston',
+ 'Johannesburg',
+ 'Krugersdorp',
+ 'Pretoria',
+ 'Randburg',
+ 'Randfontein',
+ 'Roodepoort',
+ 'Soweto',
+ 'Springs',
+ 'Vanderbijlpark',
+ 'Vereeniging'
+ ]
+ },
+ {
+ regionName: 'KwaZulu-Natal',
+ regionShort: 'KZN',
+ regionSlug: 'kwazulunatal',
+ weight: 10,
+ cities: ['Durban', 'Empangeni', 'Ladysmith', 'Newcastle', 'Pietermaritzburg', 'Pinetown', 'Ulundi', 'Umlazi']
+ },
+ {
+ regionName: 'Limpopo',
+ regionShort: 'LP',
+ regionSlug: 'limpopo',
+ weight: 5,
+ cities: ['Giyani', 'Lebowakgomo', 'Musina', 'Phalaborwa', 'Polokwane', 'Seshego', 'Sibasa', 'Thabazimbi']
+ },
+ {
+ regionName: 'Mpumalanga',
+ regionShort: 'MP',
+ regionSlug: 'mpumalanga',
+ weight: 4,
+ cities: ['Emalahleni', 'Nelspruit', 'Secunda']
+ },
+ {
+ regionName: 'Northern Cape',
+ regionShort: 'NC',
+ regionSlug: 'northerncape',
+ weight: 1,
+ cities: ['Kimberley', 'Kuruman', 'Port Nolloth']
+ },
+ {
+ regionName: 'North West',
+ regionShort: 'NW',
+ regionSlug: 'northwest',
+ weight: 3,
+ cities: ['Klerksdorp', 'Mahikeng', 'Mmabatho', 'Potchefstroom', 'Rustenburg']
+ },
+ {
+ regionName: 'Western Cape',
+ regionShort: 'WC',
+ regionSlug: 'westerncape',
+ weight: 6,
+ cities: [
+ 'Bellville',
+ 'Cape Town',
+ 'Constantia',
+ 'George',
+ 'Hopefield',
+ 'Oudtshoorn',
+ 'Paarl',
+ "Simon's Town",
+ 'Stellenbosch',
+ 'Swellendam',
+ 'Worcester'
+ ]
+ }
+ ]
+});
+
+export default SouthAfrica;
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/ar.json b/packages/plugins/src/countries/SouthAfrica/i18n/ar.json
new file mode 100644
index 000000000..f69ae4153
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "جنوب أفريقيا",
+ "regionNames": "مقاطعات جنوب افريقيا"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/de.json b/packages/plugins/src/countries/SouthAfrica/i18n/de.json
new file mode 100644
index 000000000..1941e80ea
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Südafrika",
+ "regionNames": "Provinzen in Südafrika"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/en.json b/packages/plugins/src/countries/SouthAfrica/i18n/en.json
new file mode 100644
index 000000000..1497ae59d
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "South Africa",
+ "regionNames": "South Africa provinces"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/es.json b/packages/plugins/src/countries/SouthAfrica/i18n/es.json
new file mode 100644
index 000000000..3aeeae9b9
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Sudáfrica",
+ "regionNames": "Provincias de Sudáfrica"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/fr.json b/packages/plugins/src/countries/SouthAfrica/i18n/fr.json
new file mode 100644
index 000000000..d9273c4cd
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Afrique du Sud",
+ "regionNames": "Provinces d'Afrique du Sud"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/hi.json b/packages/plugins/src/countries/SouthAfrica/i18n/hi.json
new file mode 100644
index 000000000..55bed7b44
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "दक्षिण अफ्रीका",
+ "regionNames": "दक्षिण अफ़्रीका प्रांत"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/ja.json b/packages/plugins/src/countries/SouthAfrica/i18n/ja.json
new file mode 100644
index 000000000..3227b4a48
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "南アフリカ",
+ "regionNames": "南アフリカの州"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/nl.json b/packages/plugins/src/countries/SouthAfrica/i18n/nl.json
new file mode 100644
index 000000000..68bad0e67
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Zuid-Afrika",
+ "regionNames": "Zuid-Afrikaanse provincies"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/pt.json b/packages/plugins/src/countries/SouthAfrica/i18n/pt.json
new file mode 100644
index 000000000..f0b529f38
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "África do Sul",
+ "regionNames": "Províncias da África do Sul"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/ru.json b/packages/plugins/src/countries/SouthAfrica/i18n/ru.json
new file mode 100644
index 000000000..aad8cb0d7
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Южная Африка",
+ "regionNames": "Провинции ЮАР"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/ta.json b/packages/plugins/src/countries/SouthAfrica/i18n/ta.json
new file mode 100644
index 000000000..0c56cb532
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "தென்னாப்பிரிக்கா",
+ "regionNames": "தென்னாப்பிரிக்கா மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/SouthAfrica/i18n/zh.json b/packages/plugins/src/countries/SouthAfrica/i18n/zh.json
new file mode 100644
index 000000000..f0835ed8f
--- /dev/null
+++ b/packages/plugins/src/countries/SouthAfrica/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "南非",
+ "regionNames": "南非省份"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/bundle.ts b/packages/plugins/src/countries/SouthKorea/bundle.ts
new file mode 100644
index 000000000..5d0ef5f9f
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/bundle.ts
@@ -0,0 +1,132 @@
+import { GetCountryData } from '@generatedata/types';
+
+const SouthKorea: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'southkorea',
+ regionNames: i18n.regionNames,
+ continent: 'asia',
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxxx',
+ replacements: {
+ X: '012'
+ }
+ },
+ phoneFormat: {
+ areaCodes: ['031', '032', '033', '041', '042', '043', '044', '049', '051', '052', '053', '054', '055', '061', '062', '063', '064'],
+ displayFormats: ['AAA-xxx-xxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'North Chungcheong',
+ regionShort: 'Chungbuk',
+ regionSlug: 'chungbuk',
+ weight: 16,
+ cities: ['Chungju', 'Jecheon']
+ },
+ {
+ regionName: 'South Chungcheong',
+ regionShort: 'Chungnam',
+ regionSlug: 'chungnam',
+ weight: 20,
+ cities: ['Daejeon', 'Asan', 'Dangjin', 'Nonsan', 'Gongju', 'Boryeong']
+ },
+ {
+ regionName: 'Gangwon',
+ regionShort: 'Gangwon',
+ regionSlug: 'gangwon',
+ weight: 15,
+ cities: ['Wonju', 'Chuncheon', 'Gangneung']
+ },
+ {
+ regionName: 'Gyeonggi',
+ regionShort: 'Gyeonggi',
+ regionSlug: 'gyeonggi',
+ weight: 122,
+ cities: [
+ 'Seoul',
+ 'Incheon',
+ 'Gwangju',
+ 'Suwon',
+ 'Goyang',
+ 'Yongin',
+ 'Seongnam',
+ 'Bucheon',
+ 'Ansan',
+ 'Namyangju',
+ 'Hwaseong',
+ 'Anyang',
+ 'Pyeongtaek',
+ 'Siheung',
+ 'Uijeongbu',
+ 'Paju',
+ 'Gimpo',
+ 'Gwangmyeong',
+ 'Gwangju',
+ 'Gunpo',
+ 'Osan',
+ 'Icheon',
+ 'Yangju',
+ 'Anseong',
+ 'Guri',
+ 'Pocheon',
+ 'Uiwang',
+ 'Hanam',
+ 'Yeoju',
+ 'Dongducheon'
+ ]
+ },
+ {
+ regionName: 'North Gyeongsang',
+ regionShort: 'Gyeongbuk',
+ regionSlug: 'gyeongbuk',
+ weight: 27,
+ cities: ['Cheongju', 'Pohang', 'Gumi', 'Gyeongsan', 'Gyeongju', 'Andong', 'Gimcheon', 'Yeongju', 'Sangju', 'Yeongcheon']
+ },
+ {
+ regionName: 'South Gyeongsang',
+ regionShort: 'Gyeongnam',
+ regionSlug: 'gyeongnam',
+ weight: 34,
+ cities: [
+ 'Busan',
+ 'Daegu',
+ 'Ulsan',
+ 'Changwon',
+ 'Cheonan',
+ 'Gimhae',
+ 'Jinju',
+ 'Yangsan',
+ 'Geoje',
+ 'Seosan',
+ 'Tongyeong',
+ 'Sacheon',
+ 'Miryang'
+ ]
+ },
+ {
+ regionName: 'North Jeolla',
+ regionShort: 'Jeonbuk',
+ regionSlug: 'jeonbuk',
+ weight: 34,
+ cities: ['Jeonju', 'Iksan', 'Gunsan', 'Jeongeup']
+ },
+ {
+ regionName: 'South Jeolla',
+ regionShort: 'Jeonnam',
+ regionSlug: 'jeonnam',
+ weight: 19,
+ cities: ['Yeosu', 'Suncheon', 'Mokpo', 'Gwangyang']
+ },
+ {
+ regionName: 'Jeju',
+ regionShort: 'Jeju',
+ regionSlug: 'jeju',
+ weight: 5,
+ cities: ['Jeju', 'Seogwipo']
+ }
+ ]
+});
+
+export default SouthKorea;
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/ar.json b/packages/plugins/src/countries/SouthKorea/i18n/ar.json
new file mode 100644
index 000000000..21f8f58cb
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "كوريا الجنوبية",
+ "regionNames": "مقاطعات كوريا الجنوبية"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/de.json b/packages/plugins/src/countries/SouthKorea/i18n/de.json
new file mode 100644
index 000000000..1b1a80629
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Südkorea",
+ "regionNames": "Provinzen Südkoreas"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/en.json b/packages/plugins/src/countries/SouthKorea/i18n/en.json
new file mode 100644
index 000000000..5a93140e1
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "South Korea",
+ "regionNames": "South Korea provinces"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/es.json b/packages/plugins/src/countries/SouthKorea/i18n/es.json
new file mode 100644
index 000000000..584322100
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Corea del Sur",
+ "regionNames": "provincias de corea del sur"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/fr.json b/packages/plugins/src/countries/SouthKorea/i18n/fr.json
new file mode 100644
index 000000000..ed960df75
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Corée du Sud",
+ "regionNames": "Provinces de Corée du Sud"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/hi.json b/packages/plugins/src/countries/SouthKorea/i18n/hi.json
new file mode 100644
index 000000000..0bac67a85
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "दक्षिण कोरिया",
+ "regionNames": "दक्षिण कोरिया प्रांत"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/ja.json b/packages/plugins/src/countries/SouthKorea/i18n/ja.json
new file mode 100644
index 000000000..7def85e41
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "韓国",
+ "regionNames": "韓国の地方"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/nl.json b/packages/plugins/src/countries/SouthKorea/i18n/nl.json
new file mode 100644
index 000000000..8e45e239a
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Zuid-Korea",
+ "regionNames": "Zuid-Koreaanse provincies"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/pt.json b/packages/plugins/src/countries/SouthKorea/i18n/pt.json
new file mode 100644
index 000000000..36dd52fe4
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Coreia do Sul",
+ "regionNames": "Províncias da Coréia do Sul"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/ru.json b/packages/plugins/src/countries/SouthKorea/i18n/ru.json
new file mode 100644
index 000000000..a57184064
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Южная Корея",
+ "regionNames": "Провинции Южной Кореи"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/ta.json b/packages/plugins/src/countries/SouthKorea/i18n/ta.json
new file mode 100644
index 000000000..c25ff542b
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "தென் கொரியா",
+ "regionNames": "தென் கொரியா மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/SouthKorea/i18n/zh.json b/packages/plugins/src/countries/SouthKorea/i18n/zh.json
new file mode 100644
index 000000000..049b2b064
--- /dev/null
+++ b/packages/plugins/src/countries/SouthKorea/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "韩国",
+ "regionNames": "韩国各省"
+}
diff --git a/packages/plugins/src/countries/Spain/bundle.ts b/packages/plugins/src/countries/Spain/bundle.ts
new file mode 100644
index 000000000..1cc072e7c
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/bundle.ts
@@ -0,0 +1,192 @@
+/**
+ * Spanish autonomies and cities with population >= 100.000 or province heads
+ * Weight: percentage of population of the region to the total of Spain * 100
+ * Source: https://es.wikipedia.org/wiki/Anexo%3AMunicipios_de_Espa%C3%B1a_por_poblaci%C3%B3n
+ * @package Countries
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Spain: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'spain',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Andalucía',
+ regionShort: 'AN',
+ regionSlug: 'andalucia',
+ weight: 1788,
+ cities: [
+ 'Almería',
+ 'Cádiz',
+ 'Córdoba',
+ 'Granada',
+ 'Sevilla',
+ 'Huelva',
+ 'Jaén',
+ 'Málaga',
+ 'Jerez de la Frontera',
+ 'Marbella',
+ 'Dos Hermanas',
+ 'Algeciras'
+ ]
+ },
+ {
+ regionName: 'Aragón',
+ regionShort: 'AR',
+ regionSlug: 'aragon',
+ weight: 286,
+ cities: ['Huesca', 'Teruel', 'Zaragoza']
+ },
+ {
+ regionName: 'Principado de Asturias',
+ regionShort: 'AS',
+ regionSlug: 'asturias',
+ weight: 228,
+ cities: ['Oviedo', 'Gijón']
+ },
+ {
+ regionName: 'Cantabria',
+ regionShort: 'CA',
+ regionSlug: 'cantabria',
+ weight: 126,
+ cities: ['Santander']
+ },
+ {
+ regionName: 'Castilla - La Mancha',
+ regionShort: 'CM',
+ regionSlug: 'clm',
+ weight: 449,
+ cities: ['Ciudad Real', 'Albacete', 'Cuenca', 'Toledo', 'Guadalajara']
+ },
+ {
+ regionName: 'Castilla y León',
+ regionShort: 'CL',
+ regionSlug: 'cle',
+ weight: 539,
+ cities: ['Burgos', 'León', 'Palencia', 'Valladolid', 'Zamora', 'Ávila', 'Salamanca', 'Segovia', 'Soria']
+ },
+ {
+ regionName: 'Catalunya',
+ regionShort: 'CA',
+ regionSlug: 'cataluña',
+ weight: 1602,
+ cities: [
+ 'Barcelona',
+ 'Tarragona',
+ 'Girona',
+ 'Lleida',
+ "L'Hospitalet de Llobregat",
+ 'Badalona',
+ 'Tarrasa',
+ 'Sabadell',
+ 'Mataró',
+ 'Santa Coloma de Gramenet',
+ 'Reus'
+ ]
+ },
+ {
+ regionName: 'Ceuta',
+ regionShort: 'CE',
+ regionSlug: 'ceuta',
+ weight: 18,
+ cities: ['Ceuta']
+ },
+ {
+ regionName: 'Comunitat Valenciana',
+ regionShort: 'CV',
+ regionSlug: 'valencia',
+ weight: 1085,
+ cities: ['Castelló', 'Valéncia', 'Alacant', 'Elx', 'Torrevieja']
+ },
+ {
+ regionName: 'Canarias',
+ regionShort: 'CN',
+ regionSlug: 'canarias',
+ weight: 448,
+ cities: ['Santa Cruz de Tenerife', 'Las Palmas', 'San Cristóbal de la Laguna', 'Telde']
+ },
+ {
+ regionName: 'Illes Balears',
+ regionShort: 'BA',
+ regionSlug: 'baleares',
+ weight: 237,
+ cities: ['Palma de Mallorca']
+ },
+ {
+ regionName: 'Extremadura',
+ regionShort: 'EX',
+ regionSlug: 'extremadura',
+ weight: 234,
+ cities: ['Badajoz', 'Cáceres']
+ },
+ {
+ regionName: 'Galicia',
+ regionShort: 'GA',
+ regionSlug: 'galicia',
+ weight: 588,
+ cities: ['A Coruña', 'Ourense', 'Lugo', 'Pontevedra', 'Vigo']
+ },
+ {
+ regionName: 'Madrid',
+ regionShort: 'MA',
+ regionSlug: 'madrid',
+ weight: 1375,
+ cities: [
+ 'Madrid',
+ 'Móstoles',
+ 'Alcalá de Henares',
+ 'Fuenlabrada',
+ 'Leganés',
+ 'Getafe',
+ 'Alcorcón',
+ 'Torrejón de Ardoz',
+ 'Parla',
+ 'Alcobendas'
+ ]
+ },
+ {
+ regionName: 'Melilla',
+ regionShort: 'ME',
+ regionSlug: 'melilla',
+ weight: 17,
+ cities: ['Melilla']
+ },
+ {
+ regionName: 'Murcia',
+ regionShort: 'MU',
+ regionSlug: 'murcia',
+ weight: 312,
+ cities: ['Murcia', 'Cartagena']
+ },
+ {
+ regionName: 'Navarra',
+ regionShort: 'NA',
+ regionSlug: 'navarra',
+ weight: 136,
+ cities: ['Pamplona']
+ },
+ {
+ regionName: 'Euskadi',
+ regionShort: 'PV',
+ regionSlug: 'paisvasco',
+ weight: 464,
+ cities: ['Bilbo', 'Donosti', 'Gasteiz', 'Baracaldo']
+ },
+ {
+ regionName: 'La Rioja',
+ regionShort: 'LR',
+ regionSlug: 'larioja',
+ weight: 68,
+ cities: ['Logroño']
+ }
+ ]
+});
+
+export default Spain;
diff --git a/packages/plugins/src/countries/Spain/i18n/ar.json b/packages/plugins/src/countries/Spain/i18n/ar.json
new file mode 100644
index 000000000..a23fe782c
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "إسبانيا",
+ "regionNames": "الحكم الذاتي الاسباني"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/de.json b/packages/plugins/src/countries/Spain/i18n/de.json
new file mode 100644
index 000000000..cc7891cdd
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Spanien",
+ "regionNames": "Spanische Autonomien"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/en.json b/packages/plugins/src/countries/Spain/i18n/en.json
new file mode 100644
index 000000000..bca67af8e
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Spain",
+ "regionNames": "Spanish Autonomies"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/es.json b/packages/plugins/src/countries/Spain/i18n/es.json
new file mode 100644
index 000000000..fc64887e2
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "España",
+ "regionNames": "Autonomías Españolas"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/fr.json b/packages/plugins/src/countries/Spain/i18n/fr.json
new file mode 100644
index 000000000..7de5cee9b
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Espagne",
+ "regionNames": "Autonomies espagnoles"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/hi.json b/packages/plugins/src/countries/Spain/i18n/hi.json
new file mode 100644
index 000000000..87804cb5b
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "स्पेन",
+ "regionNames": "स्पेनिश स्वायत्तता"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/ja.json b/packages/plugins/src/countries/Spain/i18n/ja.json
new file mode 100644
index 000000000..f31212e4f
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "スペイン",
+ "regionNames": "スペインの自治区"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/nl.json b/packages/plugins/src/countries/Spain/i18n/nl.json
new file mode 100644
index 000000000..4cb373e6d
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Spanje",
+ "regionNames": "Spaanse autonomie"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/pt.json b/packages/plugins/src/countries/Spain/i18n/pt.json
new file mode 100644
index 000000000..3d5c33c5c
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Espanha",
+ "regionNames": "Autonomias espanholas"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/ru.json b/packages/plugins/src/countries/Spain/i18n/ru.json
new file mode 100644
index 000000000..7b5d1f5b6
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Испания",
+ "regionNames": "Испанские автономии"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/ta.json b/packages/plugins/src/countries/Spain/i18n/ta.json
new file mode 100644
index 000000000..056799e54
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ஸ்பெயின்",
+ "regionNames": "ஸ்பானிஷ் தன்னாட்சிகள்"
+}
diff --git a/packages/plugins/src/countries/Spain/i18n/zh.json b/packages/plugins/src/countries/Spain/i18n/zh.json
new file mode 100644
index 000000000..3490719cf
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "西班牙",
+ "regionNames": "西班牙自治"
+}
diff --git a/packages/plugins/src/countries/Spain/names.ts b/packages/plugins/src/countries/Spain/names.ts
new file mode 100644
index 000000000..cfa31e780
--- /dev/null
+++ b/packages/plugins/src/countries/Spain/names.ts
@@ -0,0 +1,530 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Adriana',
+ 'Agustina',
+ 'Aida',
+ 'Aldea',
+ 'Alejandra',
+ 'Alicia',
+ 'Alma',
+ 'Almudena',
+ 'Amalia',
+ 'Amaya',
+ 'Amparo',
+ 'Ana',
+ 'Anabel',
+ 'Andrea',
+ 'Andreina',
+ 'Anita',
+ 'Antonella',
+ 'Antonia',
+ 'Araceli',
+ 'Aurora',
+ 'Beatriz',
+ 'Bettina',
+ 'Bianca',
+ 'Blanca',
+ 'Blanche',
+ 'Bonita',
+ 'Cari',
+ 'Carina',
+ 'Carla',
+ 'Carlota',
+ 'Carlota',
+ 'Carlotta',
+ 'Carmelita',
+ 'Carolina',
+ 'Catalina',
+ 'Celia',
+ 'Chara',
+ 'Christina',
+ 'Claudia',
+ 'Concepción',
+ 'Conchita',
+ 'Consuelo',
+ 'Cristina',
+ 'Cynthia',
+ 'Delia',
+ 'Diana',
+ 'Dolores',
+ 'Dora',
+ 'Elena',
+ 'Eliana',
+ 'Elisa',
+ 'Elvira',
+ 'Erika',
+ 'Esperanza',
+ 'Ester',
+ 'Eugenia',
+ 'Eva',
+ 'Fatima',
+ 'Fernanda',
+ 'Fiorella',
+ 'Flavia',
+ 'Flora',
+ 'Gabriela',
+ 'Garbiñe',
+ 'Gisela',
+ 'Gloria',
+ 'Guiomar',
+ 'Helena',
+ 'Hilda',
+ 'Ignacia',
+ 'Ileana',
+ 'Ilona',
+ 'Imelda',
+ 'Ines',
+ 'Inez',
+ 'Irene',
+ 'Iris',
+ 'Isa',
+ 'Isabel',
+ 'Isabella',
+ 'Ivette',
+ 'Ivonne',
+ 'Jacin',
+ 'Jacqueline',
+ 'Javiera',
+ 'Joaquina',
+ 'Josefa',
+ 'Josefina',
+ 'Juana',
+ 'Julia',
+ 'Justina',
+ 'Karina',
+ 'Lana',
+ 'Lara',
+ 'Laura',
+ 'Lena',
+ 'Lidia',
+ 'Liliana',
+ 'Lolita',
+ 'Lorena',
+ 'Luciana',
+ 'Luisa',
+ 'Lupe',
+ 'Lupita',
+ 'Luz',
+ 'Macarena',
+ 'Magdalena',
+ 'Manuela',
+ 'Marcela',
+ 'Margarita',
+ 'Maria',
+ 'María',
+ 'María',
+ 'María',
+ 'Mariana',
+ 'Mariela',
+ 'Marina',
+ 'Marisa',
+ 'Marisela',
+ 'Marisol',
+ 'Marta',
+ 'Martina',
+ 'Maru',
+ 'Maruja',
+ 'Maura',
+ 'Maya',
+ 'Melania',
+ 'Melina',
+ 'Mercedes',
+ 'Merilyn',
+ 'Milagros',
+ 'Milena',
+ 'Millaray',
+ 'Mirta',
+ 'Morena',
+ 'Nadia',
+ 'Natalia',
+ 'Nazaret',
+ 'Nena',
+ 'Noelia',
+ 'Núria',
+ 'Olga',
+ 'Paloma',
+ 'Paula',
+ 'Paulina',
+ 'Pierina',
+ 'Pilar',
+ 'Purita',
+ 'Ramona',
+ 'Raquel',
+ 'Rita',
+ 'Rocío',
+ 'Rosa',
+ 'Rosalia',
+ 'Rosario',
+ 'Salma',
+ 'Samara',
+ 'Sandra',
+ 'Silvia',
+ 'Silvina',
+ 'Simone',
+ 'Soledad',
+ 'Sonia',
+ 'Sophia',
+ 'Soraya',
+ 'Tamara',
+ 'Tara',
+ 'Tatiana',
+ 'Teresa',
+ 'Tina',
+ 'Tomasa',
+ 'Tona',
+ 'Tonia',
+ 'Urraca',
+ 'Valentina',
+ 'Valeria',
+ 'Veronica',
+ 'Victoria',
+ 'Vilma',
+ 'Viola',
+ 'Violeta',
+ 'Virginia',
+ 'Viridiana',
+ 'Xiomara',
+ 'Xóchitl',
+ 'Yanet',
+ 'Yazmin',
+ 'Zulema'
+];
+
+const maleNames = [
+ 'Aarón',
+ 'Abel',
+ 'Abelardo',
+ 'Abraham',
+ 'Adan',
+ 'Ademar',
+ 'Adrià',
+ 'Adriano',
+ 'Agustín',
+ 'Alberto',
+ 'Alejandro',
+ 'Alfredo',
+ 'Alonso',
+ 'Alphons',
+ 'Álvaro',
+ 'Amílcar',
+ 'Angel',
+ 'José',
+ 'Aníbal',
+ 'Antero',
+ 'Antonin',
+ 'Antonio',
+ 'Armando',
+ 'Arnaldo',
+ 'Arnulfo',
+ 'Arsenio',
+ 'Arturo',
+ 'Augusto',
+ 'Aznar',
+ 'Benito',
+ 'Bernardo',
+ 'Beto',
+ 'Bruno',
+ 'Carlito',
+ 'Carlo',
+ 'Carlos',
+ 'Carlos',
+ 'Carlos',
+ 'Carmelo',
+ 'Cayetano',
+ 'Cesar',
+ 'Charli',
+ 'Cipriano',
+ 'Cristhian',
+ 'Cristóbal',
+ 'Cuauhtémoc',
+ 'Danilo',
+ 'List',
+ 'Dámaso',
+ 'Damián',
+ 'Daniel',
+ 'Dario',
+ 'David',
+ 'Diego',
+ 'Edgardo',
+ 'Eduardo',
+ 'Efraín',
+ 'Elbio',
+ 'Eliseo',
+ 'Emilio',
+ 'Enrique',
+ 'Ernesto',
+ 'Esteban',
+ 'Eugenio',
+ 'Ezequiel',
+ 'Fabricio',
+ 'Fausto',
+ 'Federico',
+ 'Feliciano',
+ 'Felipe',
+ 'Fernando',
+ 'Fernando',
+ 'Flavio',
+ 'Florencio',
+ 'Florentin',
+ 'Francisco',
+ 'Fulgencio',
+ 'Gabriel',
+ 'Gaspar',
+ 'Genovevo',
+ 'Geronimo',
+ 'Gilberto',
+ 'Ginés',
+ 'Gonzalo',
+ 'Gregorio',
+ 'Guido',
+ 'Guillermo',
+ 'Gustavo',
+ 'Gutierre',
+ 'Hector',
+ 'Heriberto',
+ 'Hernán',
+ 'Hernándo',
+ 'Hugo',
+ 'Ignacio',
+ 'Inigo',
+ 'Jacinto',
+ 'Jaime',
+ 'Jairo',
+ 'Javier',
+ 'Jerónimo',
+ 'Jhon',
+ 'Jimeno',
+ 'Joaquín',
+ 'Jorge',
+ 'José',
+ 'José',
+ 'José',
+ 'José',
+ 'José',
+ 'Juan',
+ 'Juan',
+ 'Juanfran',
+ 'Juanma',
+ 'Julián',
+ 'Julio',
+ 'Julio',
+ 'Jusepe',
+ 'Leandro',
+ 'Leon',
+ 'Leonardo',
+ 'Leopoldo',
+ 'Liberato',
+ 'Liberto',
+ 'Lorenzo',
+ 'Lotario',
+ 'Luca',
+ 'Lucero',
+ 'Luciano',
+ 'Lucio',
+ 'Luis',
+ 'Maikel',
+ 'Manolito',
+ 'Manolo',
+ 'Manrique',
+ 'Manuel',
+ 'José',
+ 'Marcelino',
+ 'Marcelo',
+ 'Marco',
+ 'Marcos',
+ 'Mariano',
+ 'Mario',
+ 'Mateo',
+ 'Matías',
+ 'Mauricio',
+ 'Maximiliano',
+ 'Melquíades',
+ 'Miguel',
+ 'Miguel',
+ 'Milo',
+ 'Nacho',
+ 'Nahuel',
+ 'Napoleon',
+ 'Narciso',
+ 'Nazario',
+ 'Nemesio',
+ 'Nestor',
+ 'Nicolas',
+ 'Niño',
+ 'Noe',
+ 'Nuño',
+ 'Obdulio',
+ 'Omar',
+ 'Osorio',
+ 'Osvaldo',
+ 'Oswaldo',
+ 'Pablo',
+ 'Paco',
+ 'Panfilo',
+ 'Pascual',
+ 'Pasqual',
+ 'Patricio',
+ 'Pedro',
+ 'Pepe',
+ 'Perez',
+ 'Primitivo',
+ 'Quique',
+ 'Rafa',
+ 'Ramiro',
+ 'Ramón',
+ 'Raphael',
+ 'Raul',
+ 'Reinaldo',
+ 'Reno',
+ 'Ricardo',
+ 'Roberto',
+ 'Rodolfo',
+ 'Rodrigo',
+ 'Rogelio',
+ 'Rolando',
+ 'Roman',
+ 'Ronaldo',
+ 'Rosendo',
+ 'Salvador',
+ 'Samuel',
+ 'Santiago',
+ 'Sebastian',
+ 'Servando',
+ 'Silvestre',
+ 'Sixto',
+ 'Suero',
+ 'Tato',
+ 'Tomás',
+ 'Toni',
+ 'Tonin',
+ 'Tonino',
+ 'Tonio',
+ 'Tono',
+ 'Ulises',
+ 'Venancio',
+ 'Vicente',
+ 'Víctor',
+ 'Victorino',
+ 'Vladimiro',
+ 'Wenceslao',
+ 'Wilfredo',
+ 'Xavi'
+];
+
+const lastNames = [
+ 'Aguilar',
+ 'Alonso',
+ 'Alvarez',
+ 'Andres',
+ 'Arias',
+ 'Blanco',
+ 'Bravo',
+ 'Caballero',
+ 'Cabrera',
+ 'Calvo',
+ 'Campos',
+ 'Cano',
+ 'Carmona',
+ 'Carrasco',
+ 'Casado',
+ 'Castillo',
+ 'Castro',
+ 'Cortes',
+ 'Crespo',
+ 'Cruz',
+ 'Delgado',
+ 'Diaz',
+ 'Diez',
+ 'Dominguez',
+ 'Duran',
+ 'Esteban',
+ 'Fernandez',
+ 'Ferrer',
+ 'Flores',
+ 'Fuentes',
+ 'Gallego',
+ 'Garcia',
+ 'Garrido',
+ 'Gil',
+ 'Gimenez',
+ 'Gomez',
+ 'Gonzalez',
+ 'Guerrero',
+ 'Gutierrez',
+ 'Hernandez',
+ 'Herrera',
+ 'Herrero',
+ 'Hidalgo',
+ 'Iba',
+ 'Ibañez',
+ 'Iglesias',
+ 'Izquierdo',
+ 'Jimenez',
+ 'Leon',
+ 'Lopez',
+ 'Lorenzo',
+ 'Lozano',
+ 'Marin',
+ 'Marquez',
+ 'Marti',
+ 'Martin',
+ 'Martinez',
+ 'Medina',
+ 'Mendez',
+ 'Merino',
+ 'Miguel',
+ 'Molina',
+ 'Montero',
+ 'Mora',
+ 'Morales',
+ 'Moreno',
+ 'Moya',
+ 'Muñoz',
+ 'Muムoz',
+ 'Navarro',
+ 'Nieto',
+ 'Nuñez',
+ 'Nuムez',
+ 'Ortega',
+ 'Ortiz',
+ 'Pardo',
+ 'Pascual',
+ 'Pastor',
+ 'Perez',
+ 'Peña',
+ 'Prieto',
+ 'Ramirez',
+ 'Ramos',
+ 'Redondo',
+ 'Reyes',
+ 'Rodriguez',
+ 'Romero',
+ 'Rubio',
+ 'Ruiz',
+ 'Saez',
+ 'Sanchez',
+ 'Santana',
+ 'Santos',
+ 'Sanz',
+ 'Serrano',
+ 'Soler',
+ 'Soto',
+ 'Suarez',
+ 'Torres',
+ 'Vazquez',
+ 'Vega',
+ 'Velasco',
+ 'Vicente',
+ 'Vidal',
+ 'Vila',
+ 'ムez'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Sweden/bundle.ts b/packages/plugins/src/countries/Sweden/bundle.ts
new file mode 100644
index 000000000..c3c27d616
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/bundle.ts
@@ -0,0 +1,59 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Sweden: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'sweden',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ format: 'xxxxx'
+ }
+ },
+ regions: [
+ {
+ regionName: 'Gävleborgs län',
+ regionShort: 'X',
+ regionSlug: 'gavleborg',
+ weight: 1,
+ cities: ['Gävle', 'Sandviken', 'Hudiksvall', 'Bollnäs', 'Söderhamn', 'Hofors', 'Ockelbo']
+ },
+ {
+ regionName: 'Dalarnas län',
+ regionShort: 'W',
+ regionSlug: 'dalarna',
+ weight: 1,
+ cities: ['Borlänge', 'Falun', 'Avesta', 'Ludvika', 'Mora']
+ },
+ {
+ regionName: 'Stockholms län',
+ regionShort: 'AB',
+ regionSlug: 'stockholms_lan',
+ weight: 5,
+ cities: ['Stockholm', 'Södertälje', 'Täby', 'Tumba', 'Upplands Väsby', 'Lidingo', 'Vallentuna', 'Åkersberga', 'Märsta', 'Boo']
+ },
+ {
+ regionName: 'Västra Götalands län',
+ regionShort: 'O',
+ regionSlug: 'vastra_gotaland',
+ weight: 4,
+ cities: ['Göteborg', 'Borås', 'Trollhättan', 'Skövde', 'Uddevalla', 'Lidköping', 'Alingsås', 'Kungälv', 'Vänersborg', 'Lerum']
+ },
+ {
+ regionName: 'Östergötlands län',
+ regionShort: 'E',
+ regionSlug: 'ostergotland',
+ weight: 2,
+ cities: ['Linköping', 'Norrköping', 'Motala', 'Finspång', 'Mjölby']
+ },
+ {
+ regionName: 'Jönköpings län',
+ regionShort: 'F',
+ regionSlug: 'jonkoping',
+ weight: 2,
+ cities: ['Jönköping', 'Värnamo', 'Nässjö', 'Tranås', 'Vetlanda']
+ }
+ ]
+});
+
+export default Sweden;
diff --git a/packages/plugins/src/countries/Sweden/i18n/ar.json b/packages/plugins/src/countries/Sweden/i18n/ar.json
new file mode 100644
index 000000000..6bb63b1fa
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "السويد",
+ "regionNames": "المقاطعات السويدية"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/de.json b/packages/plugins/src/countries/Sweden/i18n/de.json
new file mode 100644
index 000000000..9d079e462
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Schweden",
+ "regionNames": "Schwedische Landkreise"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/en.json b/packages/plugins/src/countries/Sweden/i18n/en.json
new file mode 100644
index 000000000..77be1ca16
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Sweden",
+ "regionNames": "Swedish counties"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/es.json b/packages/plugins/src/countries/Sweden/i18n/es.json
new file mode 100644
index 000000000..8d7ebc62b
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Suecia",
+ "regionNames": "condados suecos"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/fr.json b/packages/plugins/src/countries/Sweden/i18n/fr.json
new file mode 100644
index 000000000..c562f7698
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Suède",
+ "regionNames": "Comtés suédois"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/hi.json b/packages/plugins/src/countries/Sweden/i18n/hi.json
new file mode 100644
index 000000000..551055c40
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "स्वीडन",
+ "regionNames": "स्वीडिश काउंटी"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/ja.json b/packages/plugins/src/countries/Sweden/i18n/ja.json
new file mode 100644
index 000000000..4bf2b35c2
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "スウェーデン",
+ "regionNames": "スウェーデンの郡"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/nl.json b/packages/plugins/src/countries/Sweden/i18n/nl.json
new file mode 100644
index 000000000..4e924738c
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Zweden",
+ "regionNames": "Zweedse provincies"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/pt.json b/packages/plugins/src/countries/Sweden/i18n/pt.json
new file mode 100644
index 000000000..cb56399e6
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Suécia",
+ "regionNames": "Condados suecos"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/ru.json b/packages/plugins/src/countries/Sweden/i18n/ru.json
new file mode 100644
index 000000000..78a7d3139
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/ru.json
@@ -0,0 +1,5 @@
+
+{
+ "countryName": "Швеция",
+ "regionNames": "Шведские округа"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/ta.json b/packages/plugins/src/countries/Sweden/i18n/ta.json
new file mode 100644
index 000000000..bc6d6d742
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ஸ்வீடன்",
+ "regionNames": "ஸ்வீடிஷ் மாவட்டங்கள்"
+}
diff --git a/packages/plugins/src/countries/Sweden/i18n/zh.json b/packages/plugins/src/countries/Sweden/i18n/zh.json
new file mode 100644
index 000000000..1c93b21de
--- /dev/null
+++ b/packages/plugins/src/countries/Sweden/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "瑞典",
+ "regionNames": "瑞典县"
+}
diff --git a/packages/plugins/src/countries/Turkey/bundle.ts b/packages/plugins/src/countries/Turkey/bundle.ts
new file mode 100644
index 000000000..69a91769c
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/bundle.ts
@@ -0,0 +1,171 @@
+/**
+ * Turkey provinces with population >= 1 million, plus the five largest city in each.
+ * Sources:
+ * http://en.wikipedia.org/wiki/Provinces_of_Turkey
+ * http://en.wikipedia.org/wiki/List_of_cities_in_Turkey
+ *
+ * Note: Turkey doesn't appear to have short codes for the provinces, so this just sets them all to the full name.
+ */
+import { GetCountryData } from '@generatedata/types';
+
+const Turkey: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'turkey',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+
+ extendedData: {
+ zipFormat: {
+ format: 'xyyyy',
+ replacements: {
+ x: '01234567',
+ y: '0123456789'
+ }
+ }
+ },
+
+ regions: [
+ {
+ regionName: 'Istanbul',
+ regionShort: 'Istanbul',
+ regionSlug: 'istanbul',
+ weight: 14,
+ cities: ['Istanbul']
+ },
+ {
+ regionName: 'Ankara',
+ regionShort: 'Ankara',
+ regionSlug: 'ankara',
+ weight: 5,
+ cities: ['Ankara', 'Polatlı', 'Beypazarı', 'Şereflikoçhisar', 'Kızılcahamam']
+ },
+ {
+ regionName: 'İzmir',
+ regionShort: 'İzmir',
+ regionSlug: 'izmir',
+ weight: 4,
+ cities: ['Izmir', 'Ödemiş', 'Bergama', 'Tire', 'Çeşme']
+ },
+ {
+ regionName: 'Bursa',
+ regionShort: 'Bursa',
+ regionSlug: 'bursa',
+ weight: 3,
+ cities: ['Bursa', 'İnegöl', 'Mustafakemalpaşa', 'Orhangazi', 'Karacabey']
+ },
+ {
+ regionName: 'Antalya',
+ regionShort: 'Antalya',
+ regionSlug: 'antalya',
+ weight: 2,
+ cities: ['Antalya', 'Alanya', 'Manavgat', 'Serik', 'Kumluca']
+ },
+ {
+ regionName: 'Adana',
+ regionShort: 'Adana',
+ regionSlug: 'adana',
+ weight: 2,
+ cities: ['Adana', 'Ceyhan', 'Kozan', 'İmamoğlu', 'Pozantı']
+ },
+ {
+ regionName: 'Konya',
+ regionShort: 'Konya',
+ regionSlug: 'konya',
+ weight: 2,
+ cities: ['Konya', 'Ereğli', 'Akşehir', 'Seydişehir', 'Karapınar']
+ },
+ {
+ regionName: 'Gaziantep',
+ regionShort: 'Gaziantep',
+ regionSlug: 'gaziantep',
+ weight: 2,
+ cities: ['Gaziantep', 'Nizip', 'İslahiye', 'Nurdağı', 'Araban']
+ },
+ {
+ regionName: 'Şanlıurfa',
+ regionShort: 'Şanlıurfa',
+ regionSlug: 'sanliurfa',
+ weight: 2,
+ cities: ['Şanlıurfa', 'Siverek', 'Viranşehir', 'Suruç', 'Birecik']
+ },
+ {
+ regionName: 'Mersin',
+ regionShort: 'Mersin',
+ regionSlug: 'mersin',
+ weight: 2,
+ cities: ['Mersin', 'Tarsus', 'Silifke', 'Erdemli', 'Anamur']
+ },
+ {
+ regionName: 'Kocaeli',
+ regionShort: 'Kocaeli',
+ regionSlug: 'kocaeli',
+ weight: 2,
+ cities: ['İzmit', 'Gebze', 'Darıca', 'Gölcük', 'Körfez']
+ },
+ {
+ regionName: 'Diyarbakır',
+ regionShort: 'Diyarbakır',
+ regionSlug: 'diyarbakir',
+ weight: 2,
+ cities: ['Diyarbakır', 'Ergani', 'Bismil', 'Silvan', 'Çermik']
+ },
+ {
+ regionName: 'Hatay',
+ regionShort: 'Hatay',
+ regionSlug: 'hatay',
+ weight: 2,
+ cities: ['Antakya', 'İskenderun', 'Dörtyol', 'Kırıkhan', 'Reyhanlı']
+ },
+ {
+ regionName: 'Manisa',
+ regionShort: 'Manisa',
+ regionSlug: 'manisa',
+ weight: 1,
+ cities: ['Manisa', 'Turgutlu', 'Akhisar', 'Salihli', 'Soma']
+ },
+ {
+ regionName: 'Kayseri',
+ regionShort: 'Kayseri',
+ regionSlug: 'kayseri',
+ weight: 1,
+ cities: ['Kayseri', 'Develi', 'Yahyalı', 'Bünyan', 'Pınarbaşı']
+ },
+ {
+ regionName: 'Samsun',
+ regionShort: 'Samsun',
+ regionSlug: 'samsun',
+ weight: 1,
+ cities: ['Samsun', 'Bafra', 'Çarşamba', 'Terme', 'Vezirköprü']
+ },
+ {
+ regionName: 'Balıkesir',
+ regionShort: 'Balıkesir',
+ regionSlug: 'balikesir',
+ weight: 1,
+ cities: ['Balıkesir', 'Bandırma', 'Edremit', 'Gönen', 'Burhaniye']
+ },
+ {
+ regionName: 'Kahramanmaraş',
+ regionShort: 'Kahramanmaraş',
+ regionSlug: 'kahramanmaras',
+ weight: 1,
+ cities: ['Kahramanmaraş', 'Elbistan', 'Afşin', 'Pazarcık', 'Göksun']
+ },
+ {
+ regionName: 'Van',
+ regionShort: 'Van',
+ regionSlug: 'van',
+ weight: 1,
+ cities: ['Van', 'Erciş', 'Bostaniçi', 'Muradiye', 'Çaldıran']
+ },
+ {
+ regionName: 'Aydın',
+ regionShort: 'Aydın',
+ regionSlug: 'aydin',
+ weight: 1,
+ cities: ['Aydın', 'Nazilli', 'Söke', 'Kuşadası', 'Didim']
+ }
+ ]
+});
+
+export default Turkey;
diff --git a/packages/plugins/src/countries/Turkey/i18n/ar.json b/packages/plugins/src/countries/Turkey/i18n/ar.json
new file mode 100644
index 000000000..256b4f917
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ديك رومى",
+ "regionNames": "محافظات تركيا"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/de.json b/packages/plugins/src/countries/Turkey/i18n/de.json
new file mode 100644
index 000000000..5d6b59fec
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Truthahn",
+ "regionNames": "Provinzen der Türkei"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/en.json b/packages/plugins/src/countries/Turkey/i18n/en.json
new file mode 100644
index 000000000..7e5cd83ef
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Turkey",
+ "regionNames": "Turkey prov."
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/es.json b/packages/plugins/src/countries/Turkey/i18n/es.json
new file mode 100644
index 000000000..bc28b02ab
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Turquía",
+ "regionNames": "Provincias de Turquía"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/fr.json b/packages/plugins/src/countries/Turkey/i18n/fr.json
new file mode 100644
index 000000000..8a4ecc90c
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Turquie",
+ "regionNames": "Provinces de Turquie"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/hi.json b/packages/plugins/src/countries/Turkey/i18n/hi.json
new file mode 100644
index 000000000..96e339eea
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "पूर्व के पास",
+ "regionNames": "तुर्की प्रांत"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/ja.json b/packages/plugins/src/countries/Turkey/i18n/ja.json
new file mode 100644
index 000000000..cc6ad490e
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "トルコの国",
+ "regionNames": "トルコの地方"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/nl.json b/packages/plugins/src/countries/Turkey/i18n/nl.json
new file mode 100644
index 000000000..1b97c7389
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Turkije",
+ "regionNames": "turkije provincies"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/pt.json b/packages/plugins/src/countries/Turkey/i18n/pt.json
new file mode 100644
index 000000000..e8ced47bf
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Turquia",
+ "regionNames": "Províncias da Turquia"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/ru.json b/packages/plugins/src/countries/Turkey/i18n/ru.json
new file mode 100644
index 000000000..3d077a4ca
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Турция",
+ "regionNames": "Турция пров."
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/ta.json b/packages/plugins/src/countries/Turkey/i18n/ta.json
new file mode 100644
index 000000000..f8b68314f
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "துருக்கி",
+ "regionNames": "துருக்கி மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Turkey/i18n/zh.json b/packages/plugins/src/countries/Turkey/i18n/zh.json
new file mode 100644
index 000000000..18c75ee37
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "土耳其",
+ "regionNames": "土耳其省份"
+}
diff --git a/packages/plugins/src/countries/Turkey/names.ts b/packages/plugins/src/countries/Turkey/names.ts
new file mode 100644
index 000000000..771ea78fd
--- /dev/null
+++ b/packages/plugins/src/countries/Turkey/names.ts
@@ -0,0 +1,529 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Aynur',
+ 'Aysel',
+ 'Ayşe',
+ 'Ayşegül',
+ 'Ayşen',
+ 'Ayşenaz',
+ 'Ayşenur',
+ 'Başak',
+ 'Beyza',
+ 'Buket',
+ 'Büşra',
+ 'Canan',
+ 'Candan',
+ 'Cansel',
+ 'Cansu',
+ 'Ceren',
+ 'Ceylan',
+ 'Defne',
+ 'Dilek',
+ 'Ebru',
+ 'Eda',
+ 'Elif',
+ 'Elmas',
+ 'Emine',
+ 'Esra',
+ 'Fadime',
+ 'Fi̇li̇z',
+ 'Gökçe',
+ 'Gül',
+ 'Gülay',
+ 'Gülbahar',
+ 'Gülcan',
+ 'Güler',
+ 'Güllü',
+ 'Gülseren',
+ 'Gülsüm',
+ 'Gülten',
+ 'Gülşen',
+ 'Hacer',
+ 'Hanife',
+ 'Hatice',
+ 'Havva',
+ 'Hülya',
+ 'İlknur',
+ 'Kardelen',
+ 'Kübra',
+ 'Lale',
+ 'Leyla',
+ 'Manolya',
+ 'Melek',
+ 'Melike',
+ 'Meli̇sa',
+ 'Menekşe',
+ 'Meral',
+ 'Merve',
+ 'Meryem',
+ 'Müge',
+ 'Naz',
+ 'Nazan',
+ 'Nazar',
+ 'Nazi̇fe',
+ 'Nazi̇k',
+ 'Nazi̇me',
+ 'Nazi̇re',
+ 'Nazli',
+ 'Nazlican',
+ 'Nazmi̇ye',
+ 'Nida',
+ 'Nisa',
+ 'Nur',
+ 'Nuran',
+ 'Nuray',
+ 'Nurcan',
+ 'Nurdan',
+ 'Nurgül',
+ 'Nurhan',
+ 'Nuriye',
+ 'Nursel',
+ 'Nurten',
+ 'Rabia',
+ 'Semra',
+ 'Sevim',
+ 'Sevi̇',
+ 'Sevi̇lay',
+ 'Sevi̇m',
+ 'Sevi̇n',
+ 'Sevi̇nç',
+ 'Songül',
+ 'Su',
+ 'Sude',
+ 'Sudenaz',
+ 'Sudenur',
+ 'Sultan',
+ 'Suna',
+ 'Sunay',
+ 'Suzan',
+ 'Sümeyra',
+ 'Tuğba',
+ 'Yasemin',
+ 'Zehra',
+ 'Zeynep',
+ 'Çağla',
+ 'Çiçek',
+ 'Özlem',
+ 'Ümmü',
+ 'Ümmügülsüm',
+ 'Şenay',
+ 'Şenel',
+ 'Şengül',
+ 'Şeni̇z',
+ 'Şennur',
+ 'Şerife',
+ 'Fatma'
+];
+
+const maleNames = [
+ 'Abdullah',
+ 'Abuzer',
+ 'Adem',
+ 'Ahmet',
+ 'Ali̇',
+ 'Ali̇can',
+ 'Ali̇han',
+ 'Ali̇m',
+ 'Ali̇şan',
+ 'Alp',
+ 'Alparslan',
+ 'Alpay',
+ 'Alpcan',
+ 'Alper',
+ 'Alperen',
+ 'Alphan',
+ 'Alpteki̇n',
+ 'Alptuğ',
+ 'Ata',
+ 'Ataberk',
+ 'Atabey',
+ 'Atacan',
+ 'Atahan',
+ 'Atakan',
+ 'Atalay',
+ 'Ataman',
+ 'Atay',
+ 'Azi̇z',
+ 'Bedirhan',
+ 'Berk',
+ 'Berkan',
+ 'Berkant',
+ 'Berkay',
+ 'Berkcan',
+ 'Berke',
+ 'Berkehan',
+ 'Berker',
+ 'Berki̇n',
+ 'Burak',
+ 'Can',
+ 'Canberk',
+ 'Caner',
+ 'Cankat',
+ 'Cem',
+ 'Cemal',
+ 'Cemaletti̇n',
+ 'Cemali̇',
+ 'Cemi̇l',
+ 'Cemre',
+ 'Efe',
+ 'Emirhan',
+ 'Emi̇n',
+ 'Emre',
+ 'Enes',
+ 'Ercan',
+ 'Eren',
+ 'Ergun',
+ 'Fati̇h',
+ 'Furkan',
+ 'Gürsel',
+ 'Hakan',
+ 'Hali̇l',
+ 'Hami̇t',
+ 'Hasan',
+ 'Hüseyi̇n',
+ 'İbrahi̇m',
+ 'İhsan',
+ 'İsmai̇L',
+ 'İsmai̇l',
+ 'Kadi̇r',
+ 'Kemal',
+ 'Keri̇m',
+ 'Kubilay',
+ 'Mahmut',
+ 'Mehmet',
+ 'Menderes',
+ 'Mert',
+ 'Murat',
+ 'Mustafa',
+ 'Nur',
+ 'Nureddi̇n',
+ 'Nuretti̇n',
+ 'Nuri̇',
+ 'Nurullah',
+ 'Olcay',
+ 'Osman',
+ 'Poyraz',
+ 'Ramazan',
+ 'Recep',
+ 'Rıza',
+ 'Sali̇h',
+ 'Samet',
+ 'Serhat',
+ 'Serkan',
+ 'Sonat',
+ 'Süleyman',
+ 'Tayfun',
+ 'Temel',
+ 'Volkan',
+ 'Yusuf',
+ 'Ömer',
+ 'Şahi̇n',
+ 'Mehmet'
+];
+
+const lastNames = [
+ 'Acar',
+ 'Adıgüzel',
+ 'Ak',
+ 'Akar',
+ 'Akay',
+ 'Akbaş',
+ 'Akbulut',
+ 'Akdağ',
+ 'Akdemir',
+ 'Akdeniz',
+ 'Akdoğan',
+ 'Akgül',
+ 'Akgün',
+ 'Akkaya',
+ 'Akkuş',
+ 'Akman',
+ 'Akpınar',
+ 'Aksoy',
+ 'Aksu',
+ 'Aktaş',
+ 'Akyol',
+ 'Akyüz',
+ 'Akça',
+ 'Akçay',
+ 'Akın',
+ 'Albayrak',
+ 'Alkan',
+ 'Altay',
+ 'Altun',
+ 'Altuntaş',
+ 'Altın',
+ 'Altıntaş',
+ 'Aras',
+ 'Arslan',
+ 'Arı',
+ 'Arıkan',
+ 'Aslan',
+ 'Atalay',
+ 'Ataş',
+ 'Ateş',
+ 'Atmaca',
+ 'Avcı',
+ 'Ay',
+ 'Ayaz',
+ 'Aydemir',
+ 'Aydoğan',
+ 'Aydoğdu',
+ 'Aydın',
+ 'Aygün',
+ 'Ayhan',
+ 'Açıkgöz',
+ 'Bakır',
+ 'Bal',
+ 'Balcı',
+ 'Baran',
+ 'Bayrak',
+ 'Bayraktar',
+ 'Bayram',
+ 'Baş',
+ 'Başaran',
+ 'Bektaş',
+ 'Bilgin',
+ 'Bingöl',
+ 'Biçer',
+ 'Bolat',
+ 'Boz',
+ 'Bozkurt',
+ 'Budak',
+ 'Bulut',
+ 'Bülbül',
+ 'Can',
+ 'Cengiz',
+ 'Ceylan',
+ 'Coşkun',
+ 'Dağ',
+ 'Demir',
+ 'Demirbaş',
+ 'Demircan',
+ 'Demirci',
+ 'Demirel',
+ 'Demirtaş',
+ 'Deniz',
+ 'Dinç',
+ 'Dinçer',
+ 'Doğan',
+ 'Doğru',
+ 'Duman',
+ 'Durak',
+ 'Duran',
+ 'Durmaz',
+ 'Durmuş',
+ 'Dursun',
+ 'Dönmez',
+ 'Dündar',
+ 'Efe',
+ 'Eker',
+ 'Ekici',
+ 'Ekinci',
+ 'Er',
+ 'Ercan',
+ 'Erdem',
+ 'Erdoğan',
+ 'Eren',
+ 'Ergin',
+ 'Ergün',
+ 'Erkan',
+ 'Erol',
+ 'Eroğlu',
+ 'Ersoy',
+ 'Ertaş',
+ 'Ertürk',
+ 'Esen',
+ 'Eser',
+ 'Fidan',
+ 'Fırat',
+ 'Gedik',
+ 'Genç',
+ 'Gezer',
+ 'Gök',
+ 'Göktaş',
+ 'Gökçe',
+ 'Gül',
+ 'Güler',
+ 'Güleç',
+ 'Gültekin',
+ 'Gümüş',
+ 'Gün',
+ 'Günay',
+ 'Gündoğdu',
+ 'Gündüz',
+ 'Güner',
+ 'Güney',
+ 'Güneş',
+ 'Güngör',
+ 'Gür',
+ 'Gürbüz',
+ 'Gürsoy',
+ 'Güven',
+ 'Güzel',
+ 'Güçlü',
+ 'Işık',
+ 'Kahraman',
+ 'Kalkan',
+ 'Kandemir',
+ 'Kaplan',
+ 'Kara',
+ 'Karabulut',
+ 'Karaca',
+ 'Karadağ',
+ 'Karadeniz',
+ 'Karagöz',
+ 'Karahan',
+ 'Karakaya',
+ 'Karakaş',
+ 'Karakoç',
+ 'Karakuş',
+ 'Karaman',
+ 'Karataş',
+ 'Kartal',
+ 'Kaya',
+ 'Kaçar',
+ 'Keleş',
+ 'Keskin',
+ 'Koca',
+ 'Korkmaz',
+ 'Koyuncu',
+ 'Koç',
+ 'Koçak',
+ 'Kurt',
+ 'Kuru',
+ 'Kutlu',
+ 'Kuş',
+ 'Köksal',
+ 'Köse',
+ 'Küçük',
+ 'Kılınç',
+ 'Kılıç',
+ 'Mert',
+ 'Metin',
+ 'Mutlu',
+ 'Oral',
+ 'Orhan',
+ 'Oruç',
+ 'Oğuz',
+ 'Parlak',
+ 'Pehlivan',
+ 'Polat',
+ 'Sarı',
+ 'Sarıkaya',
+ 'Savaş',
+ 'Sağlam',
+ 'Sert',
+ 'Sevim',
+ 'Sevinç',
+ 'Sezer',
+ 'Sezgin',
+ 'Soylu',
+ 'Sönmez',
+ 'Tan',
+ 'Taş',
+ 'Taşdemir',
+ 'Taşkın',
+ 'Taşçi',
+ 'Tekin',
+ 'Temel',
+ 'Topal',
+ 'Toprak',
+ 'Topçu',
+ 'Torun',
+ 'Tosun',
+ 'Tuna',
+ 'Tuncer',
+ 'Tunç',
+ 'Turan',
+ 'Turgut',
+ 'Turhan',
+ 'Türk',
+ 'Türkmen',
+ 'Türkoğlu',
+ 'Uslu',
+ 'Usta',
+ 'Uyar',
+ 'Uysal',
+ 'Uzun',
+ 'Uçar',
+ 'Uğur',
+ 'Uğurlu',
+ 'Varol',
+ 'Vural',
+ 'Yalçın',
+ 'Yalçınkaya',
+ 'Yaman',
+ 'Yavuz',
+ 'Yazici',
+ 'Yaşar',
+ 'Yeşil',
+ 'Yiğit',
+ 'Yüce',
+ 'Yücel',
+ 'Yüksel',
+ 'Yıldırım',
+ 'Yıldız',
+ 'Zengin',
+ 'Çakar',
+ 'Çakmak',
+ 'Çakır',
+ 'Çalışkan',
+ 'Çam',
+ 'Çağlar',
+ 'Çelebi',
+ 'Çelik',
+ 'Çetin',
+ 'Çetinkaya',
+ 'Çevik',
+ 'Çiftçi',
+ 'Çimen',
+ 'Çiçek',
+ 'Çoban',
+ 'Çolak',
+ 'Çınar',
+ 'Öksüz',
+ 'Ölmez',
+ 'Önal',
+ 'Önder',
+ 'Öner',
+ 'Öz',
+ 'Özbek',
+ 'Özcan',
+ 'Özdemir',
+ 'Özden',
+ 'Özel',
+ 'Özen',
+ 'Özer',
+ 'Özkan',
+ 'Özkaya',
+ 'Özmen',
+ 'Öztürk',
+ 'Özçelik',
+ 'Ünal',
+ 'Ünlü',
+ 'Ünsal',
+ 'İlhan',
+ 'İnan',
+ 'İnce',
+ 'İpek',
+ 'Şahin',
+ 'Şanli',
+ 'Şeker',
+ 'Şen',
+ 'Şener',
+ 'Şengül',
+ 'Şenol',
+ 'Şentürk',
+ 'Şimşek',
+ 'Yılmaz'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/UK/bundle.ts b/packages/plugins/src/countries/UK/bundle.ts
new file mode 100644
index 000000000..9e800db62
--- /dev/null
+++ b/packages/plugins/src/countries/UK/bundle.ts
@@ -0,0 +1,613 @@
+import { GetCountryData } from '@generatedata/types';
+
+const UK: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'united_kingdom',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+
+ extendedData: {
+ zipFormat: {
+ format: 'Lx xLL|Lxx xLL|LxL xLL|LLx xLL|LLxx xLL|LLxL xLL'
+ },
+ phoneFormat: {
+ displayFormats: ['0xxxx xxxxxx']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Aberdeenshire',
+ regionShort: 'AB',
+ regionSlug: 'aberdeenshire',
+ weight: 1,
+ cities: ['Aberdeen', 'Peterhead', 'Fraserburgh', 'Inverurie', 'Huntley', 'Ellon', 'Turriff']
+ },
+ {
+ regionName: 'Anglesey',
+ regionShort: 'AG',
+ regionSlug: 'anglesey',
+ weight: 1,
+ cities: ['Beaumaris', 'Holyhead', 'Llangefni', 'Amlwch', 'Menai Bridge']
+ },
+ {
+ regionName: 'Angus',
+ regionShort: 'AN',
+ regionSlug: 'angus',
+ weight: 1,
+ cities: ['Forfar', 'Dundee', 'Arbroath', 'Brechin', 'Montrose', 'Carnoustie', 'Kirriemuir']
+ },
+ {
+ regionName: 'Argyllshire',
+ regionShort: 'AR',
+ regionSlug: 'argyllshire',
+ weight: 1,
+ cities: ['Inveraray', 'Oban', 'Dunoon', 'Campbeltown', 'Lochgilphead', 'Tobermory']
+ },
+ {
+ regionName: 'Ayrshire',
+ regionShort: 'AY',
+ regionSlug: 'ayrshire',
+ weight: 1,
+ cities: ['Ayr', 'Kilmarnock', 'Irvine', 'Saltcoats', 'Kilwinning', 'Largs', 'Troon', 'Cumnock']
+ },
+ {
+ regionName: 'Banffshire',
+ regionShort: 'BA',
+ regionSlug: 'banffshire',
+ weight: 1,
+ cities: ['Banff', 'Buckie', 'Keith', 'Macduff', 'Portsoy', 'Dufftown']
+ },
+ {
+ regionName: 'Bedfordshire',
+ regionShort: 'BD',
+ regionSlug: 'bedfordshire',
+ weight: 1,
+ cities: ['Bedford', 'Luton', 'Dunstable', 'Leighton Buzzard', 'Biggleswade', 'Sandy']
+ },
+ {
+ regionName: 'Berwickshire',
+ regionShort: 'BE',
+ regionSlug: 'berwickshire',
+ weight: 1,
+ cities: ['Greenlaw', 'Duns', 'Eyemouth', 'Lauder', 'Coldstream']
+ },
+ {
+ regionName: 'Buckinghamshire',
+ regionShort: 'BK',
+ regionSlug: 'buckinghamshire',
+ weight: 1,
+ cities: ['Aylesbury', 'Milton Keynes', 'Slough', 'Buckingham', 'High Wycombe']
+ },
+ {
+ regionName: 'Berkshire',
+ regionShort: 'BR',
+ regionSlug: 'berkshire',
+ weight: 1,
+ cities: ['Reading', 'Bracknell', 'Maidenhead', 'Newbury', 'Windsor', 'Wokingham', 'Abingdon']
+ },
+ {
+ regionName: 'Caithness',
+ regionShort: 'CA',
+ regionSlug: 'caithness',
+ weight: 1,
+ cities: ['Wick', 'Thurso', 'Halkirk', 'Castletown']
+ },
+ {
+ regionName: 'Cambridgeshire',
+ regionShort: 'CA',
+ regionSlug: 'cambridgeshire',
+ weight: 1,
+ cities: ['Cambridge', 'Wisbech', 'Ely', 'March', 'Whittlesey', 'Chatteris', 'Linton']
+ },
+ {
+ regionName: 'Cardiganshire',
+ regionShort: 'CG',
+ regionSlug: 'cardiganshire',
+ weight: 1,
+ cities: ['Cardigan', 'Aberystwyth', 'Lampeter', 'New Quay', 'Tregaron']
+ },
+ {
+ regionName: 'Cheshire',
+ regionShort: 'CH',
+ regionSlug: 'cheshire',
+ weight: 1,
+ cities: ['Chester', 'Stockport', 'Ellesmere Port', 'Birkenhead', 'Wallasey', 'Runcorn', 'Macclesfield', 'Crewe']
+ },
+ {
+ regionName: 'Clackmannanshire',
+ regionShort: 'CL',
+ regionSlug: 'clackmannanshire',
+ weight: 1,
+ cities: ['Clackmannan', 'Alloa', 'Tillicoultry', 'Tullibody']
+ },
+ {
+ regionName: 'Carmarthenshire',
+ regionShort: 'CM',
+ regionSlug: 'carmarthenshire',
+ weight: 1,
+ cities: ['Carmarthen', 'Llanelli', 'Ammanford', 'Llandovery', 'Kidwelly', 'St. Clears']
+ },
+ {
+ regionName: 'Cornwall',
+ regionShort: 'CO',
+ regionSlug: 'cornwall',
+ weight: 1,
+ cities: ['Bodmin', 'Truro', 'Camborne', 'Redruth', 'St. Austell', 'Falmouth', 'Penzance', 'Newquay']
+ },
+ {
+ regionName: 'Cumberland',
+ regionShort: 'CU',
+ regionSlug: 'cumberland',
+ weight: 1,
+ cities: ['Carlisle', 'Whitehaven', 'Workington', 'Penrith', 'Keswick', 'Brampton']
+ },
+ {
+ regionName: 'Derbyshire',
+ regionShort: 'DB',
+ regionSlug: 'derbyshire',
+ weight: 1,
+ cities: ['Derby', 'Chesterfield', 'Glossop', 'Ilkeston', 'Long Eaton', 'Swadlincote', 'Buxton', 'Matlock', 'Ashbourne']
+ },
+ {
+ regionName: 'Denbighshire',
+ regionShort: 'DE',
+ regionSlug: 'denbighshire',
+ weight: 1,
+ cities: ['Denbigh', 'Wrexham', 'Ruthin', 'Abergele', 'Llangollen']
+ },
+ {
+ regionName: 'Devon',
+ regionShort: 'DE',
+ regionSlug: 'devon',
+ weight: 1,
+ cities: ['Exeter', 'Plymouth', 'Torquay', 'Paignton', 'Barnstaple', 'Tiverton', 'Newton Abbot', 'Tavistock']
+ },
+ {
+ regionName: 'Dunbartonshire',
+ regionShort: 'DN',
+ regionSlug: 'dunbartonshire',
+ weight: 1,
+ cities: ['Dumbarton', 'Clydebank', 'Cumbernauld', 'Helensburgh', 'Alexandria', 'Kirkintilloch']
+ },
+ {
+ regionName: 'Dorset',
+ regionShort: 'DO',
+ regionSlug: 'dorset',
+ weight: 1,
+ cities: ['Dorchester', 'Poole', 'Weymouth', 'Sherborne', 'Wimborne Minster', 'Shaftesbury']
+ },
+ {
+ regionName: 'East Lothian',
+ regionShort: 'EL',
+ regionSlug: 'east_lothian',
+ weight: 1,
+ cities: ['Haddington', 'North Berwick', 'Dunbar', 'Tranent', 'East Linton']
+ },
+ {
+ regionName: 'Essex',
+ regionShort: 'ES',
+ regionSlug: 'essex',
+ weight: 1,
+ cities: ['Chelmsford', 'Basildon', 'Romford', 'Southend', 'Colchester', 'Harlow', 'Brentwood', 'West Ham']
+ },
+ {
+ regionName: 'Fife',
+ regionShort: 'FI',
+ regionSlug: 'fife',
+ weight: 1,
+ cities: ['Cupar', 'Dunfermline', 'Glenrothes', 'Kirkcaldy', 'St. Andrews', 'Cowdenbeath', 'Burntisland']
+ },
+ {
+ regionName: 'Flintshire',
+ regionShort: 'FL',
+ regionSlug: 'flintshire',
+ weight: 1,
+ cities: ['Mold', 'Flint', 'Rhyl', 'Prestatyn', "Connah's Quay", 'Holywell', 'Buckley', 'St. Asaph']
+ },
+ {
+ regionName: 'Glamorgan',
+ regionShort: 'GL',
+ regionSlug: 'glamorgan',
+ weight: 1,
+ cities: ['Cardiff', 'Swansea', 'Merthyr Tydfil', 'Barry', 'Caerphilly', 'Bridgend', 'Neath', 'Pontypridd']
+ },
+ {
+ regionName: 'Gloucestershire',
+ regionShort: 'GL',
+ regionSlug: 'gloucestershire',
+ weight: 1,
+ cities: ['Gloucester', 'Bristol', 'Cheltenham', 'Stroud', 'Cirencester', 'Tewkesbury']
+ },
+ {
+ regionName: 'Hampshire',
+ regionShort: 'HA',
+ regionSlug: 'hampshire',
+ weight: 1,
+ cities: ['Winchester', 'Southampton', 'Portsmouth', 'Bournemouth', 'Basingstoke', 'Newport']
+ },
+ {
+ regionName: 'Herefordshire',
+ regionShort: 'HE',
+ regionSlug: 'herefordshire',
+ weight: 1,
+ cities: ['Hereford', 'Ross-on-Wye', 'Leominster', 'Ledbury', 'Bromyard', 'Kington']
+ },
+ {
+ regionName: 'Hertfordshire',
+ regionShort: 'HR',
+ regionSlug: 'hertfordshire',
+ weight: 1,
+ cities: ['Hertford', 'Watford', 'St. Albans', 'Hemel Hempstead', 'Stevenage', 'Hatfield']
+ },
+ {
+ regionName: 'Huntingdonshire',
+ regionShort: 'HU',
+ regionSlug: 'huntingdonshire',
+ weight: 1,
+ cities: ['Huntingdon', 'St. Ives', 'St. Neots', 'Ramsey', 'Yaxley']
+ },
+ {
+ regionName: 'Inverness-shire',
+ regionShort: 'IN',
+ regionSlug: 'inverness_shire',
+ weight: 1,
+ cities: ['Inverness', 'Fort William', 'Kingussie', 'Newtonmore', 'Portree']
+ },
+ {
+ regionName: 'Kincardineshire',
+ regionShort: 'KC',
+ regionSlug: 'kincardineshire',
+ weight: 1,
+ cities: ['Stonehaven', 'Banchory', 'Laurencekirk', 'Inverbervie']
+ },
+ {
+ regionName: 'Kent',
+ regionShort: 'KE',
+ regionSlug: 'kent',
+ weight: 1,
+ cities: ['Maidstone', 'Canterbury', 'Bromley', 'Rochester', 'Margate', 'Folkestone', 'Dover', 'Greenwich']
+ },
+ {
+ regionName: 'Kirkcudbrightshire',
+ regionShort: 'KK',
+ regionSlug: 'kirkcudbrightshire',
+ weight: 1,
+ cities: ['Kircudbright', 'Castle Douglas', 'Dalbeattie', 'New Galloway']
+ },
+ {
+ regionName: 'Kinross-shire',
+ regionShort: 'KR',
+ regionSlug: 'kinross_shire',
+ weight: 1,
+ cities: ['Kinross', 'Milnathort']
+ },
+ {
+ regionName: 'Lancashire',
+ regionShort: 'LA',
+ regionSlug: 'lancashire',
+ weight: 1,
+ cities: ['Lancaster', 'Liverpool', 'Manchester', 'Preston', 'Bolton', 'Warrington', 'Barrow-in-Furness']
+ },
+ {
+ regionName: 'Leicestershire',
+ regionShort: 'LE',
+ regionSlug: 'leicestershire',
+ weight: 1,
+ cities: ['Leicester', 'Loughborough', 'Hinckley', 'Melton Mowbray', 'Coalville', 'Lutterworth']
+ },
+ {
+ regionName: 'Lincolnshire',
+ regionShort: 'LI',
+ regionSlug: 'lincolnshire',
+ weight: 1,
+ cities: ['Lincoln', 'Grimsby', 'Scunthorpe', 'Boston', 'Grantham', 'Stamford', 'Skegness', 'Louth']
+ },
+ {
+ regionName: 'Lanarkshire',
+ regionShort: 'LK',
+ regionSlug: 'lanarkshire',
+ weight: 1,
+ cities: ['Lanark', 'Glasgow', 'East Kilbride', 'Hamilton', 'Motherwell', 'Coatbridge', 'Carluke']
+ },
+ {
+ regionName: 'Merionethshire',
+ regionShort: 'ME',
+ regionSlug: 'merionethshire',
+ weight: 1,
+ cities: ['Dolgellau', 'Bala', 'Tywyn', 'Blaenau Ffestiniog', 'Barmouth', 'Harlech']
+ },
+ {
+ regionName: 'Montgomeryshire',
+ regionShort: 'MG',
+ regionSlug: 'montgomeryshire',
+ weight: 1,
+ cities: ['Montgomery', 'Newtown', 'Welshpool', 'Machynlleth', 'Llanidloes']
+ },
+ {
+ regionName: 'Midlothian',
+ regionShort: 'ML',
+ regionSlug: 'midlothian',
+ weight: 1,
+ cities: ['Edinburgh', 'Musselburgh', 'Penicuik', 'Dalkeith', 'Bonnyrigg']
+ },
+ {
+ regionName: 'Monmouthshire',
+ regionShort: 'MO',
+ regionSlug: 'monmouthshire',
+ weight: 1,
+ cities: ['Monmouth', 'Newport', 'Blackwood', 'Cwmbran', 'Abergavenny', 'Chepstow', 'Tredegar']
+ },
+ {
+ regionName: 'Morayshire',
+ regionShort: 'MO',
+ regionSlug: 'morayshire',
+ weight: 1,
+ cities: ['Elgin', 'Forres', 'Rothes', 'Lossiemouth', 'Fochabers']
+ },
+ {
+ regionName: 'Northumberland',
+ regionShort: 'NB',
+ regionSlug: 'northumberland',
+ weight: 1,
+ cities: ['Alnwick', 'Newcastle-upon-Tyne', 'Morpeth', 'Hexham', 'Berwick-upon-Tweed']
+ },
+ {
+ regionName: 'Norfolk',
+ regionShort: 'NF',
+ regionSlug: 'norfolk',
+ weight: 1,
+ cities: ['Norwich', 'Great Yarmouth', "King's Lynn", 'Dereham', 'Cromer', 'Hunstanton']
+ },
+ {
+ regionName: 'Northamptonshire',
+ regionShort: 'NT',
+ regionSlug: 'northamptonshire',
+ weight: 1,
+ cities: ['Northampton', 'Peterborough', 'Corby', 'Kettering', 'Wellingborough']
+ },
+ {
+ regionName: 'Nottinghamshire',
+ regionShort: 'NT',
+ regionSlug: 'nottinghamshire',
+ weight: 1,
+ cities: ['Nottingham', 'Mansfield', 'Worksop', 'Newark', 'Retford', 'Southwell']
+ },
+ {
+ regionName: 'Orkney',
+ regionShort: 'OK',
+ regionSlug: 'orkney',
+ weight: 1,
+ cities: ['Kirkwall', 'Sromness', 'Balfour']
+ },
+ {
+ regionName: 'Oxfordshire',
+ regionShort: 'OX',
+ regionSlug: 'oxfordshire',
+ weight: 1,
+ cities: ['Oxford', 'Banbury', 'Witney', 'Bicester', 'Henley-on-Thames', 'Carterton', 'Thame', 'Bloxham']
+ },
+ {
+ regionName: 'Pembrokeshire',
+ regionShort: 'PE',
+ regionSlug: 'pembrokeshire',
+ weight: 1,
+ cities: ['Pembroke', 'Milford Haven', 'Haverfordwest', 'Fishguard', 'Tenby', "St. David's"]
+ },
+ {
+ regionName: 'Radnorshire',
+ regionShort: 'RA',
+ regionSlug: 'radnorshire',
+ weight: 1,
+ cities: ['Presteigne', 'Llandrindod Wells', 'Knighton', 'Rhayader', 'New Radnor']
+ },
+ {
+ regionName: 'Renfrewshire',
+ regionShort: 'RF',
+ regionSlug: 'renfrewshire',
+ weight: 1,
+ cities: ['Renfrew', 'Paisley', 'Greenock', 'Johnstone', 'Port Glasgow', 'Barrhead', 'Kilmalcolm']
+ },
+ {
+ regionName: 'Roxburghshire',
+ regionShort: 'RO',
+ regionSlug: 'roxburghshire',
+ weight: 1,
+ cities: ['Jedburgh', 'Hawick', 'Kelso', 'Melrose', 'Roxburgh']
+ },
+ {
+ regionName: 'Rutland',
+ regionShort: 'RU',
+ regionSlug: 'rutland',
+ weight: 1,
+ cities: ['Oakham', 'Uppingham. Cottesmore']
+ },
+ {
+ regionName: 'Shropshire',
+ regionShort: 'SA',
+ regionSlug: 'shropshire',
+ weight: 1,
+ cities: ['Shrewsbury', 'Telford', 'Oswestry', 'Bridgnorth', 'Whitchurch', 'Market Drayton', 'Ludlow']
+ },
+ {
+ regionName: 'Selkirkshire',
+ regionShort: 'SE',
+ regionSlug: 'selkirkshire',
+ weight: 1,
+ cities: ['Selkirk', 'Clovenfords', 'Galashiels']
+ },
+ {
+ regionName: 'Suffolk',
+ regionShort: 'SF',
+ regionSlug: 'suffolk',
+ weight: 1,
+ cities: ['Ipswich', 'Bury St. Edmunds', 'Lowestoft', 'Felixstowe', 'Sudbury', 'Haverhill', 'Bungay']
+ },
+ {
+ regionName: 'Shetland',
+ regionShort: 'SH',
+ regionSlug: 'shetland',
+ weight: 1,
+ cities: ['Lerwick', 'Scalloway', 'Baltasound']
+ },
+ {
+ regionName: 'Somerset',
+ regionShort: 'SO',
+ regionSlug: 'somerset',
+ weight: 1,
+ cities: ['Taunton', 'Bath', 'Weston-super-Mare', 'Yeovil', 'Bridgwater', 'Wells', 'Glastonbury']
+ },
+ {
+ regionName: 'Surrey',
+ regionShort: 'SR',
+ regionSlug: 'surrey',
+ weight: 1,
+ cities: ['Guildford', 'Croydon', 'Woking', 'Sutton', 'Kingston-on-Thames', 'Wandsworth', 'Wimbledon', 'Brixton']
+ },
+ {
+ regionName: 'Sussex',
+ regionShort: 'SS',
+ regionSlug: 'sussex',
+ weight: 1,
+ cities: ['Chichester', 'Brighton', 'Worthing', 'Crawley', 'Hastings', 'Eastbourne', 'Bognor Regis', 'Horsham']
+ },
+ {
+ regionName: 'Stirlingshire',
+ regionShort: 'ST',
+ regionSlug: 'stirlingshire',
+ weight: 1,
+ cities: ['Stirling', 'Falkirk', 'Grangemouth', 'Kilsyth', 'Bridge of Allan', 'Denny', 'Alva']
+ },
+ {
+ regionName: 'Staffordshire',
+ regionShort: 'ST',
+ regionSlug: 'staffordshire',
+ weight: 1,
+ cities: ['Stafford', 'Stoke-on-Trent', 'Wolverhampton', 'Walsall', 'Cannock', 'Lichfield']
+ },
+ {
+ regionName: 'Sutherland',
+ regionShort: 'SU',
+ regionSlug: 'sutherland',
+ weight: 1,
+ cities: ['Dornoch', 'Helmsdale', 'Brora', 'Golspie', 'Lairg', 'Durness', 'Tongue']
+ },
+ {
+ regionName: 'Warwickshire',
+ regionShort: 'WA',
+ regionSlug: 'warwickshire',
+ weight: 1,
+ cities: ['Warwick', 'Birmingham', 'Coventry', 'Nuneaton', 'Rugby', 'Solihull', 'Stratford-upon-Avon']
+ },
+ {
+ regionName: 'Westmorland',
+ regionShort: 'WE',
+ regionSlug: 'westmorland',
+ weight: 1,
+ cities: ['Appleby', 'Kendal', 'Windermere', 'Ambleside', 'Kirkby Lonsdale']
+ },
+ {
+ regionName: 'Wigtownshire',
+ regionShort: 'WI',
+ regionSlug: 'wigtownshire',
+ weight: 1,
+ cities: ['Wigtown', 'Stranraer', 'Newton Stewart', 'Whithorn']
+ },
+ {
+ regionName: 'Wiltshire',
+ regionShort: 'WI',
+ regionSlug: 'wiltshire',
+ weight: 1,
+ cities: ['Trowbridge', 'Salisbury', 'Swindon', 'Chippenham', 'Devizes', 'Marlborough', 'Warminster']
+ },
+ {
+ regionName: 'West Lothian',
+ regionShort: 'WL',
+ regionSlug: 'west_lothian',
+ weight: 1,
+ cities: ['Linlithgow', 'Livingston', "Bo'ness", 'Broxburn', 'Whitburn', 'Armadale', 'Bathgate']
+ },
+ {
+ regionName: 'Worcestershire',
+ regionShort: 'WO',
+ regionSlug: 'worcestershire',
+ weight: 1,
+ cities: ['Worcester', 'Dudley', 'Kidderminster', 'Stourbridge', 'Halesowen', 'Malvern', 'Evesham']
+ },
+ {
+ regionName: 'Yorkshire',
+ regionShort: 'YK',
+ regionSlug: 'yorkshire',
+ weight: 1,
+ cities: [
+ 'Northallerton',
+ 'Middlesbrough',
+ 'Scarborough',
+ 'Whitby',
+ 'Beverley',
+ 'Hull',
+ 'Bridlington',
+ 'Driffield',
+ 'Hornsea',
+ 'Filey',
+ 'Wakefield',
+ 'Leeds',
+ 'Sheffield',
+ 'Bradford',
+ 'Halifax',
+ 'Harrogate',
+ 'York'
+ ]
+ },
+ {
+ regionName: 'Durham',
+ regionShort: 'DU',
+ regionSlug: 'durham',
+ weight: 1,
+ cities: ['Durham', 'Sunderland', 'Stockton-on-Tees', 'Darlington', 'Hartlepool', 'Gateshead', 'Washington']
+ },
+ {
+ regionName: 'Brecknockshire',
+ regionShort: 'BR',
+ regionSlug: 'brecknockshire',
+ weight: 1,
+ cities: ['Brecon', 'Builth Wells', 'Hay-on-Wye', 'Talgarth', 'Llanwrtwd Wells']
+ },
+ {
+ regionName: 'Buteshire',
+ regionShort: 'BU',
+ regionSlug: 'buteshire',
+ weight: 1,
+ cities: ['Rothesay', 'Millport', 'Brodick', 'Lochranza']
+ },
+ {
+ regionName: 'Dumfriesshire',
+ regionShort: 'DF',
+ regionSlug: 'dumfriesshire',
+ weight: 1,
+ cities: ['Dumfries', 'Annan', 'Lockerbie', 'Moffat', 'Sanquhar', 'Langholm', 'Gretna']
+ },
+ {
+ regionName: 'Nairnshire',
+ regionShort: 'NA',
+ regionSlug: 'nairnshire',
+ weight: 1,
+ cities: ['Nairn', 'Auldearn', 'Cawdor', 'Ferness']
+ },
+ {
+ regionName: 'Perthshire',
+ regionShort: 'PE',
+ regionSlug: 'perthshire',
+ weight: 1,
+ cities: ['Perth', 'Crieff', 'Pitlochry', 'Callander', 'Blairgowrie', 'Rattray', 'Coupar Angus', 'Kincardine']
+ },
+ {
+ regionName: 'Ross-shire',
+ regionShort: 'RO',
+ regionSlug: 'ross_shire',
+ weight: 1,
+ cities: ['Dingwall', 'Stornaway', 'Tain', 'Alness', 'Invergordon']
+ }
+ ]
+});
+
+export default UK;
diff --git a/packages/plugins/src/countries/UK/i18n/ar.json b/packages/plugins/src/countries/UK/i18n/ar.json
new file mode 100644
index 000000000..0c1237ad2
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "المملكة المتحدة",
+ "regionNames": "مقاطعات المملكة المتحدة"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/de.json b/packages/plugins/src/countries/UK/i18n/de.json
new file mode 100644
index 000000000..c53ddbb35
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Großbritannien",
+ "regionNames": "Britische Grafschaften"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/en.json b/packages/plugins/src/countries/UK/i18n/en.json
new file mode 100644
index 000000000..e83335700
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "United Kingdom",
+ "regionNames": "UK Counties"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/es.json b/packages/plugins/src/countries/UK/i18n/es.json
new file mode 100644
index 000000000..af7b4b734
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Reino Unido",
+ "regionNames": "Condados del Reino Unido"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/fr.json b/packages/plugins/src/countries/UK/i18n/fr.json
new file mode 100644
index 000000000..5ac2ce93b
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Royaume-Uni",
+ "regionNames": "Comtés du Royaume-Uni"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/hi.json b/packages/plugins/src/countries/UK/i18n/hi.json
new file mode 100644
index 000000000..7897a559d
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "यूनाइटेड किंगडम",
+ "regionNames": "यूके काउंटियों"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/ja.json b/packages/plugins/src/countries/UK/i18n/ja.json
new file mode 100644
index 000000000..a270866cb
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "イギリス",
+ "regionNames": "英国の郡"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/nl.json b/packages/plugins/src/countries/UK/i18n/nl.json
new file mode 100644
index 000000000..cd6534846
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Verenigd Koninkrijk",
+ "regionNames": "Britse provincies"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/pt.json b/packages/plugins/src/countries/UK/i18n/pt.json
new file mode 100644
index 000000000..b64326a4c
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Reino Unido",
+ "regionNames": "Condados do Reino Unido"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/ru.json b/packages/plugins/src/countries/UK/i18n/ru.json
new file mode 100644
index 000000000..df988fa21
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Соединенное Королевство",
+ "regionNames": "Округа Великобритании"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/ta.json b/packages/plugins/src/countries/UK/i18n/ta.json
new file mode 100644
index 000000000..161d3c7f8
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ஐக்கிய இராச்சியம்",
+ "regionNames": "இங்கிலாந்து மாவட்டங்கள்"
+}
diff --git a/packages/plugins/src/countries/UK/i18n/zh.json b/packages/plugins/src/countries/UK/i18n/zh.json
new file mode 100644
index 000000000..6750261d5
--- /dev/null
+++ b/packages/plugins/src/countries/UK/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "英国",
+ "regionNames": "英国各县"
+}
diff --git a/packages/plugins/src/countries/US/bundle.ts b/packages/plugins/src/countries/US/bundle.ts
new file mode 100644
index 000000000..e7be0db08
--- /dev/null
+++ b/packages/plugins/src/countries/US/bundle.ts
@@ -0,0 +1,364 @@
+import { GetCountryData } from '@generatedata/types';
+
+const US: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'US',
+ regionNames: i18n.regionNames,
+ continent: 'north_america',
+
+ extendedData: {
+ zipFormat: {
+ format: 'Xxxxx'
+ },
+ phoneFormat: {
+ displayFormats: ['(AAA} Xxx-xxxx', '1 (AAA} Xxx-xxxx', '1-AAA-Xxx-xxxx']
+ }
+ },
+
+ regions: [
+ {
+ regionName: 'Alabama',
+ regionShort: 'AL',
+ regionSlug: 'alabama',
+ weight: 2,
+ cities: [
+ 'Birmingham',
+ 'Montgomery',
+ 'Mobile',
+ 'Huntsville',
+ 'Tuscaloosa',
+ 'Birmingham',
+ 'Montgomery',
+ 'Mobile',
+ 'Huntsville',
+ 'Tuscaloosa'
+ ],
+ extendedData: {
+ zipFormat: {
+ format: 'ZYxxx',
+ replacements: {
+ Z: '3',
+ Y: '56',
+ x: '0123456789'
+ }
+ }
+ }
+ },
+ {
+ regionName: 'Alaska',
+ regionShort: 'AK',
+ regionSlug: 'alaska',
+ weight: 2,
+ cities: ['Anchorage', 'Fairbanks', 'Juneau', 'College', 'Anchorage', 'Fairbanks', 'Juneau', 'College', 'Ketchikan'],
+ extendedData: {
+ zipFormat: {
+ format: 'ZZYxx',
+ replacements: {
+ Z: '9',
+ Y: '56789',
+ x: '0123456789'
+ }
+ }
+ }
+ },
+ {
+ regionName: 'Arizona',
+ regionShort: 'AZ',
+ regionSlug: 'arizona',
+ weight: 2,
+ cities: ['Phoenix', 'Tucson', 'Mesa', 'Glendale', 'Chandler'],
+ extendedData: {
+ zipFormat: {
+ format: 'ZYxxx',
+ replacements: {
+ Z: '8',
+ Y: '56',
+ x: '0123456789'
+ }
+ }
+ }
+ },
+ {
+ regionName: 'Arkansas',
+ regionShort: 'AR',
+ regionSlug: 'arkansas',
+ weight: 2,
+ cities: ['Little Rock', 'Fort Smith', 'Fayetteville', 'Springdale', 'Jonesboro'],
+ extendedData: {
+ zipFormat: {
+ format: 'ZYxxx',
+ replacements: {
+ Z: '7',
+ Y: '12',
+ x: '0123456789'
+ }
+ }
+ }
+ },
+ {
+ regionName: 'California',
+ regionShort: 'CA',
+ regionSlug: 'california',
+ weight: 2,
+ cities: ['Los Angeles', 'San Diego', 'San Jose', 'San Francisco', 'Fresno', 'Sacramento'],
+ extendedData: {
+ zipFormat: {
+ // 'area': 'US-CA',
+ format: 'ZYxxx',
+ replacements: {
+ Z: '9',
+ Y: '0123456',
+ x: '0123456789'
+ }
+ }
+ }
+ },
+ {
+ regionName: 'Colorado',
+ regionShort: 'CO',
+ regionSlug: 'colorado',
+ weight: 2,
+ cities: ['Denver', 'Colorado Springs', 'Aurora', 'Lakewood', 'Fort Collins']
+ },
+ {
+ regionName: 'Connecticut',
+ regionShort: 'CT',
+ regionSlug: 'connecticut',
+ weight: 2,
+ cities: ['Bridgeport', 'New Haven', 'Hartford', 'Stamford', 'Waterbury']
+ },
+ {
+ regionName: 'Delaware',
+ regionShort: 'DE',
+ regionSlug: 'delaware',
+ weight: 2,
+ cities: ['Wilmington', 'Dover', 'Newark', 'Pike Creek', 'Bear']
+ },
+ {
+ regionName: 'Florida',
+ regionShort: 'FL',
+ regionSlug: 'florida',
+ weight: 2,
+ cities: ['Jacksonville', 'Miami', 'Tampa', 'St. Petersburg', 'Orlando', 'Tallahassee']
+ },
+ {
+ regionName: 'Georgia',
+ regionShort: 'GA',
+ regionSlug: 'georgia',
+ weight: 2,
+ cities: ['Georgia', 'Atlanta', 'Augusta', 'Columbus', 'Savannah', 'Athens']
+ },
+ {
+ regionName: 'Hawaii',
+ regionShort: 'HI',
+ regionSlug: 'hawaii',
+ weight: 2,
+ cities: ['Honolulu', 'Hilo', 'Kailua', 'Kaneohe', 'Kapolei']
+ },
+ {
+ regionName: 'Idaho',
+ regionShort: 'ID',
+ regionSlug: 'idaho',
+ weight: 2,
+ cities: ['Boise', 'Nampa', 'Meridian', 'Pocatello', 'Idaho Falls']
+ },
+ {
+ regionName: 'Illinois',
+ regionShort: 'IL',
+ regionSlug: 'illinois',
+ weight: 2,
+ cities: ['Chicago', 'Aurora', 'Rockford', 'Joliet', 'Naperville', 'Springfield']
+ },
+ {
+ regionName: 'Indiana',
+ regionShort: 'IN',
+ regionSlug: 'indiana',
+ weight: 2,
+ cities: ['Indianapolis', 'Fort Wayne', 'Evansville', 'South Bend', 'Gary']
+ },
+ {
+ regionName: 'Iowa',
+ regionShort: 'IA',
+ regionSlug: 'iowa',
+ weight: 2,
+ cities: ['Des Moines', 'Cedar Rapids', 'Davenport', 'Sioux City', 'Iowa City']
+ },
+ {
+ regionName: 'Kansas',
+ regionShort: 'KS',
+ regionSlug: 'kansas',
+ weight: 2,
+ cities: ['Wichita', 'Overland Park', 'Kansas City', 'Topeka', 'Olathe']
+ },
+ {
+ regionName: 'Kentucky',
+ regionShort: 'KY',
+ regionSlug: 'kentucky',
+ weight: 2,
+ cities: ['Louisville', 'Lexington', 'Owensboro', 'Bowling Green', 'Covington', 'Frankfort']
+ },
+ {
+ regionName: 'Louisiana',
+ regionShort: 'LA',
+ regionSlug: 'louisiana',
+ weight: 2,
+ cities: ['New Orleans', 'Baton Rouge', 'Shreveport', 'Metairie', 'Lafayette']
+ },
+ {
+ regionName: 'Maine',
+ regionShort: 'ME',
+ regionSlug: 'maine',
+ weight: 2,
+ cities: ['Portland', 'Lewiston', 'Bangor', 'South Portland', 'Auburn', 'Augusta']
+ },
+ {
+ regionName: 'Maryland',
+ regionShort: 'MD',
+ regionSlug: 'maryland',
+ weight: 2,
+ cities: ['Baltimore', 'Rockville', 'Frederick', 'Gaithersburg', 'Columbia', 'Annapolis']
+ },
+ {
+ regionName: 'Massachusetts',
+ regionShort: 'MA',
+ regionSlug: 'massachusetts',
+ weight: 2,
+ cities: ['Boston', 'Worcester', 'Springfield', 'Lowell', 'Cambridge']
+ },
+ {
+ regionName: 'Michigan',
+ regionShort: 'MI',
+ regionSlug: 'michigan',
+ weight: 2,
+ cities: ['Detroit', 'Grand Rapids', 'Warren', 'Sterling Heights', 'Flint', 'Lansing']
+ },
+ {
+ regionName: 'Minnesota',
+ regionShort: 'MN',
+ regionSlug: 'minnesota',
+ weight: 2,
+ cities: ['Minneapolis', 'Saint Paul', 'Rochester', 'Duluth', 'Bloomington']
+ },
+ {
+ regionName: 'Mississippi',
+ regionShort: 'MS',
+ regionSlug: 'mississippi',
+ weight: 2,
+ cities: ['Jackson', 'Gulfport', 'Hattiesburg', 'Biloxi', 'Southaven']
+ },
+ {
+ regionName: 'Missouri',
+ regionShort: 'MO',
+ regionSlug: 'missouri',
+ weight: 2,
+ cities: ['Kansas City', 'Saint Louis', 'Springfield', 'Independence', 'Columbia', 'Jefferson City']
+ },
+ {
+ regionName: 'Montana',
+ regionShort: 'MT',
+ regionSlug: 'montana',
+ weight: 2,
+ cities: ['Billings', 'Missoula', 'Great Falls', 'Butte', 'Bozeman', 'Helena']
+ },
+ {
+ regionName: 'Nebraska',
+ regionShort: 'NE',
+ regionSlug: 'nebraska',
+ weight: 2,
+ cities: ['Omaha', 'Lincoln', 'Bellevue', 'Grand Island', 'Kearney']
+ },
+ {
+ regionName: 'Nevada',
+ regionShort: 'NV',
+ regionSlug: 'nevada',
+ weight: 2,
+ cities: ['Las Vegas', 'Henderson', 'North Las Vegas', 'Reno', 'Paradise', 'Carson City']
+ },
+ {
+ regionName: 'Ohio',
+ regionShort: 'OH',
+ regionSlug: 'ohio',
+ weight: 2,
+ cities: ['Columbus', 'Cleveland', 'Cincinnati', 'Toledo', 'Akron']
+ },
+ {
+ regionName: 'Oklahoma',
+ regionShort: 'OK',
+ regionSlug: 'oklahoma',
+ weight: 2,
+ cities: ['Oklahoma City', 'Tulsa', 'Norman', 'Lawton', 'Broken Arrow']
+ },
+ {
+ regionName: 'Oregon',
+ regionShort: 'OR',
+ regionSlug: 'oregon',
+ weight: 2,
+ cities: ['Portland', 'Eugene', 'Salem', 'Gresham', 'Hillsboro']
+ },
+ {
+ regionName: 'Pennsylvania',
+ regionShort: 'PA',
+ regionSlug: 'pennsylvania',
+ weight: 2,
+ cities: ['Philadelphia', 'Pittsburgh', 'Allentown', 'Erie', 'Reading', 'Harrisburg']
+ },
+ {
+ regionName: 'Tennessee',
+ regionShort: 'TN',
+ regionSlug: 'tennessee',
+ weight: 2,
+ cities: ['Memphis', 'Nashville', 'Knoxville', 'Chattanooga', 'Clarksville']
+ },
+ {
+ regionName: 'Texas',
+ regionShort: 'TX',
+ regionSlug: 'texas',
+ weight: 2,
+ cities: ['Houston', 'San Antonio', 'Dallas', 'Austin', 'Fort Worth']
+ },
+ {
+ regionName: 'Utah',
+ regionShort: 'UT',
+ regionSlug: 'utah',
+ weight: 2,
+ cities: ['Salt Lake City', 'West Valley City', 'Provo', 'West Jordan', 'Sandy']
+ },
+ {
+ regionName: 'Vermont',
+ regionShort: 'VT',
+ regionSlug: 'vermont',
+ weight: 2,
+ cities: ['Burlington', 'Essex', 'Rutland', 'Colchester', 'South Burlington', 'Montpelier']
+ },
+ {
+ regionName: 'Virginia',
+ regionShort: 'VA',
+ regionSlug: 'virginia',
+ weight: 2,
+ cities: ['Virginia Beach', 'Norfolk', 'Chesapeake', 'Richmond', 'Newport News']
+ },
+ {
+ regionName: 'Washington',
+ regionShort: 'WA',
+ regionSlug: 'washington',
+ weight: 2,
+ cities: ['Seattle', 'Spokane', 'Tacoma', 'Vancouver', 'Bellevue', 'Olympia']
+ },
+ {
+ regionName: 'Wisconsin',
+ regionShort: 'WI',
+ regionSlug: 'wisconsin',
+ weight: 2,
+ cities: ['Milwaukee', 'Madison', 'Green Bay', 'Kenosha', 'Racine']
+ },
+ {
+ regionName: 'Wyoming',
+ regionShort: 'WY',
+ regionSlug: 'wyoming',
+ weight: 2,
+ cities: ['Wyoming', 'Cheyenne', 'Casper', 'Laramie', 'Gillette', 'Rock Springs']
+ }
+ ]
+});
+
+export default US;
diff --git a/packages/plugins/src/countries/US/i18n/ar.json b/packages/plugins/src/countries/US/i18n/ar.json
new file mode 100644
index 000000000..634266052
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "الولايات المتحدة",
+ "regionNames": "الولايات المتحدة"
+}
diff --git a/packages/plugins/src/countries/US/i18n/de.json b/packages/plugins/src/countries/US/i18n/de.json
new file mode 100644
index 000000000..c9c8b70e4
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Vereinigte Staaten",
+ "regionNames": "US-Bundesstaaten"
+}
diff --git a/packages/plugins/src/countries/US/i18n/en.json b/packages/plugins/src/countries/US/i18n/en.json
new file mode 100644
index 000000000..59a7c8e44
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "United States",
+ "regionNames": "US States"
+}
diff --git a/packages/plugins/src/countries/US/i18n/es.json b/packages/plugins/src/countries/US/i18n/es.json
new file mode 100644
index 000000000..b049141e1
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Estados Unidos",
+ "regionNames": "Nuestros estados"
+}
diff --git a/packages/plugins/src/countries/US/i18n/fr.json b/packages/plugins/src/countries/US/i18n/fr.json
new file mode 100644
index 000000000..457de0e4b
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "États-Unis",
+ "regionNames": "États américains"
+}
diff --git a/packages/plugins/src/countries/US/i18n/hi.json b/packages/plugins/src/countries/US/i18n/hi.json
new file mode 100644
index 000000000..5fe7bfdd2
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "संयुक्त राज्य अमेरिका",
+ "regionNames": "अमेरिकी राज्य"
+}
diff --git a/packages/plugins/src/countries/US/i18n/ja.json b/packages/plugins/src/countries/US/i18n/ja.json
new file mode 100644
index 000000000..3940e422c
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "アメリカ",
+ "regionNames": "米国の州"
+}
diff --git a/packages/plugins/src/countries/US/i18n/nl.json b/packages/plugins/src/countries/US/i18n/nl.json
new file mode 100644
index 000000000..f501691a6
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Verenigde Staten",
+ "regionNames": "Amerikaanse staten"
+}
diff --git a/packages/plugins/src/countries/US/i18n/pt.json b/packages/plugins/src/countries/US/i18n/pt.json
new file mode 100644
index 000000000..f0671dde1
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Estados Unidos",
+ "regionNames": "Estados dos EUA"
+}
diff --git a/packages/plugins/src/countries/US/i18n/ru.json b/packages/plugins/src/countries/US/i18n/ru.json
new file mode 100644
index 000000000..8d4c18ff1
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Соединенные Штаты",
+ "regionNames": "Штаты США"
+}
diff --git a/packages/plugins/src/countries/US/i18n/ta.json b/packages/plugins/src/countries/US/i18n/ta.json
new file mode 100644
index 000000000..6c8cba1c1
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "அமெரிக்கா",
+ "regionNames": "அமெரிக்க மாநிலங்கள்"
+}
diff --git a/packages/plugins/src/countries/US/i18n/zh.json b/packages/plugins/src/countries/US/i18n/zh.json
new file mode 100644
index 000000000..a3272a8c5
--- /dev/null
+++ b/packages/plugins/src/countries/US/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "美国",
+ "regionNames": "美国各州"
+}
diff --git a/packages/plugins/src/countries/US/names.ts b/packages/plugins/src/countries/US/names.ts
new file mode 100644
index 000000000..c39e2c415
--- /dev/null
+++ b/packages/plugins/src/countries/US/names.ts
@@ -0,0 +1,918 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Agnes',
+ 'Alice',
+ 'Alicia',
+ 'Allison',
+ 'Alma',
+ 'Amanda',
+ 'Amber',
+ 'Amy',
+ 'Ana',
+ 'Andrea',
+ 'Angela',
+ 'Anita',
+ 'Ann',
+ 'Anna',
+ 'Anne',
+ 'Annette',
+ 'Annie',
+ 'April',
+ 'Arlene',
+ 'Ashley',
+ 'Audrey',
+ 'Barbara',
+ 'Beatrice',
+ 'Becky',
+ 'Bernice',
+ 'Bertha',
+ 'Bessie',
+ 'Beth',
+ 'Betty',
+ 'Beverly',
+ 'Billie',
+ 'Bobbie',
+ 'Bonnie',
+ 'Brandy',
+ 'Brenda',
+ 'Brittany',
+ 'Carla',
+ 'Carmen',
+ 'Carol',
+ 'Carole',
+ 'Caroline',
+ 'Carolyn',
+ 'Carrie',
+ 'Cassandra',
+ 'Catherine',
+ 'Cathy',
+ 'Charlene',
+ 'Charlotte',
+ 'Cheryl',
+ 'Christina',
+ 'Christine',
+ 'Christy',
+ 'Cindy',
+ 'Claire',
+ 'Clara',
+ 'Claudia',
+ 'Colleen',
+ 'Connie',
+ 'Constance',
+ 'Courtney',
+ 'Crystal',
+ 'Cynthia',
+ 'Daisy',
+ 'Dana',
+ 'Danielle',
+ 'Darlene',
+ 'Dawn',
+ 'Deanna',
+ 'Debbie',
+ 'Deborah',
+ 'Debra',
+ 'Delores',
+ 'Denise',
+ 'Diana',
+ 'Diane',
+ 'Dianne',
+ 'Dolores',
+ 'Donna',
+ 'Dora',
+ 'Doris',
+ 'Dorothy',
+ 'Edith',
+ 'Edna',
+ 'Eileen',
+ 'Elaine',
+ 'Eleanor',
+ 'Elizabeth',
+ 'Ella',
+ 'Ellen',
+ 'Elsie',
+ 'Emily',
+ 'Emma',
+ 'Erica',
+ 'Erika',
+ 'Erin',
+ 'Esther',
+ 'Ethel',
+ 'Eva',
+ 'Evelyn',
+ 'Felicia',
+ 'Florence',
+ 'Frances',
+ 'Gail',
+ 'Georgia',
+ 'Geraldine',
+ 'Gertrude',
+ 'Gina',
+ 'Gladys',
+ 'Glenda',
+ 'Gloria',
+ 'Grace',
+ 'Gwendolyn',
+ 'Hazel',
+ 'Heather',
+ 'Heidi',
+ 'Helen',
+ 'Hilda',
+ 'Holly',
+ 'Ida',
+ 'Irene',
+ 'Irma',
+ 'Jackie',
+ 'Jacqueline',
+ 'Jamie',
+ 'Jane',
+ 'Janet',
+ 'Janice',
+ 'Jean',
+ 'Jeanette',
+ 'Jeanne',
+ 'Jennie',
+ 'Jennifer',
+ 'Jenny',
+ 'Jessica',
+ 'Jessie',
+ 'Jill',
+ 'Jo',
+ 'Joan',
+ 'Joann',
+ 'Joanne',
+ 'Josephine',
+ 'Joy',
+ 'Joyce',
+ 'Juanita',
+ 'Judith',
+ 'Judy',
+ 'Julia',
+ 'Julie',
+ 'June',
+ 'Karen',
+ 'Katherine',
+ 'Kathleen',
+ 'Kathryn',
+ 'Kathy',
+ 'Katie',
+ 'Katrina',
+ 'Kay',
+ 'Kelly',
+ 'Kim',
+ 'Kimberly',
+ 'Kristen',
+ 'Kristin',
+ 'Kristina',
+ 'Laura',
+ 'Lauren',
+ 'Laurie',
+ 'Leah',
+ 'Lena',
+ 'Leona',
+ 'Leslie',
+ 'Lillian',
+ 'Lillie',
+ 'Linda',
+ 'Lisa',
+ 'Lois',
+ 'Loretta',
+ 'Lori',
+ 'Lorraine',
+ 'Louise',
+ 'Lucille',
+ 'Lucy',
+ 'Lydia',
+ 'Lynn',
+ 'Mabel',
+ 'Mae',
+ 'Marcia',
+ 'Margaret',
+ 'Margie',
+ 'Maria',
+ 'Marian',
+ 'Marie',
+ 'Marilyn',
+ 'Marion',
+ 'Marjorie',
+ 'Marlene',
+ 'Marsha',
+ 'Martha',
+ 'Mary',
+ 'Mattie',
+ 'Maureen',
+ 'Maxine',
+ 'Megan',
+ 'Melanie',
+ 'Melinda',
+ 'Melissa',
+ 'Michele',
+ 'Michelle',
+ 'Mildred',
+ 'Minnie',
+ 'Miriam',
+ 'Misty',
+ 'Monica',
+ 'Myrtle',
+ 'Nancy',
+ 'Naomi',
+ 'Natalie',
+ 'Nellie',
+ 'Nicole',
+ 'Nina',
+ 'Nora',
+ 'Norma',
+ 'Olga',
+ 'Pamela',
+ 'Patricia',
+ 'Patsy',
+ 'Paula',
+ 'Pauline',
+ 'Pearl',
+ 'Peggy',
+ 'Penny',
+ 'Phyllis',
+ 'Priscilla',
+ 'Rachel',
+ 'Ramona',
+ 'Rebecca',
+ 'Regina',
+ 'Renee',
+ 'Rhonda',
+ 'Rita',
+ 'Roberta',
+ 'Robin',
+ 'Rosa',
+ 'Rose',
+ 'Rosemary',
+ 'Ruby',
+ 'Ruth',
+ 'Sally',
+ 'Samantha',
+ 'Sandra',
+ 'Sara',
+ 'Sarah',
+ 'Shannon',
+ 'Sharon',
+ 'Sheila',
+ 'Shelly',
+ 'Sherri',
+ 'Sherry',
+ 'Shirley',
+ 'Sonia',
+ 'Stacey',
+ 'Stacy',
+ 'Stella',
+ 'Stephanie',
+ 'Sue',
+ 'Susan',
+ 'Suzanne',
+ 'Sylvia',
+ 'Tamara',
+ 'Tammy',
+ 'Tanya',
+ 'Tara',
+ 'Teresa',
+ 'Terri',
+ 'Terry',
+ 'Thelma',
+ 'Theresa',
+ 'Tiffany',
+ 'Tina',
+ 'Toni',
+ 'Tonya',
+ 'Tracey',
+ 'Tracy',
+ 'Valerie',
+ 'Vanessa',
+ 'Velma',
+ 'Vera',
+ 'Veronica',
+ 'Vicki',
+ 'Vickie',
+ 'Victoria',
+ 'Viola',
+ 'Violet',
+ 'Virginia',
+ 'Vivian',
+ 'Wanda',
+ 'Wendy',
+ 'Willie',
+ 'Wilma',
+ 'Yolanda',
+ 'Yvonne'
+];
+
+const maleNames = [
+ 'Aaron',
+ 'Adam',
+ 'Adrian',
+ 'Alan',
+ 'Albert',
+ 'Alberto',
+ 'Alex',
+ 'Alexander',
+ 'Alfred',
+ 'Alfredo',
+ 'Allan',
+ 'Allen',
+ 'Alvin',
+ 'Andre',
+ 'Andrew',
+ 'Andy',
+ 'Angel',
+ 'Anthony',
+ 'Antonio',
+ 'Armando',
+ 'Arnold',
+ 'Arthur',
+ 'Barry',
+ 'Ben',
+ 'Benjamin',
+ 'Bernard',
+ 'Bill',
+ 'Billy',
+ 'Bob',
+ 'Bobby',
+ 'Brad',
+ 'Bradley',
+ 'Brandon',
+ 'Brent',
+ 'Brett',
+ 'Brian',
+ 'Bruce',
+ 'Bryan',
+ 'Byron',
+ 'Calvin',
+ 'Carl',
+ 'Carlos',
+ 'Casey',
+ 'Cecil',
+ 'Chad',
+ 'Charles',
+ 'Charlie',
+ 'Chester',
+ 'Chris',
+ 'Christian',
+ 'Christopher',
+ 'Clarence',
+ 'Claude',
+ 'Clayton',
+ 'Clifford',
+ 'Clifton',
+ 'Clinton',
+ 'Clyde',
+ 'Cody',
+ 'Corey',
+ 'Cory',
+ 'Craig',
+ 'Curtis',
+ 'Dale',
+ 'Dan',
+ 'Daniel',
+ 'Danny',
+ 'Darrell',
+ 'Darren',
+ 'Darryl',
+ 'Daryl',
+ 'Dave',
+ 'David',
+ 'Dean',
+ 'Dennis',
+ 'Derek',
+ 'Derrick',
+ 'Don',
+ 'Donald',
+ 'Douglas',
+ 'Duane',
+ 'Dustin',
+ 'Dwayne',
+ 'Dwight',
+ 'Earl',
+ 'Eddie',
+ 'Edgar',
+ 'Eduardo',
+ 'Edward',
+ 'Edwin',
+ 'Elmer',
+ 'Enrique',
+ 'Eric',
+ 'Erik',
+ 'Ernest',
+ 'Eugene',
+ 'Everett',
+ 'Felix',
+ 'Fernando',
+ 'Floyd',
+ 'Francis',
+ 'Francisco',
+ 'Frank',
+ 'Franklin',
+ 'Fred',
+ 'Freddie',
+ 'Frederick',
+ 'Gabriel',
+ 'Gary',
+ 'Gene',
+ 'George',
+ 'Gerald',
+ 'Gilbert',
+ 'Glen',
+ 'Glenn',
+ 'Gordon',
+ 'Greg',
+ 'Gregory',
+ 'Guy',
+ 'Harold',
+ 'Harry',
+ 'Harvey',
+ 'Hector',
+ 'Henry',
+ 'Herbert',
+ 'Herman',
+ 'Howard',
+ 'Hugh',
+ 'Ian',
+ 'Isaac',
+ 'Ivan',
+ 'Jack',
+ 'Jacob',
+ 'Jaime',
+ 'James',
+ 'Jamie',
+ 'Jared',
+ 'Jason',
+ 'Javier',
+ 'Jay',
+ 'Jeff',
+ 'Jeffery',
+ 'Jeffrey',
+ 'Jeremy',
+ 'Jerome',
+ 'Jerry',
+ 'Jesse',
+ 'Jessie',
+ 'Jesus',
+ 'Jim',
+ 'Jimmie',
+ 'Jimmy',
+ 'Joe',
+ 'Joel',
+ 'John',
+ 'Johnnie',
+ 'Johnny',
+ 'Jon',
+ 'Jonathan',
+ 'Jordan',
+ 'Jorge',
+ 'Jose',
+ 'Joseph',
+ 'Joshua',
+ 'Juan',
+ 'Julian',
+ 'Julio',
+ 'Justin',
+ 'Karl',
+ 'Keith',
+ 'Kelly',
+ 'Ken',
+ 'Kenneth',
+ 'Kent',
+ 'Kevin',
+ 'Kirk',
+ 'Kurt',
+ 'Kyle',
+ 'Lance',
+ 'Larry',
+ 'Lawrence',
+ 'Lee',
+ 'Leo',
+ 'Leon',
+ 'Leonard',
+ 'Leroy',
+ 'Leslie',
+ 'Lester',
+ 'Lewis',
+ 'Lloyd',
+ 'Lonnie',
+ 'Louis',
+ 'Luis',
+ 'Manuel',
+ 'Marc',
+ 'Marcus',
+ 'Mario',
+ 'Marion',
+ 'Mark',
+ 'Marshall',
+ 'Martin',
+ 'Marvin',
+ 'Mathew',
+ 'Matthew',
+ 'Maurice',
+ 'Max',
+ 'Melvin',
+ 'Michael',
+ 'Micheal',
+ 'Miguel',
+ 'Mike',
+ 'Milton',
+ 'Mitchell',
+ 'Morris',
+ 'Nathan',
+ 'Nathaniel',
+ 'Neil',
+ 'Nelson',
+ 'Nicholas',
+ 'Norman',
+ 'Oscar',
+ 'Patrick',
+ 'Paul',
+ 'Pedro',
+ 'Perry',
+ 'Peter',
+ 'Philip',
+ 'Phillip',
+ 'Rafael',
+ 'Ralph',
+ 'Ramon',
+ 'Randall',
+ 'Randy',
+ 'Raul',
+ 'Ray',
+ 'Raymond',
+ 'Reginald',
+ 'Rene',
+ 'Ricardo',
+ 'Richard',
+ 'Rick',
+ 'Ricky',
+ 'Robert',
+ 'Roberto',
+ 'Rodney',
+ 'Roger',
+ 'Roland',
+ 'Ron',
+ 'Ronald',
+ 'Ronnie',
+ 'Ross',
+ 'Roy',
+ 'Ruben',
+ 'Russell',
+ 'Ryan',
+ 'Salvador',
+ 'Sam',
+ 'Samuel',
+ 'Scott',
+ 'Sean',
+ 'Sergio',
+ 'Seth',
+ 'Shane',
+ 'Shawn',
+ 'Sidney',
+ 'Stanley',
+ 'Stephen',
+ 'Steve',
+ 'Steven',
+ 'Ted',
+ 'Terrance',
+ 'Terrence',
+ 'Terry',
+ 'Theodore',
+ 'Thomas',
+ 'Tim',
+ 'Timothy',
+ 'Todd',
+ 'Tom',
+ 'Tommy',
+ 'Tony',
+ 'Tracy',
+ 'Travis',
+ 'Troy',
+ 'Tyler',
+ 'Tyrone',
+ 'Vernon',
+ 'Victor',
+ 'Vincent',
+ 'Virgil',
+ 'Wade',
+ 'Wallace',
+ 'Walter',
+ 'Warren',
+ 'Wayne',
+ 'Wesley',
+ 'Willard',
+ 'William',
+ 'Willie',
+ 'Zachary'
+];
+
+const lastNames = [
+ 'Adams',
+ 'Aguilar',
+ 'Alexander',
+ 'Allen',
+ 'Alvarado',
+ 'Alvarez',
+ 'Anderson',
+ 'Andrews',
+ 'Armstrong',
+ 'Arnold',
+ 'Austin',
+ 'Bailey',
+ 'Baker',
+ 'Banks',
+ 'Barnes',
+ 'Bell',
+ 'Bennett',
+ 'Berry',
+ 'Bishop',
+ 'Black',
+ 'Bowman',
+ 'Boyd',
+ 'Bradley',
+ 'Brooks',
+ 'Brown',
+ 'Bryant',
+ 'Burke',
+ 'Burns',
+ 'Burton',
+ 'Butler',
+ 'Campbell',
+ 'Carlson',
+ 'Carpenter',
+ 'Carr',
+ 'Carroll',
+ 'Carter',
+ 'Castillo',
+ 'Castro',
+ 'Chapman',
+ 'Chavez',
+ 'Chen',
+ 'Clark',
+ 'Cole',
+ 'Coleman',
+ 'Collins',
+ 'Contreras',
+ 'Cook',
+ 'Cooper',
+ 'Cox',
+ 'Crawford',
+ 'Cruz',
+ 'Cunningham',
+ 'Daniels',
+ 'Davis',
+ 'Day',
+ 'Dean',
+ 'Delgado',
+ 'Diaz',
+ 'Dixon',
+ 'Dominguez',
+ 'Duncan',
+ 'Dunn',
+ 'Edwards',
+ 'Elliott',
+ 'Ellis',
+ 'Espinoza',
+ 'Estrada',
+ 'Evans',
+ 'Ferguson',
+ 'Fernandez',
+ 'Fields',
+ 'Fisher',
+ 'Flores',
+ 'Ford',
+ 'Foster',
+ 'Fowler',
+ 'Fox',
+ 'Franklin',
+ 'Freeman',
+ 'Fuller',
+ 'Garcia',
+ 'Gardner',
+ 'Garrett',
+ 'Garza',
+ 'George',
+ 'Gibson',
+ 'Gilbert',
+ 'Gomez',
+ 'Gonzales',
+ 'Gonzalez',
+ 'Gordon',
+ 'Graham',
+ 'Grant',
+ 'Gray',
+ 'Green',
+ 'Greene',
+ 'Griffin',
+ 'Guerrero',
+ 'Gutierrez',
+ 'Guzman',
+ 'Hall',
+ 'Hamilton',
+ 'Hansen',
+ 'Hanson',
+ 'Harper',
+ 'Harris',
+ 'Harrison',
+ 'Hart',
+ 'Harvey',
+ 'Hawkins',
+ 'Hayes',
+ 'Henderson',
+ 'Henry',
+ 'Hernandez',
+ 'Herrera',
+ 'Hicks',
+ 'Hill',
+ 'Hoffman',
+ 'Holmes',
+ 'Howard',
+ 'Howell',
+ 'Hudson',
+ 'Hughes',
+ 'Hunt',
+ 'Hunter',
+ 'Jackson',
+ 'Jacobs',
+ 'James',
+ 'Jenkins',
+ 'Jensen',
+ 'Jimenez',
+ 'Johnson',
+ 'Johnston',
+ 'Jones',
+ 'Jordan',
+ 'Kelley',
+ 'Kelly',
+ 'Kennedy',
+ 'Kim',
+ 'King',
+ 'Knight',
+ 'Lane',
+ 'Larson',
+ 'Lawrence',
+ 'Lawson',
+ 'Le',
+ 'Lee',
+ 'Lewis',
+ 'Li',
+ 'Little',
+ 'Long',
+ 'Lopez',
+ 'Lucas',
+ 'Luna',
+ 'Lynch',
+ 'Maldonado',
+ 'Marquez',
+ 'Marshall',
+ 'Martin',
+ 'Martinez',
+ 'Mason',
+ 'Matthews',
+ 'Mccoy',
+ 'Mcdonald',
+ 'Medina',
+ 'Mendez',
+ 'Mendoza',
+ 'Meyer',
+ 'Miller',
+ 'Mills',
+ 'Mitchell',
+ 'Montgomery',
+ 'Moore',
+ 'Morales',
+ 'Moreno',
+ 'Morgan',
+ 'Morris',
+ 'Morrison',
+ 'Munoz',
+ 'Murphy',
+ 'Murray',
+ 'Myers',
+ 'Name',
+ 'Nelson',
+ 'Nguyen',
+ 'Nichols',
+ 'Nunez',
+ 'Obrien',
+ 'Oliver',
+ 'Olson',
+ 'Ortega',
+ 'Ortiz',
+ 'Owens',
+ 'Padilla',
+ 'Palmer',
+ 'Park',
+ 'Parker',
+ 'Patel',
+ 'Patterson',
+ 'Payne',
+ 'Pena',
+ 'Perez',
+ 'Perkins',
+ 'Perry',
+ 'Peters',
+ 'Peterson',
+ 'Phillips',
+ 'Pierce',
+ 'Porter',
+ 'Powell',
+ 'Price',
+ 'Ramirez',
+ 'Ramos',
+ 'Ray',
+ 'Reed',
+ 'Reid',
+ 'Reyes',
+ 'Reynolds',
+ 'Rice',
+ 'Richards',
+ 'Richardson',
+ 'Riley',
+ 'Rios',
+ 'Rivera',
+ 'Roberts',
+ 'Robertson',
+ 'Robinson',
+ 'Rodriguez',
+ 'Rogers',
+ 'Rojas',
+ 'Romero',
+ 'Rose',
+ 'Ross',
+ 'Ruiz',
+ 'Russell',
+ 'Ryan',
+ 'Salazar',
+ 'Sanchez',
+ 'Sanders',
+ 'Sandoval',
+ 'Santiago',
+ 'Santos',
+ 'Schmidt',
+ 'Schultz',
+ 'Scott',
+ 'Shaw',
+ 'Silva',
+ 'Simmons',
+ 'Simpson',
+ 'Sims',
+ 'Singh',
+ 'Smith',
+ 'Snyder',
+ 'Soto',
+ 'Spencer',
+ 'Stephens',
+ 'Stevens',
+ 'Stewart',
+ 'Stone',
+ 'Sullivan',
+ 'Taylor',
+ 'Thomas',
+ 'Thompson',
+ 'Torres',
+ 'Tran',
+ 'Tucker',
+ 'Turner',
+ 'Valdez',
+ 'Vargas',
+ 'Vasquez',
+ 'Vazquez',
+ 'Vega',
+ 'Wagner',
+ 'Walker',
+ 'Wallace',
+ 'Walsh',
+ 'Wang',
+ 'Ward',
+ 'Warren',
+ 'Washington',
+ 'Watkins',
+ 'Watson',
+ 'Weaver',
+ 'Webb',
+ 'Weber',
+ 'Welch',
+ 'Wells',
+ 'West',
+ 'Wheeler',
+ 'White',
+ 'Williams',
+ 'Williamson',
+ 'Willis',
+ 'Wilson',
+ 'Wong',
+ 'Wood',
+ 'Woods',
+ 'Wright',
+ 'Yang',
+ 'Young'
+];
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/Ukraine/bundle.ts b/packages/plugins/src/countries/Ukraine/bundle.ts
new file mode 100644
index 000000000..49733a92b
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/bundle.ts
@@ -0,0 +1,190 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Spain: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ countrySlug: 'ukraine',
+ regionNames: i18n.regionNames,
+ continent: 'europe',
+ extendedData: {
+ zipFormat: {
+ // could be more specific
+ // https://en.wikipedia.org/wiki/Postal_codes_in_Ukraine
+ format: 'XX-XX'
+ },
+ phoneFormat: {
+ displayFormats: ['XXX-XX-XX']
+ }
+ },
+ regions: [
+ {
+ regionName: 'Cherkasy oblast',
+ regionShort: 'CK',
+ regionSlug: 'cherkasy',
+ weight: 12,
+ cities: ['Cherkasy', 'Uman', 'Smila']
+ },
+ {
+ regionName: 'Chernihiv oblast',
+ regionShort: 'CH',
+ regionSlug: 'chernihiv',
+ weight: 10,
+ cities: ['Chernihiv', 'Nizhyn']
+ },
+ {
+ regionName: 'Chernivtsi oblast',
+ regionShort: 'CV',
+ regionSlug: 'chernivtsi',
+ weight: 9,
+ cities: ['Chernivtsi', 'Kitsman', 'Vashkivtsi']
+ },
+ {
+ regionName: 'Dnipropetrovsk oblast',
+ regionShort: 'DP',
+ regionSlug: 'dnipropetrovsk',
+ weight: 32,
+ cities: ['Dnipro', 'Kryvyi Rih', 'Kamianske', 'Nikopol', 'Pavlohrad', 'Novomoskovsk']
+ },
+ {
+ regionName: 'Donetsk oblast',
+ regionShort: 'DT',
+ regionSlug: 'donetsk',
+ weight: 42,
+ cities: ['Donetsk', 'Mariupol', 'Makiivka', 'Horlivka', 'Kramatorsk', 'Sloviansk', 'Yenakiieve', 'Bakhmut', 'Kostiantynivka']
+ },
+ {
+ regionName: 'Ivano-Frankivsk oblast',
+ regionShort: 'IF',
+ regionSlug: 'ivano-frankivsk',
+ weight: 14,
+ cities: ['Ivano-Frankivsk', 'Kalush']
+ },
+ {
+ regionName: 'Kharkiv oblast',
+ regionShort: 'KK',
+ regionSlug: 'kharkiv',
+ weight: 27,
+ cities: ['Kharkiv', 'Lozova', 'Izium']
+ },
+ {
+ regionName: 'Kherson oblast',
+ regionShort: 'KS',
+ regionSlug: 'kherson',
+ weight: 10,
+ cities: ['Kherson', 'Nova Kakhovka']
+ },
+ {
+ regionName: 'Khmelnytskyi oblast',
+ regionShort: 'KM',
+ regionSlug: 'khmelnytskyi',
+ weight: 13,
+ cities: ['Khmelnytskyi', 'Kamianets-Podilskyi']
+ },
+ {
+ regionName: 'Kyiv oblast',
+ regionShort: 'KV',
+ regionSlug: 'kyiv',
+ weight: 18,
+ cities: ['Kyiv', 'Bila Tserkva', 'Brovary', 'Boryspil', 'Irpin']
+ },
+ {
+ regionName: 'Kirovohrad oblast',
+ regionShort: 'KH',
+ regionSlug: 'kirovohrad',
+ weight: 9,
+ cities: ['Kropyvnytskyi', 'Oleksandriia']
+ },
+ {
+ regionName: 'Luhansk oblast',
+ regionShort: 'LH',
+ regionSlug: 'luhansk',
+ weight: 22,
+ cities: ['Luhansk', 'Alchevsk', 'Sievierodonetsk', 'Lysychansk', 'Khrustalnyi', 'Kadiyivka', 'Dovzhansk']
+ },
+ {
+ regionName: 'Lviv oblast',
+ regionShort: 'LV',
+ regionSlug: 'lviv',
+ weight: 25,
+ cities: ['Lviv', 'Drohobych', 'Chervonohrad']
+ },
+ {
+ regionName: 'Mykolaiv oblast',
+ regionShort: 'MY',
+ regionSlug: 'mykolaiv',
+ weight: 11,
+ cities: ['Mykolaiv', 'Pervomaisk']
+ },
+ {
+ regionName: 'Odessa oblast',
+ regionShort: 'OD',
+ regionSlug: 'odessa',
+ weight: 24,
+ cities: ['Odessa', 'Izmail']
+ },
+ {
+ regionName: 'Poltava oblast',
+ regionShort: 'PL',
+ regionSlug: 'poltava',
+ weight: 14,
+ cities: ['Poltava', 'Kremenchuk']
+ },
+ {
+ regionName: 'Rivne oblast',
+ regionShort: 'RV',
+ regionSlug: 'rivne',
+ weight: 12,
+ cities: ['Rivne', 'Varash', 'Dubno']
+ },
+ {
+ regionName: 'Sumy oblast',
+ regionShort: 'SM',
+ regionSlug: 'sumy',
+ weight: 11,
+ cities: ['Sumy', 'Konotop', 'Shostka']
+ },
+ {
+ regionName: 'Ternopil oblast',
+ regionShort: 'TP',
+ regionSlug: 'ternopil',
+ weight: 10,
+ cities: ['Ternopil', 'Chortkiv']
+ },
+ {
+ regionName: 'Vinnytsia oblast',
+ regionShort: 'VI',
+ regionSlug: 'vinnytsia',
+ weight: 16,
+ cities: ['Vinnytsia', 'Khmilnyk', 'Haisyn']
+ },
+ {
+ regionName: 'Volyn oblast',
+ regionShort: 'VO',
+ regionSlug: 'volyn',
+ weight: 10,
+ cities: ['Lutsk', 'Kovel']
+ },
+ {
+ regionName: 'Zakarpattia oblast',
+ regionShort: 'ZK',
+ regionSlug: 'zakarpattia',
+ weight: 13,
+ cities: ['Uzhhorod', 'Mukachevo']
+ },
+ {
+ regionName: 'Zaporizhzhia oblast',
+ regionShort: 'ZP',
+ regionSlug: 'zaporizhzhia',
+ weight: 17,
+ cities: ['Zaporizhzhia', 'Melitopol', 'Berdiansk']
+ },
+ {
+ regionName: 'Zhytomyr oblast',
+ regionShort: 'ZT',
+ regionSlug: 'zhytomyr',
+ weight: 12,
+ cities: ['Zhytomyr', 'Berdychiv', 'Korosten']
+ }
+ ]
+});
+
+export default Spain;
diff --git a/packages/plugins/src/countries/Ukraine/i18n/ar.json b/packages/plugins/src/countries/Ukraine/i18n/ar.json
new file mode 100644
index 000000000..c59370896
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "أوكرانيا",
+ "regionNames": "مقاطعات أوكرانيا"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/de.json b/packages/plugins/src/countries/Ukraine/i18n/de.json
new file mode 100644
index 000000000..e61be09af
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ukraine",
+ "regionNames": "Oblaste der Ukraine"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/en.json b/packages/plugins/src/countries/Ukraine/i18n/en.json
new file mode 100644
index 000000000..9a2eacd7b
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ukraine",
+ "regionNames": "Ukrainian Oblasts"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/es.json b/packages/plugins/src/countries/Ukraine/i18n/es.json
new file mode 100644
index 000000000..2ad9504f1
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ucrania",
+ "regionNames": "Óblasts de Ucrania"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/fr.json b/packages/plugins/src/countries/Ukraine/i18n/fr.json
new file mode 100644
index 000000000..f02a3428e
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ukraine",
+ "regionNames": "Oblasts ukrainiens"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/hi.json b/packages/plugins/src/countries/Ukraine/i18n/hi.json
new file mode 100644
index 000000000..d5b49ab88
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "यूक्रेन",
+ "regionNames": "यूक्रेनियन ओब्लास्ट"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/ja.json b/packages/plugins/src/countries/Ukraine/i18n/ja.json
new file mode 100644
index 000000000..5933248cc
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ウクライナ",
+ "regionNames": "ウクライナの州"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/nl.json b/packages/plugins/src/countries/Ukraine/i18n/nl.json
new file mode 100644
index 000000000..62e367435
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Oekraïne",
+ "regionNames": "Oekraïense oblasten"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/pt.json b/packages/plugins/src/countries/Ukraine/i18n/pt.json
new file mode 100644
index 000000000..664c4ebdb
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Ucrânia",
+ "regionNames": "Oblasts Ucranianos"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/ru.json b/packages/plugins/src/countries/Ukraine/i18n/ru.json
new file mode 100644
index 000000000..3cb25f088
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Украина",
+ "regionNames": "Украинские области"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/ta.json b/packages/plugins/src/countries/Ukraine/i18n/ta.json
new file mode 100644
index 000000000..c6b2b9081
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "உக்ரைன்",
+ "regionNames": "உக்ரேனிய மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Ukraine/i18n/zh.json b/packages/plugins/src/countries/Ukraine/i18n/zh.json
new file mode 100644
index 000000000..d45c0ff7b
--- /dev/null
+++ b/packages/plugins/src/countries/Ukraine/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "乌克兰",
+ "regionNames": "乌克兰州"
+}
diff --git a/packages/plugins/src/countries/Vietnam/bundle.ts b/packages/plugins/src/countries/Vietnam/bundle.ts
new file mode 100644
index 000000000..6973de869
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/bundle.ts
@@ -0,0 +1,586 @@
+import { GetCountryData } from '@generatedata/types';
+
+const Vietnam: GetCountryData = (i18n) => ({
+ countryName: i18n.countryName,
+ continent: 'asia',
+ countrySlug: 'VN',
+ regionNames: i18n.regionNames, // + 5 Municipalities which are the 5 largest cities
+
+ extendedData: {
+ // the general zip format for the country. This may be optionally overridden for each region if a more
+ // specific format is desired. To prevent duplicate code, the replacements listed here cover ALL zip formats
+ // for each province
+ zipFormat: {
+ format: '%*****',
+ replacements: {
+ '%': '123456789',
+ '*': '0123456789'
+ }
+ },
+
+ // the general phone format and area codes for the country
+ // https://en.wikipedia.org/wiki/Telephone_numbers_in_Vietnam
+ phoneFormat: {
+ areaCodes: [
+ 203,
+ 204,
+ 205,
+ 206,
+ 207,
+ 208,
+ 209, // Landlines
+ 210,
+ 211,
+ 212,
+ 213,
+ 214,
+ 215,
+ 216,
+ 218,
+ 219,
+ 220,
+ 221,
+ 222,
+ 225,
+ 226,
+ 227,
+ 228,
+ 229,
+ 232,
+ 233,
+ 234,
+ 235,
+ 236,
+ 237,
+ 238,
+ 239,
+ 242,
+ 243,
+ 244,
+ 245,
+ 246,
+ 247,
+ 248,
+ 251,
+ 252,
+ 254,
+ 255,
+ 256,
+ 257,
+ 258,
+ 259,
+ 260,
+ 261,
+ 262,
+ 263,
+ 269,
+ 270,
+ 271,
+ 272,
+ 273,
+ 274,
+ 275,
+ 276,
+ 277,
+ 278,
+ 279,
+ 282,
+ 283,
+ 284,
+ 285,
+ 286,
+ 287,
+ 290,
+ 291,
+ 292,
+ 293,
+ 294,
+ 296,
+ 297,
+ 299
+ ],
+ displayFormats: [
+ '(AAA) Xxx-xxxx',
+ '+84 AAAXxxxxxx',
+ '+84 (AAA) Xxx-xxxx', // Outside Vietnam use 84 as Country Calling Code
+ '+84-AAA-Xxx-xxxx'
+ ]
+ }
+ },
+
+ // our country-wide data, with info separated into regions
+ // https://en.wikipedia.org/wiki/Vietnam
+ regions: [
+ // Red River Delta
+ {
+ regionName: 'Bắc Ninh',
+ regionShort: 'Bắc Ninh',
+ regionSlug: 'bac_ninh',
+ weight: 1,
+ cities: ['Bắc Ninh', 'Từ Sơn', 'Hồ', 'Phố Mới', 'Lim', 'Chờ', 'Gia Bình', 'Thứa']
+ },
+ {
+ regionName: 'Hà Nam',
+ regionShort: 'Hà Nam',
+ regionSlug: 'ha_nam',
+ weight: 1,
+ cities: ['Phủ Lý', 'Bình Mỹ', 'Hòa Mạc', 'Quế', 'Vĩnh Trụ', 'Kiện Khê']
+ },
+ {
+ regionName: 'Hải Dương',
+ regionShort: 'Hải Dương',
+ regionSlug: 'hai_duong',
+ weight: 1,
+ cities: [
+ 'Hải Dương',
+ 'Chí Linh',
+ 'Kẻ Sặt',
+ 'Lai Cách',
+ 'Gia Lộc',
+ 'Phú Thái',
+ 'Kinh Môn',
+ 'Nam Sách',
+ 'Ninh Giang',
+ 'Thanh Hà',
+ 'Thanh Miện',
+ 'Tứ Kỳ'
+ ]
+ },
+ {
+ regionName: 'Hưng Yên',
+ regionShort: 'Hưng Yên',
+ regionSlug: 'hung_yen',
+ weight: 1,
+ cities: ['Hưng Yên', 'Mỹ Hào', 'Ân Thi', 'Khoái Châu', 'Lương Bằng', 'Trần Cao', 'Vương', 'Văn Giang', 'Như Quỳnh', 'Yên Mỹ']
+ },
+ {
+ regionName: 'Nam Định',
+ regionShort: 'Nam Định',
+ regionSlug: 'nam_dinh',
+ weight: 1,
+ cities: ['Nam Định', 'Ngô Đồng', 'Yên Định', 'Mỹ Lộc', 'Nam Giang', 'Liễu Đề', 'Cổ Lễ', 'Gôi', 'Xuân Trường', 'Lâm']
+ },
+ {
+ regionName: 'Ninh Bình',
+ regionShort: 'Ninh Bình',
+ regionSlug: 'ninh_binh',
+ weight: 1,
+ cities: ['Ninh Bình', 'Tam Điệp', 'Me', 'Thiên Tôn', 'Phát Diệm', 'Nho Quan', 'Yên Ninh', 'Yên Thịnh']
+ },
+ {
+ regionName: 'Thái Bình',
+ regionShort: 'Thái Bình',
+ regionSlug: 'thai_binh',
+ weight: 1,
+ cities: ['Thái Bình', 'Vũ Thư', 'Tiền Hải', 'Diêm Điền', 'Quỳnh Côi', 'Thanh Nê', 'Hưng Hà', 'Đông Hưng']
+ },
+ {
+ regionName: 'Vĩnh Phúc',
+ regionShort: 'Vĩnh Phúc',
+ regionSlug: 'vinh_phuc',
+ weight: 1,
+ cities: ['Vĩnh Yên', 'Phúc Yên', 'Yên Lạc', 'Vĩnh Tường', 'Hợp Hòa', 'Tam Đảo', 'Hoa Sơn', 'Hương Canh']
+ },
+ {
+ regionName: 'Hà Nội',
+ regionShort: 'Hà Nội',
+ regionSlug: 'ha_noi_city',
+ weight: 1,
+ cities: ['Hà Nội']
+ },
+ {
+ regionName: 'Hải Phòng',
+ regionShort: 'Hải Phòng',
+ regionSlug: 'hai_phong_city',
+ weight: 1,
+ cities: ['Hải Phòng']
+ },
+
+ // Northeast Region
+ {
+ regionName: 'Bắc Giang',
+ regionShort: 'Bắc Giang',
+ regionSlug: 'bac_giang',
+ weight: 1,
+ cities: ['Bắc Giang']
+ },
+ {
+ regionName: 'Bắc Kạn',
+ regionShort: 'Bắc Kạn',
+ regionSlug: 'bac_kan',
+ weight: 1,
+ cities: ['Bắc Kạn']
+ },
+ {
+ regionName: 'Cao Bằng',
+ regionShort: 'Cao Bằng',
+ regionSlug: 'cao_bang',
+ weight: 1,
+ cities: ['Cao Bằng']
+ },
+ {
+ regionName: 'Hà Giang',
+ regionShort: 'Hà Giang',
+ regionSlug: 'ha_giang',
+ weight: 1,
+ cities: ['Hà Giang']
+ },
+ {
+ regionName: 'Lạng Sơn',
+ regionShort: 'Lạng Sơn',
+ regionSlug: 'lang_son',
+ weight: 1,
+ cities: ['Lạng Sơn']
+ },
+ {
+ regionName: 'Lào Cai',
+ regionShort: 'Lào Cai',
+ regionSlug: 'lao_cai',
+ weight: 1,
+ cities: ['Lào Cai']
+ },
+ {
+ regionName: 'Phú Thọ',
+ regionShort: 'Phú Thọ',
+ regionSlug: 'phu_tho',
+ weight: 1,
+ cities: ['Việt Trì']
+ },
+ {
+ regionName: 'Quảng Ninh',
+ regionShort: 'Quảng Ninh',
+ regionSlug: 'quang_ninh',
+ weight: 1,
+ cities: ['Hạ Long']
+ },
+ {
+ regionName: 'Thái Nguyên',
+ regionShort: 'Thái Nguyên',
+ regionSlug: 'thai_nguyen',
+ weight: 1,
+ cities: ['Thái Nguyên']
+ },
+ {
+ regionName: 'Tuyên Quang',
+ regionShort: 'Tuyên Quang',
+ regionSlug: 'tuyen_quang',
+ weight: 1,
+ cities: ['Tuyên Quang']
+ },
+ {
+ regionName: 'Yên Bái',
+ regionShort: 'Yên Bái',
+ regionSlug: 'yen_bai',
+ weight: 1,
+ cities: ['Yên Bái', 'Nghĩa Lộ']
+ },
+
+ // Northwest Region
+ {
+ regionName: 'Điện Biên',
+ regionShort: 'Điện Biên',
+ regionSlug: 'dien_bien',
+ weight: 1,
+ cities: ['Điện Biên Phủ', 'Mường Lay']
+ },
+ {
+ regionName: 'Hòa Bình',
+ regionShort: 'Hòa Bình',
+ regionSlug: 'hoa_binh',
+ weight: 1,
+ cities: ['Hòa Bình', 'Cao Phong', 'Bo', 'Kỳ Sơn']
+ },
+ {
+ regionName: 'Lai Châu',
+ regionShort: 'Lai Châu',
+ regionSlug: 'lai_chau',
+ weight: 1,
+ cities: ['Lai Châu', 'Mường Tè', 'Nậm Nhùn', 'Phong Thổ', 'Sìn Hồ', 'Tam Đường', 'Tân Uyên', 'Than Uyên']
+ },
+ {
+ regionName: 'Sơn La',
+ regionShort: 'Sơn La',
+ regionSlug: 'son_la',
+ weight: 1,
+ cities: ['Sơn La', 'Bắc Yên', 'Hát Lót', 'Mộc Châu', 'Ong', 'Phú Yên', 'Quỳnh Nhai', 'Sông Mã', 'Sốp Cộp', 'Thuận Châu', 'Yên Châu']
+ },
+
+ // North Central Coast
+ {
+ regionName: 'Hà Tĩnh',
+ regionShort: 'Hà Tĩnh',
+ regionSlug: 'ha_tinh',
+ weight: 1,
+ cities: ['Hà Tĩnh']
+ },
+ {
+ regionName: 'Nghệ An',
+ regionShort: 'Nghệ An',
+ regionSlug: 'nghe_an',
+ weight: 1,
+ cities: ['Vinh']
+ },
+ {
+ regionName: 'Quảng Bình',
+ regionShort: 'Quảng Bình',
+ regionSlug: 'quang_binh',
+ weight: 1,
+ cities: ['Đồng Hới']
+ },
+ {
+ regionName: 'Quảng Trị',
+ regionShort: 'Quảng Trị',
+ regionSlug: 'quang_tri',
+ weight: 1,
+ cities: ['Đông Hà']
+ },
+ {
+ regionName: 'Thanh Hóa',
+ regionShort: 'Thanh Hóa',
+ regionSlug: 'thanh_hoa',
+ weight: 1,
+ cities: ['Thanh Hóa']
+ },
+ {
+ regionName: 'Thừa Thiên–Huế',
+ regionShort: 'Thừa Thiên–Huế',
+ regionSlug: 'thua_thien-hue',
+ weight: 1,
+ cities: ['Huế']
+ },
+
+ // Central Highlands
+ {
+ regionName: 'Đắk Lắk',
+ regionShort: 'Đắk Lắk',
+ regionSlug: 'dak_lak',
+ weight: 1,
+ cities: ['Buôn Ma Thuột']
+ },
+ {
+ regionName: 'Đắk Nông',
+ regionShort: 'Đắk Nông',
+ regionSlug: 'dak_nong',
+ weight: 1,
+ cities: ['Gia Nghĩa']
+ },
+ {
+ regionName: 'Gia Lai',
+ regionShort: 'Gia Lai',
+ regionSlug: 'gia_lai',
+ weight: 1,
+ cities: ['Pleiku']
+ },
+ {
+ regionName: 'Kon Tum',
+ regionShort: 'Kon Tum',
+ regionSlug: 'kon_tum',
+ weight: 1,
+ cities: ['Kon Tum']
+ },
+ {
+ regionName: 'Lâm Đồng',
+ regionShort: 'Lâm Đồng',
+ regionSlug: 'lam_dong',
+ weight: 1,
+ cities: ['Da Lat']
+ },
+
+ // South Central Coast
+ {
+ regionName: 'Bình Định',
+ regionShort: 'Bình Định',
+ regionSlug: 'binh_dinh',
+ weight: 1,
+ cities: ['Quy Nhơn']
+ },
+ {
+ regionName: 'Bình Thuận',
+ regionShort: 'Bình Thuận',
+ regionSlug: 'binh_thuan',
+ weight: 1,
+ cities: ['Phan Thiết']
+ },
+ {
+ regionName: 'Khánh Hòa',
+ regionShort: 'Khánh Hòa',
+ regionSlug: 'khanh_hoa',
+ weight: 1,
+ cities: ['Nha Trang']
+ },
+ {
+ regionName: 'Ninh Thuận',
+ regionShort: 'Ninh Thuận',
+ regionSlug: 'ninh_thuan',
+ weight: 1,
+ cities: ['Phan Rang–Tháp Chàm']
+ },
+ {
+ regionName: 'Phú Yên',
+ regionShort: 'Phú Yên',
+ regionSlug: 'phu_yen',
+ weight: 1,
+ cities: ['Tuy Hòa']
+ },
+ {
+ regionName: 'Quảng Nam',
+ regionShort: 'Quảng Nam',
+ regionSlug: 'quang_nam',
+ weight: 1,
+ cities: ['Tam Kỳ']
+ },
+ {
+ regionName: 'Quảng Ngãi',
+ regionShort: 'Quảng Ngãi',
+ regionSlug: 'quang_ngai',
+ weight: 1,
+ cities: ['Quảng Ngãi']
+ },
+ {
+ regionName: 'Đà Nẵng',
+ regionShort: 'Đà Nẵng',
+ regionSlug: 'da_nang_city',
+ weight: 1,
+ cities: ['Đà Nẵng']
+ },
+
+ // Southeast
+ {
+ regionName: 'Bà Rịa–Vũng Tàu',
+ regionShort: 'Bà Rịa–Vũng Tàu',
+ regionSlug: 'ba_ria-vung_tau',
+ weight: 1,
+ cities: ['Quảng Ngãi']
+ },
+ {
+ regionName: 'Bình Dương',
+ regionShort: 'Bình Dương',
+ regionSlug: 'binh_duong',
+ weight: 1,
+ cities: ['Thủ Dầu Một']
+ },
+ {
+ regionName: 'Bình Phước',
+ regionShort: 'Bình Phước',
+ regionSlug: 'binh_phuoc',
+ weight: 1,
+ cities: ['Đồng Xoài']
+ },
+ {
+ regionName: 'Đồng Nai',
+ regionShort: 'Đồng Nai',
+ regionSlug: 'dong_nai',
+ weight: 1,
+ cities: ['Biên Hòa']
+ },
+ {
+ regionName: 'Tây Ninh',
+ regionShort: 'Tây Ninh',
+ regionSlug: 'tag_ninh',
+ weight: 1,
+ cities: ['Tây Ninh']
+ },
+ {
+ regionName: 'Hồ Chí Minh City',
+ regionShort: 'Hồ Chí Minh City',
+ regionSlug: 'ho_chi_minh_city',
+ weight: 1,
+ cities: ['Hồ Chí Minh City']
+ },
+
+ // Mekong Delta
+ {
+ regionName: 'An Giang',
+ regionShort: 'An Giang',
+ regionSlug: 'an_giang',
+ weight: 1,
+ cities: ['Long Xuyên']
+ },
+ {
+ regionName: 'Bạc Liêu',
+ regionShort: 'Bạc Liêu',
+ regionSlug: 'bac_lieu',
+ weight: 1,
+ cities: ['Bạc Liêu']
+ },
+ {
+ regionName: 'Bến Tre',
+ regionShort: 'Bến Tre',
+ regionSlug: 'ben_tre',
+ weight: 1,
+ cities: ['Bến Tre']
+ },
+ {
+ regionName: 'Cà Mau',
+ regionShort: 'Cà Mau',
+ regionSlug: 'ca_mau',
+ weight: 1,
+ cities: ['Cà Mau']
+ },
+ {
+ regionName: 'Đồng Tháp',
+ regionShort: 'Đồng Tháp',
+ regionSlug: 'dong_thap',
+ weight: 1,
+ cities: ['Cao Lãnh']
+ },
+ {
+ regionName: 'Hậu Giang',
+ regionShort: 'Hậu Giang',
+ regionSlug: 'hau_giang',
+ weight: 1,
+ cities: ['Vị Thanh']
+ },
+ {
+ regionName: 'Kiên Giang',
+ regionShort: 'Kiên Giang',
+ regionSlug: 'kien_giang',
+ weight: 1,
+ cities: ['Rạch Giá']
+ },
+ {
+ regionName: 'Long An',
+ regionShort: 'Long An',
+ regionSlug: 'long_an',
+ weight: 1,
+ cities: ['Tân An']
+ },
+ {
+ regionName: 'Sóc Trăng',
+ regionShort: 'Sóc Trăng',
+ regionSlug: 'soc_trang',
+ weight: 1,
+ cities: ['Sóc Trăng']
+ },
+ {
+ regionName: 'Tiền Giang',
+ regionShort: 'Tiền Giang',
+ regionSlug: 'tien_gian',
+ weight: 1,
+ cities: ['Mỹ Tho']
+ },
+ {
+ regionName: 'Trà Vinh',
+ regionShort: 'Trà Vinh',
+ regionSlug: 'tra_vinh',
+ weight: 1,
+ cities: ['Trà Vinh']
+ },
+ {
+ regionName: 'Vĩnh Long',
+ regionShort: 'Vĩnh Long',
+ regionSlug: 'vinh_long',
+ weight: 1,
+ cities: ['Vĩnh Long']
+ },
+ {
+ regionName: 'Cần Thơ',
+ regionShort: 'Cần Thơ',
+ regionSlug: 'can_tho',
+ weight: 1,
+ cities: ['Cần Thơ']
+ }
+ ]
+});
+
+export default Vietnam;
diff --git a/packages/plugins/src/countries/Vietnam/i18n/ar.json b/packages/plugins/src/countries/Vietnam/i18n/ar.json
new file mode 100644
index 000000000..e269a061e
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "فيتنام",
+ "regionNames": "المقاطعات الفيتنامية"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/de.json b/packages/plugins/src/countries/Vietnam/i18n/de.json
new file mode 100644
index 000000000..0ca250303
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Vietnam",
+ "regionNames": "Vietnam Provinzen"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/en.json b/packages/plugins/src/countries/Vietnam/i18n/en.json
new file mode 100644
index 000000000..021c089f8
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Vietnam",
+ "regionNames": "Vietnam regions"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/es.json b/packages/plugins/src/countries/Vietnam/i18n/es.json
new file mode 100644
index 000000000..ee109c7fb
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Vietnam",
+ "regionNames": "Provincias de Vietnam"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/fr.json b/packages/plugins/src/countries/Vietnam/i18n/fr.json
new file mode 100644
index 000000000..f877344a2
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Viêt Nam",
+ "regionNames": "Provinces du Vietnam"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/hi.json b/packages/plugins/src/countries/Vietnam/i18n/hi.json
new file mode 100644
index 000000000..5f8e3be02
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "वियतनाम",
+ "regionNames": "वियतनाम क्षेत्र"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/ja.json b/packages/plugins/src/countries/Vietnam/i18n/ja.json
new file mode 100644
index 000000000..5947d0d54
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "ベトナム",
+ "regionNames": "ベトナムの省"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/nl.json b/packages/plugins/src/countries/Vietnam/i18n/nl.json
new file mode 100644
index 000000000..31b35387d
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Vietnam",
+ "regionNames": "Vietnamese provincies"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/pt.json b/packages/plugins/src/countries/Vietnam/i18n/pt.json
new file mode 100644
index 000000000..83f8692ea
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Vietnã",
+ "regionNames": "Regiões vietnamitas"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/ru.json b/packages/plugins/src/countries/Vietnam/i18n/ru.json
new file mode 100644
index 000000000..52df45c19
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "Вьетнам",
+ "regionNames": "Вьетнам регионы"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/ta.json b/packages/plugins/src/countries/Vietnam/i18n/ta.json
new file mode 100644
index 000000000..aca579d27
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "வியட்நாம்",
+ "regionNames": "வியட்நாம் மாகாணங்கள்"
+}
diff --git a/packages/plugins/src/countries/Vietnam/i18n/zh.json b/packages/plugins/src/countries/Vietnam/i18n/zh.json
new file mode 100644
index 000000000..1247b2eff
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "countryName": "越南",
+ "regionNames": "越南各省"
+}
diff --git a/packages/plugins/src/countries/Vietnam/names.ts b/packages/plugins/src/countries/Vietnam/names.ts
new file mode 100644
index 000000000..08820f4ce
--- /dev/null
+++ b/packages/plugins/src/countries/Vietnam/names.ts
@@ -0,0 +1,410 @@
+import { CountryNames } from '@generatedata/types';
+
+const femaleNames = [
+ 'Alex',
+ 'Alissa',
+ 'An',
+ 'Anh',
+ 'Annie',
+ 'Bao Tran',
+ 'Bella',
+ 'Binh',
+ 'Cara',
+ 'Cat',
+ 'Charollte',
+ 'Chau Anh',
+ 'Chi',
+ 'Chloe',
+ 'Chu',
+ 'Da Hyun',
+ 'Diem Hang',
+ 'Eunice',
+ 'Fio',
+ 'Giang',
+ 'Ha',
+ 'Ha Tit',
+ 'Hai Anh',
+ 'Hana',
+ 'Hang',
+ 'Hoang',
+ 'Hoàng Anh',
+ 'Huong',
+ 'Huong Lan',
+ 'Huyen',
+ 'Isabella',
+ 'Jane',
+ 'Jiyoon',
+ 'Kaylee',
+ 'Khaingan',
+ 'Khanh',
+ 'Kim',
+ 'Lala',
+ 'Lam',
+ 'Lan',
+ 'Lien',
+ 'Liliane',
+ 'Linh',
+ 'Louis',
+ 'Maki',
+ 'Marielle',
+ 'Mellisa',
+ 'Merry',
+ 'Mia',
+ 'Min',
+ 'Minh',
+ 'Mo',
+ 'My',
+ 'Nabi',
+ 'Nga',
+ 'Ngan',
+ 'Nghi',
+ 'Ngoc',
+ 'Nguyen',
+ 'Nguyen Phuong Tra',
+ 'Nguyen Quynh Ngan',
+ 'Nguyen Thi',
+ 'Nguyet Quyen',
+ 'Ngọc',
+ 'Nhi',
+ 'Nhu',
+ 'Nhung',
+ 'Nhân',
+ 'Oanh',
+ 'Phung',
+ 'Phuong',
+ 'Quynh',
+ 'Quynh Anh',
+ 'Renesmee',
+ 'Sal',
+ 'Sam',
+ 'Sara',
+ 'Sue',
+ 'Sunny',
+ 'Sylvia',
+ 'Tg',
+ 'Thao',
+ 'Thu',
+ 'Thuan',
+ 'Thuy',
+ 'Thủy',
+ 'Tien',
+ 'Tina',
+ 'Tio',
+ 'Tr.li',
+ 'Tra',
+ 'Tracy',
+ 'Tram',
+ 'Trang',
+ 'Trinh',
+ 'Uyên',
+ 'Van',
+ 'Vi',
+ 'Vy',
+ 'Xuan Thuong'
+];
+
+const maleNames = [
+ 'Andy',
+ 'Anh Tuan',
+ 'Arif',
+ 'Bao',
+ 'Binh',
+ 'Bui',
+ 'Calvin',
+ 'Chien',
+ 'Dang',
+ 'Daniel',
+ 'Dao',
+ 'David',
+ 'Daòt',
+ 'Derick',
+ 'Dinh',
+ 'Dong',
+ 'Duc',
+ 'Duc Anh',
+ 'Duong',
+ 'Eddy',
+ 'Finney',
+ 'Garrett',
+ 'Hai',
+ 'Hao',
+ 'Harrison',
+ 'Harry',
+ 'Henry',
+ 'Hiep',
+ 'Hoang',
+ 'Hrishikesh',
+ 'Huy',
+ 'Huynh Chi Hai',
+ 'Hy Khánh',
+ 'Hyeon Gyu',
+ 'Hyun Sik',
+ 'Hùng',
+ 'Jackson',
+ 'James ',
+ 'Jason',
+ 'Jason',
+ 'Jie',
+ 'John',
+ 'Joontae',
+ 'Ken',
+ 'Khanh',
+ 'Kiet',
+ 'Kong',
+ 'Kwonmin',
+ 'Lam Anh',
+ 'Le',
+ 'Lee',
+ 'Lee Viert',
+ 'Liam',
+ 'Linh',
+ 'Minh',
+ 'Mop',
+ 'Nabi',
+ 'Nam',
+ 'Nathan',
+ 'Nghi',
+ 'Ngoc',
+ 'Nguyen',
+ 'Nhân',
+ 'Philip',
+ 'Phuc',
+ 'Phung Huu',
+ 'Quang',
+ 'Quyen',
+ 'Quyền',
+ 'Redge',
+ 'Ron',
+ 'Sam',
+ 'Tam',
+ 'Tan',
+ 'Thai Duong',
+ 'Thang',
+ 'Thanh',
+ 'Thien',
+ 'Thomas',
+ 'Thuan',
+ 'Tie',
+ 'Tien',
+ 'Toni',
+ 'Tony',
+ 'Tran',
+ 'Tran Van',
+ 'Tri',
+ 'Trinh',
+ 'Trung',
+ 'Tsundere',
+ 'Tuan',
+ 'Tuan Anh',
+ 'Tyuo(µµà±)',
+ 'Tú',
+ 'Van',
+ 'Viet',
+ 'Viet Phung',
+ 'Vũ',
+ 'Wade',
+ 'Williams'
+];
+
+const lastNames = [
+ 'An',
+ 'Bao',
+ 'Biên',
+ 'Biện',
+ 'Bành',
+ 'Bạch',
+ 'Cao',
+ 'Chiêm',
+ 'Chu',
+ 'Chung',
+ 'Chân',
+ 'Châu',
+ 'Cung',
+ 'Cung',
+ 'Cái',
+ 'Cát',
+ 'Cảnh',
+ 'Cảnh',
+ 'Cổ',
+ 'Củng',
+ 'Cừu',
+ 'Diệp',
+ 'Doãn',
+ 'Dung',
+ 'Dũ',
+ 'Dư',
+ 'Dịch',
+ 'Dữu',
+ 'Gia Cát',
+ 'Giang',
+ 'Giả',
+ 'Giản',
+ 'Hoa',
+ 'Hoạn',
+ 'Hoắc',
+ 'Hy',
+ 'Hà',
+ 'Hàn',
+ 'Hác',
+ 'Hình',
+ 'Hướng',
+ 'Hạ',
+ 'Hầu',
+ 'Hậ',
+ 'Hồng',
+ 'Hứa',
+ 'Kha',
+ 'Khuất',
+ 'Khâu',
+ 'Khổng',
+ 'Kim',
+ 'Kiều',
+ 'Kỳ',
+ 'Kỷ',
+ 'La',
+ 'Lai',
+ 'Lam',
+ 'Liên',
+ 'Liêu',
+ 'Liễu',
+ 'Long',
+ 'Lâm',
+ 'Lãnh',
+ 'Lôi',
+ 'Lăng',
+ 'Lư',
+ 'Lưu',
+ 'Lương',
+ 'Lạc',
+ 'Lệ',
+ 'Lục',
+ 'Lữ',
+ 'Mai',
+ 'Mao',
+ 'Minh',
+ 'Miêu',
+ 'Mã',
+ 'Mông',
+ 'Mạc',
+ 'Mạch',
+ 'Mạnh',
+ 'Mẫn',
+ 'Nghiêm',
+ 'Nghê',
+ 'Ngân',
+ 'Ngư',
+ 'Ngưu',
+ 'Nhan',
+ 'Nhiếp',
+ 'Nhiều',
+ 'Nhung',
+ 'Nhâm',
+ 'Nhạc',
+ 'Ninh',
+ 'Nông',
+ 'Phí',
+ 'Phòng',
+ 'Phó',
+ 'Phù',
+ 'Phùng',
+ 'Phương',
+ 'Quan',
+ 'Quang',
+ 'Quyền',
+ 'Quách',
+ 'Quản',
+ 'Quảng',
+ 'Quế',
+ 'Sài',
+ 'Sầm',
+ 'Sử',
+ 'Thai',
+ 'Thang',
+ 'Thanh Hóa',
+ 'Thi',
+ 'Thiện',
+ 'Thiệu',
+ 'Thành',
+ 'Thái',
+ 'Thân',
+ 'Thích',
+ 'Thôi',
+ 'Thư',
+ 'Thường',
+ 'Thạch',
+ 'Thảo',
+ 'Thủy',
+ 'Tiêu',
+ 'Tiêu',
+ 'Tiết',
+ 'Tiền',
+ 'Trang',
+ 'Triệu',
+ 'Trác',
+ 'Trâu',
+ 'Trì',
+ 'Trương',
+ 'Trạch',
+ 'Trại',
+ 'Trầm',
+ 'Trịnh',
+ 'Tào',
+ 'Tân',
+ 'Tô',
+ 'Tôn',
+ 'Tôn Thất',
+ 'Tông',
+ 'Tăng',
+ 'Tư Mã',
+ 'Tưởng',
+ 'Tạ',
+ 'Tất',
+ 'Tần',
+ 'Tề',
+ 'Tống',
+ 'Từ',
+ 'Vi',
+ 'Vân',
+ 'Văn',
+ 'Vĩnh',
+ 'Vũ',
+ 'Vũ Văn',
+ 'Vưu',
+ 'Vương',
+ 'Vạn',
+ 'Xà',
+ 'Xầm',
+ 'Xế',
+ 'Yên',
+ 'Yến',
+ 'Ái',
+ 'Ân',
+ 'Ôn',
+ 'Ông',
+ 'Úc',
+ 'Đinh',
+ 'Điền',
+ 'Đoàn',
+ 'Đàm',
+ 'Đào',
+ 'Đái',
+ 'Đường',
+ 'Đậu',
+ 'Đồ',
+ 'Đồng',
+ 'Đổng',
+ 'Ổn',
+ 'Ứng'
+];
+
+// a large proportion of surnames (40%) of Vietnamese are Nguyễn. Since we can't weight them, we just repeat them
+const num = Math.round((lastNames.length * 100) / 40);
+const names = Array(num).fill('Nguyễn');
+
+const namesData: CountryNames = {
+ femaleNames,
+ maleNames,
+ lastNames: [...lastNames, ...names]
+};
+
+export default namesData;
diff --git a/packages/plugins/src/countries/types.d.ts b/packages/plugins/src/countries/types.d.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/plugins/src/country.ts b/packages/plugins/src/country.ts
new file mode 100644
index 000000000..536fc9b46
--- /dev/null
+++ b/packages/plugins/src/country.ts
@@ -0,0 +1 @@
+// TODO move entire file to countries plugin section
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.generate.ts b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.generate.ts
new file mode 100644
index 000000000..dbbf2fe2b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.generate.ts
@@ -0,0 +1,16 @@
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = ({ rowState }: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ // for backward compatibility
+ const value = Array.isArray(rowState) ? rowState : rowState.value;
+
+ const formats = value.split('|');
+ let chosenFormat = formats[0];
+ if (formats.length > 1) {
+ chosenFormat = formats[utils.randomUtils.getRandomNum(0, formats.length - 1)];
+ }
+ const val = utils.randomUtils.generateRandomAlphanumericStr(chosenFormat);
+
+ return { display: val };
+};
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.scss b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.scss
new file mode 100644
index 000000000..af16e025f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.scss
@@ -0,0 +1,22 @@
+.row {
+ display: flex;
+ align-items: flex-start;
+ padding-bottom: 12px;
+
+ .col1 {
+ flex: 0 0 30px;
+ }
+ .col2 {
+ flex: 1;
+ }
+ .col3 {
+ flex: 0 0 30px;
+ }
+ .col4 {
+ flex: 1;
+ }
+}
+
+.copy {
+ margin: 4px 0 0 4px;
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.scss.d.ts b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.scss.d.ts
new file mode 100644
index 000000000..810bcfc7a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.scss.d.ts
@@ -0,0 +1,17 @@
+declare namespace AlphanumericScssNamespace {
+ export interface IAlphanumericScss {
+ col1: string;
+ col2: string;
+ col3: string;
+ col4: string;
+ copy: string;
+ row: string;
+ }
+}
+
+declare const AlphanumericScssModule: AlphanumericScssNamespace.IAlphanumericScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: AlphanumericScssNamespace.IAlphanumericScss;
+};
+
+export = AlphanumericScssModule;
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.state.tsx b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.state.tsx
new file mode 100644
index 000000000..ab0d43867
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.state.tsx
@@ -0,0 +1,17 @@
+export type GenerationOptionsType = {
+ value: string;
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ value: 'LLLxxLLLxLL'
+};
+
+export type AlphanumericState = {
+ example: string;
+ value: string;
+};
+
+export const initialState: AlphanumericState = {
+ example: 'LLLxxLLLxLL',
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.tsx b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.tsx
new file mode 100644
index 000000000..9f7094d50
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.tsx
@@ -0,0 +1,164 @@
+import * as React from 'react';
+import Dropdown from '~components/dropdown/Dropdown';
+import TextField from '~components/TextField';
+import CopyToClipboard from '~components/copyToClipboard/CopyToClipboard';
+import { DTExampleProps, DTOptionsProps, DTHelpProps, DTMetadata } from '~types/dataTypes';
+import { AlphanumericState, GenerationOptionsType } from './Alphanumeric.state';
+import styles from './Alphanumeric.scss';
+import sharedStyles from '../../../styles/shared.scss';
+
+const Copy = ({ content, tooltip, message }: any) => (
+
+
+
+);
+
+export const Example = ({ i18n, data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: any): void => {
+ onUpdate({
+ example: value,
+ value: value
+ });
+ };
+
+ const options = [
+ { value: 'LxL xLx', label: `V6M 4C1 ${i18n.exampleCanPostalCode}` },
+ { value: 'xxxxx', label: `90210 ${i18n.exampleUSZipCode}` },
+ { value: 'LLLxxLLLxLL', label: `eZg29gdF5K1 ${i18n.examplePassword}` }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+export const Options = ({ coreI18n, data, throttle, onUpdate }: DTOptionsProps) => {
+ const titleColError = data.value.trim() === '' ? coreI18n.requiredField : '';
+
+ return (
+ onUpdate({ ...data, value: e.target.value })}
+ style={{ width: '100%' }}
+ throttle={throttle}
+ />
+ );
+};
+Options.defaultProps = {
+ throttle: true
+};
+
+export const Help = ({ coreI18n, i18n }: DTHelpProps) => (
+ <>
+ {i18n.helpIntro}
+
+
+
+ L
+
+
+
+
+
+
+ V
+
+
+
+
+
+
+
+
+ l
+
+
+
+
+
+
+ v
+
+
+
+
+
+
+
+
+ D
+
+
+
+
+
+
+ F
+
+
+
+
+
+
+
+
+ C
+
+
+
+
+
+
+ x
+
+
+
+
+
+
+
+
+ c
+
+
+
+
+
+
+ X
+
+
+
+
+
+
+
+
+ E
+
+
+
+
+
+
+ H
+
+
+
+
+
+
+ >
+);
+
+export const rowStateReducer = (state: AlphanumericState): GenerationOptionsType => ({ value: state.value });
+
+export const getMetadata = (): DTMetadata => ({
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ },
+ general: {
+ dataType: 'infer'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.worker.ts b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.worker.ts
new file mode 100644
index 000000000..f07ab9bdf
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/Alphanumeric.worker.ts
@@ -0,0 +1,12 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Alphanumeric.generate';
+
+let utilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/README.md b/packages/plugins/src/dataTypes/Alphanumeric/README.md
new file mode 100644
index 000000000..f58874851
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/README.md
@@ -0,0 +1,150 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Alphanumeric
+
+This Data Type generates random alphanumeric strings in a custom format. This can be used for anything you fancy. The only
+limit is your imagination. (It's late, I can't believe I just typed that).
+
+## Typings
+
+The `settings` value for the `Alphanumeric` plugin `dataTemplate` entry contains a single `value` property, which is a
+string containing whatever placeholder characters you want. See the Examples section below to see how it all fits
+together.
+
+```typescript
+{
+ value: string;
+}
+```
+
+## Placeholders
+
+These are the available placeholders. Any character not specified here will be output as-is.
+
+```
+C, c, E - any consonant (upper case, lower case, any)
+V, v, F - any vowel (upper case, lower case, any)
+L, l, D - any letter (upper case, lower case, any)
+X - 1-9
+x - 0-9
+H - 0-F (hexidecimal value)
+```
+
+## Misc notes / caveats
+
+- To generate multiple different formats in a single field, just separate the `value` contents with a pipe, e.g.
+
+```javascript
+{
+ plugin: "Alphanumeric",
+ title: "multipleFormats",
+ settings: {
+ value: "Xxxxx|xxxx|CXxxx"
+ }
+}
+```
+
+- Currently you can't escape any of the placeholders listed above. Feature Request issue open here: https://github.com/benkeen/generatedata/issues/817
+- This is an older Data Type. Most of the new ones use an array for the value/options field within settings. At some point
+ we may update it for consistency.
+
+## Examples
+
+- [US zip code](#us-zip-code)
+- [Tag numbers](#tag-numbers)
+
+### US zip code
+
+Note, this can actually be produced more accurately with the [Postal/Zip](../PostalZip/README.md) Data Type. This is just
+for illustration purposes.
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "Alphanumeric",
+ title: "US Zip",
+ settings: {
+ value: "Xxxxx"
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "Zip": 42694
+ },
+ {
+ "Zip": 23645
+ },
+ {
+ "Zip": 12831
+ },
+ {
+ "Zip": 34048
+ },
+ {
+ "Zip": 51791
+ },
+ ...
+]
+```
+
+### Tag numbers
+
+This generates a fake clothing tag number of the format `C-152314-DG`.
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "Alphanumeric",
+ title: "TagNumber",
+ settings: {
+ value: "C-xxxxxx-CC"
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "TagNumber": "L-670724-GG"
+ },
+ {
+ "TagNumber": "S-371395-QY"
+ },
+ {
+ "TagNumber": "C-861426-QF"
+ },
+ {
+ "TagNumber": "F-750425-VY"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/__tests__/Alphanumeric.generate.test.ts b/packages/plugins/src/dataTypes/Alphanumeric/__tests__/Alphanumeric.generate.test.ts
new file mode 100644
index 000000000..68e3009a3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/__tests__/Alphanumeric.generate.test.ts
@@ -0,0 +1,33 @@
+import sinon from 'sinon';
+import utils from '../../../../utils';
+import { onmessage } from '../Alphanumeric.worker';
+import { initialState } from '../Alphanumeric.state';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+
+const i18n = require('../i18n/en.json');
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ it('generates random data', () => {
+ const payload: any = {
+ data: {
+ ...getBlankDTGeneratorPayload(),
+ rowState: {
+ value: initialState.value
+ },
+ i18n
+ }
+ };
+
+ sinon.stub(utils.randomUtils, 'generateRandomAlphanumericStr').returns('*****');
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: '*****' });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/__tests__/Alphanumeric.test.tsx b/packages/plugins/src/dataTypes/Alphanumeric/__tests__/Alphanumeric.test.tsx
new file mode 100644
index 000000000..5a63d111b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/__tests__/Alphanumeric.test.tsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import sinon from 'sinon';
+import { render, fireEvent } from '@testing-library/react';
+import { Options, Help, Example } from '../Alphanumeric';
+import { initialState } from '../Alphanumeric.state';
+import * as langUtils from '~utils/langUtils';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ gridPanelDimensions: { width: 100, height: 100 },
+ isCountryNamesLoading: false,
+ isCountryNamesLoaded: false,
+ countryNamesMap: null
+};
+
+describe('Example', () => {
+ it('renders', () => {
+ sinon.stub(langUtils, 'getStrings').returns({ core: {} });
+
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('Options', () => {
+ it('changing the content should call callback', () => {
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+ const { container } = render( );
+ const field = container.querySelector('input');
+
+ // @ts-ignore
+ fireEvent.change(field, {
+ target: {
+ value: 'new value!!'
+ }
+ });
+
+ expect(onUpdate).toBeCalledWith({
+ ...initialState,
+ value: 'new value!!'
+ });
+ });
+});
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/bundle.ts b/packages/plugins/src/dataTypes/Alphanumeric/bundle.ts
new file mode 100644
index 000000000..724d3131a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, rowStateReducer, getMetadata } from './Alphanumeric';
+import { initialState } from './Alphanumeric.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ rowStateReducer,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/config.ts b/packages/plugins/src/dataTypes/Alphanumeric/config.ts
new file mode 100644
index 000000000..e1e661969
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'numeric',
+ fieldGroupOrder: 10
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/ar.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ar.json
new file mode 100644
index 000000000..787f97c5a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ar.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "أبجدي رقمي",
+ "DESC": "يولد سلسلة أبجدية رقمية عشوائية مخصصة أو أي طول أو تنسيق محدد بواسطة أحرف العنصر النائب.",
+ "DEFAULT_TITLE": "أبجدي رقمي",
+ "exampleCanPostalCode": "(الرمز البريدي الكندي)",
+ "examplePassword": "(كلمه السر)",
+ "exampleUSZipCode": "(الرمز البريدي للولايات المتحدة)",
+ "help1": "حرف كبير L حرف.",
+ "help10": "أي رقم ، 1-9.",
+ "help11": "ساكن (علوي أو سفلي).",
+ "help12": "رقم سداسي عشري (0-F)",
+ "help2": "حرف متحرك كبير.",
+ "help3": "حرف صغير.",
+ "help4": "حرف متحرك صغير.",
+ "help5": "حرف (علوي أو سفلي).",
+ "help6": "حرف علة (علوي أو سفلي).",
+ "help7": "حرف ساكن كبير.",
+ "help8": "أي رقم ، 0-9.",
+ "help9": "حرف ساكن صغير.",
+ "helpIntro": "يتيح لك نوع البيانات هذا إنشاء سلاسل أبجدية رقمية عشوائية. يحتوي الجدول التالي على وسيلة إيضاح الأحرف لهذا الحقل. ستظهر أية أحرف أخرى تدخلها في هذا الحقل بدون إلغاء....",
+ "incompleteFields": "تتطلب الحقول الأبجدية الرقمية إدخال التنسيق في حقل نص الخيارات. الرجاء إصلاح الصفوف التالية:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/de.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/de.json
new file mode 100644
index 000000000..ccfbe8b65
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/de.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "Alphanumerisch",
+ "DESC": "Erzeugt eine benutzerdefinierte zufällige alphanumerische Zeichenfolge oder Länge oder Format , durch Platzhalter Zeichen definiert.",
+ "DEFAULT_TITLE": "alphanumerisch",
+ "exampleCanPostalCode": "(Can. PLZ)",
+ "examplePassword": "(Password)",
+ "exampleUSZipCode": "(US Postleitzahl)",
+ "help1": "Ein Groß-L etter.",
+ "help10": "Jede Anzahl, 1-9.",
+ "help11": "Ein Konsonant (obere oder untere).",
+ "help12": "Ein H exidecimal Nummer (0-F)",
+ "help2": "Ein Groß-V owel.",
+ "help3": "Ein Kleinbuchstaben l etter.",
+ "help4": "Ein Kleinbuchstaben v owel.",
+ "help5": "Ein Brief (oben oder unten).",
+ "help6": "Ein Vokal (obere oder untere).",
+ "help7": "Ein Groß-C onsonant.",
+ "help8": "Jede Zahl, 0-9.",
+ "help9": "Ein Kleinbuchstaben c onsonant.",
+ "helpIntro": "Dieser Datentyp können Sie zufällige alphanumerische Zeichenfolgen. Die folgende Tabelle enthält die Zeichenlegende für dieses Feld. Alle anderen Zeichen, die Sie in diesem Feld erscheint unescaped.",
+ "incompleteFields": "Alphanumerische Felder müssen Sie das Format, in dem Optionen Textfeld eingeben. Bitte beheben Sie die folgenden Zeilen:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/en.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/en.json
new file mode 100644
index 000000000..7d207679b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/en.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "Alphanumeric",
+ "DESC": "Generates a custom random alphanumeric string or any length or format, defined by placeholder chars.",
+ "DEFAULT_TITLE": "alphanumeric",
+ "exampleCanPostalCode": "(Can. Postal code)",
+ "examplePassword": "(Password)",
+ "exampleUSZipCode": "(US Zip code)",
+ "help1": "An uppercase L etter.",
+ "help10": "Any number, 1-9.",
+ "help11": "A consonant (upper or lower).",
+ "help12": "An H exidecimal number (0-F)",
+ "help2": "An uppercase V owel.",
+ "help3": "A lowercase l etter.",
+ "help4": "A lowercase v owel.",
+ "help5": "A letter (upper or lower).",
+ "help6": "A vowel (upper or lower).",
+ "help7": "An uppercase C onsonant.",
+ "help8": "Any number, 0-9.",
+ "help9": "A lowercase c onsonant.",
+ "helpIntro": "This Data Type lets you generate random alphanumeric strings. You can use the placeholders listed below to customize the form of the values generated. Any other characters besides the ones listed below will be output as they are.",
+ "incompleteFields": "Alphanumeric fields require you to enter the format in the Options text field. Please fix the following rows:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/es.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/es.json
new file mode 100644
index 000000000..8902344f7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/es.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "Alfanumérico",
+ "DESC": "Genera una cadena alfanumérica personalizada azar o cualquier longitud o formato, definida por caracteres de marcador de posición .",
+ "DEFAULT_TITLE": "alfanumérico",
+ "exampleCanPostalCode": "(Código postal canadiense)",
+ "examplePassword": "(Contraseña)",
+ "exampleUSZipCode": "(Código postal yankee)",
+ "help1": "Una L etra mayúscula.",
+ "help10": "Cualquier número, 1-9.",
+ "help11": "Una constante (mayúscula o minúscula).",
+ "help12": "Un número H exadecimal (0-F)",
+ "help2": "Una V ocal mayúscula.",
+ "help3": "Una l etra minúscula.",
+ "help4": "Una v ocal minúscula.",
+ "help5": "Una letra (mayúscula o minúscula).",
+ "help6": "Una vocal (mayúscula o minúscula).",
+ "help7": "Una C onstante mayúscula.",
+ "help8": "Cualquier número, 0-9.",
+ "help9": "Una c onstante minúscula.",
+ "helpIntro": "Estos tipos de datos te permiten generar cadenas alfanuméricas aleatorias. La siguiente tabla contiene la leyenda para este campo. Cualquier otro carácter que introduzcas en este campo aparecerá sin escapar.",
+ "incompleteFields": "Los campos alfanuméricos requieren que se introduzca el formato en el campo de texto Opciones. Por favor, corrija las siguientes filas:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/fr.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/fr.json
new file mode 100644
index 000000000..7abc2eaaa
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/fr.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "Alphanumérique",
+ "DESC": "Génère une chaîne alphanumérique aléatoire coutume ou toute longueur ou le format , définie par les caractères d'espace réservé .",
+ "DEFAULT_TITLE": "alphanumérique",
+ "exampleCanPostalCode": "(Code postal Canada)",
+ "examplePassword": "(Mot de passe)",
+ "exampleUSZipCode": "(Code postal US)",
+ "help1": "Une L ettre majuscule.",
+ "help10": "N'importe quel nombre, 1-9.",
+ "help11": "Une consonne (majuscule ou minuscule).",
+ "help12": "Un nombre H exadécimal (0-F)",
+ "help2": "Une V oyelle majuscule.",
+ "help3": "Une l ettre minuscule.",
+ "help4": "Une v oyelle minuscule.",
+ "help5": "Une lettre (majuscule ou minuscule).",
+ "help6": "Une voyelle (majuscule ou minuscule).",
+ "help7": "Une C onsonne majuscule.",
+ "help8": "N'importe quel nombre, 0-9.",
+ "help9": "Une c onsonne minuscule.",
+ "helpIntro": "Ce type de données permet de générer au hasard des chaînes alphanumériques. Le tableau suivant contient la légende de caractères pour ce champ. Tous les autres caractères que vous entrez dans ce champ apparaîtront non-échappés.",
+ "incompleteFields": "Les champs alphanumériques nécessitent la saisie du format dans le champ de texte Options. Corrigez les lignes suivantes:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/hi.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/hi.json
new file mode 100644
index 000000000..da7360034
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/hi.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "अक्षरांकीय",
+ "DESC": "प्लेसहोल्डर वर्णों द्वारा परिभाषित एक कस्टम यादृच्छिक अल्फ़ान्यूमेरिक स्ट्रिंग या कोई लंबाई या प्रारूप उत्पन्न करता है।",
+ "DEFAULT_TITLE": "अक्षरांकीय",
+ "exampleCanPostalCode": "(कनाडाई पोस्टल कोड)",
+ "examplePassword": "(पासवर्ड)",
+ "exampleUSZipCode": "(यूएस ज़िप कोड)",
+ "help1": "एक बड़े अक्षर।",
+ "help10": "कोई भी संख्या, 1-9।",
+ "help11": "एक व्यंजन (ऊपरी या निचला)।",
+ "help12": "एक हेक्साडेसिमल संख्या (0-एफ)",
+ "help2": "एक अपरकेस स्वर।",
+ "help3": "एक छोटा अक्षर।",
+ "help4": "एक लोअरकेस स्वर।",
+ "help5": "एक अक्षर (ऊपरी या निचला)।",
+ "help6": "एक स्वर (ऊपरी या निचला)।",
+ "help7": "एक अपरकेस व्यंजन।",
+ "help8": "कोई भी संख्या, 0-9।",
+ "help9": "एक लोअरकेस व्यंजन।",
+ "helpIntro": "यह डेटा प्रकार आपको यादृच्छिक अल्फ़ान्यूमेरिक स्ट्रिंग उत्पन्न करने देता है। आप जेनरेट किए गए मानों के रूप को अनुकूलित करने के लिए नीचे सूचीबद्ध प्लेसहोल्डर का उपयोग कर सकते हैं। नीचे सूचीबद्ध लोगों के अलावा कोई भी अन्य वर्ण आउटपुट होंगे जैसे वे हैं।",
+ "incompleteFields": "अल्फ़ान्यूमेरिक फ़ील्ड के लिए आपको विकल्प टेक्स्ट फ़ील्ड में प्रारूप दर्ज करना होगा। कृपया निम्न पंक्तियों को ठीक करें:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/ja.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ja.json
new file mode 100644
index 000000000..ba6247908
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ja.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "字母数字",
+ "DESC": "プレースホルダー文字で定義された、カスタムのランダムな英数字の文字列または任意の長さまたは形式を生成します。",
+ "DEFAULT_TITLE": "英数字",
+ "exampleCanPostalCode": "(カナダの郵便番号)",
+ "examplePassword": "(パスワード)",
+ "exampleUSZipCode": "(米国の郵便番号)",
+ "help1": "大文字。",
+ "help10": "任意の数、1〜9。",
+ "help11": "子音(上または下)。",
+ "help12": "16進数(0-F)",
+ "help2": "大文字の母音。",
+ "help3": "小文字。",
+ "help4": "小文字の母音。",
+ "help5": "文字(上または下)。",
+ "help6": "母音(上または下)。",
+ "help7": "大文字の子音。",
+ "help8": "0〜9の任意の数。",
+ "help9": "小文字の子音。",
+ "helpIntro": "このデータ型を使用すると、ランダムな英数字の文字列を生成できます。 次の表には、このフィールドの文字の凡例が含まれています。 このフィールドに入力した他の文字は、エスケープされていないように見えます。",
+ "incompleteFields": "英数字フィールドでは、[オプション]テキストフィールドに形式を入力する必要があります。 次の行を修正してください。"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/nl.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/nl.json
new file mode 100644
index 000000000..5a999fbae
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/nl.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "Alfanumeriek",
+ "DESC": "Genereert een aangepaste willekeurige alfanumerieke tekenreeks van een bepaalde lengte in het opgegeven formaat, gedefinieerd door placeholder tekens.",
+ "DEFAULT_TITLE": "alfanumeriek",
+ "exampleCanPostalCode": "(Canadese Postcode)",
+ "examplePassword": "(Wachtwoord)",
+ "exampleUSZipCode": "(US Postcode)",
+ "help1": "Een hoofdletter L etter.",
+ "help10": "Een getal, 1-9.",
+ "help11": "Een medeklinker (klein of groot geschreven).",
+ "help12": "Een H exidecimal getal (0-F)",
+ "help2": "V Een klinker in hoofdletter.",
+ "help3": "Een kleine letter l etter.",
+ "help4": "v Een klinker, klein geschreven.",
+ "help5": "Een letter (klein of groot geschreven).",
+ "help6": "Een klinker (klein of groot geschreven).",
+ "help7": "Een medeklinker groot geschreven C .",
+ "help8": "Een getal, 0-9.",
+ "help9": "c Een klein geschreven medeklinker.",
+ "helpIntro": "Met dit gegevenstype kunt u een string genereren met willekeurige alfanumerieke tekens. De volgende tabel bevat de karakter legenda voor dit veld. Alle andere tekens die u in dit veld invoert zullen onveranderd in de string worden opgenomen.",
+ "incompleteFields": "Voor Alfanumerieke velden moet u het formaat in het Opties tekstveld in voeren. Pas AUB de volgende rijen aan:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/pt.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/pt.json
new file mode 100644
index 000000000..dae2dac1d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/pt.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "Alfanumérica",
+ "DESC": "Gera uma string alfanumérica aleatória personalizada ou qualquer comprimento ou formato, definido por caracteres de espaço reservado.",
+ "DEFAULT_TITLE": "alfanumérica",
+ "exampleCanPostalCode": "(Can. Código postal)",
+ "examplePassword": "(Senha)",
+ "exampleUSZipCode": "(Código postal dos EUA)",
+ "help1": "AUma letra maiúscula.",
+ "help10": "Qualquer número, 1-9.",
+ "help11": "Uma consoante (superior ou inferior).",
+ "help12": "Um número hexadecimal (0-F)",
+ "help2": "Uma vogal maiúscula.",
+ "help3": "Uma letra minúscula.",
+ "help4": "Uma vogal minúscula.",
+ "help5": "Uma letra (superior ou inferior).",
+ "help6": "Uma vogal (superior ou inferior).",
+ "help7": "Uma consoante maiúscula.",
+ "help8": "Qualquer número, 0-9.",
+ "help9": "Uma consoante minúscula.",
+ "helpIntro": "Este tipo de dados permite gerar strings alfanuméricas aleatórias. Você pode usar os marcadores de posição listados abaixo para personalizar a forma dos valores gerados. Quaisquer outros caracteres além dos listados abaixo serão produzidos como estão.",
+ "incompleteFields": "Os campos alfanuméricos exigem que você insira o formato no campo de texto Opções. Corrija as seguintes linhas:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/ru.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ru.json
new file mode 100644
index 000000000..af8757414
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ru.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "буквенно-цифровой",
+ "DESC": "Создает пользовательскую случайную буквенно-цифровую строку любой длины или формата, определяемую символами-заполнителями.",
+ "DEFAULT_TITLE": "буквенно-цифровой",
+ "exampleCanPostalCode": "(канадский почтовый индекс)",
+ "examplePassword": "(Пароль)",
+ "exampleUSZipCode": "(почтовый индекс США)",
+ "help1": "Прописная буква.",
+ "help10": "Любое число, 1-9.",
+ "help11": "Согласный (верхний или нижний).",
+ "help12": "Шестнадцатеричный номер (0-F)",
+ "help2": "Заглавная гласная.",
+ "help3": "Строчная буква.",
+ "help4": "Гласная в нижнем регистре.",
+ "help5": "Буква (верхняя или нижняя).",
+ "help6": "Гласная (верхняя или нижняя).",
+ "help7": "Согласная в верхнем регистре.",
+ "help8": "Любое число, 0-9.",
+ "help9": "Строчная согласная.",
+ "helpIntro": "Этот тип данных позволяет генерировать случайные буквенно-цифровые строки. Вы можете использовать перечисленные ниже заполнители, чтобы настроить форму генерируемых значений. Любые другие символы, кроме перечисленных ниже, будут выводиться как есть.",
+ "incompleteFields": "Буквенно-цифровые поля требуют ввода формата в текстовом поле «Параметры». Исправьте следующие строки:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/ta.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ta.json
new file mode 100644
index 000000000..cba1edecc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/ta.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "எண்ணெழுத்து",
+ "DESC": "தனிப்பயன் சீரற்ற எண்ணெழுத்து சரம் அல்லது எந்த நீளம் அல்லது வடிவமைப்பை உருவாக்குகிறது, இது ஒதுக்கிட எழுத்துகளால் வரையறுக்கப்படுகிறது.",
+ "DEFAULT_TITLE": "எண்ணெழுத்து",
+ "exampleCanPostalCode": "(கனடிய அஞ்சல் குறியீடு)",
+ "examplePassword": "(கடவுச்சொல்)",
+ "exampleUSZipCode": "(யு.எஸ். ஜிப் குறியீடு)",
+ "help1": "ஒரு பெரிய கடிதம்.",
+ "help10": "எந்த எண்ணும், 1-9.",
+ "help11": "ஒரு மெய் (மேல் அல்லது கீழ்).",
+ "help12": "ஒரு ஹெக்ஸாடெசிமல் எண் (0-எஃப்)",
+ "help2": "ஒரு பெரிய உயிரெழுத்து.",
+ "help3": "ஒரு சிறிய கடிதம்.",
+ "help4": "ஒரு சிறிய உயிரெழுத்து.",
+ "help5": "ஒரு கடிதம் (மேல் அல்லது கீழ்).",
+ "help6": "ஒரு உயிர் (மேல் அல்லது கீழ்).",
+ "help7": "ஒரு பெரிய மெய்.",
+ "help8": "எந்த எண்ணும், 0-9.",
+ "help9": "ஒரு சிறிய மெய்.",
+ "helpIntro": "சீரற்ற ஆல்பா-எண் சரங்களை உருவாக்க இந்த தரவு வகை உங்களை அனுமதிக்கிறது. பின்வரும் அட்டவணையில் இந்த புலத்திற்கான எழுத்து புராணம் உள்ளது. இந்தத் துறையில் நீங்கள் உள்ளிட்ட வேறு எந்த எழுத்துக்களும் தப்பிக்கப்படாமல் தோன்றும்.",
+ "incompleteFields": "விருப்பங்கள் உரை புலத்தில் வடிவமைப்பை உள்ளிட எண்ணெழுத்து புலங்கள் தேவை. பின்வரும் வரிசைகளை சரிசெய்யவும்:"
+}
diff --git a/packages/plugins/src/dataTypes/Alphanumeric/i18n/zh.json b/packages/plugins/src/dataTypes/Alphanumeric/i18n/zh.json
new file mode 100644
index 000000000..41fcf6afa
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Alphanumeric/i18n/zh.json
@@ -0,0 +1,22 @@
+{
+ "NAME": "字母数字",
+ "DESC": "生成由占位符定义的自定义随机字母数字字符串或任何长度或格式。",
+ "DEFAULT_TITLE": "字母数字",
+ "exampleCanPostalCode": "(加拿大邮政编码)",
+ "examplePassword": "(密码)",
+ "exampleUSZipCode": "(美国邮政编码)",
+ "help1": "一个大写字母。",
+ "help10": "1-9之间的任何数字。",
+ "help11": "辅音(上或下)。",
+ "help12": "十六进制数(0-F)",
+ "help2": "大写元音。",
+ "help3": "小写字母。",
+ "help4": "小写的元音",
+ "help5": "字母(大写或小写)。",
+ "help6": "字母(大写或小写)。",
+ "help7": "大写辅音。",
+ "help8": "任何数字,0-9。",
+ "help9": "小写的辅音。",
+ "helpIntro": "通过此数据类型,您可以生成随机的字母数字字符串。 下表包含此字段的字符图例。 您在此字段中输入的任何其他字符都将显示为未转义。",
+ "incompleteFields": "字母数字字段要求您在“选项”文本字段中输入格式。 请修复以下行:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.generate.ts b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.generate.ts
new file mode 100644
index 000000000..ffb564ad6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.generate.ts
@@ -0,0 +1,13 @@
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const rowNum = data.rowNum;
+ const { incrementStart, incrementValue, incrementPlaceholder } = data.rowState;
+
+ let value = (rowNum - 1) * incrementValue + incrementStart;
+ if (incrementPlaceholder) {
+ value = utils.generalUtils.template(incrementPlaceholder, { INCR: value });
+ }
+ return { display: value };
+};
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.scss b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.scss
new file mode 100644
index 000000000..7eb1d2e11
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.scss
@@ -0,0 +1,3 @@
+.options span {
+ margin-right: 2px;
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.scss.d.ts b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.scss.d.ts
new file mode 100644
index 000000000..2aa079d08
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace AutoIncrementScssNamespace {
+ export interface IAutoIncrementScss {
+ options: string;
+ }
+}
+
+declare const AutoIncrementScssModule: AutoIncrementScssNamespace.IAutoIncrementScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: AutoIncrementScssNamespace.IAutoIncrementScss;
+};
+
+export = AutoIncrementScssModule;
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.state.tsx b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.state.tsx
new file mode 100755
index 000000000..5fa6461ff
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.state.tsx
@@ -0,0 +1,25 @@
+export type GenerationOptionsType = {
+ incrementStart: number;
+ incrementValue: number;
+ incrementPlaceholder?: string;
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ incrementStart: 1,
+ incrementValue: 1,
+ incrementPlaceholder: ''
+};
+
+export type AutoIncrementState = {
+ example: string;
+ incrementStart: string;
+ incrementValue: string;
+ incrementPlaceholder: string;
+};
+
+export const initialState: AutoIncrementState = {
+ example: '1,1',
+ incrementStart: '1',
+ incrementValue: '1',
+ incrementPlaceholder: ''
+};
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.tsx b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.tsx
new file mode 100755
index 000000000..215336563
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.tsx
@@ -0,0 +1,125 @@
+import * as React from 'react';
+import Dropdown from '~components/dropdown/Dropdown';
+import TextField from '~components/TextField';
+import { isNumeric } from '@generatedata/utils/number';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTMetadataType, DTOptionsProps } from '~types/dataTypes';
+import { AutoIncrementState, GenerationOptionsType } from './AutoIncrement.state';
+import styles from './AutoIncrement.scss';
+
+export const Example = ({ data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: string): void => {
+ const [incrementStart, incrementValue, incrementPlaceholder] = value.split(',');
+
+ onUpdate({
+ example: value,
+ incrementStart,
+ incrementValue,
+ incrementPlaceholder
+ });
+ };
+
+ const options = [
+ { value: '1,1,', label: '1, 2, 3, 4, 5, 6...' },
+ { value: '100,1,', label: '100, 101, 102, 103, 104...' },
+ { value: '0,2,', label: '0, 2, 4, 6, 8, 10...' },
+ { value: '0,5,', label: '0, 5, 10, 15, 20, 25...' },
+ { value: '1000,-1,', label: '1000, 999, 998, 997...' },
+ { value: '0,-1,', label: '0, -1, -2, -3, -4...' },
+ { value: '0,0.5,', label: '0, 0.5, 1, 1.5, 2...' },
+ { value: '1,1,ROW-{{INCR}}', label: 'ROW-1, ROW-2, ROW-3,...' },
+ { value: '2,4,{{INCR}}i', label: '2i, 4i, 6i, 8i...' }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+export const Options = ({ coreI18n, i18n, data, onUpdate }: DTOptionsProps) => {
+ const onChange = (field: string, value: number | string): void => {
+ onUpdate({
+ ...data,
+ [field]: value
+ });
+ };
+
+ const incStartError = data.incrementStart.trim() === '' ? coreI18n.requiredField : '';
+ const incValueError = data.incrementValue.trim() === '' ? coreI18n.requiredField : '';
+
+ return (
+
+
+ {i18n.startAt}
+ onChange('incrementStart', e.target.value)}
+ />
+
+ {i18n.increment}
+ onChange('incrementValue', e.target.value)}
+ />
+
+
+
+ {i18n.placeholderStr}
+ onChange('incrementPlaceholder', e.target.value)}
+ />
+
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+ {i18n.helpIntro}
+
+
+
+ ROW-{'{{INCR}}'} -> ROW-1, ROW-2, ROW-3, ROW-4, ...
+
+
+ {'{{INCR}}'}F -> 1F, 2F, 3F, 4F, ...
+
+
+ >
+);
+
+export const getMetadata = (rowData: AutoIncrementState): DTMetadata => {
+ let dataType: DTMetadataType = isNumeric(rowData.incrementStart) && isNumeric(rowData.incrementValue) ? 'number' : 'string';
+
+ // if there's a placeholder defined, set it to a string if it contains any chars besides numbers & {{INCR}}, even spaces
+ if (rowData.incrementPlaceholder.trim()) {
+ const val = rowData.incrementPlaceholder.replace(/{{INCR}}/g, '');
+ dataType = val === '' || /^[\d\.]+$/.test(val) ? 'number' : 'string';
+ }
+
+ return {
+ general: {
+ dataType
+ },
+ sql: {
+ field: 'mediumint',
+ field_Oracle: 'number default NULL',
+ field_MSSQL: 'INTEGER NULL',
+ field_Postgres: 'integer NULL'
+ }
+ };
+};
+
+export const rowStateReducer = (state: AutoIncrementState): GenerationOptionsType => {
+ const incrementStart = state.incrementStart ? parseFloat(state.incrementStart) : 0;
+ const incrementValue = state.incrementValue ? parseFloat(state.incrementValue) : 0;
+
+ return {
+ incrementStart,
+ incrementValue,
+ incrementPlaceholder: state.incrementPlaceholder
+ };
+};
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.worker.ts b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.worker.ts
new file mode 100644
index 000000000..eba203e1c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/AutoIncrement.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './AutoIncrement.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/README.md b/packages/plugins/src/dataTypes/AutoIncrement/README.md
new file mode 100644
index 000000000..b999bef40
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/README.md
@@ -0,0 +1,154 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » AutoIncrement
+
+This Data Type generates auto-increment sequences in a variety of different ways.
+
+> Note: if you're generating a database table and are looking for a simple _auto-increment primary key_ field, that option
+> actually already exists in the [SQL Export Type](../../exportTypes/SQL/README.md) - you don't need a separate field for
+> that.
+
+## Typings
+
+```typescript
+{
+ incrementStart: number;
+ incrementValue: number;
+ incrementPlaceholder?: string;
+}
+
+```
+
+## The `incrementPlaceholder` property
+
+Providing the start value and the increment jump (plus or minus) is required via the `incrementStart` and `incrementValue`
+properties. But providing a totally custom placeholder is optional. That lets you fine-tune exactly how the value is
+outputted.
+
+The `{{INCR}}` string has special meaning within that field. That string gets switched out for whatever the auto-increment
+number has been calculated. That lets you use that value within the context of an entire string.
+
+# Examples
+
+This example generates four separate fields with different auto-increment values, like so:
+
+- 1, 2, 3, 4, ...
+- A-10, A-20, A-30, A-40, ...
+- 100B, 95B, 90B, 85B, ...
+- 10-20, 11-22, 12-24, 13-35...
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "AutoIncrement",
+ title: "Simple increment",
+ settings: {
+ incrementStart: 1,
+ incrementValue: 1
+ }
+ },
+ {
+ plugin: "AutoIncrement",
+ title: "Tens with prefix",
+ settings: {
+ incrementStart: 10,
+ incrementValue: 10,
+ incrementPlaceholder: "A-{{INCR}}"
+ }
+ },
+ {
+ plugin: "AutoIncrement",
+ title: "Decrement with suffix",
+ settings: {
+ incrementStart: 100,
+ incrementValue: -5,
+ incrementPlaceholder: "{{INCR}}B"
+ }
+ },
+ {
+ plugin: "AutoIncrement",
+ title: "Incrementing range",
+ settings: {
+ incrementStart: 10,
+ incrementValue: 1,
+ incrementPlaceholder: "{{INCR}}-{{INCR*2}}"
+ }
+ }
+ ],
+ exportSettings: {
+ type: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "Simple increment": 1,
+ "Tens with prefix": "A-10",
+ "Decrement with suffix": "100B",
+ "Incrementing range": "10-20"
+ },
+ {
+ "Simple increment": 2,
+ "Tens with prefix": "A-20",
+ "Decrement with suffix": "95B",
+ "Incrementing range": "11-22"
+ },
+ {
+ "Simple increment": 3,
+ "Tens with prefix": "A-30",
+ "Decrement with suffix": "90B",
+ "Incrementing range": "12-24"
+ },
+ {
+ "Simple increment": 4,
+ "Tens with prefix": "A-40",
+ "Decrement with suffix": "85B",
+ "Incrementing range": "13-26"
+ },
+ {
+ "Simple increment": 5,
+ "Tens with prefix": "A-50",
+ "Decrement with suffix": "80B",
+ "Incrementing range": "14-28"
+ },
+ {
+ "Simple increment": 6,
+ "Tens with prefix": "A-60",
+ "Decrement with suffix": "75B",
+ "Incrementing range": "15-30"
+ },
+ {
+ "Simple increment": 7,
+ "Tens with prefix": "A-70",
+ "Decrement with suffix": "70B",
+ "Incrementing range": "16-32"
+ },
+ {
+ "Simple increment": 8,
+ "Tens with prefix": "A-80",
+ "Decrement with suffix": "65B",
+ "Incrementing range": "17-34"
+ },
+ {
+ "Simple increment": 9,
+ "Tens with prefix": "A-90",
+ "Decrement with suffix": "60B",
+ "Incrementing range": "18-36"
+ },
+ {
+ "Simple increment": 10,
+ "Tens with prefix": "A-100",
+ "Decrement with suffix": "55B",
+ "Incrementing range": "19-38"
+ }
+]
+```
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/__tests__/AutoIncrement.generate.test.ts b/packages/plugins/src/dataTypes/AutoIncrement/__tests__/AutoIncrement.generate.test.ts
new file mode 100644
index 000000000..2bb6eaa57
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/__tests__/AutoIncrement.generate.test.ts
@@ -0,0 +1,32 @@
+import { onmessage } from '../AutoIncrement.worker';
+import { initialState } from '../AutoIncrement.state';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+const i18n = require('../i18n/en.json');
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ const rowState = {
+ incrementStart: parseInt(initialState.incrementStart, 10),
+ incrementValue: parseInt(initialState.incrementValue, 10),
+ incrementPlaceholder: initialState.incrementPlaceholder
+ };
+
+ it('generates random data', () => {
+ const payload: any = {
+ data: {
+ ...getBlankDTGeneratorPayload(),
+ rowState,
+ i18n
+ }
+ };
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: 1 });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/__tests__/AutoIncrement.test.tsx b/packages/plugins/src/dataTypes/AutoIncrement/__tests__/AutoIncrement.test.tsx
new file mode 100644
index 000000000..fca00ac02
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/__tests__/AutoIncrement.test.tsx
@@ -0,0 +1,104 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Help, getMetadata } from '../AutoIncrement';
+import { AutoIncrementState } from '../AutoIncrement.state';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('getMetadata', () => {
+ it('returns number as the general data type when no placeholder and numeric values for the incr + starting value', () => {
+ const rowData1: AutoIncrementState = {
+ incrementPlaceholder: '',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData1).general!.dataType).toEqual('number');
+
+ const rowData2: AutoIncrementState = {
+ incrementPlaceholder: '',
+ incrementValue: '1.5',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData2).general!.dataType).toEqual('number');
+ });
+
+ it('still returns a number when the placeholder is set but only contains the placeholder or numeric vals', () => {
+ const rowData1: AutoIncrementState = {
+ incrementPlaceholder: '{{INCR}}',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData1).general!.dataType).toEqual('number');
+
+ const rowData2: AutoIncrementState = {
+ incrementPlaceholder: '100{{INCR}}',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData2).general!.dataType).toEqual('number');
+
+ const rowData3: AutoIncrementState = {
+ incrementPlaceholder: '123{{INCR}}456',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData3).general!.dataType).toEqual('number');
+ });
+
+ it('returns a number when the placeholder contains whitespace only', () => {
+ const rowData1: AutoIncrementState = {
+ incrementPlaceholder: ' ',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData1).general!.dataType).toEqual('number');
+ });
+
+ it('returns a number when the placeholder string contains multiple placeholders and nothing else', () => {
+ const rowData1: AutoIncrementState = {
+ incrementPlaceholder: '{{INCR}}{{INCR}}{{INCR}}',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData1).general!.dataType).toEqual('number');
+ });
+
+ it('returns a string when the placeholder contains whitespace and the placeholder', () => {
+ const rowData1: AutoIncrementState = {
+ incrementPlaceholder: ' {{INCR}}',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData1).general!.dataType).toEqual('string');
+ });
+
+ it('returns a string when the placeholder contains any non-numeric chars and the placeholder', () => {
+ const rowData1: AutoIncrementState = {
+ incrementPlaceholder: 'b{{INCR}}',
+ incrementValue: '1',
+ incrementStart: '1',
+ example: ''
+ };
+ expect(getMetadata(rowData1).general!.dataType).toEqual('string');
+ });
+});
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/bundle.ts b/packages/plugins/src/dataTypes/AutoIncrement/bundle.ts
new file mode 100644
index 000000000..96f0a2f7f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, rowStateReducer, getMetadata } from './AutoIncrement';
+import { initialState } from './AutoIncrement.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ rowStateReducer,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/config.ts b/packages/plugins/src/dataTypes/AutoIncrement/config.ts
new file mode 100644
index 000000000..ad789ad88
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'numeric',
+ fieldGroupOrder: 20
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/ar.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ar.json
new file mode 100644
index 000000000..f54c6ea2f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ar.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "زيادة تلقائية",
+ "DESC": "يوفر العديد من الاختلافات في حقل زيادة تلقائية قياسي: قيمة بدء مخصصة ، وأحجام خطوات قابلة للتخصيص ، وإدراج سلسلة عشوائية ، وزيادة أو إنقاص والمزيد.",
+ "DEFAULT_TITLE": "زيادة تلقائية",
+ "helpIntro": "يُنشئ عمودًا يحتوي على رقم فريد في كل صف ، يتزايد حسب القيمة التي تدخلها. يمكن أن يكون هذا الخيار مفيدًا لإدراج البيانات في حقل قاعدة بيانات باستخدام مفتاح أساسي لزيادة تلقائية.",
+ "helpPara2": "تتيح لك سلسلة العنصر النائب الاختيارية تضمين قيمة الزيادة المُنشأة داخل سلسلة ، عبر العنصر النائب {{INCR}}. فمثلا:",
+ "increment": "زيادة راتب:",
+ "placeholderStr": "سلسلة العنصر النائب:",
+ "startAt": "تبدأ في:",
+ "incompleteFields": "الرجاء إدخال حقلي البدء عند والزيادة لجميع صفوف الزيادة التلقائية:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/de.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/de.json
new file mode 100644
index 000000000..7ee2d5bef
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/de.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "Automatisches Inkrementieren",
+ "DESC": "Bietet viele Variationen eines Standardfelds für die automatische Inkrementierung: benutzerdefinierter Startwert, anpassbare Schrittgrößen, willkürliche Zeichenfolgeneinbeziehung, Inkrementierung oder Dekrementierung und mehr.",
+ "DEFAULT_TITLE": "autoink",
+ "helpIntro": "Erzeugt eine Spalte, die in jeder Zeile eine eindeutige Nummer enthält, die um den von Ihnen eingegebenen Wert erhöht wird. Diese Option kann hilfreich sein, um die Daten mit einem automatisch inkrementierten Primärschlüssel in ein Datenbankfeld einzufügen.",
+ "helpPara2": "Mit der optionalen Platzhalterzeichenfolge können Sie den generierten Inkrementwert über den Platzhalter {{INCR}} in eine Zeichenfolge einbetten. Beispielsweise:",
+ "increment": "Zuwachs:",
+ "placeholderStr": "Platzhalterzeichenfolge:",
+ "startAt": "Anfangen bei:",
+ "incompleteFields": "Bitte geben Sie die Felder Start bei und Inkrementieren für alle Zeilen mit automatischer Inkrementierung ein:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/en.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/en.json
new file mode 100644
index 000000000..178c960e0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/en.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "Auto-increment",
+ "DESC": "Provides many variations on an standard auto-increment field: custom start value, customizable step sizes, arbitrary string inclusion, increment or decrement and more. ",
+ "DEFAULT_TITLE": "autoincrement",
+ "helpIntro": "Generates a column that contains a unique number on each row, incrementing by whatever value you enter. This option can be helpful for inserting the data into a database field with an auto-increment primary key.",
+ "helpPara2": "The optional placeholder string lets you embed the generated increment value within a string, via the {{INCR}} placeholder. For example:",
+ "increment": "Increment:",
+ "placeholderStr": "Placeholder string:",
+ "startAt": "Start at:",
+ "incompleteFields": "Please enter the Start At and Increment fields for all Auto-increment rows:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/es.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/es.json
new file mode 100644
index 000000000..a9bc6a17e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/es.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "Auto-incrementado",
+ "DESC": "Proporciona muchas variaciones en un campo de incremento automático estándar : se inicia la costumbre de valor, tamaños de paso personalizables , la inclusión cadena arbitraria , incremento o decremento y más.",
+ "DEFAULT_TITLE": "autoincremento",
+ "helpIntro": "Genera una columna que contiene un número único en cada fila, incrementándolo en el valor que introduzcas. Esta opción puede ser de ayuda para insertar los datos en una columna de base de datos con una clave primaria auto-incrementada.",
+ "helpPara2": "La cadena de patrón opcional permite introducir el valor de incremento generado dentro de una cadena de texto usando el patrón de sustitución {{INCR}} . Por ejemplo:",
+ "increment": "Incremento:",
+ "placeholderStr": "Patrón de sustitución:",
+ "startAt": "Comenzar en:",
+ "incompleteFields": "Por favor, introduce los campos "Empezar en" e "Incremento" para todas las filas auto-incrementadas:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/fr.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/fr.json
new file mode 100644
index 000000000..da654fd7b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/fr.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "Auto-incrémentation",
+ "DESC": "Fournit beaucoup de variations sur un champ à incrémentation automatique norme : commencer personnalisé valeur , tailles de pas personnalisables , l'inclusion arbitraire de chaîne , augmentation ou la diminution et plus encore. ",
+ "DEFAULT_TITLE": "incrementation",
+ "helpIntro": "Génère une colonne qui contient un numéro unique sur chaque ligne, quel que soit la valeur d'incrémentation que vous entrez. Cette option peut être utile pour insérer les données dans un champ de base de données avec une clé primaire auto-incrémentée.",
+ "helpPara2": "L'option 'chaîne complémentaire' vous permet d'intégrer la valeur d'incrément généré dans une chaîne grâce à la balise {{INCR}} . Par exemple:",
+ "incompleteFields": "Renseignez l'option 'Début' des champs d'incrémentation pour toutes les lignes concernées:",
+ "increment": "Pas:",
+ "placeholderStr": "Chaîne complémentaire:",
+ "startAt": "Début:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/hi.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/hi.json
new file mode 100644
index 000000000..2ea6dc9ce
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/hi.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "स्वत: वेतनवृद्धि",
+ "DESC": "एक मानक ऑटो-इंक्रीमेंट फ़ील्ड पर कई विविधताएँ प्रदान करता है: कस्टम प्रारंभ मान, अनुकूलन योग्य चरण आकार, मनमाना स्ट्रिंग समावेशन, वृद्धि या कमी और बहुत कुछ।",
+ "DEFAULT_TITLE": "स्वत: वेतनवृद्धि",
+ "helpIntro": "एक कॉलम उत्पन्न करता है जिसमें प्रत्येक पंक्ति पर एक अद्वितीय संख्या होती है, जो आपके द्वारा दर्ज किए गए मान से बढ़ती है। यह विकल्प ऑटो-इन्क्रीमेंट प्राथमिक कुंजी के साथ डेटाबेस फ़ील्ड में डेटा डालने के लिए सहायक हो सकता है।",
+ "helpPara2": "वैकल्पिक प्लेसहोल्डर स्ट्रिंग आपको {{INCR}} प्लेसहोल्डर के माध्यम से एक स्ट्रिंग के भीतर उत्पन्न वृद्धि मान को एम्बेड करने देती है। उदाहरण के लिए:",
+ "increment": "वेतन वृद्धि:",
+ "placeholderStr": "प्लेसहोल्डर स्ट्रिंग:",
+ "startAt": "पर शुरू करें:",
+ "incompleteFields": "कृपया सभी ऑटो-इन्क्रीमेंट पंक्तियों के लिए स्टार्ट एट और इंक्रीमेंट फ़ील्ड दर्ज करें:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/ja.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ja.json
new file mode 100644
index 000000000..c7c5a2403
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ja.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "自動増加",
+ "DESC": "標準の自動インクリメントフィールドに多くのバリエーションを提供します:カスタム開始値、カスタマイズ可能なステップサイズ、任意の文字列の包含、インクリメントまたはデクリメントなど。",
+ "DEFAULT_TITLE": "自動増加",
+ "helpIntro": "入力した値に応じて、各行に一意の番号を含む列を生成します。 このオプションは、自動インクリメントの主キーを使用してデータベースフィールドにデータを挿入する場合に役立ちます。",
+ "helpPara2": "オプションのプレースホルダー文字列を使用すると、{{INCR}}プレースホルダーを介して、生成された増分値を文字列内に埋め込むことができます。 例えば:",
+ "increment": "インクリメント:",
+ "placeholderStr": "プレースホルダー文字列:",
+ "startAt": "開始:",
+ "incompleteFields": "すべての自動インクリメント行の[開始位置]フィールドと[インクリメント]フィールドに入力してください。"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/nl.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/nl.json
new file mode 100644
index 000000000..cd5afde43
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/nl.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "Auto-increment",
+ "DESC": "Biedt een groot aantal variaties op een standaard auto-increment veld : aangepaste begin waarde , aanpasbare stap maten , willekeurige reeks opnemen , verhogen of verlagen en meer.",
+ "DEFAULT_TITLE": "automatisch",
+ "helpIntro": "Genereert een kolom die een uniek nummer in elke rij opneemt, verhoogt met de waarde die u hebt ingevoerd. Deze optie kan nuttig zijn voor het inlezen van de gegevens in een database dat een veld met een auto-increment bevat (unieke sleutel).",
+ "helpPara2": "Met de optionele placeholderstring kunt u de gegenereerde waarde opnemen als onderdeel van een string. Gebruik hiervoor de {{INCR}} binnen de string. Bijvoorbeeld:",
+ "incompleteFields": "Vul de Startwaarde en Increment velden voor alle Auto-increment rijen:",
+ "increment": "Increment:",
+ "placeholderStr": "Placeholder string:",
+ "startAt": "Start met:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/pt.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/pt.json
new file mode 100644
index 000000000..0ac5a7bf3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/pt.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "Incremento automático",
+ "DESC": "Fornece muitas variações em um campo de incremento automático padrão: valor inicial personalizado, tamanhos de etapa personalizáveis, inclusão de string arbitrária, incremento ou decremento e muito mais.",
+ "DEFAULT_TITLE": "incremento",
+ "helpIntro": "Gera uma coluna que contém um número exclusivo em cada linha, incrementando por qualquer valor que você inserir. Esta opção pode ser útil para inserir os dados em um campo de banco de dados com uma chave primária de incremento automático.",
+ "helpPara2": "A string de espaço reservado opcional permite incorporar o valor de incremento gerado em uma string, por meio do marcador de posição {{INCR}} . Por exemplo:",
+ "increment": "Incremento:",
+ "placeholderStr": "String de espaço reservado:",
+ "startAt": "Começa ás:",
+ "incompleteFields": "Insira os campos Iniciar em e Incremento para todas as linhas de incremento automático:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/ru.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ru.json
new file mode 100644
index 000000000..5c9697795
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ru.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "Автоматическое приращение",
+ "DESC": "Предоставляет множество вариантов стандартного поля автоинкремента: пользовательское начальное значение, настраиваемые размеры шага, включение произвольной строки, увеличение или уменьшение и многое другое.",
+ "DEFAULT_TITLE": "автоматическоеприращение",
+ "helpIntro": "Создает столбец, содержащий уникальный номер в каждой строке, увеличивающийся на любое введенное вами значение. Этот параметр может быть полезен для вставки данных в поле базы данных с автоматически увеличивающимся первичным ключом.",
+ "helpPara2": "Необязательная строка-заполнитель позволяет встроить сгенерированное значение приращения в строку с помощью заполнителя {{INCR}} . Например:",
+ "increment": "Приращение:",
+ "placeholderStr": "Строка-заполнитель:",
+ "startAt": "Начните с:",
+ "incompleteFields": "Пожалуйста, введите поля Start At и Increment для всех строк с автоинкрементом:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/ta.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ta.json
new file mode 100644
index 000000000..34ede21f2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/ta.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "தானாக அதிகரிப்பு",
+ "DESC": "நிலையான தானாக அதிகரிக்கும் துறையில் பல வேறுபாடுகளை வழங்குகிறது: தனிப்பயன் தொடக்க மதிப்பு, தனிப்பயனாக்கக்கூடிய படி அளவுகள், தன்னிச்சையான சரம் சேர்த்தல், அதிகரிப்பு அல்லது குறைவு மற்றும் பல.",
+ "DEFAULT_TITLE": "autoincrement",
+ "helpIntro": "ஒவ்வொரு வரிசையிலும் தனித்துவமான எண்ணைக் கொண்ட ஒரு நெடுவரிசையை உருவாக்குகிறது, நீங்கள் உள்ளிட்ட எந்த மதிப்பையும் அதிகரிக்கிறது. தானாக அதிகரிக்கும் முதன்மை விசையுடன் தரவுத்தள புலத்தில் தரவைச் செருக இந்த விருப்பம் உதவியாக இருக்கும்.",
+ "helpPara2": "{{INCR}} ஒதுக்கிடத்தின் வழியாக, ஒரு சரத்திற்குள் உருவாக்கப்பட்ட அதிகரிப்பு மதிப்பை உட்பொதிக்க விருப்ப ஒதுக்கிட சரம் உங்களை அனுமதிக்கிறது. உதாரணத்திற்கு:",
+ "increment": "அதிகரிப்பு:",
+ "placeholderStr": "ஒதுக்கிட சரம்:",
+ "startAt": "தொடங்கும் இடம் அல்லது நேரம்:",
+ "incompleteFields": "அனைத்து தானியங்கு-அதிகரிக்கும் வரிசைகளுக்கும் தொடக்க மற்றும் அதிகரிப்பு புலங்களை உள்ளிடவும்:"
+}
diff --git a/packages/plugins/src/dataTypes/AutoIncrement/i18n/zh.json b/packages/plugins/src/dataTypes/AutoIncrement/i18n/zh.json
new file mode 100644
index 000000000..ef043d6dc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/AutoIncrement/i18n/zh.json
@@ -0,0 +1,11 @@
+{
+ "NAME": "自动递增",
+ "DESC": "在标准自动递增字段上提供了多种变体:自定义起始值,可自定义步长,任意字符串包含,递增或递减等等。",
+ "DEFAULT_TITLE": "自动递增",
+ "helpIntro": "生成一列,该列在每一行上包含一个唯一的数字,并按您输入的任何值递增。 此选项对于使用自动增量主键将数据插入数据库字段很有用。",
+ "helpPara2": "可选的占位符字符串使您可以通过{{INCR}}占位符将生成的增量值嵌入到字符串中。 例如:",
+ "increment": "增量:",
+ "placeholderStr": "占位符字符串:",
+ "startAt": "开始于:",
+ "incompleteFields": "请为所有自动增量行输入“开始于”和“增量”字段:"
+}
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.generate.ts b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.generate.ts
new file mode 100644
index 000000000..6944e6fb1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.generate.ts
@@ -0,0 +1,50 @@
+import { BitcoinAddressFormat } from './BitcoinAddress.state';
+// import { WorkerUtils } from '../../';
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+// import { BitcoinAddressFormat, BitcoinAddressState } from './BitcoinAddress';
+// import ECPairFactory from 'ecpair';
+// import * as ecc from 'tiny-secp256k1';
+// import { payments } from 'bitcoinjs-lib';
+
+// const ECPair = ECPairFactory(ecc);
+
+export const generate = ({ rowState }: DTGenerationData): DTGenerateResult => {
+ // utils: WorkerUtils
+ const formats: any = {};
+ if (rowState[BitcoinAddressFormat.Legacy].enabled && rowState[BitcoinAddressFormat.Legacy].weight) {
+ formats[BitcoinAddressFormat.Legacy] = rowState[BitcoinAddressFormat.Legacy].weight;
+ }
+ if (rowState[BitcoinAddressFormat.Compatibility].enabled && rowState[BitcoinAddressFormat.Compatibility].weight) {
+ formats[BitcoinAddressFormat.Compatibility] = rowState[BitcoinAddressFormat.Compatibility].weight;
+ }
+ if (rowState[BitcoinAddressFormat.Segwit].enabled && rowState[BitcoinAddressFormat.Segwit].weight) {
+ formats[BitcoinAddressFormat.Segwit] = rowState[BitcoinAddressFormat.Segwit].weight;
+ }
+
+ // get a random format
+ // const format = utils.randomUtils.getRandomWeightedValue(formats);
+
+ // let display;
+ // switch (format) {
+ // // Nope! actual generation needs to be better than this. Will put this on hold.
+ //
+ // // case BitcoinAddressFormat.Legacy: {
+ // // const length = utils.randomUtils.getRandomNum(25, 35);
+ // // display = utils.randomUtils.generateRandomAlphanumericStr('1' + ('x'.repeat(length)), placeholders);
+ // // break;
+ // // }
+ // // case BitcoinAddressFormat.Compatibility: {
+ // // const length = utils.randomUtils.getRandomNum(25, 35);
+ // // display = utils.randomUtils.generateRandomAlphanumericStr('3' + ('x'.repeat(length)), placeholders);
+ // // break;
+ // // }
+ // // case BitcoinAddressFormat.Segwit:
+ // // display = utils.randomUtils.generateRandomAlphanumericStr('bc1' + ('y'.repeat(39)), placeholders);
+ // // break;
+ // }
+
+ return { display: '' };
+ // const keyPair = ECPair.makeRandom();
+ // const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey });
+ // return address;
+};
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.scss b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.scss
new file mode 100644
index 000000000..335a26b79
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.scss
@@ -0,0 +1,21 @@
+.formatHeader {
+ display: flex;
+
+ svg {
+ color: #666666;
+ }
+ span {
+ display: inline-block;
+ margin-right: 4px;
+ }
+}
+
+.labelCol {
+ padding-right: 20px;
+}
+
+.table {
+ input:disabled {
+ color: #999999;
+ }
+}
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.scss.d.ts b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.scss.d.ts
new file mode 100644
index 000000000..3a9ba4b4d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.scss.d.ts
@@ -0,0 +1,14 @@
+declare namespace BitcoinAddressScssNamespace {
+ export interface IBitcoinAddressScss {
+ formatHeader: string;
+ labelCol: string;
+ table: string;
+ }
+}
+
+declare const BitcoinAddressScssModule: BitcoinAddressScssNamespace.IBitcoinAddressScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: BitcoinAddressScssNamespace.IBitcoinAddressScss;
+};
+
+export = BitcoinAddressScssModule;
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.state.tsx b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.state.tsx
new file mode 100644
index 000000000..d1bbc2cb7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.state.tsx
@@ -0,0 +1,37 @@
+export const enum BitcoinAddressFormat {
+ Legacy = 'Legacy',
+ Compatibility = 'Compatibility',
+ Segwit = 'Segwit'
+}
+
+export type BitcoinAddressState = {
+ [BitcoinAddressFormat.Legacy]: {
+ enabled: boolean;
+ weight: number;
+ };
+ [BitcoinAddressFormat.Compatibility]: {
+ enabled: boolean;
+ weight: number;
+ };
+ [BitcoinAddressFormat.Segwit]: {
+ enabled: boolean;
+ weight: number;
+ };
+};
+
+export type GenerationOptionsType = BitcoinAddressState;
+
+export const initialState: BitcoinAddressState = {
+ [BitcoinAddressFormat.Legacy]: {
+ enabled: true,
+ weight: 1
+ },
+ [BitcoinAddressFormat.Compatibility]: {
+ enabled: true,
+ weight: 1
+ },
+ [BitcoinAddressFormat.Segwit]: {
+ enabled: true,
+ weight: 1
+ }
+};
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.tsx b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.tsx
new file mode 100644
index 000000000..ea20f6257
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.tsx
@@ -0,0 +1,177 @@
+import React from 'react';
+import Button from '@mui/material/Button';
+import InfoIcon from '@mui/icons-material/Info';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import TextField from '~components/TextField';
+import CheckboxPill from '~components/pills/CheckboxPill';
+import { DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { Tooltip } from '~components/tooltips';
+import { BitcoinAddressFormat } from './BitcoinAddress.state';
+import styles from './BitcoinAddress.scss';
+
+const BitcoinDialog = ({ visible, data, id, onClose, coreI18n, onUpdate, i18n }: any) => {
+ const onToggleFormat = (format: BitcoinAddressFormat, checked: boolean): void => {
+ onUpdate({
+ ...data,
+ [format]: {
+ ...data[format],
+ enabled: checked
+ }
+ });
+ };
+
+ const onChangeWeight = (prop: BitcoinAddressFormat, weight: number): void => {
+ onUpdate({
+ ...data,
+ [prop]: {
+ ...data[prop],
+ weight
+ }
+ });
+ };
+
+ return (
+
+
+
{i18n.bitcoinSettings}
+
+
+
+
+ {coreI18n.format}
+
+
+ {i18n.weight}
+
+
+
+
+
+
+
+
+
+
+ onToggleFormat(BitcoinAddressFormat.Legacy, !data[BitcoinAddressFormat.Legacy].enabled)}
+ name={`format-${id}`}
+ checked={data[BitcoinAddressFormat.Legacy].enabled}
+ />
+
+
+ onChangeWeight(BitcoinAddressFormat.Legacy, parseInt(e.target.value))}
+ />
+
+
+
+
+
+ onToggleFormat(BitcoinAddressFormat.Compatibility, !data[BitcoinAddressFormat.Compatibility].enabled)
+ }
+ name={`format-${id}`}
+ checked={data[BitcoinAddressFormat.Compatibility].enabled}
+ />
+
+
+ onChangeWeight(BitcoinAddressFormat.Compatibility, parseInt(e.target.value))}
+ />
+
+
+
+
+ onToggleFormat(BitcoinAddressFormat.Segwit, !data[BitcoinAddressFormat.Segwit].enabled)}
+ name={`format-${id}`}
+ checked={data[BitcoinAddressFormat.Segwit].enabled}
+ />
+
+
+ onChangeWeight(BitcoinAddressFormat.Segwit, parseInt(e.target.value))}
+ />
+
+
+
+
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ data, id, i18n, coreI18n, onUpdate }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+
+ let count = 0;
+ if (data[BitcoinAddressFormat.Legacy].enabled) {
+ count++;
+ }
+ if (data[BitcoinAddressFormat.Compatibility].enabled) {
+ count++;
+ }
+ if (data[BitcoinAddressFormat.Segwit].enabled) {
+ count++;
+ }
+
+ let buttonLabel = `1 ${i18n.format}`;
+ if (count > 1) {
+ buttonLabel = `${count} ${i18n.formats}`;
+ }
+
+ return (
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+ setDialogVisibility(false)}
+ onUpdate={onUpdate}
+ />
+
+ );
+};
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(50)',
+ field_Oracle: 'varchar2(50)',
+ field_MSSQL: 'VARCHAR(50) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.worker.ts b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.worker.ts
new file mode 100644
index 000000000..d5b827bb1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/BitcoinAddress.worker.ts
@@ -0,0 +1,6 @@
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './BitcoinAddress.generate';
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/__tests__/BitcoinAddress.generate.test.ts b/packages/plugins/src/dataTypes/BitcoinAddress/__tests__/BitcoinAddress.generate.test.ts
new file mode 100644
index 000000000..123b66f11
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/__tests__/BitcoinAddress.generate.test.ts
@@ -0,0 +1,26 @@
+import { generate } from '../BitcoinAddress.generate';
+import { BitcoinAddressFormat, BitcoinAddressState } from '../BitcoinAddress.state';
+import { DTGenerationData } from '~types/dataTypes';
+
+describe('generate', () => {
+ it('generates', () => {
+ const rowState: BitcoinAddressState = {
+ [BitcoinAddressFormat.Legacy]: {
+ enabled: true,
+ weight: 1
+ },
+ [BitcoinAddressFormat.Compatibility]: {
+ enabled: false,
+ weight: 1
+ },
+ [BitcoinAddressFormat.Segwit]: {
+ enabled: false,
+ weight: 1
+ }
+ };
+ const state = {
+ rowState
+ };
+ expect(generate(state as unknown as DTGenerationData)).toEqual({ display: '' });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/bundle.ts b/packages/plugins/src/dataTypes/BitcoinAddress/bundle.ts
new file mode 100644
index 000000000..d7da7cdc9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/bundle.ts
@@ -0,0 +1,11 @@
+import { DTBundle } from '~types/dataTypes';
+import { Options, getMetadata } from './BitcoinAddress';
+import { initialState } from './BitcoinAddress.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/config.ts b/packages/plugins/src/dataTypes/BitcoinAddress/config.ts
new file mode 100644
index 000000000..f6026c471
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'financial',
+ fieldGroupOrder: 5
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/i18n/en.json b/packages/plugins/src/dataTypes/BitcoinAddress/i18n/en.json
new file mode 100644
index 000000000..a43f3d31e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/i18n/en.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "Bitcoin Address",
+ "DESC": "This generates a random Bitcoin address in any of the three formats: P2PKH/Legacy, P2SH/Compatibility and P2WPKH/Bech32.",
+ "DEFAULT_TITLE": "bitcoin",
+ "weight": "Weight",
+ "weightDesc": "The weight field determines how likely the format will be generated relative to the other selected formats. So if you select 2 formats and enter weights of 9 and 1, the first format will be 9 times more likely to appear than the second.",
+ "format": "Format",
+ "formats": "Formats",
+ "bitcoinSettings": "Bitcoin Settings"
+}
diff --git a/packages/plugins/src/dataTypes/BitcoinAddress/i18n/ru.json b/packages/plugins/src/dataTypes/BitcoinAddress/i18n/ru.json
new file mode 100644
index 000000000..5533d1ee8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/BitcoinAddress/i18n/ru.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "Биткойн-адрес",
+ "DESC": "Это генерирует случайный биткойн-адрес в любом из трех форматов: P2PKH/Legacy, P2SH/Compatibility и P2WPKH/Bech32.",
+ "DEFAULT_TITLE": "биткойн",
+ "weight": "Масса",
+ "weightDesc": "Поле веса определяет вероятность того, что формат будет сгенерирован относительно других выбранных форматов. Таким образом, если вы выберете 2 формата и введете веса 9 и 1, вероятность появления первого формата будет в 9 раз выше, чем второго.",
+ "format": "Формат",
+ "formats": "Форматы",
+ "bitcoinSettings": "Настройки биткойнов"
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/Boolean.generate.ts b/packages/plugins/src/dataTypes/Boolean/Boolean.generate.ts
new file mode 100644
index 000000000..dfa9a8754
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/Boolean.generate.ts
@@ -0,0 +1,18 @@
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ // for backward compatibility
+ const formats = Array.isArray(data.rowState) ? data.rowState : data.rowState.values;
+
+ let val = '';
+ if (formats.length) {
+ let chosenFormat = formats[0];
+ if (formats.length > 1) {
+ chosenFormat = formats[utils.randomUtils.getRandomNum(0, formats.length - 1)];
+ }
+ val = chosenFormat.trim();
+ }
+
+ return { display: val };
+};
diff --git a/packages/plugins/src/dataTypes/Boolean/Boolean.state.tsx b/packages/plugins/src/dataTypes/Boolean/Boolean.state.tsx
new file mode 100755
index 000000000..2de0b25a1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/Boolean.state.tsx
@@ -0,0 +1,17 @@
+export type GenerationOptionsType = {
+ values: string[];
+};
+
+export type BooleanState = {
+ example: string;
+ values: string[];
+};
+
+export const defaultGenerationOptions = {
+ values: ['Yes', 'No']
+};
+
+export const initialState: BooleanState = {
+ example: 'Yes|No',
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/Boolean/Boolean.tsx b/packages/plugins/src/dataTypes/Boolean/Boolean.tsx
new file mode 100755
index 000000000..0c27ddbe1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/Boolean.tsx
@@ -0,0 +1,54 @@
+import * as React from 'react';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import Dropdown from '~components/dropdown/Dropdown';
+import CreatablePillField from '~components/creatablePillField/CreatablePillField';
+import { BooleanState, GenerationOptionsType } from './Boolean.state';
+
+export const Example = ({ data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: any): void => {
+ onUpdate({
+ example: value,
+ values: value.split('|')
+ });
+ };
+
+ const options = [
+ { value: 'Yes|No', label: 'Yes / No' },
+ { value: 'true|false', label: 'true / false' },
+ { value: 'True|False', label: 'True / False' },
+ { value: '0|1', label: '0 / 1' },
+ { value: 'Y|N', label: 'Y / N' },
+ { value: 'T|F', label: 'T / F' },
+ { value: 'On|Off', label: 'On / Off' },
+ { value: 'Down|Up', label: 'Down / Up' }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+export const Options = ({ coreI18n, data, onUpdate }: DTOptionsProps) => (
+ onUpdate({ ...data, values })}
+ />
+);
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+
+ >
+);
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'boolean'
+ },
+ sql: {
+ field: 'varchar(255) default NULL',
+ field_Oracle: 'varchar2(255) default NULL',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
+
+export const rowStateReducer = (state: BooleanState): GenerationOptionsType => ({ values: state.values });
diff --git a/packages/plugins/src/dataTypes/Boolean/Boolean.worker.ts b/packages/plugins/src/dataTypes/Boolean/Boolean.worker.ts
new file mode 100644
index 000000000..326484c1d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/Boolean.worker.ts
@@ -0,0 +1,13 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Boolean.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/Boolean/README.md b/packages/plugins/src/dataTypes/Boolean/README.md
new file mode 100644
index 000000000..8ff450bbe
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/README.md
@@ -0,0 +1,113 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Boolean
+
+This Data Type generates random Boolean strings according to whatever format you want. It's actually just a convenience
+wrapper over some lower-level functions that supplies a bunch of preset boolean options via the UI, like `Yes/No`, `0/1`
+and others.
+
+By the way, even though you'll typically enter two values to randomly choose between, there's no reason why you can't
+add more - like the in the final field here.
+
+## Typings
+
+The settings for this Data Type are just a single `value` property, containing an array of the values you want the script
+to randomly pull from.
+
+```typescript
+{
+ value: string[];
+}
+```
+
+Note that they **must be** strings - so even if you want to generate a numeric boolean value (e.g. `0` and `1`), you'll still
+need to enter a string here (`["0", "1"]`). Whether or not the actual generated content will be a string, number, boolean
+etc. value will depend on the Export Type you've chosen. e.g. in XML, it would just output the character as-is without
+single or double quotes around it, even if it was a string. But if it's in a programming language, it has to be
+syntactically correct for that language.
+
+This Data Type asks the Export Types to _infer_ the type of data it us, based on the content generated. So it's up to the
+Export Type to determine exactly it appears. In the examples below, you can see that the [JSON](../../exportTypes/JSON/README.md)
+Export Type chose to render the genuine JS boolean and numbers as booleans and numbers, and not double quote them.
+
+### Example
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "Boolean",
+ title: "boolean1",
+ settings: {
+ values: ["Yes", "No"]
+ }
+ },
+ {
+ plugin: "Boolean",
+ title: "boolean2",
+ settings: {
+ values: ["0", "1"]
+ }
+ },
+ {
+ plugin: "Boolean",
+ title: "boolean3",
+ settings: {
+ values: ["true", "false"]
+ }
+ },
+ {
+ plugin: "Boolean",
+ title: "notReallyABoolean",
+ settings: {
+ values: ["Yes", "No", "Maybe"]
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "boolean1": "No",
+ "boolean2": 1,
+ "boolean3": true,
+ "notReallyABoolean": "Maybe"
+ },
+ {
+ "boolean1": "Yes",
+ "boolean2": 0,
+ "boolean3": false,
+ "notReallyABoolean": "Maybe"
+ },
+ {
+ "boolean1": "No",
+ "boolean2": 0,
+ "boolean3": false,
+ "notReallyABoolean": "Maybe"
+ },
+ {
+ "boolean1": "Yes",
+ "boolean2": 0,
+ "boolean3": false,
+ "notReallyABoolean": "No"
+ },
+ {
+ "boolean1": "Yes",
+ "boolean2": 1,
+ "boolean3": true,
+ "notReallyABoolean": "No"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/Boolean/__tests__/Boolean.generate.test.ts b/packages/plugins/src/dataTypes/Boolean/__tests__/Boolean.generate.test.ts
new file mode 100644
index 000000000..093918791
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/__tests__/Boolean.generate.test.ts
@@ -0,0 +1,26 @@
+import { onmessage } from '../Boolean.worker';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+
+const i18n = require('../i18n/en.json');
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ it('generates random data', () => {
+ const payload: any = {
+ data: {
+ ...getBlankDTGeneratorPayload(),
+ rowState: ['Booyah'],
+ i18n
+ }
+ };
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: 'Booyah' });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Boolean/__tests__/Boolean.test.tsx b/packages/plugins/src/dataTypes/Boolean/__tests__/Boolean.test.tsx
new file mode 100644
index 000000000..6a0c54cc4
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/__tests__/Boolean.test.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../Boolean';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ dimensions: { width: 100, height: 100 }
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Boolean/bundle.ts b/packages/plugins/src/dataTypes/Boolean/bundle.ts
new file mode 100644
index 000000000..b7e88fda6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, rowStateReducer, getMetadata } from './Boolean';
+import { initialState } from './Boolean.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ rowStateReducer,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Boolean/config.ts b/packages/plugins/src/dataTypes/Boolean/config.ts
new file mode 100644
index 000000000..ca6feaadd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'numeric',
+ fieldGroupOrder: 11
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/ar.json b/packages/plugins/src/dataTypes/Boolean/i18n/ar.json
new file mode 100644
index 000000000..974dc5aa7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/ar.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "قيمة منطقية",
+ "DESC": "يولد قيمة منطقية بالتنسيق الذي تحتاجه.",
+ "DEFAULT_TITLE": "قيمة منطقية",
+ "helpIntro": "تحتوي القائمة المنسدلة \"الأمثلة\" على عدة أمثلة على قيم منطقية (كلها بالإنجليزية أو عالمية). لإنشاء قيم أخرى ، ما عليك سوى كتابة نص في حقل الخيارات والنقر فوق إدخال."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/de.json b/packages/plugins/src/dataTypes/Boolean/i18n/de.json
new file mode 100644
index 000000000..9ab8ba063
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/de.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "Boolean",
+ "DESC": "Dieser Datentyp zufällig generiert eine Boolean nach dem Format, das Sie angeben.",
+ "DEFAULT_TITLE": "boolescherwert",
+ "helpIntro": "Die Dropdown-Liste Beispiele enthält mehrere boolesche Beispielwerte (alle englisch oder universell). Um andere Werte zu erstellen, geben Sie einfach Text in das Feld Optionen ein und klicken Sie auf die Eingabetaste."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/en.json b/packages/plugins/src/dataTypes/Boolean/i18n/en.json
new file mode 100644
index 000000000..04224b0e2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/en.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "Boolean",
+ "DESC": "Generates a boolean value in the format you need.",
+ "DEFAULT_TITLE": "boolean",
+ "helpIntro": "The Examples dropdown contains several example boolean values (all English or universal). To create other values, just type text into the Options field and click <enter>."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/es.json b/packages/plugins/src/dataTypes/Boolean/i18n/es.json
new file mode 100644
index 000000000..cc1dd97a0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/es.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "Boolean",
+ "DESC": "Este tipo de dato genera aleatoriamente Boolean de acuerdo al formato que especifiques.",
+ "DEFAULT_TITLE": "booleano",
+ "helpIntro": "El menú desplegable Ejemplos contiene varios valores booleanos de ejemplo (todos en inglés o universales). Para crear otros valores, simplemente escriba texto en el campo Opciones y haga clic en Intro."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/fr.json b/packages/plugins/src/dataTypes/Boolean/i18n/fr.json
new file mode 100644
index 000000000..3626d8472
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/fr.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "Booléen",
+ "DESC": "Ce type de données génère aléatoirement des noms de personnes (principalement occidentaux) en fonction du format que vous spécifiez.",
+ "DEFAULT_TITLE": "booleen",
+ "helpIntro": "La liste déroulante Exemples contient plusieurs exemples de valeurs booléennes (toutes en anglais ou universelles). Pour créer d'autres valeurs, tapez simplement du texte dans le champ Options et cliquez sur Entrée."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/hi.json b/packages/plugins/src/dataTypes/Boolean/i18n/hi.json
new file mode 100644
index 000000000..6bab50e09
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/hi.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "बूलियन",
+ "DESC": "आपको आवश्यक प्रारूप में एक बूलियन मान उत्पन्न करता है।",
+ "DEFAULT_TITLE": "बूलियन",
+ "helpIntro": "उदाहरण ड्रॉपडाउन में कई उदाहरण बूलियन मान (सभी अंग्रेज़ी या सार्वभौमिक) हैं। अन्य मान बनाने के लिए, बस विकल्प फ़ील्ड में टेक्स्ट टाइप करें और एंटर पर क्लिक करें।"
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/ja.json b/packages/plugins/src/dataTypes/Boolean/i18n/ja.json
new file mode 100644
index 000000000..c15528f9d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/ja.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "ブール値",
+ "DESC": "必要な形式でブール値を生成します。",
+ "DEFAULT_TITLE": "ブール値",
+ "helpIntro": "[例]ドロップダウンには、いくつかのブール値の例(すべて英語またはユニバーサル)が含まれています。 他の値を作成するには、[オプション]フィールドにテキストを入力して、[Enter]をクリックします。"
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/nl.json b/packages/plugins/src/dataTypes/Boolean/i18n/nl.json
new file mode 100644
index 000000000..d15e4bf8f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/nl.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "Boolean",
+ "DESC": "Genereerd een Boolean waarde in elk gewenst formaat.",
+ "DEFAULT_TITLE": "boolean",
+ "helpIntro": "De vervolgkeuzelijst Voorbeelden bevat verschillende voorbeeldbooleaanse waarden (allemaal Engels of universeel). Om andere waarden te maken, typ gewoon tekst in het veld Opties en klik op Enter."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/pt.json b/packages/plugins/src/dataTypes/Boolean/i18n/pt.json
new file mode 100644
index 000000000..e11cdde7b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/pt.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "Boleano",
+ "DESC": "Gera um valor booleano no formato que você precisa.",
+ "DEFAULT_TITLE": "boleano",
+ "helpIntro": "A lista suspensa Exemplos contém vários valores booleanos de exemplo (todos em inglês ou universais). Para criar outros valores, basta digitar o texto no campo Opções e clicar em <inserir>."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/ru.json b/packages/plugins/src/dataTypes/Boolean/i18n/ru.json
new file mode 100644
index 000000000..cecc3e717
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/ru.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "логический",
+ "DESC": "Генерирует логическое значение в нужном вам формате.",
+ "DEFAULT_TITLE": "логический",
+ "helpIntro": "Раскрывающийся список «Примеры» содержит несколько примеров логических значений (все на английском или универсальном языке). Чтобы создать другие значения, просто введите текст в поле «Параметры» и нажмите «Ввод»."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/ta.json b/packages/plugins/src/dataTypes/Boolean/i18n/ta.json
new file mode 100644
index 000000000..efd36d5f2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/ta.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "பூலியன்",
+ "DESC": "உங்களுக்கு தேவையான வடிவத்தில் பூலியன் மதிப்பை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "பூலியன்",
+ "helpIntro": "எடுத்துக்காட்டுகள் கீழிறங்கும் பல உதாரண பூலியன் மதிப்புகள் உள்ளன (அனைத்தும் ஆங்கிலம் அல்லது உலகளாவியவை). பிற மதிப்புகளை உருவாக்க, விருப்பங்கள் புலத்தில் உரையைத் தட்டச்சு செய்து உள்ளிடவும் என்பதைக் கிளிக் செய்யவும்."
+}
diff --git a/packages/plugins/src/dataTypes/Boolean/i18n/zh.json b/packages/plugins/src/dataTypes/Boolean/i18n/zh.json
new file mode 100644
index 000000000..7b9c5a296
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Boolean/i18n/zh.json
@@ -0,0 +1,6 @@
+{
+ "NAME": "布尔型",
+ "DESC": "以所需的格式生成布尔值。",
+ "DEFAULT_TITLE": "布尔值",
+ "helpIntro": "“示例”下拉列表包含几个示例布尔值(全部为英语或通用)。 要创建其他值,只需在“选项”字段中键入文本,然后单击Enter。"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/CVV.generate.ts b/packages/plugins/src/dataTypes/CVV/CVV.generate.ts
new file mode 100644
index 000000000..3ad656dd7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/CVV.generate.ts
@@ -0,0 +1,8 @@
+// @author Ben Keen , origin code Zeeshan Shaikh
+// @package DataTypes
+import { DTGenerateResult } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = (_data: any, utils: WorkerUtils): DTGenerateResult => ({
+ display: utils.randomUtils.getRandomNum(111, 999)
+});
diff --git a/packages/plugins/src/dataTypes/CVV/CVV.state.tsx b/packages/plugins/src/dataTypes/CVV/CVV.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/CVV.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/dataTypes/CVV/CVV.tsx b/packages/plugins/src/dataTypes/CVV/CVV.tsx
new file mode 100644
index 000000000..48a6ccc49
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/CVV.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import { DTHelpProps, DTMetadata } from '~types/dataTypes';
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.DESC}
;
+
+export const getMetadata = (): DTMetadata => ({
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/CVV/CVV.worker.ts b/packages/plugins/src/dataTypes/CVV/CVV.worker.ts
new file mode 100644
index 000000000..29d350fb2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/CVV.worker.ts
@@ -0,0 +1,17 @@
+// @author Ben Keen , origin code Zeeshan Shaikh
+// @package DataTypes
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './CVV.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(undefined, utils));
+};
+
diff --git a/packages/plugins/src/dataTypes/CVV/README.md b/packages/plugins/src/dataTypes/CVV/README.md
new file mode 100644
index 000000000..c03dacd1f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/README.md
@@ -0,0 +1,56 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » CVV
+
+CVV stands for Card Verification Value. It's that 3-digit number you see on the back of your credit card. This Data Type
+generates a random CVV. All it really does is generate a random 3 digit code. Simple!
+
+## Examples
+
+```typescript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "CVV",
+ title: "cvv",
+ settings: {}
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+ [
+ {
+ "cvv": 454
+ },
+ {
+ "cvv": 742
+ },
+ {
+ "cvv": 945
+ },
+ {
+ "cvv": 285
+ },
+ {
+ "cvv": 907
+ },
+ {
+ "cvv": 408
+ },
+ {
+ "cvv": 572
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/CVV/__tests__/CVV.generate.test.ts b/packages/plugins/src/dataTypes/CVV/__tests__/CVV.generate.test.ts
new file mode 100644
index 000000000..8b82cd473
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/__tests__/CVV.generate.test.ts
@@ -0,0 +1,29 @@
+import sinon from 'sinon';
+import { onmessage } from '../CVV.worker';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+import utils from '~utils/index';
+
+const i18n = require('../i18n/en.json');
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ it('generates random data', () => {
+ const payload: any = {
+ data: {
+ ...getBlankDTGeneratorPayload(),
+ i18n
+ }
+ };
+
+ sinon.stub(utils.randomUtils, 'getRandomNum').onCall(0).returns(123);
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: 123 });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/CVV/__tests__/CVV.ui.test.tsx b/packages/plugins/src/dataTypes/CVV/__tests__/CVV.ui.test.tsx
new file mode 100644
index 000000000..d7b34ebf3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/__tests__/CVV.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../CVV';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/CVV/bundle.ts b/packages/plugins/src/dataTypes/CVV/bundle.ts
new file mode 100644
index 000000000..07b6355c6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/bundle.ts
@@ -0,0 +1,9 @@
+import { DTBundle } from '~types/dataTypes';
+import { Help, getMetadata } from './CVV';
+
+const bundle: DTBundle = {
+ Help,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/CVV/config.ts b/packages/plugins/src/dataTypes/CVV/config.ts
new file mode 100644
index 000000000..1607848f5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'financial',
+ fieldGroupOrder: 50
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/ar.json b/packages/plugins/src/dataTypes/CVV/i18n/ar.json
new file mode 100644
index 000000000..f3484f376
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/ar.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "يولد رقم بطاقة ائتمان عشوائي CVV من 111 إلى 999",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/de.json b/packages/plugins/src/dataTypes/CVV/i18n/de.json
new file mode 100644
index 000000000..2eba57fae
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/de.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "Erzeugt eine Zufalls CVV-Nummer von 111 bis 999.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/en.json b/packages/plugins/src/dataTypes/CVV/i18n/en.json
new file mode 100644
index 000000000..63716d146
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/en.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "Generates a random credit card CVV number from 111 to 999.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/es.json b/packages/plugins/src/dataTypes/CVV/i18n/es.json
new file mode 100644
index 000000000..b2d696771
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/es.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "Genera un número de tarjeta de crédito CVV azar 111-999.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/fr.json b/packages/plugins/src/dataTypes/CVV/i18n/fr.json
new file mode 100644
index 000000000..9ef7c4d27
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/fr.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "Génère un nombre aléatoire de CVV de la carte de crédit de 111 à 999.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/hi.json b/packages/plugins/src/dataTypes/CVV/i18n/hi.json
new file mode 100644
index 000000000..51d3c5ceb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/hi.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "111 से 999 तक एक रैंडम क्रेडिट कार्ड CVV नंबर जेनरेट करता है।",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/ja.json b/packages/plugins/src/dataTypes/CVV/i18n/ja.json
new file mode 100644
index 000000000..94d00a14e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/ja.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "111から999までのランダムなクレジットカードCVV番号を生成します。",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/nl.json b/packages/plugins/src/dataTypes/CVV/i18n/nl.json
new file mode 100644
index 000000000..81adec4be
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/nl.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "Genereert een willekeurig creditcard CVV-nummer tussen 111 en 999.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/pt.json b/packages/plugins/src/dataTypes/CVV/i18n/pt.json
new file mode 100644
index 000000000..26829071b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/pt.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "Gera um número CVV de cartão de crédito aleatório de 111 a 999.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/ru.json b/packages/plugins/src/dataTypes/CVV/i18n/ru.json
new file mode 100644
index 000000000..8c2272898
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/ru.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "Генерирует случайный номер CVV кредитной карты от 111 до 999.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/ta.json b/packages/plugins/src/dataTypes/CVV/i18n/ta.json
new file mode 100644
index 000000000..301b06623
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/ta.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "சீரற்ற கிரெடிட் கார்டு சி.வி.வி எண்ணை 111 முதல் 999 வரை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/CVV/i18n/zh.json b/packages/plugins/src/dataTypes/CVV/i18n/zh.json
new file mode 100644
index 000000000..e656d99d9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/CVV/i18n/zh.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "CVV",
+ "DESC": "生成从111到999的随机信用卡CVV号码。",
+ "DEFAULT_TITLE": "cvv"
+}
diff --git a/packages/plugins/src/dataTypes/City/City.generate.ts b/packages/plugins/src/dataTypes/City/City.generate.ts
new file mode 100644
index 000000000..ee1b89765
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/City.generate.ts
@@ -0,0 +1,49 @@
+import { CityState, RegionSource, RegionSourceEnum } from './City.state';
+import { CountryType, Region } from '~types/countries';
+import { DTGenerateResult, DTGenerationExistingRowData, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+import { countryList } from '../../../../_plugins';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { rowState } = data;
+ const { source, selectedCountries } = rowState as CityState;
+
+ let country: CountryType;
+ let regionRow: any;
+ if (source === RegionSourceEnum.regionRow) {
+ regionRow = data.existingRowData.find(({ id }: DTGenerationExistingRowData) => id === rowState.targetRowId);
+ country = regionRow!.data.countryDataType;
+ } else if (source === RegionSourceEnum.any) {
+ country = utils.randomUtils.getRandomArrayValue(countryList as CountryType[]);
+ } else {
+ const list = rowState.selectedCountries.length ? selectedCountries : countryList;
+ country = utils.randomUtils.getRandomArrayValue(list as string[]) as CountryType;
+ }
+
+ // this can occur if the user hasn't configured the region DT properly
+ if (!country) {
+ return { display: '' };
+ }
+
+ const countryData = data.countryData[country];
+
+ let selectedRegion;
+ if (regionRow) {
+ // check the user fully filled out the region row. If the didn't include a display format, it'll be incomplete
+ if (!regionRow.data.displayFormat || !regionRow.data.display) {
+ return { display: '' };
+ }
+ selectedRegion = countryData.regions.find((i: Region) => {
+ if (regionRow.data.displayFormat === 'short') {
+ return i.regionShort === regionRow.data.display;
+ }
+ return i.regionName === regionRow.data.display;
+ });
+ } else {
+ selectedRegion = utils.randomUtils.getRandomArrayValue(countryData.regions);
+ }
+
+ return {
+ display: utils.randomUtils.getRandomArrayValue(selectedRegion.cities)
+ };
+};
diff --git a/packages/plugins/src/dataTypes/City/City.scss b/packages/plugins/src/dataTypes/City/City.scss
new file mode 100644
index 000000000..a013c6a46
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/City.scss
@@ -0,0 +1,3 @@
+.buttonLabel {
+ margin-top: 4px;
+}
diff --git a/packages/plugins/src/dataTypes/City/City.scss.d.ts b/packages/plugins/src/dataTypes/City/City.scss.d.ts
new file mode 100644
index 000000000..45e0a5d12
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/City.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace CityScssNamespace {
+ export interface ICityScss {
+ buttonLabel: string;
+ }
+}
+
+declare const CityScssModule: CityScssNamespace.ICityScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: CityScssNamespace.ICityScss;
+};
+
+export = CityScssModule;
diff --git a/packages/plugins/src/dataTypes/City/City.state.tsx b/packages/plugins/src/dataTypes/City/City.state.tsx
new file mode 100644
index 000000000..230863fe1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/City.state.tsx
@@ -0,0 +1,37 @@
+import { CountryType } from '../../';
+
+export const enum RegionSourceEnum {
+ any = 'any',
+ countries = 'countries',
+ regionRow = 'regionRow'
+}
+export type RegionSource = `${RegionSourceEnum}`;
+
+export type CityStateAny = {
+ source: 'any';
+ selectedCountries?: [];
+ targetRowId?: '';
+};
+
+export type CityStateRegionRow = {
+ source: 'regionRow';
+ selectedCountries?: [];
+ targetRowId: string;
+};
+
+export type CityStateCountryRow = {
+ source: 'countries';
+ selectedCountries: CountryType[];
+};
+
+export type CityState = CityStateAny | CityStateRegionRow | CityStateCountryRow;
+
+export type GenerationOptionsType = CityState;
+
+export const initialState: CityState = {
+ source: 'any',
+ selectedCountries: [],
+ targetRowId: ''
+};
+
+export const defaultGenerationOptions = initialState;
diff --git a/packages/plugins/src/dataTypes/City/City.store.tsx b/packages/plugins/src/dataTypes/City/City.store.tsx
new file mode 100644
index 000000000..983913312
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/City.store.tsx
@@ -0,0 +1,40 @@
+import { createSelector } from 'reselect';
+import { DTCustomProps } from '~types/dataTypes';
+import { getSortedRowsArray } from '~store/generator/generator.selectors';
+import { REMOVE_ROW, SELECT_DATA_TYPE } from '~store/generator/generator.actions';
+import { CityState, CityStateRegionRow, RegionSourceEnum } from './City.state';
+
+const getRegionRows = createSelector(getSortedRowsArray, (rows) =>
+ rows.map((row, index) => ({ ...row, index })).filter(({ dataType }) => dataType === 'Region')
+);
+
+export const customProps: DTCustomProps = {
+ regionRows: getRegionRows
+};
+
+export const actionInterceptors = {
+ // when a Region plugin row is removed, clean up any city fields that may have been mapped to it
+ [REMOVE_ROW]: (regionRowId: string, rowState: CityStateRegionRow, actionPayload: any): CityState | null => {
+ if (actionPayload.id === rowState.targetRowId) {
+ return {
+ ...rowState,
+ source: RegionSourceEnum.any,
+ targetRowId: ''
+ };
+ }
+ return null;
+ },
+
+ [SELECT_DATA_TYPE]: (regionRowId: string, rowState: CityStateRegionRow, actionPayload: any): CityState | null => {
+ if (actionPayload.id === rowState.targetRowId) {
+ if (actionPayload.value !== 'Region') {
+ return {
+ ...rowState,
+ source: RegionSourceEnum.any,
+ targetRowId: ''
+ };
+ }
+ }
+ return null;
+ }
+};
diff --git a/packages/plugins/src/dataTypes/City/City.tsx b/packages/plugins/src/dataTypes/City/City.tsx
new file mode 100644
index 000000000..c73c595cf
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/City.tsx
@@ -0,0 +1,168 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import { DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { getI18nString } from '~utils/langUtils';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import { countryList } from '../../../../_plugins';
+import { RegionSourceEnum, RegionSource } from './City.state';
+import styles from './City.scss';
+
+const CityDialog = ({ visible, data, id, onClose, countryI18n, coreI18n, i18n, onUpdate, regionRows }: any) => {
+ const regionPluginRows = regionRows.map(({ index, id, title }: any) => ({
+ value: id,
+ label: `${i18n.row} #${index + 1}: ${title}`
+ }));
+
+ const regionPluginRowsExist = regionPluginRows.length > 0;
+
+ const onUpdateSource = (source: RegionSource): void => {
+ const newValues = {
+ ...data,
+ source
+ };
+ if (source === RegionSourceEnum.regionRow) {
+ newValues.targetRowId = regionPluginRows[0].value;
+ }
+ onUpdate(newValues);
+ };
+
+ const onChangeTargetRow = (row: DropdownOption): void => {
+ onUpdate({
+ ...data,
+ targetRowId: row.value
+ });
+ };
+
+ const onSelectCountries = (countries: any): void => {
+ onUpdate({
+ ...data,
+ selectedCountries: countries ? countries.map(({ value }: DropdownOption) => value) : []
+ });
+ };
+
+ const getRegionRow = (): React.ReactNode => {
+ if (data.source !== RegionSourceEnum.regionRow) {
+ return null;
+ }
+
+ return ;
+ };
+
+ const getCountryPluginsList = (): React.ReactNode => {
+ if (data.source !== RegionSourceEnum.countries) {
+ return null;
+ }
+ const countryPluginOptions = countryList.map((countryName) => ({
+ value: countryName,
+ label: countryI18n[countryName].countryName
+ }));
+
+ return (
+
+ );
+ };
+
+ return (
+
+
+
{i18n.selectCities}
+
+ {i18n.explanation}
+
+ {i18n.source}
+
+
+ onUpdateSource(RegionSourceEnum.any)}
+ name={`${id}-source`}
+ checked={data.source === RegionSourceEnum.any}
+ tooltip={i18n.anyDesc}
+ />
+ onUpdateSource(RegionSourceEnum.countries)}
+ name={`${id}-source`}
+ checked={data.source === RegionSourceEnum.countries}
+ tooltip={i18n.countriesDesc}
+ />
+ onUpdateSource(RegionSourceEnum.regionRow)}
+ name={`${id}-source`}
+ checked={data.source === RegionSourceEnum.regionRow}
+ tooltip={i18n.rowDesc}
+ disabled={!regionPluginRowsExist}
+ />
+
+
+ {getRegionRow()}
+ {getCountryPluginsList()}
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ id, data, coreI18n, i18n, countryI18n, onUpdate, regionRows }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+ const numSelected = data.selectedCountries.length;
+
+ let label = '';
+ if (data.source === RegionSourceEnum.any) {
+ label = i18n.anyCity;
+ } else if (data.source === RegionSourceEnum.countries) {
+ if (numSelected === 1) {
+ label = i18n.anyCityFrom1Country;
+ } else {
+ label = getI18nString(i18n.anyCityFromNCountries, [`${numSelected} `]);
+ }
+ } else if (data.source === RegionSourceEnum.regionRow) {
+ const row = regionRows.find((row: any) => row.id === data.targetRowId);
+ const rowNum = row.index + 1;
+ label = `${i18n.regionRow} #${rowNum}`;
+ }
+
+ return (
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+ setDialogVisibility(false)}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.DESC}
;
+
+export const getMetadata = (): DTMetadata => ({
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/City/City.worker.ts b/packages/plugins/src/dataTypes/City/City.worker.ts
new file mode 100644
index 000000000..f7bf686a1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/City.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './City.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/City/README.md b/packages/plugins/src/dataTypes/City/README.md
new file mode 100644
index 000000000..472e1631e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/README.md
@@ -0,0 +1,182 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » City
+
+Generates a random city name. You have the choice of generating a city name from anywhere in the world, or mapping
+it to other data in the generated row, so the city is actually within a region or country from another field. This makes
+the generated data more realistic.
+
+## Typings
+
+```
+{
+ source: RegionSource; // 'any', 'countries', 'regionRow'
+ selectedCountries: CountryType[];
+ targetRowId: string;
+}
+```
+
+## Examples
+
+- [Random city from anywhere](#random-city-from-anywhere)
+- [Random city within a specific country list](#random-city-within-a-specific-country-list)
+- [Random city within a generated region row](#random-city-within-a-generated-region-row)
+
+### Random city from anywhere
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "City",
+ title: "any-city",
+ settings: {
+ source: "any"
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "any-city": "Changi"
+ },
+ {
+ "any-city": "Kitzbühel"
+ },
+ {
+ "any-city": "Glendale"
+ },
+ {
+ "any-city": "Coevorden"
+ },
+ {
+ "any-city": "Edam"
+ },
+ {
+ "any-city": "Te Awamutu"
+ },
+ ...
+]
+```
+
+### Random city within a specific country list
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "City",
+ title: "city-within-country",
+ settings: {
+ source: "countries",
+ selectedCountries: ["Australia", "Canada"]
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "city-within-country": "Greater Hobart"
+ },
+ {
+ "city-within-country": "Palmerston"
+ },
+ {
+ "city-within-country": "Coleville Lake"
+ },
+ {
+ "city-within-country": "Stratford"
+ },
+ {
+ "city-within-country": "Gjoa Haven"
+ },
+ {
+ "city-within-country": "Mount Isa"
+ },
+ ...
+]
+```
+
+### Random city within a generated region row
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "City",
+ title: "city-within-country",
+ settings: {
+ source: "countries",
+ selectedCountries: ["Australia", "Canada"]
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "region-source": "South Australia",
+ "city-within-region": "Whyalla"
+ },
+ {
+ "region-source": "Queensland",
+ "city-within-region": "Hervey Bay"
+ },
+ {
+ "region-source": "Bayern",
+ "city-within-region": "Fürth"
+ },
+ {
+ "region-source": "Baden Württemberg",
+ "city-within-region": "Friedrichshafen"
+ },
+ {
+ "region-source": "Baden Württemberg",
+ "city-within-region": "Tübingen"
+ },
+ {
+ "region-source": "Australian Capital Territory",
+ "city-within-region": "Canberra"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/City/__tests__/City.test.tsx b/packages/plugins/src/dataTypes/City/__tests__/City.test.tsx
new file mode 100644
index 000000000..356f2a9af
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/__tests__/City.test.tsx
@@ -0,0 +1,48 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Help, Options } from '../City';
+import { initialState } from '../City.state';
+
+const i18n = require('../i18n/en.json');
+
+const helpProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+const optionsProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ gridPanelDimensions: { width: 100, height: 100 },
+ isCountryNamesLoading: false,
+ isCountryNamesLoaded: false,
+ countryNamesMap: null
+};
+
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('Options', () => {
+ it('renders', () => {
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+
+ const { container } = render(
+
+ );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/City/bundle.ts b/packages/plugins/src/dataTypes/City/bundle.ts
new file mode 100644
index 000000000..e42d56a50
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/bundle.ts
@@ -0,0 +1,15 @@
+import { DTBundle } from '~types/dataTypes';
+import { Options, Help, getMetadata } from './City';
+import { initialState } from './City.state';
+import { customProps, actionInterceptors } from './City.store';
+
+const bundle: DTBundle = {
+ getMetadata,
+ initialState,
+ Options,
+ Help,
+ customProps,
+ actionInterceptors
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/City/config.ts b/packages/plugins/src/dataTypes/City/config.ts
new file mode 100644
index 000000000..fcd0351d2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/config.ts
@@ -0,0 +1,9 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'geo',
+ fieldGroupOrder: 20,
+ dependencies: ['Country', 'Region']
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/City/i18n/ar.json b/packages/plugins/src/dataTypes/City/i18n/ar.json
new file mode 100644
index 000000000..a14aa6710
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/ar.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "مدينة",
+ "DESC": "يعرض مدينة عشوائية ، أو إذا كانت البيانات متاحة ، يعرض مدينة للبلد / المنطقة المناسبة.",
+ "DEFAULT_TITLE": "مدينة",
+ "selectCities": "حدد المدن",
+ "regionRow": "صف المنطقة",
+ "source": "مصدر",
+ "anyCity": "أي مدينة",
+ "explanation": "يتحكم هذا في أسماء المدن التي سيتم إنشاؤها بواسطة هذا الحقل.",
+ "anyDesc": "يولد أي اسم مدينة.",
+ "countriesDesc": "يولد أسماء المدن من البلدان المحددة.",
+ "rowDesc": "يشير هذا إلى حقل منطقة معين في مجموعة البيانات الخاصة بك التي تحتوي على ملحقات المنطقة كمصدر لها. يُنشئ المدن التي تحدد القيمة في هذا المجال.",
+ "row": "صف",
+ "country": "بلد",
+ "countries": "بلدان",
+ "anyCityFrom1Country": "أي مدينة من دولة واحدة",
+ "anyCityFromNCountries": "أي مدينة من%1دولة"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/de.json b/packages/plugins/src/dataTypes/City/i18n/de.json
new file mode 100644
index 000000000..e71e414a8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/de.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "Stadt",
+ "DESC": "Zeigt eine zufällige Stadt an oder wenn Daten verfügbar sind, wird eine Stadt für das entsprechende Land / die entsprechende Region angezeigt.",
+ "DEFAULT_TITLE": "stadt",
+ "selectCities": "Wählen Sie Städte",
+ "regionRow": "Regionszeile",
+ "source": "Quelle",
+ "anyCity": "Irgendeine Stadt",
+ "explanation": "Hiermit wird gesteuert, welche Städtenamen von diesem Feld generiert werden.",
+ "anyDesc": "Erzeugt einen beliebigen Städtenamen.",
+ "countriesDesc": "Generiert Städtenamen aus den angegebenen Ländern.",
+ "rowDesc": "Dadurch wird ein bestimmtes Regionsfeld in Ihrem Datensatz ermittelt, dessen Quelle Regions-Plugins sind. Es werden Städte generiert, die dem Wert in diesem Feld zugeordnet sind.",
+ "row": "Reihe",
+ "country": "Land",
+ "countries": "Länder",
+ "anyCityFrom1Country": "Jede Stadt aus 1 Land",
+ "anyCityFromNCountries": "Jede Stadt aus %1 Ländern"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/en.json b/packages/plugins/src/dataTypes/City/i18n/en.json
new file mode 100644
index 000000000..1e4737cf2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/en.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "City",
+ "DESC": "Displays a random city, or if data is available, displays a city for the appropriate country/region.",
+ "DEFAULT_TITLE": "city",
+ "selectCities": "Select Cities",
+ "regionRow": "Region row",
+ "source": "Source",
+ "anyCity": "Any city",
+ "explanation": "This controls what city names will be generated by this field.",
+ "anyDesc": "Generates any city name.",
+ "countriesDesc": "Generates city names from the specified countries.",
+ "rowDesc": "This pinpoints a specific Region field in your data set that has Region Plugins as its source. It generates cities that map to the value in that field.",
+ "row": "Row",
+ "country": "country",
+ "countries": "countries",
+ "anyCityFrom1Country": "Any city from 1 country",
+ "anyCityFromNCountries": "Any city from %1 countries"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/es.json b/packages/plugins/src/dataTypes/City/i18n/es.json
new file mode 100644
index 000000000..3349cbfcf
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/es.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "Ciudad",
+ "DESC": "Muestra una ciudad aleatoria o, si hay datos disponibles, muestra una ciudad para el país / región correspondiente.",
+ "DEFAULT_TITLE": "ciudad",
+ "selectCities": "Seleccionar ciudades",
+ "regionRow": "Fila de región",
+ "source": "Fuente",
+ "anyCity": "Alguna ciudad",
+ "explanation": "Esto controla qué nombres de ciudades serán generados por este campo.",
+ "anyDesc": "Genera cualquier nombre de ciudad.",
+ "countriesDesc": "Genera nombres de ciudades de los países especificados.",
+ "rowDesc": "Esto señala un campo de región específico en su conjunto de datos que tiene complementos de región como fuente. Genera ciudades que se asignan al valor en ese campo.",
+ "row": "Fila",
+ "country": "país",
+ "countries": "países",
+ "anyCityFrom1Country": "Cualquier ciudad de 1 país",
+ "anyCityFromNCountries": "Cualquier ciudad de %1 países"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/fr.json b/packages/plugins/src/dataTypes/City/i18n/fr.json
new file mode 100644
index 000000000..7403958bb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/fr.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "Ville",
+ "DESC": "Affiche une ville au hasard ou, si des données sont disponibles, affiche une ville pour le pays / la région approprié.",
+ "DEFAULT_TITLE": "ville",
+ "selectCities": "Sélectionnez les villes",
+ "regionRow": "Ligne de région",
+ "source": "La source",
+ "anyCity": "N'importe quelle ville",
+ "explanation": "Cela contrôle les noms de villes qui seront générés par ce champ.",
+ "anyDesc": "Génère n'importe quel nom de ville.",
+ "countriesDesc": "Génère des noms de villes à partir des pays spécifiés.",
+ "rowDesc": "Cela identifie un champ de région spécifique dans votre ensemble de données qui a des plugins de région comme source. Il génère des villes qui correspondent à la valeur de ce champ.",
+ "row": "Rangée",
+ "country": "pays",
+ "countries": "des pays",
+ "anyCityFrom1Country": "Cualquier ciudad de 1 país",
+ "anyCityFromNCountries": "Cualquier ciudad de %1 países"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/hi.json b/packages/plugins/src/dataTypes/City/i18n/hi.json
new file mode 100644
index 000000000..e65e559f2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/hi.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "शहर",
+ "DESC": "एक यादृच्छिक शहर प्रदर्शित करता है, या यदि डेटा उपलब्ध है, तो उपयुक्त देश/क्षेत्र के लिए एक शहर प्रदर्शित करता है।",
+ "DEFAULT_TITLE": "शहर",
+ "selectCities": "शहरों का चयन करें",
+ "regionRow": "क्षेत्र पंक्ति",
+ "source": "स्रोत",
+ "anyCity": "कोई भी शहर",
+ "explanation": "यह नियंत्रित करता है कि इस क्षेत्र द्वारा कौन से शहर के नाम उत्पन्न किए जाएंगे।",
+ "anyDesc": "किसी भी शहर का नाम उत्पन्न करता है।",
+ "countriesDesc": "निर्दिष्ट देशों से शहर के नाम उत्पन्न करता है।",
+ "rowDesc": "यह आपके डेटा सेट में एक विशिष्ट क्षेत्र फ़ील्ड को इंगित करता है जिसमें इसके स्रोत के रूप में क्षेत्र प्लगइन्स हैं। यह उन शहरों को उत्पन्न करता है जो उस क्षेत्र में मूल्य के लिए मैप करते हैं।",
+ "row": "पंक्ति",
+ "country": "देश",
+ "countries": "देशों",
+ "anyCityFrom1Country": "1 देश का कोई भी शहर",
+ "anyCityFromNCountries": "%1 देशों का कोई भी शहर"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/ja.json b/packages/plugins/src/dataTypes/City/i18n/ja.json
new file mode 100644
index 000000000..fd4d2fa53
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/ja.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "市",
+ "DESC": "ランダムな都市を表示します。データが利用可能な場合は、適切な国/地域の都市を表示します。",
+ "DEFAULT_TITLE": "市",
+ "selectCities": "都市を選択",
+ "regionRow": "地域の行",
+ "source": "ソース",
+ "anyCity": "任意の都市",
+ "explanation": "これは、このフィールドによって生成される都市名を制御します。",
+ "anyDesc": "任意の都市名を生成します。",
+ "countriesDesc": "指定された国から都市名を生成します。",
+ "rowDesc": "これにより、リージョンプラグインをソースとして持つデータセット内の特定のリージョンフィールドが特定されます。 そのフィールドの値にマップする都市を生成します。",
+ "row": "行",
+ "country": "国",
+ "countries": "国",
+ "anyCityFrom1Country": "1か国の任意の都市",
+ "anyCityFromNCountries": "%1か国の任意の都市"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/nl.json b/packages/plugins/src/dataTypes/City/i18n/nl.json
new file mode 100644
index 000000000..81cfbcca6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/nl.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "Stad",
+ "DESC": "Geeft een willekeurige stad weer, of als gegevens beschikbaar zijn, wordt een stad voor het betreffende land / regio weergegeven.",
+ "DEFAULT_TITLE": "stad",
+ "selectCities": "Selecteer steden",
+ "regionRow": "Regio rij",
+ "source": "Bron",
+ "anyCity": "Elke stad",
+ "explanation": "Dit bepaalt welke stadsnamen door dit veld worden gegenereerd.",
+ "anyDesc": "Genereert een willekeurige plaatsnaam.",
+ "countriesDesc": "Genereert plaatsnamen uit de opgegeven landen.",
+ "rowDesc": "Dit lokaliseert een specifiek regioveld in uw dataset dat regioplug-ins als bron heeft. Het genereert steden die overeenkomen met de waarde in dat veld.",
+ "row": "Rij",
+ "country": "land",
+ "countries": "landen",
+ "anyCityFrom1Country": "Elke stad uit 1 land",
+ "anyCityFromNCountries": "Elke stad uit %1 landen"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/pt.json b/packages/plugins/src/dataTypes/City/i18n/pt.json
new file mode 100644
index 000000000..47e9245ef
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/pt.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "Cidade",
+ "DESC": "Exibe uma cidade aleatória ou, se houver dados disponíveis, exibe uma cidade para o país / região apropriado.",
+ "DEFAULT_TITLE": "cidade",
+ "selectCities": "Selecione as cidades",
+ "regionRow": "Linha de região",
+ "source": "Fonte",
+ "anyCity": "Qualquer cidade",
+ "explanation": "Isso controla quais nomes de cidades serão gerados por este campo.",
+ "anyDesc": "Gera qualquer nome de cidade.",
+ "countriesDesc": "Gera nomes de cidades dos países especificados.",
+ "rowDesc": "Isso aponta um campo de região específico em seu conjunto de dados que tem Plugins de região como sua fonte. Ele gera cidades que mapeiam para o valor naquele campo.",
+ "row": "Linha",
+ "country": "país",
+ "countries": "países",
+ "anyCityFrom1Country": "Qualquer cidade de 1 país",
+ "anyCityFromNCountries": "Qualquer cidade de %1 países"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/ru.json b/packages/plugins/src/dataTypes/City/i18n/ru.json
new file mode 100644
index 000000000..87894fdf7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/ru.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "Город",
+ "DESC": "Отображает случайный город или, если данные доступны, отображает город для соответствующей страны/региона.",
+ "DEFAULT_TITLE": "город",
+ "selectCities": "Выберите города",
+ "regionRow": "Строка региона",
+ "source": "Источник",
+ "anyCity": "Любой город",
+ "explanation": "Это определяет, какие названия городов будут генерироваться этим полем.",
+ "anyDesc": "Генерирует любое название города.",
+ "countriesDesc": "Генерирует названия городов из указанных стран.",
+ "rowDesc": "Это указывает на конкретное поле «Регион» в вашем наборе данных, источником которого являются «Плагины регионов». Он генерирует города, которые сопоставляются со значением в этом поле.",
+ "row": "Строка",
+ "country": "страна",
+ "countries": "страны",
+ "anyCityFrom1Country": "Любой город из 1 страны",
+ "anyCityFromNCountries": "Любой город из %1 страны"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/ta.json b/packages/plugins/src/dataTypes/City/i18n/ta.json
new file mode 100644
index 000000000..fe50e97ca
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/ta.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "நகரம்",
+ "DESC": "ஒரு சீரற்ற நகரத்தைக் காட்டுகிறது, அல்லது தரவு கிடைத்தால், பொருத்தமான நாடு / பிராந்தியத்திற்கு ஒரு நகரத்தைக் காண்பிக்கும்.",
+ "DEFAULT_TITLE": "நகரம்",
+ "selectCities": "நகரங்களைத் தேர்ந்தெடுக்கவும்",
+ "regionRow": "பிராந்திய வரிசை",
+ "source": "மூல",
+ "anyCity": "எந்த நகரமும்",
+ "explanation": "இந்தத் துறையால் எந்த நகரப் பெயர்கள் உருவாக்கப்படும் என்பதை இது கட்டுப்படுத்துகிறது.",
+ "anyDesc": "எந்த நகரப் பெயரையும் உருவாக்குகிறது.",
+ "countriesDesc": "குறிப்பிட்ட நாடுகளிலிருந்து நகரப் பெயர்களை உருவாக்குகிறது.",
+ "rowDesc": "இது உங்கள் தரவுத் தொகுப்பில் பிராந்திய செருகுநிரல்களை அதன் மூலமாகக் கொண்ட ஒரு குறிப்பிட்ட பிராந்திய புலத்தை சுட்டிக்காட்டுகிறது. அந்த துறையில் உள்ள மதிப்பைக் குறிக்கும் நகரங்களை இது உருவாக்குகிறது.",
+ "row": "வரிசை",
+ "country": "நாடு",
+ "countries": "நாடுகள்",
+ "anyCityFrom1Country": "1 நாட்டிலிருந்து எந்த நகரமும்",
+ "anyCityFromNCountries": "%1 நாடுகளைச் சேர்ந்த எந்த நகரமும்"
+}
diff --git a/packages/plugins/src/dataTypes/City/i18n/zh.json b/packages/plugins/src/dataTypes/City/i18n/zh.json
new file mode 100644
index 000000000..03e4dec30
--- /dev/null
+++ b/packages/plugins/src/dataTypes/City/i18n/zh.json
@@ -0,0 +1,18 @@
+{
+ "NAME": "市",
+ "DESC": "显示一个随机的城市,或者如果有数据,则显示相应国家/地区的城市。",
+ "DEFAULT_TITLE": "市",
+ "selectCities": "选择城市",
+ "regionRow": "区域行",
+ "source": "资源",
+ "anyCity": "任何城市",
+ "explanation": "这控制了此字段将生成的城市名称。",
+ "anyDesc": "生成任何城市名称。",
+ "countriesDesc": "从指定国家/地区生成城市名称。",
+ "rowDesc": "这将在数据集中以区域插件为源的特定区域字段中查明。 它生成映射到该字段中的值的城市。",
+ "row": "行",
+ "country": "国家",
+ "countries": "国家",
+ "anyCityFrom1Country": "来自1个国家/地区的任何城市",
+ "anyCityFromNCountries": "来自%1个国家/地区的任何城市"
+}
diff --git a/packages/plugins/src/dataTypes/Colour/Colour.generate.ts b/packages/plugins/src/dataTypes/Colour/Colour.generate.ts
new file mode 100644
index 000000000..6ae6b8155
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/Colour.generate.ts
@@ -0,0 +1,19 @@
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import rc from 'randomcolor';
+import { ColourFormatEnum } from './Colour.state';
+
+export const generate = (data: DTGenerationData): DTGenerateResult => {
+ const { value, luminosity, format, alpha } = data.rowState;
+
+ const display: any = rc({
+ count: 1,
+ hue: value,
+ luminosity: luminosity,
+ format,
+ alpha: format === ColourFormatEnum.rgba ? alpha : 1
+ });
+
+ return {
+ display
+ };
+};
diff --git a/packages/plugins/src/dataTypes/Colour/Colour.scss b/packages/plugins/src/dataTypes/Colour/Colour.scss
new file mode 100644
index 000000000..2a448cd94
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/Colour.scss
@@ -0,0 +1,42 @@
+.demoColours {
+ margin: 0;
+ padding: 0;
+ display: inline-flex;
+ gap: 7px;
+ flex-wrap: wrap;
+ list-style-type: none;
+
+ li {
+ span {
+ width: 40px;
+ height: 40px;
+ display: inline-block;
+ border-radius: 20px;
+ }
+ }
+}
+
+.buttonLabel {
+ margin-top: 4px;
+}
+
+.labelCol {
+ padding-right: 20px;
+}
+
+.settings {
+ margin-bottom: 20px;
+
+ tr {
+ height: 35px;
+
+ td > div {
+ margin-bottom: 0;
+ }
+ }
+}
+
+.colourDropdown {
+ width: 150px;
+ display: inline-block;
+}
diff --git a/packages/plugins/src/dataTypes/Colour/Colour.scss.d.ts b/packages/plugins/src/dataTypes/Colour/Colour.scss.d.ts
new file mode 100644
index 000000000..e1103a602
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/Colour.scss.d.ts
@@ -0,0 +1,16 @@
+declare namespace ColourScssNamespace {
+ export interface IColourScss {
+ buttonLabel: string;
+ colourDropdown: string;
+ demoColours: string;
+ labelCol: string;
+ settings: string;
+ }
+}
+
+declare const ColourScssModule: ColourScssNamespace.IColourScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ColourScssNamespace.IColourScss;
+};
+
+export = ColourScssModule;
diff --git a/packages/plugins/src/dataTypes/Colour/Colour.state.tsx b/packages/plugins/src/dataTypes/Colour/Colour.state.tsx
new file mode 100755
index 000000000..c1f3565ff
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/Colour.state.tsx
@@ -0,0 +1,41 @@
+export const enum ColourFormatEnum {
+ hex = 'hex',
+ rgb = 'rgb',
+ rgba = 'rgba'
+}
+export type ColourFormat = `${ColourFormatEnum}`;
+
+export const enum LuminosityTypeEnum {
+ any = 'any',
+ bright = 'bright',
+ light = 'light',
+ dark = 'dark'
+}
+export type LuminosityType = `${LuminosityTypeEnum}`;
+
+export type ColourState = {
+ example: string;
+ value: string;
+ format: ColourFormat;
+ luminosity: LuminosityType;
+ alpha: number;
+};
+
+export type GenerationOptionsType = {
+ value: string;
+ format: ColourFormat;
+ luminosity?: LuminosityType;
+ alpha?: number;
+};
+
+export const defaultGenerationOptions: ColourState = {
+ example: 'any',
+ value: 'any',
+ luminosity: 'any',
+ format: 'hex',
+ alpha: 1
+};
+
+export const initialState: ColourState = {
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/Colour/Colour.tsx b/packages/plugins/src/dataTypes/Colour/Colour.tsx
new file mode 100755
index 000000000..7e2fba127
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/Colour.tsx
@@ -0,0 +1,226 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import Slider from '@mui/material/Slider';
+import rc from 'randomcolor';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import { Tooltip } from '~components/tooltips';
+import { ColourFormatEnum, ColourState, LuminosityTypeEnum, GenerationOptionsType } from './Colour.state';
+import styles from './Colour.scss';
+
+const getModalOptions = ({ i18n }: any): DropdownOption[] => [
+ { value: 'any', label: i18n.anyColour },
+ { value: 'blue', label: i18n.blues },
+ { value: 'green', label: i18n.greens },
+ { value: 'red', label: i18n.reds },
+ { value: 'orange', label: i18n.oranges },
+ { value: 'yellow', label: i18n.yellows },
+ { value: 'purple', label: i18n.purples },
+ { value: 'pink', label: i18n.pinks },
+ { value: 'monochrome', label: i18n.monochromes }
+];
+
+export const Example = ({ i18n, data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: any): void => {
+ onUpdate({
+ example: value,
+ value: value
+ });
+ };
+
+ return onChange(i.value)} options={getModalOptions({ i18n })} />;
+};
+
+const ColourDialog = ({ visible, data, id, onClose, coreI18n, onUpdate, i18n }: any) => {
+ const [randomDemoColours, setRandomDemoColours] = React.useState([]);
+ const [counter, setCounter] = React.useState(0);
+
+ React.useEffect(() => {
+ setRandomDemoColours(
+ rc({
+ count: 30,
+ hue: data.value,
+ luminosity: data.luminosity,
+ format: data.format,
+ alpha: data.format === ColourFormatEnum.rgba ? data.alpha : 1
+ })
+ );
+ }, [data, counter]);
+
+ const onChange = (prop: string, value: any): void => {
+ onUpdate({
+ ...data,
+ [prop]: value
+ });
+ };
+
+ return (
+
+
+
{i18n.configureColours}
+
+
+
+
+ {i18n.colour}
+
+ onChange('value', i.value)}
+ options={getModalOptions({ i18n })}
+ />
+
+
+
+ {i18n.luminosity}
+
+
+ onChange('luminosity', LuminosityTypeEnum.any)}
+ name={`luminosity-${id}`}
+ checked={data.luminosity === LuminosityTypeEnum.any}
+ style={{ marginRight: 6 }}
+ />
+ onChange('luminosity', LuminosityTypeEnum.bright)}
+ name={`luminosity-${id}`}
+ checked={data.luminosity === LuminosityTypeEnum.bright}
+ style={{ marginRight: 6 }}
+ />
+ onChange('luminosity', LuminosityTypeEnum.light)}
+ name={`luminosity-${id}`}
+ checked={data.luminosity === LuminosityTypeEnum.light}
+ style={{ marginRight: 6 }}
+ />
+ onChange('luminosity', LuminosityTypeEnum.dark)}
+ name={`luminosity-${id}`}
+ checked={data.luminosity === LuminosityTypeEnum.dark}
+ />
+
+
+
+
+ {i18n.format}
+
+
+ onChange('format', ColourFormatEnum.hex)}
+ name={`format-${id}`}
+ checked={data.format === ColourFormatEnum.hex}
+ style={{ marginRight: 6 }}
+ />
+ onChange('format', ColourFormatEnum.rgb)}
+ name={`format-${id}`}
+ checked={data.format === ColourFormatEnum.rgb}
+ style={{ marginRight: 6 }}
+ />
+ onChange('format', ColourFormatEnum.rgba)}
+ name={`format-${id}`}
+ checked={data.format === ColourFormatEnum.rgba}
+ />
+
+
+
+
+ {i18n.alpha}
+
+ onChange('alpha', value)}
+ step={0.001}
+ min={0}
+ max={1}
+ valueLabelDisplay="auto"
+ disabled={data.format !== ColourFormatEnum.rgba}
+ />
+
+
+
+
+
+
+ {randomDemoColours.map((colour: string, index: number) => (
+
+
+
+
+
+ ))}
+
+
+
+ setCounter(counter + 1)} color="primary" variant="outlined">
+ {i18n.refresh}
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ id, i18n, coreI18n, data, onUpdate }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+
+ const options = getModalOptions({ i18n });
+ let buttonLabel = '';
+
+ options.forEach(({ value, label }) => {
+ if (data.value === value) {
+ buttonLabel = label;
+ }
+ });
+
+ return (
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+ setDialogVisibility(false)}
+ onUpdate={onUpdate}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+ {i18n.helpDesc1}
+ {i18n.helpDesc2}
+ >
+);
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'string(12) default NULL',
+ field_Oracle: 'varchar2(12) default NULL',
+ field_MSSQL: 'VARCHAR(12) NULL'
+ }
+});
+
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export const rowStateReducer = ({ example, ...other }: ColourState): GenerationOptionsType => other;
diff --git a/packages/plugins/src/dataTypes/Colour/Colour.worker.ts b/packages/plugins/src/dataTypes/Colour/Colour.worker.ts
new file mode 100644
index 000000000..43b51138b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/Colour.worker.ts
@@ -0,0 +1,6 @@
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Colour.generate';
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/dataTypes/Colour/README.md b/packages/plugins/src/dataTypes/Colour/README.md
new file mode 100644
index 000000000..70c180e63
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/README.md
@@ -0,0 +1,201 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Colour
+
+The Colour Data Type generates colours in different formats (hex, rgb, rgba), luminosities (bright, light, dark) and
+alphas. Under the hood it uses the [randomColor package](https://github.com/davidmerfield/randomColor).
+
+## Typings
+
+```typescript
+export const enum ColourFormatEnum {
+ hex = 'hex',
+ rgb = 'rgb',
+ rgba = 'rgba'
+}
+export type ColourFormat = `${ColourFormatEnum}`;
+
+export const enum LuminosityTypeEnum {
+ any = 'any',
+ bright = 'bright',
+ light = 'light',
+ dark = 'dark'
+}
+export type LuminosityType = `${LuminosityTypeEnum}`;
+
+export type ColourState = {
+ example: string;
+ value: string;
+ luminosity: LuminosityType;
+ format: ColourFormat;
+ alpha: number;
+};
+```
+
+Note: `value` maps to the `hue` setting in the [randomColor package](https://github.com/davidmerfield/randomColor). It can
+either be a hex code or one of the predefined constants: `red, orange, yellow, green, blue, purple, pink, monochrome`.
+
+## Examples
+
+When it comes to actually illustrating the colours generated, markdown isn't exacty great! But these examples are here
+anyway, just so you can see how they work.
+
+- [Any random color in hex mode](#any-random-color-in-hex-mode)
+- [Any bright red](#any-bright-red)
+- [Any green with 0.5% alpha in rgba](#any-green-with-05-alpha-in-rgba)
+
+### Any random color in hex mode
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "Colour",
+ title: "colour",
+ settings: {
+ value: "",
+ format: "hex"
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "colour": "#fcbebd"
+ },
+ {
+ "colour": "#21d147"
+ },
+ {
+ "colour": "#70e26a"
+ },
+ {
+ "colour": "#bbd852"
+ },
+ {
+ "colour": "#39b513"
+ },
+ {
+ "colour": "#60acc4"
+ },
+ ...
+]
+```
+
+### Any bright red
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "Colour",
+ title: "bright-red",
+ settings: {
+ value: "#EE4B2B",
+ format: "hex",
+ luminosity: "bright"
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "bright-red": "#e84c80"
+ },
+ {
+ "bright-red": "#a3033d"
+ },
+ {
+ "bright-red": "#f7270c"
+ },
+ {
+ "bright-red": "#ea697f"
+ },
+ {
+ "bright-red": "#f9653b"
+ },
+ {
+ "bright-red": "#b71a17"
+ },
+ ...
+]
+```
+
+### Any green with 0.5% alpha in RGBA
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "Colour",
+ title: "green-with-alpha",
+ settings: {
+ value: "green",
+ format: "rgba",
+ alpha: 0.5
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "green-with-alpha": "rgba(65, 224, 208, 0.5)"
+ },
+ {
+ "green-with-alpha": "rgba(130, 229, 134, 0.5)"
+ },
+ {
+ "green-with-alpha": "rgba(105, 229, 169, 0.5)"
+ },
+ {
+ "green-with-alpha": "rgba(164, 252, 193, 0.5)"
+ },
+ {
+ "green-with-alpha": "rgba(7, 163, 1, 0.5)"
+ },
+ {
+ "green-with-alpha": "rgba(119, 244, 130, 0.5)"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/Colour/__tests__/Colour.test.tsx b/packages/plugins/src/dataTypes/Colour/__tests__/Colour.test.tsx
new file mode 100644
index 000000000..c7a5146e2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/__tests__/Colour.test.tsx
@@ -0,0 +1,19 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../Colour';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ dimensions: { width: 100, height: 100 }
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Colour/bundle.ts b/packages/plugins/src/dataTypes/Colour/bundle.ts
new file mode 100644
index 000000000..4c95982b5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, getMetadata, rowStateReducer } from './Colour';
+import { initialState } from './Colour.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ getMetadata,
+ rowStateReducer
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Colour/config.ts b/packages/plugins/src/dataTypes/Colour/config.ts
new file mode 100644
index 000000000..1085c8184
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'other',
+ fieldGroupOrder: 50
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/ar.json b/packages/plugins/src/dataTypes/Colour/i18n/ar.json
new file mode 100644
index 000000000..01ce6ef52
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/ar.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "لون",
+ "DESC": "يولد لونًا عشوائيًا بتدرج وتنسيق معينين.",
+ "DEFAULT_TITLE": "لون",
+ "anyColour": "اي لون",
+ "blues": "بلوز",
+ "greens": "الخضر",
+ "reds": "ريدز",
+ "oranges": "البرتقال",
+ "yellows": "يصفر",
+ "purples": "الأرجواني",
+ "pinks": "الوردي",
+ "monochromes": "أحادية اللون",
+ "configureColours": "تكوين الألوان",
+ "colour": "لون",
+ "luminosity": "لمعان",
+ "any": "أي",
+ "bright": "لامع",
+ "light": "ضوء",
+ "dark": "داكن",
+ "format": "صيغة",
+ "alpha": "ألفا",
+ "refresh": "ينعش",
+ "helpDesc1": "يولد نوع البيانات هذا ألوانًا عشوائية بأشكال وتنسيقات مختلفة (Hex و rgb و rgba) وإضاءة. باستخدام rgba ، يمكنك اختيار قيمة ألفا لتوفير ألوان باهتة.",
+ "helpDesc2": "لمشاهدة جميع الإعدادات ، ما عليك سوى النقر فوق الزر الموجود في العمود \"خيارات\"."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/de.json b/packages/plugins/src/dataTypes/Colour/i18n/de.json
new file mode 100644
index 000000000..5f3a4e538
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/de.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "Farbe",
+ "DESC": "Erzeugt eine zufällige Farbe in einem bestimmten Farbton und Format.",
+ "DEFAULT_TITLE": "Farbe",
+ "anyColour": "Jede Farbe",
+ "blues": "Blues",
+ "greens": "Grüne",
+ "reds": "Rote",
+ "oranges": "Orangen",
+ "yellows": "Gelbe",
+ "purples": "Purpur",
+ "pinks": "Rosa",
+ "monochromes": "Monochrome",
+ "configureColours": "Farben konfigurieren",
+ "colour": "Farbe",
+ "luminosity": "Helligkeit",
+ "any": "Irgendein",
+ "bright": "Hell",
+ "light": "Hell",
+ "dark": "Dunkel",
+ "format": "Format",
+ "alpha": "Alpha",
+ "refresh": "Aktualisierung",
+ "helpDesc1": "Dieser Datentyp generiert zufällige Farben in verschiedenen Farbtönen, Formaten (Hex, rgb, rgba) und Helligkeiten. Mit rgba können Sie den Alpha-Wert wählen, um verblasste Farben bereitzustellen.",
+ "helpDesc2": "Um alle Einstellungen anzuzeigen, klicken Sie einfach auf die Schaltfläche in der Spalte Optionen."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/en.json b/packages/plugins/src/dataTypes/Colour/i18n/en.json
new file mode 100644
index 000000000..28970287d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/en.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "Colour",
+ "DESC": "Generates a random colour in a specific hue and format.",
+ "DEFAULT_TITLE": "colour",
+ "anyColour": "Any colour",
+ "blues": "Blues",
+ "greens": "Greens",
+ "reds": "Reds",
+ "oranges": "Oranges",
+ "yellows": "Yellows",
+ "purples": "Purples",
+ "pinks": "Pinks",
+ "monochromes": "Monochromes",
+ "configureColours": "Configure Colours",
+ "colour": "Colour",
+ "luminosity": "Luminosity",
+ "any": "Any",
+ "bright": "Bright",
+ "light": "Light",
+ "dark": "Dark",
+ "format": "Format",
+ "alpha": "Alpha",
+ "refresh": "Refresh",
+ "helpDesc1": "This Data Type generates random colours in different hues, formats (Hex, rgb, rbga) and luminosities. With rgba you can choose the alpha value to provide faded colours.",
+ "helpDesc2": "To see all the settings, just click on the button in the Options column."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/es.json b/packages/plugins/src/dataTypes/Colour/i18n/es.json
new file mode 100644
index 000000000..b4206a7fd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/es.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "Color",
+ "DESC": "Genera un color aleatorio en un tono y formato específicos.",
+ "DEFAULT_TITLE": "color",
+ "anyColour": "Cualquier color",
+ "blues": "Blues",
+ "greens": "Verduras",
+ "reds": "Rojas",
+ "oranges": "Naranjas",
+ "yellows": "Amarillos",
+ "purples": "Morados",
+ "pinks": "Rosas",
+ "monochromes": "Monocromas",
+ "configureColours": "Configurar colores",
+ "colour": "Color",
+ "luminosity": "Luminosidad",
+ "any": "Alguna",
+ "bright": "Brillante",
+ "light": "Ligera",
+ "dark": "Oscura",
+ "format": "Formato",
+ "alpha": "Alfa",
+ "refresh": "Actualizar",
+ "helpDesc1": "TEste tipo de datos genera colores aleatorios en diferentes tonalidades, formatos (Hex, rgb, rgba) y luminosidades. Con rgba puede elegir el valor alfa para proporcionar colores desvaídos.",
+ "helpDesc2": "Para ver todas las configuraciones, simplemente haga clic en el botón en la columna Opciones."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/fr.json b/packages/plugins/src/dataTypes/Colour/i18n/fr.json
new file mode 100644
index 000000000..743ca074c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/fr.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "Couleur",
+ "DESC": "Génère une couleur aléatoire dans une teinte et un format spécifiques.",
+ "DEFAULT_TITLE": "Couleur",
+ "anyColour": "N'importe quelle couleur",
+ "blues": "Bleues",
+ "greens": "Légumes verts",
+ "reds": "rouges",
+ "oranges": "Des oranges",
+ "yellows": "Jaunes",
+ "purples": "Violets",
+ "pinks": "Roses",
+ "monochromes": "Monochromes",
+ "configureColours": "Configurer les couleurs",
+ "colour": "Couleur",
+ "luminosity": "Luminosité",
+ "any": "Toute",
+ "bright": "Brillante",
+ "light": "Légère",
+ "dark": "Sombre",
+ "format": "Format",
+ "alpha": "Alpha",
+ "refresh": "Rafraîchir",
+ "helpDesc1": "Ce type de données génère des couleurs aléatoires dans différentes teintes, formats (Hex, rgb, rgba) et luminosités. Avec rgba, vous pouvez choisir la valeur alpha pour fournir des couleurs fanées.",
+ "helpDesc2": "Pour voir tous les paramètres, cliquez simplement sur le bouton dans la colonne Options."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/hi.json b/packages/plugins/src/dataTypes/Colour/i18n/hi.json
new file mode 100644
index 000000000..25dadcabf
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/hi.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "रंग",
+ "DESC": "एक विशिष्ट रंग और प्रारूप में एक यादृच्छिक रंग उत्पन्न करता है।",
+ "DEFAULT_TITLE": "रंग",
+ "anyColour": "कोई भी रंग",
+ "blues": "ब्लूज़",
+ "greens": "साग",
+ "reds": "रेड्स",
+ "oranges": "संतरे",
+ "yellows": "पीली",
+ "purples": "बैंगनी",
+ "pinks": "गुलाबी",
+ "monochromes": "मोनोक्रोम",
+ "configureColours": "रंग कॉन्फ़िगर करें",
+ "colour": "रंग",
+ "luminosity": "चमक",
+ "any": "कोई भी",
+ "bright": "पूर्व",
+ "light": "रोशनी",
+ "dark": "अंधेरा",
+ "format": "प्रारूप",
+ "alpha": "अल्फा",
+ "refresh": "ताज़ा करना",
+ "helpDesc1": "यह डेटा प्रकार विभिन्न रंगों, स्वरूपों (हेक्स, आरजीबी, आरजीबीए) और चमक में यादृच्छिक रंग उत्पन्न करता है। आरजीबीए के साथ आप फीका रंग प्रदान करने के लिए अल्फा मान चुन सकते हैं।",
+ "helpDesc2": "सभी सेटिंग्स देखने के लिए, बस विकल्प कॉलम में बटन पर क्लिक करें।"
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/ja.json b/packages/plugins/src/dataTypes/Colour/i18n/ja.json
new file mode 100644
index 000000000..8cc6bf6a4
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/ja.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "色",
+ "DESC": "特定の色相と形式でランダムな色を生成します。",
+ "DEFAULT_TITLE": "色",
+ "anyColour": "任意の色",
+ "blues": "ブルース",
+ "greens": "グリーンズ",
+ "reds": "レッズ",
+ "oranges": "オレンジ",
+ "yellows": "黄色",
+ "purples": "パープル",
+ "pinks": "ピンク",
+ "monochromes": "モノクローム",
+ "configureColours": "色を構成する",
+ "colour": "色",
+ "luminosity": "輝度",
+ "any": "どれでも",
+ "bright": "明るい",
+ "light": "光",
+ "dark": "暗い",
+ "format": "フォーマット",
+ "alpha": "アルファ",
+ "refresh": "更新",
+ "helpDesc1": "このデータ型は、さまざまな色相、形式(Hex、rgb、rgba)、および明度でランダムな色を生成します。 rgbaを使用すると、アルファ値を選択して色あせた色を提供できます。",
+ "helpDesc2": "すべての設定を表示するには、[オプション]列のボタンをクリックするだけです。"
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/nl.json b/packages/plugins/src/dataTypes/Colour/i18n/nl.json
new file mode 100644
index 000000000..cddc42bc7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/nl.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "Kleur",
+ "DESC": "Genereert een willekeurige kleur in een specifieke tint en formaat.",
+ "DEFAULT_TITLE": "kleur",
+ "anyColour": "Elke kleur",
+ "blues": "Blues",
+ "greens": "Groenen",
+ "reds": "rood",
+ "oranges": "Sinaasappels",
+ "yellows": "geel",
+ "purples": "paars",
+ "pinks": "roze",
+ "monochromes": "Monochromen",
+ "configureColours": "Kleuren configureren",
+ "colour": "Kleur",
+ "luminosity": "Helderheid",
+ "any": "Ieder",
+ "bright": "Helder",
+ "light": "Licht",
+ "dark": "Donker",
+ "format": "Formaat",
+ "alpha": "Alfa",
+ "refresh": "Vernieuwen",
+ "helpDesc1": "Dit gegevenstype genereert willekeurige kleuren in verschillende tinten, formaten (Hex, rgb, rgba) en lichtsterkten. Met rgba kunt u de alpha-waarde kiezen om vervaagde kleuren te geven.",
+ "helpDesc2": "Om alle instellingen te zien, klikt u op de knop in de kolom Opties."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/pt.json b/packages/plugins/src/dataTypes/Colour/i18n/pt.json
new file mode 100644
index 000000000..2c8495ba9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/pt.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "Cor",
+ "DESC": "Gera uma cor aleatória em um matiz e formato específicos.",
+ "DEFAULT_TITLE": "cor",
+ "anyColour": "Qualquer cor",
+ "blues": "Blues",
+ "greens": "Verdes",
+ "reds": "Vermelhas",
+ "oranges": "Laranjas",
+ "yellows": "Amarelas",
+ "purples": "Roxos",
+ "pinks": "Rosa",
+ "monochromes": "Monocromos",
+ "configureColours": "Configurar Cores",
+ "colour": "Cor",
+ "luminosity": "Luminosidade",
+ "any": "Alguma",
+ "bright": "Brilhante",
+ "light": "Luz",
+ "dark": "Escura",
+ "format": "Formato",
+ "alpha": "Alfa",
+ "refresh": "Atualizar",
+ "helpDesc1": "Este tipo de dados gera cores aleatórias em diferentes matizes, formatos (Hex, rgb, rgba) e luminosidades. Com rgba, você pode escolher o valor alfa para fornecer cores esmaecidas.",
+ "helpDesc2": "Para ver todas as configurações, basta clicar no botão na coluna Opções."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/ru.json b/packages/plugins/src/dataTypes/Colour/i18n/ru.json
new file mode 100644
index 000000000..d479e42b5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/ru.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "Цвет",
+ "DESC": "Генерирует случайный цвет определенного оттенка и формата.",
+ "DEFAULT_TITLE": "цвет",
+ "anyColour": "Любой цвет",
+ "blues": "Блюз",
+ "greens": "Зелень",
+ "reds": "Красные",
+ "oranges": "Апельсины",
+ "yellows": "Желтые",
+ "purples": "Пурпурные",
+ "pinks": "Розовые",
+ "monochromes": "Монохромы",
+ "configureColours": "Настройка цветов",
+ "colour": "Цвет",
+ "luminosity": "Светимость",
+ "any": "Любой",
+ "bright": "Яркий",
+ "light": "Легкий",
+ "dark": "Темный",
+ "format": "Формат",
+ "alpha": "Альфа",
+ "refresh": "Обновить",
+ "helpDesc1": "Этот тип данных генерирует случайные цвета различных оттенков, форматов (Hex, rgb, rbga) и яркости. С помощью rgba вы можете выбрать альфа-значение, чтобы получить блеклые цвета.",
+ "helpDesc2": "Чтобы увидеть все настройки, просто нажмите на кнопку в столбце «Параметры»."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/ta.json b/packages/plugins/src/dataTypes/Colour/i18n/ta.json
new file mode 100644
index 000000000..49671f9dd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/ta.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "நிறம்",
+ "DESC": "ஒரு குறிப்பிட்ட சாயல் மற்றும் வடிவத்தில் ஒரு சீரற்ற நிறத்தை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "நிறம்",
+ "anyColour": "எந்த நிறமும்",
+ "blues": "ப்ளூஸ்",
+ "greens": "கீரைகள்",
+ "reds": "சிவப்பு",
+ "oranges": "ஆரஞ்சு",
+ "yellows": "மஞ்சள்",
+ "purples": "ஊதா",
+ "pinks": "இளஞ்சிவப்பு",
+ "monochromes": "ஒரே வண்ணமுடையவை",
+ "configureColours": "வண்ணங்களை உள்ளமைக்கவும்",
+ "colour": "நிறம்",
+ "luminosity": "ஒளிர்வு",
+ "any": "ஏதேனும்",
+ "bright": "பிரகாசமான",
+ "light": "ஒளி",
+ "dark": "இருள்",
+ "format": "வடிவம்",
+ "alpha": "ஆல்பா",
+ "refresh": "புதுப்பிப்பு",
+ "helpDesc1": "இந்த டேட்டா வகை பல்வேறு நிறங்கள், வடிவங்கள் (ஹெக்ஸ், ஆர்ஜிபி, ஆர்க்பா) மற்றும் ஒளிரும் வகையில் சீரற்ற வண்ணங்களை உருவாக்குகிறது. Rgba உடன் நீங்கள் மங்கலான வண்ணங்களை வழங்க ஆல்பா மதிப்பை தேர்வு செய்யலாம்.",
+ "helpDesc2": "அனைத்து அமைப்புகளையும் காண, விருப்பங்கள் நெடுவரிசையில் உள்ள பொத்தானைக் கிளிக் செய்யவும்."
+}
diff --git a/packages/plugins/src/dataTypes/Colour/i18n/zh.json b/packages/plugins/src/dataTypes/Colour/i18n/zh.json
new file mode 100644
index 000000000..0e67ca496
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Colour/i18n/zh.json
@@ -0,0 +1,26 @@
+{
+ "NAME": "颜色",
+ "DESC": "以特定的色调和格式生成随机颜色。",
+ "DEFAULT_TITLE": "颜色",
+ "anyColour": "任何颜色",
+ "blues": "蓝调",
+ "greens": "青菜",
+ "reds": "红人",
+ "oranges": "橙子",
+ "yellows": "黄种人",
+ "purples": "紫色",
+ "pinks": "粉红色",
+ "monochromes": "单色",
+ "colour": "颜色",
+ "luminosity": "亮度",
+ "any": "任何",
+ "bright": "明亮的",
+ "light": "光",
+ "dark": "黑暗的",
+ "format": "格式",
+ "alpha": "Α",
+ "refresh": "刷新",
+ "helpDesc1": "此数据类型生成不同色调、格式(Hex、rgb、rgba)和亮度的随机颜色。 使用 rgba,您可以选择 alpha 值以提供褪色的颜色。",
+ "helpDesc2": "要查看所有设置,只需单击“选项”列中的按钮。",
+ "configureColours": "配置颜色"
+}
diff --git a/packages/plugins/src/dataTypes/Company/Company.generate.ts b/packages/plugins/src/dataTypes/Company/Company.generate.ts
new file mode 100644
index 000000000..2246f517a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/Company.generate.ts
@@ -0,0 +1,49 @@
+import { WorkerUtils } from '../../';
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+
+let utils: WorkerUtils;
+
+const getWords = (): string[] => {
+ const { words } = utils.stringUtils.getLipsumWords();
+ return words;
+};
+
+const companyTypes = [
+ 'Company',
+ 'Corp.',
+ 'Corporation',
+ 'Inc.',
+ 'Incorporated',
+ 'LLC',
+ 'LLP',
+ 'Ltd',
+ 'Limited',
+ 'PC',
+ 'Foundation',
+ 'Institute',
+ 'Associates',
+ 'Industries',
+ 'Consulting'
+];
+
+const removePunctuation = (arr: string[]): string[] => arr.map((i: string) => i.replace(/[.,:;]/g, ''));
+
+export const generateCompanyName = (wordsArr: string[], types = companyTypes): string => {
+ const numCompanyNameWords = utils.randomUtils.getRandomNum(1, 3);
+ const offset = utils.randomUtils.getRandomNum(0, wordsArr.length - (numCompanyNameWords + 1));
+ const selectedWords = removePunctuation(wordsArr.slice(offset, offset + numCompanyNameWords));
+ const companyType = types[utils.randomUtils.getRandomNum(0, types.length - 1)];
+
+ return utils.stringUtils.uppercaseWords(selectedWords.join(' ')) + ' ' + companyType;
+};
+
+export const setUtils = (workerUtils: WorkerUtils) => (utils = workerUtils);
+
+export const generate = (data: DTGenerationData, workerUtils: WorkerUtils): DTGenerateResult => {
+ setUtils(workerUtils);
+
+ const words = getWords();
+ return {
+ display: generateCompanyName(words)
+ };
+};
diff --git a/packages/plugins/src/dataTypes/Company/Company.state.tsx b/packages/plugins/src/dataTypes/Company/Company.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/Company.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/dataTypes/Company/Company.tsx b/packages/plugins/src/dataTypes/Company/Company.tsx
new file mode 100644
index 000000000..48a6ccc49
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/Company.tsx
@@ -0,0 +1,12 @@
+import * as React from 'react';
+import { DTHelpProps, DTMetadata } from '~types/dataTypes';
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.DESC}
;
+
+export const getMetadata = (): DTMetadata => ({
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/Company/Company.worker.ts b/packages/plugins/src/dataTypes/Company/Company.worker.ts
new file mode 100644
index 000000000..7f0bb2bde
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/Company.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Company.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/Company/README.md b/packages/plugins/src/dataTypes/Company/README.md
new file mode 100644
index 000000000..bad21783e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/README.md
@@ -0,0 +1,54 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Company
+
+Generates a random company name. This is a very simple plugin and the same functionality can actually be achieved with
+the [Custom List](../List) Data Type. But it's a quick convenient way to have all the company names auto-generated with
+no additional configuration.
+
+### Examples
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "Company",
+ title: "company-name",
+ settings: {}
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "company-name": "Rutrum LLC"
+ },
+ {
+ "company-name": "Orci Ltd"
+ },
+ {
+ "company-name": "Pede Nonummy Ut LLP"
+ },
+ {
+ "company-name": "Aliquam Adipiscing Foundation"
+ },
+ {
+ "company-name": "Luctus Vulputate Institute"
+ },
+ {
+ "company-name": "Morbi LLC"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/Company/__tests__/Company.generate.test.tsx b/packages/plugins/src/dataTypes/Company/__tests__/Company.generate.test.tsx
new file mode 100644
index 000000000..343964a32
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/__tests__/Company.generate.test.tsx
@@ -0,0 +1,53 @@
+import * as sinon from 'sinon';
+import * as generation from '../Company.generate';
+import utils from '../../../../utils';
+
+const companyTypes = ['Inc.', 'Co.'];
+const words = ['one', 'two', 'three'];
+
+describe('generateCompanyName', () => {
+ beforeAll(() => {
+ generation.setUtils(utils);
+ });
+
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('generates a company name', () => {
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(1) // number of words
+ .onCall(1).returns(0) // company name words offset
+ .onCall(2).returns(0); // company type
+
+ expect(generation.generateCompanyName(words, companyTypes)).toEqual('One Inc.');
+ });
+
+ it('picks a random company word', () => {
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(1) // number of words
+ .onCall(1).returns(1) // company name words offset
+ .onCall(2).returns(0); // company type
+
+ expect(generation.generateCompanyName(words, companyTypes)).toEqual('Two Inc.');
+ });
+
+ it('picks multiple company words', () => {
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(3) // number of words
+ .onCall(1).returns(0) // company name words offset
+ .onCall(2).returns(0); // company type
+
+ expect(generation.generateCompanyName(words, companyTypes)).toEqual('One Two Three Inc.');
+ });
+
+ it('picks a random company type', () => {
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(1) // number of words
+ .onCall(1).returns(2) // company name words offset
+ .onCall(2).returns(1); // company type
+
+ expect(generation.generateCompanyName(words, companyTypes)).toEqual('Three Co.');
+ });
+});
+
diff --git a/packages/plugins/src/dataTypes/Company/__tests__/Company.ui.test.tsx b/packages/plugins/src/dataTypes/Company/__tests__/Company.ui.test.tsx
new file mode 100644
index 000000000..1a0e02e26
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/__tests__/Company.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../Company';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Company/bundle.ts b/packages/plugins/src/dataTypes/Company/bundle.ts
new file mode 100644
index 000000000..2b949391a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/bundle.ts
@@ -0,0 +1,9 @@
+import { DTBundle } from '~types/dataTypes';
+import { Help, getMetadata } from './Company';
+
+const bundle: DTBundle = {
+ Help,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Company/config.ts b/packages/plugins/src/dataTypes/Company/config.ts
new file mode 100644
index 000000000..1ca22fd49
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'humanData',
+ fieldGroupOrder: 60
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Company/i18n/ar.json b/packages/plugins/src/dataTypes/Company/i18n/ar.json
new file mode 100644
index 000000000..0417a6c43
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/ar.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "شركة",
+ "DESC": "يولد اسم شركة عشوائيًا ، يتكون من كلمة lorem ipsum ولاحقة مناسبة ، مثل Dolor Inc. ، أو Convallis Limited.",
+ "DEFAULT_TITLE": "شركة"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/de.json b/packages/plugins/src/dataTypes/Company/i18n/de.json
new file mode 100644
index 000000000..68898a042
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/de.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "Firma",
+ "DESC": "Dieser Datentyp generiert einen zufälligen Name des Unternehmens, einer Lorem ipsum Wort besteht und eine entsprechende Suffix, wie Dolor Inc., oder Convallis Begrenzte.",
+ "DEFAULT_TITLE": "unternehmen"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/en.json b/packages/plugins/src/dataTypes/Company/i18n/en.json
new file mode 100644
index 000000000..7d91f0f03
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/en.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "Company",
+ "DESC": "Generates a random company name, comprised of a lorem ipsum word and an appropriate suffix, like Dolor Inc., or Convallis Limited.",
+ "DEFAULT_TITLE": "company"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/es.json b/packages/plugins/src/dataTypes/Company/i18n/es.json
new file mode 100644
index 000000000..b59a87f5b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/es.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "Empresa",
+ "DESC": "Este tipo de dato genera un nombre de empresa al azar, generado a partir de una palabra lorem ipsum y de un sufijo apropiado como Dolor Inc., o Convallis Limited.",
+ "DEFAULT_TITLE": "empresa"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/fr.json b/packages/plugins/src/dataTypes/Company/i18n/fr.json
new file mode 100644
index 000000000..e7d01a035
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/fr.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "Entreprise",
+ "DESC": "Ce type de données génère un nom d'entreprise aléatoire, composé d'un mot Lorem ipsum et un suffixe, comme Dolor Inc, ou Convallis limitée.",
+ "DEFAULT_TITLE": "compagnie"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/hi.json b/packages/plugins/src/dataTypes/Company/i18n/hi.json
new file mode 100644
index 000000000..1c56f2fe0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/hi.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "कंपनी",
+ "DESC": "एक यादृच्छिक कंपनी नाम उत्पन्न करता है, जिसमें एक लोरेम इप्सम शब्द और एक उपयुक्त प्रत्यय होता है, जैसे डोलर इंक, या कॉन्वलिस लिमिटेड।",
+ "DEFAULT_TITLE": "कंपनी"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/ja.json b/packages/plugins/src/dataTypes/Company/i18n/ja.json
new file mode 100644
index 000000000..08bcdf9cf
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/ja.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "会社",
+ "DESC": "Loremipsumの単語と適切な接尾辞で構成されるランダムな会社名を生成します(DolorInc。やConvallisLimitedなど)。",
+ "DEFAULT_TITLE": "会社"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/nl.json b/packages/plugins/src/dataTypes/Company/i18n/nl.json
new file mode 100644
index 000000000..572a618a3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/nl.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "Bedrijfsnaam",
+ "DESC": "Dit gegevenstype genereert een willekeurige bedrijfsnaam, bestaande uit een Lorem ipsum woord en een passend achtervoegsel, zoals bijvoorbeeld Dolor Inc, of Convallis Limited.",
+ "DEFAULT_TITLE": "bedrijf"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/pt.json b/packages/plugins/src/dataTypes/Company/i18n/pt.json
new file mode 100644
index 000000000..8eb6ddf08
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/pt.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "Empresa",
+ "DESC": "Gera um nome de empresa aleatório, composto de uma palavra lorem ipsum e um sufixo apropriado, como Dolor Inc. ou Convallis Limited.",
+ "DEFAULT_TITLE": "empresa"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/ru.json b/packages/plugins/src/dataTypes/Company/i18n/ru.json
new file mode 100644
index 000000000..559f01e4d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/ru.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "Компания",
+ "DESC": "Генерирует случайное название компании, состоящее из слова lorem ipsum и соответствующего суффикса, например, Dolor Inc. или Convallis Limited.",
+ "DEFAULT_TITLE": "Компания"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/ta.json b/packages/plugins/src/dataTypes/Company/i18n/ta.json
new file mode 100644
index 000000000..0c4513736
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/ta.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "நிறுவனம்",
+ "DESC": "ஒரு லோரெம் இப்சம் சொல் மற்றும் டோலர் இன்க், அல்லது கான்வாலிஸ் லிமிடெட் போன்ற பொருத்தமான பின்னொட்டு ஆகியவற்றைக் கொண்ட ஒரு சீரற்ற நிறுவனத்தின் பெயரை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "நிறுவனம்"
+}
diff --git a/packages/plugins/src/dataTypes/Company/i18n/zh.json b/packages/plugins/src/dataTypes/Company/i18n/zh.json
new file mode 100644
index 000000000..06ad6a6e1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Company/i18n/zh.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "公司",
+ "DESC": "生成一个随机的公司名称,该名称由lorem ipsum单词和适当的后缀组成,例如Dolor Inc.或Convallis Limited。",
+ "DEFAULT_TITLE": "公司"
+}
diff --git a/packages/plugins/src/dataTypes/Computed/Computed.generate.ts b/packages/plugins/src/dataTypes/Computed/Computed.generate.ts
new file mode 100644
index 000000000..50aee0f75
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Computed/Computed.generate.ts
@@ -0,0 +1,16 @@
+import { DTGenerateResult, DTGenerationData, DTGenerationExistingRowData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const placeholders: any = {};
+
+ data.existingRowData.forEach((row: DTGenerationExistingRowData) => {
+ placeholders[`ROW${row.colIndex + 1}`] = row.data.display;
+ placeholders[`ROWDATA${row.colIndex + 1}`] = row;
+ });
+ placeholders.ROWNUM = data.rowNum;
+
+ return {
+ display: utils.generalUtils.template(data.rowState.value, placeholders)
+ };
+};
diff --git a/packages/plugins/src/dataTypes/Computed/Computed.scss b/packages/plugins/src/dataTypes/Computed/Computed.scss
new file mode 100644
index 000000000..52b9b2220
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Computed/Computed.scss
@@ -0,0 +1,25 @@
+.row {
+ display: flex;
+ margin-bottom: 15px;
+
+ .col1 {
+ flex: 0 0 160px;
+ }
+ .col2 {
+ flex: 1;
+ }
+
+ label {
+ background-color: #dfecfc;
+ border-radius: 3px;
+ padding: 1px 6px;
+ }
+}
+
+.copyCol {
+ flex: 0 0 26px;
+}
+
+.copy {
+ margin: 4px 0 0 4px;
+}
diff --git a/packages/plugins/src/dataTypes/Computed/Computed.scss.d.ts b/packages/plugins/src/dataTypes/Computed/Computed.scss.d.ts
new file mode 100644
index 000000000..909480aa7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Computed/Computed.scss.d.ts
@@ -0,0 +1,16 @@
+declare namespace ComputedScssNamespace {
+ export interface IComputedScss {
+ col1: string;
+ col2: string;
+ copy: string;
+ copyCol: string;
+ row: string;
+ }
+}
+
+declare const ComputedScssModule: ComputedScssNamespace.IComputedScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: ComputedScssNamespace.IComputedScss;
+};
+
+export = ComputedScssModule;
diff --git a/packages/plugins/src/dataTypes/Computed/Computed.state.tsx b/packages/plugins/src/dataTypes/Computed/Computed.state.tsx
new file mode 100755
index 000000000..959bd9257
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Computed/Computed.state.tsx
@@ -0,0 +1,11 @@
+export type GenerationOptionsType = {
+ value: string;
+};
+
+export const initialState = {
+ value: ''
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ value: ''
+};
diff --git a/packages/plugins/src/dataTypes/Computed/Computed.tsx b/packages/plugins/src/dataTypes/Computed/Computed.tsx
new file mode 100755
index 000000000..7c2f003b2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Computed/Computed.tsx
@@ -0,0 +1,105 @@
+import * as React from 'react';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import CopyToClipboard from '~components/copyToClipboard/CopyToClipboard';
+import sharedStyles from '../../../styles/shared.scss';
+import styles from './Computed.scss';
+
+const Copy = ({ content, message, tooltip }: any) => (
+
+
+
+);
+
+export const Example = ({ coreI18n }: DTExampleProps) => {coreI18n.seeHelpDialog}
;
+
+export const Options = ({ data, onUpdate }: DTOptionsProps) => (
+ "});
+// }
+// return errors;
+// };
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'number'
+ },
+ sql: {
+ field: 'mediumint default NULL',
+ field_Oracle: 'varchar2(50) default NULL',
+ field_MSSQL: 'INTEGER NULL',
+ field_Postgres: 'integer NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/NumberRange/NumberRange.worker.ts b/packages/plugins/src/dataTypes/NumberRange/NumberRange.worker.ts
new file mode 100644
index 000000000..30ffba747
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/NumberRange.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './NumberRange.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/NumberRange/README.md b/packages/plugins/src/dataTypes/NumberRange/README.md
new file mode 100644
index 000000000..0446505c9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/README.md
@@ -0,0 +1,74 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » NumberRange
+
+This Data Type generates a number.
+
+
+### Examples
+
+This example generates 2 pieces of data:
+- a random number between 1 and 100.
+- a random number between -10000 and -20000.
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'NumberRange',
+ title: 'num1',
+ settings: {
+ min: 1,
+ max: 100
+ }
+ },
+ {
+ plugin: 'NumberRange',
+ title: 'num2',
+ settings: {
+ min: -10000,
+ max: -20000
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "num1": 100,
+ "num2": -2811
+ },
+ {
+ "num1": 23,
+ "num2": -4337
+ },
+ {
+ "num1": 89,
+ "num2": -8604
+ },
+ {
+ "num1": 72,
+ "num2": -9465
+ },
+ {
+ "num1": 8,
+ "num2": -2012
+ },
+ {
+ "num1": 4,
+ "num2": -8977
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/NumberRange/__tests__/NumberRange.ui.test.tsx b/packages/plugins/src/dataTypes/NumberRange/__tests__/NumberRange.ui.test.tsx
new file mode 100644
index 000000000..fe8a801b8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/__tests__/NumberRange.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../NumberRange';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/NumberRange/bundle.ts b/packages/plugins/src/dataTypes/NumberRange/bundle.ts
new file mode 100644
index 000000000..94140a61c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/bundle.ts
@@ -0,0 +1,13 @@
+import { DTBundle } from '~types/dataTypes';
+import { rowStateReducer, Help, Options, getMetadata } from './NumberRange';
+import { initialState } from './NumberRange.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ Help,
+ rowStateReducer,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/NumberRange/config.ts b/packages/plugins/src/dataTypes/NumberRange/config.ts
new file mode 100644
index 000000000..d0e84e8ff
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'numeric',
+ fieldGroupOrder: 30
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/ar.json b/packages/plugins/src/dataTypes/NumberRange/i18n/ar.json
new file mode 100644
index 000000000..ee915d53c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/ar.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "نطاق العدد",
+ "DESC": "يقوم هذا بشكل عشوائي بإنشاء رقم بين القيم التي تحددها. كلا الحقلين يسمحان لك بإدخال أرقام سالبة.",
+ "DEFAULT_TITLE": "ترقيم",
+ "and": "و",
+ "between": "ما بين",
+ "enterNumericValue": "الرجاء إدخال رقم",
+ "minValueGreaterThanMax": "يرجى جعل القيمة القصوى أكبر من أو تساوي الحد الأدنى للقيمة"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/de.json b/packages/plugins/src/dataTypes/NumberRange/i18n/de.json
new file mode 100644
index 000000000..81c0d6c1f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/de.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "Nummernkreis",
+ "DESC": "Diese zufällig generiert eine Zahl zwischen den Werten, die Sie angeben. Beide Felder können Sie negative Werte eingeben.",
+ "DEFAULT_TITLE": "nummernkreis",
+ "and": "und",
+ "between": "Zwischen",
+ "enterNumericValue": "Bitte geben Sie einen numerischen Wert ein",
+ "minValueGreaterThanMax": "Bitte machen Sie den Maximalwert größer als den Minimalwert"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/en.json b/packages/plugins/src/dataTypes/NumberRange/i18n/en.json
new file mode 100644
index 000000000..5a18d1c55
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/en.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "Number Range",
+ "DESC": "This randomly generates a number between the values you specify. Both fields allow you to enter negative numbers.",
+ "DEFAULT_TITLE": "numberrange",
+ "and": "and",
+ "between": "Between",
+ "enterNumericValue": "Please enter a number",
+ "minValueGreaterThanMax": "Please make the max value larger than or equal to the min value"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/es.json b/packages/plugins/src/dataTypes/NumberRange/i18n/es.json
new file mode 100644
index 000000000..734ef9616
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/es.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "Rango numérico",
+ "DESC": "Este tipo genera un número aleatorio entre los valores que especifiques. Ambos campos permite introducir números negativos.",
+ "DEFAULT_TITLE": "rangonumeros",
+ "and": "y",
+ "between": "Entre",
+ "enterNumericValue": "Por favor, introduzca un valor numérico",
+ "minValueGreaterThanMax": "Haz que el valor máximo sea mayor que el valor mínimo"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/fr.json b/packages/plugins/src/dataTypes/NumberRange/i18n/fr.json
new file mode 100644
index 000000000..d2a199195
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/fr.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "Plage de numéros",
+ "DESC": "Cela génère au hasard un nombre entre les valeurs que vous spécifiez. Les deux champs vous permettent d'entrer des nombres négatifs.",
+ "DEFAULT_TITLE": "plagenumeros",
+ "and": "et",
+ "between": "Entre",
+ "enterNumericValue": "Veuillez saisir une valeur numérique",
+ "minValueGreaterThanMax": "Veuillez rendre la valeur maximale supérieure à la valeur minimale"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/hi.json b/packages/plugins/src/dataTypes/NumberRange/i18n/hi.json
new file mode 100644
index 000000000..61a1b1019
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/hi.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "संख्या सीमा",
+ "DESC": "यह बेतरतीब ढंग से आपके द्वारा निर्दिष्ट मानों के बीच एक संख्या उत्पन्न करता है। दोनों फ़ील्ड आपको ऋणात्मक संख्याएँ दर्ज करने देती हैं।",
+ "DEFAULT_TITLE": "numberrange",
+ "and": "तथा",
+ "between": "बीच में",
+ "enterNumericValue": "एक संख्या दर्ज करें",
+ "minValueGreaterThanMax": "कृपया अधिकतम मान को न्यूनतम मान से बड़ा या उसके बराबर करें"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/ja.json b/packages/plugins/src/dataTypes/NumberRange/i18n/ja.json
new file mode 100644
index 000000000..b7a7989a6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/ja.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "番号の範囲",
+ "DESC": "これにより、指定した値の間にランダムに数値が生成されます。 どちらのフィールドでも、負の数を入力できます。",
+ "DEFAULT_TITLE": "番号の範囲",
+ "and": "そして",
+ "between": "の間に",
+ "enterNumericValue": "数値を入力してください",
+ "minValueGreaterThanMax": "最大値を最小値より大きくしてください"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/nl.json b/packages/plugins/src/dataTypes/NumberRange/i18n/nl.json
new file mode 100644
index 000000000..8dc8a86aa
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/nl.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "Nummer Range",
+ "DESC": "Dit genereert een willekeurig getal tussen de waarden die u opgeeft. In beide velden kunt u een negatieve waarde invoeren.",
+ "DEFAULT_TITLE": "nummerbereik",
+ "and": "en",
+ "between": "Tussen",
+ "enterNumericValue": "Voer een numerieke waarde in",
+ "minValueGreaterThanMax": "Maak de maximale waarde groter dan de minimale waarde"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/pt.json b/packages/plugins/src/dataTypes/NumberRange/i18n/pt.json
new file mode 100644
index 000000000..814424d4b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/pt.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "Faixa de Números",
+ "DESC": "Isso gera aleatoriamente um número entre os valores que você especifica. Ambos os campos permitem que você insira números negativos.",
+ "DEFAULT_TITLE": "faixanumero",
+ "and": "e",
+ "between": "Entre",
+ "enterNumericValue": "Por favor, coloque um numero",
+ "minValueGreaterThanMax": "Faça o valor máximo maior ou igual ao valor mínimo"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/ru.json b/packages/plugins/src/dataTypes/NumberRange/i18n/ru.json
new file mode 100644
index 000000000..a20a2e19a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/ru.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "Диапазон номеров",
+ "DESC": "Это случайным образом генерирует число между указанными вами значениями. Оба поля позволяют вводить отрицательные числа.",
+ "DEFAULT_TITLE": "numberrange",
+ "and": "а также",
+ "between": "Между",
+ "enterNumericValue": "Пожалуйста, введите номер",
+ "minValueGreaterThanMax": "Пожалуйста, сделайте максимальное значение больше или равным минимальному значению"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/ta.json b/packages/plugins/src/dataTypes/NumberRange/i18n/ta.json
new file mode 100644
index 000000000..248dac5c9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/ta.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "எண் வரம்பு",
+ "DESC": "இது நீங்கள் குறிப்பிடும் மதிப்புகளுக்கு இடையில் தோராயமாக ஒரு எண்ணை உருவாக்குகிறது. இரண்டு புலங்களும் எதிர்மறை எண்களை உள்ளிட உங்களை அனுமதிக்கின்றன.",
+ "DEFAULT_TITLE": "எண்",
+ "and": "மற்றும்",
+ "between": "இடையில்",
+ "enterNumericValue": "எண் மதிப்பை உள்ளிடவும்",
+ "minValueGreaterThanMax": "அதிகபட்ச மதிப்பை நிமிட மதிப்பை விட பெரிதாக்கவும்"
+}
diff --git a/packages/plugins/src/dataTypes/NumberRange/i18n/zh.json b/packages/plugins/src/dataTypes/NumberRange/i18n/zh.json
new file mode 100644
index 000000000..149f2814d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/NumberRange/i18n/zh.json
@@ -0,0 +1,9 @@
+{
+ "NAME": "编号范围",
+ "DESC": "这将在您指定的值之间随机生成一个数字。 这两个字段均允许您输入负数。",
+ "DEFAULT_TITLE": "数字范围",
+ "and": "和",
+ "between": "之间",
+ "enterNumericValue": "请输入一个数值",
+ "minValueGreaterThanMax": "请使最大值大于最小值"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.generate.ts b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.generate.ts
new file mode 100644
index 000000000..55e79b898
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.generate.ts
@@ -0,0 +1,88 @@
+import { DTGenerateResult } from '~types/dataTypes';
+
+// data: GenerationData
+export const generate = (): DTGenerateResult => {
+ return { display: '' };
+};
+
+
+/*
+ private $generatedOrgNrs = array();
+ static $sep = "-";
+
+ * Generate a random personal number, and return the display string and additional meta data for use
+ * by any other Data Type.
+ public function generate($generator, $generationContextData) {
+ $generationOptions = $generationContextData["generationOptions"];
+
+ // Default, 10 siffers + '-'
+ // TODO: support several countries?
+ static::$sep = self::getOrganisationNumberSeparator($generationOptions["cc_separator"]);
+
+ $orgnr = $this->generateRandomSwedishOrganisationNumber(static::$sep);
+
+ // pretty sodding unlikely, but just in case!
+ while (in_array($orgnr, $this->generatedOrgNrs)) {
+ $orgnr = $this->generateRandomSwedishOrganisationNumber(static::$sep);
+ }
+ $this->generatedOrgNrs[] = $orgnr;
+ return array(
+ "display" => $orgnr
+ );
+ }
+
+ // TODO: add support for separator
+ // TODO: add support for organisation numbers
+ private static function generateRandomSwedishOrganisationNumber($sep) {
+ $new_str = "";
+ $rand = 0;
+
+ $cnt = 11; // 10 siffers + 1 increment for separator
+
+ for ($i=0; $i<$cnt; $i++) {
+ switch ($i) {
+ case 0:
+ $rand = mt_rand(0, 99);
+ $new_str .= sprintf("%02d", $rand);
+ break;
+ case 2:
+ $rand = mt_rand(20, 99);
+ $new_str .= sprintf("%02d", $rand);
+ break;
+ case 4:
+ $rand = mt_rand(0, 99);
+ $new_str .= sprintf("%02d", $rand);
+ break;
+ case 6:
+ $new_str .= $sep;
+ break;
+ case 7:
+ $rand = mt_rand(0, 999);
+ $new_str .= sprintf("%03d", $rand);
+ break;
+ case 10:
+ // Same calculation as for personal numbers
+ // TODO: move to Utils??
+ $ctrl = DataType_PersonalNumber::recalcCtrl($new_str . "0", $sep);
+ $new_str .= sprintf("%01d", $ctrl);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $new_str;
+ }
+
+ private static function getOrganisationNumberSeparator($separators) {
+ $separatorList = explode("|", $separators);
+ $chosenSep = $separatorList[rand(0, count($separatorList)-1)];
+
+ // if no separator was entered use '' as default
+ if ($separators == "") {
+ $chosenSep = "";
+ }
+ return $chosenSep;
+ }
+
+*/
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.state.tsx b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.state.tsx
new file mode 100644
index 000000000..c82162254
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.state.tsx
@@ -0,0 +1,11 @@
+export type OrganizationNumberState = {
+ example: string;
+ separator: string;
+};
+
+export const initialState: OrganizationNumberState = {
+ example: '',
+ separator: ' '
+};
+
+export const defaultGenerationOptions = {};
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.tsx b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.tsx
new file mode 100644
index 000000000..d7d93b0cf
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.tsx
@@ -0,0 +1,130 @@
+import * as React from 'react';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import Dropdown from '~components/dropdown/Dropdown';
+
+export const Example = ({ i18n, data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: any): void => {
+ onUpdate({
+ example: value,
+ value: value
+ });
+ };
+
+ const options = [
+ { value: 'OrganisationNumberWithoutHyphen', label: i18n.example_OrganisationNumberWithoutHyphen },
+ { value: 'OrganisationNumberWithHyphen', label: i18n.example_OrganisationNumberWithHyphen }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+export const Options = ({ id, data, onUpdate, i18n }: DTOptionsProps) => {
+ const onChange = (separator: string): void => {
+ onUpdate({
+ ...data,
+ separator
+ });
+ };
+
+ return (
+
+ {i18n.separators}
+ onChange(e.target.value)}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+
+ {i18n.DESC}
+ {i18n.help_text}
+
+
+
+
+
+
+ OrganisationNumberWithoutHyphen
+
+ {i18n.type_OrganisationNumberWithoutHyphen}
+
+
+
+ OrganisationNumberWithHyphen
+
+ {i18n.type_OrganisationNumberWithHyphen}
+
+
+
+ >
+);
+
+export const getMetadata = (): DTMetadata => {
+ // Called before separator is set, so margin should be used
+ // $len = 10 + strlen(static::$sep);
+ const len = 11; // Should be enough, allow for max one char sep
+ return {
+ sql: {
+ field: `varchar(${len}) default NULL`,
+ field_Oracle: `varchar2(${len}) default NULL`,
+ field_MSSQL: `VARCHAR(${len}) NULL`
+ }
+ };
+};
+
+// var _exampleChange = function (msg) {
+// var rowID = msg.rowID;
+// var selectedFormat = msg.value;
+//
+// var $separatorField = $("#dtOptionOrganisationNumber_sep_" + rowID);
+//
+// switch (selectedFormat) {
+// case "OrganisationNumberWithoutHyphen":
+// $separatorField.val("");
+// break;
+//
+// case "OrganisationNumberWithHyphen":
+// $separatorField.val("-");
+// break;
+//
+// default:
+// //$separatorField.val(selectedFormat);
+// break;
+//
+// }
+// };
+
+/**
+ * Called when the user submits the form to generate some data. If the selected data set contains
+ * one or more rows of this data type, this function is called with the list of row numbers. Note that
+ * the row numbers passed are the *original* row numbers of the rows on creation. It's possible that the
+ * user has re-sorted or deleted some rows. So to get the visible row number for a row, call
+ * gen._getVisibleRowOrderByRowNum(row)
+ */
+// var _validate = function (rows) {
+// var visibleProblemRows = [];
+// var problemFields = [];
+// for (var i = 0; i < rows.length; i++) {
+// if ($("#dtOption_" + rows[i]).val() === "") {
+// var visibleRowNum = generator.getVisibleRowOrderByRowNum(rows[i]);
+// visibleProblemRows.push(visibleRowNum);
+// problemFields.push($("#dtOption_" + rows[i]));
+// }
+// }
+// var errors = [];
+// if (visibleProblemRows.length) {
+// errors.push({
+// els: problemFields,
+// error: LANG.incomplete_fields + " " + visibleProblemRows.join(", ") + " "
+// });
+// }
+// return errors;
+// };
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.worker.ts b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.worker.ts
new file mode 100644
index 000000000..6b6135fbb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/OrganizationNumber.worker.ts
@@ -0,0 +1,5 @@
+import { generate } from './OrganizationNumber.generate';
+
+export const onmessage = () => {
+ postMessage(generate());
+};
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/README.md b/packages/plugins/src/dataTypes/OrganizationNumber/README.md
new file mode 100644
index 000000000..ced66c22c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/README.md
@@ -0,0 +1,7 @@
+## Organization Number
+
+This Data Type generates a random organisation number, used in some countries for registration of companies, associations etc...
+
+### Example API Usage
+
+Not available.
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/__tests__/OrganizationNumber.test.tsx b/packages/plugins/src/dataTypes/OrganizationNumber/__tests__/OrganizationNumber.test.tsx
new file mode 100644
index 000000000..ededb38bc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/__tests__/OrganizationNumber.test.tsx
@@ -0,0 +1,41 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help, Options } from '../OrganizationNumber';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+const optionsProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ gridPanelDimensions: { width: 100, height: 100 },
+ isCountryNamesLoaded: false,
+ isCountryNamesLoading: false,
+ countryNamesMap: null
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('Options', () => {
+ it('renders', () => {
+ const { container } = render(
+ {}}
+ />
+ );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/bundle.ts b/packages/plugins/src/dataTypes/OrganizationNumber/bundle.ts
new file mode 100644
index 000000000..48bf9b376
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/bundle.ts
@@ -0,0 +1,13 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, getMetadata } from './OrganizationNumber';
+import { initialState } from './OrganizationNumber.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/config.ts b/packages/plugins/src/dataTypes/OrganizationNumber/config.ts
new file mode 100644
index 000000000..4a6e452cb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'countrySpecific',
+ fieldGroupOrder: 40
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ar.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ar.json
new file mode 100644
index 000000000..f089b16ce
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ar.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Organisation Number",
+ "DESC": "Generates organisation numbers, used in some countries for registration of companies, associations etc.",
+ "DEFAULT_TITLE": "orgnumber",
+ "help_text": "At the present time only Swedish ones are supported. The organisation numbers are generated according to the format you specify:",
+ "incomplete_fields": "The Organisation Number Data Type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "A (Swedish) organisation number with 12 siffers and no hyphen",
+ "type_OrganisationNumberWithHyphen": "A (Swedish) organisation number with 12 siffers and a hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the organisation number fields. Note: you can mix several separators, use | to separate them (for example -|+)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/de.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/de.json
new file mode 100644
index 000000000..dac7a43e8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/de.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Organisation Anzahl",
+ "DESC": "Erzeugt Organisation Zahlen in einigen Ländern für die Registrierung von Unternehmen, Verbänden , etc. verwendet",
+ "DEFAULT_TITLE": "nummer",
+ "help_text": "In der heutigen Zeit nur schwedische diejenigen unterstützt werden. Die Organisation Zahlen werden nach dem Format, das Sie angeben, generiert:",
+ "incomplete_fields": "Die Organisation Nummer Datentyp muss den im Textfeld eingegebenen Optionen Format haben . Bitte korrigieren Sie die folgenden Zeilen :",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "A (Swedish) Organisation Anzahl mit 12 siffers und ohne Bindestrich",
+ "type_OrganisationNumberWithHyphen": "A (Swedish) Organisation Anzahl mit 12 siffers und einem Bindestrich",
+ "separators": "Abscheider :",
+ "separator_help": "Die Zeichen, die Sie hier eingeben, wird als Trennzeichen in der Organisation Zahlenfeldern verwendet werden. Hinweis: Sie können mehrere Trennzeichen zu mischen, zu verwenden | um sie zu trennen (zum Beispiel - | +)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/en.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/en.json
new file mode 100644
index 000000000..f089b16ce
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/en.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Organisation Number",
+ "DESC": "Generates organisation numbers, used in some countries for registration of companies, associations etc.",
+ "DEFAULT_TITLE": "orgnumber",
+ "help_text": "At the present time only Swedish ones are supported. The organisation numbers are generated according to the format you specify:",
+ "incomplete_fields": "The Organisation Number Data Type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "A (Swedish) organisation number with 12 siffers and no hyphen",
+ "type_OrganisationNumberWithHyphen": "A (Swedish) organisation number with 12 siffers and a hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the organisation number fields. Note: you can mix several separators, use | to separate them (for example -|+)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/es.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/es.json
new file mode 100644
index 000000000..8b2622d38
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/es.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Número Organización",
+ "DESC": "Genera números de organización , que se utiliza en algunos países para el registro de empresas , asociaciones , etc.",
+ "DEFAULT_TITLE": "numero",
+ "help_text": "En la actualidad sólo se admiten los suecos. Los números de la organización se generan de acuerdo con el formato que se especifica :",
+ "incomplete_fields": "El tipo de datos Número organización tiene que tener el formato especificado en el campo de texto Opciones . Por favor, corrija los siguientes filas :",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "Un (sueco ) Número de organización con 12 siffers y sin guión",
+ "type_OrganisationNumberWithHyphen": "Un (sueco ) Número de organización con 12 siffers y un guión",
+ "separators": "Separadores :",
+ "separator_help": "Los caracteres que introduzca aquí se utilizará como separador de los campos de número de organizaciones . Nota: usted puede mezclar varios separadores , utilice | para separarlos ( por ejemplo - | +)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/fr.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/fr.json
new file mode 100644
index 000000000..ea0a09b57
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/fr.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Nombre d' Organisation",
+ "DESC": "Génère des numéros de l'organisation , utilisées dans certains pays pour l'enregistrement des entreprises , associations, etc.",
+ "DEFAULT_TITLE": "nombre",
+ "help_text": "À l'heure actuelle seuls suédoises sont pris en charge . Les numéros de l'organisation sont générées selon le format que vous spécifiez :",
+ "incomplete_fields": "L' Organisation Nombre Type de données doit avoir le format entré dans le champ de texte Options. S'il vous plaît fixer les lignes suivantes :",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "Un ( suédois) le numéro de l'organisation avec 12 siffers et sans trait d'union",
+ "type_OrganisationNumberWithHyphen": "Un ( suédois) le numéro de l'organisation avec 12 siffers et un trait d'union",
+ "separators": "Séparateurs :",
+ "separator_help": "Les caractères que vous entrez ici seront utilisés comme séparateur dans les champs de numéro de l'organisation . Remarque : vous pouvez mélanger plusieurs séparateurs , utilisez | pour les séparer ( par exemple - | + )"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/hi.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/hi.json
new file mode 100644
index 000000000..62ded395f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/hi.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "संगठन संख्या",
+ "DESC": "कुछ देशों में कंपनियों, संघों आदि के पंजीकरण के लिए उपयोग की जाने वाली संगठन संख्या उत्पन्न करता है।",
+ "DEFAULT_TITLE": "orgnumber",
+ "help_text": "वर्तमान समय में केवल स्वीडिश समर्थित हैं। संगठन संख्या आपके द्वारा निर्दिष्ट प्रारूप के अनुसार उत्पन्न होती है:",
+ "incomplete_fields": "संगठन संख्या डेटा प्रकार को विकल्प टेक्स्ट फ़ील्ड में प्रारूप दर्ज करने की आवश्यकता है। कृपया निम्न पंक्तियों को ठीक करें:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "A (स्वीडिश) संगठन संख्या जिसमें 12 सिफ़र हैं और कोई हाइफ़न नहीं है",
+ "type_OrganisationNumberWithHyphen": "ए (स्वीडिश) संगठन संख्या 12 सिफर और एक हाइफ़न के साथ",
+ "separators": "विभाजक:",
+ "separator_help": "आपके द्वारा यहां दर्ज किए गए वर्ण संगठन संख्या फ़ील्ड में विभाजक के रूप में उपयोग किए जाएंगे। नोट: आप कई विभाजकों को मिला सकते हैं, उपयोग करें | उन्हें अलग करने के लिए (उदाहरण के लिए -|+)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ja.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ja.json
new file mode 100644
index 000000000..f089b16ce
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ja.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Organisation Number",
+ "DESC": "Generates organisation numbers, used in some countries for registration of companies, associations etc.",
+ "DEFAULT_TITLE": "orgnumber",
+ "help_text": "At the present time only Swedish ones are supported. The organisation numbers are generated according to the format you specify:",
+ "incomplete_fields": "The Organisation Number Data Type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "A (Swedish) organisation number with 12 siffers and no hyphen",
+ "type_OrganisationNumberWithHyphen": "A (Swedish) organisation number with 12 siffers and a hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the organisation number fields. Note: you can mix several separators, use | to separate them (for example -|+)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/nl.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/nl.json
new file mode 100644
index 000000000..178ec0926
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/nl.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Organisatie Nummer",
+ "DESC": "Genereert een organisatienummer, dat in sommige landen voor de registratie van bedrijven, verenigingen etc. wordt gebruikt.",
+ "DEFAULT_TITLE": "aantal",
+ "help_text": "Op dit moment wordt alleen het Zweedse organisatienummer ondersteund. De organisatienummers worden gegenereerd volgens het formaat dat u opgeeft :",
+ "incomplete_fields": "Het Optie veld moet verplicht worden ingevuld bij het data type Organisatie Nummer. Corrigeer de volgende rij(en):",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "Een (Zweeds) organisatienummer met 12 cijfers zonder koppelteken",
+ "type_OrganisationNumberWithHyphen": "Een (Zweeds) organisatienummer met 12 cijfers en een koppelteken",
+ "separators": "Scheidingsteken:",
+ "separator_help": "De tekens die u hier invoert worden als scheidingsteken gebruikt in de organisatienummer veld. Let op : je kunt verschillende scheidingstekens combineren door ze te scheiden met een | ( bijvoorbeeld - | + )"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/pt.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/pt.json
new file mode 100644
index 000000000..92a886feb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/pt.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Número da Organização",
+ "DESC": "Gera números de organizações, usados em alguns países para registro de empresas, associações etc.",
+ "DEFAULT_TITLE": "numero",
+ "help_text": "No momento, apenas os suecos são suportados. Os números da organização são gerados de acordo com o formato que você especificar:",
+ "incomplete_fields": "O tipo de dados do número da organização precisa ter o formato inserido no campo de texto Opções. Corrija as seguintes linhas:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "Um número de organização (sueco) com 12 siffers e nenhum hífen",
+ "type_OrganisationNumberWithHyphen": "Um número de organização (sueco) com 12 siffers e um hífen",
+ "separators": "Separadores:",
+ "separator_help": "Os caracteres inseridos aqui serão usados como separadores nos campos de número da organização. Nota: você pode misturar vários separadores, use | para separá-los (por exemplo - | +)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ru.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ru.json
new file mode 100644
index 000000000..f882561f5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ru.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Номер организации",
+ "DESC": "Генерирует номера организаций, используемые в некоторых странах для регистрации компаний, ассоциаций и т. д.",
+ "DEFAULT_TITLE": "orgnumber",
+ "help_text": "В настоящее время поддерживаются только шведские. Номера организаций генерируются в соответствии с указанным форматом:",
+ "incomplete_fields": "Тип данных «Номер организации» должен иметь формат, введенный в текстовом поле «Параметры». Исправьте следующие строки:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "(Шведский) номер организации с 12 сифферсами и без дефиса.",
+ "type_OrganisationNumberWithHyphen": "(Шведский) номер организации с 12 сифферсами и дефисом.",
+ "separators": "Сепараторы:",
+ "separator_help": "Введенные здесь символы будут использоваться в качестве разделителя в полях номера организации. Примечание: вы можете смешивать несколько разделителей, используйте | чтобы разделить их (например -|+)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ta.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ta.json
new file mode 100644
index 000000000..f089b16ce
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/ta.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Organisation Number",
+ "DESC": "Generates organisation numbers, used in some countries for registration of companies, associations etc.",
+ "DEFAULT_TITLE": "orgnumber",
+ "help_text": "At the present time only Swedish ones are supported. The organisation numbers are generated according to the format you specify:",
+ "incomplete_fields": "The Organisation Number Data Type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "A (Swedish) organisation number with 12 siffers and no hyphen",
+ "type_OrganisationNumberWithHyphen": "A (Swedish) organisation number with 12 siffers and a hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the organisation number fields. Note: you can mix several separators, use | to separate them (for example -|+)"
+}
diff --git a/packages/plugins/src/dataTypes/OrganizationNumber/i18n/zh.json b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/zh.json
new file mode 100644
index 000000000..f089b16ce
--- /dev/null
+++ b/packages/plugins/src/dataTypes/OrganizationNumber/i18n/zh.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Organisation Number",
+ "DESC": "Generates organisation numbers, used in some countries for registration of companies, associations etc.",
+ "DEFAULT_TITLE": "orgnumber",
+ "help_text": "At the present time only Swedish ones are supported. The organisation numbers are generated according to the format you specify:",
+ "incomplete_fields": "The Organisation Number Data Type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_OrganisationNumberWithoutHyphen": "167502241016",
+ "example_OrganisationNumberWithHyphen": "16750224-1016",
+ "type_OrganisationNumberWithoutHyphen": "A (Swedish) organisation number with 12 siffers and no hyphen",
+ "type_OrganisationNumberWithHyphen": "A (Swedish) organisation number with 12 siffers and a hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the organisation number fields. Note: you can mix several separators, use | to separate them (for example -|+)"
+}
diff --git a/packages/plugins/src/dataTypes/PAN/PAN.generate.ts b/packages/plugins/src/dataTypes/PAN/PAN.generate.ts
new file mode 100644
index 000000000..ff4400cad
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/PAN.generate.ts
@@ -0,0 +1,74 @@
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+let utils: WorkerUtils;
+
+export const generate = (data: DTGenerationData, workerUtils: WorkerUtils): DTGenerateResult => {
+ utils = workerUtils;
+
+ const cards = Object.keys(data.rowState.cardFormats);
+ if (!cards.length) {
+ return { display: '' };
+ }
+
+ const randomCard = utils.randomUtils.getRandomArrayValue(cards);
+
+ const { formats, prefix } = data.rowState.cardFormats[randomCard];
+ const randomPrefix: number = utils.randomUtils.getRandomArrayValue(prefix);
+ const randomFormat: string = utils.randomUtils.getRandomArrayValue(formats);
+
+ const shouldDisplay = !!randomPrefix && !!randomFormat;
+
+ return {
+ display: shouldDisplay ? generatePAN(randomPrefix, randomFormat) : '',
+ cardType: randomCard
+ };
+};
+
+const generatePAN = (prefix: number, format: string): string => {
+ // first, strip out the non-X formatting chars, we'll add them back in at the end
+ const xChars = format.replace(/[^X]/g, '');
+ const prefixStr = prefix.toString();
+
+ let panNums = prefixStr + utils.randomUtils.generateRandomAlphanumericStr(xChars.substring(prefixStr.length + 1));
+ const numChars = panNums.length;
+ const reversedNums = utils.stringUtils.reverse(panNums);
+
+ // calculate sum
+ let sum = 0;
+ let pos = 0;
+ while (pos < numChars) {
+ // see issue 843 - incorrect PAN check digits
+ const currentNum: number = +reversedNums[pos];
+ let odd = currentNum * 2;
+ if (odd > 9) {
+ odd -= 9;
+ }
+ sum += odd;
+
+ if (pos + 1 < numChars) {
+ // see issue 843 - incorrect PAN check digits
+ sum += +reversedNums[pos + 1];
+ }
+ pos += 2;
+ }
+
+ // calculate check digit
+ const checkDigit = ((Math.floor(sum / 10) + 1) * 10 - sum) % 10;
+ panNums += checkDigit;
+
+ // lastly, convert it into the format provided
+ let finalPan = '';
+ let index = 0;
+
+ for (let i = 0; i < format.length; i++) {
+ if (format[i] === 'X') {
+ finalPan += panNums[index];
+ index++;
+ } else {
+ finalPan += format[i];
+ }
+ }
+
+ return finalPan;
+};
diff --git a/packages/plugins/src/dataTypes/PAN/PAN.scss b/packages/plugins/src/dataTypes/PAN/PAN.scss
new file mode 100644
index 000000000..250987db9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/PAN.scss
@@ -0,0 +1,23 @@
+@use '../../../styles/variables' as c;
+
+.buttonLabel {
+ margin-top: 4px;
+}
+
+.validLengthsTip {
+ b {
+ color: c.$primary-color;
+ font-weight: bold;
+ }
+ margin: 2px 0 6px;
+}
+
+.error {
+ color: c.$error;
+ padding: 2px 0 6px;
+}
+
+.noCreditCards {
+ color: #666666;
+ font-style: italic;
+}
diff --git a/packages/plugins/src/dataTypes/PAN/PAN.scss.d.ts b/packages/plugins/src/dataTypes/PAN/PAN.scss.d.ts
new file mode 100644
index 000000000..47148bc62
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/PAN.scss.d.ts
@@ -0,0 +1,15 @@
+declare namespace PanScssNamespace {
+ export interface IPanScss {
+ buttonLabel: string;
+ error: string;
+ noCreditCards: string;
+ validLengthsTip: string;
+ }
+}
+
+declare const PanScssModule: PanScssNamespace.IPanScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: PanScssNamespace.IPanScss;
+};
+
+export = PanScssModule;
diff --git a/packages/plugins/src/dataTypes/PAN/PAN.state.tsx b/packages/plugins/src/dataTypes/PAN/PAN.state.tsx
new file mode 100644
index 000000000..50e0cec8e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/PAN.state.tsx
@@ -0,0 +1,24 @@
+import { creditCardFormats, CreditCardFormatType, CreditCardType, creditCardTypes } from './formats';
+import { cloneObj } from '@generatedata/utils/general';
+
+export type PanState = {
+ example: string;
+ cardTypes: CreditCardType[];
+ cardFormats: CreditCardFormatType;
+};
+
+export type GenerationOptionsType = {
+ cardFormats: CreditCardFormatType;
+ ccCard: CreditCardType | 'any';
+};
+
+export const defaultGenerationOptions = {
+ cardFormats: Object.values(CreditCardType),
+ ccCard: 'any'
+};
+
+export const initialState: PanState = {
+ example: 'any',
+ cardTypes: creditCardTypes,
+ cardFormats: cloneObj(creditCardFormats)
+};
diff --git a/packages/plugins/src/dataTypes/PAN/PAN.tsx b/packages/plugins/src/dataTypes/PAN/PAN.tsx
new file mode 100644
index 000000000..412c73a42
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/PAN.tsx
@@ -0,0 +1,264 @@
+import React, { useEffect, useState } from 'react';
+import Button from '@mui/material/Button';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import CreatablePillField from '~components/creatablePillField/CreatablePillField';
+import { cloneObj } from '~utils/generalUtils';
+import { toSentenceCase } from '~utils/stringUtils';
+import { getI18nString } from '~utils/langUtils';
+import { PanState, GenerationOptionsType } from './PAN.state';
+import { creditCardFormats, CreditCardFormatType, CreditCardType, creditCardTypes } from './formats';
+import styles from './PAN.scss';
+
+export const getCreditCardOptions = (formats: string[], i18n: any): DropdownOption[] =>
+ formats.map((format) => ({
+ value: format,
+ label: i18n[format]
+ }));
+
+export const Example = ({ i18n, data, onUpdate }: DTExampleProps): React.ReactNode => {
+ const onChange = (value: string): void => {
+ const newData: Partial = {
+ ...data,
+ example: value
+ };
+
+ if (value === 'any') {
+ newData.cardTypes = creditCardTypes;
+ newData.cardFormats = cloneObj(creditCardFormats);
+ } else if (value === 'visa_mastercard') {
+ newData.cardTypes = [CreditCardType.visa, CreditCardType.mastercard];
+ newData.cardFormats = {
+ [CreditCardType.visa as string]: creditCardFormats[CreditCardType.visa],
+ [CreditCardType.mastercard as string]: creditCardFormats[CreditCardType.mastercard]
+ };
+ } else if (value === 'visa_mastercard_amex') {
+ newData.cardTypes = [CreditCardType.visa, CreditCardType.mastercard, CreditCardType.amex];
+ newData.cardFormats = {
+ [CreditCardType.visa as string]: creditCardFormats[CreditCardType.visa],
+ [CreditCardType.mastercard as string]: creditCardFormats[CreditCardType.mastercard],
+ [CreditCardType.amex as string]: creditCardFormats[CreditCardType.amex]
+ };
+ } else {
+ newData.cardTypes = [value as CreditCardType];
+ newData.cardFormats = {
+ [value]: creditCardFormats[value as CreditCardType]
+ };
+ }
+ onUpdate(newData);
+ };
+
+ const options = [
+ {
+ label: i18n.multipleCards,
+ options: [
+ { value: 'any', label: i18n.anyCard },
+ { value: 'visa_mastercard', label: i18n.visaMastercard },
+ { value: 'visa_mastercard_amex', label: i18n.visaMastercardAmex }
+ ]
+ },
+ {
+ label: i18n.specificCards,
+ options: getCreditCardOptions(creditCardTypes, i18n)
+ }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+const validFormat = (cardType: CreditCardType, format: string, allFormats: CreditCardFormatType): boolean => {
+ const validNumChars = allFormats[cardType]!.validNumChars;
+ const numChars = format.replace(/[^X]/gi, '').length;
+ return validNumChars.indexOf(numChars) !== -1;
+};
+
+const PANDialog = ({ visible, data, onClose, onUpdateSelectedCards, onUpdateCardFormats, coreI18n, i18n }: any) => {
+ const [selectedCard, setSelectedCard] = useState(null);
+ const [formatError, setFormatError] = useState('');
+
+ useEffect(() => {
+ if (data.cardTypes.indexOf(selectedCard) === -1) {
+ const firstCard = data.cardTypes[0] as CreditCardType;
+ setSelectedCard(firstCard);
+ }
+ }, [data.cardTypes.length]);
+
+ const updateFormats = (formats: string[]): void => {
+ setFormatError('');
+ const uppercaseFormats = formats.map((format) => format.replace(/x/g, 'X'));
+ onUpdateCardFormats(selectedCard, uppercaseFormats);
+ };
+
+ const validateFormat = (format: string): boolean => {
+ const isValid = validFormat(selectedCard as CreditCardType, format, creditCardFormats);
+ if (!isValid) {
+ setFormatError(i18n.invalidNewFormat);
+ }
+ return isValid;
+ };
+
+ const selectCreditCard = (creditCard: CreditCardType): void => {
+ setSelectedCard(creditCard);
+ };
+
+ const getFormatDesc = (creditCard: CreditCardType | null): JSX.Element | null => {
+ if (!creditCard) {
+ return null;
+ }
+
+ const validLengths = creditCardFormats[creditCard]!.validNumChars;
+ const i18nKey = validLengths.length === 1 ? i18n.validCardNumberSingleLength : i18n.validCardNumberMultipleLengths;
+
+ const text = getI18nString(i18nKey, [
+ `${i18n[creditCard]} `,
+ validLengths.map((length, index) => `${length} `).join(', ')
+ ]);
+
+ return
;
+ };
+
+ const getFormatError = (): JSX.Element | null => {
+ if (!formatError) {
+ return null;
+ }
+ return {formatError}
;
+ };
+
+ const getFormatsSection = () => {
+ if (!data.cardTypes.length) {
+ return {i18n.noCreditCards}
;
+ }
+
+ return (
+ <>
+
+ selectCreditCard(value)}
+ />
+
+
+ {getFormatDesc(selectedCard)}
+ {getFormatError()}
+
+ updateFormats(formats)}
+ placeholder={i18n.enterFormats}
+ onValidateNewItem={(newFormat: string): boolean => validateFormat(newFormat)}
+ />
+ >
+ );
+ };
+
+ return (
+
+
+
{i18n.selectCreditCards}
+
+
+
{toSentenceCase(i18n.creditCards)}
+ {
+ onUpdateSelectedCards(formats ? formats.map(({ value }: DropdownOption) => value) : []);
+ }}
+ />
+
+
+ {i18n.formats}
+ {i18n.formatsDesc}
+
+ {getFormatsSection()}
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ data, i18n, coreI18n, onUpdate }: DTOptionsProps): React.ReactNode => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+ const numSelected = data.cardTypes.length;
+
+ const onUpdateSelectedCards = (cardTypes: CreditCardType[]): void => {
+ const cardFormats: CreditCardFormatType = {};
+ cardTypes.forEach((cardType) => {
+ if (data.cardFormats[cardType]) {
+ cardFormats[cardType] = data.cardFormats[cardType];
+ } else {
+ cardFormats[cardType] = creditCardFormats[cardType];
+ }
+ });
+
+ onUpdate({
+ ...data,
+ cardTypes,
+ cardFormats
+ });
+ };
+
+ const onUpdateCardFormats = (cardType: CreditCardType, formats: string[]): void => {
+ onUpdate({
+ ...data,
+ cardFormats: {
+ ...data.cardFormats,
+ [cardType]: {
+ ...data.cardFormats[cardType],
+ formats
+ }
+ }
+ });
+ };
+
+ const label = `${numSelected} ` + (numSelected === 1 ? i18n.creditCard : i18n.creditCards);
+
+ return (
+
+
setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+
setDialogVisibility(false)}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+ {i18n.help1}
+ {i18n.help2}
+ >
+);
+
+export const rowStateReducer = ({ cardFormats, example }: PanState): GenerationOptionsType => ({
+ cardFormats,
+ ccCard: example as CreditCardType
+});
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/PAN/PAN.worker.ts b/packages/plugins/src/dataTypes/PAN/PAN.worker.ts
new file mode 100644
index 000000000..7f17d3913
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/PAN.worker.ts
@@ -0,0 +1,13 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './PAN.generate';
+
+let utilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage): void => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/PAN/README.md b/packages/plugins/src/dataTypes/PAN/README.md
new file mode 100644
index 000000000..a06ebf0f6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/README.md
@@ -0,0 +1,48 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » PAN
+
+This Data Type generates a *valid* Personal Access Number (PAN) for a variety of credit card types.
+
+- *brand*: the credit card name. Options: `mastercard, visa, visaElectron, amex, discover, carteBlanche, dinersClubInt,
+dinersClubEnRoute, jcb, maestro, solo, switch, laser, rand_card`. The last item is special. If it's entered, you need to
+include the *random_card* setting as well.
+to generate from.
+- *separator*: defaults to space ` `. The separator character
+- *format*: an array of possible formats to output the CC number.
+- *length*: this one's a bit confusing, honestly. You need to pass in a list of comma-delimited lengths of possible
+credit card lengths. [But aren't they defined in the format list?]
+- *random_card*: an array of credit card names to indicate the subset of credit card types to generate.
+
+
+### Example API Usage
+
+This generates a Visa number. Just POST the following JSON content to:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+```javascript
+{
+ "numRows": 50,
+ "rows": [
+ {
+ "type": "PAN",
+ "title": "cc_num",
+ "settings": {
+ "brand": "visa",
+ "format": ["XXXX XXXX XXXX XXXX", "XXX XXXXX XXXXX XXX"],
+ "length": "16"
+ }
+ }
+ ],
+ "export": {
+ "type": "JSON",
+ "settings": {
+ "stripWhitespace": false,
+ "dataStructureFormat": "simple"
+ }
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/dataTypes/PAN/__tests__/PAN.ui.test.tsx b/packages/plugins/src/dataTypes/PAN/__tests__/PAN.ui.test.tsx
new file mode 100644
index 000000000..148331bcd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/__tests__/PAN.ui.test.tsx
@@ -0,0 +1,32 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { getCreditCardOptions, Help } from '../PAN';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('getCreditCardOptions', () => {
+ it('returns data in the expected format', () => {
+ const i18n = {
+ one: 'First one',
+ two: 'Second one',
+ three: 'Third one'
+ };
+ expect(getCreditCardOptions(['one', 'two', 'three'], i18n)).toEqual([
+ { value: 'one', label: 'First one' },
+ { value: 'two', label: 'Second one' },
+ { value: 'three', label: 'Third one' }
+ ]);
+ });
+});
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/PAN/bundle.ts b/packages/plugins/src/dataTypes/PAN/bundle.ts
new file mode 100644
index 000000000..9c689984f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, rowStateReducer, getMetadata } from './PAN';
+import { initialState } from './PAN.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ rowStateReducer,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/PAN/config.ts b/packages/plugins/src/dataTypes/PAN/config.ts
new file mode 100644
index 000000000..a4b99d4da
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'financial',
+ fieldGroupOrder: 30
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/PAN/formats.ts b/packages/plugins/src/dataTypes/PAN/formats.ts
new file mode 100644
index 000000000..2cbd9c90e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/formats.ts
@@ -0,0 +1,150 @@
+const formatGroup1 = [
+ 'XXXXXXXXXXXXXXXX',
+ 'XXXX XXXX XXXX XXXX',
+ 'XXXXXX XXXXXX XXXX',
+ 'XXX XXXXX XXXXX XXX',
+ 'XXXXXX XXXXXXXXXX'
+];
+
+const formatGroup2 = [
+ 'XXXXXXXXXXXXXXX',
+ 'XXXX XXXXXX XXXXX'
+];
+
+const formatGroup3 = [
+ 'XXXXXXXXXXXXXXXX', // 16
+ 'XXXX XXXX XXXX XXXX',
+ 'XXXXXX XXXXXX XXXX',
+ 'XXX XXXXX XXXXX XXX',
+ 'XXXXXX XXXXXXXXXX',
+ 'XXXXXXXXXXXXXXXXXX', // 18
+ 'XXXXXXXXXXXXXXXXXXX', // 19
+ 'XXXXXX XX XXXX XXXX XXX'
+];
+
+export enum CreditCardType {
+ visa = 'visa',
+ visaElectron = 'visaElectron',
+ mastercard = 'mastercard',
+ discover = 'discover',
+ amex = 'amex',
+ carteBlanche = 'carteBlanche',
+ dinersClubInt = 'dinersClubInt',
+ dinersClubEnRoute = 'dinersClubEnRoute',
+ maestro = 'maestro',
+ solo = 'solo',
+ switch = 'switch',
+ laser = 'laser',
+}
+
+// all available credit cards in array format
+export const creditCardTypes: CreditCardType[] = [];
+for (const value in CreditCardType) {
+ creditCardTypes.push(value as CreditCardType);
+}
+
+export type CreditCardFormatType = {
+ [key in CreditCardType]?: {
+ prefix: number[];
+ formats: string[];
+ validNumChars: number[];
+ };
+}
+
+export const creditCardFormats: CreditCardFormatType = {
+ visa: {
+ prefix: [4539, 4556, 4916, 4532, 4929, 40240071, 4485, 4716, 4],
+ formats: [
+ 'XXXXXXXXXXXXX',
+ 'XXXX XXX XX XXXX',
+ 'XXXXXXXXXXXXXXXX',
+ 'XXXX XXXX XXXX XXXX',
+ 'XXXXXX XXXXXX XXXX',
+ 'XXX XXXXX XXXXX XXX',
+ 'XXXXXX XXXXXXXXXX'
+ ],
+ validNumChars: [13, 16]
+ },
+ visaElectron: {
+ prefix: [4026, 417500, 4508, 4844, 4913, 4917],
+ formats: formatGroup1,
+ validNumChars: [16]
+ },
+ mastercard: {
+ prefix: [51, 52, 53, 54, 55],
+ formats: formatGroup1,
+ validNumChars: [16]
+ },
+ discover: {
+ prefix: [6011, 644, 645, 646, 647, 648, 649, 65],
+ formats: formatGroup1,
+ validNumChars: [16]
+ },
+ amex: {
+ prefix: [34, 37],
+ formats: formatGroup2,
+ validNumChars: [15]
+ },
+ carteBlanche: {
+ prefix: [300, 301, 302, 303, 304, 305],
+ formats: formatGroup2,
+ validNumChars: [15]
+ },
+ dinersClubInt: {
+ prefix: [36],
+ formats: formatGroup2,
+ validNumChars: [15]
+ },
+ dinersClubEnRoute: {
+ prefix: [2014, 2149],
+ formats: formatGroup2,
+ validNumChars: [15]
+ },
+ maestro: {
+ prefix: [5018, 5038, 6304, 6759, 6761, 6762, 6763, 5893, 56, 57, 58],
+ formats: [
+ 'XXXXXXXXXXXX',
+ 'XXXXXXXXXXXXX',
+ 'XXXX XXX XX XXXX',
+ 'XXXXXXXXXXXXXX',
+ 'XXXX XXXXXX XXXX',
+ 'XXXXXXXXXXXXXXX',
+ 'XXXX XXXXXX XXXXX',
+ 'XXXXXXXXXXXXXXXX',
+ 'XXXX XXXX XXXX XXXX',
+ 'XXXXXX XXXXXX XXXX',
+ 'XXX XXXXX XXXXX XXX',
+ 'XXXXXX XXXXXXXXXX',
+ 'XXXXXXXXXXXXXXXXX',
+ 'XXXXXXXXXXXXXXXXXX',
+ 'XXXXXXXXXXXXXXXXXXX',
+ 'XXXXXX XX XXXX XXXX XXX'
+ ],
+ validNumChars: [12, 13, 14, 15, 16, 17, 18, 19]
+ },
+ solo: {
+ prefix: [6334, 6767],
+ formats: formatGroup3,
+ validNumChars: [16, 18, 19]
+ },
+ switch: {
+ prefix: [4903, 4905, 4905, 4911, 4936, 564182, 633110, 6333, 6759],
+ formats: formatGroup3,
+ validNumChars: [16, 18, 19]
+ },
+ laser: {
+ prefix: [6304, 6706, 6771, 6709],
+ formats: [
+ 'XXXXXXXXXXXXXXXX', // 16
+ 'XXXX XXXX XXXX XXXX',
+ 'XXXXXX XXXXXX XXXX',
+ 'XXX XXXXX XXXXX XXX',
+ 'XXXXXX XXXXXXXXXX',
+ 'XXXXXXXXXXXXXXXXX', // 17
+ 'XXXXXXXXXXXXXXXXXX', // 18
+ 'XXXXXXXXXXXXXXXXXXX', // 19
+ 'XXXXXX XX XXXX XXXX XXX'
+ ],
+ validNumChars: [16, 17, 18, 19]
+ }
+};
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/ar.json b/packages/plugins/src/dataTypes/PAN/i18n/ar.json
new file mode 100644
index 000000000..2b2ceb74c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/ar.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "بطاقة الائتمان PAN",
+ "DESC": "يقوم نوع البيانات هذا بإنشاء أرقام بطاقات ائتمان عشوائية وصالحة بتنسيق قابل للتخصيص.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "أي بطاقة",
+ "creditCard": "بطاقة ائتمان",
+ "creditCards": "بطاقات الائتمان",
+ "creditCardTypes": "أنواع بطاقات الائتمان",
+ "formats": "التنسيقات",
+ "multipleCards": "بطاقات متعددة",
+ "specificCards": "بطاقات محددة",
+ "visaMastercard": "Visa و Mastercard",
+ "visaMastercardAmex": "Visa و Mastercard و Amex",
+ "validCardNumberSingleLength": "يجب أن يحتوي رقم %1 الصالح على %2 X's.",
+ "validCardNumberMultipleLengths": "يجب أن يحتوي الرقم%1 الصالح على واحد من عدد X التالي:%2",
+ "selectCreditCards": "حدد بطاقات الائتمان",
+ "formatsDesc": "يتيح لك هذا تخصيص تنسيقات العرض لكل بطاقة ائتمان حددتها أعلاه. يمكنك إضافة التنسيقات الخاصة بك ، باستخدام أي محدد حرف تريده - طالما أن عدد X (الذي يتم تحويله تلقائيًا إلى أرقام) الذي تدخله صالحًا لهذا الائتمان نوع البطاقة.",
+ "enterFormats": "أدخل تنسيقات بطاقة الائتمان",
+ "help1": "يؤدي هذا إلى إنشاء PANs صالحة لبطاقات الائتمان (أرقام الوصول الأساسية) للعديد من بطاقات الائتمان الرئيسية في جميع أنحاء العالم. بشكل افتراضي ، ستُنشئ أرقام بطاقات الائتمان من أي نوع مدعوم (Visa و Mastercard و American Express وغيرها) ، ولكن لديك خيار اختيار أيهما تريد.",
+ "help2": "بالإضافة إلى ذلك ، يمكنك تخصيص تنسيق الرقم الذي تم إنشاؤه (على سبيل المثال ، توفير محدد أحرف مخصص). لذلك ، يمكنك إدخال سلسلة من X في حقل التنسيق المخصص ، مع أي أحرف أخرى تريدها ، ثم انقر فوق إدخال. سيتحقق الحقل من أنك أدخلت عددًا صالحًا من X's: بطاقات ائتمان مختلفة لها أطوال أحرف صالحة مختلفة. ستخبرك كل بطاقة ائتمان محددة بعدد صحيح من الأحرف X تحتاج إلى إدخاله",
+ "invalidNewFormat": "الرجاء إدخال سلسلة تحتوي على العدد الصحيح من X.",
+ "noCreditCards": "لم يتم اختيار بطاقات ائتمان."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/de.json b/packages/plugins/src/dataTypes/PAN/i18n/de.json
new file mode 100644
index 000000000..9ff4feee3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/de.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "Kreditkarte - PAN",
+ "DESC": "Dieser Datentyp generiert zufällige, gültige Kreditkartennummern in einem anpassbaren Format.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "Beliebige Karte",
+ "creditCard": "Kreditkarte",
+ "creditCards": "Kreditkarten",
+ "creditCardTypes": "Kreditkartentypen",
+ "formats": "Formate",
+ "multipleCards": "Mehrere Karten",
+ "specificCards": "Spezifische Karten",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard und Amex",
+ "validCardNumberSingleLength": "Eine gültige %1-Zahl muss %2 X enthalten.",
+ "validCardNumberMultipleLengths": "Eine gültige %1-Zahl muss eine der folgenden X-Zahlen enthalten: %2",
+ "selectCreditCards": "Wählen Sie Kreditkarten aus",
+ "formatsDesc": "Auf diese Weise können Sie die Anzeigeformate jeder oben ausgewählten Kreditkarte anpassen. Sie können Ihre eigenen Formate mit einem beliebigen Zeichenbegrenzer hinzufügen - solange die von Ihnen eingegebene Anzahl von X (automatisch in Zahlen konvertiert) für diesen Kreditkartentyp gültig ist.",
+ "enterFormats": "Geben Sie die Kreditkartenformate ein",
+ "help1": "Dadurch werden gültige Kreditkarten-PANs (Primary Access Numbers) für viele der weltweit wichtigsten Kreditkarten generiert. Standardmäßig werden Kreditkartennummern von jedem unterstützten Typ (Visa, Mastercard, American Express und andere) generiert. Sie können jedoch auswählen, was Sie möchten.",
+ "help2": "Darüber hinaus können Sie das Format der generierten Nummer anpassen (z. B. ein benutzerdefiniertes Zeichenbegrenzer angeben). Dazu geben Sie im Feld für das benutzerdefinierte Format eine X-Zeichenfolge mit den gewünschten anderen Zeichen ein und klicken auf die Eingabetaste. Das Feld bestätigt, dass Sie eine gültige Anzahl von X eingegeben haben: Verschiedene Kreditkarten haben unterschiedliche gültige Zeichenlängen. Jede ausgewählte Kreditkarte informiert Sie über die gültige Anzahl von X Zeichen, die Sie eingeben müssen.",
+ "invalidNewFormat": "Bitte geben Sie eine Zeichenfolge ein, die die richtige Anzahl von X enthält.",
+ "noCreditCards": "Keine Kreditkarten ausgewählt."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/en.json b/packages/plugins/src/dataTypes/PAN/i18n/en.json
new file mode 100644
index 000000000..c4f05a5bd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/en.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "Credit Card - PAN",
+ "DESC": "This data type generates random, valid credit card numbers in a customizable format.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "Any card",
+ "creditCard": "credit card",
+ "creditCards": "credit cards",
+ "creditCardTypes": "Credit card types",
+ "formats": "Formats",
+ "multipleCards": "Multiple cards",
+ "specificCards": "Specific cards",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard and Amex",
+ "validCardNumberSingleLength": "A valid %1 number must contain %2 X's.",
+ "validCardNumberMultipleLengths": "A valid %1 number must contain one of the following number of X's: %2",
+ "selectCreditCards": "Select credit cards",
+ "formatsDesc": "This lets you customize the display formats of each credit card you have selected above. You can add your own formats, using whatever character delimiter you like - as long as the number of X's (automatically converted to numbers) you enter is valid for that credit card type.",
+ "enterFormats": "Enter credit card formats",
+ "help1": "This generates valid credit card PANs (Primary Access Numbers) for many of the major worldwide credit cards. By default, it'll generate credit card numbers from any supported type (Visa, Mastercard, American Express and others), but you have the option to choose whichever you want.",
+ "help2": "In addition, you can customize the format of the generated number (e.g. supply a custom character delimiter). For that, you would enter a string of X's in the custom format field, with whatever other characters you want, then click enter. The field will validate that you've entered a valid number of X's: different credit cards have different valid character lengths. Each credit card selected will inform you of the valid number of X characters you need to enter.",
+ "invalidNewFormat": "Please enter a string that contains the correct number of X's.",
+ "noCreditCards": "No credit cards selected."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/es.json b/packages/plugins/src/dataTypes/PAN/i18n/es.json
new file mode 100644
index 000000000..a587cf0cb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/es.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "Tarjeta de crédito - PAN",
+ "DESC": "Este tipo de datos genera números de tarjetas de crédito válidos y aleatorios en un formato personalizable.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "Cualquier tarjeta",
+ "creditCard": "tarjeta de crédito",
+ "creditCards": "tarjetas de crédito",
+ "creditCardTypes": "Tipos de tarjetas de crédito",
+ "formats": "Formatos",
+ "multipleCards": "Varias cartas",
+ "specificCards": "Cartas específicas",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard y Amex",
+ "validCardNumberSingleLength": "Un número %1 válido debe contener %2 X.",
+ "validCardNumberMultipleLengths": "Un número %1 válido debe contener uno de los siguientes números de X: %2",
+ "selectCreditCards": "Seleccionar tarjetas de crédito",
+ "formatsDesc": "Esto le permite personalizar los formatos de visualización de cada tarjeta de crédito que haya seleccionado anteriormente. Puede agregar sus propios formatos, utilizando el delimitador de caracteres que desee, siempre que el número de X (convertido automáticamente en números) que ingrese sea válido para ese tipo de tarjeta de crédito.",
+ "enterFormats": "Ingrese los formatos de la tarjeta de crédito",
+ "help1": "Esto genera PAN de tarjetas de crédito (números de acceso primarios) válidos para muchas de las principales tarjetas de crédito del mundo. De forma predeterminada, generará números de tarjetas de crédito de cualquier tipo admitido (Visa, Mastercard, American Express y otros), pero tiene la opción de elegir el que desee.",
+ "help2": "Además, puede personalizar el formato del número generado (por ejemplo, proporcionar un delimitador de caracteres personalizado). Para eso, debe ingresar una cadena de X en el campo de formato personalizado, con cualquier otro carácter que desee, luego haga clic en ingresar. El campo validará que ha ingresado un número válido de X: diferentes tarjetas de crédito tienen diferentes longitudes de caracteres válidos. Cada tarjeta de crédito seleccionada le informará del número válido de X caracteres que debe ingresar.",
+ "invalidNewFormat": "Ingrese una cadena que contenga el número correcto de X.",
+ "noCreditCards": "No se seleccionaron tarjetas de crédito."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/fr.json b/packages/plugins/src/dataTypes/PAN/i18n/fr.json
new file mode 100644
index 000000000..f821f0aa6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/fr.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "Carte de crédit - PAN",
+ "DESC": "Ce type de données génère des numéros de carte de crédit aléatoires et valides dans un format personnalisable.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "N'importe quelle carte",
+ "creditCard": "carte de crédit",
+ "creditCards": "cartes de crédit",
+ "creditCardTypes": "Types de cartes de crédit",
+ "formats": "Formats",
+ "multipleCards": "Cartes multiples",
+ "specificCards": "Cartes spécifiques",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard et Amex",
+ "validCardNumberSingleLength": "Un nombre %1 valide doit contenir %2 X.",
+ "validCardNumberMultipleLengths": "Un nombre %1 valide doit contenir l'un des nombres de X suivants: %2",
+ "selectCreditCards": "Sélectionnez les cartes de crédit",
+ "formatsDesc": "Cela vous permet de personnaliser les formats d'affichage de chaque carte de crédit que vous avez sélectionnée ci-dessus. Vous pouvez ajouter vos propres formats, en utilisant le séparateur de caractères de votre choix - à condition que le nombre de X (automatiquement converti en nombres) que vous entrez soit valide pour ce type de carte de crédit.",
+ "enterFormats": "Entrez les formats de carte de crédit",
+ "help1": "Cela génère des PAN de cartes de crédit valides (numéros d'accès primaires) pour la plupart des principales cartes de crédit mondiales. Par défaut, il générera des numéros de carte de crédit à partir de n'importe quel type pris en charge (Visa, Mastercard, American Express et autres), mais vous avez la possibilité de choisir celui que vous voulez.",
+ "help2": "De plus, vous pouvez personnaliser le format du nombre généré (par exemple, fournir un séparateur de caractères personnalisé). Pour cela, vous devez entrer une chaîne de X dans le champ de format personnalisé, avec les autres caractères souhaités, puis cliquez sur Entrée. Le champ validera que vous avez entré un nombre valide de X: différentes cartes de crédit ont des longueurs de caractères valides différentes. Chaque carte de crédit sélectionnée vous informera du nombre valide de X caractères que vous devez saisir.",
+ "invalidNewFormat": "Veuillez saisir une chaîne contenant le nombre correct de X.",
+ "noCreditCards": "Aucune carte de crédit sélectionnée."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/hi.json b/packages/plugins/src/dataTypes/PAN/i18n/hi.json
new file mode 100644
index 000000000..02a97f7e6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/hi.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "क्रेडिट कार्ड - पर्सनल एक्सेस नंबर",
+ "DESC": "यह डेटा प्रकार एक अनुकूलन योग्य प्रारूप में यादृच्छिक, मान्य क्रेडिट कार्ड नंबर उत्पन्न करता है।",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "कोई भी कार्ड",
+ "creditCard": "क्रेडिट कार्ड",
+ "creditCards": "क्रेडिट कार्ड",
+ "creditCardTypes": "क्रेडिट कार्ड के प्रकार",
+ "formats": "प्रारूप",
+ "multipleCards": "एकाधिक कार्ड",
+ "specificCards": "विशिष्ट कार्ड",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard तथा Amex",
+ "validCardNumberSingleLength": "एक वैध %1 संख्या में %2 X होना चाहिए।",
+ "validCardNumberMultipleLengths": "एक वैध %1 संख्या में निम्न में से एक X की संख्या होनी चाहिए: %2",
+ "selectCreditCards": "क्रेडिट कार्ड चुनें",
+ "formatsDesc": "इससे आप ऊपर चुने गए प्रत्येक क्रेडिट कार्ड के प्रदर्शन स्वरूपों को अनुकूलित कर सकते हैं। आप जो भी वर्ण सीमांकक पसंद करते हैं, उसका उपयोग करके आप अपने स्वयं के प्रारूप जोड़ सकते हैं - जब तक कि आपके द्वारा दर्ज किए गए X की संख्या (स्वचालित रूप से संख्याओं में परिवर्तित) उस क्रेडिट कार्ड प्रकार के लिए मान्य है।",
+ "enterFormats": "क्रेडिट कार्ड प्रारूप दर्ज करें",
+ "help1": "यह दुनिया भर के कई प्रमुख क्रेडिट कार्डों के लिए वैध क्रेडिट कार्ड पैन (प्राथमिक एक्सेस नंबर) उत्पन्न करता है। डिफ़ॉल्ट रूप से, यह किसी भी समर्थित प्रकार (वीज़ा, मास्टरकार्ड, अमेरिकन एक्सप्रेस और अन्य) से क्रेडिट कार्ड नंबर उत्पन्न करेगा, लेकिन आपके पास जो भी आप चाहते हैं उसे चुनने का विकल्प है।",
+ "help2": "इसके अलावा, आप जनरेट की गई संख्या के प्रारूप को अनुकूलित कर सकते हैं (उदाहरण के लिए एक कस्टम वर्ण सीमांकक की आपूर्ति)। उसके लिए, आप कस्टम प्रारूप फ़ील्ड में एक्स की एक स्ट्रिंग दर्ज करेंगे, जो भी अन्य वर्ण आप चाहते हैं, फिर एंटर पर क्लिक करें। फ़ील्ड मान्य करेगा कि आपने X की मान्य संख्या दर्ज की है: विभिन्न क्रेडिट कार्डों की अलग-अलग मान्य वर्ण लंबाई होती है। चयनित प्रत्येक क्रेडिट कार्ड आपको X वर्णों की वैध संख्या के बारे में सूचित करेगा, जिन्हें आपको दर्ज करने की आवश्यकता है।",
+ "invalidNewFormat": "कृपया एक स्ट्रिंग दर्ज करें जिसमें X की सही संख्या हो।",
+ "noCreditCards": "कोई क्रेडिट कार्ड नहीं चुना गया."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/ja.json b/packages/plugins/src/dataTypes/PAN/i18n/ja.json
new file mode 100644
index 000000000..2df41baf9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/ja.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "クレジットカード-PAN",
+ "DESC": "このデータ型は、カスタマイズ可能な形式でランダムで有効なクレジットカード番号を生成します。",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "任意のカード",
+ "creditCard": "クレジットカード",
+ "creditCards": "クレジットカード",
+ "creditCardTypes": "クレジットカードの種類",
+ "formats": "フォーマット",
+ "multipleCards": "複数のカード",
+ "specificCards": "特定のカード",
+ "visaMastercard": "Visa、Mastercard",
+ "visaMastercardAmex": "Visa、Mastercard、Amex",
+ "validCardNumberSingleLength": "有効な%1番号には、%2Xが含まれている必要があります。",
+ "validCardNumberMultipleLengths": "有効な%1番号には、次のXの番号のいずれかが含まれている必要があります:%2",
+ "selectCreditCards": "クレジットカードを選択",
+ "formatsDesc": "これにより、上記で選択した各クレジットカードの表示形式をカスタマイズできます。 入力したXの数(自動的に数字に変換される)がそのクレジットカードタイプで有効である限り、任意の文字区切り文字を使用して、独自の形式を追加できます。",
+ "enterFormats": "クレジットカードのフォーマットを入力してください",
+ "help1": "これにより、世界中の主要なクレジットカードの多くに有効なクレジットカードPAN(プライマリアクセス番号)が生成されます。 デフォルトでは、サポートされている任意のタイプ(Visa、Mastercard、American Expressなど)からクレジットカード番号が生成されますが、必要に応じて選択することもできます。",
+ "help2": "さらに、生成された数値の形式をカスタマイズできます(たとえば、カスタム文字区切り文字を指定します)。 そのためには、カスタムフォーマットフィールドにXの文字列を他の任意の文字とともに入力し、Enterをクリックします。 このフィールドは、有効な数のXを入力したことを検証します。クレジットカードが異なれば、有効な文字長も異なります。 選択した各クレジットカードは、入力する必要のあるX文字の有効な数を通知します。",
+ "invalidNewFormat": "正しい数のXを含む文字列を入力してください。",
+ "noCreditCards": "クレジットカードが選択されていません。"
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/nl.json b/packages/plugins/src/dataTypes/PAN/i18n/nl.json
new file mode 100644
index 000000000..bdc3de4eb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/nl.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "Creditcard - PAN",
+ "DESC": "Dit gegevenstype genereert willekeurige, geldige creditcardnummers in een aanpasbare indeling.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "Elke kaart",
+ "creditCard": "kredietkaart",
+ "creditCards": "kredietkaarten",
+ "creditCardTypes": "Creditcardtypes",
+ "formats": "Formaten",
+ "multipleCards": "Meerdere kaarten",
+ "specificCards": "Specifieke kaarten",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard en Amex",
+ "validCardNumberSingleLength": "Een geldig %1-nummer moet %2 X-en bevatten.",
+ "validCardNumberMultipleLengths": "Een geldig %1-nummer moet een van de volgende X'en bevatten: %2",
+ "selectCreditCards": "Selecteer creditcards",
+ "formatsDesc": "Hiermee kunt u de weergaveformaten van elke creditcard die u hierboven heeft geselecteerd, aanpassen. U kunt uw eigen formaten toevoegen, met elk tekenscheidingsteken dat u maar wilt - zolang het aantal X-en (automatisch omgezet naar getallen) dat u invoert geldig is voor dat creditcardtype.",
+ "enterFormats": "Voer creditcardformaten in",
+ "help1": "Dit genereert geldige creditcard-PAN's (primaire toegangsnummers) voor veel van de belangrijkste wereldwijde creditcards. Standaard genereert het creditcardnummers van elk ondersteund type (Visa, Mastercard, American Express en andere), maar u heeft de mogelijkheid om te kiezen wat u maar wilt.",
+ "help2": "Bovendien kunt u het formaat van het gegenereerde nummer aanpassen (bijv. Een aangepast scheidingsteken voor tekens opgeven). Daarvoor zou je een reeks X-en in het aangepaste formaatveld invoeren, met welke andere tekens je maar wilt, en vervolgens op Enter klikken. Het veld bevestigt dat u een geldig aantal X-en heeft ingevoerd: verschillende creditcards hebben verschillende geldige tekenlengtes. Elke geselecteerde creditcard zal u informeren over het geldige aantal X-tekens dat u moet invoeren.",
+ "invalidNewFormat": "Voer een string in die het juiste aantal X-en bevat.",
+ "noCreditCards": "Geen creditcards geselecteerd."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/pt.json b/packages/plugins/src/dataTypes/PAN/i18n/pt.json
new file mode 100644
index 000000000..f7664e2fc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/pt.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "Cartão de Crédito - PAN",
+ "DESC": "Este tipo de dados gera números de cartão de crédito válidos aleatórios em um formato personalizável.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "Qualquer cartão",
+ "creditCard": "Cartão de crédito",
+ "creditCards": "cartões de crédito",
+ "creditCardTypes": "Tipos de cartão de crédito",
+ "formats": "Formatos",
+ "multipleCards": "Múltiplas cartas",
+ "specificCards": "Múltiplas cartas",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard e Amex",
+ "validCardNumberSingleLength": "Um número %1 válido deve conter %2 X's.",
+ "validCardNumberMultipleLengths": "Um número %1 válido deve conter um dos seguintes números de X: %2",
+ "selectCreditCards": "Selecione os cartões de crédito",
+ "formatsDesc": "Isso permite que você personalize os formatos de exibição de cada cartão de crédito que você selecionou acima. Você pode adicionar seus próprios formatos, usando qualquer delimitador de caracteres que desejar - desde que o número de Xs (convertidos automaticamente em números) que você inserir seja válido para esse tipo de cartão de crédito.",
+ "enterFormats": "Insira formatos de cartão de crédito",
+ "help1": "Isso gera PANs (números de acesso primários) de cartão de crédito válidos para muitos dos principais cartões de crédito em todo o mundo. Por padrão, ele irá gerar números de cartão de crédito de qualquer tipo compatível (Visa, Mastercard, American Express e outros), mas você tem a opção de escolher o que quiser.",
+ "help2": "Além disso, você pode personalizar o formato do número gerado (por exemplo, fornecer um delimitador de caractere personalizado). Para isso, você deve inserir uma string de X no campo de formato personalizado, com quaisquer outros caracteres que desejar, e clicar em entrar. O campo validará se você inseriu um número válido de Xs: diferentes cartões de crédito têm diferentes comprimentos de caracteres válidos. Cada cartão de crédito selecionado irá informá-lo do número válido de X caracteres que você precisa inserir.",
+ "invalidNewFormat": "Insira uma string que contenha o número correto de Xs.",
+ "noCreditCards": "Nenhum cartão de crédito selecionado."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/ru.json b/packages/plugins/src/dataTypes/PAN/i18n/ru.json
new file mode 100644
index 000000000..161392b60
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/ru.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "Кредитная карта — ПАН",
+ "DESC": "Этот тип данных генерирует случайные действительные номера кредитных карт в настраиваемом формате.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "Любая карта",
+ "creditCard": "кредитная карта",
+ "creditCards": "кредитные карты",
+ "creditCardTypes": "Типы кредитных карт",
+ "formats": "Форматы",
+ "multipleCards": "Несколько карт",
+ "specificCards": "Конкретные карты",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard and Amex",
+ "validCardNumberSingleLength": "Допустимый номер %1 должен содержать %2 X.",
+ "validCardNumberMultipleLengths": "Действительный номер %1 должен содержать один из следующих количеств X: %2",
+ "selectCreditCards": "Выберите кредитные карты",
+ "formatsDesc": "Это позволяет вам настроить форматы отображения каждой кредитной карты, которую вы выбрали выше. Вы можете добавить свои собственные форматы, используя любой разделитель символов, который вам нравится, при условии, что введенное вами количество X (автоматически преобразованное в числа) допустимо для этого типа кредитной карты.",
+ "enterFormats": "Введите форматы кредитных карт",
+ "help1": "Это генерирует действительные PAN кредитных карт (первичные номера доступа) для многих основных мировых кредитных карт. По умолчанию он генерирует номера кредитных карт любого поддерживаемого типа (Visa, Mastercard, American Express и другие), но у вас есть возможность выбрать любой из них.",
+ "help2": "Кроме того, вы можете настроить формат сгенерированного числа (например, указать пользовательский разделитель символов). Для этого вы должны ввести строку X в поле пользовательского формата с любыми другими символами, которые вы хотите, а затем нажать Enter. Поле подтвердит, что вы ввели допустимое количество X: разные кредитные карты имеют разную допустимую длину символов. Каждая выбранная кредитная карта будет информировать вас о допустимом количестве X символов, которые необходимо ввести.",
+ "invalidNewFormat": "Пожалуйста, введите строку, содержащую правильное количество X.",
+ "noCreditCards": "Кредитные карты не выбраны."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/ta.json b/packages/plugins/src/dataTypes/PAN/i18n/ta.json
new file mode 100644
index 000000000..5c0531e6a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/ta.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "கடன் அட்டை - PAN",
+ "DESC": "இந்த தரவு வகை தனிப்பயனாக்கக்கூடிய வடிவத்தில் சீரற்ற, செல்லுபடியாகும் கிரெடிட் கார்டு எண்களை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "எந்த அட்டையும்",
+ "creditCard": "கடன் அட்டை",
+ "creditCards": "கடன் அட்டைகள்",
+ "creditCardTypes": "கடன் அட்டை வகைகள்",
+ "formats": "வடிவங்கள்",
+ "multipleCards": "பல அட்டைகள்",
+ "specificCards": "குறிப்பிட்ட அட்டைகள்",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard, Amex",
+ "validCardNumberSingleLength": "செல்லுபடியாகும் %1 எண்ணில் %2 X கள் இருக்க வேண்டும்.",
+ "validCardNumberMultipleLengths": "செல்லுபடியாகும் %1 எண்ணில் பின்வரும் X இன் எண்ணிக்கையில் ஒன்று இருக்க வேண்டும்: %2",
+ "selectCreditCards": "கடன் அட்டைகளைத் தேர்ந்தெடுக்கவும்",
+ "formatsDesc": "நீங்கள் மேலே தேர்ந்தெடுத்த ஒவ்வொரு கிரெடிட் கார்டின் காட்சி வடிவங்களையும் தனிப்பயனாக்க இது உங்களை அனுமதிக்கிறது. நீங்கள் விரும்பும் எழுத்துக்குறி டிலிமிட்டரைப் பயன்படுத்தி உங்கள் சொந்த வடிவங்களைச் சேர்க்கலாம் - நீங்கள் உள்ளிட்ட எக்ஸ் இன் எண்ணிக்கை (தானாக எண்களாக மாற்றப்படும்) அந்த கிரெடிட் கார்டு வகைக்கு செல்லுபடியாகும்.",
+ "enterFormats": "கிரெடிட் கார்டு வடிவங்களை உள்ளிடவும்",
+ "help1": "இது உலகளாவிய பல முக்கிய கிரெடிட் கார்டுகளுக்கு செல்லுபடியாகும் கிரெடிட் கார்டு பான் (முதன்மை அணுகல் எண்கள்) உருவாக்குகிறது. இயல்பாக, இது எந்தவொரு ஆதரவு வகையிலிருந்தும் (விசா, மாஸ்டர்கார்டு, அமெரிக்கன் எக்ஸ்பிரஸ் மற்றும் பிறவற்றிலிருந்து) கிரெடிட் கார்டு எண்களை உருவாக்கும், ஆனால் நீங்கள் விரும்பும் ஒன்றைத் தேர்வுசெய்ய உங்களுக்கு விருப்பம் உள்ளது.",
+ "help2": "கூடுதலாக, நீங்கள் உருவாக்கிய எண்ணின் வடிவமைப்பைத் தனிப்பயனாக்கலாம் (எ.கா. தனிப்பயன் எழுத்துக்குறி டிலிமிட்டரை வழங்குதல்). அதற்காக, நீங்கள் தனிப்பயன் வடிவமைப்பு புலத்தில் X இன் ஒரு சரத்தை உள்ளிடுவீர்கள், நீங்கள் விரும்பும் வேறு எந்த எழுத்துகளையும் கொண்டு, பின்னர் Enter என்பதைக் கிளிக் செய்க. நீங்கள் X இன் செல்லுபடியாகும் எண்ணை உள்ளிட்டுள்ளீர்கள் என்பதை புலம் சரிபார்க்கும்: வெவ்வேறு கிரெடிட் கார்டுகள் வெவ்வேறு செல்லுபடியாகும் எழுத்து நீளங்களைக் கொண்டுள்ளன. தேர்ந்தெடுக்கப்பட்ட ஒவ்வொரு கிரெடிட் கார்டும் நீங்கள் உள்ளிட வேண்டிய எக்ஸ் எழுத்துக்களின் சரியான எண்ணிக்கையை உங்களுக்குத் தெரிவிக்கும்.",
+ "invalidNewFormat": "எக்ஸ் இன் சரியான எண்ணிக்கையைக் கொண்ட ஒரு சரத்தை உள்ளிடவும்.",
+ "noCreditCards": "கடன் அட்டைகள் எதுவும் தேர்ந்தெடுக்கப்படவில்லை."
+}
diff --git a/packages/plugins/src/dataTypes/PAN/i18n/zh.json b/packages/plugins/src/dataTypes/PAN/i18n/zh.json
new file mode 100644
index 000000000..fce07a73f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PAN/i18n/zh.json
@@ -0,0 +1,35 @@
+{
+ "NAME": "信用卡-PAN",
+ "DESC": "此数据类型以可自定义的格式生成随机的有效信用卡号。",
+ "DEFAULT_TITLE": "pan",
+ "mastercard": "Mastercard",
+ "visa": "Visa",
+ "visaElectron": "Visa Electron",
+ "amex": "American Express",
+ "discover": "Discover",
+ "carteBlanche": "Carte Blanche",
+ "dinersClubInt": "Diners Club International",
+ "dinersClubEnRoute": "Diners Club enRoute",
+ "maestro": "Maestro",
+ "solo": "Solo",
+ "switch": "Switch",
+ "laser": "Laser",
+ "anyCard": "任何卡",
+ "creditCard": "信用卡",
+ "creditCards": "信用卡",
+ "creditCardTypes": "信用卡种类",
+ "formats": "格式",
+ "multipleCards": "多张卡",
+ "specificCards": "特定卡",
+ "visaMastercard": "Visa, Mastercard",
+ "visaMastercardAmex": "Visa, Mastercard, Amex",
+ "validCardNumberSingleLength": "有效的%1数字必须包含%2X。",
+ "validCardNumberMultipleLengths": "有效的%1数字必须包含以下X之一:%2",
+ "selectCreditCards": "选择信用卡",
+ "formatsDesc": "这使您可以自定义上面选择的每张信用卡的显示格式。 您可以使用任意喜欢的字符定界符来添加自己的格式-只要输入的X的数量(自动转换为数字)对于该信用卡类型有效即可。",
+ "enterFormats": "输入信用卡格式",
+ "help1": "这将为全球许多主要信用卡生成有效的信用卡PAN(主要访问号码)。 默认情况下,它将从任何受支持的类型(Visa,Mastercard,American Express和其他类型)生成信用卡号,但是您可以选择所需的任何一种。",
+ "help2": "此外,您可以自定义生成数字的格式(例如,提供自定义字符定界符)。 为此,您需要在自定义格式字段中输入X字符串,并输入所需的其他任何字符,然后单击Enter。 该字段将验证您是否输入了有效的X:不同的信用卡具有不同的有效字符长度。 所选的每张信用卡都会告知您需要输入的有效X字符数。",
+ "invalidNewFormat": "请输入包含正确数量X的字符串。",
+ "noCreditCards": "未选择信用卡。"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/PIN.generate.ts b/packages/plugins/src/dataTypes/PIN/PIN.generate.ts
new file mode 100644
index 000000000..2a11da536
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/PIN.generate.ts
@@ -0,0 +1,8 @@
+import { WorkerUtils } from '../../';
+import { DTGenerateResult } from '~types/dataTypes';
+
+export const generate = (data: any, utils: WorkerUtils): DTGenerateResult => {
+ return {
+ display: utils.randomUtils.getRandomNum(1111, 9999)
+ };
+};
diff --git a/packages/plugins/src/dataTypes/PIN/PIN.state.tsx b/packages/plugins/src/dataTypes/PIN/PIN.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/PIN.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/dataTypes/PIN/PIN.tsx b/packages/plugins/src/dataTypes/PIN/PIN.tsx
new file mode 100644
index 000000000..dc6b0c800
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/PIN.tsx
@@ -0,0 +1,15 @@
+import * as React from 'react';
+import { DTHelpProps, DTMetadata } from '~types/dataTypes';
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.DESC}
;
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'number'
+ },
+ sql: {
+ field: 'varchar(4)',
+ field_Oracle: 'varchar2(4)',
+ field_MSSQL: 'VARCHAR(4) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/PIN/PIN.worker.ts b/packages/plugins/src/dataTypes/PIN/PIN.worker.ts
new file mode 100644
index 000000000..d7960956a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/PIN.worker.ts
@@ -0,0 +1,12 @@
+import utils from '../../../utils';
+import { generate } from './PIN.generate';
+import { DTWorkerOnMessage } from "~types/dataTypes";
+
+let workerUtilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+ postMessage(generate(undefined, utils));
+};
diff --git a/packages/plugins/src/dataTypes/PIN/README.md b/packages/plugins/src/dataTypes/PIN/README.md
new file mode 100644
index 000000000..3c8f59928
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/README.md
@@ -0,0 +1,34 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » PIN
+
+This Data Type generates a four character PIN.
+
+
+### Example API Usage
+
+Just POST the following JSON content to:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "PIN",
+ "title": "pin"
+ }
+ ],
+ "export": {
+ "type": "JSON",
+ "settings": {
+ "stripWhitespace": false,
+ "dataStructureFormat": "simple"
+ }
+ }
+}
+```
+
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/dataTypes/PIN/__tests__/PIN.generate.test.ts b/packages/plugins/src/dataTypes/PIN/__tests__/PIN.generate.test.ts
new file mode 100644
index 000000000..958dbe5d2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/__tests__/PIN.generate.test.ts
@@ -0,0 +1,25 @@
+import sinon from 'sinon';
+import { onmessage } from '../PIN.worker';
+import utils from '~utils/index';
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ it('generates random data', () => {
+ sinon.stub(utils.randomUtils, 'getRandomNum').returns(444);
+
+ const payload: any = {
+ data: {
+ workerUtilsUrl: ''
+ }
+ };
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: 444 });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/PIN/__tests__/PIN.ui.test.tsx b/packages/plugins/src/dataTypes/PIN/__tests__/PIN.ui.test.tsx
new file mode 100644
index 000000000..5477f152d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/__tests__/PIN.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../PIN';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/PIN/bundle.ts b/packages/plugins/src/dataTypes/PIN/bundle.ts
new file mode 100644
index 000000000..42c5c5d12
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/bundle.ts
@@ -0,0 +1,9 @@
+import { DTBundle } from '~types/dataTypes';
+import { Help, getMetadata } from './PIN';
+
+const bundle: DTBundle = {
+ Help,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/PIN/config.ts b/packages/plugins/src/dataTypes/PIN/config.ts
new file mode 100644
index 000000000..1607848f5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'financial',
+ fieldGroupOrder: 50
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/ar.json b/packages/plugins/src/dataTypes/PIN/i18n/ar.json
new file mode 100644
index 000000000..2f710fc31
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/ar.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "يُنشئ رقم PIN عشوائيًا لبطاقة الائتمان من 1111 إلى 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/de.json b/packages/plugins/src/dataTypes/PIN/i18n/de.json
new file mode 100644
index 000000000..4ed42be2f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/de.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "Erzeugt eine Zufallskreditkarte PIN-Nummer von 1111 bis 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/en.json b/packages/plugins/src/dataTypes/PIN/i18n/en.json
new file mode 100644
index 000000000..8e61f99f6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/en.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "Generates a random credit card PIN number from 1111 to 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/es.json b/packages/plugins/src/dataTypes/PIN/i18n/es.json
new file mode 100644
index 000000000..01ff963e3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/es.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "Genera un número PIN de la tarjeta de crédito azar de 1111 a 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/fr.json b/packages/plugins/src/dataTypes/PIN/i18n/fr.json
new file mode 100644
index 000000000..c48285214
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/fr.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "Génère un code PIN aléatoire de carte de crédit de 1111 à 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/hi.json b/packages/plugins/src/dataTypes/PIN/i18n/hi.json
new file mode 100644
index 000000000..f8cbca128
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/hi.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "1111 से 9999 तक एक यादृच्छिक क्रेडिट कार्ड पिन नंबर उत्पन्न करता है।",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/ja.json b/packages/plugins/src/dataTypes/PIN/i18n/ja.json
new file mode 100644
index 000000000..24115c4a8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/ja.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "1111から9999までのランダムなクレジットカードPIN番号を生成します。",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/nl.json b/packages/plugins/src/dataTypes/PIN/i18n/nl.json
new file mode 100644
index 000000000..b3022d664
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/nl.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "Genereert een willekeurig creditcard pincode van 1111 tot 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/pt.json b/packages/plugins/src/dataTypes/PIN/i18n/pt.json
new file mode 100644
index 000000000..b5b108252
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/pt.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "Gera um número PIN de cartão de crédito aleatório de 1111 a 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/ru.json b/packages/plugins/src/dataTypes/PIN/i18n/ru.json
new file mode 100644
index 000000000..05b876583
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/ru.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "Генерирует случайный PIN-код кредитной карты от 1111 до 9999.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/ta.json b/packages/plugins/src/dataTypes/PIN/i18n/ta.json
new file mode 100644
index 000000000..590fdf674
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/ta.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "சீரற்ற கிரெடிட் கார்டு பின் எண்ணை 1111 முதல் 9999 வரை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PIN/i18n/zh.json b/packages/plugins/src/dataTypes/PIN/i18n/zh.json
new file mode 100644
index 000000000..2d50ea10d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PIN/i18n/zh.json
@@ -0,0 +1,5 @@
+{
+ "NAME": "PIN",
+ "DESC": "生成1111到9999之间的随机信用卡PIN码。",
+ "DEFAULT_TITLE": "pin"
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.generate.ts b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.generate.ts
new file mode 100644
index 000000000..333469342
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.generate.ts
@@ -0,0 +1,5 @@
+import { DTGenerateResult } from '~types/dataTypes';
+
+export const generate = (): DTGenerateResult => {
+ return { display: '' };
+};
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.state.tsx b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.state.tsx
new file mode 100644
index 000000000..1fe0e737e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.state.tsx
@@ -0,0 +1,8 @@
+export const defaultGenerationOptions = {
+ separator: ' '
+};
+
+export const initialState = {
+ example: '',
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.tsx b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.tsx
new file mode 100644
index 000000000..1401fc649
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.tsx
@@ -0,0 +1,69 @@
+import * as React from 'react';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import Dropdown from '~components/dropdown/Dropdown';
+
+// TODO: figure out what this component IS. Is it for specific countries? Sweden has 12 chars...
+// https://en.wikipedia.org/wiki/Personal_identity_number_%28Sweden%29
+
+export const Example = ({ coreI18n, i18n, data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: any): void => {
+ onUpdate({
+ example: value,
+ value: value
+ });
+ };
+
+ const options = [
+ { value: '', label: coreI18n.pleaseSelect },
+ { value: 'PersonalNumberWithoutHyphen', label: i18n.example_PersonalNumberWithoutHyphen },
+ { value: 'PersonalNumberWithHyphen', label: i18n.example_PersonalNumberWithHyphen }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+export const Options = ({ i18n, data }: DTOptionsProps) => (
+
+ {i18n.separators}
+
+
+);
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+
+ {i18n.DESC}
+ {i18n.help_text}
+
+
+
+
+
+
+ PersonalNumberWithoutHyphen
+
+ {i18n.type_PersonalNumberWithoutHyphen}
+
+
+
+ PersonalNumberWithHyphen
+
+ {i18n.type_PersonalNumberWithHyphen}
+
+
+
+ >
+);
+
+export const getMetadata = (): DTMetadata => {
+ // Called before separator is set, so margin should be used
+ // $len = 12 + strlen(static::$sep);
+ const len = 13; // should be enough, allow for max one char sep
+ return {
+ sql: {
+ field: `varchar(${len}) default NULL`,
+ field_Oracle: `varchar2(${len}) default NULL`,
+ field_MSSQL: `VARCHAR(${len}) NULL`
+ }
+ };
+};
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.worker.ts b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.worker.ts
new file mode 100644
index 000000000..81c0bf02a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/PersonalNumber.worker.ts
@@ -0,0 +1,134 @@
+import { DTGenerateResult, DTMetadata } from '~types/dataTypes';
+
+export const generate = (): DTGenerateResult => {
+ return { display: '' };
+};
+
+/*
+ protected $isEnabled = true;
+ protected $dataTypeName = "PersonalNumber";
+ protected $dataTypeFieldGroup = "humanData";
+ protected $dataTypeFieldGroupOrder = 110;
+ protected $jsModules = array("PersonalNumber.js");
+ private $generatedPersonnrs = array();
+
+ // Separator in personal number
+ static $sep = "-";
+
+
+ // Generate a random personal number, and return the display string and additional meta data for use
+ // by any other Data Type.
+ public function generate($generator, $generationContextData) {
+ $generationOptions = $generationContextData["generationOptions"];
+
+ // Default, 12 siffers + '-'
+ // TODO: Option for 12 siffers without '-'
+ // TODO: more options? (not 10 siffers since it could generate real personal number)
+ // TODO: support several countries?
+ static::$sep = self::getPersonalNumberSeparator($generationOptions["cc_separator"]);
+ $personnr = $this->generateRandomSwedishPersonalNumber(static::$sep);
+
+ // pretty sodding unlikely, but just in case!
+ while (in_array($personnr, $this->generatedPersonnrs)) {
+ $personnr = $this->generateRandomSwedishPersonalNumber(static::$sep);
+ }
+ $this->generatedPersonnrs[] = $personnr;
+ return array(
+ "display" => $personnr
+ );
+ }
+
+ // TODO: add support for separator
+ // TODO: add support for organisation numbers
+ private static function generateRandomSwedishPersonalNumber($sep) {
+ $new_str = "16";
+ $cnt = 13; // 12 siffers + 1 increment for separator
+
+ for ($i=2; $i<$cnt; $i++) {
+ switch ($i) {
+ case 2:
+ $rand = mt_rand(0, 99);
+ $new_str .= sprintf("%02d", $rand);
+ break;
+ case 4:
+ $rand = mt_rand(1, 12);
+ $new_str .= sprintf("%02d", $rand);
+ break;
+ case 6:
+ $rand = mt_rand(1, 30);
+ $new_str .= sprintf("%02d", $rand);
+ break;
+ case 8:
+ $new_str .= $sep;
+ break;
+ case 9:
+ $rand = mt_rand(0, 999);
+ $new_str .= sprintf("%03d", $rand);
+ break;
+ case 12:
+ $ctrl = static::recalcCtrl($new_str . "0", $sep);
+ $new_str .= sprintf("%01d", $ctrl);
+ break;
+ default:
+ break;
+ }
+ }
+
+ return $new_str;
+ }
+
+ // Function to recalculate control siffer in swedish personal number
+ public static function recalcCtrl($idNumber, $separator) {
+ $strArr = explode($separator, $idNumber);
+ $idNr = "";
+ for ($i=0; $i= 10) {
+ $partSum = (int)($partSum / 10) + ($partSum % 10);
+ }
+ } else {
+ $partSum = intval($idNrArr[$i]);
+ }
+ $sum += $partSum;
+ }
+
+ $ctrl = (10 - ($sum % 10)) % 10;
+
+ return $ctrl;
+ }
+
+
+ private static function getPersonalNumberSeparator($separators) {
+ $separatorList = explode("|", $separators);
+ $chosenSep = $separatorList[rand(0, count($separatorList)-1)];
+
+ // if no separator was entered use '' as default
+ if ($separators == "") {
+ $chosenSep = "";
+ }
+ return $chosenSep;
+ }
+
+ public function getRowGenerationOptionsUI($generator, $postdata, $colNum, $numCols) {
+ return array(
+ "cc_separator" => $postdata["dtOptionPersonalNumber_sep_$colNum"]
+ );
+ }
+
+*/
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/README.md b/packages/plugins/src/dataTypes/PersonalNumber/README.md
new file mode 100644
index 000000000..364424000
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/README.md
@@ -0,0 +1,4 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » PersonalNumber
+
+This Data Type generates a random personal number, used in some countries for social security insurance.
+
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/__tests__/PersonalNumber.test.tsx b/packages/plugins/src/dataTypes/PersonalNumber/__tests__/PersonalNumber.test.tsx
new file mode 100644
index 000000000..2dba02b6d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/__tests__/PersonalNumber.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../PersonalNumber';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/bundle.ts b/packages/plugins/src/dataTypes/PersonalNumber/bundle.ts
new file mode 100644
index 000000000..0d0f07c16
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/bundle.ts
@@ -0,0 +1,13 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, getMetadata } from './PersonalNumber';
+import { initialState } from './PersonalNumber.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/config.ts b/packages/plugins/src/dataTypes/PersonalNumber/config.ts
new file mode 100644
index 000000000..19a79dca0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'countrySpecific',
+ fieldGroupOrder: 30
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/ar.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ar.json
new file mode 100644
index 000000000..99f71d30a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ar.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Personal Number",
+ "DESC": "Generates a personal number, used in some countries for social security insurance.",
+ "DEFAULT_TITLE": "number",
+ "help_text": "At the present time only swedish ones are supported. The personal numbers are generated according to the format you specify:",
+ "incomplete_fields": "The PersonalNumber data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "A (swedish) personal number with 12 siffers and no hyphen",
+ "type_PersonalNumberWithHyphen": "A (swedish) personal number with 12 siffers and an hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the personal number fields. Note: you can mix several separators, use | to separate them (for example -|+)."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/de.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/de.json
new file mode 100644
index 000000000..6d4bf6b9c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/de.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Persönliche Nummer",
+ "DESC": "Erzeugt persönliche Nummer , in einigen Ländern für die Sozialversicherung verwendet .",
+ "DEFAULT_TITLE": "number",
+ "help_text": "In der heutigen Zeit nur schwedisch diejenigen unterstützt werden. Die persönlichen Nummern werden nach dem Format, das Sie angeben, generiert:",
+ "incomplete_fields": "Die PersonalNumber Datentyp muss den im Textfeld eingegebenen Optionen Format haben . Bitte korrigieren Sie die folgenden Zeilen :",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "A ( schwedisch ) persönliche Nummer mit 12 siffers und ohne Bindestrich",
+ "type_PersonalNumberWithHyphen": "A ( schwedisch ) persönliche Nummer mit 12 siffers und einem Bindestrich",
+ "separators": "Abscheider :",
+ "separator_help": "Die Zeichen, die Sie hier eingeben, wird als Trennzeichen in den persönlichen Zahlenfeldern verwendet werden. Hinweis: Sie können mehrere Trennzeichen zu mischen, zu verwenden | um sie zu trennen (zum Beispiel - | +) ."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/en.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/en.json
new file mode 100644
index 000000000..9ef8265e4
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/en.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Swedish Personal Number",
+ "DESC": "Generates a personal number, used in some countries for social security insurance.",
+ "DEFAULT_TITLE": "number",
+ "help_text": "The personal numbers are generated according to the format you specify:",
+ "incomplete_fields": "The PersonalNumber data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "A (swedish) personal number with 12 siffers and no hyphen",
+ "type_PersonalNumberWithHyphen": "A (swedish) personal number with 12 siffers and an hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the personal number fields. Note: you can mix several separators, use | to separate them (for example -|+)."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/es.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/es.json
new file mode 100644
index 000000000..c2fe0449f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/es.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Número personal",
+ "DESC": "Genera número personal , que se utiliza en algunos países para el seguro de la seguridad social.",
+ "DEFAULT_TITLE": "number",
+ "help_text": "En la actualidad sólo se admiten los suecos . Los números personales se generan de acuerdo con el formato que se especifica :",
+ "incomplete_fields": "El tipo de datos PersonalNumber necesita tener el formato especificado en el campo de texto Opciones . Por favor, corrija los siguientes filas :",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "Un número personal ( sueco ) con 12 siffers y sin guión",
+ "type_PersonalNumberWithHyphen": "Un número personal ( sueco ) con 12 siffers y un guión",
+ "separators": "Separadores :",
+ "separator_help": "Los caracteres que introduzca aquí se utilizará como separador de los campos de número de personales . Nota: usted puede mezclar varios separadores , utilice | para separarlos ( por ejemplo - | +) ."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/fr.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/fr.json
new file mode 100644
index 000000000..ebf802064
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/fr.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Numéro personnel",
+ "DESC": "Génère numéro personnel , utilisé dans certains pays pour l'assurance de la sécurité sociale .",
+ "DEFAULT_TITLE": "number",
+ "help_text": "À l'heure actuelle seuls suédois sont pris en charge . Les numéros personnels sont générées selon le format que vous spécifiez :",
+ "incomplete_fields": "Le type de données PersonalNumber doit avoir le format entré dans le champ de texte Options. S'il vous plaît fixer les lignes suivantes :",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "Un ( swedish ) numéro personnel avec 12 siffers et sans trait d'union",
+ "type_PersonalNumberWithHyphen": "Un ( swedish ) numéro personnel avec 12 siffers et un trait d'union",
+ "separators": "Séparateurs :",
+ "separator_help": "Les caractères que vous entrez ici seront utilisés comme séparateur dans les champs numériques personnels . Remarque : vous pouvez mélanger plusieurs séparateurs , utilisez | pour les séparer ( par exemple - | + ) ."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/hi.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/hi.json
new file mode 100644
index 000000000..8cdbcfc59
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/hi.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "स्वीडिश व्यक्तिगत नंबर",
+ "DESC": "कुछ देशों में सामाजिक सुरक्षा बीमा के लिए उपयोग किया जाने वाला एक व्यक्तिगत नंबर बनाता है।",
+ "DEFAULT_TITLE": "number",
+ "help_text": "व्यक्तिगत नंबर आपके द्वारा निर्दिष्ट प्रारूप के अनुसार उत्पन्न होते हैं:",
+ "incomplete_fields": "PersonalNumber डेटा प्रकार के लिए विकल्प टेक्स्ट फ़ील्ड में प्रारूप दर्ज होना आवश्यक है। कृपया निम्न पंक्तियों को ठीक करें:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "A (स्वीडिश) व्यक्तिगत नंबर जिसमें 12 सिफ़र हैं और कोई हाइफ़न नहीं है",
+ "type_PersonalNumberWithHyphen": "A (स्वीडिश) १२ सिफ़र्स और एक हाइफ़न के साथ व्यक्तिगत नंबर",
+ "separators": "विभाजक:",
+ "separator_help": "आपके द्वारा यहां दर्ज किए गए वर्ण व्यक्तिगत संख्या फ़ील्ड में विभाजक के रूप में उपयोग किए जाएंगे। नोट: आप कई विभाजकों को मिला सकते हैं, उपयोग करें | उन्हें अलग करने के लिए (उदाहरण के लिए -|+)।"
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/ja.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ja.json
new file mode 100644
index 000000000..99f71d30a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ja.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Personal Number",
+ "DESC": "Generates a personal number, used in some countries for social security insurance.",
+ "DEFAULT_TITLE": "number",
+ "help_text": "At the present time only swedish ones are supported. The personal numbers are generated according to the format you specify:",
+ "incomplete_fields": "The PersonalNumber data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "A (swedish) personal number with 12 siffers and no hyphen",
+ "type_PersonalNumberWithHyphen": "A (swedish) personal number with 12 siffers and an hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the personal number fields. Note: you can mix several separators, use | to separate them (for example -|+)."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/nl.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/nl.json
new file mode 100644
index 000000000..53d82f3ba
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/nl.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Persoonsnummer",
+ "DESC": "Genereert persoonlijke nummers die in sommige landen worden gebruikt voor de sociale verzekering .",
+ "DEFAULT_TITLE": "number",
+ "help_text": "Op dit moment worden alleen de Zweedse persoonsnummers ondersteund. De persoonlijke nummers worden gegenereerd volgens het formaat dat u opgeeft :",
+ "incomplete_fields": "U moet in het Optieveld het formaat opgeven. Corrigeer de volgende rijen :",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "Een (Zweeds) persoonsnummer van 12 cijferszonder koppelteken",
+ "type_PersonalNumberWithHyphen": "Een (Zweeds) persoonsnummer van 12 cijfers met eenkoppelteken",
+ "separators": "Scheidingstekens :",
+ "separator_help": "De tekens die u hier invoert worden gebruikt als scheidingsteken in het persoonnummer. Let op : je kunt meerdere schedingstekens tegelijkertijd gebruiken. Scheiden door een | teken. Bijvoorbeeld - | + ."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/pt.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/pt.json
new file mode 100644
index 000000000..396bc10af
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/pt.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Número pessoal sueco",
+ "DESC": "Gera um número pessoal, usado em alguns países para seguro de previdência social.",
+ "DEFAULT_TITLE": "númeropessoal",
+ "help_text": "Os números pessoais são gerados de acordo com o formato que você especificar:",
+ "incomplete_fields": "O tipo de dados PersonalNumber precisa ter o formato inserido no campo de texto Opções. Corrija as seguintes linhas:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "Um número pessoal (sueco) com 12 siffers e nenhum hífen",
+ "type_PersonalNumberWithHyphen": "Um número pessoal (sueco) com 12 siffers e um hífen",
+ "separators": "Separadores:",
+ "separator_help": "Os caracteres inseridos aqui serão usados como separadores nos campos de números pessoais. Nota: você pode misturar vários separadores, use | para separá-los (por exemplo - | +)."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/ru.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ru.json
new file mode 100644
index 000000000..4ca343028
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ru.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Шведский личный номер",
+ "DESC": "Генерирует личный номер, используемый в некоторых странах для социального страхования.",
+ "DEFAULT_TITLE": "number",
+ "help_text": "Персональные номера генерируются в соответствии с указанным вами форматом:",
+ "incomplete_fields": "Тип данных PersonalNumber должен иметь формат, введенный в текстовом поле Options. Исправьте следующие строки:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "(Шведский) личный номер с 12 сифферами и без дефиса.",
+ "type_PersonalNumberWithHyphen": "(Шведский) личный номер с 12 сифферами и дефисом.",
+ "separators": "Сепараторы:",
+ "separator_help": "Введенные здесь символы будут использоваться в качестве разделителя в полях личного номера. Примечание: вы можете смешивать несколько разделителей, используйте | чтобы разделить их (например, -|+)."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/ta.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ta.json
new file mode 100644
index 000000000..99f71d30a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/ta.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Personal Number",
+ "DESC": "Generates a personal number, used in some countries for social security insurance.",
+ "DEFAULT_TITLE": "number",
+ "help_text": "At the present time only swedish ones are supported. The personal numbers are generated according to the format you specify:",
+ "incomplete_fields": "The PersonalNumber data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "A (swedish) personal number with 12 siffers and no hyphen",
+ "type_PersonalNumberWithHyphen": "A (swedish) personal number with 12 siffers and an hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the personal number fields. Note: you can mix several separators, use | to separate them (for example -|+)."
+}
diff --git a/packages/plugins/src/dataTypes/PersonalNumber/i18n/zh.json b/packages/plugins/src/dataTypes/PersonalNumber/i18n/zh.json
new file mode 100644
index 000000000..99f71d30a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PersonalNumber/i18n/zh.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Personal Number",
+ "DESC": "Generates a personal number, used in some countries for social security insurance.",
+ "DEFAULT_TITLE": "number",
+ "help_text": "At the present time only swedish ones are supported. The personal numbers are generated according to the format you specify:",
+ "incomplete_fields": "The PersonalNumber data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "example_PersonalNumberWithoutHyphen": "167502241016",
+ "example_PersonalNumberWithHyphen": "16750224-1016",
+ "type_PersonalNumberWithoutHyphen": "A (swedish) personal number with 12 siffers and no hyphen",
+ "type_PersonalNumberWithHyphen": "A (swedish) personal number with 12 siffers and an hyphen",
+ "separators": "Separators:",
+ "separator_help": "The characters you enter here will be used as separator in the personal number fields. Note: you can mix several separators, use | to separate them (for example -|+)."
+}
diff --git a/packages/plugins/src/dataTypes/Phone/Phone.generate.ts b/packages/plugins/src/dataTypes/Phone/Phone.generate.ts
new file mode 100644
index 000000000..f1bfdd7ba
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/Phone.generate.ts
@@ -0,0 +1,12 @@
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = ({ rowState }: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ // for backward compatibility
+ const value = Array.isArray(rowState) ? rowState : rowState.option;
+ const item: string = utils.randomUtils.getRandomArrayValue(value);
+
+ return {
+ display: utils.randomUtils.generateRandomAlphanumericStr(item)
+ };
+};
diff --git a/packages/plugins/src/dataTypes/Phone/Phone.state.tsx b/packages/plugins/src/dataTypes/Phone/Phone.state.tsx
new file mode 100755
index 000000000..cc1b8c704
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/Phone.state.tsx
@@ -0,0 +1,17 @@
+export type PhoneState = {
+ example: string;
+ option: string[];
+};
+
+export type GenerationOptionsType = {
+ option: string[];
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ option: ['1-Xxx-Xxx-xxxx', '(Xxx) Xxx-xxxx']
+};
+
+export const initialState: PhoneState = {
+ example: '1-Xxx-Xxx-xxxx|(Xxx) Xxx-xxxx',
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/Phone/Phone.tsx b/packages/plugins/src/dataTypes/Phone/Phone.tsx
new file mode 100755
index 000000000..c7c8ff652
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/Phone.tsx
@@ -0,0 +1,55 @@
+import * as React from 'react';
+import Dropdown from '~components/dropdown/Dropdown';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import CreatablePillField from '~components/creatablePillField/CreatablePillField';
+import { PhoneState, GenerationOptionsType } from './Phone.state';
+
+export const rowStateReducer = (state: PhoneState): GenerationOptionsType => ({ option: state.option });
+
+export const Example = ({ i18n, data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: any): void => {
+ onUpdate({
+ example: value,
+ option: value.split('|')
+ });
+ };
+
+ const options = [
+ { value: '1-Xxx-Xxx-xxxx|(Xxx) Xxx-xxxx', label: i18n.northAmerica },
+ {
+ value:
+ '(01xxxx) xxxxx|(01xxx) xxxxxx|(01x1) xxx xxxx|(011x) xxx xxxx|(02x) xxxx xxxx|03xx xxx xxxx|055 xxxx xxxx|056 xxxx xxxx|070 xxxx xxxx|07624 xxxxxx|076 xxxx xxxx|07xxx xxxxxx|0800 xxx xxxx|08xx xxx xxxx|09xx xxx xxxx|(016977) xxxx|(01xxx) xxxxx|0500 xxxxxx|0800 xxxxxx|0800 1111|0845 46 4x',
+ label: i18n.uk
+ },
+ { value: '0X xx xx xx xx', label: i18n.france },
+ { value: '(0X) xxxx xxxx', label: i18n.australia },
+ { value: '(0xx) xxxxxxxx|(0xxx) xxxxxxxx|(0xxxx) xxxxxxx|(03xxxx) xxxxxx', label: i18n.germany },
+ { value: '0xx-xxx-xxxx', label: i18n.japan },
+ { value: '1-Xxx-Xxx-xxxx|Xxx-xxxx', label: i18n.differentFormats }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+export const Options = ({ data, coreI18n, onUpdate }: DTOptionsProps) => (
+ onUpdate({ ...data, option: values })}
+ />
+);
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+
+
+ >
+);
+
+export const getMetadata = (): DTMetadata => ({
+ sql: {
+ field: 'varchar(100) default NULL',
+ field_Oracle: 'varchar2(100) default NULL',
+ field_MSSQL: 'VARCHAR(100) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/Phone/Phone.worker.ts b/packages/plugins/src/dataTypes/Phone/Phone.worker.ts
new file mode 100644
index 000000000..84ff39c22
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/Phone.worker.ts
@@ -0,0 +1,13 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from "~types/dataTypes";
+import { generate } from './Phone.generate';
+
+let workerUtilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/Phone/README.md b/packages/plugins/src/dataTypes/Phone/README.md
new file mode 100644
index 000000000..8e1be1617
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/README.md
@@ -0,0 +1,116 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Phone
+
+This Data Type generates a phone number with a simple search-replace algorithm. Any `X`'s in the placeholder
+string are replaced with 1-9; any `x`'s (lowercase) are replaced with 0-9.
+
+
+## Examples
+
+- [Generate a random phone number in a single format](#generate-a-random-phone-number-in-a-single-format)
+- [Generate a random phone number in multiple formats](#generate-a-random-phone-number-in-multiple-formats)
+
+### Generate a random phone number in a single format
+
+```typescript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Phone',
+ title: 'phoneNum',
+ settings: {
+ option: ['(Xxx) Xxx-xxxx']
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: ExportType.JSON,
+ settings: {
+ dataStructureFormat
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "phoneNum": "(143) 454-2481"
+ },
+ {
+ "phoneNum": "(988) 411-7252"
+ },
+ {
+ "phoneNum": "(340) 460-3834"
+ },
+ {
+ "phoneNum": "(380) 685-3692"
+ },
+ {
+ "phoneNum": "(337) 643-8780"
+ },
+ {
+ "phoneNum": "(344) 746-2939"
+ },
+ ...
+]
+```
+
+### Generate a random phone number in multiple formats
+
+```typescript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Phone',
+ title: 'phoneNum',
+ settings: {
+ option: [
+ '(Xxx) Xxx-xxxx',
+ '1 (Xxx) Xxx-xxxx',
+ 'Xxx Xxx-xxxx'
+ ]
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+[
+ {
+ "phoneNum": "882 579-5511"
+ },
+ {
+ "phoneNum": "(237) 433-6484"
+ },
+ {
+ "phoneNum": "(244) 278-9347"
+ },
+ {
+ "phoneNum": "1 (927) 607-4334"
+ },
+ {
+ "phoneNum": "553 957-8737"
+ },
+ {
+ "phoneNum": "302 923-7126"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/Phone/__tests__/Phone.generate.test.ts b/packages/plugins/src/dataTypes/Phone/__tests__/Phone.generate.test.ts
new file mode 100644
index 000000000..ec8cf5fda
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/__tests__/Phone.generate.test.ts
@@ -0,0 +1,30 @@
+import sinon from 'sinon';
+import { onmessage } from '../Phone.worker';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+import utils from '~utils/index';
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ it('generates random data', () => {
+ const payload: any = {
+ data: {
+ ...getBlankDTGeneratorPayload(),
+ rowState: {
+ option: 'Xxx-xxxx'
+ }
+ }
+ };
+
+ sinon.stub(utils.randomUtils, 'getRandomArrayValue').returns(0);
+ sinon.stub(utils.randomUtils, 'generateRandomAlphanumericStr').returns('converted');
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: 'converted' });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Phone/__tests__/Phone.ui.test.tsx b/packages/plugins/src/dataTypes/Phone/__tests__/Phone.ui.test.tsx
new file mode 100644
index 000000000..8d7c26f75
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/__tests__/Phone.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../Phone';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Phone/bundle.ts b/packages/plugins/src/dataTypes/Phone/bundle.ts
new file mode 100644
index 000000000..7cc8a0091
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, rowStateReducer, getMetadata } from './Phone';
+import { initialState } from './Phone.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ rowStateReducer,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Phone/config.ts b/packages/plugins/src/dataTypes/Phone/config.ts
new file mode 100644
index 000000000..452499b09
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'humanData',
+ fieldGroupOrder: 20
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/ar.json b/packages/plugins/src/dataTypes/Phone/i18n/ar.json
new file mode 100644
index 000000000..f10a5e0ec
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/ar.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "الهاتف / الفاكس",
+ "DESC": "يولد رقم هاتف / فاكس عشوائيًا بتنسيقات متنوعة لبلدان / مناطق مختلفة.",
+ "DEFAULT_TITLE": "هاتف",
+ "differentFormats": "تنسيقات مختلفة",
+ "northAmerica": "شمال امريكا",
+ "helpText1": "أيًا كان النص الذي تدخله في حقل نص الخيارات ، فسيتم استخدامه لإنشاء أرقام هواتف. سيتم تحويل رأس المال X إلى رقم عشوائي بين 1 و 9 ؛ سيتم تحويل أحرف x الصغيرة إلى رقم عشوائي بين 0 و 9.",
+ "helpText2": "حدد إحدى القيم في أمثلة القائمة المنسدلة لبعض الأفكار. تذكر: أي شيء بخلاف الحرفين x و X يتم تركهما بدون تحويل",
+ "incompleteFields": "يحتاج نوع بيانات الهاتف / الفاكس إلى إدخال التنسيق في حقل نص الخيارات. الرجاء إصلاح الصفوف التالية:",
+ "uk": "المملكة المتحدة",
+ "france": "فرنسا",
+ "germany": "ألمانيا",
+ "australia": "أستراليا",
+ "japan": "اليابان"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/de.json b/packages/plugins/src/dataTypes/Phone/i18n/de.json
new file mode 100644
index 000000000..451805332
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/de.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Telefon / Fax",
+ "DESC": "Erzeugt eine Zufalls Telefon- / Faxnummer in einer Vielzahl von Formaten für verschiedene Länder / Regionen .",
+ "DEFAULT_TITLE": "telefon",
+ "australia": "Australien",
+ "differentFormats": "Unterschiedliche Formate",
+ "northAmerica": "Nordamerika",
+ "france": "Frankreich",
+ "germany": "Deutschland",
+ "helpText1": "Was Text Sie in den Optionen Textfeld eingeben werden verwendet, um Telefonnummern zu generieren. Kapital X 's zu einer Zufallszahl zwischen 1 und 9 umgewandelt werden; Kleinbuchstaben x 's zu einer Zufallszahl umgewandelt werden zwischen 0 und 9.",
+ "helpText2": "Wählen Sie einen der Werte im Beispiel Dropdown für einige Ideen. Denken Sie daran: alles andere als die X und x -Zeichen übrig unbekehrt.",
+ "incompleteFields": "The Phone / Fax Datentyp haben muss das Format eingetragen im Options Textfeld. Bitte beheben Sie die folgenden Zeilen:",
+ "japan": "Japan",
+ "uk": "Vereinigtes Königreich"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/en.json b/packages/plugins/src/dataTypes/Phone/i18n/en.json
new file mode 100644
index 000000000..a1c7f0c95
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/en.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Phone / Fax",
+ "DESC": "Generates a random phone/fax number in a variety of formats for different countries/regions.",
+ "DEFAULT_TITLE": "phone",
+ "differentFormats": "Different formats",
+ "northAmerica": "North America",
+ "helpText1": "Whatever text you enter into the options text field will be used to generate telephone numbers. Uppercase X 's will be converted to a random number between 1 and 9; lowercase x 's will be converted to a random number between 0 and 9.",
+ "helpText2": "Select one of the values in the example dropdown for some ideas. Remember: any other characters besides X and x will be left unconverted.",
+ "incompleteFields": "The Phone / Fax data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "uk": "UK",
+ "france": "France",
+ "germany": "Germany",
+ "australia": "Australia",
+ "japan": "Japan"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/es.json b/packages/plugins/src/dataTypes/Phone/i18n/es.json
new file mode 100644
index 000000000..3616fea30
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/es.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Teléfono / Fax",
+ "DESC": "Genera un número de teléfono / fax al azar en una variedad de formatos para diferentes países / regiones.",
+ "DEFAULT_TITLE": "telefono",
+ "differentFormats": "Formatos diferentes",
+ "northAmerica": "Norteamérica",
+ "helpText1": "Cualquier texto que introduces en el campo de texto de las opciones será usado para generar números de teléfono. Una X mayúscula será convertida a un número aleatorio entre 1 y 9; Una x minúscula será convertida a un número entre 0 y 9.",
+ "helpText2": "Elije uno de los valores del desplegable de ejemplos para ver algunas ideas. Recuerda: cualquier cosa distinta de los caracteres X y x no será convertida.",
+ "incompleteFields": "El tipo de dato Teléfono requiere informar el formato en el campo de texto Opciones. Por favor, arregla las siguientes filas:",
+ "uk": "Reino Unido",
+ "france": "Francia",
+ "germany": "Alemania",
+ "australia": "Australia",
+ "japan": "Japón"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/fr.json b/packages/plugins/src/dataTypes/Phone/i18n/fr.json
new file mode 100644
index 000000000..c5c7fc898
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/fr.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Téléphone / Fax",
+ "DESC": "Génère un numéro de téléphone / fax aléatoire dans une variété de formats pour les différents pays / régions .",
+ "DEFAULT_TITLE": "telephone",
+ "australia": "Australie",
+ "differentFormats": "Différents formats",
+ "northAmerica": "Amérique du Nord",
+ "france": "France",
+ "germany": "Allemagne",
+ "helpText1": "Quel que soit le texte que vous saisissez dans le champ de texte options, il sera utilisé pour générer des numéros de téléphone. Les X majuscules seront converties en un nombre aléatoire compris entre 1 et 9 et les x minuscules seront converties en un nombre aléatoire entre 0 et 9.",
+ "helpText2": "Sélectionnez l'une des valeurs de l'exemple pour avoir une idée du fonctionnement. Rappelez-vous: rien d'autre que les caractères X et x ne sera converti.",
+ "incompleteFields": "Le type de téléphone / fax de données doit avoir un format précisé dans le champ de texte Options. Corrigez les lignes suivantes:",
+ "japan": "Japon",
+ "uk": "Royaume-Uni"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/hi.json b/packages/plugins/src/dataTypes/Phone/i18n/hi.json
new file mode 100644
index 000000000..1359060c8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/hi.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "फोन / फैक्स",
+ "DESC": "विभिन्न देशों/क्षेत्रों के लिए विभिन्न स्वरूपों में एक यादृच्छिक फोन/फैक्स नंबर उत्पन्न करता है।",
+ "DEFAULT_TITLE": "फ़ोन",
+ "differentFormats": "विभिन्न प्रारूप",
+ "northAmerica": "उत्तरी अमेरिका",
+ "helpText1": "आप विकल्प टेक्स्ट फ़ील्ड में जो भी टेक्स्ट दर्ज करते हैं, उसका उपयोग टेलीफ़ोन नंबर जेनरेट करने के लिए किया जाएगा। अपरकेस X को 1 और 9 के बीच एक यादृच्छिक संख्या में बदल दिया जाएगा; लोअरकेस x को 0 और 9 के बीच एक यादृच्छिक संख्या में बदल दिया जाएगा।",
+ "helpText2": "कुछ उपायों के लिए उदाहरण ड्रॉपडाउन में से किसी एक मान का चयन करें। याद रखें: X और x के अलावा कोई अन्य वर्ण अपरिवर्तित छोड़ दिया जाएगा।",
+ "incompleteFields": "फ़ोन / फ़ैक्स डेटा प्रकार को विकल्प टेक्स्ट फ़ील्ड में प्रारूप दर्ज करने की आवश्यकता है। कृपया निम्न पंक्तियों को ठीक करें:",
+ "uk": "यूके",
+ "france": "फ्रांस",
+ "germany": "जर्मनी",
+ "australia": "ऑस्ट्रेलिया",
+ "japan": "जापान"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/ja.json b/packages/plugins/src/dataTypes/Phone/i18n/ja.json
new file mode 100644
index 000000000..a78f6ced4
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/ja.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "電話/ファックス",
+ "DESC": "国/地域ごとにさまざまな形式でランダムな電話/ファックス番号を生成します。",
+ "DEFAULT_TITLE": "電話",
+ "differentFormats": "さまざまな形式",
+ "northAmerica": "北米",
+ "helpText1": "オプションテキストフィールドに入力したテキストはすべて、電話番号の生成に使用されます。 大文字のXは、1から9までの乱数に変換されます。 小文字のxは、0から9までの乱数に変換されます。",
+ "helpText2": "いくつかのアイデアについては、例のドロップダウンで値の1つを選択してください。 覚えておいてください:Xとx文字以外は変換されないままです。",
+ "incompleteFields": "電話/ファックスのデータ型では、[オプション]テキストフィールドに形式を入力する必要があります。 次の行を修正してください。",
+ "uk": "英国",
+ "france": "フランス",
+ "germany": "ドイツ",
+ "australia": "オーストラリア",
+ "japan": "日本"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/nl.json b/packages/plugins/src/dataTypes/Phone/i18n/nl.json
new file mode 100644
index 000000000..095d648a3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/nl.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Telefoon / Faxnummer",
+ "DESC": "Genereert een willekeurig telefoonnummer of faxnummer in een verscheidenheid van formaten voor verschillende landen of regio's.",
+ "DEFAULT_TITLE": "telefoon",
+ "australia": "Australië",
+ "differentFormats": "Verschillende formaten",
+ "northAmerica": "Noord Amerika",
+ "france": "Frankrijk",
+ "germany": "Duitsland",
+ "helpText1": "De tekst/tekens die u in het optieveld invoerd worden gebruikt voor het genereren van de nummers. Hoofdletter X zal worden omgezet in een willekeurig getal tussen 1 en 9; Kleine letter x ' zal worden omgezet naar een willekeurig getal tussen 0 en 9.",
+ "helpText2": "In de dropdown staan verschillende voorbeelden. Vergeet niet: iets anders dan de X en x karakter worden 1 op 1 overgenomen.",
+ "incompleteFields": "U moet een waarde invoeren in het Optieveld. Pas de volgende rij(en) aan:",
+ "japan": "Japan",
+ "uk": "UK"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/pt.json b/packages/plugins/src/dataTypes/Phone/i18n/pt.json
new file mode 100644
index 000000000..c49ee3265
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/pt.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Telefone / Fax",
+ "DESC": "Gera um número de telefone / fax aleatório em uma variedade de formatos para diferentes países / regiões.",
+ "DEFAULT_TITLE": "telefone",
+ "differentFormats": "Formatos diferentes",
+ "northAmerica": "América do Norte",
+ "helpText1": "Qualquer texto inserido no campo de texto de opções será usado para gerar números de telefone. X em maiúsculas serão convertidos em um número aleatório entre 1 e 9; x minúsculos serão convertidos em um número aleatório entre 0 e 9.",
+ "helpText2": "Selecione um dos valores no menu suspenso de exemplo para algumas ideias. Lembre-se: quaisquer outros caracteres além de X e x não serão convertidos.",
+ "incompleteFields": "O tipo de dados Telefone / Fax precisa ter o formato inserido no campo de texto Opções. Corrija as seguintes linhas:",
+ "uk": "Reino Unido",
+ "france": "França",
+ "germany": "Alemanha",
+ "australia": "Austrália",
+ "japan": "Japão"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/ru.json b/packages/plugins/src/dataTypes/Phone/i18n/ru.json
new file mode 100644
index 000000000..d754afed1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/ru.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Телефон/факс",
+ "DESC": "Генерирует случайный номер телефона/факса в различных форматах для разных стран/регионов.",
+ "DEFAULT_TITLE": "Телефон",
+ "differentFormats": "Различные форматы",
+ "northAmerica": "Северная Америка",
+ "helpText1": "Какой бы текст вы ни ввели в текстовое поле параметров, он будет использоваться для генерации телефонных номеров. Прописные X будут преобразованы в случайное число от 1 до 9; строчные x будут преобразованы в случайное число от 0 до 9.",
+ "helpText2": "Выберите одно из значений в раскрывающемся списке примеров для некоторых идей. Помните: любые другие символы, кроме X и x, не будут преобразованы.",
+ "incompleteFields": "Тип данных «Телефон/факс» должен иметь формат, введенный в текстовом поле «Параметры». Исправьте следующие строки:",
+ "uk": "Великобритания",
+ "france": "Франция",
+ "germany": "Германия",
+ "australia": "Австралия",
+ "japan": "Япония"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/ta.json b/packages/plugins/src/dataTypes/Phone/i18n/ta.json
new file mode 100644
index 000000000..c8bf6ad62
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/ta.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "தொலைபேசி / தொலைநகல்",
+ "DESC": "வெவ்வேறு நாடுகள் / பிராந்தியங்களுக்கான பல்வேறு வடிவங்களில் சீரற்ற தொலைபேசி / தொலைநகல் எண்ணை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "தொலைபேசி",
+ "differentFormats": "வெவ்வேறு வடிவங்கள்",
+ "northAmerica": "வட அமெரிக்கா",
+ "helpText1": "தொலைபேசி எண்களை உருவாக்க விருப்பங்கள் உரை புலத்தில் நீங்கள் எந்த உரையை உள்ளிடுகிறீர்கள். மூலதன எக்ஸ் 1 மற்றும் 9 க்கு இடையில் ஒரு சீரற்ற எண்ணாக மாற்றப்படும்; லோயர்-கேஸ் x கள் 0 மற்றும் 9 க்கு இடையில் ஒரு சீரற்ற எண்ணாக மாற்றப்படும்.",
+ "helpText2": "சில யோசனைகளுக்கு எடுத்துக்காட்டு கீழ்தோன்றலில் உள்ள மதிப்புகளில் ஒன்றைத் தேர்ந்தெடுக்கவும். நினைவில் கொள்ளுங்கள்: எக்ஸ் மற்றும் எக்ஸ் எழுத்துக்களைத் தவிர வேறு எதுவும் மாற்றப்படாமல் உள்ளன.",
+ "incompleteFields": "தொலைபேசி / தொலைநகல் தரவு வகைக்கு விருப்பங்கள் உரை புலத்தில் உள்ளிடப்பட்டிருக்க வேண்டும். பின்வரும் வரிசைகளை சரிசெய்யவும்:",
+ "uk": "யுகே",
+ "france": "பிரான்ஸ்",
+ "germany": "ஜெர்மனி",
+ "australia": "ஆஸ்திரேலியா",
+ "japan": "ஜப்பான்"
+}
diff --git a/packages/plugins/src/dataTypes/Phone/i18n/zh.json b/packages/plugins/src/dataTypes/Phone/i18n/zh.json
new file mode 100644
index 000000000..a6e1450d3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Phone/i18n/zh.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "电话/传真",
+ "DESC": "为不同的国家/地区生成各种格式的随机电话/传真号码。",
+ "DEFAULT_TITLE": "电话",
+ "differentFormats": "不同格式",
+ "northAmerica": "北美",
+ "helpText1": "您在选项文本字段中输入的任何文本都将用于生成电话号码。 大写的X将转换为1到9之间的随机数; 小写的x将转换为0到9之间的随机数。",
+ "helpText2": "在示例下拉列表中选择一些值以获取一些想法。 请记住:X和x字符以外的任何其他字符都将保持不变。",
+ "incompleteFields": "电话/传真数据类型需要具有在“选项”文本字段中输入的格式。 请修正以下几行:",
+ "uk": "英国",
+ "france": "法国",
+ "germany": "德国",
+ "australia": "澳大利亚",
+ "japan": "日本"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/PostalZip.generate.ts b/packages/plugins/src/dataTypes/PostalZip/PostalZip.generate.ts
new file mode 100644
index 000000000..5e3fb5c9a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/PostalZip.generate.ts
@@ -0,0 +1,82 @@
+import utils from '../../../utils';
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { CountryDataType, CountryType, Region } from '~types/countries';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { rowState, countryData, existingRowData } = data;
+ const { source, selectedCountries } = rowState;
+
+ const countryList = utils.countryUtils.getCountryList();
+ let country: CountryType;
+ let regionRow: any;
+
+ if (source === 'any') {
+ country = utils.randomUtils.getRandomArrayValue(countryList as CountryType[]);
+ } else if (source === 'countries') {
+ const list = selectedCountries.length ? selectedCountries : countryList;
+ country = utils.randomUtils.getRandomArrayValue(list);
+ } else if (source === 'countryRow') {
+ const countryRow = existingRowData.find(({ id }) => id === rowState.targetRowId);
+ country = countryRow!.data.countryDataType;
+
+ // region row
+ } else {
+ regionRow = existingRowData.find(({ id }) => id === rowState.targetRowId);
+ country = regionRow!.data.countryDataType;
+ }
+
+ if (!country) {
+ return { display: '' };
+ }
+
+ // if the user selected a row containing region data as the source for generating a zip, check the user has
+ // actually entered all necessary UI options for formatting options (short / full), otherwise we won't know what to generate
+ if (regionRow) {
+ if (!regionRow!.data.display || !regionRow!.data.displayFormat) {
+ return { display: '' };
+ }
+ }
+
+ const selectedCountry = countryData[country];
+ let selectedRegion: Region;
+ if (regionRow) {
+ const property = regionRow!.data.displayFormat === 'short' ? 'regionShort' : 'regionName';
+ selectedRegion = selectedCountry.regions.find((i: Region) => i[property] === regionRow!.data.display) as Region;
+ } else {
+ selectedRegion = utils.randomUtils.getRandomArrayValue(selectedCountry.regions);
+ }
+
+ return {
+ display: getRegionPostalCode(selectedCountry, selectedRegion)
+ };
+};
+
+const getRegionPostalCode = (countryData: CountryDataType, region: Region): string => {
+ let placeholders: any = {};
+
+ let format: string = countryData.extendedData.zipFormat.format;
+
+ if (countryData.extendedData.zipFormat.replacements) {
+ placeholders = countryData.extendedData.zipFormat.replacements;
+ }
+
+ if (region.extendedData) {
+ if (region.extendedData.zipFormat) {
+ if (region.extendedData.zipFormat.format) {
+ format = region.extendedData.zipFormat.format;
+ }
+ if (region.extendedData.zipFormat.replacements) {
+ placeholders = {
+ ...placeholders,
+ ...region.extendedData.zipFormat.replacements
+ };
+ }
+ }
+ }
+
+ const formats = format.split('|');
+ const selectedFormat = utils.randomUtils.getRandomArrayValue(formats);
+
+ return utils.randomUtils.generatePlaceholderStr(selectedFormat, placeholders);
+};
diff --git a/packages/plugins/src/dataTypes/PostalZip/PostalZip.scss b/packages/plugins/src/dataTypes/PostalZip/PostalZip.scss
new file mode 100644
index 000000000..a013c6a46
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/PostalZip.scss
@@ -0,0 +1,3 @@
+.buttonLabel {
+ margin-top: 4px;
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/PostalZip.scss.d.ts b/packages/plugins/src/dataTypes/PostalZip/PostalZip.scss.d.ts
new file mode 100644
index 000000000..b54f0596c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/PostalZip.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace PostalZipScssNamespace {
+ export interface IPostalZipScss {
+ buttonLabel: string;
+ }
+}
+
+declare const PostalZipScssModule: PostalZipScssNamespace.IPostalZipScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: PostalZipScssNamespace.IPostalZipScss;
+};
+
+export = PostalZipScssModule;
diff --git a/packages/plugins/src/dataTypes/PostalZip/PostalZip.state.tsx b/packages/plugins/src/dataTypes/PostalZip/PostalZip.state.tsx
new file mode 100755
index 000000000..94193f20e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/PostalZip.state.tsx
@@ -0,0 +1,39 @@
+import { CountryType } from '../../';
+
+export const enum PostalZipSourceEnum {
+ any = 'any',
+ countries = 'countries',
+ countryRow = 'countryRow',
+ regionRow = 'regionRow'
+}
+export type PostalZipSource = `${PostalZipSourceEnum}`;
+
+export type PostalZipState = {
+ source: PostalZipSource;
+ selectedCountries: CountryType[];
+ targetRowId: string;
+};
+
+export const initialState: PostalZipState = {
+ source: PostalZipSourceEnum.any,
+ selectedCountries: [],
+ targetRowId: ''
+};
+
+export const defaultGenerationOptions = initialState;
+
+export type PostalZipStateAny = {
+ source: 'any' | PostalZipSourceEnum.any;
+};
+
+export type PostalZipStateCountries = {
+ source: 'countries' | PostalZipSourceEnum.countries;
+ selectedCountries: CountryType[];
+};
+
+export type PostalZipStateDataRow = {
+ source: PostalZipSourceEnum.countryRow | PostalZipSourceEnum.regionRow | 'countryRow' | 'regionRow';
+ targetRowId: string;
+};
+
+export type GenerationOptionsType = PostalZipStateAny | PostalZipStateCountries | PostalZipStateDataRow;
diff --git a/packages/plugins/src/dataTypes/PostalZip/PostalZip.store.tsx b/packages/plugins/src/dataTypes/PostalZip/PostalZip.store.tsx
new file mode 100644
index 000000000..0ee5e923d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/PostalZip.store.tsx
@@ -0,0 +1,54 @@
+import { createSelector } from 'reselect';
+import { DTCustomProps } from '~types/dataTypes';
+import { getSortedRowsArray } from '~store/generator/generator.selectors';
+import { PostalZipState, PostalZipSource } from './PostalZip.state';
+import { REMOVE_ROW, SELECT_DATA_TYPE } from '~store/generator/generator.actions';
+
+
+// Postal/Zip Data Types can take their source as either Country fields (set to "Plugins" as the source), Regions, or
+// even City rows. All this info is presented in the UI for the user to choose
+const getCountryRows = createSelector(
+ getSortedRowsArray,
+ (rows) => rows.map((row, index) => ({ ...row, index })).filter(({ dataType, data }) => (
+ dataType === 'Country' && data.source === 'plugins'
+ ))
+);
+
+const getRegionRows = createSelector(
+ getSortedRowsArray,
+ (rows) => rows.map((row, index) => ({ ...row, index })).filter(({ dataType }) => dataType === 'Region')
+);
+
+export const customProps: DTCustomProps = {
+ countryRows: getCountryRows,
+ regionRows: getRegionRows
+};
+
+export const actionInterceptors = {
+
+ // when a Country/Region plugin row is removed, clean up any postal/zip fields that were mapped to it
+ [REMOVE_ROW]: (sourceRowId: string, rowState: PostalZipState, actionPayload: any): PostalZipState | null => {
+ if (actionPayload.id === rowState.targetRowId) {
+ return {
+ ...rowState,
+ source: 'any',
+ targetRowId: ''
+ };
+ }
+ return null;
+ },
+
+ [SELECT_DATA_TYPE]: (sourceRowId: string, rowState: PostalZipState, actionPayload: any): PostalZipState | null => {
+ if (actionPayload.id === rowState.targetRowId) {
+ if ((rowState.source === 'regionRow' && actionPayload.value !== 'Region') ||
+ (rowState.source === 'countryRow' && actionPayload.value !== 'Country')) {
+ return {
+ ...rowState,
+ source: 'any',
+ targetRowId: ''
+ };
+ }
+ }
+ return null;
+ }
+};
diff --git a/packages/plugins/src/dataTypes/PostalZip/PostalZip.tsx b/packages/plugins/src/dataTypes/PostalZip/PostalZip.tsx
new file mode 100755
index 000000000..f12f9a5b2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/PostalZip.tsx
@@ -0,0 +1,190 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import { DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { countryList } from '../../../../_plugins';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import { PostalZipSource } from './PostalZip.state';
+import styles from './PostalZip.scss';
+
+const ZipDialog = ({ visible, data, id, onClose, countryI18n, coreI18n, i18n, onUpdate, countryRows, regionRows }: any) => {
+ const countryPluginRows = countryRows.map(({ index, id, title }: any) => ({ value: id, label: `${i18n.row} #${index + 1}: ${title}` }));
+ const countryPluginRowsExist = countryPluginRows.length > 0;
+ const regionPluginRows = regionRows.map(({ index, id, title }: any) => ({ value: id, label: `${i18n.row} #${index + 1}: ${title}` }));
+ const regionPluginRowsExist = regionPluginRows.length > 0;
+
+ const onUpdateSource = (source: PostalZipSource): void => {
+ const newValues = {
+ ...data,
+ source
+ };
+ if (source === 'countryRow') {
+ newValues.targetRowId = countryPluginRows[0].value;
+ }
+ if (source === 'regionRow') {
+ newValues.targetRowId = regionPluginRows[0].value;
+ }
+ onUpdate(newValues);
+ };
+
+ const onChangeTargetRow = (row: DropdownOption): void => {
+ onUpdate({
+ ...data,
+ targetRowId: row.value
+ });
+ };
+
+ const onSelectCountries = (countries: any): void => {
+ onUpdate({
+ ...data,
+ selectedCountries: countries ? countries.map(({ value }: DropdownOption) => value) : []
+ });
+ };
+
+ const getRegionRowDropdown = (): React.ReactNode => {
+ if (data.source !== 'regionRow') {
+ return null;
+ }
+
+ return ;
+ };
+
+ const getCountryPluginDropdown = (): React.ReactNode => {
+ if (data.source !== 'countryRow') {
+ return null;
+ }
+
+ return ;
+ };
+
+ const getCountriesDropdown = (): React.ReactNode => {
+ if (data.source !== 'countries') {
+ return null;
+ }
+
+ const countryPluginOptions = countryList.map((countryName) => ({
+ value: countryName,
+ label: countryI18n[countryName].countryName
+ }));
+
+ return (
+
+ );
+ };
+
+ return (
+
+
+
{i18n.selectPostalCodes}
+
+ {i18n.explanation}
+
+ {i18n.source}
+
+
+ onUpdateSource('any')}
+ name={`${id}-source`}
+ checked={data.source === 'any'}
+ tooltip={i18n.anyFormatDesc}
+ />
+ onUpdateSource('countries')}
+ name={`${id}-source`}
+ checked={data.source === 'countries'}
+ tooltip={i18n.countriesDesc}
+ />
+ onUpdateSource('countryRow')}
+ name={`${id}-source`}
+ checked={data.source === 'countryRow'}
+ disabled={!countryPluginRowsExist}
+ tooltip={i18n.countryRowDesc}
+ />
+ onUpdateSource('regionRow')}
+ name={`${id}-source`}
+ checked={data.source === 'regionRow'}
+ disabled={!regionPluginRowsExist}
+ tooltip={i18n.regionRowDesc}
+ />
+
+ {getCountriesDropdown()}
+ {getCountryPluginDropdown()}
+ {getRegionRowDropdown()}
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ id, data, coreI18n, i18n, countryI18n, onUpdate, countryRows, regionRows, cityRows }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+ const numSelected = data.selectedCountries.length;
+
+ let label = '';
+ if (data.source === 'any') {
+ label = i18n.anyFormat;
+ } else if (data.source === 'countries') {
+ label = `${i18n.anyPostalZipFrom} ${numSelected} ` + (numSelected === 1 ? i18n.country : i18n.countries);
+ } else if (data.source === 'countryRow') {
+ const row = countryRows.find((row: any) => row.id === data.targetRowId);
+ const rowNum = row.index + 1;
+ label = `${i18n.countryRow} #${rowNum}`;
+ } else if (data.source === 'regionRow') {
+ const row = regionRows.find((row: any) => row.id === data.targetRowId);
+ const rowNum = row.index + 1;
+ label = `${i18n.regionRow} #${rowNum}`;
+ }
+
+ return (
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+ setDialogVisibility(false)}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.DESC}
;
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(10) default NULL',
+ field_Oracle: 'varchar2(10) default NULL',
+ field_MSSQL: 'VARCHAR(10) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/PostalZip/PostalZip.worker.ts b/packages/plugins/src/dataTypes/PostalZip/PostalZip.worker.ts
new file mode 100644
index 000000000..56baf1b0a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/PostalZip.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './PostalZip.generate';
+
+let workerUtilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/PostalZip/README.md b/packages/plugins/src/dataTypes/PostalZip/README.md
new file mode 100644
index 000000000..33bade1ff
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/README.md
@@ -0,0 +1,281 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » PostalZip
+
+This Data Type works in conjunction with the Countries plugins to generate country-specific postal/zip formats
+for consistency of data across each row.
+
+## Examples
+
+- [Generate a postal/zip code in a random format](#generate-a-postalzip-code-in-a-random-format)
+- [Generate a postal code from a set of predefined countries](#generate-a-postal-code-from-a-set-of-predefined-countries)
+- [Generate a postal code that maps to a country row](#generate-a-postal-code-that-maps-to-a-country-row)
+- [Generate a postal code that maps to a region row](#generate-a-postal-code-that-maps-to-a-region-row)
+
+
+### Generate a postal/zip code in a random format
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "PostalZip",
+ title: "Any postal code",
+ settings: {
+ source: "any"
+ }
+ },
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```typescript
+[
+ {
+ "Any postal code": 95201
+ },
+ {
+ "Any postal code": 8322
+ },
+ {
+ "Any postal code": 34353
+ },
+ {
+ "Any postal code": "7933-9534"
+ },
+ {
+ "Any postal code": "39456-26578"
+ },
+ {
+ "Any postal code": 16580
+ },
+ ...
+]
+```
+
+### Generate a postal code from a set of predefined countries
+
+This example outputs a random postal code from either Canada or the US.
+
+```typescript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: "PostalZip",
+ title: "two-countries",
+ settings: {
+ source: "countries",
+ selectedCountries: ["Canada", "US"]
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: "JSON",
+ settings: {
+ dataStructureFormat: "simple"
+ }
+ }
+}
+```
+
+Sample output:
+
+```typescript
+[
+ {
+ "two-countries": "L4S 7S6"
+ },
+ {
+ "two-countries": "H1P 6R6"
+ },
+ {
+ "two-countries": 37488
+ },
+ {
+ "two-countries": 84824
+ },
+ {
+ "two-countries": 47702
+ },
+ {
+ "two-countries": "B7G 6Z7"
+ },
+ ...
+]
+```
+
+### Generate a postal code that maps to a country row
+
+This generates a random country from a short list, then generates a postal code in the format of each country.
+
+```typescript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Country',
+ title: 'country-plugins-subset',
+ settings: {
+ source: 'plugins',
+ selectedCountries: [
+ 'Canada',
+ 'US',
+ 'Norway'
+ ]
+ },
+ id: '123'
+ },
+ {
+ plugin: 'PostalZip',
+ title: 'zip',
+ settings: {
+ source: 'countryRow',
+ targetRowId: '123'
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: ExportType.JSON,
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```typescript
+[
+ {
+ "country-plugins-subset": "United States",
+ "zip": 71534
+ },
+ {
+ "country-plugins-subset": "Norway",
+ "zip": 6225
+ },
+ {
+ "country-plugins-subset": "Norway",
+ "zip": 1466
+ },
+ {
+ "country-plugins-subset": "Canada",
+ "zip": "J1N 8M4"
+ },
+ {
+ "country-plugins-subset": "Canada",
+ "zip": "B4X 8E8"
+ },
+ {
+ "country-plugins-subset": "United States",
+ "zip": 38648
+ },
+ {
+ "country-plugins-subset": "Norway",
+ "zip": 7797
+ },
+ {
+ "country-plugins-subset": "Norway",
+ "zip": 7802
+ },
+ ...
+]
+```
+
+### Generate a postal code that maps to a region row
+
+```
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Region',
+ title: 'Region from 2 countries',
+ settings: {
+ source: 'countries',
+ selectedCountries: ['Canada', 'US'],
+ formats: ['full']
+ },
+ id: '555'
+ },
+ {
+ plugin: 'PostalZip',
+ title: 'zip',
+ settings: {
+ source: 'regionRow',
+ targetRowId: '555'
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: ExportType.JSON,
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```typescript
+[
+ {
+ "Region from 2 countries": "Connecticut",
+ "zip": 79839
+ },
+ {
+ "Region from 2 countries": "Northwest Territories",
+ "zip": "35H 2C5"
+ },
+ {
+ "Region from 2 countries": "Louisiana",
+ "zip": 67174
+ },
+ {
+ "Region from 2 countries": "Prince Edward Island",
+ "zip": "F1V 2H3"
+ },
+ {
+ "Region from 2 countries": "Ontario",
+ "zip": "N0Z 1L0"
+ },
+ {
+ "Region from 2 countries": "Nevada",
+ "zip": 72129
+ },
+ {
+ "Region from 2 countries": "Ohio",
+ "zip": 28624
+ },
+ {
+ "Region from 2 countries": "Arizona",
+ "zip": 85129
+ },
+ {
+ "Region from 2 countries": "Yukon",
+ "zip": "Y7R 9N2"
+ },
+ {
+ "Region from 2 countries": "Tennessee",
+ "zip": 60459
+ }
+ ]
+```
diff --git a/packages/plugins/src/dataTypes/PostalZip/__tests__/PostalZip.cli.test.ts b/packages/plugins/src/dataTypes/PostalZip/__tests__/PostalZip.cli.test.ts
new file mode 100644
index 000000000..9afb6c57e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/__tests__/PostalZip.cli.test.ts
@@ -0,0 +1,88 @@
+// @ts-ignore
+import generate, { DataType, ExportType, GDTemplate } from '../../../../../../cli/dist/cli/src';
+
+describe('CLI data generation', () => {
+ const getTemplate = (): GDTemplate => ({
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: DataType.PostalZip,
+ title: 'Any postal code',
+ settings: {
+ source: 'any'
+ }
+ },
+ {
+ plugin: DataType.PostalZip,
+ title: 'two-countries',
+ settings: {
+ source: 'countries',
+ selectedCountries: ['Canada', 'US']
+ }
+ },
+ {
+ plugin: 'Country',
+ title: 'country-plugins-subset',
+ settings: {
+ source: 'plugins',
+ selectedCountries: [
+ 'Canada',
+ 'US',
+ 'Norway'
+ ]
+ },
+ id: '123'
+ },
+ {
+ plugin: 'PostalZip',
+ title: 'zip',
+ settings: {
+ source: 'countryRow',
+ targetRowId: '123'
+ }
+ },
+ {
+ plugin: 'Region',
+ title: 'Region from 2 countries',
+ settings: {
+ source: 'countries',
+ selectedCountries: ['Canada', 'US'],
+ formats: ['full']
+ },
+ id: '555'
+ },
+ {
+ plugin: 'PostalZip',
+ title: 'zip2',
+ settings: {
+ source: 'regionRow',
+ targetRowId: '555'
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: ExportType.JSON,
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+ });
+
+ it('Generates as expected', async () => {
+ const data: any = await generate(getTemplate());
+ const generatedJSON = JSON.parse(data);
+
+ expect(generatedJSON.length).toEqual(10);
+ expect(Object.keys(generatedJSON[0])).toEqual([
+ 'Any postal code',
+ 'two-countries',
+ 'country-plugins-subset',
+ 'zip',
+ 'Region from 2 countries',
+ 'zip2'
+ ]);
+ });
+});
+
diff --git a/packages/plugins/src/dataTypes/PostalZip/__tests__/PostalZip.test.tsx b/packages/plugins/src/dataTypes/PostalZip/__tests__/PostalZip.test.tsx
new file mode 100644
index 000000000..8eb29683e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/__tests__/PostalZip.test.tsx
@@ -0,0 +1,44 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help, Options } from '../PostalZip';
+const i18n = require('../i18n/en.json');
+
+const defaultHelpProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+const optionsProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ gridPanelDimensions: { width: 100, height: 100 },
+ isCountryNamesLoading: false,
+ isCountryNamesLoaded: false,
+ countryNamesMap: null
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('Options', () => {
+ it('renders', () => {
+ const { container } = render(
+ {}}
+ countryRows={[]}
+ regionRows={[]}
+ />);
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/PostalZip/bundle.ts b/packages/plugins/src/dataTypes/PostalZip/bundle.ts
new file mode 100644
index 000000000..bdd87e928
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/bundle.ts
@@ -0,0 +1,15 @@
+import { DTBundle } from '~types/dataTypes';
+import { Options, Help, getMetadata } from './PostalZip';
+import { customProps, actionInterceptors } from './PostalZip.store';
+import { initialState } from './PostalZip.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ Help,
+ getMetadata,
+ customProps,
+ actionInterceptors
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/PostalZip/config.ts b/packages/plugins/src/dataTypes/PostalZip/config.ts
new file mode 100644
index 000000000..e802c25a0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/config.ts
@@ -0,0 +1,9 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'geo',
+ fieldGroupOrder: 30,
+ dependencies: ['Country', 'Region']
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/ar.json b/packages/plugins/src/dataTypes/PostalZip/i18n/ar.json
new file mode 100644
index 000000000..a21c0d7dd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/ar.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "البريدي / الرمز البريدي",
+ "DESC": "يولد رمزًا بريديًا أو رمزًا بريديًا عشوائيًا. لمزيد من التحكم ، استخدم خيار نوع البيانات الأبجدي الرقمي.",
+ "DEFAULT_TITLE": "البريدي / الرمز البريدي",
+ "selectPostalCodes": "حدد الرمز البريدي / الرمز البريدي",
+ "explanation": "يتحكم هذا في تنسيقات البريد / البريد / الرمز البريدي التي تم إنشاؤها بواسطة هذا الحقل. إذا كنت تريد شيئًا أكثر أساسية ، فجرّب نوع البيانات الأبجدي الرقمي.",
+ "source": "مصدر",
+ "anyFormat": "بأي شكل",
+ "anyFormatDesc": "يولد رمزًا بريديًا / بريديًا / بريديًا من أي بلد بأي تنسيق معروف.",
+ "countries": "بلدان",
+ "countriesDesc": "يولد رمزًا بريديًا / بريديًا / بريديًا بتنسيق من قائمة محددة من البلدان.",
+ "countryRow": "صف البلد",
+ "countryRowDesc": "يتيح لك هذا تحديد حقل بلد معين في مجموعة البيانات الخاصة بك التي تحتوي على مكونات البلد الإضافية كمصدر لها. يقوم بإنشاء الرموز البريدية / البريدية / البريدية التي تحدد القيمة من هذا الحقل.",
+ "regionRow": "صف المنطقة",
+ "regionRowDesc": "يتيح لك هذا تحديد حقل منطقة معين في مجموعة البيانات الخاصة بك. يقوم بإنشاء الرموز البريدية / البريدية / البريدية التي تحدد القيمة من هذا الحقل.",
+ "country": "بلد",
+ "row": "صف",
+ "anyPostalZipFrom": "أي بريد / الرمز البريدي من"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/de.json b/packages/plugins/src/dataTypes/PostalZip/i18n/de.json
new file mode 100644
index 000000000..c813b2a28
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/de.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "Post / Postleitzahl",
+ "DESC": "Erzeugt eine zufällige Postleitzahl. Verwenden Sie zur besseren Kontrolle die Option für alphanumerische Datentypen.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "Wählen Sie die Postleitzahl / Postleitzahl",
+ "explanation": "Dies steuert die Formate von Post / Post / Postleitzahl, die von diesem Feld generiert werden. Wenn Sie etwas wesentlich grundlegenderes wünschen, versuchen Sie es mit dem alphanumerischen Datentyp.",
+ "source": "Quelle",
+ "anyFormat": "Beliebiges Format",
+ "anyFormatDesc": "Generiert eine Post / Post / Postleitzahl aus einem beliebigen Land in einem bekannten Format.",
+ "countries": "Länder",
+ "countriesDesc": "Generiert eine Post / Post / Postleitzahl in einem Format aus einer bestimmten Liste von Ländern.",
+ "countryRow": "Country Row",
+ "countryRowDesc": "Auf diese Weise können Sie ein bestimmtes Länderfeld in Ihrem Datensatz bestimmen, dessen Quelle Länder-Plugins sind. Es werden Post- / Post- / Postleitzahlen generiert, die dem Wert aus diesem Feld zugeordnet sind.",
+ "regionRow": "Regionszeile",
+ "regionRowDesc": "Auf diese Weise können Sie ein bestimmtes Regionsfeld in Ihrem Datensatz genau bestimmen. Es werden Post- / Post- / Postleitzahlen generiert, die dem Wert aus diesem Feld zugeordnet sind.",
+ "country": "Land",
+ "row": "Reihe",
+ "anyPostalZipFrom": "Jede Post / Postleitzahl von"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/en.json b/packages/plugins/src/dataTypes/PostalZip/i18n/en.json
new file mode 100644
index 000000000..20a148dd5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/en.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "Postal / Zip",
+ "DESC": "Generates a random zip or postal code. For greater control, use the alpha-numeric data type option.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "Select postal code / zips",
+ "explanation": "This controls the formats of post/postal/zipcode generated by this field. If you want something far more basic, try the Alphanumeric data type.",
+ "source": "Source",
+ "anyFormat": "Any format",
+ "anyFormatDesc": "Generates a postal/post/zipcode from any country in any known format.",
+ "countries": "Countries",
+ "countriesDesc": "Generates a postal/post/zipcode in a format from a specific list of countries.",
+ "countryRow": "Country Row",
+ "countryRowDesc": "This lets you pinpoint a specific Country field in your data set that has Country Plugins as its source. It generates postal/post/zip codes that map to the value from that field.",
+ "regionRow": "Region Row",
+ "regionRowDesc": "This lets you pinpoint a specific Region field in your data set. It generates postal/post/zip codes that map to the value from that field.",
+ "country": "Country",
+ "row": "Row",
+ "anyPostalZipFrom": "Any postal/zip from"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/es.json b/packages/plugins/src/dataTypes/PostalZip/i18n/es.json
new file mode 100644
index 000000000..c04cb127f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/es.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "Postal / Código Postal",
+ "DESC": "Genera un código postal aleatorio. Para un mayor control, utilice la opción de tipo de datos alfanuméricos.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "Seleccionar código postal / cremalleras",
+ "explanation": "Esto controla los formatos de correo postal / postal / código postal generados por este campo. Si desea algo mucho más básico, pruebe el tipo de datos alfanuméricos.",
+ "source": "Fuente",
+ "anyFormat": "Cualquier formato",
+ "anyFormatDesc": "Genera un código postal / postal / postal de cualquier país en cualquier formato conocido.",
+ "countries": "Países",
+ "countriesDesc": "Genera un código postal / postal / postal en un formato de una lista específica de países.",
+ "countryRow": "Fila de país",
+ "countryRowDesc": "Esto le permite identificar un campo de país específico en su conjunto de datos que tiene complementos de país como fuente. Genera códigos postales / postales / postales que se asignan al valor de ese campo.",
+ "regionRow": "Fila de región",
+ "regionRowDesc": "Esto le permite señalar un campo de región específico en su conjunto de datos. Genera códigos postales / postales / postales que se asignan al valor de ese campo.",
+ "country": "País",
+ "row": "Fila",
+ "anyPostalZipFrom": "Cualquier postal / zip de"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/fr.json b/packages/plugins/src/dataTypes/PostalZip/i18n/fr.json
new file mode 100644
index 000000000..f0a7af357
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/fr.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "Postal / Zip",
+ "DESC": "Génère un zip ou un code postal aléatoire. Pour un meilleur contrôle, utilisez l'option de type de données alphanumériques.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "Sélectionnez le code postal / zips",
+ "explanation": "Ceci contrôle les formats de courrier / postal / code postal générés par ce champ. Si vous voulez quelque chose de beaucoup plus basique, essayez le type de données alphanumériques.",
+ "source": "La source",
+ "anyFormat": "Tout format",
+ "anyFormatDesc": "Génère un code postal / postal / postal de n'importe quel pays dans n'importe quel format connu.",
+ "countries": "Des pays",
+ "countriesDesc": "Génère un code postal / postal / postal dans un format à partir d'une liste spécifique de pays.",
+ "countryRow": "Country Row",
+ "countryRowDesc": "Cela vous permet d'identifier un champ Pays spécifique dans votre ensemble de données dont la source est les Plugins Pays. Il génère des codes postaux / postaux / zip qui correspondent à la valeur de ce champ.",
+ "regionRow": "Ligne de région",
+ "regionRowDesc": "Cela vous permet d'identifier un champ de région spécifique dans votre ensemble de données. Il génère des codes postaux / postaux / zip qui correspondent à la valeur de ce champ.",
+ "country": "Pays",
+ "row": "Rangée",
+ "anyPostalZipFrom": "Tout courrier / zip de"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/hi.json b/packages/plugins/src/dataTypes/PostalZip/i18n/hi.json
new file mode 100644
index 000000000..9bff0727b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/hi.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "डाक / ज़िप",
+ "DESC": "एक यादृच्छिक ज़िप या डाक कोड उत्पन्न करता है। अधिक नियंत्रण के लिए, अल्फ़ा-न्यूमेरिक डेटा प्रकार विकल्प का उपयोग करें।",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "डाक कोड / ज़िप चुनें",
+ "explanation": "यह इस क्षेत्र द्वारा उत्पन्न डाक/डाक/ज़िपकोड के स्वरूपों को नियंत्रित करता है। यदि आप कुछ अधिक बुनियादी चाहते हैं, तो अल्फ़ान्यूमेरिक डेटा प्रकार आज़माएं।",
+ "source": "स्रोत",
+ "anyFormat": "कोई भी प्रारूप",
+ "anyFormatDesc": "किसी भी ज्ञात प्रारूप में किसी भी देश से डाक/पोस्ट/ज़िप कोड उत्पन्न करता है।",
+ "countries": "देशों",
+ "countriesDesc": "देशों की विशिष्ट सूची से एक प्रारूप में डाक/पोस्ट/ज़िपकोड उत्पन्न करता है।",
+ "countryRow": "देश पंक्ति",
+ "countryRowDesc": "यह आपको अपने डेटा सेट में एक विशिष्ट देश फ़ील्ड को इंगित करने देता है जिसमें इसके स्रोत के रूप में देश प्लगइन्स हैं। यह डाक/पोस्ट/ज़िप कोड उत्पन्न करता है जो उस क्षेत्र से मूल्य के लिए मैप करता है।",
+ "regionRow": "क्षेत्र पंक्ति",
+ "regionRowDesc": "यह आपको अपने डेटा सेट में एक विशिष्ट क्षेत्र फ़ील्ड को इंगित करने देता है। यह डाक/पोस्ट/ज़िप कोड उत्पन्न करता है जो उस क्षेत्र से मूल्य के लिए मैप करता है।",
+ "country": "देश",
+ "row": "पंक्ति",
+ "anyPostalZipFrom": "से कोई डाक/ज़िप"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/ja.json b/packages/plugins/src/dataTypes/PostalZip/i18n/ja.json
new file mode 100644
index 000000000..3013a096c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/ja.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "郵便番号/郵便番号",
+ "DESC": "ランダムな郵便番号または郵便番号を生成します。 制御を強化するには、英数字のデータ型オプションを使用します。",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "郵便番号/郵便番号を選択します",
+ "explanation": "这控制了此字段生成的邮编/邮政编码/邮政编码的格式。 如果您想要更基本的东西,请尝试字母数字数据类型。",
+ "source": "资源",
+ "anyFormat": "任何格式",
+ "anyFormatDesc": "从任何国家/地区以任何已知格式生成邮政编码。",
+ "countries": "国别",
+ "countriesDesc": "以特定国家/地区列表的格式生成邮政编码。",
+ "countryRow": "国家行",
+ "countryRowDesc": "这样,您就可以在数据集中以“国家/地区插件”作为来源的特定“国家/地区”字段中定位。 它将生成映射到该字段值的邮政编码。",
+ "regionRow": "区域行",
+ "regionRowDesc": "这使您可以精确定位数据集中的特定“区域”字段。 它将生成映射到该字段值的邮政编码。",
+ "country": "国家",
+ "row": "行",
+ "anyPostalZipFrom": "来自的任何邮政/邮编"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/nl.json b/packages/plugins/src/dataTypes/PostalZip/i18n/nl.json
new file mode 100644
index 000000000..3cbde50a6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/nl.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "Post / postcode",
+ "DESC": "Genereert een willekeurige postcode. Gebruik de optie voor alfanumerieke gegevenstypen voor meer controle.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "Selecteer postcode / ritsen",
+ "explanation": "Dit beheert de formaten van post / post / postcode die door dit veld worden gegenereerd. Als u iets veel eenvoudiger wilt, probeer dan het gegevenstype Alfanumeriek.",
+ "source": "Bron",
+ "anyFormat": "Elk formaat",
+ "anyFormatDesc": "Genereert een postcode / post / postcode vanuit elk land in elk bekend formaat.",
+ "countries": "Landen",
+ "countriesDesc": "Genereert een postcode / post / postcode in een indeling uit een specifieke lijst met landen.",
+ "countryRow": "Country Row",
+ "countryRowDesc": "Hiermee kunt u een specifiek landveld in uw dataset lokaliseren dat landplug-ins als bron heeft. Het genereert post- / post- / postcodes die verwijzen naar de waarde uit dat veld.",
+ "regionRow": "Regio Rij",
+ "regionRowDesc": "Hiermee kunt u een specifiek regioveld in uw gegevensset lokaliseren. Het genereert post- / post- / postcodes die verwijzen naar de waarde uit dat veld.",
+ "country": "Land",
+ "row": "Rij",
+ "anyPostalZipFrom": "Elke post / zip van"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/pt.json b/packages/plugins/src/dataTypes/PostalZip/i18n/pt.json
new file mode 100644
index 000000000..90822f163
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/pt.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "Postal / Zip",
+ "DESC": "Gera um CEP ou código postal aleatório. Para maior controle, use a opção de tipo de dados alfanuméricos.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "Selecione o código postal / CEPs",
+ "explanation": "Isso controla os formatos de post / postal / CEP gerados por este campo. Se você quiser algo muito mais básico, tente o tipo de dados alfanumérico.",
+ "source": "Fonte",
+ "anyFormat": "Qualquer formato",
+ "anyFormatDesc": "Gera um código postal / postal / CEP de qualquer país em qualquer formato conhecido.",
+ "countries": "Países",
+ "countriesDesc": "Gera um código postal / postal / CEP em um formato de uma lista específica de países.",
+ "countryRow": "Country Row",
+ "countryRowDesc": "Isso permite que você localize um campo de país específico em seu conjunto de dados que tem Plug-ins de país como sua fonte. Ele gera códigos postais / postais / CEP que mapeiam para o valor desse campo.",
+ "regionRow": "Region Row",
+ "regionRowDesc": "Isso permite que você localize um campo de região específico em seu conjunto de dados. Ele gera códigos postais / postais / CEP que mapeiam para o valor desse campo.",
+ "country": "País",
+ "row": "Linha",
+ "anyPostalZipFrom": "Qualquer postal / zip de"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/ru.json b/packages/plugins/src/dataTypes/PostalZip/i18n/ru.json
new file mode 100644
index 000000000..553106f5f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/ru.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "Почтовый / ИНдекс",
+ "DESC": "Генерирует случайный почтовый индекс или почтовый индекс. Для большего контроля используйте опцию буквенно-цифрового типа данных.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "Выберите почтовый индекс / почтовый индекс",
+ "explanation": "Это управляет форматами почтового индекса/почтового индекса, генерируемого этим полем. Если вы хотите что-то гораздо более простое, попробуйте тип данных Alphanumeric.",
+ "source": "Источник",
+ "anyFormat": "Любой формат",
+ "anyFormatDesc": "Генерирует почтовый/почтовый/почтовый индекс из любой страны в любом известном формате.",
+ "countries": "Страны",
+ "countriesDesc": "Генерирует почтовый/почтовый/почтовый индекс в формате из определенного списка стран.",
+ "countryRow": "Кантри Роу",
+ "countryRowDesc": "Это позволяет вам точно определить конкретное поле страны в вашем наборе данных, источником которого являются плагины страны. Он генерирует почтовые/почтовые/почтовые индексы, которые сопоставляются со значением из этого поля.",
+ "regionRow": "Строка региона",
+ "regionRowDesc": "Это позволяет точно определить конкретное поле «Регион» в наборе данных. Он генерирует почтовые/почтовые/почтовые индексы, которые сопоставляются со значением из этого поля.",
+ "country": "Страна",
+ "row": "Строка",
+ "anyPostalZipFrom": "Любая почта/zip от"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/ta.json b/packages/plugins/src/dataTypes/PostalZip/i18n/ta.json
new file mode 100644
index 000000000..bce0232cd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/ta.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "அஞ்சல் / ஜிப்",
+ "DESC": "சீரற்ற ஜிப் அல்லது அஞ்சல் குறியீட்டை உருவாக்குகிறது. அதிக கட்டுப்பாட்டுக்கு, ஆல்பா-எண் தரவு வகை விருப்பத்தைப் பயன்படுத்தவும்.",
+ "DEFAULT_TITLE": "postalZip",
+ "selectPostalCodes": "அஞ்சல் குறியீடு / ஜிப்ஸைத் தேர்ந்தெடுக்கவும்",
+ "explanation": "இந்த புலத்தால் உருவாக்கப்பட்ட அஞ்சல் / அஞ்சல் / ஜிப்கோடின் வடிவங்களை இது கட்டுப்படுத்துகிறது. நீங்கள் மிகவும் அடிப்படை ஒன்றை விரும்பினால், எண்ணெழுத்து தரவு வகையை முயற்சிக்கவும்.",
+ "source": "மூல",
+ "anyFormat": "எந்த வடிவமும்",
+ "anyFormatDesc": "எந்தவொரு நாட்டிலிருந்தும் எந்தவொரு அறியப்பட்ட வடிவத்திலும் ஒரு அஞ்சல் / இடுகை / ஜிப்கோடை உருவாக்குகிறது.",
+ "countries": "நாடுகள்",
+ "countriesDesc": "ஒரு குறிப்பிட்ட நாடுகளின் பட்டியலிலிருந்து ஒரு வடிவத்தில் ஒரு அஞ்சல் / இடுகை / ஜிப்கோடை உருவாக்குகிறது.",
+ "countryRow": "நாட்டின் வரிசை",
+ "countryRowDesc": "இது உங்கள் தரவுத் தொகுப்பில் ஒரு குறிப்பிட்ட நாட்டு புலத்தை அதன் மூல செருகிகளைக் கொண்டிருக்கும். இது அந்தத் துறையிலிருந்து மதிப்பைக் குறிக்கும் அஞ்சல் / இடுகை / ஜிப் குறியீடுகளை உருவாக்குகிறது.",
+ "regionRow": "பிராந்திய வரிசை",
+ "regionRowDesc": "உங்கள் தரவு தொகுப்பில் ஒரு குறிப்பிட்ட பிராந்திய புலத்தை சுட்டிக்காட்ட இது உங்களை அனுமதிக்கிறது. இது அந்தத் துறையிலிருந்து மதிப்பைக் குறிக்கும் அஞ்சல் / இடுகை / ஜிப் குறியீடுகளை உருவாக்குகிறது.",
+ "country": "நாடு",
+ "row": "வரிசை",
+ "anyPostalZipFrom": "எந்த அஞ்சல் / ஜிப்"
+}
diff --git a/packages/plugins/src/dataTypes/PostalZip/i18n/zh.json b/packages/plugins/src/dataTypes/PostalZip/i18n/zh.json
new file mode 100644
index 000000000..4e01f767e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/PostalZip/i18n/zh.json
@@ -0,0 +1,19 @@
+{
+ "NAME": "邮政/邮编",
+ "DESC": "生成一个随机的邮政编码。 为了更好地控制,请使用字母数字数据类型选项。",
+ "DEFAULT_TITLE": "邮政编码",
+ "selectPostalCodes": "选择邮政编码/邮编",
+ "explanation": "这控制由该字段生成的邮政/邮政编码/邮政编码的格式。 如果您想要更基本的东西,请尝试字母数字数据类型。",
+ "source": "来源",
+ "anyFormat": "任何格式",
+ "anyFormatDesc": "以任何已知格式从任何国家/地区生成邮政编码。",
+ "countries": "国家",
+ "countriesDesc": "以特定国家/地区列表的格式生成邮政编码。",
+ "countryRow": "乡村行",
+ "countryRowDesc": "这使您可以在以国家/地区插件为来源的数据集中查明特定国家/地区字段。 它生成映射到该字段值的邮政编码。",
+ "regionRow": "区域行",
+ "regionRowDesc": "这使您可以查明数据集中的特定区域字段。 它生成映射到该字段值的邮政编码。",
+ "country": "国家",
+ "row": "排",
+ "anyPostalZipFrom": "来自的任何邮政/邮编"
+}
diff --git a/packages/plugins/src/dataTypes/README.md b/packages/plugins/src/dataTypes/README.md
new file mode 100644
index 000000000..8213ca130
--- /dev/null
+++ b/packages/plugins/src/dataTypes/README.md
@@ -0,0 +1,42 @@
+# [Docs](../../../../docs/README.md) » [Plugins](../README.md) » Data Types
+
+These pages demonstrate the available configurations for each Data Type. Most of them use JSON as the export format,
+because it's nice and concise for illustration purposes. But remember that all of them can be used with any Export
+Types (XML, SQL, CSV etc.). See the [Export Types](#export-types) section below for how to configure those.
+
+- [Alphanumeric](./Alphanumeric/README.md)
+- [AutoIncrement](./AutoIncrement/README.md)
+- [Boolean](./Boolean/README.md)
+- [City](./City/README.md)
+- [Colour](./Colour/README.md)
+- [Company](./Company/README.md)
+- Computed
+- [Constant](./Constant/README.md)
+- [Country](./Country/README.md)
+- [Currency](./Currency/README.md)
+- [CVV](./CVV/README.md)
+- [Date](./Date/README.md)
+- [Email](./Email/README.md)
+- [GUID](./GUID/README.md)
+- [IBAN](./IBAN/README.md)
+- [LatLng](./LatLng/README.md)
+- List
+- [Names](./Names/README.md)
+- NormalDistribution
+- [NumberRange](./NumberRange/README.md)
+- OrganizationNumber
+- PAN
+- PersonalNumber
+- [Phone](./Phone/README.md)
+- PIN
+- [PostalZip](./PostalZip/README.md)
+- [Region](./Region/README.md)
+- Rut
+- [StreetAddress](./StreetAddress/README.md)
+- [TextFixed](./TextFixed/README.md)
+- TextRandom
+- [Time](./Time/README.md)
+- Track1
+- Track2
+- URLs
+- [WeightedList](./WeightedList/README.md)
diff --git a/packages/plugins/src/dataTypes/Region/README.md b/packages/plugins/src/dataTypes/Region/README.md
new file mode 100644
index 000000000..c4a7c45b6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/README.md
@@ -0,0 +1,338 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Region
+
+This Data Type generates a random region. A region is something like a State, Province or County - whatever is
+applicable for a particular country. The region data is stored in the [Country Plugins](../../countries/README.md), so
+see that section to see what information is currently available. All country plugins provide regions.
+
+Countries are typically subdivided geographically in numerous ways, so we choose the one that's most useful overall -
+almost always whatever region is used for addresses. For the shortcodes, typically they follow the ISO-3166-2 standard.
+
+This Data Type lets you generate regions in a number of different ways, hence the varied typings listed below.
+
+You can choose to generate regions from 3 different sources:
+1. `Any region` - This will generate a random region pulled from _any_ of the country plugins.
+2. `Countries` - This generates a region from whatever list of countries you supply.
+3. `Country Row` - This option lets you target a Country Data Type row in your data set, and use that as the source. It'll
+then generate a region in whatever random country is generated for that row.
+
+You can also choose whether this field outputs the full region name or a shortcode version (or either).
+
+## Typings
+
+```typescript
+export type RegionSource = 'anyRegion' | 'countries' | 'countryRow';
+export type RegionFormat = 'full' | 'short';
+
+export type RegionState = {
+ source: RegionSource;
+ targetRowId: string;
+ selectedCountries: CountryType[];
+ formats: RegionFormat[];
+}
+
+export type RegionStateAny = Pick;
+export type RegionStateCountryRow = Pick;
+export type RegionStateCountries = Pick;
+```
+
+## Examples
+
+- [Any region, with full and short names](#any-region-with-full-and-short-names)
+- [Any region from two countries](#any-region-from-two-countries)
+- [Any region mapped to a country row](#any-region-mapped-to-a-country-row)
+- [Full Region mapped to Country Subset row](#full-region-mapped-to-country-subset-row)
+
+
+### Any region, with full and short names
+
+This example outputs three rows of region data using the `anyRegion` option as a data source. The first displays the
+full region name, the second shows their shortcode, the third shows randomly short or full.
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Region',
+ title: 'Full Region',
+ settings: {
+ source: 'anyRegion',
+ formats: ['full']
+ }
+ },
+ {
+ plugin: 'Region',
+ title: 'Short Region Code',
+ settings: {
+ source: 'anyRegion',
+ formats: ['short']
+ }
+ },
+ {
+ plugin: 'Region',
+ title: 'Short/Full Region Code',
+ settings: {
+ source: 'anyRegion',
+ formats: ['short', 'full']
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "Full Region": "Moscow Oblast",
+ "Short Region Code": "MP",
+ "Short/Full Region Code": "SB",
+ },
+ {
+ "Full Region": "Hidalgo",
+ "Short Region Code": "DS",
+ "Short/Full Region Code": "Agder",
+ },
+ {
+ "Full Region": "Gilgit Baltistan",
+ "Short Region Code": "QC",
+ "Short/Full Region Code": "Extremadura",
+ },
+ {
+ "Full Region": "Alajuela",
+ "Short Region Code": "Manisa",
+ "Short/Full Region Code": "Dr",
+ },
+ {
+ "Full Region": "Punjab",
+ "Short Region Code": "Adana",
+ "Short/Full Region Code": "Overijssel"
+ },
+ ...
+]
+```
+
+### Any region from two countries
+
+This example use the `countries` source to let you fine-tune what countries should be used as the region data source.
+This generates regions from either Canada or Russia.
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Region',
+ title: 'Region from 2 countries',
+ settings: {
+ source: 'countries',
+ selectedCountries: ['Canada', 'Russia'],
+ formats: ['full']
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "Region from 2 countries": "Prince Edward Island"
+ },
+ {
+ "Region from 2 countries": "Saint Petersburg City"
+ },
+ {
+ "Region from 2 countries": "Bryansk Oblast"
+ },
+ {
+ "Region from 2 countries": "Ulyanovsk Oblast"
+ },
+ {
+ "Region from 2 countries": "Newfoundland and Labrador"
+ },
+ ...
+]
+```
+
+### Any region mapped to a country row
+
+This example shows how you can map one or more Region fields to a Country row. When generating the group of data,
+the generator will know to generate a region from the randomly generated country for that group, ensuring consistency.
+
+Note the use of the `id` and `targetRowId` properties. You can enter _any string value_ you want for those, as long as
+it's unique across the entire data template. In the website, it generates a random GUID internally for each row.
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Country',
+ title: 'Country',
+ settings: {
+ source: 'plugins',
+ selectedCountries: []
+ },
+ id: '1'
+ },
+ {
+ plugin: 'Region',
+ title: 'Full Region mapped to Country Row #1',
+ settings: {
+ source: 'countryRow',
+ targetRowId: '1',
+ formats: ['full']
+ }
+ },
+ {
+ plugin: 'Region',
+ title: 'Short Region mapped to Country Row #1',
+ settings: {
+ source: 'countryRow',
+ targetRowId: '1',
+ formats: ['short']
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "Country": "Chile",
+ "Full Region mapped to Country Row #1": "Metropolitana de Santiago",
+ "Short Region mapped to Country Row #1": "X"
+ },
+ {
+ "Country": "Sweden",
+ "Full Region mapped to Country Row #1": "Östergötlands län",
+ "Short Region mapped to Country Row #1": "E"
+ },
+ {
+ "Country": "Australia",
+ "Full Region mapped to Country Row #1": "Tasmania",
+ "Short Region mapped to Country Row #1": "NT"
+ },
+ {
+ "Country": "New Zealand",
+ "Full Region mapped to Country Row #1": "North Island",
+ "Short Region mapped to Country Row #1": "NI"
+ },
+ {
+ "Country": "Chile",
+ "Full Region mapped to Country Row #1": "Valparaíso",
+ "Short Region mapped to Country Row #1": "VII"
+ },
+ {
+ "Country": "Ukraine",
+ "Full Region mapped to Country Row #1": "Volyn oblast",
+ "Short Region mapped to Country Row #1": "RV"
+ },
+ ...
+]
+```
+
+### Full Region mapped to Country Subset row
+
+This example generates a region for a subset of countries.
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Country',
+ title: 'country-plugin2',
+ settings: {
+ source: 'plugins',
+ selectedCountries: ['Canada', 'US']
+ },
+ id: '123'
+ },
+ {
+ plugin: 'Region',
+ title: 'Full Region mapped to Country Subset row',
+ settings: {
+ source: 'countryRow',
+ targetRowId: '123',
+ formats: ['full']
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "country": "Canada",
+ "region": "Nova Scotia"
+ },
+ {
+ "country": "United States",
+ "region": "California"
+ },
+ {
+ "country": "Canada",
+ "region": "Northwest Territories"
+ },
+ {
+ "country": "Canada",
+ "region": "Alberta"
+ },
+ {
+ "country": "Canada",
+ "region": "Northwest Territories"
+ },
+ {
+ "country": "Canada",
+ "region": "British Columbia"
+ },
+ {
+ "country": "United States",
+ "region": "Arkansas"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/Region/Region.generate.ts b/packages/plugins/src/dataTypes/Region/Region.generate.ts
new file mode 100644
index 000000000..cf2081aab
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/Region.generate.ts
@@ -0,0 +1,59 @@
+import { countryList } from '../../../../_plugins';
+import { getRandomArrayValue } from '~utils/randomUtils';
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { Region, CountryType } from '~types/countries';
+import { RegionFormat } from './Region.state';
+
+// used for caching purposes
+const countryRegions: any = {};
+
+export const generate = (data: DTGenerationData): DTGenerateResult => {
+ const { rowState, countryData, existingRowData } = data;
+
+ let country: CountryType;
+
+ if (rowState.source === 'countryRow') {
+ const countryRow = existingRowData.find(({ id }: any) => id === rowState.targetRowId);
+ country = countryRow!.data.countryDataType;
+ } else {
+ const list = rowState.source === 'anyRegion' ? countryList : rowState.selectedCountries;
+ country = getRandomArrayValue(list);
+ }
+
+ if (!country) {
+ return {
+ display: '',
+ countryDataType: ''
+ };
+ }
+
+ if (!rowState.formats.length) {
+ return {
+ display: '',
+ displayFormat: '',
+ countryDataType: country
+ };
+ }
+
+ const displayFormat = getRandomArrayValue(rowState.formats) as RegionFormat;
+ if (countryRegions[country]) {
+ return {
+ display: getRandomArrayValue(countryRegions[country][displayFormat]),
+ displayFormat,
+ countryDataType: country
+ };
+ } else {
+ const selectedCountryData = countryData[country];
+
+ countryRegions[country] = {
+ full: selectedCountryData.regions.map((i: Region) => i.regionName),
+ short: selectedCountryData.regions.map((i: Region) => i.regionShort)
+ };
+
+ return {
+ display: getRandomArrayValue(countryRegions[country][displayFormat]),
+ displayFormat,
+ countryDataType: country
+ };
+ }
+};
diff --git a/packages/plugins/src/dataTypes/Region/Region.scss b/packages/plugins/src/dataTypes/Region/Region.scss
new file mode 100644
index 000000000..a013c6a46
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/Region.scss
@@ -0,0 +1,3 @@
+.buttonLabel {
+ margin-top: 4px;
+}
diff --git a/packages/plugins/src/dataTypes/Region/Region.scss.d.ts b/packages/plugins/src/dataTypes/Region/Region.scss.d.ts
new file mode 100644
index 000000000..47b5f3c54
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/Region.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace RegionScssNamespace {
+ export interface IRegionScss {
+ buttonLabel: string;
+ }
+}
+
+declare const RegionScssModule: RegionScssNamespace.IRegionScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: RegionScssNamespace.IRegionScss;
+};
+
+export = RegionScssModule;
diff --git a/packages/plugins/src/dataTypes/Region/Region.state.tsx b/packages/plugins/src/dataTypes/Region/Region.state.tsx
new file mode 100644
index 000000000..128a8e938
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/Region.state.tsx
@@ -0,0 +1,26 @@
+import { CountryType } from '../../';
+
+export type RegionSource = 'anyRegion' | 'countries' | 'countryRow';
+export type RegionFormat = 'full' | 'short';
+
+export type RegionState = {
+ source: RegionSource;
+ targetRowId: string;
+ selectedCountries: CountryType[];
+ formats: RegionFormat[];
+};
+
+export type RegionStateAny = Pick;
+export type RegionStateCountryRow = Pick;
+export type RegionStateCountries = Pick;
+
+export const initialState: RegionState = {
+ source: 'anyRegion',
+ selectedCountries: [],
+ targetRowId: '',
+ formats: ['full']
+};
+
+export const defaultGenerationOptions = initialState;
+
+export type GenerationOptionsType = RegionStateAny | RegionStateCountryRow | RegionStateCountries;
diff --git a/packages/plugins/src/dataTypes/Region/Region.store.tsx b/packages/plugins/src/dataTypes/Region/Region.store.tsx
new file mode 100644
index 000000000..2eee5eaad
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/Region.store.tsx
@@ -0,0 +1,61 @@
+import { createSelector } from 'reselect';
+import { DTCustomProps } from '~types/dataTypes';
+import { getSortedRowsArray } from '~store/generator/generator.selectors';
+import { RegionState } from './Region.state';
+import { REMOVE_ROW, CONFIGURE_DATA_TYPE, SELECT_DATA_TYPE } from '~store/generator/generator.actions';
+
+// custom selector that extracts information about the country fields, needed by this component. The
+// core script handles processing this and passing it back via a `countryRows` prop to our Options component, as defined
+// in the `customProps` bit below
+const getCountryRows = createSelector(
+ getSortedRowsArray,
+ (rows) => rows.map((row, index) => ({ ...row, index })).filter(({ dataType, data }) => (
+ dataType === 'Country' && data.source === 'plugins'
+ ))
+);
+
+export const customProps: DTCustomProps = {
+ countryRows: getCountryRows
+};
+
+export const actionInterceptors = {
+ // when a Country plugin row is removed, clean up any region fields that may have been mapped to it
+ [REMOVE_ROW]: (countryRowId: string, rowState: RegionState, actionPayload: any): RegionState | null => {
+ if (actionPayload.id === rowState.targetRowId) {
+ return {
+ ...rowState,
+ source: 'anyRegion',
+ targetRowId: ''
+ };
+ }
+ return null;
+ },
+
+ // check any mapped Country rows don't make changes to their config that invalidates the region mapping
+ [CONFIGURE_DATA_TYPE]: (countryRowId: string, rowState: RegionState, actionPayload: any): RegionState | null => {
+ if (actionPayload.id === rowState.targetRowId) {
+ if (actionPayload.data.source !== 'plugins') {
+ return {
+ ...rowState,
+ source: 'anyRegion',
+ targetRowId: ''
+ };
+ }
+ }
+ return null;
+ },
+
+ // when a user changes a Country row to something else, update any region mapping
+ [SELECT_DATA_TYPE]: (countryRowId: string, rowState: RegionState, actionPayload: any): RegionState | null => {
+ if (actionPayload.id === rowState.targetRowId) {
+ if (actionPayload.value !== 'Country') {
+ return {
+ ...rowState,
+ source: 'anyRegion',
+ targetRowId: ''
+ };
+ }
+ }
+ return null;
+ }
+};
diff --git a/packages/plugins/src/dataTypes/Region/Region.tsx b/packages/plugins/src/dataTypes/Region/Region.tsx
new file mode 100644
index 000000000..cf1d62a6c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/Region.tsx
@@ -0,0 +1,209 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import { DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { countryList } from '../../../../_plugins';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import styles from './Region.scss';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import { removeItem } from '~utils/arrayUtils';
+import { getI18nString } from '~utils/langUtils';
+import { RegionSource, RegionFormat } from './Region.state';
+
+const RegionDialog = ({ visible, data, id, onClose, onSetFormats, countryI18n, coreI18n, i18n, onUpdate, countryRows }: any) => {
+ const countryPluginRows = countryRows.map(({ index, id, title }: any) => ({ value: id, label: `${i18n.row} #${index + 1}: ${title}` }));
+
+ const countryPluginRowsExist = countryPluginRows.length > 0;
+
+ const onUpdateSource = (source: RegionSource): void => {
+ const newValues = {
+ ...data,
+ source
+ };
+
+ // always autoselect the first Country row when switching to `Country Row` as the source
+ if (source === 'countryRow') {
+ newValues.targetRowId = countryPluginRows[0].value;
+ }
+ onUpdate(newValues);
+ };
+
+ const onChangeTargetRow = (row: DropdownOption): void => {
+ onUpdate({
+ ...data,
+ targetRowId: row.value
+ });
+ };
+
+ const onSelectCountries = (countries: any): void => {
+ onUpdate({
+ ...data,
+ selectedCountries: countries ? countries.map(({ value }: DropdownOption) => value) : []
+ });
+ };
+
+ const getCountryRow = (): React.ReactNode => {
+ if (data.source !== 'countryRow') {
+ return null;
+ }
+
+ return ;
+ };
+
+ const getCountryPluginsList = (): React.ReactNode => {
+ if (data.source !== 'countries') {
+ return null;
+ }
+ const countryPluginOptions = countryList.map((countryName) => ({
+ value: countryName,
+ label: countryI18n[countryName].countryName
+ }));
+
+ return (
+
+ );
+ };
+
+ return (
+
+
+
{i18n.selectRegions}
+
+ {i18n.explanation}
+
+ {i18n.source}
+
+
+ onUpdateSource('anyRegion')}
+ name={`${id}-source`}
+ checked={data.source === 'anyRegion'}
+ tooltip={i18n.anyDesc}
+ />
+ onUpdateSource('countries')}
+ name={`${id}-source`}
+ checked={data.source === 'countries'}
+ tooltip={i18n.countriesDesc}
+ />
+ onUpdateSource('countryRow')}
+ name={`${id}-source`}
+ checked={data.source === 'countryRow'}
+ tooltip={i18n.rowDesc}
+ disabled={!countryPluginRowsExist}
+ />
+
+
+ {getCountryRow()}
+ {getCountryPluginsList()}
+
+ {i18n.format}
+
+
+ onSetFormats('full', e.target.checked)}
+ />
+ {i18n.full}
+ onSetFormats('short', e.target.checked)}
+ />
+ {i18n.short}
+
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ id, data, coreI18n, i18n, countryI18n, onUpdate, countryRows }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+ const numSelected = data.selectedCountries.length;
+
+ const onSetFormats = (field: RegionFormat, checked: boolean): void => {
+ let formats = [...data.formats];
+
+ if (checked) {
+ formats.push(field);
+ } else {
+ formats = removeItem(formats, field);
+ }
+
+ onUpdate({
+ ...data,
+ formats
+ });
+ };
+
+ let label = '';
+ if (data.source === 'anyRegion') {
+ label = i18n.anyRegion;
+ } else if (data.source === 'countries') {
+ if (numSelected === 1) {
+ label = i18n.anyRegionFromCountry;
+ } else {
+ label = getI18nString(i18n.anyRegionFromCountries, [numSelected]);
+ }
+ } else if (data.source === 'countryRow') {
+ const row = countryRows.find((row: any) => row.id === data.targetRowId);
+ const rowNum = row.index + 1;
+ label = `${i18n.countryRow} #${rowNum}`;
+ }
+
+ return (
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+ setDialogVisibility(false)}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => (
+
+ {i18n.DESC}
+
+);
+
+export const getMetadata = (): DTMetadata => ({
+ sql: {
+ field: 'varchar(50) default NULL',
+ field_Oracle: 'varchar2(50) default NULL',
+ field_MSSQL: 'VARCHAR(50) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/Region/Region.worker.ts b/packages/plugins/src/dataTypes/Region/Region.worker.ts
new file mode 100644
index 000000000..0d19c07d3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/Region.worker.ts
@@ -0,0 +1,6 @@
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Region.generate';
+
+export const onmessage = (e: DTWorkerOnMessage): void => {
+ postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/dataTypes/Region/__tests__/Region.test.tsx b/packages/plugins/src/dataTypes/Region/__tests__/Region.test.tsx
new file mode 100644
index 000000000..4b8f071fc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/__tests__/Region.test.tsx
@@ -0,0 +1,51 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help, Options } from '../Region';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ data: {
+ selectedCountries: []
+ },
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ gridPanelDimensions: { width: 100, height: 100 },
+ onUpdate: () => {}
+};
+
+const optionsProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ gridPanelDimensions: { width: 100, height: 100 },
+ isCountryNamesLoading: false,
+ isCountryNamesLoaded: false,
+ countryNamesMap: null
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('Options', () => {
+ it('renders', () => {
+ const { container } = render(
+ {}}
+ countryRows={[]}
+ />
+ );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Region/bundle.ts b/packages/plugins/src/dataTypes/Region/bundle.ts
new file mode 100644
index 000000000..39789a9c7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/bundle.ts
@@ -0,0 +1,15 @@
+import { DTBundle } from '~types/dataTypes';
+import { Help, Options, getMetadata } from './Region';
+import { customProps, actionInterceptors } from './Region.store';
+import { initialState } from './Region.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ Help,
+ getMetadata,
+ customProps,
+ actionInterceptors
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Region/config.ts b/packages/plugins/src/dataTypes/Region/config.ts
new file mode 100644
index 000000000..1a2b05479
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/config.ts
@@ -0,0 +1,9 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'geo',
+ fieldGroupOrder: 40,
+ dependencies: ['Country', 'Names']
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Region/i18n/ar.json b/packages/plugins/src/dataTypes/Region/i18n/ar.json
new file mode 100644
index 000000000..33c251df6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/ar.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "منطقة",
+ "DESC": "يولد مقاطعات أو ولايات أو أقاليم أو مقاطعات كندية عشوائية ، بناءً على الخيارات التي تحددها.",
+ "DEFAULT_TITLE": "منطقة",
+ "helpText": "يحدد الخياران الفرعيان للاسم الكامل والاختصار ما إذا كان الناتج سيحتوي على السلسلة الكاملة (مثل \"كولومبيا البريطانية\") أو اختصارها (مثل \"BC\"). بالنسبة لمقاطعات المملكة المتحدة ، يكون الاختصار هو رمز تشابمان القياسي المكون من 3 أحرف.",
+ "explanation": "يتحكم هذا في أسماء المناطق التي سيتم إنشاؤها بواسطة هذا الحقل.",
+ "full": "ممتلئ",
+ "short": "قصيرة",
+ "noCountriesSelected": "لم يتم اختيار دول.",
+ "countries": "بلدان",
+ "country": "بلد",
+ "anyRegion": "أي منطقة",
+ "source": "مصدر",
+ "format": "شكل",
+ "selectRegions": "حدد المناطق",
+ "anyDesc": "يولد أي اسم منطقة.",
+ "countriesDesc": "يولد مناطق لأي من البلدان المحددة.",
+ "countryRow": "صف البلد",
+ "rowDesc": "يتيح لك هذا تحديد حقل بلد معين في مجموعة البيانات الخاصة بك التي تحتوي على مكونات البلد الإضافية كمصدر لها. يُنشئ المناطق التي تحدد القيمة الموجودة في هذا الحقل.",
+ "row": "صف",
+ "anyRegionFromCountry": "أي منطقة من بلد واحد",
+ "anyRegionFromCountries": "أي منطقة من دول %1"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/de.json b/packages/plugins/src/dataTypes/Region/i18n/de.json
new file mode 100644
index 000000000..6ee00aca8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/de.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Region",
+ "DESC": "Generiert zufällige kanadische Provinzen, Bundesstaaten, Territorien oder Landkreise basierend auf den von Ihnen ausgewählten Optionen.",
+ "DEFAULT_TITLE": "region",
+ "helpText": "Die Unteroptionen Vollständiger Name und Abkürzung bestimmen, ob die Ausgabe die vollständige Zeichenfolge (z. B. \"British Columbia\") oder deren Abkürzung (z. B. \"BC\") enthält. Für Grafschaften in Großbritannien ist die Abkürzung der standardmäßige 3-stellige Chapman-Code.",
+ "explanation": "Hiermit wird gesteuert, welche Regionsnamen von diesem Feld generiert werden.",
+ "full": "Voll",
+ "short": "Kurz",
+ "noCountriesSelected": "Keine Länder ausgewählt.",
+ "countries": "Länder",
+ "country": "Land",
+ "anyRegion": "Jede Region",
+ "source": "Quelle",
+ "format": "Format",
+ "selectRegions": "Wählen Sie Regionen",
+ "anyDesc": "Erzeugt einen beliebigen Regionsnamen.",
+ "countriesDesc": "Generiert Regionen für eines der angegebenen Länder.",
+ "countryRow": "Country Row",
+ "rowDesc": "Auf diese Weise können Sie ein bestimmtes Länderfeld in Ihrem Datensatz bestimmen, dessen Quelle Länder-Plugins sind. Es werden Regionen generiert, die dem Wert in diesem Feld zugeordnet sind.",
+ "row": "Reihe",
+ "anyRegionFromCountry": "Jede Region aus 1 Land",
+ "anyRegionFromCountries": "Jede Region aus %1 Ländern"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/en.json b/packages/plugins/src/dataTypes/Region/i18n/en.json
new file mode 100644
index 000000000..84e15797c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/en.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Region",
+ "DESC": "Generates random Canadian provinces, states, territories or counties, based on the options you select.",
+ "DEFAULT_TITLE": "region",
+ "helpText": "The Full Name and Abbreviation sub-options determine whether the output will contain the full string (e.g. \"British Columbia\") or its abbreviation (e.g. \"BC\"). For UK counties, the abbreviation is the standard 3-character Chapman code.",
+ "explanation": "This controls what region names will be generated by this field.",
+ "full": "Full",
+ "short": "Short",
+ "noCountriesSelected": "No countries selected.",
+ "countries": "Countries",
+ "country": "Country",
+ "anyRegion": "Any region",
+ "source": "Source",
+ "format": "Format",
+ "selectRegions": "Select Regions",
+ "anyDesc": "Generates any region name.",
+ "countriesDesc": "Generates regions for any of the specified countries.",
+ "countryRow": "Country Row",
+ "rowDesc": "This lets you pinpoint a specific Country field in your data set that has Country Plugins as its source. It generates regions that map to the value in that field.",
+ "row": "Row",
+ "anyRegionFromCountry": "Any region from 1 country",
+ "anyRegionFromCountries": "Any region from %1 countries"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/es.json b/packages/plugins/src/dataTypes/Region/i18n/es.json
new file mode 100644
index 000000000..095ac5112
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/es.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Región",
+ "DESC": "Genera provincias, estados, territorios o condados canadienses aleatorios, según las opciones que seleccione.",
+ "DEFAULT_TITLE": "región",
+ "helpText": "Las subopciones de nombre completo y abreviatura determinan si la salida contendrá la cadena completa (por ejemplo, \"Columbia Británica\") o su abreviatura (por ejemplo, \"BC\"). Para los condados del Reino Unido, la abreviatura es el código Chapman estándar de 3 caracteres.",
+ "explanation": "Esto controla qué nombres de región generará este campo.",
+ "full": "Llena",
+ "short": "Corta",
+ "noCountriesSelected": "No se seleccionó ningún país.",
+ "countries": "Países",
+ "country": "País",
+ "anyRegion": "Cualquier región",
+ "source": "Fuente",
+ "format": "Formato",
+ "selectRegions": "Seleccionar regiones",
+ "anyDesc": "Genera cualquier nombre de región.",
+ "countriesDesc": "Genera regiones para cualquiera de los países especificados.",
+ "countryRow": "Fila de país",
+ "rowDesc": "Esto le permite identificar un campo de país específico en su conjunto de datos que tiene complementos de país como fuente. Genera regiones que se asignan al valor en ese campo.",
+ "row": "Fila",
+ "anyRegionFromCountry": "Cualquier región de 1 país",
+ "anyRegionFromCountries": "Cualquier región de %1 países"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/fr.json b/packages/plugins/src/dataTypes/Region/i18n/fr.json
new file mode 100644
index 000000000..b6d6e8bf0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/fr.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Région",
+ "DESC": "Génère des provinces, états, territoires ou comtés canadiens aléatoires, selon les options que vous sélectionnez.",
+ "DEFAULT_TITLE": "région",
+ "helpText": "Les sous-options Nom complet et Abréviation déterminent si la sortie contiendra la chaîne complète (par exemple «Colombie-Britannique») ou son abréviation (par exemple «BC»). Pour les comtés britanniques, l'abréviation est le code Chapman standard à 3 caractères.",
+ "explanation": "Cela contrôle les noms de région générés par ce champ.",
+ "full": "Pleine",
+ "short": "Courte",
+ "noCountriesSelected": "Aucun pays sélectionné.",
+ "countries": "Des pays",
+ "country": "Pays",
+ "anyRegion": "Toute région",
+ "source": "La source",
+ "format": "Format",
+ "selectRegions": "Sélectionnez les régions",
+ "anyDesc": "Génère n'importe quel nom de région.",
+ "countriesDesc": "Génère des régions pour l'un des pays spécifiés.",
+ "countryRow": "Country Row",
+ "rowDesc": "Cela vous permet d'identifier un champ Pays spécifique dans votre ensemble de données dont la source est les Plugins Pays. Il génère des régions qui correspondent à la valeur de ce champ.",
+ "row": "Rangée",
+ "anyRegionFromCountry": "Toute région d'un pays",
+ "anyRegionFromCountries": "Toute région de %1 pays"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/hi.json b/packages/plugins/src/dataTypes/Region/i18n/hi.json
new file mode 100644
index 000000000..5f4766cca
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/hi.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "क्षेत्र",
+ "DESC": "आपके द्वारा चुने गए विकल्पों के आधार पर यादृच्छिक कनाडाई प्रांत, राज्य, क्षेत्र या काउंटी उत्पन्न करता है।",
+ "DEFAULT_TITLE": "क्षेत्र",
+ "helpText": "पूरा नाम और संक्षिप्त नाम उप-विकल्प यह निर्धारित करते हैं कि आउटपुट में पूरी स्ट्रिंग होगी (जैसे \"ब्रिटिश कोलंबिया\") या उसका संक्षिप्त नाम (जैसे \"BC\")। यूके काउंटियों के लिए, संक्षिप्त नाम मानक 3-वर्ण चैपमैन कोड है।",
+ "explanation": "यह नियंत्रित करता है कि इस क्षेत्र द्वारा कौन से क्षेत्र के नाम उत्पन्न किए जाएंगे।",
+ "full": "भरा हुआ",
+ "short": "छोटा",
+ "noCountriesSelected": "कोई देश नहीं चुना गया.",
+ "countries": "देशों",
+ "country": "देश",
+ "anyRegion": "कोई भी क्षेत्र",
+ "source": "स्रोत",
+ "format": "प्रारूप",
+ "selectRegions": "क्षेत्रों का चयन करें",
+ "anyDesc": "किसी भी क्षेत्र का नाम उत्पन्न करता है।",
+ "countriesDesc": "निर्दिष्ट देशों में से किसी के लिए क्षेत्र उत्पन्न करता है।",
+ "countryRow": "देश पंक्ति",
+ "rowDesc": "यह आपको अपने डेटा सेट में एक विशिष्ट देश फ़ील्ड को इंगित करने देता है जिसमें इसके स्रोत के रूप में देश प्लगइन्स हैं। यह उन क्षेत्रों को उत्पन्न करता है जो उस क्षेत्र में मूल्य के लिए मैप करते हैं।",
+ "row": "पंक्ति",
+ "anyRegionFromCountry": "1 देश का कोई भी क्षेत्र",
+ "anyRegionFromCountries": "%1 देशों से कोई भी क्षेत्र"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/ja.json b/packages/plugins/src/dataTypes/Region/i18n/ja.json
new file mode 100644
index 000000000..6536c6915
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/ja.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "領域",
+ "DESC": "選択したオプションに基づいて、ランダムなカナダの州、州、準州、または郡を生成します。",
+ "DEFAULT_TITLE": "領域",
+ "helpText": "[フルネーム]および[略語]サブオプションは、出力に完全な文字列(「ブリティッシュコロンビア」など)を含めるか、その略語(「BC」など)を含めるかを決定します。 英国の郡の場合、略語は標準の3文字のチャップマンコードです。",
+ "explanation": "これは、このフィールドによって生成されるリージョン名を制御します。",
+ "full": "フル",
+ "short": "ショーツ",
+ "noCountriesSelected": "国が選択されていません。",
+ "countries": "国",
+ "country": "国",
+ "anyRegion": "任意の地域",
+ "source": "ソース",
+ "format": "フォーマット",
+ "selectRegions": "地域を選択",
+ "anyDesc": "任意のリージョン名を生成します。",
+ "countriesDesc": "指定された国のいずれかの地域を生成します。",
+ "countryRow": "国の行",
+ "rowDesc": "これにより、カントリープラグインをソースとして持つデータセット内の特定の国フィールドを特定できます。 そのフィールドの値にマップする領域を生成します。",
+ "row": "行",
+ "anyRegionFromCountry": "1か国の任意の地域",
+ "anyRegionFromCountries": "%1諸国の任意の地域"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/nl.json b/packages/plugins/src/dataTypes/Region/i18n/nl.json
new file mode 100644
index 000000000..2e19ead03
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/nl.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Regio",
+ "DESC": "Genereert willekeurige Canadese provincies, staten, territoria of provincies op basis van de opties die u selecteert.",
+ "DEFAULT_TITLE": "regio",
+ "helpText": "De subopties Volledige naam en afkorting bepalen of de uitvoer de volledige tekenreeks (bijvoorbeeld \"British Columbia\") of de afkorting ervan (bijvoorbeeld \"BC\") zal bevatten. Voor Britse provincies is de afkorting de standaard Chapman-code van 3 tekens.",
+ "explanation": "Dit bepaalt welke regionamen door dit veld worden gegenereerd.",
+ "full": "Vol",
+ "short": "Kort",
+ "noCountriesSelected": "Geen landen geselecteerd.",
+ "countries": "Landen",
+ "country": "Land",
+ "anyRegion": "Elke regio",
+ "source": "Bron",
+ "format": "Formaat",
+ "selectRegions": "Selecteer Regio's",
+ "anyDesc": "Genereert een regionale naam.",
+ "countriesDesc": "Genereert regio's voor elk van de opgegeven landen.",
+ "countryRow": "Country Row",
+ "rowDesc": "Hiermee kunt u een specifiek landveld in uw dataset lokaliseren dat landplug-ins als bron heeft. Het genereert regio's die zijn toegewezen aan de waarde in dat veld.",
+ "row": "Rij",
+ "anyRegionFromCountry": "Elke regio uit 1 land",
+ "anyRegionFromCountries": "Elke regio uit %1 landen"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/pt.json b/packages/plugins/src/dataTypes/Region/i18n/pt.json
new file mode 100644
index 000000000..f825c858c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/pt.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Região",
+ "DESC": "Gera províncias, estados, territórios ou condados canadenses aleatórios, com base nas opções selecionadas.",
+ "DEFAULT_TITLE": "região",
+ "helpText": "As subopções Nome completo e Abreviação determinam se a saída conterá a string completa (por exemplo, \"British Columbia\") ou sua abreviação (por exemplo, \"BC\"). Para condados do Reino Unido, a abreviatura é o código Chapman padrão de 3 caracteres.",
+ "explanation": "Isso controla quais nomes de região serão gerados por este campo.",
+ "full": "Cheia",
+ "short": "Baixa",
+ "noCountriesSelected": "Nenhum país selecionado.",
+ "countries": "Países",
+ "country": "País",
+ "anyRegion": "Qualquer região",
+ "source": "Fonte",
+ "format": "Formato",
+ "selectRegions": "Selecione as regiões",
+ "anyDesc": "Gera qualquer nome de região.",
+ "countriesDesc": "Gera regiões para qualquer um dos países especificados.",
+ "countryRow": "Country Row",
+ "rowDesc": "Isso permite que você localize um campo de país específico em seu conjunto de dados que tem Plug-ins de país como sua fonte. Ele gera regiões que mapeiam para o valor naquele campo.",
+ "row": "Linha",
+ "anyRegionFromCountry": "Qualquer região de 1 país",
+ "anyRegionFromCountries": "Qualquer região de %1 países"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/ru.json b/packages/plugins/src/dataTypes/Region/i18n/ru.json
new file mode 100644
index 000000000..798298ea2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/ru.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Pегион",
+ "DESC": "Генерирует случайные канадские провинции, штаты, территории или округа на основе выбранных вами параметров.",
+ "DEFAULT_TITLE": "pегион",
+ "helpText": "Подпараметры «Полное имя» и «Аббревиатура» определяют, будет ли вывод содержать полную строку (например, «Британская Колумбия») или ее аббревиатуру (например, «BC»). Для округов Великобритании аббревиатура представляет собой стандартный трехзначный код Чепмена.",
+ "explanation": "Это определяет, какие имена регионов будут генерироваться этим полем.",
+ "full": "Полный",
+ "short": "короткий",
+ "noCountriesSelected": "Страны не выбраны.",
+ "countries": "Страны",
+ "country": "Страна",
+ "anyRegion": "Любой регион",
+ "source": "Источник",
+ "format": "Формат",
+ "selectRegions": "Выберите регионы",
+ "anyDesc": "Генерирует любое имя региона.",
+ "countriesDesc": "Генерирует регионы для любой из указанных стран.",
+ "countryRow": "Кантри Роу",
+ "rowDesc": "Это позволяет вам точно определить конкретное поле страны в вашем наборе данных, источником которого являются плагины страны. Он генерирует области, которые сопоставляются со значением в этом поле.",
+ "row": "Строка",
+ "anyRegionFromCountry": "Любой регион из 1 страны",
+ "anyRegionFromCountries": "Любой регион из %1 страны"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/ta.json b/packages/plugins/src/dataTypes/Region/i18n/ta.json
new file mode 100644
index 000000000..7d58cb041
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/ta.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "பிராந்தியம்",
+ "DESC": "நீங்கள் தேர்ந்தெடுக்கும் விருப்பங்களின் அடிப்படையில் சீரற்ற கனேடிய மாகாணங்கள், மாநிலங்கள், பிரதேசங்கள் அல்லது மாவட்டங்களை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "பகுதி",
+ "helpText": "வெளியீட்டில் முழு சரம் (எ.கா. \"பிரிட்டிஷ் கொலம்பியா\") அல்லது அதன் சுருக்கம் (எ.கா. \"கிமு\") உள்ளதா என்பதை முழு பெயர் மற்றும் சுருக்க துணை விருப்பங்கள் தீர்மானிக்கின்றன. இங்கிலாந்து மாவட்டங்களைப் பொறுத்தவரை, சுருக்கமானது நிலையான 3-எழுத்து சாப்மேன் குறியீடாகும்.",
+ "explanation": "இந்த புலத்தால் எந்த பிராந்திய பெயர்கள் உருவாக்கப்படும் என்பதை இது கட்டுப்படுத்துகிறது.",
+ "full": "முழு",
+ "short": "குறுகிய",
+ "noCountriesSelected": "எந்த நாடுகளும் தேர்ந்தெடுக்கப்படவில்லை.",
+ "countries": "நாடுகள்",
+ "country": "நாடு",
+ "anyRegion": "எந்த பிராந்தியமும்",
+ "source": "மூல",
+ "format": "வடிவம்",
+ "selectRegions": "பிராந்தியங்களைத் தேர்ந்தெடுக்கவும்",
+ "anyDesc": "எந்த பிராந்திய பெயரையும் உருவாக்குகிறது.",
+ "countriesDesc": "குறிப்பிட்ட எந்த நாடுகளுக்கும் பிராந்தியங்களை உருவாக்குகிறது.",
+ "countryRow": "நாட்டின் வரிசை",
+ "rowDesc": "இது உங்கள் தரவுத் தொகுப்பில் ஒரு குறிப்பிட்ட நாட்டு புலத்தை அதன் மூல செருகிகளைக் கொண்டிருக்கும். அந்த புலத்தின் மதிப்பைக் குறிக்கும் பகுதிகளை இது உருவாக்குகிறது.",
+ "row": "வரிசை",
+ "anyRegionFromCountry": "1 நாட்டிலிருந்து எந்த பிராந்தியமும்",
+ "anyRegionFromCountries": "%1 நாடுகளில் இருந்து எந்த பிராந்தியமும்"
+}
diff --git a/packages/plugins/src/dataTypes/Region/i18n/zh.json b/packages/plugins/src/dataTypes/Region/i18n/zh.json
new file mode 100644
index 000000000..e6f3a80bd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Region/i18n/zh.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "地区",
+ "DESC": "根据您选择的选项,生成随机的加拿大省,州,地区或县。",
+ "DEFAULT_TITLE": "地区",
+ "helpText": "全名和缩写子选项确定输出将包含完整字符串(例如“ British Columbia”)还是其缩写(例如“ BC”)。 对于英国县,该缩写是标准的3个字符的查普曼代码。",
+ "explanation": "这控制了此字段将生成的区域名称。",
+ "full": "充分",
+ "short": "短",
+ "noCountriesSelected": "未选择国家。",
+ "countries": "国别",
+ "country": "国家",
+ "anyRegion": "任何地区",
+ "source": "资源",
+ "format": "格式",
+ "selectRegions": "选择地区",
+ "anyDesc": "生成任何区域名称。",
+ "countriesDesc": "生成任何指定国家/地区的区域。",
+ "countryRow": "国家行",
+ "rowDesc": "这样,您就可以在数据集中以“国家/地区插件”作为来源的特定“国家/地区”字段中定位。 它生成映射到该字段中值的区域。",
+ "row": "行",
+ "anyRegionFromCountry": "1个国家/地区的任何地区",
+ "anyRegionFromCountries": "来自%1个国家/地区的任何地区"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/README.md b/packages/plugins/src/dataTypes/Rut/README.md
new file mode 100644
index 000000000..5e89ba810
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/README.md
@@ -0,0 +1,26 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Rut
+
+This Data Type generates a random Chilean Unique National Role Number
+
+
+### Example API Usage
+
+```javascript
+{
+ "numRows": 20,
+ "rows": [
+ {
+ "type": "Rut",
+ "title": "Rut"
+ }
+ ],
+ "export": {
+ "type": "JSON"
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/dataTypes/Rut/Rut.generate.ts b/packages/plugins/src/dataTypes/Rut/Rut.generate.ts
new file mode 100644
index 000000000..ebcd2329b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/Rut.generate.ts
@@ -0,0 +1,52 @@
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = ({ rowState }: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { formatCode, uppercaseDigit } = rowState;
+
+ const { getRandomNum } = utils.randomUtils;
+ const rutNumber = `${getRandomNum(5, 50)}${getRandomNum(0, 999)}${getRandomNum(0, 999)}`;
+ const digit = getDigit(rutNumber);
+
+ let display = '';
+
+ if (formatCode === '12.345.678-9' || formatCode === '12.345.678-9' || formatCode === '12.345.678') {
+ display = utils.numberUtils.numberFormat(Number(rutNumber), 0, ',', '.');
+ } else if (formatCode === '12345678-9' || formatCode === '123456789' || formatCode === '12345678') {
+ display = rutNumber;
+ }
+
+ if (formatCode.indexOf('-') !== -1) {
+ display += '-';
+ }
+
+ if (formatCode.indexOf('9') !== -1) {
+ display += uppercaseDigit ? digit.toUpperCase() : digit;
+ }
+
+ return {
+ display,
+ rut: rutNumber,
+ digit
+ };
+};
+
+const getDigit = (rut: string): string => {
+ const rutNumReversedChars = rut.split('').reverse();
+
+ let n = 0;
+ for (let i = 0; i < rutNumReversedChars.length; i++) {
+ n += parseInt(rutNumReversedChars[i], 10) * ((i % 6) + 2);
+ }
+
+ const digit = 11 - (n % 11);
+
+ if (digit == 10) {
+ return 'k';
+ }
+ if (digit == 11) {
+ return '0';
+ }
+
+ return digit.toString();
+};
diff --git a/packages/plugins/src/dataTypes/Rut/Rut.state.tsx b/packages/plugins/src/dataTypes/Rut/Rut.state.tsx
new file mode 100644
index 000000000..57714b513
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/Rut.state.tsx
@@ -0,0 +1,19 @@
+export type FormatCode = '12345678-9' | '12.345.678-9' | '123456789' | '12.345.678-9' | '12345678' | '12.345.678' | '9';
+
+export type RutState = {
+ example: string;
+ formatCode: FormatCode;
+ uppercaseDigit: boolean;
+};
+
+export type GenerationOptionsType = Omit;
+
+export const defaultGenerationOptions = {
+ formatCode: '12345678-9' as FormatCode,
+ uppercaseDigit: true
+};
+
+export const initialState: RutState = {
+ example: '',
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/Rut/Rut.tsx b/packages/plugins/src/dataTypes/Rut/Rut.tsx
new file mode 100644
index 000000000..34102fec7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/Rut.tsx
@@ -0,0 +1,61 @@
+import * as React from 'react';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import { DTMetadata, DTOptionsProps } from '~types/dataTypes';
+
+export const Options = ({ i18n, id, data, onUpdate }: DTOptionsProps) => {
+ const options = [
+ { value: '12345678-9', label: '12345678-9' },
+ { value: '12.345.678-9', label: '12.345.678-9' },
+ { value: '123456789', label: '123456789' },
+ { value: '12.345.6789', label: '12.345.6789' },
+ { value: '12345678', label: '12345678' },
+ { value: '12.345.678', label: '12.345.678' },
+ { value: '9', label: `9 (${i18n.onlyDigit})` }
+ ];
+
+ const onChange = (field: string, value: string | boolean): void => {
+ onUpdate({
+ ...data,
+ [field]: value
+ });
+ };
+
+ // the uppercase checkbox only applies to formats that include the final digit
+ const getUppercaseCheckbox = (): JSX.Element | null => {
+ if (data.formatCode === '12345678' || data.formatCode === '12.345.678') {
+ return null;
+ }
+
+ return (
+
+ onChange('uppercaseDigit', e.target.checked)}
+ />
+ {i18n.digitUppercase}
+
+ );
+ };
+
+ return (
+
+
+ onChange('formatCode', value)} />
+
+ {getUppercaseCheckbox()}
+
+ );
+};
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(15) default NULL',
+ field_Oracle: 'varchar2(15) default NULL',
+ field_MSSQL: 'VARCHAR(15) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/Rut/Rut.worker.ts b/packages/plugins/src/dataTypes/Rut/Rut.worker.ts
new file mode 100644
index 000000000..e7f669077
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/Rut.worker.ts
@@ -0,0 +1,20 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Rut.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
+
+
+
+
+
+
diff --git a/packages/plugins/src/dataTypes/Rut/bundle.ts b/packages/plugins/src/dataTypes/Rut/bundle.ts
new file mode 100644
index 000000000..7a1e1abb5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/bundle.ts
@@ -0,0 +1,11 @@
+import { DTBundle } from '~types/dataTypes';
+import { Options, getMetadata } from './Rut';
+import { initialState } from './Rut.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Rut/config.ts b/packages/plugins/src/dataTypes/Rut/config.ts
new file mode 100644
index 000000000..33dc2aff8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'countrySpecific',
+ fieldGroupOrder: 20
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/ar.json b/packages/plugins/src/dataTypes/Rut/i18n/ar.json
new file mode 100644
index 000000000..c2396f830
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/ar.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "رقم RUT التشيلي",
+ "DESC": "يُنشئ رقم تعريف RUT / RUN الوطني الشيلي.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "فقط رقم التحقق",
+ "digitUppercase": "رقم كبير"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/de.json b/packages/plugins/src/dataTypes/Rut/i18n/de.json
new file mode 100644
index 000000000..9215c1e5d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/de.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "Chilenische RUT-Nummer",
+ "DESC": "Generiert eine chilenische nationale RUT / RUN-Identifikationsnummer.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "Nur Bestätigungsziffer",
+ "digitUppercase": "Großbuchstabe"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/en.json b/packages/plugins/src/dataTypes/Rut/i18n/en.json
new file mode 100644
index 000000000..2857958ed
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/en.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "Chilean RUT number",
+ "DESC": "Generates a RUT/RUN Chilean National Identification Number.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "Only verification digit",
+ "digitUppercase": "Uppercase digit"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/es.json b/packages/plugins/src/dataTypes/Rut/i18n/es.json
new file mode 100644
index 000000000..56a10c07d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/es.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "Número de RUT chileno",
+ "DESC": "Genera un Número de Identificación Nacional de Chile RUT / RUN.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "Solo dígito de verificación",
+ "digitUppercase": "Dígito en mayúsculas"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/fr.json b/packages/plugins/src/dataTypes/Rut/i18n/fr.json
new file mode 100644
index 000000000..b4bc3d649
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/fr.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "Numéro RUT chilien",
+ "DESC": "Génère un numéro d'identification national chilien RUT / RUN.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "Seul le chiffre de vérification",
+ "digitUppercase": "Chiffre majuscule"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/hi.json b/packages/plugins/src/dataTypes/Rut/i18n/hi.json
new file mode 100644
index 000000000..50c106631
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/hi.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "चिली आरयूटी नंबर",
+ "DESC": "एक RUT/RUN चिली राष्ट्रीय पहचान संख्या उत्पन्न करता है।",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "केवल सत्यापन अंक",
+ "digitUppercase": "अपरकेस अंक"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/ja.json b/packages/plugins/src/dataTypes/Rut/i18n/ja.json
new file mode 100644
index 000000000..17ddc40db
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/ja.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "チリのRUT番号",
+ "DESC": "RUT / RUNチリ国民識別番号を生成します。",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "確認桁のみ",
+ "digitUppercase": "大文字"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/nl.json b/packages/plugins/src/dataTypes/Rut/i18n/nl.json
new file mode 100644
index 000000000..9a022682f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/nl.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "Chileens RUT-nummer",
+ "DESC": "Genereert een RUT / RUN Chileens nationaal identificatienummer.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "Alleen verificatiecijfer",
+ "digitUppercase": "Cijfer in hoofdletters"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/pt.json b/packages/plugins/src/dataTypes/Rut/i18n/pt.json
new file mode 100644
index 000000000..198f724c2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/pt.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "Número RUT chileno",
+ "DESC": "Gera um número de identificação nacional chileno RUT / RUN.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "Apenas dígito de verificação",
+ "digitUppercase": "Dígito maiúsculo"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/ru.json b/packages/plugins/src/dataTypes/Rut/i18n/ru.json
new file mode 100644
index 000000000..c0c15c06d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/ru.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "Чилийский номер RUT",
+ "DESC": "Генерирует чилийский национальный идентификационный номер RUT/RUN.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "Только проверочная цифра",
+ "digitUppercase": "Заглавная цифра"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/ta.json b/packages/plugins/src/dataTypes/Rut/i18n/ta.json
new file mode 100644
index 000000000..d392562ee
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/ta.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "சிலி RUT எண்",
+ "DESC": "RUT / RUN சிலி தேசிய அடையாள எண்ணை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "சரிபார்ப்பு இலக்கத்தை மட்டுமே",
+ "digitUppercase": "பெரிய எண்"
+}
diff --git a/packages/plugins/src/dataTypes/Rut/i18n/zh.json b/packages/plugins/src/dataTypes/Rut/i18n/zh.json
new file mode 100644
index 000000000..f5c5ea7b5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Rut/i18n/zh.json
@@ -0,0 +1,7 @@
+{
+ "NAME": "智利RUT号码",
+ "DESC": "生成RUT / RUN智利国家标识号。",
+ "DEFAULT_TITLE": "rut",
+ "onlyDigit": "仅验证码",
+ "digitUppercase": "大写数字"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/SIRET.generate.ts b/packages/plugins/src/dataTypes/SIRET/SIRET.generate.ts
new file mode 100644
index 000000000..4c32604c6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/SIRET.generate.ts
@@ -0,0 +1,147 @@
+import { DTGenerateResult } from '~types/dataTypes';
+
+// data: DTGenerationData
+export const generate = (): DTGenerateResult => {
+ return { display: '' };
+};
+
+
+/*
+ // custom member vars for this Data Type
+ private $rSIREN = '';
+ private $rNIC = '';
+
+ public function generate($generator, $generationContextData) {
+ $myOption = $generationContextData["generationOptions"];
+
+ self::generateSiret();
+ if ($myOption == "SIRET") {
+ $myResult = self::getSIREN() . '-' . self::getNIC();
+ } else {
+ $myResult = self::getSIREN();
+ }
+
+ return array(
+ "display" => $myResult
+ );
+ }
+
+ private function generateSiret() {
+ $sumSiren = 0;
+ $sumSiret = 0;
+ $cleSiren= 1;
+ $cleSiret= 2;
+ $minRan = 0;
+ $maxRan = 9;
+ $siren = '';
+
+ // generation d'un siren valide
+ for ($i=0; $i<8; $i++) {
+
+ // on génére un nombre entre 0 et 9
+ $rand = mt_rand($minRan, $maxRan);
+
+ // on concatène se nombre au siret
+ $siren .= $rand;
+
+ * Le numéro SIRET est composé de 14 chiffres,
+ * dont un chiffre de contrôle (le dernier) qui permet de vérifier la validité du numéro de SIRET (SIREN + NIC).
+ * Celui-ci est calculé suivant la formule de Luhn.
+ * Le principe est le suivant : on multiplie les chiffres de rang impair à partir de la droite par 1, ceux de rang pair par 2 ;
+ * la somme des chiffres obtenus doit être multiple de 10.
+ $ctrlSiren = $rand * $cleSiren;
+ $ctrlSiret = $rand * $cleSiret;
+
+ // Si la valeur obtenu et supérieur ou egale à 10 il faut décomposer en 1+0
+ // ce qui équivaux à lui retirer 9
+ // contôle pour le siren
+ if ($ctrlSiren > 9) {
+ $sumSiren += ($ctrlSiren-9);
+ } else {
+ $sumSiren += $ctrlSiren;
+ }
+
+ // contôle pour le siret
+ if ($ctrlSiret > 9) {
+ $sumSiret += ($ctrlSiret - 9);
+ } else {
+ $sumSiret += $ctrlSiret;
+ }
+
+ // mise à jour des clés
+ if ($cleSiren == 1) {
+ $cleSiren = 2;
+ $cleSiret = 1;
+ } else {
+ $cleSiren = 1;
+ $cleSiret = 2;
+ }
+ }
+
+ // la somme doit être congrue à zéro modulo 10
+ $moduloSiren = ($sumSiren % 10);
+ if ($moduloSiren == 0) {
+ $diffSiren = 0;
+ } else {
+ $diffSiren = 10 - $moduloSiren;
+ }
+
+ $siren .= $diffSiren;
+
+ // la cle du siren est ajouté au calcul du siret
+ $ctrlSiret = $diffSiren * $cleSiret;
+
+ // contôle pour le siret
+ if ($ctrlSiret > 9) {
+ $sumSiret += ($ctrlSiret - 9);
+ } else {
+ $sumSiret += $ctrlSiret;
+ }
+
+ // aon ajoute un début de NIC au siren
+ $siret = $siren . "0000";
+
+ // la somme doit être congrue à zéro modulo 10
+ $moduloSiret = ($sumSiret % 10);
+ if ($moduloSiret == 0) {
+ $diffSiret = 0;
+ } else {
+ $diffSiret = 10 - $moduloSiret;
+ }
+
+ $siret .= $diffSiret;
+
+ $this->rSIREN = substr($siret, 0, 9);
+ $this->rNIC = substr($siret, 9, 14);
+ }
+
+ public function getRowGenerationOptionsUI($generator, $post, $colNum, $numCols) {
+ if (!isset($post["dtOption_$colNum"]) || empty($post["dtOption_$colNum"])) {
+ return false;
+ }
+ return $post["dtOption_$colNum"];
+ }
+
+ public function getRowGenerationOptionsAPI($generator, $post, $colNum, $numCols) {
+ if (!isset($post["dtOption_$colNum"]) || empty($post["dtOption_$colNum"])) {
+ return false;
+ }
+ return $post["dtOption_$colNum"];
+ }
+
+ public function getDataTypeMetadata() {
+ return array(
+ "SQLField" => "varchar(14)",
+ "SQLField_Oracle" => "varchar2(14)",
+ "SQLField_MSSQL" => "VARCHAR(14) NULL"
+ );
+ }
+
+ public function getSIREN() {
+ return $this->rSIREN;
+ }
+
+ public function getNIC() {
+ return $this->rNIC;
+ }
+*/
diff --git a/packages/plugins/src/dataTypes/SIRET/SIRET.scss b/packages/plugins/src/dataTypes/SIRET/SIRET.scss
new file mode 100644
index 000000000..0d33a2dd6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/SIRET.scss
@@ -0,0 +1,19 @@
+.row {
+ display: flex;
+ align-items: flex-start;
+ padding-bottom: 12px;
+
+ .col1 {
+ flex: 0 0 60px;
+ font-weight: bold;
+ }
+ .col2 {
+ flex: 1;
+ }
+
+ label {
+ background-color: #dfecfc;
+ border-radius: 3px;
+ padding: 1px 6px;
+ }
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/SIRET.scss.d.ts b/packages/plugins/src/dataTypes/SIRET/SIRET.scss.d.ts
new file mode 100644
index 000000000..cda3fccd7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/SIRET.scss.d.ts
@@ -0,0 +1,14 @@
+declare namespace SiretScssNamespace {
+ export interface ISiretScss {
+ col1: string;
+ col2: string;
+ row: string;
+ }
+}
+
+declare const SiretScssModule: SiretScssNamespace.ISiretScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: SiretScssNamespace.ISiretScss;
+};
+
+export = SiretScssModule;
diff --git a/packages/plugins/src/dataTypes/SIRET/SIRET.state.tsx b/packages/plugins/src/dataTypes/SIRET/SIRET.state.tsx
new file mode 100644
index 000000000..b55bd7533
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/SIRET.state.tsx
@@ -0,0 +1,17 @@
+type SiretState = {
+ example: string;
+ option: string;
+};
+
+export type GenerationOptions = {
+ option: string;
+};
+
+export const defaultGenerationOptions: GenerationOptions = {
+ option: ''
+};
+
+export const initialState: SiretState = {
+ example: '',
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/SIRET/SIRET.tsx b/packages/plugins/src/dataTypes/SIRET/SIRET.tsx
new file mode 100644
index 000000000..3d56df762
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/SIRET.tsx
@@ -0,0 +1,49 @@
+import * as React from 'react';
+import { DTHelpProps, DTOptionsProps } from '~types/dataTypes';
+import styles from './SIRET.scss';
+
+export const Options = ({ id, data, onUpdate }: DTOptionsProps) => {
+ const onChange = (e: React.FormEvent): void => {
+ // @ts-ignore
+ const value = e.target.value;
+ onUpdate({
+ ...data,
+ option: value
+ });
+ };
+
+ return (
+ <>
+
+ SIRET
+
+ SIREN
+ >
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+ {i18n.DESC}
+
+
+
{i18n.SIRET}
+
{i18n.typeSIRET}
+
+
+
{i18n.SIREN}
+
{i18n.typeSIREN}
+
+
+
+ {i18n.moreInfo}{' '}
+
+ Wikipedia - SIRET
+
+
+ >
+);
diff --git a/packages/plugins/src/dataTypes/SIRET/SIRET.worker.ts b/packages/plugins/src/dataTypes/SIRET/SIRET.worker.ts
new file mode 100644
index 000000000..44e955363
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/SIRET.worker.ts
@@ -0,0 +1,158 @@
+import { DTGenerateResult, DTWorkerOnMessage } from '~types/dataTypes';
+
+// data: DTGenerationData
+export const generate = (): DTGenerateResult => {
+ return { display: '' };
+};
+
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate());
+};
+
+/*
+ // custom member vars for this Data Type
+ private $rSIREN = '';
+ private $rNIC = '';
+
+ public function generate($generator, $generationContextData) {
+ $myOption = $generationContextData["generationOptions"];
+
+ self::generateSiret();
+ if ($myOption == "SIRET") {
+ $myResult = self::getSIREN() . '-' . self::getNIC();
+ } else {
+ $myResult = self::getSIREN();
+ }
+
+ return array(
+ "display" => $myResult
+ );
+ }
+
+ private function generateSiret() {
+ $sumSiren = 0;
+ $sumSiret = 0;
+ $cleSiren= 1;
+ $cleSiret= 2;
+ $minRan = 0;
+ $maxRan = 9;
+ $siren = '';
+
+ // generation d'un siren valide
+ for ($i=0; $i<8; $i++) {
+
+ // on génére un nombre entre 0 et 9
+ $rand = mt_rand($minRan, $maxRan);
+
+ // on concatène se nombre au siret
+ $siren .= $rand;
+
+ * Le numéro SIRET est composé de 14 chiffres,
+ * dont un chiffre de contrôle (le dernier) qui permet de vérifier la validité du numéro de SIRET (SIREN + NIC).
+ * Celui-ci est calculé suivant la formule de Luhn.
+ * Le principe est le suivant : on multiplie les chiffres de rang impair à partir de la droite par 1, ceux de rang pair par 2 ;
+ * la somme des chiffres obtenus doit être multiple de 10.
+ $ctrlSiren = $rand * $cleSiren;
+ $ctrlSiret = $rand * $cleSiret;
+
+ // Si la valeur obtenu et supérieur ou egale à 10 il faut décomposer en 1+0
+ // ce qui équivaux à lui retirer 9
+ // contôle pour le siren
+ if ($ctrlSiren > 9) {
+ $sumSiren += ($ctrlSiren-9);
+ } else {
+ $sumSiren += $ctrlSiren;
+ }
+
+ // contôle pour le siret
+ if ($ctrlSiret > 9) {
+ $sumSiret += ($ctrlSiret - 9);
+ } else {
+ $sumSiret += $ctrlSiret;
+ }
+
+ // mise à jour des clés
+ if ($cleSiren == 1) {
+ $cleSiren = 2;
+ $cleSiret = 1;
+ } else {
+ $cleSiren = 1;
+ $cleSiret = 2;
+ }
+ }
+
+ // la somme doit être congrue à zéro modulo 10
+ $moduloSiren = ($sumSiren % 10);
+ if ($moduloSiren == 0) {
+ $diffSiren = 0;
+ } else {
+ $diffSiren = 10 - $moduloSiren;
+ }
+
+ $siren .= $diffSiren;
+
+ // la cle du siren est ajouté au calcul du siret
+ $ctrlSiret = $diffSiren * $cleSiret;
+
+ // contôle pour le siret
+ if ($ctrlSiret > 9) {
+ $sumSiret += ($ctrlSiret - 9);
+ } else {
+ $sumSiret += $ctrlSiret;
+ }
+
+ // aon ajoute un début de NIC au siren
+ $siret = $siren . "0000";
+
+ // la somme doit être congrue à zéro modulo 10
+ $moduloSiret = ($sumSiret % 10);
+ if ($moduloSiret == 0) {
+ $diffSiret = 0;
+ } else {
+ $diffSiret = 10 - $moduloSiret;
+ }
+
+ $siret .= $diffSiret;
+
+ $this->rSIREN = substr($siret, 0, 9);
+ $this->rNIC = substr($siret, 9, 14);
+ }
+
+ public function getRowGenerationOptionsUI($generator, $post, $colNum, $numCols) {
+ if (!isset($post["dtOption_$colNum"]) || empty($post["dtOption_$colNum"])) {
+ return false;
+ }
+ return $post["dtOption_$colNum"];
+ }
+
+ public function getRowGenerationOptionsAPI($generator, $post, $colNum, $numCols) {
+ if (!isset($post["dtOption_$colNum"]) || empty($post["dtOption_$colNum"])) {
+ return false;
+ }
+ return $post["dtOption_$colNum"];
+ }
+
+ public function getDataTypeMetadata() {
+ return array(
+ "SQLField" => "varchar(14)",
+ "SQLField_Oracle" => "varchar2(14)",
+ "SQLField_MSSQL" => "VARCHAR(14) NULL"
+ );
+ }
+
+ public function getSIREN() {
+ return $this->rSIREN;
+ }
+
+ public function getNIC() {
+ return $this->rNIC;
+ }
+*/
diff --git a/packages/plugins/src/dataTypes/SIRET/__tests__/SIRET.ui.test.tsx b/packages/plugins/src/dataTypes/SIRET/__tests__/SIRET.ui.test.tsx
new file mode 100644
index 000000000..4c8f582b7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/__tests__/SIRET.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../SIRET';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/SIRET/bundle.ts b/packages/plugins/src/dataTypes/SIRET/bundle.ts
new file mode 100644
index 000000000..dc8c82a84
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/bundle.ts
@@ -0,0 +1,11 @@
+import { DTBundle } from '~types/dataTypes';
+import { Options, Help } from './SIRET';
+import { initialState } from './SIRET.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ Help,
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/SIRET/config.ts b/packages/plugins/src/dataTypes/SIRET/config.ts
new file mode 100644
index 000000000..07dc88903
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'countrySpecific',
+ fieldGroupOrder: 10
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/ar.json b/packages/plugins/src/dataTypes/SIRET/i18n/ar.json
new file mode 100644
index 000000000..b83228bca
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/ar.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "This Data Type generates a random SIRET/SIREN French business identification number.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "The SIRET data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " A random SIRET compound of a SIREN and NIC (508102811-00009).",
+ "typeSIREN": " A random SIREN (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "More info:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/de.json b/packages/plugins/src/dataTypes/SIRET/i18n/de.json
new file mode 100644
index 000000000..e3cfcaa7a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/de.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "Dieser Datentyp erzeugt zufällig SIRET nach dem Format, das Sie angeben.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "Der SIRET Datentyp muß das im Optionstext-Feld eingegangene Format lassen. Befestigen Sie bitte die folgenden Reihen(Rudern):",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " Ein zusammengesetzter unsicherer SIRET eines SIREN und NIC (508102811-00009).",
+ "typeSIREN": " Ein SIREN unsicher (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "Mehr Informationen:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/en.json b/packages/plugins/src/dataTypes/SIRET/i18n/en.json
new file mode 100644
index 000000000..425951f9c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/en.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "French SIRET",
+ "DESC": "This Data Type generates a random SIRET/SIREN French business identification number.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "The SIRET data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " A random SIRET compound of a SIREN and NIC (508102811-00009).",
+ "typeSIREN": " A random SIREN (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "More info:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/es.json b/packages/plugins/src/dataTypes/SIRET/i18n/es.json
new file mode 100644
index 000000000..f737fffee
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/es.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "Este tipo de datos al azar genera SIRET según el formato que usted especifica.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "El tipo de datos SIRET tiene que tener el formato entrado en el campo de texto de Opciones. Por favor fije las filas siguientes:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " Un SIRET aleatorio compuesto de un SIREN y un NIC 508102811-00009.",
+ "typeSIREN": " Un SIREN aleatorio (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "Más información:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/fr.json b/packages/plugins/src/dataTypes/SIRET/i18n/fr.json
new file mode 100644
index 000000000..a0b812872
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/fr.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "Ce type de données produit aléatoirement des SIRET selon le format que vous spécifiez.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "Le type de données SIRET a besoin du format dans le champ texte d'Options. S'il vous plaît corrigez les colonnes suivantes :",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " Un SIRET aléatoire composé d'un SIREN et NIC (508102811-00009).",
+ "typeSIREN": " Un SIREN aléatoire (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "Lien Wiki:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/hi.json b/packages/plugins/src/dataTypes/SIRET/i18n/hi.json
new file mode 100644
index 000000000..ca5eaabcb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/hi.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "फ्रेंच SIRET",
+ "DESC": "यह डेटा प्रकार एक यादृच्छिक SIRET/SIREN फ़्रेंच व्यवसाय पहचान संख्या उत्पन्न करता है।",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "SIRET डेटा प्रकार को विकल्प टेक्स्ट फ़ील्ड में प्रारूप दर्ज करने की आवश्यकता है। कृपया निम्न पंक्तियों को ठीक करें:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": "A random SIRET compound of a SIREN and NIC (508102811-00009).",
+ "typeSIREN": "A random SIREN (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "और जानकारी:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/ja.json b/packages/plugins/src/dataTypes/SIRET/i18n/ja.json
new file mode 100644
index 000000000..b83228bca
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/ja.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "This Data Type generates a random SIRET/SIREN French business identification number.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "The SIRET data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " A random SIRET compound of a SIREN and NIC (508102811-00009).",
+ "typeSIREN": " A random SIREN (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "More info:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/nl.json b/packages/plugins/src/dataTypes/SIRET/i18n/nl.json
new file mode 100644
index 000000000..42a9ac9f1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/nl.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "Dit datatype genereert een willekeurig SIRET nummer volgens het opgegeven formaat.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "De SIRET gegevenstype moet hebben het formaat in de Opties tekst veld. Verbeter de volgende rijen:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " EEN willekeurige SIRET samengesteld van een SIREN en NIC ( 508102811-00009 ).",
+ "typeSIREN": " EEN willekeurige SIREN (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "Meer informatie:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/pt.json b/packages/plugins/src/dataTypes/SIRET/i18n/pt.json
new file mode 100644
index 000000000..7a9a4f213
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/pt.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET francês",
+ "DESC": "Este tipo de dados gera um número de identificação comercial francês SIRET / SIREN aleatório.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "O tipo de dados SIRET precisa ter o formato inserido no campo de texto Opções. Corrija as seguintes linhas:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": "Um composto SIRET aleatório de um SIREN e NIC (508102811-00009).",
+ "typeSIREN": "Uma SIRENE aleatória (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "Mais informações:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/ru.json b/packages/plugins/src/dataTypes/SIRET/i18n/ru.json
new file mode 100644
index 000000000..e25741e1a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/ru.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "Французский SIRET",
+ "DESC": "Этот тип данных генерирует случайный французский бизнес-идентификационный номер SIRET/SIREN.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "Тип данных SIRET должен иметь формат, введенный в текстовом поле «Параметры». Исправьте следующие строки:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": "Случайное соединение SIRET SIREN и NIC (508102811-00009).",
+ "typeSIREN": "Случайная СИРЕНА (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "Больше информации:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/ta.json b/packages/plugins/src/dataTypes/SIRET/i18n/ta.json
new file mode 100644
index 000000000..b83228bca
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/ta.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "This Data Type generates a random SIRET/SIREN French business identification number.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "The SIRET data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " A random SIRET compound of a SIREN and NIC (508102811-00009).",
+ "typeSIREN": " A random SIREN (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "More info:"
+}
diff --git a/packages/plugins/src/dataTypes/SIRET/i18n/zh.json b/packages/plugins/src/dataTypes/SIRET/i18n/zh.json
new file mode 100644
index 000000000..b83228bca
--- /dev/null
+++ b/packages/plugins/src/dataTypes/SIRET/i18n/zh.json
@@ -0,0 +1,13 @@
+{
+ "NAME": "SIRET",
+ "DESC": "This Data Type generates a random SIRET/SIREN French business identification number.",
+ "DEFAULT_TITLE": "siret",
+ "incompleteFields": "The SIRET data type needs to have the format entered in the Options text field. Please fix the following rows:",
+ "exampleSIRET": "508102811-00009 (SIRET : SIREN-NIC)",
+ "exampleSIREN": "508102811 (SIREN)",
+ "typeSIRET": " A random SIRET compound of a SIREN and NIC (508102811-00009).",
+ "typeSIREN": " A random SIREN (508102811).",
+ "SIRET": "SIRET:",
+ "SIREN": "SIREN:",
+ "moreInfo": "More info:"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/README.md b/packages/plugins/src/dataTypes/StreetAddress/README.md
new file mode 100644
index 000000000..29d6d758e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/README.md
@@ -0,0 +1,53 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » StreetAddress
+
+This Data Type generates a basic, westernized street address out of lorem ipsum text and standard address formats.
+
+
+## Examples
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'StreetAddress',
+ title: 'street_address',
+ settings: {}
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```
+[
+ {
+ "street_address": "105-6473 Amet Avenue"
+ },
+ {
+ "street_address": "2200 Ac Rd."
+ },
+ {
+ "street_address": "474-7655 Dolor Rd."
+ },
+ {
+ "street_address": "720-4330 Sit Rd."
+ },
+ {
+ "street_address": "806 Duis Rd."
+ },
+ {
+ "street_address": "912-1517 Egestas. Street"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.generate.ts b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.generate.ts
new file mode 100644
index 000000000..21e107378
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.generate.ts
@@ -0,0 +1,36 @@
+import utils from '../../../utils';
+import { DTGenerateResult, DTGenerationData } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+const getWords = (): string[] => {
+ const { words } = utils.stringUtils.getLipsumWords();
+ return words;
+};
+
+export const generate = ({ i18n }: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { streetTypes, poBox, apNum } = i18n;
+ const streetName = utils.stringUtils.uppercaseWords(utils.randomUtils.generateRandomTextStr(getWords(), false, 1));
+ const streetType = utils.randomUtils.getRandomArrayValue(streetTypes.split(','));
+
+ const format = utils.randomUtils.getRandomNum(1, 4);
+ let streetAddress = '';
+
+ switch (format) {
+ case 1:
+ streetAddress = `${poBox} ${utils.randomUtils.getRandomNum(100, 999)}, ${utils.randomUtils.getRandomNum(100, 9999)} ${streetName} ${streetType}`;
+ break;
+ case 2:
+ streetAddress = `${utils.randomUtils.getRandomNum(100, 999)}-${utils.randomUtils.getRandomNum(100, 9999)} ${streetName} ${streetType}`;
+ break;
+ case 3:
+ streetAddress = `${apNum}${utils.randomUtils.getRandomNum(100, 999)}-${utils.randomUtils.getRandomNum(100, 9999)} ${streetName} ${streetType}`;
+ break;
+ case 4:
+ streetAddress = `${utils.randomUtils.getRandomNum(100, 9999)} ${streetName} ${streetType}`;
+ break;
+ }
+
+ return {
+ display: streetAddress
+ };
+};
diff --git a/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.state.ts b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.state.ts
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.state.ts
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.ts b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.ts
new file mode 100644
index 000000000..e17c48cfa
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.ts
@@ -0,0 +1,9 @@
+import { DTMetadata } from '~types/dataTypes';
+
+export const getMetadata = (): DTMetadata => ({
+ sql: {
+ field: 'varchar(255) default NULL',
+ field_Oracle: 'varchar2(255) default NULL',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.worker.ts b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.worker.ts
new file mode 100644
index 000000000..375e9cbdf
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/StreetAddress.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './StreetAddress.generate';
+
+let workerUtilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/StreetAddress/__tests__/StreetAddress.generate.test.ts b/packages/plugins/src/dataTypes/StreetAddress/__tests__/StreetAddress.generate.test.ts
new file mode 100644
index 000000000..6b9ad8315
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/__tests__/StreetAddress.generate.test.ts
@@ -0,0 +1,74 @@
+import { generate } from '../StreetAddress.generate';
+import * as sinon from 'sinon';
+import utils from '../../../../utils';
+import { DTGenerationData } from '~types/dataTypes';
+const i18n = require('../i18n/en.json');
+
+describe('generate', () => {
+ let data: DTGenerationData = {
+ rowNum: 1,
+ rowState: '',
+ i18n,
+ countryI18n: {},
+ existingRowData: [],
+ countryData: {},
+ template: {}
+ };
+
+ afterEach(() => {
+ sinon.restore();
+ });
+
+ it('street address format 1', () => {
+ sinon.stub(utils.randomUtils, 'generateRandomTextStr').returns('Wilkins');
+ sinon.stub(utils.randomUtils, 'getRandomArrayValue').returns('St');
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(1) // format #1
+ .onCall(1).returns(555) // PO Box
+ .onCall(2).returns(1234); // street name number
+
+ expect(generate(data, utils)).toEqual({
+ display: 'P.O. Box 555, 1234 Wilkins St'
+ });
+ });
+
+ it('street address format 2', () => {
+ sinon.stub(utils.randomUtils, 'generateRandomTextStr').returns('Wilkins');
+ sinon.stub(utils.randomUtils, 'getRandomArrayValue').returns('St');
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(2) // format #2
+ .onCall(1).returns(555)
+ .onCall(2).returns(1234);
+
+ expect(generate(data, utils)).toEqual({
+ display: '555-1234 Wilkins St'
+ });
+ });
+
+ it('street address format 3', () => {
+ sinon.stub(utils.randomUtils, 'generateRandomTextStr').returns('Wilkins');
+ sinon.stub(utils.randomUtils, 'getRandomArrayValue').returns('St');
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(3) // format #3
+ .onCall(1).returns(555)
+ .onCall(2).returns(1234);
+
+ expect(generate(data, utils)).toEqual({
+ display: 'Ap #555-1234 Wilkins St'
+ });
+ });
+
+ it('street address format 4', () => {
+ sinon.stub(utils.randomUtils, 'generateRandomTextStr').returns('Wilkins');
+ sinon.stub(utils.randomUtils, 'getRandomArrayValue').returns('St');
+ sinon.stub(utils.randomUtils, 'getRandomNum')
+ .onCall(0).returns(4) // format #4
+ .onCall(1).returns(555)
+ .onCall(2).returns(1234);
+
+ expect(generate(data, utils)).toEqual({
+ display: '555 Wilkins St'
+ });
+ });
+
+});
diff --git a/packages/plugins/src/dataTypes/StreetAddress/bundle.ts b/packages/plugins/src/dataTypes/StreetAddress/bundle.ts
new file mode 100644
index 000000000..2883fa736
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/bundle.ts
@@ -0,0 +1,8 @@
+import { DTBundle } from '~types/dataTypes';
+import { getMetadata } from './StreetAddress';
+
+const bundle: DTBundle = {
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/StreetAddress/config.ts b/packages/plugins/src/dataTypes/StreetAddress/config.ts
new file mode 100644
index 000000000..ecb94606e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'geo',
+ fieldGroupOrder: 10
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/ar.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/ar.json
new file mode 100644
index 000000000..34a32096a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/ar.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "عنوان الشارع",
+ "DESC": "يولد عناوين شوارع عشوائية.",
+ "DEFAULT_TITLE": "عنوان",
+ "apNum": "أب #",
+ "poBox": "ص. صندوق",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/de.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/de.json
new file mode 100644
index 000000000..ea9cd3541
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/de.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "Straße",
+ "DESC": "Generiert zufällige Straßenadressen .",
+ "DEFAULT_TITLE": "adresse",
+ "apNum": "Ap #",
+ "poBox": "Postfach",
+ "streetTypes": "St., St., Straße, Straße, Rd., Rd., Ave, Av., Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/en.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/en.json
new file mode 100644
index 000000000..04226b17a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/en.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "Street Address",
+ "DESC": "Generates random street addresses.",
+ "DEFAULT_TITLE": "address",
+ "apNum": "Ap #",
+ "poBox": "P.O. Box",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/es.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/es.json
new file mode 100644
index 000000000..440652be2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/es.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "Dirección",
+ "DESC": "Genera direcciones de calles al azar .",
+ "DEFAULT_TITLE": "habla",
+ "apNum": "Apdo.:",
+ "poBox": "Apartado núm.:",
+ "streetTypes": "C/,C.,Calle,Carretera,Ctra.,,Av.,Avda.,Avenida"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/fr.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/fr.json
new file mode 100644
index 000000000..2f38a2412
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/fr.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "Adresse",
+ "DESC": "Génère des adresses aléatoires .",
+ "DEFAULT_TITLE": "habla",
+ "apNum": "Appartement",
+ "poBox": "CP",
+ "streetTypes": "Rue,Route,Chemin,Rd.,Ave,Av.,Avenue,Impasse"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/hi.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/hi.json
new file mode 100644
index 000000000..20dcb7613
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/hi.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "गली का पता",
+ "DESC": "यादृच्छिक सड़क पते उत्पन्न करता है।",
+ "DEFAULT_TITLE": "पता",
+ "apNum": "Ap #",
+ "poBox": "P.O. Box",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/ja.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/ja.json
new file mode 100644
index 000000000..3ad734ef1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/ja.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "住所",
+ "DESC": "ランダムな番地を生成します。",
+ "DEFAULT_TITLE": "住所",
+ "apNum": "Ap #",
+ "poBox": "P.O. ボックス",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/nl.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/nl.json
new file mode 100644
index 000000000..3ce650ec9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/nl.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "Straatnamen",
+ "DESC": "Genereert willekeurige adressen.",
+ "DEFAULT_TITLE": "adresse",
+ "apNum": "Appartement #",
+ "poBox": "Postbus",
+ "streetTypes": "Street, St., Straat, Weg, Road, Rd., Ave, Av., Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/pt.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/pt.json
new file mode 100644
index 000000000..c46c67c5d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/pt.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "Endereço da Rua",
+ "DESC": "Gera endereços de ruas aleatórios.",
+ "DEFAULT_TITLE": "morada",
+ "apNum": "Ap #",
+ "poBox": "P.O. Caixa",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/ru.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/ru.json
new file mode 100644
index 000000000..00c54f990
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/ru.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "Адрес улицы",
+ "DESC": "Генерирует случайные адреса улиц.",
+ "DEFAULT_TITLE": "адрес",
+ "apNum": "Ap #",
+ "poBox": "P.O. Box",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/ta.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/ta.json
new file mode 100644
index 000000000..80df78adb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/ta.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "தெரு முகவரி",
+ "DESC": "சீரற்ற தெரு முகவரிகளை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "முகவரி",
+ "apNum": "Ap #",
+ "poBox": "பி.ஓ. பெட்டி",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/StreetAddress/i18n/zh.json b/packages/plugins/src/dataTypes/StreetAddress/i18n/zh.json
new file mode 100644
index 000000000..921eea685
--- /dev/null
+++ b/packages/plugins/src/dataTypes/StreetAddress/i18n/zh.json
@@ -0,0 +1,8 @@
+{
+ "NAME": "街道地址",
+ "DESC": "生成随机的街道地址。",
+ "DEFAULT_TITLE": "地址",
+ "apNum": "Ap #",
+ "poBox": "P.O. Box",
+ "streetTypes": "St.,St.,Street,Road,Rd.,Rd.,Ave,Av.,Avenue"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/README.md b/packages/plugins/src/dataTypes/TextFixed/README.md
new file mode 100644
index 000000000..3fc08cde3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/README.md
@@ -0,0 +1,45 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » TextFixed
+
+This Data Type generates a fixed number of words. You supply two parameters: an array of words to pull from, and the number
+of words you want to generate.
+
+## Typings
+
+```typescript
+export type GenerationOptionsType = {
+ words: string[];
+ numWordsToGenerate: number;
+}
+```
+
+### Examples
+
+```javascript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'TextFixed',
+ title: 'text1',
+ settings: {
+ numWordsToGenerate: 2,
+ words: ['One', 'Two', 'Three', 'Four']
+ }
+ }
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```javascript
+
+```
diff --git a/packages/plugins/src/dataTypes/TextFixed/TextFixed.generate.ts b/packages/plugins/src/dataTypes/TextFixed/TextFixed.generate.ts
new file mode 100644
index 000000000..ed14bd826
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/TextFixed.generate.ts
@@ -0,0 +1,11 @@
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = ({ rowState }: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { words, numWordsToGenerate } = rowState;
+ const textStr = utils.randomUtils.generateRandomTextStr(words, false, numWordsToGenerate);
+
+ return {
+ display: textStr
+ };
+};
diff --git a/packages/plugins/src/dataTypes/TextFixed/TextFixed.scss b/packages/plugins/src/dataTypes/TextFixed/TextFixed.scss
new file mode 100644
index 000000000..31bcb3a0e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/TextFixed.scss
@@ -0,0 +1,4 @@
+.customText {
+ width: 100%;
+ height: 150px;
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/TextFixed.scss.d.ts b/packages/plugins/src/dataTypes/TextFixed/TextFixed.scss.d.ts
new file mode 100644
index 000000000..e3781af3a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/TextFixed.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace TextFixedScssNamespace {
+ export interface ITextFixedScss {
+ customText: string;
+ }
+}
+
+declare const TextFixedScssModule: TextFixedScssNamespace.ITextFixedScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: TextFixedScssNamespace.ITextFixedScss;
+};
+
+export = TextFixedScssModule;
diff --git a/packages/plugins/src/dataTypes/TextFixed/TextFixed.state.tsx b/packages/plugins/src/dataTypes/TextFixed/TextFixed.state.tsx
new file mode 100755
index 000000000..e9a45c8fb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/TextFixed.state.tsx
@@ -0,0 +1,20 @@
+export type TextSource = 'lipsum' | 'custom';
+
+export type TextFixedState = {
+ numWords: number;
+ textSource: TextSource;
+ customText: string;
+};
+
+export type GenerationOptionsType = {
+ words: string[];
+ numWordsToGenerate: number;
+}
+
+export const initialState: TextFixedState = {
+ numWords: 10,
+ textSource: 'lipsum',
+ customText: ''
+};
+
+export const defaultGenerationOptions = initialState;
diff --git a/packages/plugins/src/dataTypes/TextFixed/TextFixed.tsx b/packages/plugins/src/dataTypes/TextFixed/TextFixed.tsx
new file mode 100755
index 000000000..97c7b8fcd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/TextFixed.tsx
@@ -0,0 +1,130 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import TextField from '~components/TextField';
+import { getLipsumWords } from '~utils/stringUtils';
+import { DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import { TextSource, TextFixedState, GenerationOptionsType } from './TextFixed.state';
+import styles from './TextFixed.scss';
+
+const TextFieldDialog = ({ visible, data, id, onClose, onUpdateSource, onUpdateCustomText, coreI18n, i18n }: any) => {
+ const getCustomTextField = (): JSX.Element | null => {
+ if (data.textSource !== 'custom') {
+ return null;
+ }
+
+ return (
+
+ );
+ };
+
+ return (
+
+
+
{i18n.selectTextSource}
+
+ {i18n.explanation}
+
+ {i18n.source}
+
+
+ onUpdateSource('lipsum')}
+ name={`${id}-source`}
+ checked={data.textSource === 'lipsum'}
+ tooltip={i18n.lipsumDesc}
+ />
+ onUpdateSource('custom')}
+ name={`${id}-source`}
+ checked={data.textSource === 'custom'}
+ tooltip={i18n.customTextDesc}
+ />
+
+ {getCustomTextField()}
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ coreI18n, i18n, id, data, onUpdate }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+
+ const onUpdateSource = (textSource: TextSource): void => {
+ onUpdate({
+ ...data,
+ textSource
+ });
+ };
+
+ const onUpdateNumWords = (e: React.ChangeEvent): void => {
+ onUpdate({
+ ...data,
+ numWords: parseInt(e.target.value, 10)
+ });
+ };
+
+ const onUpdateCustomText = (e: React.ChangeEvent): void => {
+ onUpdate({
+ ...data,
+ customText: e.target.value
+ });
+ };
+
+ return (
+ <>
+ {i18n.TextFixed_generate}
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+ {i18n.TextFixed_words}
+
+ setDialogVisibility(false)}
+ />
+ >
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.TextFixed_help}
;
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'TEXT default NULL',
+ field_Oracle: 'BLOB default NULL',
+ field_MSSQL: 'VARCHAR(MAX) NULL'
+ }
+});
+
+export const rowStateReducer = ({ customText, textSource, numWords }: TextFixedState): GenerationOptionsType => {
+ const { words } = getLipsumWords();
+ return {
+ words: textSource === 'lipsum' ? words : customText.split(/\s+/),
+ numWordsToGenerate: numWords
+ };
+};
diff --git a/packages/plugins/src/dataTypes/TextFixed/TextFixed.worker.ts b/packages/plugins/src/dataTypes/TextFixed/TextFixed.worker.ts
new file mode 100644
index 000000000..15fc16a81
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/TextFixed.worker.ts
@@ -0,0 +1,12 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './TextFixed.generate';
+
+let utilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/TextFixed/__tests__/TextFixed.generate.test.ts b/packages/plugins/src/dataTypes/TextFixed/__tests__/TextFixed.generate.test.ts
new file mode 100644
index 000000000..e0a6e18dc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/__tests__/TextFixed.generate.test.ts
@@ -0,0 +1,30 @@
+import sinon from 'sinon';
+import { onmessage } from '../TextFixed.worker';
+import utils from '../../../../utils';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ it('generates random data', () => {
+ const payload: any = {
+ data: {
+ ...getBlankDTGeneratorPayload(),
+ rowState: {
+ words: ['word'],
+ numWordsToGenerate: 1
+ }
+ }
+ };
+
+ sinon.stub(utils.randomUtils, 'generateRandomTextStr').returns('chicken');
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: 'chicken' });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/TextFixed/__tests__/TextFixed.ui.test.tsx b/packages/plugins/src/dataTypes/TextFixed/__tests__/TextFixed.ui.test.tsx
new file mode 100644
index 000000000..643887f1a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/__tests__/TextFixed.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../TextFixed';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/TextFixed/bundle.ts b/packages/plugins/src/dataTypes/TextFixed/bundle.ts
new file mode 100644
index 000000000..034b99c76
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/bundle.ts
@@ -0,0 +1,13 @@
+import { DTBundle } from '~types/dataTypes';
+import { Options, Help, getMetadata, rowStateReducer } from './TextFixed';
+import { initialState } from './TextFixed.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ Help,
+ getMetadata,
+ rowStateReducer
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/TextFixed/config.ts b/packages/plugins/src/dataTypes/TextFixed/config.ts
new file mode 100644
index 000000000..e36f9090c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'text',
+ fieldGroupOrder: 10
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/ar.json b/packages/plugins/src/dataTypes/TextFixed/i18n/ar.json
new file mode 100644
index 000000000..4a950df2c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/ar.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "عدد الكلمات الثابتة",
+ "DESC": "يُنشئ هذا الخيار عددًا ثابتًا من الكلمات العشوائية ، مأخوذة من النص القياسي lorem ipsum latin.",
+ "DEFAULT_TITLE": "text",
+ "TextFixed_generate": "انشاء",
+ "TextFixed_help": "يُنشئ نوع البيانات هذا سلسلة عشوائية من الكلمات. لديك خيار استخدام النص الافتراضي العشوائي lorem ipsum dummy ، أو تقديم نص خاص بك.",
+ "TextFixed_words": "كلمات",
+ "selectTextSource": "حدد مصدر النص",
+ "lipsumDesc": "Lorem ipsum هو نص وهمي قياسي يستند إلى اللاتينية ويستخدم منذ القرن الخامس عشر الميلادي.",
+ "customTextDesc": "يتيح لك هذا الحقل إدخال أي نص ترغب في استخدامه كمصدر للبيانات.",
+ "source": "مصدر",
+ "explanation": "يتيح لك هذا القسم اختيار مصدر البيانات للكلمات.",
+ "enterCustomText": "أدخل بعض النص المخصص هنا. تأكد من إدخال كمية جيدة من الكلمات.",
+ "custom": "مخصص"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/de.json b/packages/plugins/src/dataTypes/TextFixed/i18n/de.json
new file mode 100644
index 000000000..da075e33f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/de.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Feste Anzahl der Worte",
+ "DESC": "Diese Option erzeugt eine feste Anzahl von zufälligen Wörtern, von der Norm Lorem ipsum lateinischen Text.",
+ "DEFAULT_TITLE": "text",
+ "TextFixed_generate": "Erzeugen",
+ "TextFixed_help": "Dieser Datentyp generiert eine zufällige Wortfolge. Sie haben die Wahl, den standardmäßigen zufälligen Lorem-Ipsum-Dummy-Text zu verwenden oder einen eigenen Text bereitzustellen.",
+ "TextFixed_words": "Text",
+ "selectTextSource": "Wählen Sie Textquelle",
+ "lipsumDesc": "Lorem ipsum ist ein Standard-Dummy-Text in lateinischer Sprache, der seit dem 16. Jahrhundert verwendet wird.",
+ "customTextDesc": "In dieses Feld können Sie den Text eingeben, den Sie als Datenquelle verwenden möchten.",
+ "source": "Quelle",
+ "explanation": "In diesem Abschnitt können Sie die Datenquelle für die Wörter auswählen.",
+ "enterCustomText": "Geben Sie hier einen benutzerdefinierten Text ein. Stellen Sie sicher, dass Sie eine gute Anzahl von Wörtern eingeben.",
+ "custom": "Benutzerdefiniert"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/en.json b/packages/plugins/src/dataTypes/TextFixed/i18n/en.json
new file mode 100644
index 000000000..0112b2abc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/en.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Fixed Number of Words",
+ "DESC": "This option generates a fixed number of random words, pulled from the lorem ipsum latin text.",
+ "DEFAULT_TITLE": "text",
+ "TextFixed_generate": "Generate",
+ "TextFixed_help": "This Data Type generates a random string of words. You have the choice of using the default random lorem ipsum dummy text, or provide a text of your own.",
+ "TextFixed_words": "words",
+ "selectTextSource": "Select Text Source",
+ "lipsumDesc": "Lorem ipsum is a standard dummy text based in Latin that's been in use since the 1500's.",
+ "customTextDesc": "This field lets you enter whatever text you would like to be used as the data source.",
+ "source": "Source",
+ "explanation": "This section lets you customize the text used as the data source.",
+ "enterCustomText": "Enter some custom text here. Make sure you enter a good amount of words.",
+ "custom": "Custom"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/es.json b/packages/plugins/src/dataTypes/TextFixed/i18n/es.json
new file mode 100644
index 000000000..d8df4d7eb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/es.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Número fijo de palabras",
+ "DESC": "Esta opción genera un número fijo palabras al azar, sacadas del texto estándar en Latín lorem ipsum.",
+ "DEFAULT_TITLE": "texto",
+ "TextFixed_generate": "Generar",
+ "TextFixed_help": "Este tipo de datos genera una cadena de palabras aleatoria. Tiene la opción de utilizar el texto de relleno de lorem ipsum aleatorio predeterminado o proporcionar un texto propio.",
+ "TextFixed_words": "palabras",
+ "selectTextSource": "Seleccionar fuente de texto",
+ "lipsumDesc": "Lorem ipsum es un texto ficticio estándar basado en latín que se utiliza desde el siglo XVI.",
+ "customTextDesc": "Este campo le permite ingresar cualquier texto que desee utilizar como fuente de datos.",
+ "source": "Fuente",
+ "explanation": "Esta sección le permite elegir la fuente de datos para las palabras.",
+ "enterCustomText": "Ingrese un texto personalizado aquí. Asegúrese de ingresar una buena cantidad de palabras.",
+ "custom": "Personalizada"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/fr.json b/packages/plugins/src/dataTypes/TextFixed/i18n/fr.json
new file mode 100644
index 000000000..5380e2afb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/fr.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Texte - Longueur Fixe",
+ "DESC": "Cette option génère un nombre fixe de mots aléatoires lorem ipsum (faux texte latin).",
+ "DEFAULT_TITLE": "texto",
+ "TextFixed_generate": "Générer",
+ "TextFixed_help": "Ce type de données génère une chaîne aléatoire de mots. Vous avez le choix d'utiliser le texte factice aléatoire par défaut de lorem ipsum ou de fournir un texte de votre choix.",
+ "TextFixed_words": "mots",
+ "selectTextSource": "Sélectionnez la source du texte",
+ "lipsumDesc": "Lorem ipsum est un texte factice standard basé en latin et utilisé depuis les années 1500.",
+ "customTextDesc": "Ce champ vous permet de saisir le texte que vous souhaitez utiliser comme source de données.",
+ "source": "La source",
+ "explanation": "Cette section vous permet de choisir la source de données pour les mots.",
+ "enterCustomText": "Entrez un texte personnalisé ici. Assurez-vous de saisir une bonne quantité de mots.",
+ "custom": "Douane"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/hi.json b/packages/plugins/src/dataTypes/TextFixed/i18n/hi.json
new file mode 100644
index 000000000..466a10ea6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/hi.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "शब्दों की निश्चित संख्या",
+ "DESC": "यह विकल्प लोरेम इप्सम लैटिन टेक्स्ट से खींचे गए यादृच्छिक शब्दों की एक निश्चित संख्या उत्पन्न करता है।",
+ "DEFAULT_TITLE": "मूलपाठ",
+ "TextFixed_generate": "उत्पन्न",
+ "TextFixed_help": "यह डेटा प्रकार शब्दों की एक यादृच्छिक स्ट्रिंग उत्पन्न करता है। आपके पास डिफ़ॉल्ट रैंडम लोरेम इप्सम डमी टेक्स्ट का उपयोग करने का विकल्प है, या अपना खुद का टेक्स्ट प्रदान करें।",
+ "TextFixed_words": "शब्दों",
+ "selectTextSource": "पाठ स्रोत का चयन करें",
+ "lipsumDesc": "लोरेम इप्सम लैटिन में आधारित एक मानक डमी टेक्स्ट है जो 1500 के दशक से उपयोग में है।",
+ "customTextDesc": "यह फ़ील्ड आपको वह टेक्स्ट दर्ज करने देती है जिसे आप डेटा स्रोत के रूप में उपयोग करना चाहते हैं।",
+ "source": "स्रोत",
+ "explanation": "यह अनुभाग आपको डेटा स्रोत के रूप में उपयोग किए गए टेक्स्ट को कस्टमाइज़ करने देता है।",
+ "enterCustomText": "यहां कुछ कस्टम टेक्स्ट दर्ज करें। सुनिश्चित करें कि आपने अच्छी मात्रा में शब्द दर्ज किए हैं।",
+ "custom": "रीति"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/ja.json b/packages/plugins/src/dataTypes/TextFixed/i18n/ja.json
new file mode 100644
index 000000000..35c914a45
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/ja.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "固定単語数",
+ "DESC": "このオプションは、標準のlorem ipsumlatinテキストから取得した固定数のランダムな単語を生成します。",
+ "DEFAULT_TITLE": "テキスト",
+ "TextFixed_generate": "生む",
+ "TextFixed_help": "このデータ型は、ランダムな単語の文字列を生成します。 デフォルトのランダムloremipsumダミーテキストを使用するか、独自のテキストを提供するかを選択できます。",
+ "TextFixed_words": "言葉",
+ "selectTextSource": "抽出ソースを選択",
+ "lipsumDesc": "Lorem ipsumは、1500年代から使用されている、ラテン語に基づく標準のダミーテキストです。",
+ "customTextDesc": "このフィールドでは、データソースとして使用するテキストを入力できます。",
+ "source": "ソース",
+ "explanation": "このセクションでは、単語のデータソースを選択できます。",
+ "enterCustomText": "ここにカスタムテキストを入力します。 十分な量の単語を入力してください。",
+ "custom": "カスタム"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/nl.json b/packages/plugins/src/dataTypes/TextFixed/i18n/nl.json
new file mode 100644
index 000000000..9188428d5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/nl.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Vast Aantal Woorden",
+ "DESC": "Deze optie genereert een vast aantal willekeurige woorden, die uit de standaard Latijnse tekst Lorem ipsum worden gehaald.",
+ "DEFAULT_TITLE": "tekst",
+ "TextFixed_generate": "Genereer",
+ "TextFixed_help": "Dit gegevenstype genereert een willekeurige reeks woorden. U heeft de keuze om de standaard willekeurige lorem ipsum dummy-tekst te gebruiken, of u kunt uw eigen tekst opgeven.",
+ "TextFixed_words": "woorden",
+ "selectTextSource": "Selecteer Tekstbron",
+ "lipsumDesc": "Lorem ipsum is een standaard dummy-tekst op basis van het Latijn die al sinds de jaren 1500 in gebruik is.",
+ "customTextDesc": "In dit veld kunt u elke tekst invoeren die u als gegevensbron wilt gebruiken.",
+ "source": "Bron",
+ "explanation": "In dit gedeelte kunt u de gegevensbron voor de woorden kiezen.",
+ "enterCustomText": "Voer hier wat aangepaste tekst in. Zorg ervoor dat u een groot aantal woorden invoert.",
+ "custom": "Op maat"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/pt.json b/packages/plugins/src/dataTypes/TextFixed/i18n/pt.json
new file mode 100644
index 000000000..5150d355f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/pt.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Número Fixo de Palavras",
+ "DESC": "Esta opção gera um número fixo de palavras aleatórias, extraídas do texto latino lorem ipsum.",
+ "DEFAULT_TITLE": "texto",
+ "TextFixed_generate": "Gerar",
+ "TextFixed_help": "Este tipo de dados gera uma sequência aleatória de palavras. Você tem a opção de usar o texto fictício lorem ipsum aleatório padrão ou fornecer um texto próprio.",
+ "TextFixed_words": "palavras",
+ "selectTextSource": "Selecione a fonte do texto",
+ "lipsumDesc": "Lorem ipsum é um texto fictício padrão baseado em latim que é usado desde 1500.",
+ "customTextDesc": "Este campo permite inserir qualquer texto que você gostaria de usar como fonte de dados.",
+ "source": "Fonte",
+ "explanation": "Esta seção permite personalizar o texto usado como fonte de dados.",
+ "enterCustomText": "Insira algum texto personalizado aqui. Certifique-se de inserir uma boa quantidade de palavras.",
+ "custom": "Personalizada"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/ru.json b/packages/plugins/src/dataTypes/TextFixed/i18n/ru.json
new file mode 100644
index 000000000..d36222b99
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/ru.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "Фиксированное количество слов",
+ "DESC": "Эта опция генерирует фиксированное количество случайных слов, взятых из латинского текста lorem ipsum.",
+ "DEFAULT_TITLE": "text",
+ "TextFixed_generate": "Создать",
+ "TextFixed_help": "Этот тип данных генерирует случайную строку слов. У вас есть выбор: использовать стандартный фиктивный текст lorem ipsum по умолчанию или предоставить свой собственный текст.",
+ "TextFixed_words": "слова",
+ "selectTextSource": "Выберите источник текста",
+ "lipsumDesc": "Lorem ipsum — стандартный фиктивный текст на латыни, используемый с 1500-х годов.",
+ "customTextDesc": "Это поле позволяет вам ввести любой текст, который вы хотите использовать в качестве источника данных.",
+ "source": "Источник",
+ "explanation": "Этот раздел позволяет настроить текст, используемый в качестве источника данных.",
+ "enterCustomText": "Введите здесь произвольный текст. Убедитесь, что вы вводите достаточное количество слов.",
+ "custom": "Обычай"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/ta.json b/packages/plugins/src/dataTypes/TextFixed/i18n/ta.json
new file mode 100644
index 000000000..a2f133342
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/ta.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "சொற்களின் நிலையான எண்ணிக்கை",
+ "DESC": "இந்த விருப்பம் ஒரு நிலையான எண்ணிக்கையிலான சீரற்ற சொற்களை உருவாக்குகிறது, இது நிலையான லோரெம் இப்சம் லத்தீன் உரையிலிருந்து இழுக்கப்படுகிறது.",
+ "DEFAULT_TITLE": "உரை",
+ "TextFixed_generate": "உருவாக்கு",
+ "TextFixed_help": "இந்த தரவு வகை சீரற்ற சொற்களை உருவாக்குகிறது. இயல்புநிலை சீரற்ற லோரெம் இப்சம் போலி உரையைப் பயன்படுத்துவதற்கான தேர்வு உங்களுக்கு உள்ளது, அல்லது உங்கள் சொந்த உரையை வழங்கவும்.",
+ "TextFixed_words": "சொற்கள்",
+ "selectTextSource": "உரை மூலத்தைத் தேர்ந்தெடுக்கவும்",
+ "lipsumDesc": "லோரெம் இப்சம் என்பது லத்தீன் மொழியை அடிப்படையாகக் கொண்ட ஒரு நிலையான போலி உரை, இது 1500 களில் இருந்து பயன்பாட்டில் உள்ளது.",
+ "customTextDesc": "தரவு மூலமாக நீங்கள் பயன்படுத்த விரும்பும் எந்த உரையையும் உள்ளிட இந்த புலம் உங்களை அனுமதிக்கிறது.",
+ "source": "மூல",
+ "explanation": "இந்த பிரிவு சொற்களுக்கான தரவு மூலத்தைத் தேர்வுசெய்ய உங்களை அனுமதிக்கிறது.",
+ "enterCustomText": "சில தனிப்பயன் உரையை இங்கே உள்ளிடவும். நீங்கள் ஒரு நல்ல அளவு சொற்களை உள்ளிடுவதை உறுதிசெய்க.",
+ "custom": "தனிப்பயன்"
+}
diff --git a/packages/plugins/src/dataTypes/TextFixed/i18n/zh.json b/packages/plugins/src/dataTypes/TextFixed/i18n/zh.json
new file mode 100644
index 000000000..eed4ef93f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextFixed/i18n/zh.json
@@ -0,0 +1,15 @@
+{
+ "NAME": "固定字数",
+ "DESC": "此选项会从标准lorem ipsum拉丁文文本中提取固定数量的随机词。",
+ "DEFAULT_TITLE": "文本",
+ "TextFixed_generate": "生成",
+ "TextFixed_help": "此数据类型生成随机的单词字符串。 您可以选择使用默认的随机lorem ipsum虚拟文本,也可以提供自己的文本。",
+ "TextFixed_words": "话",
+ "selectTextSource": "选择文字来源",
+ "lipsumDesc": "Lorem ipsum是基于拉丁文的标准伪文本,自1500以来一直在使用。",
+ "customTextDesc": "通过此字段,您可以输入任何想要用作数据源的文本。",
+ "source": "资源",
+ "explanation": "本部分允许您选择单词的数据源。",
+ "enterCustomText": "在此处输入一些自定义文本。 确保输入了大量单词。",
+ "custom": "自订"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/README.md b/packages/plugins/src/dataTypes/TextRandom/README.md
new file mode 100644
index 000000000..0c9c52fcb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/README.md
@@ -0,0 +1,29 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » TextRandom
+
+This Data Type generates a random number of random words, taken from lorem ipsum. You can specify the min and max
+number of words.
+
+
+### Example API Usage
+
+```javascript
+{
+ "numRows": 20,
+ "rows": [
+ {
+ "type": "TextFixed",
+ "title": "text",
+ "settings": {
+ "numWords": 5
+ }
+ }
+ ],
+ "export": {
+ "type": "JSON",
+ "settings": {
+ "stripWhitespace": false,
+ "dataStructureFormat": "complex"
+ }
+ }
+}
+```
diff --git a/packages/plugins/src/dataTypes/TextRandom/TextRandom.generate.ts b/packages/plugins/src/dataTypes/TextRandom/TextRandom.generate.ts
new file mode 100644
index 000000000..c5e56a9c3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/TextRandom.generate.ts
@@ -0,0 +1,10 @@
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { fromStart, minWords, maxWords, words } = data.rowState;
+ const textStr = utils.randomUtils.generateRandomTextStr(words, fromStart, minWords, maxWords);
+ return {
+ display: textStr
+ };
+};
diff --git a/packages/plugins/src/dataTypes/TextRandom/TextRandom.scss b/packages/plugins/src/dataTypes/TextRandom/TextRandom.scss
new file mode 100644
index 000000000..31bcb3a0e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/TextRandom.scss
@@ -0,0 +1,4 @@
+.customText {
+ width: 100%;
+ height: 150px;
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/TextRandom.scss.d.ts b/packages/plugins/src/dataTypes/TextRandom/TextRandom.scss.d.ts
new file mode 100644
index 000000000..6fa6d4962
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/TextRandom.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace TextRandomScssNamespace {
+ export interface ITextRandomScss {
+ customText: string;
+ }
+}
+
+declare const TextRandomScssModule: TextRandomScssNamespace.ITextRandomScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: TextRandomScssNamespace.ITextRandomScss;
+};
+
+export = TextRandomScssModule;
diff --git a/packages/plugins/src/dataTypes/TextRandom/TextRandom.state.tsx b/packages/plugins/src/dataTypes/TextRandom/TextRandom.state.tsx
new file mode 100644
index 000000000..5c58dcec7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/TextRandom.state.tsx
@@ -0,0 +1,26 @@
+export type TextSource = 'lipsum' | 'custom';
+
+export type TextRandomState = {
+ fromStart: boolean;
+ minWords: number;
+ maxWords: number;
+ textSource: TextSource;
+ customText: string;
+};
+
+export type GenerationOptionsType = {
+ fromStart: boolean;
+ minWords: number;
+ maxWords: number;
+ words: string[];
+}
+
+export const initialState: TextRandomState = {
+ fromStart: false,
+ minWords: 1,
+ maxWords: 10,
+ textSource: 'lipsum',
+ customText: ''
+};
+
+export const defaultGenerationOptions = initialState;
diff --git a/packages/plugins/src/dataTypes/TextRandom/TextRandom.tsx b/packages/plugins/src/dataTypes/TextRandom/TextRandom.tsx
new file mode 100644
index 000000000..003e10ae1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/TextRandom.tsx
@@ -0,0 +1,155 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import TextField from '~components/TextField';
+import { DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { getLipsumWords } from '~utils/stringUtils';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import { TextSource, TextRandomState, GenerationOptionsType } from './TextRandom.state';
+import styles from './TextRandom.scss';
+
+const TextRandomDialog = ({ visible, data, id, onClose, onChangeFromStart, onUpdateSource, onUpdateCustomText, coreI18n, i18n }: any) => {
+ const getCustomTextField = (): JSX.Element | null => {
+ if (data.textSource !== 'custom') {
+ return null;
+ }
+
+ return (
+
+ );
+ };
+
+ return (
+
+
+
{i18n.selectTextSource}
+
+ {i18n.explanation}
+
+ {i18n.source}
+
+
+ onUpdateSource('lipsum')}
+ name={`${id}-source`}
+ checked={data.textSource === 'lipsum'}
+ tooltip={i18n.lipsumDesc}
+ />
+ onUpdateSource('custom')}
+ name={`${id}-source`}
+ checked={data.textSource === 'custom'}
+ tooltip={i18n.customTextDesc}
+ />
+
+ {getCustomTextField()}
+
+ onChangeFromStart(e.target.checked)}
+ />
+ {i18n.fromStart}
+
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ coreI18n, i18n, id, data, onUpdate }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+
+ const onChange = (field: string, value: string | boolean): void => {
+ onUpdate({
+ ...data,
+ [field]: value
+ });
+ };
+
+ const onUpdateSource = (textSource: TextSource): void => {
+ onUpdate({
+ ...data,
+ textSource
+ });
+ };
+
+ const onUpdateCustomText = (e: React.FormEvent): void => {
+ onUpdate({
+ ...data,
+ customText: e.currentTarget.value
+ });
+ };
+
+ return (
+ <>
+ {i18n.generate}
+ onChange('minWords', e.target.value)}
+ />
+ {i18n.to}
+ onChange('maxWords', e.target.value)}
+ />
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+ {i18n.words}
+
+ onChange('fromStart', isChecked)}
+ onUpdateSource={onUpdateSource}
+ onUpdateCustomText={onUpdateCustomText}
+ onClose={(): void => setDialogVisibility(false)}
+ />
+ >
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.help}
;
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'TEXT default NULL',
+ field_Oracle: 'BLOB default NULL',
+ field_MSSQL: 'VARCHAR(MAX) NULL'
+ }
+});
+
+export const rowStateReducer = ({ fromStart, customText, textSource, minWords, maxWords }: TextRandomState): GenerationOptionsType => {
+ const { words } = getLipsumWords();
+ return {
+ fromStart,
+ minWords,
+ maxWords,
+ words: textSource === 'lipsum' ? words : customText.split(/\s+/)
+ };
+};
diff --git a/packages/plugins/src/dataTypes/TextRandom/TextRandom.worker.ts b/packages/plugins/src/dataTypes/TextRandom/TextRandom.worker.ts
new file mode 100644
index 000000000..1959ede4d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/TextRandom.worker.ts
@@ -0,0 +1,12 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './TextRandom.generate';
+
+let utilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage): void => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/TextRandom/__tests__/TextRandom.generate.test.ts b/packages/plugins/src/dataTypes/TextRandom/__tests__/TextRandom.generate.test.ts
new file mode 100644
index 000000000..f45333575
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/__tests__/TextRandom.generate.test.ts
@@ -0,0 +1,33 @@
+import sinon from 'sinon';
+import { onmessage } from '../TextRandom.worker';
+import utils from '../../../../utils';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+
+describe('onmessage', () => {
+ const postMessage = jest.fn();
+ const importScripts = jest.fn();
+ beforeAll(() => {
+ window.postMessage = postMessage;
+ window.importScripts = importScripts;
+ });
+
+ it('generates random data', () => {
+ const payload: any = {
+ data: {
+ ...getBlankDTGeneratorPayload(),
+ rowState: {
+ numWordsToGenerate: 1,
+ fromStart: 1,
+ minWords: 1,
+ maxWords: 1,
+ words: ['word']
+ }
+ }
+ };
+
+ sinon.stub(utils.randomUtils, 'generateRandomTextStr').returns('testXXX');
+
+ onmessage(payload);
+ expect(postMessage).toHaveBeenCalledWith({ display: 'testXXX' });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/TextRandom/__tests__/TextRandom.ui.test.tsx b/packages/plugins/src/dataTypes/TextRandom/__tests__/TextRandom.ui.test.tsx
new file mode 100644
index 000000000..4be7f2536
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/__tests__/TextRandom.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../TextRandom';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/TextRandom/bundle.ts b/packages/plugins/src/dataTypes/TextRandom/bundle.ts
new file mode 100644
index 000000000..26f50cfac
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/bundle.ts
@@ -0,0 +1,13 @@
+import { DTBundle } from '~types/dataTypes';
+import { Options, Help, getMetadata, rowStateReducer } from './TextRandom';
+import { initialState } from './TextRandom.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Options,
+ Help,
+ getMetadata,
+ rowStateReducer
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/TextRandom/config.ts b/packages/plugins/src/dataTypes/TextRandom/config.ts
new file mode 100644
index 000000000..e36f9090c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'text',
+ fieldGroupOrder: 10
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/ar.json b/packages/plugins/src/dataTypes/TextRandom/i18n/ar.json
new file mode 100644
index 000000000..06d1f7ee9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/ar.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "عدد عشوائي من الكلمات",
+ "DESC": "يُنشئ نوع البيانات هذا عددًا عشوائيًا من الكلمات ضمن النطاق المحدد.",
+ "DEFAULT_TITLE": "text",
+ "fromStart": "ابدأ من بداية النص",
+ "generate": "انشاء",
+ "to": "إلى",
+ "words": "كلمات",
+ "help": "يُنشئ نوع البيانات هذا عددًا عشوائيًا من الكلمات ضمن النطاق المحدد. يمكنك اختيار إنشاء كلمات من النص الافتراضي lorem ipsum dummy text ، أو توفير نص خاص بك.",
+ "selectTextSource": "حدد مصدر النص",
+ "lipsumDesc": "Lorem ipsum هو نص وهمي قياسي يستند إلى اللاتينية ويستخدم منذ القرن الخامس عشر الميلادي.",
+ "customTextDesc": "يتيح لك هذا الحقل إدخال أي نص ترغب في استخدامه كمصدر للبيانات.",
+ "source": "مصدر",
+ "explanation": "يتيح لك هذا القسم تخصيص النص المستخدم كمصدر بيانات.",
+ "enterCustomText": "أدخل بعض النص المخصص هنا. تأكد من إدخال كمية جيدة من الكلمات.",
+ "custom": "مخصص"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/de.json b/packages/plugins/src/dataTypes/TextRandom/i18n/de.json
new file mode 100644
index 000000000..457819bd9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/de.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "Zufällige Anzahl von Wörtern",
+ "DESC": "Dieser Datentyp generiert eine zufällige Anzahl von Wörtern innerhalb des angegebenen Bereichs.",
+ "DEFAULT_TITLE": "Text",
+ "fromStart": "Beginnen Sie am Anfang des Textes",
+ "generate": "Generieren",
+ "to": "zu",
+ "words": "Wörter",
+ "help": "Dieser Datentyp generiert eine zufällige Anzahl von Wörtern innerhalb des angegebenen Bereichs. Sie können wählen, ob Sie Wörter aus dem Standard-Lorem-Ipsum-Dummy-Text generieren oder einen eigenen Text angeben möchten.",
+ "selectTextSource": "Wählen Sie Textquelle",
+ "lipsumDesc": "Lorem ipsum ist ein Standard-Dummy-Text in lateinischer Sprache, der seit dem 16. Jahrhundert verwendet wird.",
+ "customTextDesc": "In dieses Feld können Sie den Text eingeben, den Sie als Datenquelle verwenden möchten.",
+ "source": "Quelle",
+ "explanation": "In diesem Abschnitt können Sie den als Datenquelle verwendeten Text anpassen.",
+ "enterCustomText": "Geben Sie hier einen benutzerdefinierten Text ein. Stellen Sie sicher, dass Sie eine gute Anzahl von Wörtern eingeben.",
+ "custom": "Benutzerdefiniert"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/en.json b/packages/plugins/src/dataTypes/TextRandom/i18n/en.json
new file mode 100644
index 000000000..1f2a4b7b5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/en.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "Random Number of Words",
+ "DESC": "This Data Type generates a random number of words within the range specified.",
+ "DEFAULT_TITLE": "text",
+ "fromStart": "Start from beginning of text",
+ "generate": "Generate",
+ "to": "to",
+ "words": "words",
+ "help": "This Data Type generates a random number of words within the range specified. You can choose to generate words from the default lorem ipsum dummy text, or supply a text of your own.",
+ "selectTextSource": "Select Text Source",
+ "lipsumDesc": "Lorem ipsum is a standard dummy text based in Latin that's been in use since the 1500's.",
+ "customTextDesc": "This field lets you enter whatever text you would like to be used as the data source.",
+ "source": "Source",
+ "explanation": "This section lets you customize the text used as the data source.",
+ "enterCustomText": "Enter some custom text here. Make sure you enter enough words to cover what you need.",
+ "custom": "Custom"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/es.json b/packages/plugins/src/dataTypes/TextRandom/i18n/es.json
new file mode 100644
index 000000000..6c2b35c98
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/es.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "Número aleatorio de palabras",
+ "DESC": "Este tipo de datos genera un número aleatorio de palabras dentro del rango especificado.",
+ "DEFAULT_TITLE": "texto",
+ "fromStart": "Empezar desde el principio del texto",
+ "generate": "Generar",
+ "to": "a",
+ "words": "palabras",
+ "help": "Este tipo de datos genera un número aleatorio de palabras dentro del rango especificado. Puede optar por generar palabras a partir del texto ficticio predeterminado de lorem ipsum o proporcionar un texto propio.",
+ "selectTextSource": "Seleccionar fuente de texto",
+ "lipsumDesc": "Lorem ipsum es un texto ficticio estándar basado en latín que ha estado en uso desde el siglo XVI.",
+ "customTextDesc": "Este campo le permite ingresar cualquier texto que le gustaría que se use como fuente de datos.",
+ "source": "Fuente",
+ "explanation": "Esta sección le permite personalizar el texto utilizado como fuente de datos.",
+ "enterCustomText": "Ingrese un texto personalizado aquí. Asegúrate de introducir una buena cantidad de palabras.",
+ "custom": "Costumbre"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/fr.json b/packages/plugins/src/dataTypes/TextRandom/i18n/fr.json
new file mode 100644
index 000000000..6c3751b53
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/fr.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "Nombre aléatoire de mots",
+ "DESC": "Ce type de données génère un nombre aléatoire de mots dans la plage spécifiée.",
+ "DEFAULT_TITLE": "texte",
+ "fromStart": "Commencer au début du texte",
+ "generate": "produire",
+ "to": "à",
+ "words": "mots",
+ "help": "Ce type de données génère un nombre aléatoire de mots dans la plage spécifiée. Vous pouvez choisir de générer des mots à partir du texte factice par défaut de lorem ipsum ou de fournir votre propre texte.",
+ "selectTextSource": "Sélectionnez la source du texte",
+ "lipsumDesc": "Lorem ipsum est un texte factice standard basé en latin et utilisé depuis les années 1500.",
+ "customTextDesc": "Ce champ vous permet de saisir le texte que vous souhaitez utiliser comme source de données.",
+ "source": "La source",
+ "explanation": "Cette section vous permet de personnaliser le texte utilisé comme source de données.",
+ "enterCustomText": "Entrez un texte personnalisé ici. Assurez-vous de saisir une bonne quantité de mots.",
+ "custom": "Douane"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/hi.json b/packages/plugins/src/dataTypes/TextRandom/i18n/hi.json
new file mode 100644
index 000000000..8f19f2045
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/hi.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "शब्दों की यादृच्छिक संख्या",
+ "DESC": "यह डेटा प्रकार निर्दिष्ट सीमा के भीतर शब्दों की एक यादृच्छिक संख्या उत्पन्न करता है।",
+ "DEFAULT_TITLE": "मूलपाठ",
+ "fromStart": "पाठ की शुरुआत से शुरू करें",
+ "generate": "उत्पन्न",
+ "to": "प्रति",
+ "words": "शब्दों",
+ "help": "यह डेटा प्रकार निर्दिष्ट सीमा के भीतर शब्दों की एक यादृच्छिक संख्या उत्पन्न करता है। आप डिफ़ॉल्ट लोरेम इप्सम डमी टेक्स्ट से शब्द उत्पन्न करना चुन सकते हैं, या अपना खुद का टेक्स्ट प्रदान कर सकते हैं।",
+ "selectTextSource": "पाठ स्रोत का चयन करें",
+ "lipsumDesc": "लोरेम इप्सम लैटिन में आधारित एक मानक डमी टेक्स्ट है जो 1500 के दशक से उपयोग में है।",
+ "customTextDesc": "यह फ़ील्ड आपको वह टेक्स्ट दर्ज करने देती है जिसे आप डेटा स्रोत के रूप में उपयोग करना चाहते हैं।",
+ "source": "स्रोत",
+ "explanation": "यह अनुभाग आपको डेटा स्रोत के रूप में उपयोग किए गए टेक्स्ट को कस्टमाइज़ करने देता है।",
+ "enterCustomText": "यहां कुछ कस्टम टेक्स्ट दर्ज करें। सुनिश्चित करें कि आप जो चाहते हैं उसे कवर करने के लिए पर्याप्त शब्द दर्ज करें।",
+ "custom": "रीति"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/ja.json b/packages/plugins/src/dataTypes/TextRandom/i18n/ja.json
new file mode 100644
index 000000000..46bcfaa39
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/ja.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "ランダムな単語数",
+ "DESC": "このデータ型は、指定された範囲内でランダムな数の単語を生成します。",
+ "DEFAULT_TITLE": "テキスト",
+ "fromStart": "テキストの最初から始める",
+ "generate": "生む",
+ "to": "に",
+ "words": "言葉",
+ "help": "このデータ型は、指定された範囲内でランダムな数の単語を生成します。 デフォルトのloremipsumダミーテキストから単語を生成するか、独自のテキストを提供するかを選択できます。",
+ "selectTextSource": "テキストソースを選択",
+ "lipsumDesc": "Lorem ipsumは、1500年代から使用されている、ラテン語に基づく標準のダミーテキストです。",
+ "customTextDesc": "このフィールドでは、データソースとして使用するテキストを入力できます。",
+ "source": "ソース",
+ "explanation": "このセクションでは、データソースとして使用されるテキストをカスタマイズできます。",
+ "enterCustomText": "ここにカスタムテキストを入力します。 十分な量の単語を入力してください。",
+ "custom": "カスタム"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/nl.json b/packages/plugins/src/dataTypes/TextRandom/i18n/nl.json
new file mode 100644
index 000000000..c5afa005b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/nl.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "Willekeurig aantal woorden",
+ "DESC": "Dit gegevenstype genereert een willekeurig aantal woorden binnen het opgegeven bereik.",
+ "DEFAULT_TITLE": "tekst",
+ "fromStart": "Begin vanaf het begin van de tekst",
+ "generate": "Genereer",
+ "to": "naar",
+ "words": "woorden",
+ "help": "Dit gegevenstype genereert een willekeurig aantal woorden binnen het opgegeven bereik. U kunt ervoor kiezen om woorden te genereren uit de standaard lorem ipsum dummy-tekst, of u kunt zelf een tekst opgeven.",
+ "selectTextSource": "Selecteer Tekstbron",
+ "lipsumDesc": "Lorem ipsum is een standaard dummy-tekst op basis van het Latijn die al sinds de jaren 1500 in gebruik is.",
+ "customTextDesc": "In dit veld kunt u elke tekst invoeren die u als gegevensbron wilt gebruiken.",
+ "source": "Bron",
+ "explanation": "In dit gedeelte kunt u de tekst aanpassen die als gegevensbron wordt gebruikt.",
+ "enterCustomText": "Voer hier wat aangepaste tekst in. Zorg ervoor dat u een groot aantal woorden invoert.",
+ "custom": "Op maat"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/pt.json b/packages/plugins/src/dataTypes/TextRandom/i18n/pt.json
new file mode 100644
index 000000000..432327fa6
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/pt.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "Número Aleatório de Palavras",
+ "DESC": "Este tipo de dados gera um número aleatório de palavras dentro do intervalo especificado.",
+ "DEFAULT_TITLE": "texto",
+ "fromStart": "Comece do início do texto",
+ "generate": "Gerar",
+ "to": "para",
+ "words": "palavras",
+ "help": "Este tipo de dados gera um número aleatório de palavras dentro do intervalo especificado. Você pode optar por gerar palavras a partir do texto fictício lorem ipsum padrão ou fornecer um texto próprio.",
+ "selectTextSource": "Selecione a fonte do texto",
+ "lipsumDesc": "Lorem ipsum é um texto fictício padrão baseado em latim que é usado desde 1500.",
+ "customTextDesc": "Este campo permite inserir qualquer texto que você gostaria de usar como fonte de dados.",
+ "source": "Fonte",
+ "explanation": "Esta seção permite personalizar o texto usado como fonte de dados.",
+ "enterCustomText": "Insira algum texto personalizado aqui. Certifique-se de inserir palavras suficientes para cobrir o que você precisa.",
+ "custom": "Personalizada"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/ru.json b/packages/plugins/src/dataTypes/TextRandom/i18n/ru.json
new file mode 100644
index 000000000..fb80fb626
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/ru.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "Случайное количество слов",
+ "DESC": "Этот тип данных генерирует случайное количество слов в указанном диапазоне.",
+ "DEFAULT_TITLE": "text",
+ "fromStart": "Начать с начала текста",
+ "generate": "Создать",
+ "to": "K",
+ "words": "слова",
+ "help": "Этот тип данных генерирует случайное количество слов в указанном диапазоне. Вы можете генерировать слова из фиктивного текста lorem ipsum по умолчанию или предоставить свой собственный текст.",
+ "selectTextSource": "Выберите источник текста",
+ "lipsumDesc": "Lorem ipsum — стандартный фиктивный текст на латыни, используемый с 1500-х годов.",
+ "customTextDesc": "Это поле позволяет вам ввести любой текст, который вы хотите использовать в качестве источника данных.",
+ "source": "Источник",
+ "explanation": "Этот раздел позволяет настроить текст, используемый в качестве источника данных.",
+ "enterCustomText": "Введите здесь произвольный текст. Убедитесь, что вы ввели достаточно слов, чтобы покрыть то, что вам нужно.",
+ "custom": "Обычай"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/ta.json b/packages/plugins/src/dataTypes/TextRandom/i18n/ta.json
new file mode 100644
index 000000000..fb111014d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/ta.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "சொற்களின் சீரற்ற எண்",
+ "DESC": "இந்த தரவு வகை குறிப்பிட்ட வரம்பிற்குள் சீரற்ற எண்ணிக்கையிலான சொற்களை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "உரை",
+ "fromStart": "உரையின் தொடக்கத்திலிருந்து தொடங்குங்கள்",
+ "generate": "உருவாக்கு",
+ "to": "க்கு",
+ "words": "சொற்கள்",
+ "help": "இந்த தரவு வகை குறிப்பிட்ட வரம்பிற்குள் சீரற்ற எண்ணிக்கையிலான சொற்களை உருவாக்குகிறது. இயல்புநிலை லோரெம் இப்சம் போலி உரையிலிருந்து சொற்களை உருவாக்க நீங்கள் தேர்வு செய்யலாம் அல்லது உங்கள் சொந்த உரையை வழங்கலாம்.",
+ "selectTextSource": "உரை மூலத்தைத் தேர்ந்தெடுக்கவும்",
+ "lipsumDesc": "லோரெம் இப்சம் என்பது லத்தீன் மொழியை அடிப்படையாகக் கொண்ட ஒரு நிலையான போலி உரை, இது 1500 களில் இருந்து பயன்பாட்டில் உள்ளது.",
+ "customTextDesc": "தரவு மூலமாக நீங்கள் பயன்படுத்த விரும்பும் எந்த உரையையும் உள்ளிட இந்த புலம் உங்களை அனுமதிக்கிறது.",
+ "source": "மூல",
+ "explanation": "தரவு மூலமாகப் பயன்படுத்தப்படும் உரையைத் தனிப்பயனாக்க இந்த பிரிவு உங்களை அனுமதிக்கிறது.",
+ "enterCustomText": "சில தனிப்பயன் உரையை இங்கே உள்ளிடவும். நீங்கள் ஒரு நல்ல அளவு சொற்களை உள்ளிடுவதை உறுதிசெய்க.",
+ "custom": "தனிப்பயன்"
+}
diff --git a/packages/plugins/src/dataTypes/TextRandom/i18n/zh.json b/packages/plugins/src/dataTypes/TextRandom/i18n/zh.json
new file mode 100644
index 000000000..715b3e777
--- /dev/null
+++ b/packages/plugins/src/dataTypes/TextRandom/i18n/zh.json
@@ -0,0 +1,17 @@
+{
+ "NAME": "随机词数",
+ "DESC": "此数据类型生成指定范围内的随机单词数。",
+ "DEFAULT_TITLE": "文本",
+ "fromStart": "从文字开始",
+ "generate": "生成",
+ "to": "至",
+ "words": "话",
+ "help": "此数据类型生成指定范围内的随机单词数。 您可以选择从默认lorem ipsum虚拟文本生成单词,也可以提供自己的文本。",
+ "selectTextSource": "选择文字来源",
+ "lipsumDesc": "Lorem ipsum是基于拉丁文的标准伪文本,自1500以来一直在使用。",
+ "customTextDesc": "通过此字段,您可以输入任何想要用作数据源的文本。",
+ "source": "资源",
+ "explanation": "本部分允许您自定义用作数据源的文本。",
+ "enterCustomText": "在此处输入一些自定义文本。 确保输入了大量单词。",
+ "custom": "自订"
+}
diff --git a/packages/plugins/src/dataTypes/Time/README.md b/packages/plugins/src/dataTypes/Time/README.md
new file mode 100644
index 000000000..1a9e935af
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/README.md
@@ -0,0 +1,73 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Time
+
+This Data Type generates a random time in a particular format. It's currently a bit of an awkward interface in that it
+requires passing the `fromTime` and `toTime` settings sin _seconds timestamp_ format. I realize this isn't the
+easiest format to work with, so we have a feature request to improve the DX here: https://github.com/benkeen/generatedata/issues/825
+Try using an online service such as the [epochconvertor site](https://www.epochconverter.com/) to generate those values for you.
+
+## Formats
+
+This Data Type uses date-fns for the time formats. [See their documentation](https://date-fns.org/v2.29.3/docs/format) for
+a full descriptions of each of the formatting placeholders. A few examples:
+
+- `h:mm aaa` - 3:35 pm
+- `h:mm a` - 3:35 PM
+- `h:mm aaaa` - 3:35 p.m.
+- `h:mm:ss aaa` - 3:35:00 pm
+- `h:mm:ss aa` - 3:35:00 PM
+- `h:mm:ss aaaa` - 3:35:00 P.M.
+- `H:mm` - 15:35
+- `H:mm:ss` - 15:35:00
+
+## Examples
+
+```typescript
+{
+ generationSettings: {
+ numResults: 10
+ },
+ dataTemplate: [
+ {
+ plugin: 'Time',
+ title: 'time',
+ settings: {
+ fromTime: 1678944164,
+ toTime: 1679030564,
+ format: 'h:mm a'
+ }
+ },
+ ],
+ exportSettings: {
+ plugin: 'JSON',
+ settings: {
+ dataStructureFormat: 'simple'
+ }
+ }
+}
+```
+
+Sample output:
+
+```typescript
+[
+ {
+ "time": "7:45 AM"
+ },
+ {
+ "time": "7:44 AM"
+ },
+ {
+ "time": "4:54 AM"
+ },
+ {
+ "time": "4:06 AM"
+ },
+ {
+ "time": "7:13 PM"
+ },
+ {
+ "time": "9:53 PM"
+ },
+ ...
+]
+```
diff --git a/packages/plugins/src/dataTypes/Time/Time.generate.ts b/packages/plugins/src/dataTypes/Time/Time.generate.ts
new file mode 100644
index 000000000..5696e2da4
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/Time.generate.ts
@@ -0,0 +1,20 @@
+import { format, fromUnixTime } from 'date-fns';
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { fromTime, toTime, format: displayFormat } = data.rowState;
+ if (!displayFormat) {
+ return { display: '' };
+ }
+ const time = utils.randomUtils.getRandomNum(fromTime, toTime);
+
+ let display = '';
+ try {
+ display = format(fromUnixTime(time), displayFormat);
+ } catch (e) {}
+
+ return {
+ display
+ };
+};
diff --git a/packages/plugins/src/dataTypes/Time/Time.scss b/packages/plugins/src/dataTypes/Time/Time.scss
new file mode 100644
index 000000000..2cdb6f1bb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/Time.scss
@@ -0,0 +1,71 @@
+.dateRow {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-bottom: 4px;
+
+ label {
+ margin-right: 6px;
+ }
+
+ span {
+ text-transform: none;
+ }
+}
+
+.formatCodeLabel {
+ margin-right: 4px;
+}
+
+.dateField {
+ margin: 0;
+
+ div input {
+ border: 0;
+ }
+
+ & > div {
+ border-bottom: 0;
+ }
+}
+
+.dateBtn {
+ background-color: #ffffff;
+ padding: 4px 6px;
+}
+
+.row {
+ display: flex;
+ align-items: flex-start;
+ padding-bottom: 12px;
+
+ .col1 {
+ flex: 0 0 70px;
+ }
+ .col2 {
+ flex: 1;
+ }
+ .col3 {
+ flex: 1;
+ color: #999999;
+ }
+
+ label {
+ background-color: #dfecfc;
+ border-radius: 3px;
+ padding: 1px 6px;
+ }
+}
+
+.copy {
+ margin: 4px 0 0 4px;
+}
+
+.field {
+ margin: 3px;
+ width: 90px;
+
+ input:focus {
+ border: 0
+ }
+}
diff --git a/packages/plugins/src/dataTypes/Time/Time.scss.d.ts b/packages/plugins/src/dataTypes/Time/Time.scss.d.ts
new file mode 100644
index 000000000..41a1a8bea
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/Time.scss.d.ts
@@ -0,0 +1,21 @@
+declare namespace TimeScssNamespace {
+ export interface ITimeScss {
+ col1: string;
+ col2: string;
+ col3: string;
+ copy: string;
+ dateBtn: string;
+ dateField: string;
+ dateRow: string;
+ field: string;
+ formatCodeLabel: string;
+ row: string;
+ }
+}
+
+declare const TimeScssModule: TimeScssNamespace.ITimeScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: TimeScssNamespace.ITimeScss;
+};
+
+export = TimeScssModule;
diff --git a/packages/plugins/src/dataTypes/Time/Time.state.tsx b/packages/plugins/src/dataTypes/Time/Time.state.tsx
new file mode 100755
index 000000000..551027680
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/Time.state.tsx
@@ -0,0 +1,19 @@
+import { endOfDay, format, startOfDay } from 'date-fns';
+
+export type DateState = {
+ fromTime: number;
+ toTime: number;
+ example: string;
+ format: string;
+};
+
+export type GenerationOptionsType = Omit;
+
+export const initialState: DateState = {
+ fromTime: parseInt(format(startOfDay(new Date()), 't'), 10),
+ toTime: parseInt(format(endOfDay(new Date()), 't'), 10),
+ example: 'h:mm a',
+ format: 'h:mm a'
+};
+
+export const defaultGenerationOptions = initialState;
diff --git a/packages/plugins/src/dataTypes/Time/Time.tsx b/packages/plugins/src/dataTypes/Time/Time.tsx
new file mode 100755
index 000000000..898f0ae8e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/Time.tsx
@@ -0,0 +1,148 @@
+import * as React from 'react';
+import { format, fromUnixTime, parse } from 'date-fns';
+import Dropdown from '~components/dropdown/Dropdown';
+import ArrowRightAlt from '@mui/icons-material/ArrowRightAlt';
+import TextField from '@mui/material/TextField';
+import CoreTextField from '~components/TextField';
+import CopyToClipboard from '~components/copyToClipboard/CopyToClipboard';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { ErrorTooltip } from '~components/tooltips';
+import { DateState, GenerationOptionsType } from './Time.state';
+import * as styles from './Time.scss';
+import * as sharedStyles from '../../../styles/shared.scss';
+
+const SECS_IN_DAY = 86400;
+export const rowStateReducer = ({ fromTime, toTime, format }: DateState): GenerationOptionsType => ({
+ fromTime,
+ toTime: fromTime > toTime ? toTime + SECS_IN_DAY : toTime,
+ format
+});
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
+
+export const getOptions = (): any[] => {
+ const formats = [
+ 'h:mm aaa', // 3:35 pm
+ 'h:mm a', // 3:35 PM
+ 'h:mm aaaa', // 3:35 p.m.
+ 'h:mm:ss aaa', // 3:35:00 pm
+ 'h:mm:ss aa', // 3:35:00 PM
+ 'h:mm:ss aaaa', // 3:35:00 P.M.
+ 'H:mm', // 15:35
+ 'H:mm:ss' // 15:35:00
+ ];
+
+ return formats.map((currFormat) => ({
+ label: format(new Date(), currFormat),
+ value: currFormat
+ }));
+};
+
+export const Example = ({ i18n, data, onUpdate }: DTExampleProps) => {
+ const onChange = ({ value }: { value: string }): void => {
+ onUpdate({
+ ...data,
+ example: value,
+ format: value
+ });
+ };
+
+ return ;
+};
+
+export const Options = ({ data, onUpdate, i18n, coreI18n }: DTOptionsProps) => {
+ const onChange = (field: string, value: any): void => {
+ onUpdate({
+ ...data,
+ [field]: value
+ });
+ };
+
+ let toTimeError = '';
+ if (data.fromTime > data.toTime) {
+ toTimeError = i18n.endDateEarlierThanStartDate;
+ }
+
+ return (
+
+
+
{
+ const date = parse(e.target.value, 'HH:mm', new Date());
+ onChange('fromTime', parseInt(format(date, 't'), 10));
+ }}
+ />
+
+
+ {
+ const date = parse(e.target.value, 'HH:mm', new Date());
+ onChange('toTime', parseInt(format(date, 't'), 10));
+ }}
+ />
+
+
+
+ {i18n.formatCode}
+ onChange('format', e.target.value)}
+ maxLength={255}
+ />
+
+
+ );
+};
+
+const Copy = ({ content, tooltip, message }: any) => (
+
+
+
+);
+
+const generateRows = (letters: string[], i18n: any, coreI18n: any): JSX.Element[] =>
+ letters.map((letter: string) => (
+
+
+ {letter}
+
+
+
+
+
{i18n[`${letter}Format`]}
+
{i18n[`${letter}FormatExample`]}
+
+ ));
+
+export const Help = ({ i18n, coreI18n }: DTHelpProps) => (
+ <>
+
+
+ {generateRows(['h', 'H', 'mm', 'ss', 'a', 'aaa', 'aaaa'], i18n, coreI18n)}
+ >
+);
diff --git a/packages/plugins/src/dataTypes/Time/Time.worker.ts b/packages/plugins/src/dataTypes/Time/Time.worker.ts
new file mode 100644
index 000000000..2eedd4508
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/Time.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Time.generate';
+
+let workerUtilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage): void => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/Time/__tests__/Time.test.ts b/packages/plugins/src/dataTypes/Time/__tests__/Time.test.ts
new file mode 100644
index 000000000..30cee8483
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/__tests__/Time.test.ts
@@ -0,0 +1,29 @@
+import { format } from 'date-fns';
+import * as ui from '../Time';
+
+const date = new Date(2020, 0, 3);
+
+describe('getOptions', () => {
+ it('confirm that all provided formats generate the expected value', () => {
+ const expected: any = {
+ 'MMM d, y': 'Jan 3, 2020',
+ 'MMMM do, y': 'January 3rd, 2020',
+ 'EEE, MMM dd': 'Fri, Jan 03',
+ 'EEE, MMM do, y': 'Fri, Jan 3rd, 2020',
+ 'LL.dd.yy': '01.03.20',
+ 'LL-dd-yy': '01-03-20',
+ 'LL/dd/yy': '01/03/20',
+ 'LL/dd/y': '01/03/2020',
+ 'dd.LL.yy': '03.01.20',
+ 'dd-LL-yy': '03-01-20',
+ 'dd/LL/y': '03/01/2020',
+ 'y-LL-dd HH:mm:ss': '2020-01-03 00:00:00'
+ };
+
+ ui.getOptions().forEach(({ value }: any) => {
+ if (expected.hasOwnProperty(value)) {
+ expect(format(date, value)).toEqual(expected[value]);
+ }
+ });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Time/bundle.ts b/packages/plugins/src/dataTypes/Time/bundle.ts
new file mode 100644
index 000000000..591bbad56
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Help, Example, Options, rowStateReducer, getMetadata } from './Time';
+import { initialState } from './Time.state';
+
+const bundle: DTBundle = {
+ initialState,
+ rowStateReducer,
+ Help,
+ Example,
+ Options,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Time/config.ts b/packages/plugins/src/dataTypes/Time/config.ts
new file mode 100644
index 000000000..02a637f12
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'humanData',
+ fieldGroupOrder: 50
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Time/i18n/ar.json b/packages/plugins/src/dataTypes/Time/i18n/ar.json
new file mode 100644
index 000000000..4949d6383
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/ar.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "زمن",
+ "DESC": "يولد وقتًا منسقًا مخصصًا ضمن نطاق زمني.",
+ "DEFAULT_TITLE": "زمن",
+ "description": "وصف",
+ "example": "مثال",
+ "formatCode": "كود التنسيق:",
+ "helpIntro": "يقوم نوع البيانات هذا بشكل عشوائي بإنشاء وقت منسق بين النطاق. راجع الجدول أدناه للحصول على قائمة بقواعد التنسيق المتاحة. هذه مأخوذة من خيارات تنسيق تاريخ .استخدم مثال القائمة المنسدلة للاختيار من تنسيقات التاريخ القياسية.",
+ "hFormat": "ساعة",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "ساعة بدون مبطن",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "دقائق مبطنة صفريًا",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "ثواني بدون مبطن",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "الأحرف الكبيرة صباحا ، مساء",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "صغيرة صباحا ، مساءا",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "أحرف صغيرة صباحا ، مساءا",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/de.json b/packages/plugins/src/dataTypes/Time/i18n/de.json
new file mode 100644
index 000000000..c4976980f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/de.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Zeit",
+ "DESC": "Generiert eine benutzerdefinierte formatierte Zeit innerhalb eines Zeitbereichs.",
+ "DEFAULT_TITLE": "Zeit",
+ "description": "Beschreibung",
+ "example": "Beispiel",
+ "formatCode": "Formatcode:",
+ "helpIntro": "Dieser Datentyp generiert zufällig eine formatierte Zeit zwischen einem Bereich. In der folgenden Tabelle finden Sie eine Liste der verfügbaren Formatierungsregeln. Diese stammen aus den date-fns Datumsformatierungsoptionen . Verwenden Sie das Dropdown-Beispiel, um aus den Standard-Datumsformaten auszuwählen.",
+ "hFormat": "Stunde",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "Null aufgefüllte Stunde",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "Null aufgefüllte Minuten",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "Mit Null aufgefüllte Sekunden",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "AM, PM in Großbuchstaben",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "Kleinbuchstaben am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "Kleinbuchstaben a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/en.json b/packages/plugins/src/dataTypes/Time/i18n/en.json
new file mode 100644
index 000000000..1f36da61a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/en.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Time",
+ "DESC": "Generates a custom formatted time within a time range.",
+ "DEFAULT_TITLE": "time",
+ "description": "Description",
+ "example": "Example",
+ "formatCode": "Format code:",
+ "helpIntro": "This data type randomly generates a formatted time between a range. See the table below for a list of available formatting rules. These are taken from the date-fns date formatting options . Use the example dropdown to select from standard date formats.",
+ "hFormat": "Hour",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "Zero-padded hour",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "Zero-padded minutes",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "Zero-padded seconds",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "Uppercase AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "Lowercase am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "Lowercase a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/es.json b/packages/plugins/src/dataTypes/Time/i18n/es.json
new file mode 100644
index 000000000..846a851bb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/es.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Tiempo",
+ "DESC": "Genera una hora con formato personalizado dentro de un rango de tiempo.",
+ "DEFAULT_TITLE": "tiempo",
+ "description": "Descripción",
+ "example": "Ejemplo",
+ "formatCode": "Código de formato:",
+ "helpIntro": "Este tipo de datos genera aleatoriamente un tiempo formateado entre un rango. Consulte la tabla a continuación para obtener una lista de las reglas de formato disponibles. Estos se toman de las opciones de formato de fecha date-fns . Utilice el menú desplegable de ejemplo para seleccionar entre los formatos de fecha estándar.",
+ "hFormat": "Hora",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "Hora acolchada con cero",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "Minutos sin relleno",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "Segundos acolchados con cero",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "Mayúsculas AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "Minúsculas am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "Minúsculas a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/fr.json b/packages/plugins/src/dataTypes/Time/i18n/fr.json
new file mode 100644
index 000000000..9517e9491
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/fr.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Temps",
+ "DESC": "Génère une heure formatée personnalisée dans une plage de temps.",
+ "DEFAULT_TITLE": "temps",
+ "description": "La description",
+ "example": "Exemple",
+ "formatCode": "Code de formatage :",
+ "helpIntro": "Ce type de données génère de manière aléatoire un temps formaté entre une plage. Voir le tableau ci-dessous pour une liste des règles de formatage disponibles. Celles-ci sont extraites des options de formatage de date date-fns . Utilisez l'exemple de liste déroulante pour sélectionner des formats de date standard.",
+ "hFormat": "Heure",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "Heure zéro-rembourrée",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "Minutes sans zéro",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "Secondes zéro-rembourrées",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "Majuscules AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "Minuscule am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "Minuscule a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/hi.json b/packages/plugins/src/dataTypes/Time/i18n/hi.json
new file mode 100644
index 000000000..b3d00f8ac
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/hi.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "समय",
+ "DESC": "एक समय सीमा के भीतर एक कस्टम स्वरूपित समय उत्पन्न करता है।",
+ "DEFAULT_TITLE": "समय",
+ "description": "विवरण",
+ "example": "उदाहरण",
+ "formatCode": "प्रारूप कोड:",
+ "helpIntro": "यह डेटा प्रकार बेतरतीब ढंग से एक सीमा के बीच एक स्वरूपित समय उत्पन्न करता है। उपलब्ध स्वरूपण नियमों की सूची के लिए नीचे दी गई तालिका देखें। ये date-fns दिनांक स्वरूपण विकल्पों से लिए गए हैं . मानक दिनांक स्वरूपों में से चयन करने के लिए उदाहरण ड्रॉपडाउन का उपयोग करें।",
+ "hFormat": "घंटा",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "शून्य गद्देदार घंटा",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "शून्य-गद्देदार मिनट",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "शून्य-गद्देदार सेकंड",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "अपरकेस AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "छोटे am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "छोटे a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/ja.json b/packages/plugins/src/dataTypes/Time/i18n/ja.json
new file mode 100644
index 000000000..22d70933b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/ja.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "時間",
+ "DESC": "時間範囲内にカスタムフォーマットされた時間を生成します。",
+ "DEFAULT_TITLE": "時間",
+ "description": "説明",
+ "example": "例",
+ "formatCode": "フォーマットコード:",
+ "helpIntro": "このデータ型は、範囲間のフォーマットされた時間をランダムに生成します。 使用可能なフォーマット規則のリストについては、以下の表を参照してください。 これらは、 date-fns日付フォーマットオプション から取得されます。 。 ドロップダウンの例を使用して、標準の日付形式から選択します。",
+ "hFormat": "時間",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "ゼロが埋め込まれた時間",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "ゼロが埋め込まれた分",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "ゼロが埋め込まれた秒",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "大文字の午前、午後",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "小文字の午前、午後",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "小文字の午前、午後",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/nl.json b/packages/plugins/src/dataTypes/Time/i18n/nl.json
new file mode 100644
index 000000000..66e74d344
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/nl.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Tijd",
+ "DESC": "Genereert een aangepaste opgemaakte tijd binnen een tijdbereik.",
+ "DEFAULT_TITLE": "tijd",
+ "description": "Beschrijving",
+ "example": "Voorbeeld",
+ "formatCode": "Formaatcode:",
+ "helpIntro": "Dit gegevenstype genereert willekeurig een opgemaakte tijd tussen een bereik. Zie de onderstaande tabel voor een lijst met beschikbare opmaakregels. Deze zijn afkomstig uit de date-fns datumopmaakopties . Gebruik de vervolgkeuzelijst met voorbeelden om te kiezen uit standaard datumnotaties.",
+ "hFormat": "Uur",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "Zero-padded uur",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "Nul gevulde minuten",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "Nulgevulde seconden",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "Hoofdletters AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "Kleine letters am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "Kleine letters a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/pt.json b/packages/plugins/src/dataTypes/Time/i18n/pt.json
new file mode 100644
index 000000000..5f9f894c2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/pt.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Tempo",
+ "DESC": "Gera uma hora formatada personalizada dentro de um intervalo de tempo.",
+ "DEFAULT_TITLE": "Tempo",
+ "description": "Descrição",
+ "example": "Exemplo",
+ "formatCode": "Código de formato:",
+ "helpIntro": "Este tipo de dados gera aleatoriamente um tempo formatado entre um intervalo. Consulte a tabela abaixo para obter uma lista das regras de formatação disponíveis. Eles são retirados das opções de formatação de data date-fns . Use o menu suspenso de exemplo para selecionar os formatos de data padrão.",
+ "hFormat": "Hora",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "Hora zerada",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "Minutos zerados",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "Segundos zerados",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "Maiúsculas AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "Minúsculas am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "Minúsculas a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/ru.json b/packages/plugins/src/dataTypes/Time/i18n/ru.json
new file mode 100644
index 000000000..cac5a730f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/ru.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "Время",
+ "DESC": "Генерирует пользовательское форматированное время в диапазоне времени.",
+ "DEFAULT_TITLE": "время",
+ "description": "Описание",
+ "example": "Пример",
+ "formatCode": "Код формата:",
+ "helpIntro": "Этот тип данных случайным образом генерирует отформатированное время между диапазоном. В таблице ниже приведен список доступных правил форматирования. Они взяты из параметров форматирования даты date-fns . . Используйте пример раскрывающегося списка, чтобы выбрать один из стандартных форматов даты.",
+ "hFormat": "Час",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "Час с нулевым дополнением",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "Минуты с нулями",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "Секунды с нулями",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "Заглавные буквы AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "Строчные утра, вечера",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "Строчные am, p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/ta.json b/packages/plugins/src/dataTypes/Time/i18n/ta.json
new file mode 100644
index 000000000..29119cb3a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/ta.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "நேரம்",
+ "DESC": "ஒரு கால எல்லைக்குள் தனிப்பயன் வடிவமைக்கப்பட்ட நேரத்தை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "நேரம்",
+ "description": "விளக்கம்",
+ "example": "உதாரணமாக",
+ "formatCode": "வடிவமைப்பு குறியீடு:",
+ "helpIntro": "இந்த தரவு வகை தோராயமாக ஒரு வரம்பிற்கு இடையில் வடிவமைக்கப்பட்ட நேரத்தை உருவாக்குகிறது. கிடைக்கக்கூடிய வடிவமைப்பு விதிகளின் பட்டியலுக்கு கீழே உள்ள அட்டவணையைப் பார்க்கவும். இவை தேதி- fns தேதி வடிவமைப்பு விருப்பங்கள் இலிருந்து எடுக்கப்பட்டது . நிலையான தேதி வடிவங்களிலிருந்து தேர்ந்தெடுக்க எடுத்துக்காட்டு கீழ்தோன்றலைப் பயன்படுத்தவும்.",
+ "hFormat": "மணி",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "பூஜ்ஜிய-திணிப்பு மணி",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "பூஜ்ஜியத்துடன் கூடிய நிமிடங்கள்",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "பூஜ்ஜியத்துடன் கூடிய வினாடிகள்",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "பெரிய எழுத்து AM, PM",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "சிறிய எழுத்து காலை, மாலை",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "சிறிய எழுத்து காலை, மாலை",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Time/i18n/zh.json b/packages/plugins/src/dataTypes/Time/i18n/zh.json
new file mode 100644
index 000000000..4947b289a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Time/i18n/zh.json
@@ -0,0 +1,23 @@
+{
+ "NAME": "时间",
+ "DESC": "在时间范围内生成自定义格式的时间。",
+ "DEFAULT_TITLE": "时间",
+ "description": "描述",
+ "example": "例子",
+ "formatCode": "格式代码:",
+ "helpIntro": "此数据类型随机生成一个范围之间的格式化时间。 有关可用格式规则的列表,请参阅下表。 这些取自 date-fns 日期格式选项 . 使用示例下拉菜单从标准日期格式中进行选择。",
+ "hFormat": "小时",
+ "hFormatExample": "1, 2, ..., 11, 12",
+ "HFormat": "零填充小时",
+ "HFormatExample": "01, 02, ..., 11, 12",
+ "mmFormat": "零填充分钟",
+ "mmFormatExample": "01, 02, ..., 58, 59",
+ "ssFormat": "零填充秒",
+ "ssFormatExample": "01, 02, ..., 58, 59",
+ "aFormat": "零填充秒",
+ "aFormatExample": "AM, PM",
+ "aaaFormat": "小写 am, pm",
+ "aaaFormatExample": "am, pm",
+ "aaaaFormat": "小写 a.m., p.m.",
+ "aaaaFormatExample": "a.m., p.m."
+}
diff --git a/packages/plugins/src/dataTypes/Track1/README.md b/packages/plugins/src/dataTypes/Track1/README.md
new file mode 100644
index 000000000..7c5b4ca41
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/README.md
@@ -0,0 +1,30 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Track1
+
+This Data Type generates a random Track1 number.
+
+
+### Example API Usage
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "Track1",
+ "title": "track 1"
+ }
+ ],
+ "export": {
+ "type": "JSON",
+ "settings": {
+ "stripWhitespace": false,
+ "dataStructureFormat": "complex"
+ }
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/dataTypes/Track1/Track1.generate.ts b/packages/plugins/src/dataTypes/Track1/Track1.generate.ts
new file mode 100644
index 000000000..5426fb091
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/Track1.generate.ts
@@ -0,0 +1,67 @@
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+/*
+ Source - http://en.wikipedia.org/wiki/Magnetic_stripe_card#Financial_cards
+
+ Start sentinel — one character (generally '%')
+ Format code = "B" — one character (alpha only)
+ Primary account number (PAN) — up to 19 characters. Usually, but not always, matches the credit card number
+ printed on the front of the card.
+ Field Separator — one character (generally '^')
+ Name — two to 26 characters
+ Field Separator — one character (generally '^')
+ Expiration date — four characters in the form YYMM.
+ Service code — three characters
+ Discretionary data — may include Pin Verification Key Indicator (PVKI, 1 character), PIN Verification
+ Value (PVV, 4 characters), Card Verification Value or Card Verification Code (CVV or CVC, 3 characters)
+ End sentinel — one character (generally '?')
+ Longitudinal redundancy check (LRC) — it is one character and a validity character calculated from other
+ data on the track.
+*/
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { nameSource, panSource, targetNameRowId, targetPanRowId } = data.rowState;
+
+ let pan = '';
+ if (panSource === 'random') {
+ pan = utils.randomUtils.generateRandomAlphanumericStr('Xxxxxxxxxxxxxxxx');
+ } else {
+ const found = data.existingRowData.find(({ id }) => id === targetPanRowId);
+ if (found) {
+ pan = found.data.display as string;
+ }
+ }
+
+ let name = '';
+ if (nameSource === 'random') {
+ name = utils.randomUtils.generateRandomAlphanumericStr('Lllllll Llllll');
+ } else {
+ const found = data.existingRowData.find(({ id }) => id === targetNameRowId);
+ if (found) {
+ name = found.data.display as string;
+ }
+ }
+
+ // replace whitespace in the name and PAN
+ const nameWithoutSpaces = name.replace(/\s+/g, '');
+ const panWithoutSpaces = pan.replace(/[^\d]/g, '');
+
+ const randYear = utils.stringUtils.padString(utils.randomUtils.getRandomNum(0, 99), 2);
+ const randMonth = utils.stringUtils.padString(utils.randomUtils.getRandomNum(1, 12), 2);
+ const date = `${randYear}${randMonth}`;
+ const serviceCode = utils.randomUtils.getRandomNum(111, 999);
+
+ // could be more efficient
+ const discretionaryData = [
+ utils.randomUtils.getRandomNum(1, 9),
+ utils.randomUtils.getRandomNum(111, 999),
+ utils.randomUtils.getRandomNum(1111, 9999)
+ ];
+ const index = utils.randomUtils.getRandomNum(0, 2);
+ const dataItem = discretionaryData[index];
+ const lrc = utils.randomUtils.getRandomCharInString(' 123456789');
+
+ return {
+ display: `%B${panWithoutSpaces}^${nameWithoutSpaces}^${date}${serviceCode}${dataItem}?${lrc}`
+ };
+};
diff --git a/packages/plugins/src/dataTypes/Track1/Track1.scss b/packages/plugins/src/dataTypes/Track1/Track1.scss
new file mode 100644
index 000000000..a013c6a46
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/Track1.scss
@@ -0,0 +1,3 @@
+.buttonLabel {
+ margin-top: 4px;
+}
diff --git a/packages/plugins/src/dataTypes/Track1/Track1.scss.d.ts b/packages/plugins/src/dataTypes/Track1/Track1.scss.d.ts
new file mode 100644
index 000000000..04cc421df
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/Track1.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace Track1ScssNamespace {
+ export interface ITrack1Scss {
+ buttonLabel: string;
+ }
+}
+
+declare const Track1ScssModule: Track1ScssNamespace.ITrack1Scss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: Track1ScssNamespace.ITrack1Scss;
+};
+
+export = Track1ScssModule;
diff --git a/packages/plugins/src/dataTypes/Track1/Track1.state.tsx b/packages/plugins/src/dataTypes/Track1/Track1.state.tsx
new file mode 100644
index 000000000..c6689abe0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/Track1.state.tsx
@@ -0,0 +1,19 @@
+export type Track1Source = 'row' | 'random';
+
+export type Track1State = {
+ panSource: Track1Source;
+ targetPanRowId: string;
+ nameSource: Track1Source;
+ targetNameRowId: string;
+}
+
+export const initialState: Track1State = {
+ panSource: 'random',
+ targetPanRowId: '',
+ nameSource: 'random',
+ targetNameRowId: ''
+};
+
+export const defaultGenerationOptions = initialState;
+
+export type GenerationOptionsType = Track1State;
diff --git a/packages/plugins/src/dataTypes/Track1/Track1.store.tsx b/packages/plugins/src/dataTypes/Track1/Track1.store.tsx
new file mode 100644
index 000000000..4ce20aba0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/Track1.store.tsx
@@ -0,0 +1,70 @@
+import { createSelector } from 'reselect';
+import { DTCustomProps } from '~types/dataTypes';
+import { getSortedRowsArray } from '~store/generator/generator.selectors';
+import { Track1State } from './Track1.state';
+import { REMOVE_ROW, SELECT_DATA_TYPE } from '~store/generator/generator.actions';
+
+// this defines a custom selector that extracts information about the country fields, needed by this component. The
+// core script handles processing this and passing it back via a `countryRows` prop to our Options component
+const getPANRows = createSelector(
+ getSortedRowsArray,
+ (rows) => rows.map((row, index) => ({ ...row, index })).filter(({ dataType }) => (
+ dataType === 'PAN'
+ ))
+);
+
+const getNameRows = createSelector(
+ getSortedRowsArray,
+ (rows) => rows.map((row, index) => ({ ...row, index })).filter(({ dataType }) => (
+ dataType === 'Names'
+ ))
+);
+
+export const customProps: DTCustomProps = {
+ panRows: getPANRows,
+ nameRows: getNameRows
+};
+
+export const actionInterceptors = {
+ // when a PAN plugin row is removed, clean up any region fields that may have been mapped to it
+ [REMOVE_ROW]: (rowId: string, rowState: Track1State, actionPayload: any): Track1State | null => {
+ if (actionPayload.id === rowState.targetPanRowId) {
+ return {
+ ...rowState,
+ panSource: 'random',
+ targetPanRowId: ''
+ };
+ }
+ if (actionPayload.id === rowState.targetNameRowId) {
+ return {
+ ...rowState,
+ nameSource: 'random',
+ targetNameRowId: ''
+ };
+ }
+ return null;
+ },
+
+ // when a user changes a PAN row to something else, update any Track1 mapping
+ [SELECT_DATA_TYPE]: (rowId: string, rowState: Track1State, actionPayload: any): Track1State | null => {
+ if (actionPayload.id === rowState.targetPanRowId) {
+ if (actionPayload.value !== 'PAN') {
+ return {
+ ...rowState,
+ panSource: 'random',
+ targetPanRowId: ''
+ };
+ }
+ }
+ if (actionPayload.id === rowState.targetNameRowId) {
+ if (actionPayload.value !== 'Names') {
+ return {
+ ...rowState,
+ nameSource: 'random',
+ targetNameRowId: ''
+ };
+ }
+ }
+ return null;
+ }
+};
diff --git a/packages/plugins/src/dataTypes/Track1/Track1.tsx b/packages/plugins/src/dataTypes/Track1/Track1.tsx
new file mode 100644
index 000000000..fa60cc3de
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/Track1.tsx
@@ -0,0 +1,187 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import { DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import styles from './Track1.scss';
+
+const Track1Dialog = ({
+ visible,
+ data,
+ id,
+ panRows,
+ nameRows,
+ onClose,
+ onUpdatePANSource,
+ onUpdateNameSource,
+ onUpdateRowSource,
+ coreI18n,
+ i18n
+}: any) => {
+ const getPanSourceDropdown = (): JSX.Element | null => {
+ if (data.panSource !== 'row') {
+ return null;
+ }
+
+ const panRowOptions = panRows.map(({ id, title, index }: any) => ({
+ value: id,
+ label: `${i18n.row} #${index + 1}: ${title}`
+ }));
+
+ return (
+ onUpdateRowSource('targetPanRowId', value)}
+ options={panRowOptions}
+ />
+ );
+ };
+
+ const getNameSourceDropdown = (): JSX.Element | null => {
+ if (data.nameSource !== 'row') {
+ return null;
+ }
+
+ const nameRowOptions = nameRows.map(({ title, id, index }: any) => ({
+ value: id,
+ label: `${i18n.row} #${index + 1}: ${title}`
+ }));
+
+ return (
+ onUpdateRowSource('targetNameRowId', value)}
+ options={nameRowOptions}
+ />
+ );
+ };
+
+ return (
+
+
+
{i18n.dialogTitle}
+
+ {i18n.dialogDesc}
+
+ {i18n.panSource}
+
+
+ onUpdatePANSource('random')}
+ name={`${id}-panSource`}
+ checked={data.panSource === 'random'}
+ style={{ marginRight: 10 }}
+ />
+ onUpdatePANSource('row')}
+ name={`${id}-panSource`}
+ checked={data.panSource === 'row'}
+ tooltip={i18n.countryPluginsDesc}
+ disabled={panRows.length === 0}
+ />
+
+
+ {getPanSourceDropdown()}
+
+ {i18n.nameSource}
+
+
+ onUpdateNameSource('random')}
+ name={`${id}-nameSource`}
+ checked={data.nameSource === 'random'}
+ style={{ marginRight: 10 }}
+ />
+ onUpdateNameSource('row')}
+ name={`${id}-nameSource`}
+ checked={data.nameSource === 'row'}
+ tooltip={i18n.countryPluginsDesc}
+ disabled={nameRows.length === 0}
+ />
+
+
+ {getNameSourceDropdown()}
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ i18n, coreI18n, countryI18n, panRows, nameRows, id, data, onUpdate }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+
+ const onUpdateSource = (prop: string, value: any): void => {
+ const newValues = {
+ ...data,
+ [prop]: value
+ };
+
+ // always autoselect the first PAN/Name row when switching to a row as the source
+ if (prop === 'panSource') {
+ newValues.targetPanRowId = value === 'row' ? panRows[0].id : '';
+ } else if (prop === 'nameSource') {
+ newValues.targetNameRowId = value === 'row' ? nameRows[0].id : '';
+ }
+
+ onUpdate(newValues);
+ };
+
+ let label = i18n.customizeSource;
+ if (data.targetPanRowId !== '' && data.targetNameRowId !== '') {
+ const { index: panIndex } = panRows.find((row: any) => row.id === data.targetPanRowId);
+ const { index: nameIndex } = nameRows.find((row: any) => row.id === data.targetNameRowId);
+ label = `${i18n.panRow}: #${panIndex + 1}, ${i18n.nameRow}: #${nameIndex + 1}`;
+ } else if (data.targetPanRowId !== '') {
+ const { index } = panRows.find((row: any) => row.id === data.targetPanRowId);
+ label = `${i18n.panRow}: #${index + 1}`;
+ } else if (data.targetNameRowId !== '') {
+ const row = nameRows.find((row: any) => row.id === data.targetNameRowId);
+ label = `${i18n.nameRow}: #${row.index + 1}`;
+ }
+
+ return (
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+ onUpdateSource(section, value)}
+ onUpdatePANSource={(value: any): void => onUpdateSource('panSource', value)}
+ onUpdateNameSource={(value: any): void => onUpdateSource('nameSource', value)}
+ onClose={(): void => setDialogVisibility(false)}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.DESC}
;
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/Track1/Track1.worker.ts b/packages/plugins/src/dataTypes/Track1/Track1.worker.ts
new file mode 100644
index 000000000..55fb15d54
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/Track1.worker.ts
@@ -0,0 +1,12 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Track1.generate';
+
+let utilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/Track1/__tests__/Track1.ui.test.tsx b/packages/plugins/src/dataTypes/Track1/__tests__/Track1.ui.test.tsx
new file mode 100644
index 000000000..b328d9eb7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/__tests__/Track1.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../Track1';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Track1/bundle.ts b/packages/plugins/src/dataTypes/Track1/bundle.ts
new file mode 100644
index 000000000..e2b707dd1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/bundle.ts
@@ -0,0 +1,15 @@
+import { DTBundle } from '~types/dataTypes';
+import { Help, Options, getMetadata } from './Track1';
+import { customProps, actionInterceptors } from './Track1.store';
+import { initialState } from './Track1.state';
+
+const bundle: DTBundle = {
+ Help,
+ Options,
+ initialState,
+ customProps,
+ actionInterceptors,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Track1/config.ts b/packages/plugins/src/dataTypes/Track1/config.ts
new file mode 100644
index 000000000..97b61515e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/config.ts
@@ -0,0 +1,9 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'financial',
+ fieldGroupOrder: 70,
+ dependencies: ['PAN', 'Names']
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/ar.json b/packages/plugins/src/dataTypes/Track1/i18n/ar.json
new file mode 100644
index 000000000..1cd822572
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/ar.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "المسار 1 هو تنسيق بيانات يستخدم لتخزين المعلومات على أشرطة البطاقة الممغنطة. يحتوي على اسم حامل البطاقة بالإضافة إلى رقم الحساب والبيانات التقديرية الأخرى. قد تكون بطاقة الائتمان من أي نوع (Visa ، Mastercard ، إلخ).",
+ "DEFAULT_TITLE": "المسار رقم 1",
+ "dialogTitle": "مصدر بيانات المسار 1",
+ "dialogDesc": "يتيح لك هذا استهداف الحقول الأخرى لإنشاء قيم Track1 أكثر واقعية. بشكل افتراضي ، يقوم بإنشاء سلاسل عشوائية لأجزاء PAN والاسم ، لذا فإن توفير صفوف المصدر الفعلية يضمن قيمة Track1 صالحة ومتسقة.",
+ "panSource": "مصدر PAN",
+ "randomString": "سلسلة عشوائية",
+ "panRow": "صف PAN",
+ "nameRow": "صف الاسم",
+ "row": "صف",
+ "nameSource": "مصدر الاسم",
+ "customizeSource": "تخصيص المصدر"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/de.json b/packages/plugins/src/dataTypes/Track1/i18n/de.json
new file mode 100644
index 000000000..fa0f72d94
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/de.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 ist ein Datenformat, in dem Informationen auf Magnetkartenstreifen gespeichert werden. Es enthält den Namen des Karteninhabers sowie die Kontonummer und andere diskretionäre Daten. Die Kreditkarte kann von einem beliebigen Typ sein (Visa, Mastercard usw.).",
+ "DEFAULT_TITLE": "Lied1",
+ "dialogTitle": "Track1-Datenquelle",
+ "dialogDesc": "Auf diese Weise können Sie auf andere Felder zielen, um realistischere Track1-Werte zu erstellen. Standardmäßig werden beliebige Zeichenfolgen für die PAN- und Namensteile generiert, sodass durch die Angabe der tatsächlichen Quellzeilen ein gültiger und konsistenter Track1-Wert sichergestellt wird.",
+ "panSource": "PAN-Quelle",
+ "randomString": "Zufällige Zeichenfolge",
+ "panRow": "PAN-Reihe",
+ "nameRow": "Namenszeile",
+ "row": "Reihe",
+ "nameSource": "Namensquelle",
+ "customizeSource": "Quelle anpassen"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/en.json b/packages/plugins/src/dataTypes/Track1/i18n/en.json
new file mode 100644
index 000000000..ad01f5ecb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/en.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 is a data format used to store information on magnetic card stripes. It contains the cardholder's name as well as account number and other discretionary data. The credit card may be of any type (Visa, Mastercard, etc).",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "Track1 Data Source",
+ "dialogDesc": "This lets you target other fields to create more realistic Track1 values. By default it generates arbitrary strings for the PAN and name parts, so supplying actual rows generating that data ensures a valid and consistent Track1 value.",
+ "panSource": "PAN source",
+ "randomString": "Random string",
+ "panRow": "PAN row",
+ "nameRow": "Name row",
+ "row": "Row",
+ "nameSource": "Name source",
+ "customizeSource": "Customize source"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/es.json b/packages/plugins/src/dataTypes/Track1/i18n/es.json
new file mode 100644
index 000000000..a05033620
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/es.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 es un formato de datos que se utiliza para almacenar información en bandas de tarjetas magnéticas. Contiene el nombre del titular de la tarjeta, así como el número de cuenta y otros datos discrecionales. La tarjeta de crédito puede ser de cualquier tipo (Visa, Mastercard, etc).",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "Fuente de datos Track1",
+ "dialogDesc": "Esto le permite apuntar a otros campos para crear valores Track1 más realistas. De forma predeterminada, genera cadenas arbitrarias para el PAN y las partes del nombre, por lo que el suministro de filas de origen reales garantiza un valor Track1 válido y consistente.",
+ "panSource": "Fuente PAN",
+ "randomString": "Cadena aleatoria",
+ "panRow": "Fila PAN",
+ "nameRow": "Fila de nombre",
+ "row": "Fila",
+ "nameSource": "Fuente de nombre",
+ "customizeSource": "Personalizar fuente"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/fr.json b/packages/plugins/src/dataTypes/Track1/i18n/fr.json
new file mode 100644
index 000000000..3bd4774ca
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/fr.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 est un format de données utilisé pour stocker des informations sur des bandes de cartes magnétiques. Il contient le nom du titulaire de la carte ainsi que le numéro de compte et d'autres données discrétionnaires. La carte de crédit peut être de tout type (Visa, Mastercard, etc.).",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "Source de données Track1",
+ "dialogDesc": "Cela vous permet de cibler d'autres champs pour créer des valeurs Track1 plus réalistes. Par défaut, il génère des chaînes arbitraires pour les parties PAN et nom, de sorte que la fourniture de lignes source réelles garantit une valeur Track1 valide et cohérente.",
+ "panSource": "Source PAN",
+ "randomString": "Chaîne aléatoire",
+ "panRow": "Rangée PAN",
+ "nameRow": "Ligne de nom",
+ "row": "Rangée",
+ "nameSource": "Nom de la source",
+ "customizeSource": "Personnaliser la source"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/hi.json b/packages/plugins/src/dataTypes/Track1/i18n/hi.json
new file mode 100644
index 000000000..0aec4cdd0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/hi.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 एक डेटा प्रारूप है जिसका उपयोग चुंबकीय कार्ड धारियों पर जानकारी संग्रहीत करने के लिए किया जाता है। इसमें कार्डधारक का नाम के साथ-साथ खाता संख्या और अन्य विवेकाधीन डेटा शामिल हैं। क्रेडिट कार्ड किसी भी प्रकार का हो सकता है (वीज़ा, मास्टरकार्ड, आदि)।",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "ट्रैक1 डेटा स्रोत",
+ "dialogDesc": "यह आपको अधिक यथार्थवादी Track1 मान बनाने के लिए अन्य क्षेत्रों को लक्षित करने देता है। डिफ़ॉल्ट रूप से यह पैन और नाम भागों के लिए मनमाना तार उत्पन्न करता है, इसलिए उस डेटा को उत्पन्न करने वाली वास्तविक पंक्तियों की आपूर्ति एक वैध और सुसंगत Track1 मान सुनिश्चित करती है।",
+ "panSource": "पैन स्रोत",
+ "randomString": "यादृच्छिक स्ट्रिंग",
+ "panRow": "PAN पंक्ति",
+ "nameRow": "नाम पंक्ति",
+ "row": "पंक्ति",
+ "nameSource": "नाम स्रोत",
+ "customizeSource": "स्रोत को अनुकूलित करें"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/ja.json b/packages/plugins/src/dataTypes/Track1/i18n/ja.json
new file mode 100644
index 000000000..8e17dde54
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/ja.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "トラック1は、磁気カードストライプに情報を保存するために使用されるデータ形式です。 カード所有者の名前、口座番号、その他の任意のデータが含まれています。 クレジットカードはどのタイプでもかまいません(Visa、Mastercardなど)。",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "Track1データソース",
+ "dialogDesc": "これにより、他のフィールドをターゲットにして、より現実的なTrack1値を作成できます。 デフォルトでは、PANと名前の部分に任意の文字列が生成されるため、実際のソース行を指定すると、有効で一貫性のあるTrack1値が保証されます。",
+ "panSource": "PANソース",
+ "randomString": "ランダムな文字列",
+ "panRow": "PAN行",
+ "nameRow": "名前の行",
+ "row": "行",
+ "nameSource": "名前の出典",
+ "customizeSource": "ソースをカスタマイズする"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/nl.json b/packages/plugins/src/dataTypes/Track1/i18n/nl.json
new file mode 100644
index 000000000..051598689
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/nl.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 is een gegevensformaat dat wordt gebruikt om informatie op magnetische kaartstrips op te slaan. Het bevat zowel de naam van de kaarthouder als het rekeningnummer en andere discretionaire gegevens. De creditcard kan van elk type zijn (Visa, Mastercard, enz.).",
+ "DEFAULT_TITLE": "Spoor1",
+ "dialogTitle": "Track1-gegevensbron",
+ "dialogDesc": "Hierdoor kunt u zich op andere velden richten om realistischere Track1-waarden te creëren. Standaard genereert het willekeurige strings voor de PAN- en naamgedeelten, dus het leveren van werkelijke bronrijen zorgt voor een geldige en consistente Track1-waarde.",
+ "panSource": "PAN-bron",
+ "randomString": "Willekeurige tekenreeks",
+ "panRow": "PAN-rij",
+ "nameRow": "Naam rij",
+ "row": "Rij",
+ "nameSource": "Naam bron",
+ "customizeSource": "Pas de bron aan"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/pt.json b/packages/plugins/src/dataTypes/Track1/i18n/pt.json
new file mode 100644
index 000000000..3a3067ecd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/pt.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 é um formato de dados usado para armazenar informações em tarjas magnéticas de cartões. Ele contém o nome do titular do cartão, bem como o número da conta e outros dados discricionários. O cartão de crédito pode ser de qualquer tipo (Visa, Mastercard, etc).",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "Fonte de dados Track1",
+ "dialogDesc": "Isso permite que você direcione outros campos para criar valores Track1 mais realistas. Por padrão, ele gera strings arbitrárias para o PAN e as partes do nome, portanto, o fornecimento de linhas reais que geram esses dados garante um valor Track1 válido e consistente.",
+ "panSource": "Fonte PAN",
+ "randomString": "String aleatória",
+ "panRow": "Linha PAN",
+ "nameRow": "Linha de nome",
+ "row": "Linha",
+ "nameSource": "Fonte do nome",
+ "customizeSource": "Personalizar fonte"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/ru.json b/packages/plugins/src/dataTypes/Track1/i18n/ru.json
new file mode 100644
index 000000000..50d8bf6c0
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/ru.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "Track1 — это формат данных, используемый для хранения информации на полосах магнитных карт. Он содержит имя держателя карты, а также номер счета и другие необязательные данные. Кредитная карта может быть любого типа (Visa, Mastercard и т.д.).",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "Источник данных Track1",
+ "dialogDesc": "Это позволяет настроить другие поля для создания более реалистичных значений Track1. По умолчанию он генерирует произвольные строки для части PAN и имени, поэтому предоставление фактических строк, генерирующих эти данные, гарантирует правильное и согласованное значение Track1.",
+ "panSource": "Источник PAN",
+ "randomString": "Случайная строка",
+ "panRow": "ПАНОРАМИРОВАНИЕ строки",
+ "nameRow": "Строка имени",
+ "row": "Строка",
+ "nameSource": "Источник имени",
+ "customizeSource": "Настроить источник"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/ta.json b/packages/plugins/src/dataTypes/Track1/i18n/ta.json
new file mode 100644
index 000000000..14d6887d7
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/ta.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track 1",
+ "DESC": "Track1 என்பது காந்த அட்டை கோடுகளில் தகவல்களை சேமிக்க பயன்படுத்தப்படும் தரவு வடிவமாகும். இது அட்டைதாரரின் பெயர் மற்றும் கணக்கு எண் மற்றும் பிற விருப்ப தரவுகளைக் கொண்டுள்ளது. கிரெடிட் கார்டு எந்த வகையிலும் இருக்கலாம் (விசா, மாஸ்டர்கார்டு போன்றவை).",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "ட்ராக் 1 தரவு மூல",
+ "dialogDesc": "இது மிகவும் யதார்த்தமான ட்ராக் 1 மதிப்புகளை உருவாக்க பிற புலங்களை குறிவைக்க உங்களை அனுமதிக்கிறது. முன்னிருப்பாக இது பான் மற்றும் பெயர் பகுதிகளுக்கு தன்னிச்சையான சரங்களை உருவாக்குகிறது, எனவே உண்மையான மூல வரிசைகளை வழங்குவது சரியான மற்றும் நிலையான ட்ராக் 1 மதிப்பை உறுதி செய்கிறது.",
+ "panSource": "PAN மூல",
+ "randomString": "சீரற்ற சரம்",
+ "panRow": "PAN வரிசை",
+ "nameRow": "பெயர் வரிசை",
+ "row": "வரிசை",
+ "nameSource": "பெயர் மூல",
+ "customizeSource": "மூலத்தைத் தனிப்பயனாக்குங்கள்"
+}
diff --git a/packages/plugins/src/dataTypes/Track1/i18n/zh.json b/packages/plugins/src/dataTypes/Track1/i18n/zh.json
new file mode 100644
index 000000000..dd382a032
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track1/i18n/zh.json
@@ -0,0 +1,14 @@
+{
+ "NAME": "Track1",
+ "DESC": "磁道1是一种用于在磁卡条带上存储信息的数据格式。 它包含持卡人的姓名,帐号和其他可自行决定的数据。 信用卡可以是任何类型(Visa,Mastercard等)。",
+ "DEFAULT_TITLE": "track1",
+ "dialogTitle": "Track1数据源",
+ "dialogDesc": "这使您可以将其他字段作为目标以创建更逼真的Track1值。 默认情况下,它将为PAN和名称部分生成任意字符串,因此提供实际的源行可确保有效且一致的Track1值。",
+ "panSource": "PAN源",
+ "randomString": "随机字符串",
+ "panRow": "PAN行",
+ "nameRow": "姓名行",
+ "row": "排",
+ "nameSource": "名称来源",
+ "customizeSource": "自订来源"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/README.md b/packages/plugins/src/dataTypes/Track2/README.md
new file mode 100644
index 000000000..a7f50077c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/README.md
@@ -0,0 +1,30 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » Track2
+
+This Data Type generates a random Track2 number.
+
+
+### Example API Usage
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "Track2",
+ "title": "track 2"
+ }
+ ],
+ "export": {
+ "type": "JSON",
+ "settings": {
+ "stripWhitespace": false,
+ "dataStructureFormat": "complex"
+ }
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/dataTypes/Track2/Track2.generate.ts b/packages/plugins/src/dataTypes/Track2/Track2.generate.ts
new file mode 100644
index 000000000..d896c7f69
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/Track2.generate.ts
@@ -0,0 +1,57 @@
+/**
+ * @author Ben Keen , original code Zeeshan Shaikh
+ */
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+/*
+ Source - http://en.wikipedia.org/wiki/Magnetic_stripe_card#Financial_cards
+
+ - Start sentinel: one character (generally ';')
+ - Primary account number (PAN): up to 19 characters. Usually, but not always, matches the credit card
+ number printed on the front of the card.
+ - Separator: one char (generally '=')
+ - Expiration date: four characters in the form YYMM.
+ - Service code: three digits. The first digit specifies the interchange rules, the second specifies
+ authorisation processing and the third specifies the range of services
+ - Discretionary data: as in track one
+ - End sentinel: one character (generally '?')
+ - Longitudinal redundancy check (LRC): it is one character and a validity character calculated from
+ other data on the track.
+ - Most reader devices do not return this value when the card is swiped to the presentation layer, and
+ use it only to verify the input internally to the reader.
+*/
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { panSource, targetPanRowId } = data.rowState;
+
+ let pan = '';
+ if (panSource === 'random') {
+ pan = utils.randomUtils.generateRandomAlphanumericStr('Xxxxxxxxxxxxxxxx');
+ } else {
+ const found = data.existingRowData.find(({ id }) => id === targetPanRowId);
+ if (found) {
+ pan = found.data.display as string;
+ }
+ }
+
+ const panWithoutSpaces = pan.replace(/[^\d]/g, '');
+
+ const randYear = utils.stringUtils.padString(utils.randomUtils.getRandomNum(0, 99), 2);
+ const randMonth = utils.stringUtils.padString(utils.randomUtils.getRandomNum(1, 12), 2);
+ const date = `${randYear}${randMonth}`;
+ const serviceCode = utils.randomUtils.getRandomNum(111, 999);
+
+ // could be more efficient
+ const discretionaryData = [
+ utils.randomUtils.getRandomNum(1, 9),
+ utils.randomUtils.getRandomNum(111, 999),
+ utils.randomUtils.getRandomNum(1111, 9999)
+ ];
+ const index = utils.randomUtils.getRandomNum(0, 2);
+ const dataItem = discretionaryData[index];
+ const lrc = utils.randomUtils.getRandomCharInString(' 123456789');
+
+ return {
+ display: `%B${panWithoutSpaces}=${date}${serviceCode}${dataItem}?${lrc}`
+ };
+};
diff --git a/packages/plugins/src/dataTypes/Track2/Track2.scss b/packages/plugins/src/dataTypes/Track2/Track2.scss
new file mode 100644
index 000000000..a013c6a46
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/Track2.scss
@@ -0,0 +1,3 @@
+.buttonLabel {
+ margin-top: 4px;
+}
diff --git a/packages/plugins/src/dataTypes/Track2/Track2.scss.d.ts b/packages/plugins/src/dataTypes/Track2/Track2.scss.d.ts
new file mode 100644
index 000000000..7150370cd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/Track2.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace Track2ScssNamespace {
+ export interface ITrack2Scss {
+ buttonLabel: string;
+ }
+}
+
+declare const Track2ScssModule: Track2ScssNamespace.ITrack2Scss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: Track2ScssNamespace.ITrack2Scss;
+};
+
+export = Track2ScssModule;
diff --git a/packages/plugins/src/dataTypes/Track2/Track2.state.tsx b/packages/plugins/src/dataTypes/Track2/Track2.state.tsx
new file mode 100644
index 000000000..fb246ad62
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/Track2.state.tsx
@@ -0,0 +1,15 @@
+export type Track2Source = 'row' | 'random';
+
+export type Track2State = {
+ panSource: Track2Source;
+ targetPanRowId: string;
+}
+
+export const initialState: Track2State = {
+ panSource: 'random',
+ targetPanRowId: '',
+};
+
+export const defaultGenerationOptions = initialState;
+
+export type GenerationOptionsType = Track2State;
diff --git a/packages/plugins/src/dataTypes/Track2/Track2.store.tsx b/packages/plugins/src/dataTypes/Track2/Track2.store.tsx
new file mode 100644
index 000000000..295f2aefc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/Track2.store.tsx
@@ -0,0 +1,46 @@
+import { createSelector } from 'reselect';
+import { DTCustomProps } from '~types/dataTypes';
+import { getSortedRowsArray } from '~store/generator/generator.selectors';
+import { Track2State } from './Track2.state';
+import { REMOVE_ROW, SELECT_DATA_TYPE } from '~store/generator/generator.actions';
+
+// this defines a custom selector that extracts information about the country fields, needed by this component. The
+// core script handles processing this and passing it back via a `countryRows` prop to our Options component
+const getPANRows = createSelector(
+ getSortedRowsArray,
+ (rows) => rows.map((row, index) => ({ ...row, index })).filter(({ dataType }) => (
+ dataType === 'PAN'
+ ))
+);
+
+export const customProps: DTCustomProps = {
+ panRows: getPANRows
+};
+
+export const actionInterceptors = {
+ // when a PAN plugin row is removed, clean up any region fields that may have been mapped to it
+ [REMOVE_ROW]: (rowId: string, rowState: Track2State, actionPayload: any): Track2State | null => {
+ if (actionPayload.id === rowState.targetPanRowId) {
+ return {
+ ...rowState,
+ panSource: 'random',
+ targetPanRowId: ''
+ };
+ }
+ return null;
+ },
+
+ // when a user changes a PAN row to something else, update any Track1 mapping
+ [SELECT_DATA_TYPE]: (rowId: string, rowState: Track2State, actionPayload: any): Track2State | null => {
+ if (actionPayload.id === rowState.targetPanRowId) {
+ if (actionPayload.value !== 'PAN') {
+ return {
+ ...rowState,
+ panSource: 'random',
+ targetPanRowId: ''
+ };
+ }
+ }
+ return null;
+ }
+};
diff --git a/packages/plugins/src/dataTypes/Track2/Track2.tsx b/packages/plugins/src/dataTypes/Track2/Track2.tsx
new file mode 100644
index 000000000..e922307c5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/Track2.tsx
@@ -0,0 +1,122 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import { DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import styles from './Track2.scss';
+
+const Track2Dialog = ({ visible, data, id, panRows, onClose, onUpdatePANSource, onUpdateRowSource, coreI18n, i18n }: any) => {
+ const getPanSourceDropdown = (): JSX.Element | null => {
+ if (data.panSource !== 'row') {
+ return null;
+ }
+
+ const panRowOptions = panRows.map(({ id, title, index }: any) => ({
+ value: id,
+ label: `${i18n.row} #${index + 1}: ${title}`
+ }));
+
+ return (
+ onUpdateRowSource('targetPanRowId', value)}
+ options={panRowOptions}
+ />
+ );
+ };
+
+ return (
+
+
+
{i18n.dialogTitle}
+
+ {i18n.dialogDesc}
+
+ {i18n.panSource}
+
+
+ onUpdatePANSource('random')}
+ name={`${id}-panSource`}
+ checked={data.panSource === 'random'}
+ />
+ onUpdatePANSource('row')}
+ name={`${id}-panSource`}
+ checked={data.panSource === 'row'}
+ tooltip={i18n.countryPluginsDesc}
+ disabled={panRows.length === 0}
+ />
+
+
+ {getPanSourceDropdown()}
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ i18n, coreI18n, countryI18n, panRows, id, data, onUpdate }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+
+ const onUpdateSource = (prop: string, value: any): void => {
+ const newValues = {
+ ...data,
+ [prop]: value
+ };
+
+ // always autoselect the first Country row when switching to `Country Row` as the source
+ if (prop === 'panSource') {
+ newValues.targetPanRowId = value === 'row' ? panRows[0].id : '';
+ }
+
+ onUpdate(newValues);
+ };
+
+ let label = i18n.customizeSource;
+ if (data.targetPanRowId !== '') {
+ const { index: panIndex } = panRows.find((row: any) => row.id === data.targetPanRowId);
+ label = `${i18n.panRow}: #${panIndex + 1}`;
+ }
+
+ return (
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small">
+
+
+ onUpdateSource(section, value)}
+ onUpdatePANSource={(value: any): void => onUpdateSource('panSource', value)}
+ onClose={(): void => setDialogVisibility(false)}
+ />
+
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => {i18n.DESC}
;
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
diff --git a/packages/plugins/src/dataTypes/Track2/Track2.worker.ts b/packages/plugins/src/dataTypes/Track2/Track2.worker.ts
new file mode 100644
index 000000000..290fbee6f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/Track2.worker.ts
@@ -0,0 +1,15 @@
+/**
+ * @author Ben Keen , original code Zeeshan Shaikh
+ */
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './Track2.generate';
+
+let utilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/Track2/__tests__/Track2.ui.test.tsx b/packages/plugins/src/dataTypes/Track2/__tests__/Track2.ui.test.tsx
new file mode 100644
index 000000000..1b31d75bd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/__tests__/Track2.ui.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { Help } from '../Track2';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ coreI18n: {},
+ countryI18n: {},
+ i18n
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/dataTypes/Track2/bundle.ts b/packages/plugins/src/dataTypes/Track2/bundle.ts
new file mode 100644
index 000000000..76ee44b52
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/bundle.ts
@@ -0,0 +1,15 @@
+import { DTBundle } from '~types/dataTypes';
+import { Help, Options, getMetadata } from './Track2';
+import { customProps, actionInterceptors } from './Track2.store';
+import { initialState } from './Track2.state';
+
+const bundle: DTBundle = {
+ Help,
+ Options,
+ initialState,
+ customProps,
+ actionInterceptors,
+ getMetadata
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/Track2/config.ts b/packages/plugins/src/dataTypes/Track2/config.ts
new file mode 100644
index 000000000..a82794516
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/config.ts
@@ -0,0 +1,9 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'financial',
+ fieldGroupOrder: 80,
+ dependencies: ['PAN']
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/ar.json b/packages/plugins/src/dataTypes/Track2/i18n/ar.json
new file mode 100644
index 000000000..de31b6f4c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/ar.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "المسار 2 هو تنسيق بيانات يستخدم لتخزين المعلومات على أشرطة البطاقة الممغنطة. يحتوي على رقم حساب حامل البطاقة ورقم التعريف الشخصي المشفر والبيانات التقديرية الأخرى. قد تكون بطاقة الائتمان من أي نوع (Visa ، Mastercard ، إلخ).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "مصدر بيانات Track2",
+ "dialogDesc": "يتيح لك هذا استهداف صف PAN لإنشاء قيم Track2 أكثر واقعية. بشكل افتراضي ، يقوم بإنشاء سلاسل عشوائية لـ PAN ، لذا فإن توفير صف PAN فعلي يضمن قيمة Track2 صالحة ومتسقة.",
+ "panSource": "مصدر PAN",
+ "randomString": "سلسلة عشوائية",
+ "panRow": "صف PAN",
+ "row": "صف",
+ "customizeSource": "تخصيص المصدر"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/de.json b/packages/plugins/src/dataTypes/Track2/i18n/de.json
new file mode 100644
index 000000000..8a6d0579b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/de.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2 ist ein Datenformat, in dem Informationen auf Magnetkartenstreifen gespeichert werden. Es enthält die Kontonummer des Karteninhabers, die verschlüsselte PIN und andere diskretionäre Daten. Die Kreditkarte kann von einem beliebigen Typ sein (Visa, Mastercard usw.).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Track2-Datenquelle",
+ "dialogDesc": "Auf diese Weise können Sie eine PAN-Zeile als Ziel festlegen, um realistischere Track2-Werte zu erstellen. Standardmäßig werden beliebige Zeichenfolgen für die PAN generiert, sodass durch die Angabe einer tatsächlichen PAN-Zeile ein gültiger und konsistenter Track2-Wert sichergestellt wird.",
+ "panSource": "PAN-Quelle",
+ "randomString": "Zufällige Zeichenfolge",
+ "panRow": "PAN-Reihe",
+ "row": "Reihe",
+ "customizeSource": "Quelle anpassen"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/en.json b/packages/plugins/src/dataTypes/Track2/i18n/en.json
new file mode 100644
index 000000000..d6fe62a7e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/en.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2 is a data format used to store information on magnetic card stripes. It contains the cardholder's account number, encrypted PIN and other discretionary data. The credit card may be of any type (Visa, Mastercard, etc).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Track2 Data Source",
+ "dialogDesc": "This lets you target a PAN row to create more realistic Track2 values. By default it generates arbitrary strings for the PAN, so supplying an actual PAN row ensures a valid and consistent Track2 value.",
+ "panSource": "PAN source",
+ "randomString": "Random string",
+ "panRow": "PAN row",
+ "row": "Row",
+ "customizeSource": "Customize source"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/es.json b/packages/plugins/src/dataTypes/Track2/i18n/es.json
new file mode 100644
index 000000000..d89428102
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/es.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2 es un formato de datos que se utiliza para almacenar información en bandas de tarjetas magnéticas. Contiene el número de cuenta del titular de la tarjeta, el PIN cifrado y otros datos discrecionales. La tarjeta de crédito puede ser de cualquier tipo (Visa, Mastercard, etc).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Fuente de datos Track2",
+ "dialogDesc": "Esto le permite apuntar a una fila PAN para crear valores Track2 más realistas. De forma predeterminada, genera cadenas arbitrarias para el PAN, por lo que el suministro de una fila PAN real garantiza un valor Track2 válido y consistente.",
+ "panSource": "Fuente PAN",
+ "randomString": "Cadena aleatoria",
+ "panRow": "Fila PAN",
+ "row": "Fila",
+ "customizeSource": "Personalizar fuente"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/fr.json b/packages/plugins/src/dataTypes/Track2/i18n/fr.json
new file mode 100644
index 000000000..b9d2ab935
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/fr.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2 est un format de données utilisé pour stocker des informations sur des bandes de cartes magnétiques. Il contient le numéro de compte du titulaire de la carte, le code PIN crypté et d'autres données discrétionnaires. La carte de crédit peut être de tout type (Visa, Mastercard, etc.).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Source de données Track2",
+ "dialogDesc": "Cela vous permet de cibler une ligne PAN pour créer des valeurs Track2 plus réalistes. Par défaut, il génère des chaînes arbitraires pour le PAN, de sorte que la fourniture d'une ligne PAN réelle garantit une valeur Track2 valide et cohérente.",
+ "panSource": "Source PAN",
+ "randomString": "Chaîne aléatoire",
+ "panRow": "Rangée PAN",
+ "row": "Rangée",
+ "customizeSource": "Personnaliser la source"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/hi.json b/packages/plugins/src/dataTypes/Track2/i18n/hi.json
new file mode 100644
index 000000000..bfeea5cf5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/hi.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2 एक डेटा प्रारूप है जिसका उपयोग चुंबकीय कार्ड धारियों पर जानकारी संग्रहीत करने के लिए किया जाता है। इसमें कार्डधारक का खाता नंबर, एन्क्रिप्टेड पिन और अन्य विवेकाधीन डेटा होता है। क्रेडिट कार्ड किसी भी प्रकार का हो सकता है (वीज़ा, मास्टरकार्ड, आदि)।",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Track2 डेटा स्रोत",
+ "dialogDesc": "यह आपको अधिक यथार्थवादी Track2 मान बनाने के लिए पैन पंक्ति को लक्षित करने देता है। डिफ़ॉल्ट रूप से यह पैन के लिए मनमाना तार उत्पन्न करता है, इसलिए वास्तविक पैन पंक्ति की आपूर्ति एक वैध और सुसंगत Track2 मान सुनिश्चित करती है।",
+ "panSource": "PAN स्रोत",
+ "randomString": "यादृच्छिक स्ट्रिंग",
+ "panRow": "PAN पंक्ति",
+ "row": "पंक्ति",
+ "customizeSource": "स्रोत को अनुकूलित करें"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/ja.json b/packages/plugins/src/dataTypes/Track2/i18n/ja.json
new file mode 100644
index 000000000..57c6cb1d8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/ja.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2は、磁気カードストライプに情報を保存するために使用されるデータ形式です。 カード所有者のアカウント番号、暗号化されたPIN、およびその他の任意のデータが含まれています。 クレジットカードはどのタイプでもかまいません(Visa、Mastercardなど)。",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Track2データソース",
+ "dialogDesc": "これにより、PAN行をターゲットにして、より現実的なTrack2値を作成できます。 デフォルトでは、PANに対して任意の文字列が生成されるため、実際のPAN行を指定すると、有効で一貫性のあるTrack2値が保証されます。",
+ "panSource": "PANソース",
+ "randomString": "ランダムな文字列",
+ "panRow": "PAN行",
+ "row": "行",
+ "customizeSource": "ソースをカスタマイズする"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/nl.json b/packages/plugins/src/dataTypes/Track2/i18n/nl.json
new file mode 100644
index 000000000..d0ba95d5c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/nl.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2は、磁気カードストライプに情報を保存するために使用されるデータ形式です。 カード所有者のアカウント番号、暗号化されたPIN、およびその他の任意のデータが含まれています。 クレジットカードはどのタイプでもかまいません(Visa、Mastercardなど)。",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Track2-gegevensbron",
+ "dialogDesc": "Hiermee kunt u een PAN-rij targeten om meer realistische Track2-waarden te creëren. Standaard genereert het willekeurige strings voor de PAN, dus het leveren van een werkelijke PAN-rij zorgt voor een geldige en consistente Track2-waarde.",
+ "panSource": "PAN-bron",
+ "randomString": "Willekeurige tekenreeks",
+ "panRow": "PAN-rij",
+ "row": "Rij",
+ "customizeSource": "Pas de bron aan"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/pt.json b/packages/plugins/src/dataTypes/Track2/i18n/pt.json
new file mode 100644
index 000000000..523cebb17
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/pt.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2 é um formato de dados usado para armazenar informações em tarjas magnéticas de cartões. Ele contém o número da conta do titular do cartão, PIN criptografado e outros dados discricionários. O cartão de crédito pode ser de qualquer tipo (Visa, Mastercard, etc).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Fonte de dados Track2",
+ "dialogDesc": "Isso permite que você direcione uma linha PAN para criar valores Track2 mais realistas. Por padrão, ele gera sequências arbitrárias para o PAN, portanto, fornecer uma linha PAN real garante um valor Track2 válido e consistente.",
+ "panSource": "Fonte PAN",
+ "randomString": "String aleatória",
+ "panRow": "Linha PAN",
+ "row": "Linha",
+ "customizeSource": "Personalizar fonte"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/ru.json b/packages/plugins/src/dataTypes/Track2/i18n/ru.json
new file mode 100644
index 000000000..43a29a243
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/ru.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2 — это формат данных, используемый для хранения информации на полосах магнитных карт. Он содержит номер счета держателя карты, зашифрованный PIN-код и другие необязательные данные. Кредитная карта может быть любого типа (Visa, Mastercard и т.д.).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Источник данных Track2",
+ "dialogDesc": "Это позволяет нацеливать строку PAN для создания более реалистичных значений Track2. По умолчанию он генерирует произвольные строки для PAN, поэтому предоставление фактической строки PAN обеспечивает допустимое и согласованное значение Track2.",
+ "panSource": "Источник PAN",
+ "randomString": "Случайная строка",
+ "panRow": "PAN строка",
+ "row": "Cтрока",
+ "customizeSource": "Настроить источник"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/ta.json b/packages/plugins/src/dataTypes/Track2/i18n/ta.json
new file mode 100644
index 000000000..98b35a21a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/ta.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "ட்ராக் 2 என்பது காந்த அட்டை கோடுகளில் தகவல்களைச் சேமிக்கப் பயன்படுத்தப்படும் தரவு வடிவமாகும். இது அட்டைதாரரின் கணக்கு எண், மறைகுறியாக்கப்பட்ட பின் மற்றும் பிற விருப்ப தரவுகளைக் கொண்டுள்ளது. கிரெடிட் கார்டு எந்த வகையிலும் இருக்கலாம் (விசா, மாஸ்டர்கார்டு போன்றவை).",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "ட்ராக் 2 தரவு மூல",
+ "dialogDesc": "இது மிகவும் யதார்த்தமான ட்ராக் 2 மதிப்புகளை உருவாக்க பான் வரிசையை குறிவைக்க உங்களை அனுமதிக்கிறது. முன்னிருப்பாக இது பானுக்கான தன்னிச்சையான சரங்களை உருவாக்குகிறது, எனவே உண்மையான பான் வரிசையை வழங்குவது சரியான மற்றும் நிலையான ட்ராக் 2 மதிப்பை உறுதி செய்கிறது.",
+ "panSource": "பான் மூல",
+ "randomString": "சீரற்ற சரம்",
+ "panRow": "பான் வரிசை",
+ "row": "வரிசை",
+ "customizeSource": "மூலத்தைத் தனிப்பயனாக்குங்கள்"
+}
diff --git a/packages/plugins/src/dataTypes/Track2/i18n/zh.json b/packages/plugins/src/dataTypes/Track2/i18n/zh.json
new file mode 100644
index 000000000..009a6e69f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/Track2/i18n/zh.json
@@ -0,0 +1,12 @@
+{
+ "NAME": "Track2",
+ "DESC": "Track2是用于在磁卡条带上存储信息的数据格式。 它包含持卡人的帐号,加密的PIN和其他任意数据。 信用卡可以是任何类型(Visa,Mastercard等)。",
+ "DEFAULT_TITLE": "track2",
+ "dialogTitle": "Track2数据源",
+ "dialogDesc": "这使您可以将PAN行作为目标,以创建更逼真的Track值。 默认情况下,它将为PAN生成任意字符串,因此提供实际的PAN行可确保有效且一致的Track2值。",
+ "panSource": "PAN源",
+ "randomString": "随机字符串",
+ "panRow": "PAN行",
+ "row": "排",
+ "customizeSource": "自订来源"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/README.md b/packages/plugins/src/dataTypes/URLs/README.md
new file mode 100644
index 000000000..f3a8d9edd
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/README.md
@@ -0,0 +1,3 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » URLs
+
+This Data Type generates random URLs.
diff --git a/packages/plugins/src/dataTypes/URLs/URLs.generate.ts b/packages/plugins/src/dataTypes/URLs/URLs.generate.ts
new file mode 100644
index 000000000..56fc11af2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/URLs.generate.ts
@@ -0,0 +1,33 @@
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const {
+ protocolEnabled,
+ protocolOptions,
+ hostnameEnabled,
+ hostnameOptions,
+ pathEnabled,
+ pathOptions,
+ queryParamsEnabled,
+ queryParamsOptions
+ } = data.rowState;
+
+ let url = '';
+ if (protocolEnabled) {
+ url += utils.randomUtils.getRandomArrayValue(protocolOptions);
+ }
+ if (hostnameEnabled) {
+ url += utils.randomUtils.getRandomArrayValue(hostnameOptions);
+ }
+ if (pathEnabled) {
+ url += '/' + utils.randomUtils.getRandomArrayValue(pathOptions);
+ }
+ if (queryParamsEnabled) {
+ url += '?' + utils.randomUtils.getRandomArrayValue(queryParamsOptions);
+ }
+
+ return {
+ display: url
+ };
+};
diff --git a/packages/plugins/src/dataTypes/URLs/URLs.scss b/packages/plugins/src/dataTypes/URLs/URLs.scss
new file mode 100644
index 000000000..516eb85ad
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/URLs.scss
@@ -0,0 +1,38 @@
+.buttonLabel {
+ margin-top: 3px;
+}
+
+.settingsRow {
+ display: flex;
+ height: 34px;
+
+ .firstCol {
+ flex: 0 0 160px;
+ }
+ .secondCol {
+ flex: 1;
+
+ input {
+ width: 100%;
+ }
+ }
+}
+
+.enabledSection {
+ color: black;
+}
+
+.disabledSection {
+ color: #cccccc;
+}
+
+.optionsView {
+ background-color: #f2f2f2;
+ padding: 8px;
+ margin: 0 0 10px;
+ border-radius: 3px;
+
+ pre {
+ margin: 0;
+ }
+}
diff --git a/packages/plugins/src/dataTypes/URLs/URLs.scss.d.ts b/packages/plugins/src/dataTypes/URLs/URLs.scss.d.ts
new file mode 100644
index 000000000..4fc9ec21c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/URLs.scss.d.ts
@@ -0,0 +1,18 @@
+declare namespace UrLsScssNamespace {
+ export interface IUrLsScss {
+ buttonLabel: string;
+ disabledSection: string;
+ enabledSection: string;
+ firstCol: string;
+ optionsView: string;
+ secondCol: string;
+ settingsRow: string;
+ }
+}
+
+declare const UrLsScssModule: UrLsScssNamespace.IUrLsScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: UrLsScssNamespace.IUrLsScss;
+};
+
+export = UrLsScssModule;
diff --git a/packages/plugins/src/dataTypes/URLs/URLs.state.tsx b/packages/plugins/src/dataTypes/URLs/URLs.state.tsx
new file mode 100644
index 000000000..62258f16c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/URLs.state.tsx
@@ -0,0 +1,38 @@
+export type URLsState = {
+ example: string;
+ protocolEnabled: boolean;
+ protocolOptions: string;
+ hostnameEnabled: boolean;
+ hostnameOptions: string;
+ pathEnabled: boolean;
+ pathOptions: string;
+ queryParamsEnabled: boolean;
+ queryParamsOptions: string;
+};
+
+export type GenerationOptionsType = {
+ protocolEnabled: boolean;
+ protocolOptions: string[];
+ hostnameEnabled: boolean;
+ hostnameOptions: string[];
+ pathEnabled: boolean;
+ pathOptions: string[];
+ queryParamsEnabled: boolean;
+ queryParamsOptions: string[];
+};
+
+export const defaultGenerationOptions = {
+ protocolEnabled: true,
+ protocolOptions: 'http://,https://',
+ hostnameEnabled: true,
+ hostnameOptions: 'facebook.com,google.com,instagram.com,bbc.co.uk,guardian.co.uk,nytimes.com,cnn.com,youtube.com,wikipedia.org,netflix.com,x.com,whatsapp.com,zoom.us,reddit.com,naver.com,pinterest.com,yahoo.com,baidu.com,walmart.com,ebay.com',
+ pathEnabled: false,
+ pathOptions: 'one,sub/cars,group/9,site,en-us,en-ca,fr,settings,sub,user/110',
+ queryParamsEnabled: false,
+ queryParamsOptions: 'search=1,page=1&offset=1,q=test,client=g,ad=115,gi=100,p=8,ab=441&aad=2,g=1,str=se,k=0,q=4,q=11,q=0,search=1&q=de'
+};
+
+export const initialState: URLsState = {
+ example: '',
+ ...defaultGenerationOptions
+};
diff --git a/packages/plugins/src/dataTypes/URLs/URLs.tsx b/packages/plugins/src/dataTypes/URLs/URLs.tsx
new file mode 100644
index 000000000..56e948c30
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/URLs.tsx
@@ -0,0 +1,200 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import { DTExampleProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import Dropdown from '~components/dropdown/Dropdown';
+import CheckboxPill from '~components/pills/CheckboxPill';
+import { URLsState, GenerationOptionsType, initialState } from './URLs.state';
+import styles from './URLs.scss';
+
+export const Example = ({ data, onUpdate }: DTExampleProps) => {
+ const onChange = (value: any): void => {
+ const parts = value.split(',');
+
+ onUpdate({
+ ...data,
+ example: value,
+ protocolEnabled: parts.indexOf('protocol') !== -1,
+ hostnameEnabled: parts.indexOf('hostname') !== -1,
+ pathEnabled: parts.indexOf('path') !== -1,
+ queryParamsEnabled: parts.indexOf('queryparams') !== -1
+ });
+ };
+
+ const options = [
+ { value: 'protocol,hostname', label: 'protocol://hostname' },
+ { value: 'protocol', label: 'protocol://' },
+ { value: 'hostname', label: 'hostname' },
+ { value: 'protocol,hostname,path', label: 'protocol://hostname/path' },
+ { value: 'protocol,hostname,path,queryparams', label: 'protocol://hostname/path?queryparams' },
+ { value: 'protocol,hostname,queryparams', label: 'protocol://hostname/?queryparams' },
+ { value: 'queryparams', label: '?queryparams' }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+const URLsDialog = ({ visible, data, id, onClose, onUpdate, coreI18n, i18n }: any) => {
+ return (
+
+
+
{i18n.NAME}
+
+ {i18n.optionsDesc}
+
+ {coreI18n.options}
+
+
+
+ protocol://
+ hostname
+ /path
+ ?queryparams
+
+
+
+
+
+
+ onUpdate({ ...data, protocolEnabled: !data.protocolEnabled })}
+ name={`protocol-${id}`}
+ checked={data.protocolEnabled}
+ />
+
+
+ onUpdate({ ...data, protocolOptions: e.target.value })}
+ disabled={!data.protocolEnabled}
+ />
+
+
+
+
+ onUpdate({ ...data, hostnameEnabled: !data.hostnameEnabled })}
+ name={`hostname-${id}`}
+ checked={data.hostnameEnabled}
+ />
+
+
+ onUpdate({ ...data, hostnameOptions: e.target.value })}
+ disabled={!data.hostnameEnabled}
+ />
+
+
+
+
+ onUpdate({ ...data, pathEnabled: !data.pathEnabled })}
+ name={`path-${id}`}
+ checked={data.pathEnabled}
+ />
+
+
+ onUpdate({ ...data, pathOptions: e.target.value })}
+ disabled={!data.pathEnabled}
+ />
+
+
+
+
+ onUpdate({ ...data, queryParamsEnabled: !data.queryParamsEnabled })}
+ name={`queryParams-${id}`}
+ checked={data.queryParamsEnabled}
+ />
+
+
+ onUpdate({ ...data, queryParamsOptions: e.target.value })}
+ disabled={!data.queryParamsEnabled}
+ />
+
+
+
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ i18n, id, coreI18n, data, onUpdate }: DTOptionsProps) => {
+ const [visible, setDialogVisibility] = React.useState(false);
+
+ return (
+ <>
+ setDialogVisibility(true)}
+ variant="outlined"
+ color="primary"
+ size="small"
+ className={styles.buttonLabel}
+ >
+ {coreI18n.options}
+
+ setDialogVisibility(false)}
+ i18n={i18n}
+ />
+ >
+ );
+};
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'string'
+ },
+ sql: {
+ field: 'varchar(255)',
+ field_Oracle: 'varchar2(255)',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
+
+export const cleanListWithBackup = (listStr: string, backup: string): string[] => {
+ const list: string[] = [];
+ listStr.split(',').forEach((item) => {
+ const cleanItem = item.trim();
+ if (cleanItem) {
+ list.push(cleanItem);
+ }
+ });
+ return list.length ? list : backup.split(',');
+};
+
+// clean up the UI data so the generator script doesn't have to worry about it on every call
+export const rowStateReducer = (state: URLsState): GenerationOptionsType => ({
+ protocolEnabled: state.protocolEnabled,
+ protocolOptions: cleanListWithBackup(state.protocolOptions, initialState.protocolOptions),
+ hostnameEnabled: state.hostnameEnabled,
+ hostnameOptions: cleanListWithBackup(state.hostnameOptions, initialState.hostnameOptions),
+ pathEnabled: state.pathEnabled,
+ pathOptions: cleanListWithBackup(state.pathOptions, initialState.pathOptions),
+ queryParamsEnabled: state.queryParamsEnabled,
+ queryParamsOptions: cleanListWithBackup(state.queryParamsOptions, initialState.queryParamsOptions)
+});
diff --git a/packages/plugins/src/dataTypes/URLs/URLs.worker.ts b/packages/plugins/src/dataTypes/URLs/URLs.worker.ts
new file mode 100644
index 000000000..2a652b54c
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/URLs.worker.ts
@@ -0,0 +1,12 @@
+import utils from '../../../utils';
+import { generate } from './URLs.generate';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+
+let utilsLoaded = false;
+export const onmessage = (e: DTWorkerOnMessage): void => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/URLs/__tests__/URLs.test.tsx b/packages/plugins/src/dataTypes/URLs/__tests__/URLs.test.tsx
new file mode 100644
index 000000000..91c7ac47e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/__tests__/URLs.test.tsx
@@ -0,0 +1,51 @@
+import { cleanListWithBackup } from '../URLs';
+
+describe('cleanListWithBackup', () => {
+ it('converts list of comma-delimited spaces to an array', () => {
+ const items = 'one,two,three';
+ expect(cleanListWithBackup(items, '')).toEqual([
+ 'one',
+ 'two',
+ 'three'
+ ]);
+ });
+
+ it('removes whitespace around items', () => {
+ const items = 'one, two, three, four,five';
+ expect(cleanListWithBackup(items, '')).toEqual([
+ 'one',
+ 'two',
+ 'three',
+ 'four',
+ 'five'
+ ]);
+ });
+
+ it('removes completely empty elements', () => {
+ const items = 'one, two, , ,,three';
+ expect(cleanListWithBackup(items, '')).toEqual([
+ 'one',
+ 'two',
+ 'three',
+ ]);
+ });
+
+ it('if everything is empty, relies on backup string', () => {
+ const items = '';
+ expect(cleanListWithBackup(items, 'backup,values,here')).toEqual([
+ 'backup',
+ 'values',
+ 'here',
+ ]);
+ });
+
+ it('if everything is empty, relies on backup string #2', () => {
+ const items = ', , , , , ,,,,,,,';
+ expect(cleanListWithBackup(items, 'backup,values,here')).toEqual([
+ 'backup',
+ 'values',
+ 'here',
+ ]);
+ });
+
+});
diff --git a/packages/plugins/src/dataTypes/URLs/bundle.ts b/packages/plugins/src/dataTypes/URLs/bundle.ts
new file mode 100644
index 000000000..97105cf82
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/bundle.ts
@@ -0,0 +1,13 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, getMetadata, rowStateReducer } from './URLs';
+import { initialState } from './URLs.state';
+
+const bundle: DTBundle = {
+ Example,
+ Options,
+ initialState,
+ getMetadata,
+ rowStateReducer
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/URLs/config.ts b/packages/plugins/src/dataTypes/URLs/config.ts
new file mode 100644
index 000000000..1085c8184
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'other',
+ fieldGroupOrder: 50
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/ar.json b/packages/plugins/src/dataTypes/URLs/i18n/ar.json
new file mode 100644
index 000000000..19eae3ef2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/ar.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "عناوين المواقع",
+ "DESC": "يقوم نوع البيانات هذا بإنشاء عناوين URL عشوائية ، مما يسمح لك بإنشاء عنوان URL كامل باستخدام البروتوكول واسم المضيف واسم المسار ومعلمات الاستعلام. بدلاً من ذلك ، يمكنك إنشاء أي مجموعة فرعية من هذه الخيارات.",
+ "DEFAULT_TITLE": "عنوان url",
+ "optionsDesc": "استخدم الحقول أدناه لتخصيص تنسيق عناوين URL التي تم إنشاؤها بواسطة هذا الحقل. تحتوي الحقول النصية على خيارات مفصولة بفواصل سيتم استخدامها لهذا القسم من عنوان URL.",
+ "protocol": "بروتوكول",
+ "hostname": "اسم المضيف",
+ "path": "طريق",
+ "queryParams": "معلمات الاستعلام"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/de.json b/packages/plugins/src/dataTypes/URLs/i18n/de.json
new file mode 100644
index 000000000..f5d4d1b3f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/de.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URLs",
+ "DESC": "Dieser Datentyp generiert zufällige URLs, sodass Sie eine vollständige URL mit Protokoll, Hostname, Pfadname und Abfrageparametern generieren können. Alternativ können Sie eine beliebige Teilmenge dieser Optionen generieren.",
+ "DEFAULT_TITLE": "URL",
+ "optionsDesc": "Verwenden Sie die Felder unten, um das Format der von diesem Feld generierten URLs anzupassen. Die Textfelder enthalten durch Kommas getrennte Optionen, die für diesen Abschnitt der URL verwendet werden.",
+ "protocol": "Protokoll",
+ "hostname": "Hostname",
+ "path": "Weg",
+ "queryParams": "Parameter abfragen"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/en.json b/packages/plugins/src/dataTypes/URLs/i18n/en.json
new file mode 100644
index 000000000..7db510728
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/en.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URLs",
+ "DESC": "This Data Type generates random URLs, allowing you to generate a full URL with protocol, hostname, pathname and query params. Alternatively, you can generated any subset of those options.",
+ "DEFAULT_TITLE": "url",
+ "optionsDesc": "Use the fields below to customize the format of the URLs generated by this field. The text fields contain comma-delimited options that will be used for that section of the URL.",
+ "protocol": "Protocol",
+ "hostname": "Hostname",
+ "path": "Path",
+ "queryParams": "Query Params"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/es.json b/packages/plugins/src/dataTypes/URLs/i18n/es.json
new file mode 100644
index 000000000..9344d3ab1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/es.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URLs",
+ "DESC": "Este tipo de datos genera direcciones URL aleatorias, lo que le permite generar una dirección URL completa con protocolo, nombre de host, nombre de ruta y parámetros de consulta. Alternativamente, puede generar cualquier subconjunto de esas opciones.",
+ "DEFAULT_TITLE": "URL",
+ "optionsDesc": "Utilice los campos a continuación para personalizar el formato de las URL generadas por este campo. Los campos de texto contienen opciones delimitadas por comas que se utilizarán para esa sección de la URL.",
+ "protocol": "Protocolo",
+ "hostname": "Nombre de host",
+ "path": "Sendero",
+ "queryParams": "Paráms de consulta"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/fr.json b/packages/plugins/src/dataTypes/URLs/i18n/fr.json
new file mode 100644
index 000000000..5084d5259
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/fr.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URLs",
+ "DESC": "Ce type de données génère des URL aléatoires, vous permettant de générer une URL complète avec les paramètres de protocole, de nom d'hôte, de chemin et de requête. Alternativement, vous pouvez générer n'importe quel sous-ensemble de ces options.",
+ "DEFAULT_TITLE": "url",
+ "optionsDesc": "Utilisez les champs ci-dessous pour personnaliser le format des URL générées par ce champ. Les champs de texte contiennent des options délimitées par des virgules qui seront utilisées pour cette section de l'URL.",
+ "protocol": "Protocole",
+ "hostname": "Nom d'hôte",
+ "path": "Chemin",
+ "queryParams": "Param de requête"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/hi.json b/packages/plugins/src/dataTypes/URLs/i18n/hi.json
new file mode 100644
index 000000000..09783738a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/hi.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "यूआरएल",
+ "DESC": "यह डेटा प्रकार यादृच्छिक यूआरएल उत्पन्न करता है, जिससे आप प्रोटोकॉल, होस्टनाम, पथनाम और क्वेरी पैरामीटर के साथ एक पूर्ण यूआरएल उत्पन्न कर सकते हैं। वैकल्पिक रूप से, आप उन विकल्पों में से कोई भी सबसेट जेनरेट कर सकते हैं।",
+ "DEFAULT_TITLE": "यूआरएल",
+ "optionsDesc": "इस फ़ील्ड द्वारा उत्पन्न URL के प्रारूप को अनुकूलित करने के लिए नीचे दिए गए फ़ील्ड का उपयोग करें। टेक्स्ट फ़ील्ड में अल्पविराम-सीमांकित विकल्प होते हैं जिनका उपयोग URL के उस अनुभाग के लिए किया जाएगा।",
+ "protocol": "शिष्टाचार",
+ "hostname": "होस्ट का नाम",
+ "path": "पथ",
+ "queryParams": "क्वेरी पैराम्स"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/ja.json b/packages/plugins/src/dataTypes/URLs/i18n/ja.json
new file mode 100644
index 000000000..9770c9fa8
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/ja.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URL",
+ "DESC": "このデータ型はランダムなURLを生成し、プロトコル、ホスト名、パス名、クエリパラメータを含む完全なURLを生成できるようにします。 または、これらのオプションのサブセットを生成することもできます。",
+ "DEFAULT_TITLE": "url",
+ "optionsDesc": "以下のフィールドを使用して、このフィールドによって生成されるURLの形式をカスタマイズします。 テキストフィールドには、URLのそのセクションで使用されるコンマ区切りのオプションが含まれています。",
+ "protocol": "プロトコル",
+ "hostname": "ホスト名",
+ "path": "道",
+ "queryParams": "クエリパラメータ"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/nl.json b/packages/plugins/src/dataTypes/URLs/i18n/nl.json
new file mode 100644
index 000000000..944208984
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/nl.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URL's",
+ "DESC": "Dit gegevenstype genereert willekeurige URL's, zodat u een volledige URL kunt genereren met protocol-, hostnaam, padnaam en queryparameters. Als alternatief kunt u een subset van die opties genereren.",
+ "DEFAULT_TITLE": "url",
+ "optionsDesc": "Gebruik de onderstaande velden om de indeling aan te passen van de URL's die door dit veld worden gegenereerd. De tekstvelden bevatten door komma's gescheiden opties die voor dat gedeelte van de URL worden gebruikt.",
+ "protocol": "Protocol",
+ "hostname": "Hostnaam",
+ "path": "Pad",
+ "queryParams": "Queryparameters"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/pt.json b/packages/plugins/src/dataTypes/URLs/i18n/pt.json
new file mode 100644
index 000000000..ea4f9779d
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/pt.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URLs",
+ "DESC": "Este tipo de dados gera URLs aleatórios, permitindo que você gere um URL completo com protocolo, nome de host, nome de caminho e parâmetros de consulta. Como alternativa, você pode gerar qualquer subconjunto dessas opções.",
+ "DEFAULT_TITLE": "URL",
+ "optionsDesc": "Use os campos abaixo para personalizar o formato das URLs geradas por este campo. Os campos de texto contêm opções delimitadas por vírgulas que serão usadas para essa seção da URL.",
+ "protocol": "Protocolo",
+ "hostname": "Nome de anfitrião",
+ "path": "Caminho",
+ "queryParams": "Parâmetros de consulta"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/ru.json b/packages/plugins/src/dataTypes/URLs/i18n/ru.json
new file mode 100644
index 000000000..01676c435
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/ru.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URLs",
+ "DESC": "Этот тип данных генерирует случайные URL-адреса, позволяя вам создать полный URL-адрес с протоколом, именем хоста, путем и параметрами запроса. Кроме того, вы можете сгенерировать любое подмножество этих опций.",
+ "DEFAULT_TITLE": "url",
+ "optionsDesc": "Используйте поля ниже, чтобы настроить формат URL-адресов, генерируемых этим полем. Текстовые поля содержат параметры, разделенные запятыми, которые будут использоваться для этого раздела URL-адреса.",
+ "protocol": "Протокол",
+ "hostname": "Имя хоста",
+ "path": "Дорожка",
+ "queryParams": "Параметры запроса"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/ta.json b/packages/plugins/src/dataTypes/URLs/i18n/ta.json
new file mode 100644
index 000000000..f3b646558
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/ta.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "URLகள்",
+ "DESC": "இந்த தரவு வகை சீரற்ற URLகளை உருவாக்குகிறது, இது நெறிமுறை, ஹோஸ்ட்பெயர், பாதை பெயர் மற்றும் வினவல் அளவுருக்களுடன் முழு URL ஐ உருவாக்க அனுமதிக்கிறது. மாற்றாக, அந்த விருப்பங்களின் எந்த துணைக்குழுவையும் நீங்கள் உருவாக்கலாம்.",
+ "DEFAULT_TITLE": "url",
+ "optionsDesc": "இந்தப் புலத்தால் உருவாக்கப்பட்ட URLகளின் வடிவமைப்பைத் தனிப்பயனாக்க, கீழே உள்ள புலங்களைப் பயன்படுத்தவும். உரைப் புலங்களில் கமாவால் பிரிக்கப்பட்ட விருப்பங்கள் உள்ளன, அவை URL இன் அந்தப் பிரிவிற்குப் பயன்படுத்தப்படும்.",
+ "protocol": "நெறிமுறை",
+ "hostname": "ஹோஸ்ட் பெயர்",
+ "path": "பாதை",
+ "queryParams": "வினவல் பரிமாணங்கள்"
+}
diff --git a/packages/plugins/src/dataTypes/URLs/i18n/zh.json b/packages/plugins/src/dataTypes/URLs/i18n/zh.json
new file mode 100644
index 000000000..ce4e8fec2
--- /dev/null
+++ b/packages/plugins/src/dataTypes/URLs/i18n/zh.json
@@ -0,0 +1,10 @@
+{
+ "NAME": "网址",
+ "DESC": "此数据类型生成随机 URL,允许您生成包含协议、主机名、路径名和查询参数的完整 URL。 或者,您可以生成这些选项的任何子集。",
+ "DEFAULT_TITLE": "网址",
+ "optionsDesc": "使用以下字段自定义此字段生成的 URL 的格式。 文本字段包含将用于 URL 的该部分的逗号分隔选项。",
+ "protocol": "协议",
+ "hostname": "主机名",
+ "path": "小路",
+ "queryParams": "查询参数"
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/README.md b/packages/plugins/src/dataTypes/WeightedList/README.md
new file mode 100644
index 000000000..f36c9c45a
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/README.md
@@ -0,0 +1,16 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Data Types](../README.md) » WeightedList
+
+This Data Type generates random item or items from a weighted list. A weighted list is just a list of items where each
+item has a certain probability of occurring, for example:
+
+```
+A: 5
+B: 10
+C: 5
+```
+
+In this instance, we have three list items: A, B and C, and the number next to them represent their weight. So B is
+twice as likely to appear in the results as either A or C.
+
+## Examples
+
diff --git a/packages/plugins/src/dataTypes/WeightedList/WeightedList.generate.ts b/packages/plugins/src/dataTypes/WeightedList/WeightedList.generate.ts
new file mode 100644
index 000000000..1993ca2e9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/WeightedList.generate.ts
@@ -0,0 +1,28 @@
+import { DTGenerationData, DTGenerateResult } from '~types/dataTypes';
+import { WeightedListTypeEnum } from './WeightedList.state';
+import { WorkerUtils } from '../../';
+
+export const generate = (data: DTGenerationData, utils: WorkerUtils): DTGenerateResult => {
+ const { listType, values, exactly, betweenLow, betweenHigh, delimiter, allowDuplicates } = data.rowState;
+ const numValues = Object.keys(values).length;
+
+ let items: any = [];
+ if (listType === WeightedListTypeEnum.exactly) {
+ items = utils.randomUtils.getRandomWeightedSubset(values, exactly, allowDuplicates);
+ } else if (betweenLow && betweenHigh) {
+ const numItems = utils.randomUtils.getRandomNum(betweenLow, betweenHigh);
+ items = utils.randomUtils.getRandomWeightedSubset(values, numItems, allowDuplicates);
+ } else if (betweenLow) {
+ if (betweenLow <= numValues) {
+ const numItems = utils.randomUtils.getRandomNum(betweenLow, numValues);
+ items = utils.randomUtils.getRandomWeightedSubset(values, numItems, allowDuplicates);
+ }
+ } else if (betweenHigh !== '') {
+ const numItems = utils.randomUtils.getRandomNum(0, betweenHigh);
+ items = utils.randomUtils.getRandomWeightedSubset(values, numItems, allowDuplicates);
+ }
+
+ return {
+ display: items.join(delimiter)
+ };
+};
diff --git a/packages/plugins/src/dataTypes/WeightedList/WeightedList.scss b/packages/plugins/src/dataTypes/WeightedList/WeightedList.scss
new file mode 100644
index 000000000..5fbc30ba3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/WeightedList.scss
@@ -0,0 +1,103 @@
+.colLabel {
+ display: flex;
+ flex: 0 0 140px;
+ align-items: center;
+ margin-top: 5px;
+
+ svg {
+ margin-left: 5px;
+ }
+}
+
+.content {
+ flex: 1;
+ align-items: flex-start;
+
+ & > ul {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+
+ li {
+ margin-bottom: 5px;
+ }
+ }
+}
+
+.row {
+ display: flex;
+ align-items: flex-start;
+ margin-bottom: 10px;
+}
+
+.listTable {
+ li {
+ display: flex;
+ width: 100%;
+ padding: 2px;
+
+ &:nth-child(odd) {
+ background: #efefef;
+ }
+ }
+ input {
+ width: 100%;
+ }
+ .valueCol {
+ width: 100%;
+ margin-right: 4px;
+ }
+ .weightCol {
+ width: 80px;
+ }
+ .delCol {
+ width: 40px;
+ margin-left: 4px;
+ align-self: center;
+ cursor: pointer;
+
+ &:hover {
+ svg {
+ fill: #990000;
+ }
+ }
+ }
+ .orderCol {
+ width: 25px;
+ font-weight: bold;
+ text-align: center;
+ align-self: center;
+ }
+}
+
+ul.listTableHeader {
+ li:nth-child(odd) {
+ background: transparent;
+ }
+}
+
+.listTableBody {
+ max-height: 200px;
+ overflow: scroll;
+}
+
+.addValueRow {
+ display: flex;
+ margin: 5px 0 10px;
+
+ label {
+ color: #999999;
+ display: block;
+ height: 20px;
+ }
+ input {
+ margin-right: 4px;
+ }
+ button {
+ padding: 5px 6px;
+ }
+}
+
+.allowDuplicatesCheckbox {
+ margin-top: 8px;
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/WeightedList.scss.d.ts b/packages/plugins/src/dataTypes/WeightedList/WeightedList.scss.d.ts
new file mode 100644
index 000000000..c8e6d1e14
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/WeightedList.scss.d.ts
@@ -0,0 +1,23 @@
+declare namespace WeightedListScssNamespace {
+ export interface IWeightedListScss {
+ addValueRow: string;
+ allowDuplicatesCheckbox: string;
+ colLabel: string;
+ content: string;
+ delCol: string;
+ listTable: string;
+ listTableBody: string;
+ listTableHeader: string;
+ orderCol: string;
+ row: string;
+ valueCol: string;
+ weightCol: string;
+ }
+}
+
+declare const WeightedListScssModule: WeightedListScssNamespace.IWeightedListScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: WeightedListScssNamespace.IWeightedListScss;
+};
+
+export = WeightedListScssModule;
diff --git a/packages/plugins/src/dataTypes/WeightedList/WeightedList.state.tsx b/packages/plugins/src/dataTypes/WeightedList/WeightedList.state.tsx
new file mode 100644
index 000000000..b3f656c98
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/WeightedList.state.tsx
@@ -0,0 +1,118 @@
+import { WeightedOptions } from '@generatedata/utils';
+
+export const convertListItemsToObj = (values: WeightedListItem[]): WeightedOptions => {
+ const valuesObj: WeightedOptions = {};
+ values.forEach(({ value, weight }: WeightedListItem) => {
+ valuesObj[value] = parseInt(weight, 10);
+ });
+
+ return valuesObj;
+};
+
+export const getWeightedListItems = (values: string[]): WeightedListItem[] =>
+ values.map((value) => {
+ const match = value.match(/^(.*):\s(\d+)$/) as string[];
+ return {
+ value: match[1],
+ weight: match[2]
+ };
+ });
+
+export const getWeightedListLabels = (values: WeightedListItem[]): string[] => values.map(({ value, weight }) => `${value}: ${weight}`);
+
+export const presets = {
+ evenOdd: {
+ values: [
+ { value: '1', weight: '1' },
+ { value: '2', weight: '2' },
+ { value: '3', weight: '1' },
+ { value: '4', weight: '2' },
+ { value: '5', weight: '1' },
+ { value: '6', weight: '2' },
+ { value: '7', weight: '1' },
+ { value: '8', weight: '2' },
+ { value: '9', weight: '1' },
+ { value: '10', weight: '2' }
+ ]
+ },
+ professions: {
+ values: [
+ { value: 'Astronaut', weight: '1' },
+ { value: 'Banker', weight: '5000' },
+ { value: 'Brain surgeon', weight: '1' },
+ { value: 'Cook', weight: '8000' },
+ { value: 'Fast food/counter worker', weight: '90000' },
+ { value: 'Musician', weight: '5000' },
+ { value: 'Retail salesperson', weight: '100000' },
+ { value: 'Software Developer', weight: '10000' },
+ { value: 'Surgeon', weight: '200' }
+ ]
+ },
+ householdPets: {
+ values: [
+ { value: 'Dog', weight: '50000' },
+ { value: 'Cat', weight: '35000' },
+ { value: 'Fish', weight: '10000' },
+ { value: 'Reptile', weight: '3700' },
+ { value: 'Bird', weight: '3500' },
+ { value: 'Rabbit', weight: '1500' },
+ { value: 'Capybara', weight: '1' },
+ { value: 'Sugar glider', weight: '1' },
+ { value: 'Prairie dog', weight: '1' },
+ { value: 'Pot-bellied pig', weight: '1' }
+ ]
+ }
+};
+
+export const enum WeightedListTypeEnum {
+ exactly = 'exactly',
+ between = 'between'
+}
+export type WeightedListType = `${WeightedListTypeEnum}`;
+
+export type WeightedListItem = {
+ value: string;
+ weight: string; // for convenience this is stored as a string, but when passed to
+};
+
+export type WeightedListState = {
+ example: string;
+ listType: WeightedListType;
+ exactly: string;
+ betweenLow: string;
+ betweenHigh: string;
+ allowDuplicates: boolean;
+ delimiter: string;
+ values: WeightedListItem[];
+};
+
+export type GenerationOptionsType = {
+ listType: WeightedListType;
+ exactly: string;
+ betweenLow: string;
+ betweenHigh: string;
+ allowDuplicates: boolean;
+ delimiter: string;
+ values: WeightedOptions;
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ listType: 'exactly',
+ exactly: '1',
+ betweenLow: '',
+ betweenHigh: '',
+ allowDuplicates: true,
+ delimiter: ', ',
+ values: convertListItemsToObj(presets.evenOdd.values)
+};
+
+export const initialState: WeightedListState = {
+ example: 'even-odd',
+ listType: 'exactly',
+ exactly: '1',
+ betweenLow: '',
+ betweenHigh: '',
+ values: presets.evenOdd.values,
+ delimiter: ', ',
+ allowDuplicates: true
+};
diff --git a/packages/plugins/src/dataTypes/WeightedList/WeightedList.tsx b/packages/plugins/src/dataTypes/WeightedList/WeightedList.tsx
new file mode 100644
index 000000000..661769f76
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/WeightedList.tsx
@@ -0,0 +1,447 @@
+import * as React from 'react';
+import Button from '@mui/material/Button';
+import InfoIcon from '@mui/icons-material/InfoOutlined';
+import { DTExampleProps, DTHelpProps, DTMetadata, DTOptionsProps } from '~types/dataTypes';
+import Dropdown from '~components/dropdown/Dropdown';
+import TextField from '~components/TextField';
+import CreatablePillField from '~components/creatablePillField/CreatablePillField';
+import { Dialog, DialogActions, DialogContent, DialogTitle } from '~components/dialogs';
+import { Tooltip } from '~components/tooltips';
+import * as langUtils from '~utils/langUtils';
+import {
+ presets,
+ WeightedListTypeEnum,
+ WeightedListItem,
+ getWeightedListLabels,
+ getWeightedListItems,
+ WeightedListState,
+ GenerationOptionsType,
+ convertListItemsToObj
+} from './WeightedList.state';
+import * as styles from './WeightedList.scss';
+
+export const Example = ({ data, onUpdate, i18n }: DTExampleProps) => {
+ const onChange = (example: any): void => {
+ let values: WeightedListItem[] = [];
+ if (example === 'even-odd') {
+ values = presets.evenOdd.values;
+ } else if (example === 'professions') {
+ values = presets.professions.values;
+ } else if (example === 'household-pets') {
+ values = presets.householdPets.values;
+ }
+ onUpdate({
+ ...data,
+ example: example,
+ values
+ });
+ };
+
+ const options = [
+ { value: 'even-odd', label: i18n.mostlyEvenNumbers },
+ { value: 'professions', label: i18n.professions },
+ { value: 'household-pets', label: i18n.householdPets }
+ ];
+
+ return onChange(i.value)} options={options} />;
+};
+
+const WeightedListDialog = ({ visible, data, id, onClose, onUpdate, coreI18n, i18n }: any) => {
+ const exactlyField = React.useRef();
+ const dtListBetweenLow = React.useRef();
+ const [showErrors, setShowErrors] = React.useState(false);
+ const [value, setValue] = React.useState('');
+ const [weight, setWeight] = React.useState('');
+ const [displayStrings, setDisplayStrings] = React.useState([]);
+
+ const onChangeValue = (e: any): void => setValue(e.target.value);
+ const onChangeWeight = (e: any): void => setWeight(e.target.value);
+
+ React.useEffect(() => {
+ setDisplayStrings(getWeightedListLabels(data.values));
+ }, [data.values]);
+
+ const onChange = (field: string, value: any): void => {
+ onUpdate({
+ ...data,
+ [field]: value
+ });
+ };
+
+ const onAdd = (): void => {
+ setShowErrors(true);
+ if (value && weight) {
+ onUpdate({
+ ...data,
+ values: [...data.values, { value, weight }]
+ });
+ setShowErrors(false);
+ setValue('');
+ setWeight('');
+ }
+ };
+
+ const updateDelimiter = (e: any): void => {
+ onUpdate({
+ ...data,
+ delimiter: e.target.value
+ });
+ };
+
+ let exactlyError = '';
+ let betweenLowError = '';
+ let betweenHighError = '';
+ if (data.listType === WeightedListTypeEnum.exactly) {
+ if (!data.exactly) {
+ exactlyError = coreI18n.requiredField;
+ } else if (displayStrings.length < parseInt(data.exactly, 10)) {
+ exactlyError = i18n.listTooShort;
+ }
+ } else {
+ if (!data.betweenLow) {
+ betweenLowError = coreI18n.requiredField;
+ } else if (displayStrings.length < parseInt(data.betweenLow, 10)) {
+ betweenLowError = i18n.listTooShort;
+ }
+ if (!data.betweenHigh) {
+ betweenHighError = coreI18n.requiredField;
+ } else if (displayStrings.length < parseInt(data.betweenHigh, 10)) {
+ betweenHighError = i18n.listTooShort;
+ }
+ }
+
+ const onChangeList = (newValues: string[]): void => {
+ onUpdate({
+ ...data,
+ values: getWeightedListItems(newValues)
+ });
+ };
+
+ const updateAllowDuplicates = (e: any): void => {
+ onUpdate({
+ ...data,
+ allowDuplicates: e.target.checked
+ });
+ };
+
+ return (
+
+
+
{i18n.weightedListSettings}
+
+
+
+ {i18n.numItemsLabel}
+
+
+
+
+
+
+
+
+ {i18n.allowDuplicates}
+
+
+
+
+
+
+
+
+
+
+ {i18n.delimChars}
+
+
+
+
+
+
+
+
+
+
{i18n.itemsTitle}
+
+
+
+ {displayStrings.length ? (
+
+ ) : (
+
{i18n.pleaseAddItems}
+ )}
+
+
+
+
+
+
+ {coreI18n.close}
+
+
+
+
+ );
+};
+
+export const Options = ({ coreI18n, i18n, data, id, onUpdate }: DTOptionsProps) => {
+ const [dialogVisible, setDialogVisibility] = React.useState(false);
+
+ const safeData = {
+ ...data
+ };
+
+ if (safeData.atMost) {
+ safeData.betweenHigh = safeData.atMost;
+ delete safeData.atMost;
+ }
+
+ if (!safeData.betweenLow) {
+ safeData.betweenLow = '';
+ }
+ if (!safeData.betweenHigh) {
+ safeData.betweenHigh = '';
+ }
+
+ let label;
+ if (safeData.listType === WeightedListTypeEnum.exactly) {
+ if (safeData.exactly === '1') {
+ label = langUtils.getI18nString(i18n.exactly1Item, ['1 ']);
+ } else {
+ label = langUtils.getI18nString(i18n.exactlyNItems, [`${safeData.exactly} `]);
+ }
+ } else if (!safeData.betweenLow && !safeData.betweenHigh) {
+ label = i18n.noRangeEntered;
+ } else if (safeData.betweenLow && safeData.betweenHigh) {
+ label = langUtils.getI18nString(i18n.betweenNumItems, [`${safeData.betweenLow} `, `${safeData.betweenHigh} `]);
+ } else if (safeData.betweenLow) {
+ if (safeData.betweenLow === '1') {
+ label = langUtils.getI18nString(i18n.atLeast1Item, ['1 ']);
+ } else {
+ label = langUtils.getI18nString(i18n.atLeastNItems, [`${safeData.betweenLow} `]);
+ }
+ } else {
+ if (safeData.betweenHigh === '1') {
+ label = langUtils.getI18nString(i18n.atMost1Item, ['1 ']);
+ } else {
+ label = langUtils.getI18nString(i18n.atMostNItems, [`${safeData.betweenHigh} `]);
+ }
+ }
+
+ return (
+ <>
+
+
+ setDialogVisibility(true)} variant="outlined" color="primary" size="small" style={{ marginLeft: 6 }}>
+ {i18n.customize}
+
+
+ setDialogVisibility(false)}
+ />
+ >
+ );
+};
+
+export const Help = ({ i18n }: DTHelpProps) => (
+ <>
+
+
+
+
+ {i18n.helpValueWeight}
+
+ {i18n.helpBrainSurgeon}
+ {i18n.helpAstronaut}
+ {i18n.helpBanker}
+ {i18n.helpSoftwareDeveloper}
+ {i18n.helpEtc}
+
+ {i18n.helpOtherOptions}
+
+ {i18n.helpOption1}
+ {i18n.helpOption2}
+ {i18n.helpOption3}
+
+ >
+);
+
+export const getMetadata = (): DTMetadata => ({
+ general: {
+ dataType: 'infer'
+ },
+ sql: {
+ field: 'varchar(255) default NULL',
+ field_Oracle: 'varchar2(255) default NULL',
+ field_MSSQL: 'VARCHAR(255) NULL'
+ }
+});
+
+export const rowStateReducer = ({
+ delimiter,
+ listType,
+ exactly,
+ betweenLow = '',
+ betweenHigh = '',
+ values,
+ allowDuplicates
+}: WeightedListState): GenerationOptionsType => {
+ let cleanExactly: any = '';
+ let cleanBetweenLow: any = '';
+ let cleanBetweenHigh: any = '';
+
+ if (listType === WeightedListTypeEnum.exactly) {
+ if (exactly.trim() !== '') {
+ cleanExactly = parseInt(exactly.trim(), 10);
+ }
+ } else {
+ if (betweenLow.trim() !== '') {
+ cleanBetweenLow = parseInt(betweenLow.trim(), 10);
+ }
+ if (betweenHigh.trim() !== '') {
+ cleanBetweenHigh = parseInt(betweenHigh.trim(), 10);
+ }
+
+ // ensure that the number are sorted low to high - makes the generation code have to do less work on every
+ // iteration
+ if (cleanBetweenLow !== '' && cleanBetweenHigh !== '') {
+ if (cleanBetweenLow > cleanBetweenHigh) {
+ const oldLow = cleanBetweenLow;
+ cleanBetweenLow = cleanBetweenHigh;
+ cleanBetweenHigh = oldLow;
+ }
+ }
+ }
+
+ return {
+ listType,
+ exactly: cleanExactly,
+ betweenLow: cleanBetweenLow,
+ betweenHigh: cleanBetweenHigh,
+ values: convertListItemsToObj(values),
+ delimiter: delimiter ? delimiter : ', ',
+ allowDuplicates
+ };
+};
diff --git a/packages/plugins/src/dataTypes/WeightedList/WeightedList.worker.ts b/packages/plugins/src/dataTypes/WeightedList/WeightedList.worker.ts
new file mode 100644
index 000000000..1cfae6d4f
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/WeightedList.worker.ts
@@ -0,0 +1,14 @@
+import utils from '../../../utils';
+import { DTWorkerOnMessage } from '~types/dataTypes';
+import { generate } from './WeightedList.generate';
+
+let utilsLoaded = false;
+
+export const onmessage = (e: DTWorkerOnMessage) => {
+ if (!utilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ utilsLoaded = true;
+ }
+
+ postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/dataTypes/WeightedList/__tests__/WeightedList.generate.test.ts b/packages/plugins/src/dataTypes/WeightedList/__tests__/WeightedList.generate.test.ts
new file mode 100644
index 000000000..1ef85cc62
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/__tests__/WeightedList.generate.test.ts
@@ -0,0 +1,22 @@
+import utils from '../../../../utils';
+import { generate } from '../WeightedList.generate';
+import { getBlankDTGeneratorPayload } from '../../../../../tests/testHelpers';
+
+describe('onmessage', () => {
+ it('generates random data', () => {
+ const data: any = {
+ ...getBlankDTGeneratorPayload(),
+ rowState: {
+ listType: 'exactly',
+ values: {
+ "123": 1
+ },
+ exactly: 1,
+ atMost: 1,
+ allowDuplicates: true
+ }
+ };
+
+ expect(generate(data, utils)).toEqual({ display: '123' });
+ });
+});
diff --git a/packages/plugins/src/dataTypes/WeightedList/__tests__/WeightedList.test.tsx b/packages/plugins/src/dataTypes/WeightedList/__tests__/WeightedList.test.tsx
new file mode 100644
index 000000000..08fe12efb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/__tests__/WeightedList.test.tsx
@@ -0,0 +1,67 @@
+import React from 'react'
+import { render } from '@testing-library/react';
+import { getWeightedListItems, WeightedListItem } from '../WeightedList.state';
+import { Help, Options } from '../WeightedList';
+const i18n = require('../i18n/en.json');
+
+const defaultProps = {
+ data: {
+ values: []
+ },
+ coreI18n: {},
+ countryI18n: {},
+ i18n,
+ id: 'id',
+ gridPanelDimensions: { width: 100, height: 100 },
+ onUpdate: () => {},
+ isCountryNamesLoading: false,
+ isCountryNamesLoaded: false,
+ countryNamesMap: null
+};
+
+describe('Help', () => {
+ it('renders', () => {
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('Options', () => {
+ it('renders', () => {
+ const { container } = render(
+
+ );
+ expect(container).toBeTruthy();
+ });
+});
+
+describe('getWeightedListItems', () => {
+ it('converts strings as expected', () => {
+ const items = [
+ 'one: 1',
+ 'two: 2',
+ 'three: 22491',
+ 'four: 0'
+ ];
+ const expected: WeightedListItem[] = [
+ { value: 'one', weight: '1' },
+ { value: 'two', weight: '2' },
+ { value: 'three', weight: '22491' },
+ { value: 'four', weight: '0' }
+ ]
+ expect(getWeightedListItems(items)).toEqual(expected);
+ });
+
+ it('converts strings that contain the delim chars', () => {
+ const items = [
+ 'one: two: three: 1',
+ ];
+ const expected: WeightedListItem[] = [
+ { value: 'one: two: three', weight: '1' }
+ ]
+ expect(getWeightedListItems(items)).toEqual(expected);
+ });
+});
+
diff --git a/packages/plugins/src/dataTypes/WeightedList/bundle.ts b/packages/plugins/src/dataTypes/WeightedList/bundle.ts
new file mode 100644
index 000000000..96ed0f305
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/bundle.ts
@@ -0,0 +1,14 @@
+import { DTBundle } from '~types/dataTypes';
+import { Example, Options, Help, getMetadata, rowStateReducer } from './WeightedList';
+import { initialState } from './WeightedList.state';
+
+const bundle: DTBundle = {
+ initialState,
+ Example,
+ Options,
+ Help,
+ getMetadata,
+ rowStateReducer
+};
+
+export default bundle;
diff --git a/packages/plugins/src/dataTypes/WeightedList/config.ts b/packages/plugins/src/dataTypes/WeightedList/config.ts
new file mode 100644
index 000000000..a23e8ced9
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/config.ts
@@ -0,0 +1,8 @@
+import { DTDefinition } from '../../';
+
+const definition: DTDefinition = {
+ fieldGroup: 'other',
+ fieldGroupOrder: 45
+};
+
+export default definition;
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/ar.json b/packages/plugins/src/dataTypes/WeightedList/i18n/ar.json
new file mode 100644
index 000000000..31e6316dc
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/ar.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "قائمة مرجحة",
+ "DESC": "يولد قيمة عشوائية مرجحة واحدة أو أكثر من قائمة العناصر.",
+ "DEFAULT_TITLE": "weightedlist",
+ "weightedListSettings": "إعدادات القائمة المرجحة",
+ "atMost": "في الغالب",
+ "exactly": "بالضبط",
+ "numItemsLabel": "عدد العناصر",
+ "numItemsLabelDesc": "يتحكم هذا الإعداد في عدد العناصر التي تم إنشاؤها من قائمتك. يمكنك اختيار عدد محدد من العناصر لتوليدها ، أو رقم بين نطاق من اختيارك.",
+ "exactly1Item": "عنصر واحد بالضبط من القائمة",
+ "exactlyNItems": "بالضبط %1 من العناصر من القائمة",
+ "atMost1Item": "على الأكثر %1 عنصر من القائمة",
+ "atMostNItems": "على الأكثر %1 عناصر من القائمة",
+ "customize": "يعدل أو يكيف",
+ "delimChars": "حرف (أحرف) محدد",
+ "betweenNumItems": "بين العناصر %1 و %2.",
+ "atLeast1Item": "ما لا يقل عن %1 عنصر من القائمة",
+ "atLeastNItems": "ما لا يقل عن %1 عناصر من القائمة",
+ "noRangeEntered": "لم يتم إدخال أي نطاق",
+ "between": "بين",
+ "and": "و",
+ "items": "العناصر",
+ "itemsTitle": "العناصر",
+ "mostlyEvenNumbers": "في الغالب أرقام زوجية",
+ "professions": "المهن",
+ "householdPets": "الحيوانات الأليفة المنزلية",
+ "listTooShort": "تأكد من أنك أدخلت عددًا كافيًا من العناصر في قائمتك ، وإلا فلن تنشئ عدد العناصر المحددة هنا.",
+ "allowDuplicates": "السماح بالتكرارات",
+ "value": "قيمة",
+ "weight": "وزن",
+ "addBtnLabel": "يضيف",
+ "pleaseAddItems": "الرجاء إضافة بعض العناصر.",
+ "helpIntro": "يُنشئ نوع بيانات القائمة الموزونة عنصرًا أو عناصر تم اختيارها عشوائيًا من قائمة معرّفة بشكل مخصص ، مع مراعاة الوزن الذي تريد تطبيقه على كل عنصر. يتيح لك حقل الوزن تغيير احتمالية ظهور كل عنصر حتى تتمكن من إنشاء بيانات أكثر واقعية. على سبيل المثال ، لنفترض أنك أردت إنشاء قائمة بالمهن ، لكنك أردت أن يظهر \"جراح الدماغ\" و \"رائد الفضاء\" بشكل غير متكرر لأنهما غير شائعين. لذلك ، يمكنك فقط رفع أوزان الآخرين:",
+ "helpValueWeight": "(قيمة الوزن)",
+ "helpBrainSurgeon": "جراح المخ ، 1",
+ "helpAstronaut": "رائد فضاء ، 1",
+ "helpBanker": "بانكر ، 800",
+ "helpSoftwareDeveloper": "مطور برامج ، 1000",
+ "helpEtc": "مساعدة إلخ.",
+ "helpOtherOptions": "يوفر نوع البيانات أيضًا الخيارات التالية:",
+ "helpOption1": "يتيح لك إنشاء إما عدد محدد من العناصر من القائمة ، أو مجموعة فرعية من حجم عشوائي.",
+ "helpOption2": "اختر أي حرف (أحرف) محدد يتم استخدامه لعناصر متعددة.",
+ "helpOption3": "اختر ما إذا كنت تريد إزالة التكرارات عندما يُنشئ صف واحد أكثر من عنصر واحد.",
+ "allowDuplicatesDesc": "عندما تقوم بإنشاء أكثر من عنصر واحد من قائمتك ، فإن هذا الإعداد يتحكم في ما إذا كان مسموحًا بالتكرارات أم لا.",
+ "delimCharsDesc": "إذا كنت تقوم بتوليد عناصر متعددة ، فإن هذا الحقل يحدد الحرف الذي يجب استخدامه لفصل القيم."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/de.json b/packages/plugins/src/dataTypes/WeightedList/i18n/de.json
new file mode 100644
index 000000000..00967484b
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/de.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "Gewichtete Liste",
+ "DESC": "Generiert einen oder mehrere gewichtete Zufallswerte aus einer Liste von Elementen.",
+ "DEFAULT_TITLE": "gewichteteliste",
+ "weightedListSettings": "Gewichtete Listeneinstellungen",
+ "atMost": "Maximal",
+ "exactly": "Exakt",
+ "numItemsLabel": "Anzahl Artikel",
+ "numItemsLabelDesc": "Diese Einstellung steuert die Anzahl der aus Ihrer Liste generierten Elemente. Sie können eine genaue Anzahl der zu generierenden Elemente oder eine Anzahl aus einem Bereich Ihrer Wahl auswählen.",
+ "exactly1Item": "Genau %1 Element aus der Liste",
+ "exactlyNItems": "Genau %1 Elemente aus der Liste",
+ "atMost1Item": "Höchstens %1 Element aus der Liste",
+ "atMostNItems": "Höchstens %1 Elemente aus der Liste",
+ "customize": "Anpassen",
+ "delimChars": "Trennzeichen",
+ "betweenNumItems": "Zwischen %1 und %2 Artikel",
+ "atLeast1Item": "Mindestens %1 Element aus der Liste",
+ "atLeastNItems": "Mindestens %1 Elemente aus der Liste",
+ "noRangeEntered": "Kein Bereich eingegeben",
+ "between": "Zwischen",
+ "and": "und",
+ "items": "Artikel",
+ "itemsTitle": "Artikel",
+ "mostlyEvenNumbers": "Meist gerade Zahlen",
+ "professions": "Berufe",
+ "householdPets": "Haustiere",
+ "listTooShort": "Stellen Sie sicher, dass Sie genügend Artikel in Ihre Liste eingegeben haben, sonst wird die hier angegebene Anzahl von Artikeln nicht generiert.",
+ "allowDuplicates": "Duplikate zulassen",
+ "value": "Wert",
+ "weight": "Gewicht",
+ "addBtnLabel": "Hinzufügen",
+ "pleaseAddItems": "Bitte fügen Sie einige Artikel hinzu.",
+ "helpIntro": "Der Datentyp „Gewichtete Liste“ generiert zufällig ausgewählte Elemente aus einer benutzerdefinierten Liste, wobei die Gewichtung berücksichtigt wird, die Sie auf jedes Element anwenden möchten. Mit dem Gewichtungsfeld können Sie die Wahrscheinlichkeit ändern, mit der jedes Element erscheint, sodass Sie realistischere Daten erstellen können. Angenommen, Sie möchten eine Liste mit Berufen erstellen, möchten aber, dass „Gehirnchirurg“ und „Astronaut“ sehr selten erscheinen, weil sie nicht üblich sind. Dafür könnten Sie einfach die Gewichte der anderen erhöhen:",
+ "helpValueWeight": "(Wert, Gewicht)",
+ "helpBrainSurgeon": "Gehirnchirurg, 1",
+ "helpAstronaut": "Astronaut, 1",
+ "helpBanker": "Bankier, 800",
+ "helpSoftwareDeveloper": "Softwareentwickler, 1000",
+ "helpEtc": "Hilfe usw.",
+ "helpOtherOptions": "Der Datentyp bietet außerdem die folgenden Optionen:",
+ "helpOption1": "Sie können damit entweder eine bestimmte Anzahl von Elementen aus der Liste oder eine Teilmenge einer zufälligen Größe generieren.",
+ "helpOption2": "Wählen Sie die Trennzeichen aus, die für mehrere Elemente verwendet werden.",
+ "helpOption3": "Wählen Sie aus, ob Sie Duplikate eliminieren möchten, wenn eine einzelne Zeile mehr als ein Element generiert.",
+ "allowDuplicatesDesc": "Wenn Sie mehr als ein Element aus Ihrer Liste generieren, steuert diese Einstellung, ob Duplikate zulässig sind.",
+ "delimCharsDesc": "Wenn Sie mehrere Elemente generieren, bestimmt dieses Feld, welches Zeichen zum Trennen der Werte verwendet werden soll."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/en.json b/packages/plugins/src/dataTypes/WeightedList/i18n/en.json
new file mode 100644
index 000000000..b1c47beeb
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/en.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "Weighted List",
+ "DESC": "Generates one or more weighted random values from a list of items.",
+ "DEFAULT_TITLE": "weightedlist",
+ "weightedListSettings": "Weighted List Settings",
+ "atMost": "At Most",
+ "exactly": "Exactly",
+ "numItemsLabel": "Num items",
+ "numItemsLabelDesc": "This setting controls the number of items generated from your list. You can choose an exact number of items to generate, or a number between a range of your choosing.",
+ "exactly1Item": "Exactly %1 item from list",
+ "exactlyNItems": "Exactly %1 items from list",
+ "atMost1Item": "At most %1 item from list",
+ "atMostNItems": "At most %1 items from list",
+ "customize": "Customize",
+ "delimChars": "Delimiter char(s)",
+ "betweenNumItems": "Between %1 and %2 items",
+ "atLeast1Item": "At least %1 item from list",
+ "atLeastNItems": "At least %1 items from list",
+ "noRangeEntered": "No range entered",
+ "between": "Between",
+ "and": "and",
+ "items": "items",
+ "itemsTitle": "Items",
+ "mostlyEvenNumbers": "Mostly even numbers",
+ "professions": "Professions",
+ "householdPets": "Household pets",
+ "listTooShort": "Make sure you've entered enough items in your list, or it won't generate the number of items specified here.",
+ "allowDuplicates": "Allow duplicates",
+ "value": "Value",
+ "weight": "Weight",
+ "addBtnLabel": "Add »",
+ "pleaseAddItems": "Please add some items.",
+ "helpIntro": "The Weighted List Data Type generates randomly chosen item or items from a custom-defined list, factoring in whatever weight you want to apply to each item. The weight field lets you change the probability of each item appearing so you can create more realistic-looking data. For example, say you wanted to generate a list of professions, but wanted 'Brain surgeon' and 'Astronaut' to appear very infrequently because they're not common. For that, you could just bump up the weights of the others:",
+ "helpValueWeight": "(value, weight)",
+ "helpBrainSurgeon": "Brain surgeon, 1",
+ "helpAstronaut": "Astronaut, 1",
+ "helpBanker": "Banker, 800",
+ "helpSoftwareDeveloper": "Software Developer, 1000",
+ "helpEtc": "Help etc.",
+ "helpOtherOptions": "The Data Type also provides the following options:",
+ "helpOption1": "It lets you generate either a specific number of items from the list, or a subset of a random size.",
+ "helpOption2": "Choose whatever delimiter character(s) are used for multiple items.",
+ "helpOption3": "Choose whether you want to eliminate duplicates when a single row generates more than one item.",
+ "allowDuplicatesDesc": "When you are generating more than one item from your list, this setting controls whether duplicates are permitted.",
+ "delimCharsDesc": "If you are generating multiple items, this field determines what character should be used to separate the values."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/es.json b/packages/plugins/src/dataTypes/WeightedList/i18n/es.json
new file mode 100644
index 000000000..66618bbab
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/es.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "Lista ponderada",
+ "DESC": "Genera uno o más valores aleatorios ponderados a partir de una lista de elementos.",
+ "DEFAULT_TITLE": "listaponderada",
+ "weightedListSettings": "Configuración de la lista ponderada",
+ "atMost": "A lo sumo",
+ "exactly": "Exactamente",
+ "numItemsLabel": "Número de elementos",
+ "numItemsLabelDesc": "Esta configuración controla la cantidad de elementos generados a partir de su lista. Puede elegir un número exacto de elementos para generar, o un número entre un rango de su elección.",
+ "exactly1Item": "Exactamente %1 elemento de la lista",
+ "exactlyNItems": "Exactamente %1 elementos de la lista",
+ "atMost1Item": "Como máximo %1 elemento de la lista",
+ "atMostNItems": "Como máximo %1 elementos de la lista",
+ "customize": "Personalizar",
+ "delimChars": "Carácter(es) delimitador(es)",
+ "betweenNumItems": "Entre %1 y %2 elementos",
+ "atLeast1Item": "Al menos %1 elemento de la lista",
+ "atLeastNItems": "Al menos %1 elementos de la lista",
+ "noRangeEntered": "No se ingresó rango",
+ "between": "Entre",
+ "and": "y",
+ "items": "elementos",
+ "itemsTitle": "Elementos",
+ "mostlyEvenNumbers": "En su mayoría números pares",
+ "professions": "Profesiones",
+ "householdPets": "Animales domésticos",
+ "listTooShort": "Asegúrese de haber ingresado suficientes elementos en su lista, o no generará la cantidad de elementos especificados aquí.",
+ "allowDuplicates": "Permitir duplicados",
+ "value": "Valor",
+ "weight": "Peso",
+ "addBtnLabel": "Agregar",
+ "pleaseAddItems": "Agregue algunos artículos.",
+ "helpIntro": "El tipo de datos de lista ponderada genera un elemento o elementos elegidos al azar de una lista definida de forma personalizada, teniendo en cuenta el peso que desee aplicar a cada elemento. El campo de ponderación le permite cambiar la probabilidad de que aparezca cada elemento para que pueda crear datos que parezcan más realistas. Por ejemplo, supongamos que desea generar una lista de profesiones, pero desea que 'Cirujano cerebral' y 'Astronauta' aparezcan con poca frecuencia porque no son comunes. Para eso, podrías aumentar el peso de los demás:",
+ "helpValueWeight": "(valor de peso)",
+ "helpBrainSurgeon": "Neurocirujano, 1",
+ "helpAstronaut": "Astronauta, 1",
+ "helpBanker": "Banquero, 800",
+ "helpSoftwareDeveloper": "Desarrollador de software, 1000",
+ "helpEtc": "Ayuda etc",
+ "helpOtherOptions": "El tipo de datos también proporciona las siguientes opciones:",
+ "helpOption1": "Le permite generar un número específico de elementos de la lista o un subconjunto de un tamaño aleatorio.",
+ "helpOption2": "Elija los caracteres delimitadores que se utilicen para varios elementos.",
+ "helpOption3": "Elija si desea eliminar los duplicados cuando una sola fila genera más de un elemento.",
+ "allowDuplicatesDesc": "Cuando está generando más de un elemento de su lista, esta configuración controla si se permiten duplicados.",
+ "delimCharsDesc": "Si está generando varios elementos, este campo determina qué carácter debe usarse para separar los valores."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/fr.json b/packages/plugins/src/dataTypes/WeightedList/i18n/fr.json
new file mode 100644
index 000000000..bf4351955
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/fr.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "Liste pondérée",
+ "DESC": "Génère une ou plusieurs valeurs aléatoires pondérées à partir d'une liste d'éléments.",
+ "DEFAULT_TITLE": "listepondérée",
+ "weightedListSettings": "Paramètres de la liste pondérée",
+ "atMost": "Au plus",
+ "exactly": "Exactement",
+ "numItemsLabel": "Nombre d'éléments",
+ "numItemsLabelDesc": "Ce paramètre contrôle le nombre d'éléments générés à partir de votre liste. Vous pouvez choisir un nombre exact d'éléments à générer, ou un nombre entre une plage de votre choix.",
+ "exactly1Item": "Exactement %1 élément de la liste",
+ "exactlyNItems": "Exactement %1 éléments de la liste",
+ "atMost1Item": "Au plus %1 élément de la liste",
+ "atMostNItems": "Au plus %1 éléments de la liste",
+ "customize": "Personnaliser",
+ "delimChars": "Caractère(s) délimiteur(s)",
+ "betweenNumItems": "Entre %1 et %2 éléments",
+ "atLeast1Item": "Au moins %1 élément de la liste",
+ "atLeastNItems": "Au moins %1 éléments de la liste",
+ "noRangeEntered": "Aucune plage saisie",
+ "between": "Entre",
+ "and": "et",
+ "items": "éléments",
+ "itemsTitle": "Articles",
+ "mostlyEvenNumbers": "Surtout des nombres pairs",
+ "professions": "Les professions",
+ "householdPets": "Animaux domestiques",
+ "listTooShort": "Assurez-vous d'avoir saisi suffisamment d'éléments dans votre liste, sinon elle ne générera pas le nombre d'éléments spécifié ici.",
+ "allowDuplicates": "Autoriser les doublons",
+ "value": "Évaluer",
+ "weight": "Lester",
+ "addBtnLabel": "Ajouter »",
+ "pleaseAddItems": "Veuillez ajouter quelques éléments.",
+ "helpIntro": "Le type de données Liste pondérée génère un ou plusieurs éléments choisis au hasard dans une liste personnalisée, en tenant compte du poids que vous souhaitez appliquer à chaque élément. Le champ de pondération vous permet de modifier la probabilité d'apparition de chaque élément afin que vous puissiez créer des données plus réalistes. Par exemple, imaginons que vous vouliez générer une liste de professions, mais que \"Chirurgien du cerveau\" et \"Astronaute\" n'apparaissent que très rarement car ils ne sont pas courants. Pour cela, vous pouvez simplement augmenter le poids des autres :",
+ "helpValueWeight": "(valeur, poids)",
+ "helpBrainSurgeon": "Chirurgien du cerveau, 1",
+ "helpAstronaut": "Astronaute, 1",
+ "helpBanker": "Banquier, 800",
+ "helpSoftwareDeveloper": "Développeur de logiciels, 1000",
+ "helpEtc": "Aide etc",
+ "helpOtherOptions": "Le type de données fournit également les options suivantes :",
+ "helpOption1": "Il vous permet de générer soit un nombre spécifique d'éléments de la liste, soit un sous-ensemble de taille aléatoire.",
+ "helpOption2": "Choisissez le ou les caractères de délimitation utilisés pour plusieurs éléments.",
+ "helpOption3": "Choisissez si vous souhaitez éliminer les doublons lorsqu'une seule ligne génère plusieurs éléments.",
+ "allowDuplicatesDesc": "Lorsque vous générez plusieurs éléments de votre liste, ce paramètre contrôle si les doublons sont autorisés.",
+ "delimCharsDesc": "Si vous générez plusieurs éléments, ce champ détermine le caractère à utiliser pour séparer les valeurs."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/hi.json b/packages/plugins/src/dataTypes/WeightedList/i18n/hi.json
new file mode 100644
index 000000000..9302d7fc5
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/hi.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "भारित सूची",
+ "DESC": "वस्तुओं की सूची से एक या अधिक भारित यादृच्छिक मान उत्पन्न करता है।",
+ "DEFAULT_TITLE": "भारितसूची",
+ "weightedListSettings": "भारित सूची सेटिंग्स",
+ "atMost": "अधिक से अधिक",
+ "exactly": "बिल्कुल",
+ "numItemsLabel": "आइटमों की संख्या",
+ "numItemsLabelDesc": "यह सेटिंग आपकी सूची से उत्पन्न वस्तुओं की संख्या को नियंत्रित करती है। आप उत्पन्न करने के लिए वस्तुओं की सटीक संख्या चुन सकते हैं, या अपनी पसंद की सीमा के बीच एक संख्या चुन सकते हैं।",
+ "exactly1Item": "सूची से ठीक %1 आइटम",
+ "exactlyNItems": "सूची से ठीक %1 आइटम",
+ "atMost1Item": "सूची से ज़्यादा से ज़्यादा %1 आइटम",
+ "atMostNItems": "सूची से अधिकतम %1 आइटम",
+ "customize": "अनुकूलित करें",
+ "delimChars": "सीमांकक वर्ण",
+ "betweenNumItems": "%1 और %2 आइटम के बीच",
+ "atLeast1Item": "सूची से कम से कम %1 आइटम",
+ "atLeastNItems": "सूची से कम से कम %1 आइटम",
+ "noRangeEntered": "कोई सीमा दर्ज नहीं की गई",
+ "between": "बीच में",
+ "and": "तथा",
+ "items": "सामान",
+ "itemsTitle": "सामान",
+ "mostlyEvenNumbers": "अधिकतर सम संख्याएँ",
+ "professions": "व्यवसायों",
+ "householdPets": "घरेलू पालतू जानवर",
+ "listTooShort": "सुनिश्चित करें कि आपने अपनी सूची में पर्याप्त आइटम दर्ज किए हैं, या यह यहां निर्दिष्ट आइटमों की संख्या उत्पन्न नहीं करेगा।",
+ "allowDuplicates": "डुप्लीकेट की अनुमति दें",
+ "value": "मूल्य",
+ "weight": "वज़न",
+ "addBtnLabel": "जोड़ें »",
+ "pleaseAddItems": "कृपया कुछ आइटम जोड़ें।",
+ "helpIntro": "भारित सूची डेटा प्रकार एक कस्टम-निर्धारित सूची से बेतरतीब ढंग से चुने गए आइटम या आइटम उत्पन्न करता है, प्रत्येक आइटम पर आप जो भी भार लागू करना चाहते हैं, उसमें फैक्टरिंग करते हैं। वज़न फ़ील्ड आपको प्रत्येक आइटम के प्रदर्शित होने की संभावना को बदलने देता है ताकि आप अधिक यथार्थवादी दिखने वाला डेटा बना सकें। उदाहरण के लिए, मान लें कि आप व्यवसायों की एक सूची तैयार करना चाहते हैं, लेकिन चाहते हैं कि 'ब्रेन सर्जन' और 'एस्ट्रोनॉट' बहुत कम दिखाई दें क्योंकि वे आम नहीं हैं। उसके लिए, आप बस दूसरों का वजन बढ़ा सकते हैं:",
+ "helpValueWeight": "(मूल्य, वजन)",
+ "helpBrainSurgeon": "ब्रेन सर्जन, 1",
+ "helpAstronaut": "अंतरिक्ष यात्री, 1",
+ "helpBanker": "बैंकर, 800",
+ "helpSoftwareDeveloper": "सॉफ्टवेयर डेवलपर, 1000",
+ "helpEtc": "मदद आदि।",
+ "helpOtherOptions": "डेटा प्रकार निम्नलिखित विकल्प भी प्रदान करता है:",
+ "helpOption1": "यह आपको सूची से या तो विशिष्ट संख्या में आइटम उत्पन्न करने देता है, या एक यादृच्छिक आकार का सबसेट।",
+ "helpOption2": "एक से अधिक मदों के लिए उपयोग किए जाने वाले परिसीमक वर्ण चुनें।",
+ "helpOption3": "चुनें कि क्या आप डुप्लिकेट को हटाना चाहते हैं जब एक पंक्ति एक से अधिक आइटम उत्पन्न करती है।",
+ "allowDuplicatesDesc": "जब आप अपनी सूची से एक से अधिक आइटम उत्पन्न कर रहे होते हैं, तो यह सेटिंग नियंत्रित करती है कि डुप्लिकेट की अनुमति है या नहीं।",
+ "delimCharsDesc": "यदि आप एकाधिक आइटम उत्पन्न कर रहे हैं, तो यह फ़ील्ड निर्धारित करती है कि मानों को अलग करने के लिए किस वर्ण का उपयोग किया जाना चाहिए।"
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/ja.json b/packages/plugins/src/dataTypes/WeightedList/i18n/ja.json
new file mode 100644
index 000000000..bf85c1b55
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/ja.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "加重リスト",
+ "DESC": "アイテムのリストから 1 つ以上の加重ランダム値を生成します。",
+ "DEFAULT_TITLE": "加重リスト",
+ "weightedListSettings": "加重リストの設定",
+ "atMost": "せいぜい",
+ "exactly": "丁度",
+ "numItemsLabel": "アイテム数",
+ "numItemsLabelDesc": "この設定は、リストから生成されるアイテムの数を制御します。 生成するアイテムの正確な数、または選択した範囲内の数を選択できます。",
+ "exactly1Item": "リストからちょうど %1 アイテム",
+ "exactlyNItems": "リストからちょうど %1 個のアイテム",
+ "atMost1Item": "リストから最大 %1 アイテム",
+ "atMostNItems": "リストから最大 %1 アイテム",
+ "customize": "カスタマイズ",
+ "delimChars": "区切り文字",
+ "betweenNumItems": "%1 と %2 の間のアイテム",
+ "atLeast1Item": "リストから少なくとも %1 アイテム",
+ "atLeastNItems": "リストから少なくとも %1 アイテム",
+ "noRangeEntered": "範囲が入力されていません",
+ "between": "間",
+ "and": "と",
+ "items": "アイテム",
+ "itemsTitle": "アイテム",
+ "mostlyEvenNumbers": "偶数がほとんど",
+ "professions": "職業",
+ "householdPets": "家庭用ペット",
+ "listTooShort": "リストに十分な項目を入力したことを確認してください。そうしないと、ここで指定した数の項目が生成されません。",
+ "allowDuplicates": "重複を許可",
+ "value": "価値",
+ "weight": "重さ",
+ "addBtnLabel": "追加",
+ "pleaseAddItems": "項目を追加してください。",
+ "helpIntro": "Weighted List Data Type は、カスタム定義リストからランダムに選択されたアイテムを生成し、各アイテムに適用するウェイトを考慮します。 重みフィールドを使用すると、各アイテムが表示される確率を変更できるため、より現実的なデータを作成できます。 たとえば、職業のリストを生成したいが、「脳外科医」と「宇宙飛行士」は一般的ではないため、ごくまれにしか表示されないようにしたいとします。 そのためには、他のウェイトを増やすだけです。",
+ "helpValueWeight": "(値、重量)",
+ "helpBrainSurgeon": "脳外科医 1",
+ "helpAstronaut": "宇宙飛行士 1",
+ "helpBanker": "バンカー、800",
+ "helpSoftwareDeveloper": "ソフトウェア開発者、1000",
+ "helpEtc": "ヘルプなど",
+ "helpOtherOptions": "データ型には、次のオプションもあります。",
+ "helpOption1": "リストから特定の数のアイテムを生成するか、ランダムなサイズのサブセットを生成できます。",
+ "helpOption2": "複数の項目に使用する区切り文字を選択します。",
+ "helpOption3": "1 つの行が複数の項目を生成する場合に、重複を排除するかどうかを選択します。",
+ "allowDuplicatesDesc": "リストから複数のアイテムを生成する場合、この設定は重複を許可するかどうかを制御します。",
+ "delimCharsDesc": "複数のアイテムを生成する場合、このフィールドは、値を区切るために使用する文字を決定します。"
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/nl.json b/packages/plugins/src/dataTypes/WeightedList/i18n/nl.json
new file mode 100644
index 000000000..cdd4e5246
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/nl.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "Gewogen lijst",
+ "DESC": "Genereert een of meer gewogen willekeurige waarden uit een lijst met items.",
+ "DEFAULT_TITLE": "gewogenlijst",
+ "weightedListSettings": "Gewogen lijst instellingen",
+ "atMost": "Bij de meeste",
+ "exactly": "Precies",
+ "numItemsLabel": "Aantal items",
+ "numItemsLabelDesc": "Deze instelling bepaalt het aantal items dat uit uw lijst wordt gegenereerd. U kunt een exact aantal items kiezen om te genereren, of een aantal uit een bereik naar keuze.",
+ "exactly1Item": "Exact %1 item uit lijst",
+ "exactlyNItems": "Exact %1 items uit lijst",
+ "atMost1Item": "Maximaal %1 item uit lijst",
+ "atMostNItems": "Maximaal %1 items uit de lijst",
+ "customize": "Aanpassen",
+ "delimChars": "Scheidingsteken(s)",
+ "betweenNumItems": "Tussen %1 en %2 items",
+ "atLeast1Item": "Minstens %1 item uit lijst",
+ "atLeastNItems": "Minstens %1 items uit de lijst",
+ "noRangeEntered": "Geen bereik ingevoerd",
+ "between": "Tussen",
+ "and": "en",
+ "items": "artikelen",
+ "itemsTitle": "Artikelen",
+ "mostlyEvenNumbers": "Meestal even getallen",
+ "professions": "Beroepen",
+ "householdPets": "Huisdieren",
+ "listTooShort": "Zorg ervoor dat u voldoende items in uw lijst heeft ingevoerd, anders wordt niet het aantal items gegenereerd dat hier is opgegeven.",
+ "allowDuplicates": "Duplicaten toestaan",
+ "value": "Waarde",
+ "weight": "Gewicht",
+ "addBtnLabel": "Toevoegen »",
+ "pleaseAddItems": "Voeg enkele items toe.",
+ "helpIntro": "Het gegevenstype Gewogen lijst genereert willekeurig gekozen item of items uit een zelf gedefinieerde lijst, rekening houdend met het gewicht dat u op elk item wilt toepassen. Met het gewichtsveld kunt u de kans wijzigen dat elk item wordt weergegeven, zodat u meer realistisch ogende gegevens kunt creëren. Stel dat u een lijst met beroepen wilt genereren, maar dat 'Hersenchirurg' en 'Astronaut' zeer zelden voorkomen omdat ze niet gebruikelijk zijn. Daarvoor zou je gewoon de gewichten van de anderen kunnen verhogen:",
+ "helpValueWeight": "(waarde, gewicht)",
+ "helpBrainSurgeon": "Hersenchirurg, 1",
+ "helpAstronaut": "Astronaut, 1",
+ "helpBanker": "Bankier, 800",
+ "helpSoftwareDeveloper": "Softwareontwikkelaar, 1000",
+ "helpEtc": "Hulp enz.",
+ "helpOtherOptions": "Het gegevenstype biedt ook de volgende opties:",
+ "helpOption1": "Hiermee kunt u een specifiek aantal items uit de lijst genereren, of een subset van een willekeurige grootte.",
+ "helpOption2": "Kies welk(e) scheidingsteken(s) dan ook worden gebruikt voor meerdere items.",
+ "helpOption3": "Kies of u duplicaten wilt verwijderen wanneer een enkele rij meer dan één item genereert.",
+ "allowDuplicatesDesc": "Wanneer u meer dan één item uit uw lijst genereert, bepaalt deze instelling of duplicaten zijn toegestaan.",
+ "delimCharsDesc": "Als u meerdere items genereert, bepaalt dit veld welk teken moet worden gebruikt om de waarden te scheiden."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/pt.json b/packages/plugins/src/dataTypes/WeightedList/i18n/pt.json
new file mode 100644
index 000000000..6a5aad32e
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/pt.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "Lista Ponderada",
+ "DESC": "Gera um ou mais valores aleatórios ponderados de uma lista de itens.",
+ "DEFAULT_TITLE": "listaponderada",
+ "weightedListSettings": "Configurações da lista ponderada",
+ "atMost": "No máximo",
+ "exactly": "Exatamente",
+ "numItemsLabel": "Num itens",
+ "numItemsLabelDesc": "Esta configuração controla o número de itens gerados de sua lista. Você pode escolher um número exato de itens para gerar ou um número entre um intervalo de sua escolha.",
+ "exactly1Item": "Exatamente %1 item da lista",
+ "exactlyNItems": "Exatamente %1 itens da lista",
+ "atMost1Item": "No máximo %1 item da lista",
+ "atMostNItems": "No máximo %1 itens da lista",
+ "customize": "Customizar",
+ "delimChars": "Caracteres delimitadores",
+ "betweenNumItems": "Entre %1 e %2 itens",
+ "atLeast1Item": "Pelo menos %1 item da lista",
+ "atLeastNItems": "Pelo menos %1 itens da lista",
+ "noRangeEntered": "Nenhum intervalo inserido",
+ "between": "Entre",
+ "and": "e",
+ "items": "Itens",
+ "itemsTitle": "Itens",
+ "mostlyEvenNumbers": "Principalmente números pares",
+ "professions": "Profissões",
+ "householdPets": "Animais domésticos",
+ "listTooShort": "Certifique-se de que inseriu itens suficientes em sua lista ou ela não gerará o número de itens especificados aqui.",
+ "allowDuplicates": "Permitir duplicatas",
+ "value": "Valor",
+ "weight": "Peso",
+ "addBtnLabel": "Adicionar »",
+ "pleaseAddItems": "Por favor, adicione alguns itens.",
+ "helpIntro": "O tipo de dados Lista ponderada gera itens escolhidos aleatoriamente de uma lista definida de forma personalizada, considerando o peso que você deseja aplicar a cada item. O campo de peso permite alterar a probabilidade de cada item aparecer para que você possa criar dados com aparência mais realista. Por exemplo, digamos que você queira gerar uma lista de profissões, mas deseja que 'Cirurgião cerebral' e 'Astronauta' apareçam com pouca frequência porque não são comuns. Para isso, você poderia apenas aumentar os pesos dos outros:",
+ "helpValueWeight": "(valor, peso)",
+ "helpBrainSurgeon": "Cirurgião cerebral, 1",
+ "helpAstronaut": "Astronauta, 1",
+ "helpBanker": "Banqueiro, 800",
+ "helpSoftwareDeveloper": "Desenvolvedor de software, 1000",
+ "helpEtc": "Ajuda etc",
+ "helpOtherOptions": "O tipo de dados também fornece as seguintes opções:",
+ "helpOption1": "Ele permite gerar um número específico de itens da lista ou um subconjunto de tamanho aleatório.",
+ "helpOption2": "Escolha quaisquer caracteres delimitadores usados para vários itens.",
+ "helpOption3": "Escolha se deseja eliminar duplicatas quando uma única linha gera mais de um item.",
+ "allowDuplicatesDesc": "Quando você está gerando mais de um item de sua lista, esta configuração controla se as duplicatas são permitidas.",
+ "delimCharsDesc": "Se você estiver gerando vários itens, esse campo determinará qual caractere deve ser usado para separar os valores."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/ru.json b/packages/plugins/src/dataTypes/WeightedList/i18n/ru.json
new file mode 100644
index 000000000..8aa7c6bf1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/ru.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "Взвешенный список",
+ "DESC": "Генерирует одно или несколько взвешенных случайных значений из списка элементов.",
+ "DEFAULT_TITLE": "weightedlist",
+ "weightedListSettings": "Настройки взвешенного списка",
+ "atMost": "В большинстве",
+ "exactly": "В яблочко",
+ "numItemsLabel": "Количество предметов",
+ "numItemsLabelDesc": "Этот параметр управляет количеством элементов, созданных из вашего списка. Вы можете выбрать точное количество элементов для генерации или число из диапазона по вашему выбору.",
+ "exactly1Item": "Ровно %1 элемент из списка",
+ "exactlyNItems": "Ровно %1 элементов из списка",
+ "atMost1Item": "Не более %1 элемента из списка",
+ "atMostNItems": "Не более %1 элементов из списка",
+ "customize": "Настроить",
+ "delimChars": "Символ(ы) разделителя",
+ "betweenNumItems": "Между %1 и %2 элементами",
+ "atLeast1Item": "По крайней мере %1 элемент из списка",
+ "atLeastNItems": "Не менее %1 элементов из списка",
+ "noRangeEntered": "Диапазон не указан",
+ "between": "Между",
+ "and": "и",
+ "items": "Предметы",
+ "itemsTitle": "Предметы",
+ "mostlyEvenNumbers": "В основном четные числа",
+ "professions": "Профессии",
+ "householdPets": "Домашние животные",
+ "listTooShort": "Убедитесь, что вы ввели достаточное количество элементов в свой список, иначе он не будет генерировать указанное здесь количество элементов.",
+ "allowDuplicates": "Разрешить дубликаты",
+ "value": "Ценность",
+ "weight": "Масса",
+ "addBtnLabel": "Добавлять »",
+ "pleaseAddItems": "Пожалуйста, добавьте некоторые элементы.",
+ "helpIntro": "Тип данных Взвешенный список генерирует случайно выбранный элемент или элементы из пользовательского списка с учетом любого веса, который вы хотите применить к каждому элементу. Поле веса позволяет изменить вероятность появления каждого элемента, чтобы вы могли создавать более реалистичные данные. Например, предположим, что вы хотели создать список профессий, но хотели, чтобы «нейрохирург» и «астронавт» появлялись очень редко, потому что они не распространены. Для этого вы можете просто увеличить веса других:",
+ "helpValueWeight": "(стоимость, вес)",
+ "helpBrainSurgeon": "Нейрохирург, 1",
+ "helpAstronaut": "Космонавта, 1",
+ "helpBanker": "Банкир, 800",
+ "helpSoftwareDeveloper": "Программист, 1000",
+ "helpEtc": "Помощь и т. д.",
+ "helpOtherOptions": "Тип данных также предоставляет следующие параметры:",
+ "helpOption1": "Он позволяет генерировать либо определенное количество элементов из списка, либо подмножество случайного размера.",
+ "helpOption2": "Выберите любые символы-разделители, используемые для нескольких элементов.",
+ "helpOption3": "Выберите, хотите ли вы устранить дубликаты, когда одна строка создает более одного элемента.",
+ "allowDuplicatesDesc": "Когда вы создаете более одного элемента из своего списка, этот параметр определяет, разрешены ли дубликаты.",
+ "delimCharsDesc": "Если вы создаете несколько элементов, это поле определяет, какой символ следует использовать для разделения значений."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/ta.json b/packages/plugins/src/dataTypes/WeightedList/i18n/ta.json
new file mode 100644
index 000000000..8a1b538b1
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/ta.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "எடையுள்ள பட்டியல்",
+ "DESC": "உருப்படிகளின் பட்டியலிலிருந்து ஒன்று அல்லது அதற்கு மேற்பட்ட எடையுள்ள சீரற்ற மதிப்புகளை உருவாக்குகிறது.",
+ "DEFAULT_TITLE": "எடையுள்ளபட்டியல்",
+ "weightedListSettings": "எடையுள்ள பட்டியல் அமைப்புகள்",
+ "atMost": "அதிக பட்சம்",
+ "exactly": "சரியாக",
+ "numItemsLabel": "பொருட்களின் எண்ணிக்கை",
+ "numItemsLabelDesc": "இந்த அமைப்பு உங்கள் பட்டியலில் இருந்து உருவாக்கப்பட்ட உருப்படிகளின் எண்ணிக்கையைக் கட்டுப்படுத்துகிறது. உருவாக்க வேண்டிய உருப்படிகளின் சரியான எண்ணிக்கையையோ அல்லது நீங்கள் தேர்ந்தெடுக்கும் வரம்பிற்கு இடையே உள்ள எண்ணையோ தேர்வு செய்யலாம்.",
+ "exactly1Item": "பட்டியலிலிருந்து சரியாக %1 உருப்படி",
+ "exactlyNItems": "பட்டியலிலிருந்து சரியாக %1 உருப்படிகள்",
+ "atMost1Item": "பட்டியலில் இருந்து அதிகபட்சம் %1 உருப்படி",
+ "atMostNItems": "பட்டியலில் இருந்து அதிகபட்சம் %1 உருப்படிகள்",
+ "customize": "தனிப்பயனாக்கலாம்",
+ "delimChars": "டிலிமிட்டர் எழுத்து(கள்)",
+ "betweenNumItems": "%1 மற்றும் %2 உருப்படிகளுக்கு இடையில்",
+ "atLeast1Item": "பட்டியலிலிருந்து குறைந்தது %1 உருப்படி",
+ "atLeastNItems": "பட்டியலில் இருந்து குறைந்தது %1 உருப்படிகள்",
+ "noRangeEntered": "வரம்பு இல்லை",
+ "between": "இடையில்",
+ "and": "மற்றும்",
+ "items": "பொருட்களை",
+ "itemsTitle": "பொருட்களை",
+ "mostlyEvenNumbers": "பெரும்பாலும் இரட்டை எண்கள்",
+ "professions": "தொழில்கள்",
+ "householdPets": "வீட்டு செல்லப்பிராணிகள்",
+ "listTooShort": "உங்கள் பட்டியலில் போதுமான உருப்படிகளை உள்ளிட்டுள்ளீர்கள் என்பதை உறுதிப்படுத்திக்கொள்ளவும் அல்லது இங்கு குறிப்பிடப்பட்டுள்ள உருப்படிகளின் எண்ணிக்கையை அது உருவாக்காது.",
+ "allowDuplicates": "நகல்களை அனுமதிக்கவும்",
+ "value": "மதிப்பு",
+ "weight": "எடை",
+ "addBtnLabel": "சேர் »",
+ "pleaseAddItems": "தயவுசெய்து சில பொருட்களைச் சேர்க்கவும்.",
+ "helpIntro": "எடையிடப்பட்ட பட்டியல் தரவு வகையானது, தனிப்பயன் வரையறுக்கப்பட்ட பட்டியலிலிருந்து தோராயமாக தேர்ந்தெடுக்கப்பட்ட உருப்படி அல்லது உருப்படிகளை உருவாக்குகிறது, ஒவ்வொரு பொருளுக்கும் நீங்கள் பயன்படுத்த விரும்பும் எடையை காரணியாக்குகிறது. எடைப் புலமானது ஒவ்வொரு பொருளின் தோற்றத்தின் நிகழ்தகவை மாற்ற உதவுகிறது, எனவே நீங்கள் மிகவும் யதார்த்தமான தரவை உருவாக்கலாம். எடுத்துக்காட்டாக, நீங்கள் தொழில்களின் பட்டியலை உருவாக்க விரும்புகிறீர்கள், ஆனால் 'மூளை அறுவை சிகிச்சை நிபுணர்' மற்றும் 'விண்வெளி வீரர்' மிகவும் அரிதாகவே தோன்ற வேண்டும், ஏனெனில் அவை பொதுவானவை அல்ல. அதற்காக, நீங்கள் மற்றவர்களின் எடையை அதிகரிக்கலாம்:",
+ "helpValueWeight": "(மதிப்பு, எடை)",
+ "helpBrainSurgeon": "மூளை அறுவை சிகிச்சை நிபுணர், 1",
+ "helpAstronaut": "விண்வெளி வீரர், 1",
+ "helpBanker": "வங்கியாளர், 800",
+ "helpSoftwareDeveloper": "மென்பொருள் உருவாக்குநர், 1000",
+ "helpEtc": "உதவி போன்றவை.",
+ "helpOtherOptions": "தரவு வகை பின்வரும் விருப்பங்களையும் வழங்குகிறது:",
+ "helpOption1": "பட்டியலிலிருந்து குறிப்பிட்ட எண்ணிக்கையிலான உருப்படிகள் அல்லது சீரற்ற அளவின் துணைக்குழுவை உருவாக்க இது உங்களை அனுமதிக்கிறது.",
+ "helpOption2": "பல உருப்படிகளுக்கு எந்த டிலிமிட்டர் எழுத்து(கள்) பயன்படுத்தப்படுகிறதோ அதைத் தேர்ந்தெடுக்கவும்.",
+ "helpOption3": "ஒரு வரிசை ஒன்றுக்கு மேற்பட்ட உருப்படிகளை உருவாக்கும் போது நகல்களை அகற்ற வேண்டுமா என்பதைத் தேர்வுசெய்யவும்.",
+ "allowDuplicatesDesc": "உங்கள் பட்டியலிலிருந்து ஒன்றுக்கு மேற்பட்ட உருப்படிகளை உருவாக்கும்போது, இந்த அமைப்பு நகல் அனுமதிக்கப்படுகிறதா என்பதைக் கட்டுப்படுத்தும்.",
+ "delimCharsDesc": "நீங்கள் பல உருப்படிகளை உருவாக்கினால், மதிப்புகளைப் பிரிக்க எந்த எழுத்துக்குறி பயன்படுத்தப்பட வேண்டும் என்பதை இந்தப் புலம் தீர்மானிக்கிறது."
+}
diff --git a/packages/plugins/src/dataTypes/WeightedList/i18n/zh.json b/packages/plugins/src/dataTypes/WeightedList/i18n/zh.json
new file mode 100644
index 000000000..9dc8582b3
--- /dev/null
+++ b/packages/plugins/src/dataTypes/WeightedList/i18n/zh.json
@@ -0,0 +1,46 @@
+{
+ "NAME": "加权列表",
+ "DESC": "从项目列表中生成一个或多个加权随机值。",
+ "DEFAULT_TITLE": "加权列表",
+ "weightedListSettings": "加权列表设置",
+ "atMost": "最多",
+ "exactly": "确切地",
+ "numItemsLabel": "项目数",
+ "numItemsLabelDesc": "此设置控制从列表中生成的项目数。 您可以选择要生成的项目的确切数量,或您选择的范围之间的数字。",
+ "exactly1Item": "恰好是列表中的 %1 项",
+ "exactlyNItems": "正好是列表中的 %1 个项目",
+ "atMost1Item": "列表中最多 %1 项",
+ "atMostNItems": "列表中最多 %1 个项目",
+ "customize": "定制",
+ "delimChars": "分隔符字符",
+ "betweenNumItems": "在 %1 到 %2 个项目之间",
+ "atLeast1Item": "列表中至少有 %1 项",
+ "atLeastNItems": "列表中至少有 %1 项",
+ "noRangeEntered": "未输入范围",
+ "between": "之间",
+ "and": "和",
+ "items": "项目",
+ "itemsTitle": "项目",
+ "mostlyEvenNumbers": "偶数居多",
+ "professions": "职业",
+ "householdPets": "家庭宠物",
+ "listTooShort": "确保您在列表中输入了足够的项目,否则它不会生成此处指定的项目数。",
+ "allowDuplicates": "允许重复",
+ "value": "价值",
+ "weight": "重量",
+ "addBtnLabel": "添加 »",
+ "pleaseAddItems": "请添加一些项目。",
+ "helpIntro": "加权列表 数据类型从自定义列表中生成随机选择的一个或多个项目,将您想要应用到每个项目的任何权重都考虑在内。 权重字段允许您更改每个项目出现的概率,以便您可以创建更逼真的数据。 例如,假设您想生成一份职业列表,但希望“脑外科医生”和“宇航员”很少出现,因为它们不常见。 为此,您可以增加其他人的权重:",
+ "helpValueWeight": "(价值,重量)",
+ "helpBrainSurgeon": "脑外科医生,1",
+ "helpAstronaut": "宇航员,1",
+ "helpBanker": "银行家,800",
+ "helpSoftwareDeveloper": "软件开发人员,1000",
+ "helpEtc": "帮助等",
+ "helpOtherOptions": "数据类型还提供以下选项:",
+ "helpOption1": "它允许您从列表中生成特定数量的项目,或随机大小的子集。",
+ "helpOption2": "选择用于多个项目的任何定界符。",
+ "helpOption3": "选择当单行生成多个项目时是否要消除重复项。",
+ "allowDuplicatesDesc": "当您从列表中生成多个项目时,此设置控制是否允许重复项。",
+ "delimCharsDesc": "如果您要生成多个项目,则此字段确定应该使用什么字符来分隔这些值。"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/CSV.generate.ts b/packages/plugins/src/exportTypes/CSV/CSV.generate.ts
new file mode 100644
index 000000000..a69003056
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/CSV.generate.ts
@@ -0,0 +1,35 @@
+import { ETMessageData } from '../../';
+
+export const generate = (data: ETMessageData): string => {
+ const { columns, rows, isFirstBatch, settings } = data;
+ const { delimiter, lineEndings } = settings;
+
+ const map: any = {
+ Windows: '\r\n',
+ Unix: '\n',
+ Mac: '\r'
+ };
+ const eol = map[lineEndings];
+
+ let content = '';
+ if (isFirstBatch) {
+ const titleRow = columns.map(({ title }) => title).join(delimiter);
+ content += `${titleRow}${eol}`;
+ }
+
+ const numCols = columns.length;
+ rows.forEach((row) => {
+ const cleanRow = [];
+ for (let i = 0; i < numCols; i++) {
+ // see if any of the cells contains the delimiter. If it does, wrap the cell in double quotes
+ if (row[i] && row[i].toString().indexOf(delimiter) !== -1) {
+ cleanRow.push(`"${row[i]}"`);
+ } else {
+ cleanRow.push(row[i]);
+ }
+ }
+ content += cleanRow.join(delimiter) + eol;
+ });
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/CSV/CSV.scss b/packages/plugins/src/exportTypes/CSV/CSV.scss
new file mode 100644
index 000000000..065084a23
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/CSV.scss
@@ -0,0 +1,11 @@
+.settings {
+ display: flex;
+
+ &>div {
+ flex: 1;
+ }
+
+ label {
+ display: block;
+ }
+}
diff --git a/packages/plugins/src/exportTypes/CSV/CSV.scss.d.ts b/packages/plugins/src/exportTypes/CSV/CSV.scss.d.ts
new file mode 100644
index 000000000..d06a46500
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/CSV.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace CsvScssNamespace {
+ export interface ICsvScss {
+ settings: string;
+ }
+}
+
+declare const CsvScssModule: CsvScssNamespace.ICsvScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: CsvScssNamespace.ICsvScss;
+};
+
+export = CsvScssModule;
diff --git a/packages/plugins/src/exportTypes/CSV/CSV.state.tsx b/packages/plugins/src/exportTypes/CSV/CSV.state.tsx
new file mode 100644
index 000000000..53bee9e97
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/CSV.state.tsx
@@ -0,0 +1,13 @@
+export type CSVSettings = {
+ delimiter: string;
+ lineEndings: 'Windows' | 'Unix' | 'Mac';
+};
+
+export const initialState: CSVSettings = {
+ delimiter: ',',
+ lineEndings: 'Unix'
+};
+
+export const defaultGenerationOptions = initialState;
+
+export type GenerationOptionsType = CSVSettings;
diff --git a/packages/plugins/src/exportTypes/CSV/CSV.tsx b/packages/plugins/src/exportTypes/CSV/CSV.tsx
new file mode 100644
index 000000000..a7877c9a3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/CSV.tsx
@@ -0,0 +1,64 @@
+import * as React from 'react';
+import TextField from '~components/TextField';
+import Dropdown, { DropdownOption } from '~components/dropdown/Dropdown';
+import { ETDownloadPacket, ETDownloadPacketResponse, ETSettings } from '@generatedata/types';
+import { CSVSettings } from './CSV.state';
+import etShared from '../../../styles/etShared.scss';
+import styles from './CSV.scss';
+
+const options = [
+ { value: 'Windows', label: 'Windows' },
+ { value: 'Unix', label: 'Unix' },
+ { value: 'Mac', label: 'Mac' }
+];
+
+export const Settings = ({ i18n, id, data, onUpdate }: ETSettings) => {
+ const onChange = (prop: string, value: string): void => {
+ onUpdate({
+ ...data,
+ [prop]: value
+ });
+ };
+
+ return (
+
+
+ {i18n.delimiterChars}
+ onChange('delimiter', e.target.value)}
+ />
+
+
+ {i18n.eolChar}
+ onChange('lineEndings', value)}
+ />
+
+
+ );
+};
+
+// CSV supplies their own custom Preview component, since showing raw CSV code via CodeMirror isn't much of a preview.
+// Instead, this simply renders the preview data as a table - closer to what they're actually going to end up with
+// TODO
+// export const Preview = () => {
+//
+// };
+
+export const getCodeMirrorMode = (): string => 'application/csv';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.csv`,
+ fileType: 'application/csv'
+});
+
+export const isValid = (settings: CSVSettings): boolean => {
+ return settings.delimiter !== ''; // technically spaces are valid, I suppose
+};
diff --git a/packages/plugins/src/exportTypes/CSV/CSV.worker.ts b/packages/plugins/src/exportTypes/CSV/CSV.worker.ts
new file mode 100644
index 000000000..8008b3865
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/CSV.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './CSV.generate';
+
+const context: Worker = self as any;
+
+export const onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/CSV/README.md b/packages/plugins/src/exportTypes/CSV/README.md
new file mode 100644
index 000000000..6f65e83b0
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/README.md
@@ -0,0 +1,49 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » CSV
+
+The CSV Export Type generates the random data in CSV format. It provides a couple of simple options:
+
+- `delimiter`: the character used to delimit columns
+- `eol`: the end of line character to use. For this setting, pass a string corresponding to the eol you want to
+use: `Windows`, `Unix` or `Mac`. These correspond to `\r\n`, `\n` and `\r` respectively.
+
+
+### Example API Usage
+
+Try POSTing the following JSON content to the following URL:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AutoIncrement",
+ "title": "Row",
+ "settings": {
+ "incrementStart": 1,
+ "incrementValue": 1
+ }
+ },
+ {
+ "type": "Names",
+ "title": "name",
+ "settings": {
+ "placeholder": "Name Initial. Surname"
+ }
+ }
+ ],
+ "export": {
+ "type": "CSV",
+ "settings": {
+ "delimiter": "|",
+ "eol": "Unix"
+ }
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/CSV/__tests__/CSV.test.tsx b/packages/plugins/src/exportTypes/CSV/__tests__/CSV.test.tsx
new file mode 100644
index 000000000..c39574898
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/__tests__/CSV.test.tsx
@@ -0,0 +1,31 @@
+import React from 'react';
+import sinon from 'sinon';
+import { render } from '@testing-library/react';
+import { Settings } from '../CSV';
+import { initialState } from '../CSV.state';
+import { defaultETSettings } from '../../../../../tests/testHelpers';
+import * as langUtils from '~utils/langUtils';
+
+const i18n = require('../i18n/en.json');
+
+describe('Settings', () => {
+ it('renders', () => {
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+
+ sinon.stub(langUtils, 'getStrings').returns({
+ core: i18n,
+ dataTypes: {}
+ });
+
+ const { container } = render(
+
+ );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/exportTypes/CSV/bundle.ts b/packages/plugins/src/exportTypes/CSV/bundle.ts
new file mode 100644
index 000000000..c94592130
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/bundle.ts
@@ -0,0 +1,13 @@
+import { ETBundle } from '@generatedata/types';
+import { Settings, getCodeMirrorMode, getDownloadFileInfo, isValid } from './CSV';
+import { initialState } from './CSV.state';
+
+const bundle: ETBundle = {
+ Settings,
+ initialState,
+ getCodeMirrorMode,
+ getDownloadFileInfo,
+ isValid
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/CSV/config.ts b/packages/plugins/src/exportTypes/CSV/config.ts
new file mode 100644
index 000000000..2126ebd7b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'core',
+ codeMirrorModes: []
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/ar.json b/packages/plugins/src/exportTypes/CSV/i18n/ar.json
new file mode 100644
index 000000000..3c50d2243
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/ar.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "حرف (أحرف) محدد",
+ "validationNoDelimiter": "الرجاء إدخال حرف محدد لنوع تصدير CSV.",
+ "eolChar": "نهاية حرف السطر"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/de.json b/packages/plugins/src/exportTypes/CSV/i18n/de.json
new file mode 100644
index 000000000..0456f8231
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/de.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "Delimiter char (s)",
+ "eolChar": "Zeilenendezeichen",
+ "validationNoDelimiter": "Bitte geben Sie ein Trennzeichen für die CSV-Export-Typ."
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/en.json b/packages/plugins/src/exportTypes/CSV/i18n/en.json
new file mode 100644
index 000000000..b76cfdad2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/en.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "Delimiter char(s)",
+ "validationNoDelimiter": "Please enter a delimiter character for the CSV export type.",
+ "eolChar": "End of line character"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/es.json b/packages/plugins/src/exportTypes/CSV/i18n/es.json
new file mode 100644
index 000000000..1f54bba86
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/es.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "Carácter(es) delimitador(es)",
+ "validationNoDelimiter": "Por favor, introduce un carácter delimitador para el tipo de exportación CSV.",
+ "eolChar": "Carácter de fin de línea"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/fr.json b/packages/plugins/src/exportTypes/CSV/i18n/fr.json
new file mode 100644
index 000000000..edbe9a7dd
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/fr.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "Caractère de séparation",
+ "validationNoDelimiter": "Entrez un caractère de séparation pour le type d'exportation CSV.",
+ "eolChar": "Caractère de fin de ligne"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/hi.json b/packages/plugins/src/exportTypes/CSV/i18n/hi.json
new file mode 100644
index 000000000..06e4522a3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/hi.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "सीमांकक चार(रों)",
+ "validationNoDelimiter": "कृपया CSV निर्यात प्रकार के लिए एक सीमांकक वर्ण दर्ज करें।",
+ "eolChar": "पंक्ति वर्ण का अंत"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/ja.json b/packages/plugins/src/exportTypes/CSV/i18n/ja.json
new file mode 100644
index 000000000..41112f752
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/ja.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "区切り文字",
+ "validationNoDelimiter": "CSVエクスポートタイプの区切り文字を入力してください。",
+ "eolChar": "行末文字"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/nl.json b/packages/plugins/src/exportTypes/CSV/i18n/nl.json
new file mode 100644
index 000000000..daac925a0
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/nl.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "Scheidingsteken",
+ "eolChar": "Einde van de lijn karakter",
+ "validationNoDelimiter": "Geef een scheidingsteken voor het CSV export type."
+}
+
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/pt.json b/packages/plugins/src/exportTypes/CSV/i18n/pt.json
new file mode 100644
index 000000000..27c3dba49
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/pt.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "Delimitador de caracteres",
+ "validationNoDelimiter": "Insira um caractere delimitador para o tipo de exportação CSV.",
+ "eolChar": "Caractere de fim de linha"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/ru.json b/packages/plugins/src/exportTypes/CSV/i18n/ru.json
new file mode 100644
index 000000000..cd5f3de20
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/ru.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "Символ(ы) разделителя",
+ "validationNoDelimiter": "Введите символ-разделитель для типа экспорта CSV.",
+ "eolChar": "Символ конца строки"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/ta.json b/packages/plugins/src/exportTypes/CSV/i18n/ta.json
new file mode 100644
index 000000000..451ebf3f4
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/ta.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "டிலிமிட்டர் கரி (கள்)",
+ "validationNoDelimiter": "CSV ஏற்றுமதி வகைக்கு ஒரு டிலிமிட்டர் எழுத்தை உள்ளிடவும்.",
+ "eolChar": "வரி எழுத்தின் முடிவு"
+}
diff --git a/packages/plugins/src/exportTypes/CSV/i18n/zh.json b/packages/plugins/src/exportTypes/CSV/i18n/zh.json
new file mode 100644
index 000000000..8b80fd2e9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSV/i18n/zh.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "CSV",
+ "delimiterChars": "行尾字符",
+ "validationNoDelimiter": "请输入CSV导出类型的分隔符。",
+ "eolChar": "行尾字符"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/CSharp.generate.ts b/packages/plugins/src/exportTypes/CSharp/CSharp.generate.ts
new file mode 100644
index 000000000..164cdfc53
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/CSharp.generate.ts
@@ -0,0 +1,47 @@
+import { ETMessageData } from '../../';
+import { WorkerUtils } from '../../workerUtils';
+
+export const generate = (data: ETMessageData, utils: WorkerUtils): string => {
+ const { stripWhitespace } = data;
+
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (data.isFirstBatch) {
+ content += `var data = new [] {${newline}`;
+ }
+
+ data.rows.forEach((row: any, rowIndex: number) => {
+ content += `${tab}new {${newline}${tab}${tab}`;
+ const pairs: string[] = [];
+
+ data.columns.forEach(({ title, metadata }, colIndex) => {
+ let value = row[colIndex];
+
+ // if a DT has explicitly said it's a number, use a number
+ if (
+ metadata &&
+ metadata.general &&
+ metadata.general.dataType &&
+ metadata.general.dataType === 'number' &&
+ utils.numberUtils.isNumeric(value)
+ ) {
+ // do nothin'!
+ } else {
+ value = `"${value}"`;
+ }
+
+ pairs.push(`${title} = ${value}`);
+ });
+ content += pairs.join(`,${newline}${tab}${tab}`);
+
+ if (data.isLastBatch && rowIndex === data.rows.length - 1) {
+ content += `${newline}${tab}}${newline}};`;
+ } else {
+ content += `${newline}${tab}},${newline}`;
+ }
+ });
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/CSharp/CSharp.state.tsx b/packages/plugins/src/exportTypes/CSharp/CSharp.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/CSharp.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/exportTypes/CSharp/CSharp.tsx b/packages/plugins/src/exportTypes/CSharp/CSharp.tsx
new file mode 100644
index 000000000..f5ffd70e6
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/CSharp.tsx
@@ -0,0 +1,8 @@
+import { ETDownloadPacket, ETDownloadPacketResponse } from '@generatedata/types';
+
+export const getCodeMirrorMode = (): string => 'text/x-csharp';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.cs`,
+ fileType: ''
+});
diff --git a/packages/plugins/src/exportTypes/CSharp/CSharp.worker.ts b/packages/plugins/src/exportTypes/CSharp/CSharp.worker.ts
new file mode 100644
index 000000000..7bf675bfc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/CSharp.worker.ts
@@ -0,0 +1,15 @@
+import utils from '../../../utils';
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './CSharp.generate';
+
+let workerUtilsLoaded = false;
+const context: Worker = self as any;
+
+export const onmessage = (e: ETOnMessage) => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+
+ context.postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/exportTypes/CSharp/README.md b/packages/plugins/src/exportTypes/CSharp/README.md
new file mode 100644
index 000000000..1c1cae6c0
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/README.md
@@ -0,0 +1 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » CSharp
diff --git a/packages/plugins/src/exportTypes/CSharp/bundle.ts b/packages/plugins/src/exportTypes/CSharp/bundle.ts
new file mode 100644
index 000000000..31f4f793a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/bundle.ts
@@ -0,0 +1,9 @@
+import { ETBundle } from '@generatedata/types';
+import { getCodeMirrorMode, getDownloadFileInfo } from './CSharp';
+
+const bundle: ETBundle = {
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/CSharp/config.ts b/packages/plugins/src/exportTypes/CSharp/config.ts
new file mode 100644
index 000000000..4470accff
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'programmingLanguage',
+ codeMirrorModes: ['javascript/javascript', 'clike/clike']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/ar.json b/packages/plugins/src/exportTypes/CSharp/i18n/ar.json
new file mode 100644
index 000000000..d80c652b9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "اسم الخاصية"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/de.json b/packages/plugins/src/exportTypes/CSharp/i18n/de.json
new file mode 100644
index 000000000..9186585ab
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "Name des Anwesens"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/en.json b/packages/plugins/src/exportTypes/CSharp/i18n/en.json
new file mode 100644
index 000000000..c425224f1
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "Property Name"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/es.json b/packages/plugins/src/exportTypes/CSharp/i18n/es.json
new file mode 100644
index 000000000..5e3682862
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "Nombre de la propiedad"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/fr.json b/packages/plugins/src/exportTypes/CSharp/i18n/fr.json
new file mode 100644
index 000000000..ffb8406cd
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "Nom de la propriété"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/hi.json b/packages/plugins/src/exportTypes/CSharp/i18n/hi.json
new file mode 100644
index 000000000..7fd652a42
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "संपत्ति का नाम"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/ja.json b/packages/plugins/src/exportTypes/CSharp/i18n/ja.json
new file mode 100644
index 000000000..69da77795
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "プロパティ名"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/nl.json b/packages/plugins/src/exportTypes/CSharp/i18n/nl.json
new file mode 100644
index 000000000..c7e243a76
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "Eigendomsnaam"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/pt.json b/packages/plugins/src/exportTypes/CSharp/i18n/pt.json
new file mode 100644
index 000000000..9a51b8c29
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "Nome da propriedade"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/ru.json b/packages/plugins/src/exportTypes/CSharp/i18n/ru.json
new file mode 100644
index 000000000..c4045b4c1
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "Имя свойства"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/ta.json b/packages/plugins/src/exportTypes/CSharp/i18n/ta.json
new file mode 100644
index 000000000..db956716c
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "சொத்தின் பெயர்"
+}
diff --git a/packages/plugins/src/exportTypes/CSharp/i18n/zh.json b/packages/plugins/src/exportTypes/CSharp/i18n/zh.json
new file mode 100644
index 000000000..8df0e6d78
--- /dev/null
+++ b/packages/plugins/src/exportTypes/CSharp/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "C#",
+ "COL_TITLE": "物业名称"
+}
diff --git a/packages/plugins/src/exportTypes/HTML/HTML.generate.ts b/packages/plugins/src/exportTypes/HTML/HTML.generate.ts
new file mode 100644
index 000000000..e7c6c0aab
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/HTML.generate.ts
@@ -0,0 +1,87 @@
+import { ETMessageData } from '../../';
+
+export const generate = (data: any): string => {
+ const { exportFormat } = data.settings;
+
+ let content = '';
+ if (exportFormat === 'table') {
+ content = generateTableFormat(data);
+ } else if (exportFormat === 'ul') {
+ content = generateUlFormat(data);
+ } else if (exportFormat === 'dl') {
+ content = generateDlFormat(data);
+ }
+
+ return content;
+};
+
+export const generateTableFormat = (data: ETMessageData): string => {
+ const stripWhitespace = data.stripWhitespace;
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (data.isFirstBatch) {
+ content += `${newline}${tab}${newline}`;
+ data.columns.forEach(({ title }) => {
+ content += `${tab}${tab}${title} ${newline}`;
+ });
+ content += `${tab} ${newline}`;
+ }
+
+ data.rows.forEach((row) => {
+ content += `${tab}${newline}`;
+ data.columns.forEach((col, colIndex) => {
+ content += `${tab}${tab}${row[colIndex]} ${newline}`;
+ });
+ content += `${tab} ${newline}`;
+ });
+
+ if (data.isLastBatch) {
+ content += '
';
+ }
+
+ return content;
+};
+
+export const generateUlFormat = (data: ETMessageData): string => {
+ const stripWhitespace = data.stripWhitespace;
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (data.isFirstBatch) {
+ content += `${newline}`;
+ data.columns.forEach(({ title }) => {
+ content += `${tab}${title} ${newline}`;
+ });
+ content += ` ${newline}`;
+ }
+
+ data.rows.forEach((row) => {
+ content += `${newline}`;
+ data.columns.forEach((col, colIndex) => {
+ content += `${tab}${row[colIndex]} ${newline}`;
+ });
+ content += ` ${newline}`;
+ });
+
+ return content;
+};
+
+export const generateDlFormat = (data: ETMessageData): string => {
+ const stripWhitespace = data.stripWhitespace;
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ data.rows.forEach((row) => {
+ content += `${newline}`;
+ data.columns.forEach(({ title }, colIndex) => {
+ content += `${tab}${title} ${newline}`;
+ content += `${tab}${row[colIndex]} ${newline}`;
+ });
+ content += ` ${newline}`;
+ });
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/HTML/HTML.state.tsx b/packages/plugins/src/exportTypes/HTML/HTML.state.tsx
new file mode 100644
index 000000000..ad9db98f4
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/HTML.state.tsx
@@ -0,0 +1,18 @@
+import { ETState } from '@generatedata/types';
+
+export type ExportFormat = 'table' | 'ul' | 'dl';
+
+export type GenerationOptionsType = {
+ exportFormat: ExportFormat;
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ exportFormat: 'table'
+};
+
+export interface HTMLSettings extends ETState, GenerationOptionsType {}
+
+export const initialState: HTMLSettings = {
+ ...defaultGenerationOptions,
+ isValid: true // there's no way the config settings for the HTML ET can be misconfigured, so this is always true
+};
diff --git a/packages/plugins/src/exportTypes/HTML/HTML.tsx b/packages/plugins/src/exportTypes/HTML/HTML.tsx
new file mode 100644
index 000000000..18037691c
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/HTML.tsx
@@ -0,0 +1,37 @@
+import * as React from 'react';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import etShared from '../../../styles/etShared.scss';
+import { ETDownloadPacket, ETDownloadPacketResponse, ETSettings } from '@generatedata/types';
+import { ExportFormat } from './HTML.state';
+
+export const Settings = ({ i18n, id, data, onUpdate }: ETSettings) => {
+ const onChange = (exportFormat: ExportFormat): void => {
+ onUpdate({
+ ...data,
+ exportFormat
+ });
+ };
+
+ return (
+
+ {i18n.dataFormat}
+
+ onChange('table')}
+ name={`${id}-dataFormat`}
+ checked={data.exportFormat === 'table'}
+ />
+ onChange('ul')} name={`${id}-dataFormat`} checked={data.exportFormat === 'ul'} />
+ onChange('dl')} name={`${id}-dataFormat`} checked={data.exportFormat === 'dl'} />
+
+
+ );
+};
+
+export const getCodeMirrorMode = (): string => 'text/html';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.html`,
+ fileType: 'text/html'
+});
diff --git a/packages/plugins/src/exportTypes/HTML/HTML.worker.ts b/packages/plugins/src/exportTypes/HTML/HTML.worker.ts
new file mode 100644
index 000000000..150edac1d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/HTML.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './HTML.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/HTML/README.md b/packages/plugins/src/exportTypes/HTML/README.md
new file mode 100644
index 000000000..ec2846133
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/README.md
@@ -0,0 +1,82 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » HTML
+
+This plugin outputs the data in HTML format. You can either choose to output the data in a ``, a `` or
+``'s, or if you're feeling really fancy, you can control the exact output via a custom Smarty template. The
+latter's intended for advanced users and requires knowledge of the PHP Smarty templating language
+
+### Example API Usage
+
+Try POSTing the following JSON content to the following URL:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AutoIncrement",
+ "title": "Row",
+ "settings": {
+ "incrementStart": 1,
+ "incrementValue": 1
+ }
+ },
+ {
+ "type": "Names",
+ "title": "name",
+ "settings": {
+ "placeholder": "Name Initial. Surname"
+ }
+ }
+ ],
+ "export": {
+ "type": "HTML",
+ "settings": {
+ "exportFormat": "table"
+ }
+ }
+}
+```
+
+### Custom template example
+
+Here's a second example that uses a custom Smarty template to customize the generated HTML. Note: the inconvenient
+thing about using the API for this is that you need to serialize the template into a single line. Yeah, that's a pain.
+
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AutoIncrement",
+ "title": "Row",
+ "settings": {
+ "incrementStart": 1,
+ "incrementValue": 1
+ }
+ },
+ {
+ "type": "Names",
+ "title": "name",
+ "settings": {
+ "placeholder": "Name Initial. Surname"
+ }
+ }
+ ],
+ "export": {
+ "type": "HTML",
+ "settings": {
+ "exportFormat": "custom",
+ "customTemplate": "{if $isFirstBatch}\n\n{foreach $colData as $col} {$col} \n{/foreach}\n \n{/if}{foreach $rowData as $row}\n{foreach $row as $r} {$r} \n{/foreach} \n{/foreach}{if $isLastBatch}
{/if}"
+ }
+ }
+}
+```
+
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
+
diff --git a/packages/plugins/src/exportTypes/HTML/__tests__/HTML.test.tsx b/packages/plugins/src/exportTypes/HTML/__tests__/HTML.test.tsx
new file mode 100644
index 000000000..54c0e2d39
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/__tests__/HTML.test.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Settings } from '../HTML';
+import { initialState } from '../HTML.state';
+import { defaultETSettings } from '../../../../../tests/testHelpers';
+
+const i18n = require('../i18n/en.json');
+
+describe('Settings', () => {
+ it('renders', () => {
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+
+ const { container } = render(
+
+ );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/exportTypes/HTML/bundle.ts b/packages/plugins/src/exportTypes/HTML/bundle.ts
new file mode 100644
index 000000000..218a3465e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/bundle.ts
@@ -0,0 +1,12 @@
+import { ETBundle } from '@generatedata/types';
+import { Settings, getCodeMirrorMode, getDownloadFileInfo } from './HTML';
+import { initialState } from './HTML.state';
+
+const bundle: ETBundle = {
+ Settings,
+ initialState,
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/HTML/config.ts b/packages/plugins/src/exportTypes/HTML/config.ts
new file mode 100644
index 000000000..d9c7b99d1
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'core',
+ codeMirrorModes: ['htmlmixed/htmlmixed', 'javascript/javascript', 'xml/xml', 'css/css']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/ar.json b/packages/plugins/src/exportTypes/HTML/i18n/ar.json
new file mode 100644
index 000000000..ffef72f19
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/ar.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "عنوان العمود",
+ "dataFormat": "تنسيق البيانات",
+ "batchVars": "القيم المنطقية لمعرفة ما إذا كانت الدفعة الحالية من النتائج التي يتم إنشاؤها هي الأولى أم الأخيرة. يتم استخدام هذا فقط للمستخدمين الذين يقومون بإنشاء البيانات في الصفحة ، مما يؤدي إلى إنشاء النتائج في أجزاء. بالنسبة لجميع المواقف الأخرى ، كلاهما صحيح دائمًا."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/de.json b/packages/plugins/src/exportTypes/HTML/i18n/de.json
new file mode 100644
index 000000000..771e5236f
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/de.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "Spaltentitel",
+ "dataFormat": "Datenformat",
+ "batchVars": "Boolsche ob die aktuelle Charge der Ergebnisse erzeugt ist die erste oder letzte. Dies wird immer nur für Benutzer Erzeugen der Daten in-Seite, die die Ergebnisse in Blöcken generiert verwendet. In allen anderen Situationen, beide sind immer wahr."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/en.json b/packages/plugins/src/exportTypes/HTML/i18n/en.json
new file mode 100644
index 000000000..c3722c8ea
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/en.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "Column Title",
+ "dataFormat": "Data format",
+ "batchVars": "Booleans for whether or not the current batch of results being generated is the first or last. This is only ever used for users generating the data in-page, which generates the results in chunks. For all other situations, both are always true."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/es.json b/packages/plugins/src/exportTypes/HTML/i18n/es.json
new file mode 100644
index 000000000..954124f4b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/es.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "Título de la columna",
+ "dataFormat": "Formato de datos",
+ "batchVars": "Indica si el lote actual de resultados que será generado será el primero o el último. Esto sólo se usa para datos generado en la misma página, lo que genera los resultados por lotes. En el resto de situaciones, ambos son verdaderos."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/fr.json b/packages/plugins/src/exportTypes/HTML/i18n/fr.json
new file mode 100644
index 000000000..766dfd6bc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/fr.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "Titre de la colonne",
+ "dataFormat": "Format des données",
+ "batchVars": "Booléen permettant de savoir si le lot courant des résultats générés est le premier ou le dernier. N'est utilisé que dans le cas d'une génération des données dans la page, ce qui génère des résultats en morceaux. Pour tous les autres cas, les deux valeurs sont toujours vrai."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/hi.json b/packages/plugins/src/exportTypes/HTML/i18n/hi.json
new file mode 100644
index 000000000..6c9783078
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/hi.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "कॉलम शीर्षक",
+ "dataFormat": "डेटा स्वरूप",
+ "batchVars": "उत्पन्न होने वाले परिणामों का वर्तमान बैच पहला या अंतिम है या नहीं, इसके लिए बूलियन। यह केवल उन उपयोगकर्ताओं के लिए उपयोग किया जाता है जो इन-पेज डेटा उत्पन्न करते हैं, जो परिणामों को विखंडू में उत्पन्न करता है। अन्य सभी स्थितियों के लिए, दोनों हमेशा सत्य होते हैं।"
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/ja.json b/packages/plugins/src/exportTypes/HTML/i18n/ja.json
new file mode 100644
index 000000000..e71533c1e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/ja.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "列タイトル",
+ "dataFormat": "データ形式",
+ "batchVars": "生成されている結果の現在のバッチが最初であるか最後であるかを示すブール値。 これは、結果をチャンクで生成するページ内のデータを生成するユーザーにのみ使用されます。 他のすべての状況では、両方が常に当てはまります。"
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/nl.json b/packages/plugins/src/exportTypes/HTML/i18n/nl.json
new file mode 100644
index 000000000..59863f4d5
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/nl.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "Kolomtitel",
+ "batchVars": "Booleans om aan te geven van de huidige export batch de eerste van de laatste is. Dit wordt alleen gebruikt bij data die in-page wordt gegenereerd, waarbij de resultaten in blokken worden gegenereerd. In alle andere gevallen is deze waarde altijd waar.",
+ "dataFormat": "Data Formaat"
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/pt.json b/packages/plugins/src/exportTypes/HTML/i18n/pt.json
new file mode 100644
index 000000000..1b900fb88
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/pt.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "Título da Coluna",
+ "dataFormat": "Formato de dados",
+ "batchVars": "Booleanos para definir se o lote atual de resultados sendo gerado é o primeiro ou o último. Isso só é usado para usuários que geram os dados na página, o que gera os resultados em blocos. Para todas as outras situações, ambos são sempre verdadeiros."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/ru.json b/packages/plugins/src/exportTypes/HTML/i18n/ru.json
new file mode 100644
index 000000000..635c6e5b3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/ru.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "Название столбца",
+ "dataFormat": "Формат данных",
+ "batchVars": "Логические значения, определяющие, является ли текущая партия генерируемых результатов первой или последней. Это когда-либо используется только для пользователей, генерирующих данные на странице, которые генерируют результаты в виде фрагментов. Для всех других ситуаций оба всегда верны."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/ta.json b/packages/plugins/src/exportTypes/HTML/i18n/ta.json
new file mode 100644
index 000000000..63408fc54
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/ta.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "நெடுவரிசை தலைப்பு",
+ "dataFormat": "தரவு வடிவம்",
+ "batchVars": "தற்போதைய தொகுதி முடிவுகள் உருவாக்கப்படுகிறதா இல்லையா என்பதற்கான பூலியன்ஸ் முதல் அல்லது கடைசி. பக்கத்திலுள்ள தரவை உருவாக்கும் பயனர்களுக்கு மட்டுமே இது எப்போதும் பயன்படுத்தப்படுகிறது, இது துகள்களில் முடிவுகளை உருவாக்குகிறது. மற்ற எல்லா சூழ்நிலைகளுக்கும், இரண்டும் எப்போதும் உண்மைதான்."
+}
diff --git a/packages/plugins/src/exportTypes/HTML/i18n/zh.json b/packages/plugins/src/exportTypes/HTML/i18n/zh.json
new file mode 100644
index 000000000..cf27d43fc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/HTML/i18n/zh.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "HTML",
+ "COL_TITLE": "栏标题",
+ "dataFormat": "资料格式",
+ "batchVars": "布尔值,用于确定当前生成的结果批次是第一个还是最后一个。 仅用于生成页内数据的用户,页内数据以块的形式生成结果。 对于所有其他情况,两者始终都是正确的。"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/JSON.generate.ts b/packages/plugins/src/exportTypes/JSON/JSON.generate.ts
new file mode 100644
index 000000000..8c6072282
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/JSON.generate.ts
@@ -0,0 +1,125 @@
+import { ETMessageData } from '../../';
+import { WorkerUtils } from '../../';
+
+let utils: WorkerUtils;
+export const generate = (data: any, workerUtils: WorkerUtils): string => {
+ utils = workerUtils;
+
+ const { settings, stripWhitespace } = data;
+
+ return settings.dataStructureFormat === 'simple' ? generateSimple(data, stripWhitespace) : generateComplex(data, stripWhitespace);
+};
+
+const maybeEnquote = (value: any) => {
+ const isNumeric = utils.numberUtils.isNumeric(value);
+
+ let startsWithZero = false;
+ if ((value || '').toString().length > 0) {
+ startsWithZero = value.toString()[0] === '0';
+ }
+ const isValidNumber = isNumeric && !startsWithZero;
+ if (!isValidNumber && !isJavascriptBoolean(value)) {
+ value = `"${value}"`;
+ }
+
+ return value;
+};
+
+const generateSimple = (generationData: ETMessageData, stripWhitespace: boolean): string => {
+ let content = '';
+ let comma = '';
+
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+ const space = stripWhitespace ? '' : ' ';
+
+ if (generationData.isFirstBatch) {
+ content += '[';
+ } else {
+ comma = ',';
+ }
+
+ generationData.rows.forEach((row: any) => {
+ content += `${comma}${newline}${tab}{`;
+ comma = '';
+
+ generationData.columns.forEach(({ title, metadata }: any, colIndex: number) => {
+ const propName: string = title.replace(/"/, '"');
+
+ let value = row[colIndex];
+
+ // if a DT has explicitly said it's a string, use a string
+ if (metadata && metadata.general && metadata.general.dataType && metadata.general.dataType === 'string') {
+ value = `"${value}"`;
+
+ // otherwise, do a safety check and encase it in double quote if necessary
+ } else {
+ value = maybeEnquote(value);
+ }
+
+ content += `${comma}${newline}${tab}${tab}"${propName}":${space}${value}`;
+ comma = ',';
+ });
+
+ content += `${newline}${tab}}`;
+ });
+
+ if (generationData.isLastBatch) {
+ content += `${newline}]`;
+ }
+
+ return content;
+};
+
+const generateComplex = (generationData: ETMessageData, stripWhitespace: boolean): string => {
+ let content = '';
+ const colTitles = generationData.columns.map(({ title }: any) => title);
+
+ if (generationData.isFirstBatch) {
+ if (stripWhitespace) {
+ const cols = `"${colTitles.join('","')}"`;
+ content += `{"cols":[${cols}],"data":[`;
+ } else {
+ const cols = `"${colTitles.join('",\n\t\t"')}"`;
+ content += `{\n\t"cols": [\n\t\t${cols}\n\t],\n\t"data": [\n`;
+ }
+ }
+
+ const numRows = generationData.rows.length;
+ generationData.rows.forEach((row: any, rowIndex: number) => {
+ const rowValsArr: any[] = [];
+ colTitles.forEach((colTitle: string, colIndex: number) => {
+ let value = row[colIndex];
+ value = maybeEnquote(value);
+ rowValsArr.push(value);
+ });
+
+ if (stripWhitespace) {
+ const rowVals = rowValsArr.join(',');
+ content += `[${rowVals}]`;
+ if (rowIndex < numRows - 1) {
+ content += ',';
+ }
+ } else {
+ const rowVals = rowValsArr.join(',\n\t\t\t');
+ content += `\t\t[\n\t\t\t${rowVals}\n\t\t]`;
+ if (rowIndex < numRows - 1) {
+ content += ',\n';
+ } else if (!generationData.isLastBatch) {
+ content += ',\n';
+ }
+ }
+ });
+
+ if (generationData.isLastBatch) {
+ if (stripWhitespace) {
+ content += ']}';
+ } else {
+ content += '\n\t]\n}';
+ }
+ }
+
+ return content;
+};
+
+const isJavascriptBoolean = (n: any): boolean => n === 'true' || n === 'false' || n === true || n === false;
diff --git a/packages/plugins/src/exportTypes/JSON/JSON.state.ts b/packages/plugins/src/exportTypes/JSON/JSON.state.ts
new file mode 100644
index 000000000..fccbdc6ff
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/JSON.state.ts
@@ -0,0 +1,16 @@
+import { ETState } from '@generatedata/types';
+
+export type DataStructureFormat = 'simple' | 'complex';
+
+export interface GenerationOptionsType {
+ dataStructureFormat: DataStructureFormat;
+}
+
+export interface JSONSettings extends ETState, GenerationOptionsType {}
+
+export const initialState: JSONSettings = {
+ dataStructureFormat: 'simple',
+ isValid: true
+};
+
+export const defaultGenerationOptions = initialState;
diff --git a/packages/plugins/src/exportTypes/JSON/JSON.tsx b/packages/plugins/src/exportTypes/JSON/JSON.tsx
new file mode 100644
index 000000000..e7477fdb8
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/JSON.tsx
@@ -0,0 +1,41 @@
+import * as React from 'react';
+import { ETDownloadPacket, ETDownloadPacketResponse, ETSettings } from '@generatedata/types';
+import etShared from '../../../styles/etShared.scss';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+
+export const Settings = ({ data, id, i18n, onUpdate }: ETSettings) => {
+ const onChange = (field: string, value: any): void => {
+ onUpdate({
+ ...data,
+ [field]: value
+ });
+ };
+
+ return (
+
+ {i18n.dataStructureFormat}
+
+
+ onChange('dataStructureFormat', 'simple')}
+ name={`${id}-simple`}
+ checked={data.dataStructureFormat === 'simple'}
+ />
+ onChange('dataStructureFormat', 'complex')}
+ name={`${id}-complex`}
+ checked={data.dataStructureFormat === 'complex'}
+ />
+
+
+ );
+};
+
+export const getCodeMirrorMode = (): string => 'application/ld+json';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.json`,
+ fileType: 'application/json'
+});
diff --git a/packages/plugins/src/exportTypes/JSON/JSON.worker.ts b/packages/plugins/src/exportTypes/JSON/JSON.worker.ts
new file mode 100644
index 000000000..e0ae6afd7
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/JSON.worker.ts
@@ -0,0 +1,15 @@
+import utils from '../../../utils';
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './JSON.generate';
+
+const context: Worker = self as any;
+
+let workerUtilsLoaded = false;
+export const onmessage = (e: ETOnMessage) => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+
+ context.postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/exportTypes/JSON/README.md b/packages/plugins/src/exportTypes/JSON/README.md
new file mode 100644
index 000000000..cde61ee39
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/README.md
@@ -0,0 +1,178 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » JSON
+
+- [Intro](#intro)
+- [Example API Usage](#example-api-usage)
+- [API Help](#api-help)
+- [Generating complex objects](#generating-complex-objects)
+
+
+
+### Intro
+
+The JSON Export Type generates the random data in JSON format. It provides a couple of simple options:
+
+- `stripWhitespace` to keep file size down
+- `dataStructureFormat`: this should be set to a string, and can be `simple` or `complex` - `simple` means that it
+outputs a simple array of objects, with each object being grouped in key-value pairs based on whatever title you entered
+for the row; `complex` arranges the generated content differently: it groups the generated data into two top level
+properties, like this:
+
+```javascript
+{
+ "cols": [
+ "name",
+ "email"
+ ],
+ "data": [
+ [
+ "Tom Filkin",
+ "eu.odio.Phasellus@utodio.net"
+ ],
+ [
+ "Ed Phillips",
+ "vulputate.velit.eu@metus.edu"
+ ],
+ [
+ "Sally Etkins",
+ "ut@et.net"
+ ]
+ ]
+}
+```
+
+
+### Example API Usage
+
+To generate JSON data using the API, you need to specify the JSON Export Type in the `export` setting and post the
+JSON content to here:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+Here's an example pulled from the Alphanumeric Data Type example:
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "Alphanumeric",
+ "title": "Random Password",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "Alphanumeric",
+ "title": "US Zipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "JSON",
+ "settings": {
+ "stripWhitespace": false,
+ "dataStructureFormat": "simple"
+ }
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
+
+
+### Generating Complex Objects
+
+As of 3.2.5, @tohagan added a nice feature to allow the *Simple* JSON format to generate nested objects. This is how
+it works.
+
+When field names contain "." delimiters and a JSON | Simple export is selected, nested objects will be
+generated corresponding to the structure of the fields names. The only restriction is that fields related to the same
+object must be sequential.
+
+
+#### Example 1: 2 Field Names
+
+- name.first
+- name.last
+- address.0
+- address.1
+
+##### Generates:
+
+```javascript
+ [
+ {
+ "name": {
+ "first": "Rahim",
+ "last": "Wyatt"
+ },
+ "address": {
+ "0": "Ap #496-4933 Non, Street",
+ "1": "190-7424 Malesuada Street"
+ }
+ },
+ {
+ "name": {
+ "first": "Emi",
+ "last": "King"
+ },
+ "address": {
+ "0": "5331 Lacinia. Avenue",
+ "1": "486-7654 Nisl Road"
+ }
+ }
+ ]
+```
+
+#### Example 2: 3-level Nested JSON
+
+Field names ... All "Email" type
+
+- a
+- b.c
+- b.d.e
+- b.d.f
+- b.p.a
+- b.p.b
+
+
+##### Generates:
+
+```javascript
+ [
+ {
+ "a": "et.commodo@amet.org",
+ "b": {
+ "c": "vestibulum.neque.sed@nuncsedlibero.org",
+ "d": {
+ "e": "ac@musAeneaneget.ca",
+ "f": "tellus.Suspendisse@etpedeNunc.ca"
+ },
+ "p": {
+ "a": "est.ac@nostra.ca",
+ "b": "et.tristique@rutrum.org"
+ }
+ }
+ },
+ {
+ "a": "sit.amet.ante@varius.com",
+ "b": {
+ "c": "et@neque.net",
+ "d": {
+ "e": "lacus.Quisque@Nullatempor.edu",
+ "f": "ac@sem.edu"
+ },
+ "p": {
+ "a": "libero.dui.nec@sit.ca",
+ "b": "nec@malesuada.com"
+ }
+ }
+ }
+ ]
+```
+
+
diff --git a/packages/plugins/src/exportTypes/JSON/__tests__/JSON.generator.test.ts b/packages/plugins/src/exportTypes/JSON/__tests__/JSON.generator.test.ts
new file mode 100644
index 000000000..42fd71b23
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/__tests__/JSON.generator.test.ts
@@ -0,0 +1,170 @@
+test.skip('skip', () => {});
+
+// import { generateSimple, isNested } from '../JSON.generator';
+// import { ETMessageData } from '../../';
+//
+//
+// describe('isNested', () => {
+// it('should return false when no columns have dots in them', () => {
+// const cols = [
+// 'one',
+// 'two',
+// 'three'
+// ];
+// expect(isNested(cols)).toEqual(false);
+// });
+//
+// it('should return false when there are no columns', () => {
+// expect(isNested([])).toEqual(false);
+// });
+//
+//
+// it('should return true when one item contains a dot', () => {
+// expect(isNested(['a.c', 'b', 'c'])).toEqual(true);
+// expect(isNested(['a', 'b.e', 'b.c'])).toEqual(true);
+// expect(isNested(['a', 'b', 'b.c'])).toEqual(true);
+// });
+// });
+//
+//
+// describe('generateSimple', () => {
+// const data: ETMessageData = {
+// columns: [
+// { title: 'One', dataType: 'Names' },
+// { title: 'Two', dataType: 'Names' }
+// ],
+// rows: [
+// ['Row #1, Cell #1', 'Row #1, Cell #2'],
+// ['Row #2, Cell #1', 'Row #2, Cell #2']
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// // my IDE likes to convert tabs to spaces & I'm not going to argue with it. This'll work across all developers
+// // systems, regardless of their preferences
+// it('should return expected JSON content', () => {
+// expect(generateSimple(data, false)).toEqual(
+// `[
+// \t{
+// \t\t"One": "Row #1, Cell #1",
+// \t\t"Two": "Row #1, Cell #2"
+// \t},
+// \t{
+// \t\t"One": "Row #2, Cell #1",
+// \t\t"Two": "Row #2, Cell #2"
+// \t}
+// ]`
+// );
+// });
+//
+// it('should return expected minified JSON content', () => {
+// expect(generateSimple(data, true)).toEqual(
+// `[{"One":"Row #1, Cell #1","Two":"Row #1, Cell #2"},{"One":"Row #2, Cell #1","Two":"Row #2, Cell #2"}]`
+// );
+// });
+//
+// it('should escape double quotes within property names', () => {
+// const doubleQuoteData: ETMessageData = {
+// columns: [
+// { title: 'Name', dataType: 'Names' },
+// { title: '"Phone" Number', dataType: 'Names' }
+// ],
+// rows: [
+// ['Tom', '(604) 123-1234'],
+// ['Susan', '(604) 733-1224'],
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(doubleQuoteData, false)).toEqual(
+// `[
+// \t{
+// \t\t"Name": "Tom",
+// \t\t"\"Phone\" Number": "(604) 123-1234"
+// \t},
+// \t{
+// \t\t"Name": "Susan",
+// \t\t"\"Phone\" Number": "(604) 733-1224"
+// \t}
+// ]`
+// );
+// });
+//
+// it('should escape double quotes within values', () => {
+// const doubleQuoteValueData: ETMessageData = {
+// columns: [
+// { title: 'Name', dataType: 'Names' },
+// { title: 'Phone', dataType: 'Names' }
+// ],
+// rows: [
+// ['Tom', '"Whoah" he said...'],
+// ['Susan', 'Like "TOTALLY!"'],
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(doubleQuoteValueData, false)).toEqual(
+// `[
+// \t{
+// \t\t"Name": "Tom",
+// \t\t"Phone": "\"Whoah\" he said..."
+// \t},
+// \t{
+// \t\t"Name": "Susan",
+// \t\t"Phone": "Like \"TOTALLY!\""
+// \t}
+// ]`
+// );
+// });
+//
+// it('should not put double quotes around numbers', () => {
+// const numData: ETMessageData = {
+// columns: [
+// { title: 'Num1', dataType: 'Names' },
+// { title: 'Num2', dataType: 'Names' },
+// { title: 'Num3', dataType: 'Names' },
+// { title: 'Num4', dataType: 'Names' },
+// { title: 'Num5', dataType: 'Names' },
+// { title: 'Num6', dataType: 'Names' }
+// ],
+// rows: [
+// ['0', 0, '1', 1, '1.23', 1.23]
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(numData, true)).toEqual(
+// `[{"Num1":0,"Num2":0,"Num3":1,"Num4":1,"Num5":1.23,"Num6":1.23}]`
+// );
+// });
+//
+// it('should not put double quotes around boolean values', () => {
+// const numData: ETMessageData = {
+// columns: [
+// { title: 'a', dataType: 'Names' },
+// { title: 'b', dataType: 'Names' },
+// { title: 'c', dataType: 'Names' },
+// { title: 'd', dataType: 'Names' }
+// ],
+// rows: [
+// ['false', false, 'true', true]
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(numData, true)).toEqual(
+// `[{"a":false,"b":false,"c":true,"d":true}]`
+// );
+// });
+//
+// });
diff --git a/packages/plugins/src/exportTypes/JSON/__tests__/JSON.test.tsx b/packages/plugins/src/exportTypes/JSON/__tests__/JSON.test.tsx
new file mode 100644
index 000000000..4e865a15b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/__tests__/JSON.test.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Settings } from '../JSON';
+import { initialState } from '../JSON.state';
+import { defaultETSettings } from '../../../../../tests/testHelpers';
+
+const i18n = require('../i18n/en.json');
+
+describe('Settings', () => {
+ it('renders', () => {
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+
+ const { container } = render( );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/exportTypes/JSON/bundle.ts b/packages/plugins/src/exportTypes/JSON/bundle.ts
new file mode 100644
index 000000000..b6c72d2d5
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/bundle.ts
@@ -0,0 +1,12 @@
+import { ETBundle } from '@generatedata/types';
+import { Settings, getCodeMirrorMode, getDownloadFileInfo } from './JSON';
+import { initialState } from './JSON.state';
+
+const bundle: ETBundle = {
+ Settings,
+ initialState,
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/JSON/config.ts b/packages/plugins/src/exportTypes/JSON/config.ts
new file mode 100644
index 000000000..824114250
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'core',
+ codeMirrorModes: ['javascript/javascript', 'xml/xml', 'markdown/markdown']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/ar.json b/packages/plugins/src/exportTypes/JSON/i18n/ar.json
new file mode 100644
index 000000000..d4ab3ea16
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/ar.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "اسم الخاصية",
+ "complex": "مركب",
+ "simple": "بسيط",
+ "dataStructureFormat": "تنسيق هيكل البيانات"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/de.json b/packages/plugins/src/exportTypes/JSON/i18n/de.json
new file mode 100644
index 000000000..25b4c25cd
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/de.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "Name des Anwesens",
+ "complex": "Komplex",
+ "dataStructureFormat": "Datenstruktur-Format",
+ "simple": "Einfach"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/en.json b/packages/plugins/src/exportTypes/JSON/i18n/en.json
new file mode 100644
index 000000000..ca65c2fe6
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/en.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "Property Name",
+ "complex": "Complex",
+ "simple": "Simple",
+ "dataStructureFormat": "Data structure format"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/es.json b/packages/plugins/src/exportTypes/JSON/i18n/es.json
new file mode 100644
index 000000000..0dc9d6835
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/es.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "Nombre de la propiedad",
+ "complex": "Complejo",
+ "simple": "Simple",
+ "dataStructureFormat": "Formato de estructura de datos"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/fr.json b/packages/plugins/src/exportTypes/JSON/i18n/fr.json
new file mode 100644
index 000000000..b4312f854
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/fr.json
@@ -0,0 +1,8 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "Nom de la propriété",
+ "complex": "Complexe",
+ "dataStructureFormat": "Format de la structure des données",
+ "simple": "Simple"
+}
+
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/hi.json b/packages/plugins/src/exportTypes/JSON/i18n/hi.json
new file mode 100644
index 000000000..ff32623e1
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/hi.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "संपत्ति का नाम",
+ "complex": "जटिल",
+ "simple": "सरल",
+ "dataStructureFormat": "डेटा संरचना प्रारूप"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/ja.json b/packages/plugins/src/exportTypes/JSON/i18n/ja.json
new file mode 100644
index 000000000..e4767523e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/ja.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "プロパティ名",
+ "complex": "繁雑",
+ "simple": "シンプル",
+ "dataStructureFormat": "データ構造フォーマット"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/nl.json b/packages/plugins/src/exportTypes/JSON/i18n/nl.json
new file mode 100644
index 000000000..c490a3e69
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/nl.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "Eigendomsnaam",
+ "complex": "Complex",
+ "dataStructureFormat": "Datastructuur format",
+ "simple": "Eenvoudig"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/pt.json b/packages/plugins/src/exportTypes/JSON/i18n/pt.json
new file mode 100644
index 000000000..93d2813e6
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/pt.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "Nome da propriedade",
+ "complex": "Complexa",
+ "simple": "Simples",
+ "dataStructureFormat": "Formato de estrutura de dados"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/ru.json b/packages/plugins/src/exportTypes/JSON/i18n/ru.json
new file mode 100644
index 000000000..2ecfa8af2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/ru.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "Имя свойства",
+ "complex": "Сложный",
+ "simple": "Простой",
+ "dataStructureFormat": "Формат структуры данных"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/ta.json b/packages/plugins/src/exportTypes/JSON/i18n/ta.json
new file mode 100644
index 000000000..61c9f3ddb
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/ta.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "சொத்தின் பெயர்",
+ "complex": "சிக்கலான",
+ "simple": "எளிமையானது",
+ "dataStructureFormat": "தரவு கட்டமைப்பு வடிவம்"
+}
diff --git a/packages/plugins/src/exportTypes/JSON/i18n/zh.json b/packages/plugins/src/exportTypes/JSON/i18n/zh.json
new file mode 100644
index 000000000..3b5afbc88
--- /dev/null
+++ b/packages/plugins/src/exportTypes/JSON/i18n/zh.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "JSON",
+ "COL_TITLE": "物业名称",
+ "complex": "数据结构格式",
+ "simple": "简单",
+ "dataStructureFormat": "数据结构格式"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/Javascript.generate.ts b/packages/plugins/src/exportTypes/Javascript/Javascript.generate.ts
new file mode 100644
index 000000000..5903fcd13
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/Javascript.generate.ts
@@ -0,0 +1,42 @@
+import { ETMessageData } from '../../';
+
+export const generate = (data: ETMessageData): string => {
+ const { isFirstBatch, isLastBatch, rows, columns, settings, stripWhitespace } = data;
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (isFirstBatch) {
+ if (settings.jsExportFormat === 'variable') {
+ content += `var data = [${newline}`;
+ } else if (settings.jsExportFormat == 'es6') {
+ content += `export default [${newline}`;
+ } else {
+ content += `module.exports = [${newline}`;
+ }
+ }
+
+ rows.forEach((row: any, rowIndex: number) => {
+ content += `${tab}{${newline}${tab}${tab}`;
+
+ const pairs: string[] = [];
+ columns.forEach(({ title }, colIndex) => {
+ const currValue = row[colIndex];
+ pairs.push(`"${title}": "${currValue}"`);
+ });
+
+ content += pairs.join(`,${newline}${tab}${tab}`);
+
+ if (isLastBatch && rowIndex == rows.length - 1) {
+ content += `${newline}${tab}}${newline}`;
+ } else {
+ content += `${newline}${tab}},${newline}`;
+ }
+ });
+
+ if (isLastBatch) {
+ content += `];${newline}`;
+ }
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/Javascript/Javascript.state.tsx b/packages/plugins/src/exportTypes/Javascript/Javascript.state.tsx
new file mode 100644
index 000000000..fb2c3c3c3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/Javascript.state.tsx
@@ -0,0 +1,18 @@
+import { ETState } from '@generatedata/types';
+
+export type JavascriptExportFormat = 'variable' | 'es6' | 'commonJs';
+
+export type GenerationOptionsType = {
+ jsExportFormat: JavascriptExportFormat;
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ jsExportFormat: 'variable'
+};
+
+export interface ProgrammingLanguageState extends ETState, GenerationOptionsType {}
+
+export const initialState: ProgrammingLanguageState = {
+ ...defaultGenerationOptions,
+ isValid: true
+};
diff --git a/packages/plugins/src/exportTypes/Javascript/Javascript.tsx b/packages/plugins/src/exportTypes/Javascript/Javascript.tsx
new file mode 100644
index 000000000..d76b46a63
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/Javascript.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import RadioPill, { RadioPillRow } from '~components/pills/RadioPill';
+import etShared from '../../../styles/etShared.scss';
+import { ETDownloadPacket, ETDownloadPacketResponse, ETSettings } from '@generatedata/types';
+
+export const Settings = ({ i18n, id, data, onUpdate }: ETSettings) => {
+ const onChange = (prop: string, value: any): void => {
+ onUpdate({
+ ...data,
+ [prop]: value
+ });
+ };
+
+ return (
+
+ {i18n.format}
+
+ onChange('jsExportFormat', 'variable')}
+ name={`${id}-js-export-format`}
+ checked={data.jsExportFormat === 'variable'}
+ />
+ onChange('jsExportFormat', 'es6')}
+ name={`${id}-js-export-format`}
+ checked={data.jsExportFormat === 'es6'}
+ />
+ onChange('jsExportFormat', 'commonJs')}
+ name={`${id}-js-export-format`}
+ checked={data.jsExportFormat === 'commonJs'}
+ />
+
+
+ );
+};
+
+export const getCodeMirrorMode = (): string => 'text/javascript';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => {
+ return {
+ filename: `data-${packetId}.js}`,
+ fileType: ''
+ };
+};
diff --git a/packages/plugins/src/exportTypes/Javascript/Javascript.worker.ts b/packages/plugins/src/exportTypes/Javascript/Javascript.worker.ts
new file mode 100644
index 000000000..25e89e914
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/Javascript.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './Javascript.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/Javascript/README.md b/packages/plugins/src/exportTypes/Javascript/README.md
new file mode 100644
index 000000000..3112f5658
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/README.md
@@ -0,0 +1 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » Javascript
diff --git a/packages/plugins/src/exportTypes/Javascript/__tests__/Javascript.test.tsx b/packages/plugins/src/exportTypes/Javascript/__tests__/Javascript.test.tsx
new file mode 100644
index 000000000..7726bcd08
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/__tests__/Javascript.test.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Settings } from '../Javascript';
+import { initialState } from '../Javascript.state';
+import { defaultETSettings } from '../../../../../tests/testHelpers';
+
+const i18n = require('../i18n/en.json');
+
+describe('Settings', () => {
+ it('renders', () => {
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+
+ const { container } = render(
+
+ );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/exportTypes/Javascript/bundle.ts b/packages/plugins/src/exportTypes/Javascript/bundle.ts
new file mode 100644
index 000000000..856686356
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/bundle.ts
@@ -0,0 +1,12 @@
+import { ETBundle } from '@generatedata/types';
+import { Settings, getCodeMirrorMode, getDownloadFileInfo } from './Javascript';
+import { initialState } from './Javascript.state';
+
+const bundle: ETBundle = {
+ Settings,
+ initialState,
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/Javascript/config.ts b/packages/plugins/src/exportTypes/Javascript/config.ts
new file mode 100644
index 000000000..7619b3acc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'programmingLanguage',
+ codeMirrorModes: ['javascript/javascript', 'xml/xml', 'markdown/markdown', 'clike/clike', 'perl/perl', 'php/php', 'ruby/ruby']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/ar.json b/packages/plugins/src/exportTypes/Javascript/i18n/ar.json
new file mode 100644
index 000000000..42222144c
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/ar.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "اسم الخاصية",
+ "row_label": "Var / اسم الدعامة",
+ "language": "لغة",
+ "variable": "متغير",
+ "format": "شكل",
+ "es6Format": "تصدير ES6",
+ "commonJsFormat": "تصدير CommonJS"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/de.json b/packages/plugins/src/exportTypes/Javascript/i18n/de.json
new file mode 100644
index 000000000..080f4ecf3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/de.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "Name des Anwesens",
+ "row_label": "Var/Prop Name",
+ "language": "Sprache",
+ "variable": "Variable",
+ "format": "Format",
+ "es6Format": "ES6-Export",
+ "commonJsFormat": "CommonJS-Export"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/en.json b/packages/plugins/src/exportTypes/Javascript/i18n/en.json
new file mode 100644
index 000000000..77fcc56af
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/en.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "Property Name",
+ "row_label": "Var/Prop Name",
+ "language": "Language",
+ "variable": "Variable",
+ "format": "Format",
+ "es6Format": "ES6 export",
+ "commonJsFormat": "CommonJS export"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/es.json b/packages/plugins/src/exportTypes/Javascript/i18n/es.json
new file mode 100644
index 000000000..9f2cfa8c1
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/es.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "Nombre de la propiedad",
+ "row_label": "Var/Prop Name",
+ "language": "Idioma",
+ "variable": "Variable",
+ "format": "Formato",
+ "es6Format": "Exportación ES6",
+ "commonJsFormat": "Exportación CommonJS"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/fr.json b/packages/plugins/src/exportTypes/Javascript/i18n/fr.json
new file mode 100644
index 000000000..8b10fcbcd
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/fr.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "Nom de la propriété",
+ "row_label": "Nom de la variable / prop",
+ "language": "Langue",
+ "variable": "Variable",
+ "format": "Format",
+ "es6Format": "Exportation ES6",
+ "commonJsFormat": "Exportation CommonJS"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/hi.json b/packages/plugins/src/exportTypes/Javascript/i18n/hi.json
new file mode 100644
index 000000000..c15d8c31b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/hi.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "संपत्ति का नाम",
+ "row_label": "वार/प्रोप का नाम",
+ "language": "भाषा",
+ "variable": "चर",
+ "format": "प्रारूप",
+ "es6Format": "ES6 निर्यात",
+ "commonJsFormat": "CommonJS निर्यात"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/ja.json b/packages/plugins/src/exportTypes/Javascript/i18n/ja.json
new file mode 100644
index 000000000..5f8dc1a06
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/ja.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "プロパティ名",
+ "row_label": "変数/小道具名",
+ "language": "言語",
+ "variable": "変数",
+ "format": "フォーマット",
+ "es6Format": "ES6エクスポート",
+ "commonJsFormat": "CommonJSエクスポート"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/nl.json b/packages/plugins/src/exportTypes/Javascript/i18n/nl.json
new file mode 100644
index 000000000..53358c215
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/nl.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "Eigendomsnaam",
+ "row_label": "Var / Prop-naam",
+ "language": "Taal",
+ "variable": "Variabel",
+ "format": "Formaat",
+ "es6Format": "ES6-export",
+ "commonJsFormat": "CommonJS-export"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/pt.json b/packages/plugins/src/exportTypes/Javascript/i18n/pt.json
new file mode 100644
index 000000000..40805d774
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/pt.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "Nome da propriedade",
+ "row_label": "Variável / Nome do Prop",
+ "language": "Língua",
+ "variable": "Variável",
+ "format": "Formato",
+ "es6Format": "Exportação ES6",
+ "commonJsFormat": "Exportação CommonJS"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/ru.json b/packages/plugins/src/exportTypes/Javascript/i18n/ru.json
new file mode 100644
index 000000000..b557b1de9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/ru.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "Имя свойства",
+ "row_label": "Имя переменной/свойства",
+ "language": "Язык",
+ "variable": "Переменная",
+ "format": "Формат",
+ "es6Format": "Экспорт ES6",
+ "commonJsFormat": "Экспорт CommonJS"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/ta.json b/packages/plugins/src/exportTypes/Javascript/i18n/ta.json
new file mode 100644
index 000000000..df6488b75
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/ta.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "சொத்தின் பெயர்",
+ "row_label": "வர் / ப்ராப் பெயர்",
+ "language": "மொழி",
+ "variable": "மாறி",
+ "format": "வடிவம்",
+ "es6Format": "ES6 ஏற்றுமதி",
+ "commonJsFormat": "காமன்ஜேஎஸ் ஏற்றுமதி"
+}
diff --git a/packages/plugins/src/exportTypes/Javascript/i18n/zh.json b/packages/plugins/src/exportTypes/Javascript/i18n/zh.json
new file mode 100644
index 000000000..6284b08ec
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Javascript/i18n/zh.json
@@ -0,0 +1,10 @@
+{
+ "EXPORT_TYPE_NAME": "Javascript",
+ "COL_TITLE": "物业名称",
+ "row_label": "变量/道具名称",
+ "language": "语言",
+ "variable": "变量",
+ "format": "格式",
+ "es6Format": "ES6导出",
+ "commonJsFormat": "CommonJS导出"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/LDIF.generate.ts b/packages/plugins/src/exportTypes/LDIF/LDIF.generate.ts
new file mode 100644
index 000000000..b1f23e68d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/LDIF.generate.ts
@@ -0,0 +1,16 @@
+// Original author: Marco Corona
+import { ETMessageData } from '../../';
+
+export const generate = (data: ETMessageData): string => {
+ const { columns, rows } = data;
+
+ let content = '';
+ rows.forEach((row) => {
+ columns.forEach((col, index) => {
+ content += `${col.title}: ${row[index]}\n`;
+ });
+ content += '\n';
+ });
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/LDIF/LDIF.state.tsx b/packages/plugins/src/exportTypes/LDIF/LDIF.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/LDIF.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/exportTypes/LDIF/LDIF.tsx b/packages/plugins/src/exportTypes/LDIF/LDIF.tsx
new file mode 100644
index 000000000..22e15bbdf
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/LDIF.tsx
@@ -0,0 +1,8 @@
+import { ETDownloadPacket, ETDownloadPacketResponse } from '@generatedata/types';
+
+export const getCodeMirrorMode = (): string => 'text/x-yaml';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.ldf`,
+ fileType: 'application/csv'
+});
diff --git a/packages/plugins/src/exportTypes/LDIF/LDIF.worker.ts b/packages/plugins/src/exportTypes/LDIF/LDIF.worker.ts
new file mode 100644
index 000000000..99a8ad732
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/LDIF.worker.ts
@@ -0,0 +1,9 @@
+// Original author: Marco Corona
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './LDIF.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/LDIF/README.md b/packages/plugins/src/exportTypes/LDIF/README.md
new file mode 100644
index 000000000..3fae966d4
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/README.md
@@ -0,0 +1,41 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » LDIF
+
+This plugin generates data in LDIF (LDAP Data Exchange Format).
+
+
+### Example API Usage
+
+Post the following JSON content to the following API path:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+Here's an example pulled from the Alphanumeric Data Type example:
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "Random Password",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "US Zipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "LDIF"
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/LDIF/bundle.ts b/packages/plugins/src/exportTypes/LDIF/bundle.ts
new file mode 100644
index 000000000..d566d79d8
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/bundle.ts
@@ -0,0 +1,9 @@
+import { ETBundle } from '@generatedata/types';
+import { getCodeMirrorMode, getDownloadFileInfo } from './LDIF';
+
+const bundle: ETBundle = {
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/LDIF/config.ts b/packages/plugins/src/exportTypes/LDIF/config.ts
new file mode 100644
index 000000000..d93c8b120
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'core',
+ codeMirrorModes: ['yaml/yaml']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/ar.json b/packages/plugins/src/exportTypes/LDIF/i18n/ar.json
new file mode 100644
index 000000000..6590ca76a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "سمة LDAP"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/de.json b/packages/plugins/src/exportTypes/LDIF/i18n/de.json
new file mode 100644
index 000000000..e2b683676
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "LDAP Attribute"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/en.json b/packages/plugins/src/exportTypes/LDIF/i18n/en.json
new file mode 100644
index 000000000..e2b683676
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "LDAP Attribute"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/es.json b/packages/plugins/src/exportTypes/LDIF/i18n/es.json
new file mode 100644
index 000000000..c00300530
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "Atributo LDAP"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/fr.json b/packages/plugins/src/exportTypes/LDIF/i18n/fr.json
new file mode 100644
index 000000000..1a88e9a8d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "Attribut LDAP"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/hi.json b/packages/plugins/src/exportTypes/LDIF/i18n/hi.json
new file mode 100644
index 000000000..032b76bda
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "LDAP गुण"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/ja.json b/packages/plugins/src/exportTypes/LDIF/i18n/ja.json
new file mode 100644
index 000000000..eccdce485
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "LDAP属性"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/nl.json b/packages/plugins/src/exportTypes/LDIF/i18n/nl.json
new file mode 100644
index 000000000..bc868b1ba
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "LDAP-kenmerk"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/pt.json b/packages/plugins/src/exportTypes/LDIF/i18n/pt.json
new file mode 100644
index 000000000..c00300530
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "Atributo LDAP"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/ru.json b/packages/plugins/src/exportTypes/LDIF/i18n/ru.json
new file mode 100644
index 000000000..c0296db34
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "Атрибут LDAP"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/ta.json b/packages/plugins/src/exportTypes/LDIF/i18n/ta.json
new file mode 100644
index 000000000..c3b71d80e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "LDAP பண்புக்கூறு"
+}
diff --git a/packages/plugins/src/exportTypes/LDIF/i18n/zh.json b/packages/plugins/src/exportTypes/LDIF/i18n/zh.json
new file mode 100644
index 000000000..eccdce485
--- /dev/null
+++ b/packages/plugins/src/exportTypes/LDIF/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "LDIF",
+ "COL_TITLE": "LDAP属性"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/PHP.generate.ts b/packages/plugins/src/exportTypes/PHP/PHP.generate.ts
new file mode 100644
index 000000000..f483b2682
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/PHP.generate.ts
@@ -0,0 +1,32 @@
+import { ETMessageData } from '../../';
+
+export const generate = ({ stripWhitespace, isFirstBatch, isLastBatch, rows, columns }: ETMessageData): string => {
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (isFirstBatch) {
+ content += ` {
+ const pairs: string[] = [];
+ columns.forEach(({ title }, colIndex) => {
+ const currValue = row[colIndex];
+ pairs.push(`"${title}" => "${currValue}"`);
+ });
+ content += `${tab}[${newline}${tab}${tab}` + pairs.join(`,${newline}${tab}${tab}`) + `${newline}${tab}]`;
+
+ if (isLastBatch && rowIndex == rows.length - 1) {
+ content += newline;
+ } else {
+ content += `,${newline}`;
+ }
+ });
+
+ if (isLastBatch) {
+ content += '];';
+ }
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/PHP/PHP.state.tsx b/packages/plugins/src/exportTypes/PHP/PHP.state.tsx
new file mode 100644
index 000000000..9fe588c8d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/PHP.state.tsx
@@ -0,0 +1,2 @@
+export type GenerationOptionsType = {};
+export const defaultGenerationOptions = {};
diff --git a/packages/plugins/src/exportTypes/PHP/PHP.tsx b/packages/plugins/src/exportTypes/PHP/PHP.tsx
new file mode 100644
index 000000000..799297b22
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/PHP.tsx
@@ -0,0 +1,8 @@
+import { ETDownloadPacket, ETDownloadPacketResponse } from '@generatedata/types';
+
+export const getCodeMirrorMode = (): string => 'text/x-php';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.php`,
+ fileType: ''
+});
diff --git a/packages/plugins/src/exportTypes/PHP/PHP.worker.ts b/packages/plugins/src/exportTypes/PHP/PHP.worker.ts
new file mode 100644
index 000000000..8bc3c7b48
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/PHP.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './PHP.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/PHP/README.md b/packages/plugins/src/exportTypes/PHP/README.md
new file mode 100644
index 000000000..0e9e3e843
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/README.md
@@ -0,0 +1,41 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » PHP
+
+This plugin generates data in LDIF (LDAP Data Exchange Format).
+
+
+### Example API Usage
+
+Post the following JSON content to the following API path:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+Here's an example pulled from the Alphanumeric Data Type example:
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "Random Password",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "US Zipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "LDIF"
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/PHP/bundle.ts b/packages/plugins/src/exportTypes/PHP/bundle.ts
new file mode 100644
index 000000000..6deb7576a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/bundle.ts
@@ -0,0 +1,9 @@
+import { ETBundle } from '@generatedata/types';
+import { getCodeMirrorMode, getDownloadFileInfo } from './PHP';
+
+const bundle: ETBundle = {
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/PHP/config.ts b/packages/plugins/src/exportTypes/PHP/config.ts
new file mode 100644
index 000000000..8f69e3b11
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'programmingLanguage',
+ codeMirrorModes: ['clike/clike', 'php/php']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/ar.json b/packages/plugins/src/exportTypes/PHP/i18n/ar.json
new file mode 100644
index 000000000..d7ff4d365
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "اسم الخاصية"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/de.json b/packages/plugins/src/exportTypes/PHP/i18n/de.json
new file mode 100644
index 000000000..46c20f55f
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "Name des Anwesens"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/en.json b/packages/plugins/src/exportTypes/PHP/i18n/en.json
new file mode 100644
index 000000000..b417a18b4
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "Property Name"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/es.json b/packages/plugins/src/exportTypes/PHP/i18n/es.json
new file mode 100644
index 000000000..bcdddf739
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "Nombre de la propiedad"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/fr.json b/packages/plugins/src/exportTypes/PHP/i18n/fr.json
new file mode 100644
index 000000000..21d906436
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "Nom de la propriété"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/hi.json b/packages/plugins/src/exportTypes/PHP/i18n/hi.json
new file mode 100644
index 000000000..1abe7f74e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "संपत्ति का नाम"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/ja.json b/packages/plugins/src/exportTypes/PHP/i18n/ja.json
new file mode 100644
index 000000000..5db61aebd
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "プロパティ名"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/nl.json b/packages/plugins/src/exportTypes/PHP/i18n/nl.json
new file mode 100644
index 000000000..a673d4a4e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "Eigendomsnaam"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/pt.json b/packages/plugins/src/exportTypes/PHP/i18n/pt.json
new file mode 100644
index 000000000..994682e23
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "Nome da propriedade"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/ru.json b/packages/plugins/src/exportTypes/PHP/i18n/ru.json
new file mode 100644
index 000000000..0e936a995
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "Имя свойства"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/ta.json b/packages/plugins/src/exportTypes/PHP/i18n/ta.json
new file mode 100644
index 000000000..f856a118d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "சொத்தின் பெயர்"
+}
diff --git a/packages/plugins/src/exportTypes/PHP/i18n/zh.json b/packages/plugins/src/exportTypes/PHP/i18n/zh.json
new file mode 100644
index 000000000..c9b2b6a26
--- /dev/null
+++ b/packages/plugins/src/exportTypes/PHP/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "PHP",
+ "COL_TITLE": "物业名称"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/Perl.generate.ts b/packages/plugins/src/exportTypes/Perl/Perl.generate.ts
new file mode 100644
index 000000000..480cf59b3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/Perl.generate.ts
@@ -0,0 +1,36 @@
+import { ETMessageData } from '../../';
+
+export const generate = (data: ETMessageData): string => {
+ const { stripWhitespace } = data;
+
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+ let content = '';
+
+ const { isFirstBatch, isLastBatch, rows, columns } = data;
+ if (isFirstBatch) {
+ content += `@data = (${newline}`;
+ }
+
+ rows.forEach((row: any, rowIndex: number) => {
+ const pairs: string[] = [];
+ columns.forEach(({ title }, colIndex) => {
+ const currValue = row[colIndex];
+ pairs.push(`"${title}" => "${currValue}"`);
+ });
+
+ content += `${tab}{${newline}${tab}${tab}` + pairs.join(`,${newline}${tab}${tab}`) + `${newline}${tab}}`;
+
+ if (isLastBatch && rowIndex == rows.length - 1) {
+ content += newline;
+ } else {
+ content += `,${newline}`;
+ }
+ });
+
+ if (isLastBatch) {
+ content += ');';
+ }
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/Perl/Perl.state.tsx b/packages/plugins/src/exportTypes/Perl/Perl.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/Perl.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/exportTypes/Perl/Perl.tsx b/packages/plugins/src/exportTypes/Perl/Perl.tsx
new file mode 100644
index 000000000..656499d30
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/Perl.tsx
@@ -0,0 +1,8 @@
+import { ETDownloadPacket, ETDownloadPacketResponse } from '@generatedata/types';
+
+export const getCodeMirrorMode = (): string => 'text/x-perl';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.pl`,
+ fileType: ''
+});
diff --git a/packages/plugins/src/exportTypes/Perl/Perl.worker.ts b/packages/plugins/src/exportTypes/Perl/Perl.worker.ts
new file mode 100644
index 000000000..8318ec3a4
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/Perl.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './Perl.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/Perl/README.md b/packages/plugins/src/exportTypes/Perl/README.md
new file mode 100644
index 000000000..5ff4b77d0
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/README.md
@@ -0,0 +1,41 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » Perl
+
+This plugin generates data in LDIF (LDAP Data Exchange Format).
+
+
+### Example API Usage
+
+Post the following JSON content to the following API path:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+Here's an example pulled from the Alphanumeric Data Type example:
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "Random Password",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "US Zipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "LDIF"
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/Perl/bundle.ts b/packages/plugins/src/exportTypes/Perl/bundle.ts
new file mode 100644
index 000000000..a1c1c8037
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/bundle.ts
@@ -0,0 +1,9 @@
+import { ETBundle } from '@generatedata/types';
+import { getCodeMirrorMode, getDownloadFileInfo } from './Perl';
+
+const bundle: ETBundle = {
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/Perl/config.ts b/packages/plugins/src/exportTypes/Perl/config.ts
new file mode 100644
index 000000000..7619b3acc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'programmingLanguage',
+ codeMirrorModes: ['javascript/javascript', 'xml/xml', 'markdown/markdown', 'clike/clike', 'perl/perl', 'php/php', 'ruby/ruby']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/ar.json b/packages/plugins/src/exportTypes/Perl/i18n/ar.json
new file mode 100644
index 000000000..05c3b70bd
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "اسم الخاصية"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/de.json b/packages/plugins/src/exportTypes/Perl/i18n/de.json
new file mode 100644
index 000000000..c9a05da9e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "Name des Anwesens"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/en.json b/packages/plugins/src/exportTypes/Perl/i18n/en.json
new file mode 100644
index 000000000..6a6525a43
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "Property Name"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/es.json b/packages/plugins/src/exportTypes/Perl/i18n/es.json
new file mode 100644
index 000000000..3c579e211
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "Nombre de la propiedad"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/fr.json b/packages/plugins/src/exportTypes/Perl/i18n/fr.json
new file mode 100644
index 000000000..a9db7fcdb
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "Nom de la propriété"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/hi.json b/packages/plugins/src/exportTypes/Perl/i18n/hi.json
new file mode 100644
index 000000000..f6bee6135
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "संपत्ति का नाम"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/ja.json b/packages/plugins/src/exportTypes/Perl/i18n/ja.json
new file mode 100644
index 000000000..4159e55af
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "プロパティ名"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/nl.json b/packages/plugins/src/exportTypes/Perl/i18n/nl.json
new file mode 100644
index 000000000..9f2a8262b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "Eigendomsnaam"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/pt.json b/packages/plugins/src/exportTypes/Perl/i18n/pt.json
new file mode 100644
index 000000000..7f871e35e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "Nome da propriedade"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/ru.json b/packages/plugins/src/exportTypes/Perl/i18n/ru.json
new file mode 100644
index 000000000..d03170dad
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "Имя свойства"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/ta.json b/packages/plugins/src/exportTypes/Perl/i18n/ta.json
new file mode 100644
index 000000000..4e5e4990c
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "சொத்தின் பெயர்"
+}
diff --git a/packages/plugins/src/exportTypes/Perl/i18n/zh.json b/packages/plugins/src/exportTypes/Perl/i18n/zh.json
new file mode 100644
index 000000000..08de76c62
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Perl/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Perl",
+ "COL_TITLE": "物业名称"
+}
diff --git a/packages/plugins/src/exportTypes/Python/Python.generate.ts b/packages/plugins/src/exportTypes/Python/Python.generate.ts
new file mode 100644
index 000000000..a0fa60b6a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/Python.generate.ts
@@ -0,0 +1,32 @@
+import { ETMessageData } from '../../';
+
+export const generate = ({ stripWhitespace, isFirstBatch, isLastBatch, rows, columns }: ETMessageData): string => {
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (isFirstBatch) {
+ content += `data = [${newline}`;
+ }
+
+ rows.forEach((row: any, rowIndex: number) => {
+ const pairs: string[] = [];
+ columns.forEach(({ title }, colIndex) => {
+ const currValue = row[colIndex];
+ pairs.push(`${title}: "${currValue}"`);
+ });
+ content += `${tab}{${newline}${tab}${tab}` + pairs.join(`,${newline}${tab}${tab}`) + `${newline}${tab}}`;
+
+ if (isLastBatch && rowIndex == rows.length - 1) {
+ content += newline;
+ } else {
+ content += `,${newline}`;
+ }
+ });
+
+ if (isLastBatch) {
+ content += ']';
+ }
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/Python/Python.state.tsx b/packages/plugins/src/exportTypes/Python/Python.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/Python.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/exportTypes/Python/Python.tsx b/packages/plugins/src/exportTypes/Python/Python.tsx
new file mode 100644
index 000000000..ed0926624
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/Python.tsx
@@ -0,0 +1,8 @@
+import { ETDownloadPacket, ETDownloadPacketResponse } from '@generatedata/types';
+
+export const getCodeMirrorMode = (): string => 'text/x-python';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.py`,
+ fileType: ''
+});
diff --git a/packages/plugins/src/exportTypes/Python/Python.worker.ts b/packages/plugins/src/exportTypes/Python/Python.worker.ts
new file mode 100644
index 000000000..ff0dbe0e9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/Python.worker.ts
@@ -0,0 +1,7 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './Python.generate';
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/Python/README.md b/packages/plugins/src/exportTypes/Python/README.md
new file mode 100644
index 000000000..108e0228b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/README.md
@@ -0,0 +1,41 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » Python
+
+This plugin generates data in LDIF (LDAP Data Exchange Format).
+
+
+### Example API Usage
+
+Post the following JSON content to the following API path:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+Here's an example pulled from the Alphanumeric Data Type example:
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "Random Password",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "US Zipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "LDIF"
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/Python/bundle.ts b/packages/plugins/src/exportTypes/Python/bundle.ts
new file mode 100644
index 000000000..259e112dc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/bundle.ts
@@ -0,0 +1,9 @@
+import { ETBundle } from '@generatedata/types';
+import { getCodeMirrorMode, getDownloadFileInfo } from './Python';
+
+const bundle: ETBundle = {
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/Python/config.ts b/packages/plugins/src/exportTypes/Python/config.ts
new file mode 100644
index 000000000..752f92325
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'programmingLanguage',
+ codeMirrorModes: ['python/python']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/Python/i18n/ar.json b/packages/plugins/src/exportTypes/Python/i18n/ar.json
new file mode 100644
index 000000000..bc4ecb4ab
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "اسم الخاصية"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/de.json b/packages/plugins/src/exportTypes/Python/i18n/de.json
new file mode 100644
index 000000000..e442e5883
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "Name des Anwesens"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/en.json b/packages/plugins/src/exportTypes/Python/i18n/en.json
new file mode 100644
index 000000000..f8b2e9b42
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "Property Name"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/es.json b/packages/plugins/src/exportTypes/Python/i18n/es.json
new file mode 100644
index 000000000..d0db9abfb
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "Nombre de la propiedad"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/fr.json b/packages/plugins/src/exportTypes/Python/i18n/fr.json
new file mode 100644
index 000000000..87d7868fc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "Nom de la propriété"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/hi.json b/packages/plugins/src/exportTypes/Python/i18n/hi.json
new file mode 100644
index 000000000..fe4472874
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "संपत्ति का नाम"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/ja.json b/packages/plugins/src/exportTypes/Python/i18n/ja.json
new file mode 100644
index 000000000..ad78b54a7
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "プロパティ名"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/nl.json b/packages/plugins/src/exportTypes/Python/i18n/nl.json
new file mode 100644
index 000000000..124ea598d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "Eigendomsnaam"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/pt.json b/packages/plugins/src/exportTypes/Python/i18n/pt.json
new file mode 100644
index 000000000..b91aee157
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "Nome da propriedade"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/ru.json b/packages/plugins/src/exportTypes/Python/i18n/ru.json
new file mode 100644
index 000000000..d934182e1
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "Имя свойства"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/ta.json b/packages/plugins/src/exportTypes/Python/i18n/ta.json
new file mode 100644
index 000000000..5d80f790d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "சொத்தின் பெயர்"
+}
diff --git a/packages/plugins/src/exportTypes/Python/i18n/zh.json b/packages/plugins/src/exportTypes/Python/i18n/zh.json
new file mode 100644
index 000000000..8c1db608c
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Python/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Python",
+ "COL_TITLE": "物业名称"
+}
diff --git a/packages/plugins/src/exportTypes/README.md b/packages/plugins/src/exportTypes/README.md
new file mode 100644
index 000000000..67671f9ed
--- /dev/null
+++ b/packages/plugins/src/exportTypes/README.md
@@ -0,0 +1,19 @@
+Each plugin has its own unique configuration settings. See the pages below for details and explanations of each plugin and
+what the options do.
+
+### Export Types
+
+- CSharp
+- CSV
+- HTML
+- Javascript
+- JSON
+- LDIF
+- Perl
+- PHP
+- Python
+- Ruby
+- SQL
+- Typescript
+- XML
+
diff --git a/packages/plugins/src/exportTypes/Ruby/README.md b/packages/plugins/src/exportTypes/Ruby/README.md
new file mode 100644
index 000000000..efbde9625
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/README.md
@@ -0,0 +1,41 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » Ruby
+
+This plugin generates data in LDIF (LDAP Data Exchange Format).
+
+
+### Example API Usage
+
+Post the following JSON content to the following API path:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+Here's an example pulled from the Alphanumeric Data Type example:
+
+```javascript
+{
+ "numRows": 10,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "Random Password",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "US Zipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "LDIF"
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/Ruby/Ruby.generate.ts b/packages/plugins/src/exportTypes/Ruby/Ruby.generate.ts
new file mode 100644
index 000000000..e8b71b249
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/Ruby.generate.ts
@@ -0,0 +1,32 @@
+import { ETMessageData } from '../../';
+
+export const generate = ({ stripWhitespace, rows, columns, isLastBatch, isFirstBatch }: ETMessageData): string => {
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (isFirstBatch) {
+ content += `data = [${newline}`;
+ }
+
+ rows.forEach((row: any, rowIndex: number) => {
+ const pairs: string[] = [];
+ columns.forEach(({ title }, colIndex) => {
+ const currValue = row[colIndex];
+ pairs.push(`'${title}': '${currValue}'`);
+ });
+ content += `${tab}{${newline}${tab}${tab}` + pairs.join(`,${newline}${tab}${tab}`) + `${newline}${tab}}`;
+
+ if (isLastBatch && rowIndex == rows.length - 1) {
+ content += newline;
+ } else {
+ content += `,${newline}`;
+ }
+ });
+
+ if (isLastBatch) {
+ content += '];';
+ }
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/Ruby/Ruby.state.tsx b/packages/plugins/src/exportTypes/Ruby/Ruby.state.tsx
new file mode 100644
index 000000000..e2d9ec4d2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/Ruby.state.tsx
@@ -0,0 +1,2 @@
+export const defaultGenerationOptions = {};
+export type GenerationOptionsType = {};
diff --git a/packages/plugins/src/exportTypes/Ruby/Ruby.tsx b/packages/plugins/src/exportTypes/Ruby/Ruby.tsx
new file mode 100644
index 000000000..dff5b8ea2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/Ruby.tsx
@@ -0,0 +1,8 @@
+import { ETDownloadPacket, ETDownloadPacketResponse } from '@generatedata/types';
+
+export const getCodeMirrorMode = (): string => 'text/x-ruby';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.pl`,
+ fileType: ''
+});
diff --git a/packages/plugins/src/exportTypes/Ruby/Ruby.worker.ts b/packages/plugins/src/exportTypes/Ruby/Ruby.worker.ts
new file mode 100644
index 000000000..016244ca9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/Ruby.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './Ruby.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage): void => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/Ruby/bundle.ts b/packages/plugins/src/exportTypes/Ruby/bundle.ts
new file mode 100644
index 000000000..32f5b583b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/bundle.ts
@@ -0,0 +1,9 @@
+import { ETBundle } from '@generatedata/types';
+import { getCodeMirrorMode, getDownloadFileInfo } from './Ruby';
+
+const bundle: ETBundle = {
+ getCodeMirrorMode,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/Ruby/config.ts b/packages/plugins/src/exportTypes/Ruby/config.ts
new file mode 100644
index 000000000..7d028815a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'programmingLanguage',
+ codeMirrorModes: ['clike/clike', 'ruby/ruby']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/ar.json b/packages/plugins/src/exportTypes/Ruby/i18n/ar.json
new file mode 100644
index 000000000..2beed3b70
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/ar.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "اسم الخاصية"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/de.json b/packages/plugins/src/exportTypes/Ruby/i18n/de.json
new file mode 100644
index 000000000..3f643bb09
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/de.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "Name des Anwesens"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/en.json b/packages/plugins/src/exportTypes/Ruby/i18n/en.json
new file mode 100644
index 000000000..c994a4b97
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/en.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "Property Name"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/es.json b/packages/plugins/src/exportTypes/Ruby/i18n/es.json
new file mode 100644
index 000000000..f073d2e58
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/es.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "Nombre de la propiedad"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/fr.json b/packages/plugins/src/exportTypes/Ruby/i18n/fr.json
new file mode 100644
index 000000000..f06a6cea9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/fr.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "Nom de la propriété"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/hi.json b/packages/plugins/src/exportTypes/Ruby/i18n/hi.json
new file mode 100644
index 000000000..56b04f4bb
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/hi.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "संपत्ति का नाम"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/ja.json b/packages/plugins/src/exportTypes/Ruby/i18n/ja.json
new file mode 100644
index 000000000..f32a41763
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/ja.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "プロパティ名"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/nl.json b/packages/plugins/src/exportTypes/Ruby/i18n/nl.json
new file mode 100644
index 000000000..aa3c344e2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/nl.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "Eigendomsnaam"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/pt.json b/packages/plugins/src/exportTypes/Ruby/i18n/pt.json
new file mode 100644
index 000000000..284cf8ee7
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/pt.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "Nome da propriedade"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/ru.json b/packages/plugins/src/exportTypes/Ruby/i18n/ru.json
new file mode 100644
index 000000000..40cea9c7a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/ru.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "Имя свойства"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/ta.json b/packages/plugins/src/exportTypes/Ruby/i18n/ta.json
new file mode 100644
index 000000000..f71672d17
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/ta.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "சொத்தின் பெயர்"
+}
diff --git a/packages/plugins/src/exportTypes/Ruby/i18n/zh.json b/packages/plugins/src/exportTypes/Ruby/i18n/zh.json
new file mode 100644
index 000000000..933b2227f
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Ruby/i18n/zh.json
@@ -0,0 +1,4 @@
+{
+ "EXPORT_TYPE_NAME": "Ruby",
+ "COL_TITLE": "物业名称"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/README.md b/packages/plugins/src/exportTypes/SQL/README.md
new file mode 100644
index 000000000..574573536
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/README.md
@@ -0,0 +1,75 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » SQL
+
+
+This plugin is probably the most configurable Export Type currently available. It generates the random data in SQL
+format for use in populating database such as MySQL, Oracle, SQLite and more. It offers a range of controls
+to generate exactly what you want: table names, database types, query type (INSERT/UPDATE), INSERT IGNORE, Primary Keys
+and others. Pretty cool.
+
+Using the GenerateData interface you can just select whatever options you want using your mouse & keyboard,
+but if you're using the API, these are the settings you'll be interested in.
+
+- `tableName`: the string name of the database table to insert/update the data. *required*
+- `databaseType`: a string. One of: `MySQL`, `Postgres`, `SQLite`, `Oracle`, `MSSQL`. *required*
+- `createTable`: a boolean, defaults to `true`. If this is set to true, the generated output will contain a
+`CREATE TABLE` query at the start.
+- `dropTable`: a boolean, defaults to `false`. If this is set to true, the first generated query will be a safe
+DROP TABLE to ensure the table doesn't already exist prior to the INSERT queries.
+- `encloseWithBackquotes`: a boolean, defaults to `false`. Whether column and table names will be enclosed in backquotes.
+- `statementType`: a string, one of: `insert`, `insertignore`, `update`. This governs the type of query that'll be
+generated. By and large you'll want this to be set to `insert`, the default value.
+- `insertBatchSize`: an integer. This allows you to reduce the number of queries by grouping them, e.g. a single
+INSERT statement to insert 10 rows at once. Defaults to 1.
+- `addPrimaryKey`: a boolean, defaults to `false`. This lets you choose to add a Primary Key field to your SQL
+statements.
+
+
+#### Conditional settings
+
+Some settings are database specific.
+
+- Postgres will ignore the `encloseWithBackquotes` setting.
+- Postgres, SQLite, Oracle and MSSQL won't accept the `insertignore` as a value for the `statementType` setting. That
+only works with MySQL.
+
+
+### Example API Usage
+
+Post the following JSON content to the following API path:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+```javascript
+{
+ "numRows": 15,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "Random Password",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "US Zipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "SQL",
+ "settings": {
+ "tableName": "myTable",
+ "databaseType": "MySQL",
+ "encloseWithBackquotes": true,
+ "createTable": true
+ }
+ }
+}
+```
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/SQL/SQL.generate.ts b/packages/plugins/src/exportTypes/SQL/SQL.generate.ts
new file mode 100644
index 000000000..0b7afbb57
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/SQL.generate.ts
@@ -0,0 +1,471 @@
+import { ETMessageData } from '../../';
+import { ColumnData } from '~types/general';
+import { SQLSettings } from './SQL.state';
+
+export const generate = (data: ETMessageData): string => {
+ let content = '';
+ if (data.settings.databaseType === 'MySQL') {
+ content = generateMySQL(data);
+ } else if (data.settings.databaseType === 'Postgres') {
+ content = generatePostgres(data);
+ } else if (data.settings.databaseType === 'SQLite') {
+ content = generateSQLite(data);
+ } else if (data.settings.databaseType === 'Oracle') {
+ content = generateOracle(data);
+ } else if (data.settings.databaseType === 'MSSQL') {
+ content = generateMSSQL(data);
+ }
+
+ return content;
+};
+
+export const enum QuoteType {
+ single = "'",
+ double = '"'
+}
+
+const getCurrentRow = (batch: number, batchSize: number, rowIndex: number) => {
+ return batchSize * (batch - 1) + rowIndex + 1;
+};
+
+const getWrappedValue = (value: any, colIndex: number, numericFieldIndexes: number[], quote: QuoteType = QuoteType.double): any => {
+ let val = '';
+ if (numericFieldIndexes.indexOf(colIndex) !== -1) {
+ val = value;
+ } else {
+ if (quote === QuoteType.double) {
+ if (value.toString().indexOf(QuoteType.double) !== -1) {
+ value = value.replaceAll(QuoteType.double, `${QuoteType.double}${QuoteType.double}`);
+ }
+ } else {
+ if (value.toString().indexOf(QuoteType.single) !== -1) {
+ value = value.replaceAll(QuoteType.single, `${QuoteType.single}${QuoteType.single}`);
+ }
+ }
+ val = `${quote}${value}${quote}`;
+ }
+ return val;
+};
+
+export const generateMySQL = (data: ETMessageData): string => {
+ const sqlSettings: SQLSettings = data.settings;
+ const { isFirstBatch, currentBatch, batchSize, columns, rows } = data;
+ const backquote = sqlSettings.encloseInBackQuotes ? '`' : '';
+ const colTitles = columns.map(({ title }) => title);
+ let content = '';
+
+ const numericFieldIndexes = getNumericFieldColumnIndexes(data.columns);
+
+ if (isFirstBatch) {
+ if (sqlSettings.dropTable) {
+ content += `DROP TABLE IF EXISTS ${backquote}${sqlSettings.tableName}${backquote};\n\n`;
+ }
+ if (sqlSettings.createTable) {
+ content += `CREATE TABLE ${backquote}${sqlSettings.tableName}${backquote} (\n`;
+ if (sqlSettings.addPrimaryKey) {
+ content += ` ${backquote}id${backquote} mediumint(8) unsigned NOT NULL auto_increment,\n`;
+ }
+ const cols: any[] = [];
+ columns.forEach(({ title, dataType, metadata }) => {
+ let columnTypeInfo = 'MEDIUMTEXT';
+ if (metadata && metadata.sql) {
+ if (metadata.sql.field_MySQL) {
+ columnTypeInfo = metadata.sql.field_MySQL;
+ } else if (metadata.sql.field) {
+ columnTypeInfo = metadata.sql.field;
+ }
+ }
+ cols.push(` ${backquote}${title}${backquote} ${columnTypeInfo}`);
+ });
+
+ content += cols.join(',\n');
+ if (sqlSettings.addPrimaryKey) {
+ content += `,\n PRIMARY KEY (${backquote}id${backquote})\n) AUTO_INCREMENT=1;\n\n`;
+ } else {
+ content += '\n);\n\n';
+ }
+ }
+ }
+
+ let colNamesStr = '';
+ if (sqlSettings.encloseInBackQuotes) {
+ colNamesStr = `\`${colTitles.join('`,`')}\``;
+ } else {
+ colNamesStr = colTitles.join(',');
+ }
+
+ let rowDataStr: string[] = [];
+ rows.forEach((row: any, rowIndex: number) => {
+ if (sqlSettings.statementType === 'insert') {
+ const displayVals: any = [];
+ colTitles.forEach((columnTitle: string, colIndex: number) => {
+ displayVals.push(getWrappedValue(row[colIndex], colIndex, numericFieldIndexes));
+ });
+ rowDataStr.push(displayVals.join(','));
+ if (rowDataStr.length === sqlSettings.insertBatchSize) {
+ content += `INSERT INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ rowDataStr = [];
+ }
+ } else if (sqlSettings.statementType === 'insertIgnore') {
+ const displayVals: any = [];
+ colTitles.forEach((columnTitle: string, colIndex: number) => {
+ displayVals.push(getWrappedValue(row[colIndex], colIndex, numericFieldIndexes));
+ });
+ rowDataStr.push(displayVals.join(','));
+ if (rowDataStr.length === sqlSettings.insertBatchSize) {
+ content += `INSERT IGNORE INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ rowDataStr = [];
+ }
+ } else {
+ const pairs: string[] = [];
+ colTitles.forEach((title: string, colIndex: number) => {
+ const colValue = getWrappedValue(row[colIndex], colIndex, numericFieldIndexes);
+ pairs.push(`${backquote}${title}${backquote} = ${colValue}`);
+ });
+ const pairsStr = pairs.join(', ');
+ const id = getCurrentRow(currentBatch, batchSize, rowIndex);
+ content += `UPDATE ${backquote}${sqlSettings.tableName}${backquote} SET ${pairsStr} WHERE ${backquote}id${backquote} = ${id};\n`;
+ }
+ });
+
+ if (rowDataStr.length) {
+ if (sqlSettings.statementType === 'insert') {
+ content += `INSERT INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ } else if (sqlSettings.statementType === 'insertIgnore') {
+ content += `INSERT IGNORE INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ }
+ }
+
+ return content;
+};
+
+export const generatePostgres = (generationData: ETMessageData): string => {
+ const sqlSettings: SQLSettings = generationData.settings;
+ const colTitles = generationData.columns.map(({ title }) => title);
+ let content = '';
+
+ const numericFieldIndexes = getNumericFieldColumnIndexes(generationData.columns);
+
+ if (generationData.isFirstBatch) {
+ if (sqlSettings.dropTable) {
+ content += `DROP TABLE IF EXISTS "${sqlSettings.tableName}";\n\n`;
+ }
+ if (sqlSettings.createTable) {
+ content += `CREATE TABLE "${sqlSettings.tableName}" (\n`;
+
+ if (sqlSettings.addPrimaryKey) {
+ content += ' id SERIAL PRIMARY KEY,\n';
+ }
+ const cols: any[] = [];
+ generationData.columns.forEach(({ title, dataType, metadata }) => {
+ let columnTypeInfo = 'MEDIUMTEXT';
+ if (metadata) {
+ if (metadata.sql && metadata.sql.field_Postgres) {
+ columnTypeInfo = metadata.sql.field_Postgres;
+ } else if (metadata.sql && metadata.sql.field) {
+ columnTypeInfo = metadata.sql.field;
+ }
+ }
+ cols.push(` ${title} ${columnTypeInfo}`);
+ });
+
+ content += cols.join(',\n');
+ content += '\n);\n\n';
+ }
+ }
+
+ const colNamesStr = colTitles.join(',');
+
+ let rowDataStr: string[] = [];
+ generationData.rows.forEach((row: any, rowIndex: number) => {
+ if (sqlSettings.statementType === 'insert') {
+ const displayVals: any = [];
+ colTitles.forEach((columnTitle: string, colIndex: number) => {
+ displayVals.push(getWrappedValue(row[colIndex], colIndex, numericFieldIndexes, QuoteType.single));
+ });
+ rowDataStr.push(displayVals.join(','));
+ if (rowDataStr.length === sqlSettings.insertBatchSize) {
+ content += `INSERT INTO ${sqlSettings.tableName} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ rowDataStr = [];
+ }
+ } else {
+ const pairs: string[] = [];
+ colTitles.forEach((title: string, colIndex: number) => {
+ const colValue = getWrappedValue(row[colIndex], colIndex, numericFieldIndexes);
+ pairs.push(`${title} = ${colValue}`);
+ });
+ const pairsStr = pairs.join(', ');
+ const id = getCurrentRow(generationData.currentBatch, generationData.batchSize, rowIndex);
+ content += `UPDATE ${sqlSettings.tableName} SET ${pairsStr} WHERE id = ${id};\n`;
+ }
+ });
+
+ if (rowDataStr.length && sqlSettings.statementType === 'insert') {
+ content += `INSERT INTO ${sqlSettings.tableName} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ }
+
+ return content;
+};
+
+export const generateSQLite = (generationData: ETMessageData): string => {
+ const sqlSettings: SQLSettings = generationData.settings;
+ const backquote = sqlSettings.encloseInBackQuotes ? '`' : '';
+ const colTitles = generationData.columns.map(({ title }) => title);
+ let content = '';
+
+ const numericFieldIndexes = getNumericFieldColumnIndexes(generationData.columns);
+
+ if (generationData.isFirstBatch) {
+ if (sqlSettings.dropTable) {
+ content += `DROP TABLE IF EXISTS ${backquote}${sqlSettings.tableName}${backquote};\n\n`;
+ }
+ if (sqlSettings.createTable) {
+ content += `CREATE TABLE ${backquote}${sqlSettings.tableName}${backquote} (\n`;
+ if (sqlSettings.addPrimaryKey) {
+ content += ` ${backquote}id${backquote} number primary key,\n`;
+ }
+ const cols: any[] = [];
+
+ generationData.columns.forEach(({ title, dataType, metadata }) => {
+ let columnTypeInfo = 'MEDIUMTEXT';
+
+ // figure out the content type. Default to MEDIUMTEXT, then use the specific SQLField_MySQL, then the SQLField
+ if (metadata && metadata.sql) {
+ if (metadata.sql.field_SQLite) {
+ columnTypeInfo = metadata.sql.field_SQLite;
+ } else if (metadata.sql.field) {
+ columnTypeInfo = metadata.sql.field;
+ }
+ }
+ cols.push(` ${backquote}${title}${backquote} ${columnTypeInfo}`);
+ });
+
+ content += cols.join(',\n');
+ if (sqlSettings.addPrimaryKey) {
+ content += `,\n PRIMARY KEY (${backquote}id${backquote})\n) AUTO_INCREMENT=1;\n\n`;
+ } else {
+ content += '\n);\n\n';
+ }
+ }
+ }
+
+ let colNamesStr = '';
+ if (sqlSettings.encloseInBackQuotes) {
+ colNamesStr = `\`${colTitles.join('`,`')}\``;
+ } else {
+ colNamesStr = colTitles.join(',');
+ }
+
+ let rowDataStr: string[] = [];
+ generationData.rows.forEach((row: any, rowIndex: number) => {
+ if (sqlSettings.statementType === 'insert') {
+ const displayVals: any = [];
+ colTitles.forEach((columnTitle: string, colIndex: number) => {
+ displayVals.push(getWrappedValue(row[colIndex], colIndex, numericFieldIndexes));
+ });
+ rowDataStr.push(displayVals.join(','));
+ if (rowDataStr.length === sqlSettings.insertBatchSize) {
+ content += `INSERT INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ rowDataStr = [];
+ }
+ } else {
+ const pairs: string[] = [];
+ colTitles.forEach((title: string, colIndex: number) => {
+ const colValue = getWrappedValue(row[colIndex], colIndex, numericFieldIndexes);
+ pairs.push(`${backquote}${title}${backquote} = ${colValue}`);
+ });
+ const pairsStr = pairs.join(', ');
+ const id = getCurrentRow(generationData.currentBatch, generationData.batchSize, rowIndex);
+ content += `UPDATE ${backquote}${sqlSettings.tableName}${backquote} SET ${pairsStr} WHERE ${backquote}id${backquote} = ${id};\n`;
+ }
+ });
+
+ if (rowDataStr.length && sqlSettings.statementType === 'insert') {
+ content += `INSERT INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ }
+
+ return content;
+};
+
+export const generateOracle = (generationData: ETMessageData): string => {
+ const sqlSettings: SQLSettings = generationData.settings;
+ const backquote = sqlSettings.encloseInBackQuotes ? '`' : '';
+ const colTitles = generationData.columns.map(({ title }) => title);
+ let content = '';
+
+ const numericFieldIndexes = getNumericFieldColumnIndexes(generationData.columns);
+
+ if (generationData.isFirstBatch) {
+ if (sqlSettings.dropTable) {
+ content += `DROP TABLE ${backquote}${sqlSettings.tableName}${backquote};\n\n`;
+ }
+ if (sqlSettings.createTable) {
+ content += `CREATE TABLE ${backquote}${sqlSettings.tableName}${backquote} (\n`;
+ if (sqlSettings.addPrimaryKey) {
+ content += ` ${backquote}id${backquote} number primary key,\n`;
+ }
+
+ const cols: any[] = [];
+ generationData.columns.forEach(({ title, dataType, metadata }) => {
+ let columnTypeInfo = 'MEDIUMTEXT';
+ if (metadata && metadata.sql) {
+ if (metadata.sql.field_Oracle) {
+ columnTypeInfo = metadata.sql.field_Oracle;
+ } else if (metadata.sql.field) {
+ columnTypeInfo = metadata.sql.field;
+ }
+ }
+ cols.push(` ${backquote}${title}${backquote} ${columnTypeInfo}`);
+ });
+
+ content += cols.join(',\n');
+ if (sqlSettings.addPrimaryKey) {
+ content += `,\n PRIMARY KEY (${backquote}id${backquote})\n) AUTO_INCREMENT=1;\n\n`;
+ } else {
+ content += '\n);\n\n';
+ }
+ }
+ }
+
+ let colNamesStr = '';
+ if (sqlSettings.encloseInBackQuotes) {
+ colNamesStr = `\`${colTitles.join('`,`')}\``;
+ } else {
+ colNamesStr = colTitles.join(',');
+ }
+
+ const rowDataStr: string[] = [];
+ generationData.rows.forEach((row: any, rowIndex: number) => {
+ if (sqlSettings.statementType === 'insert') {
+ const displayVals: any = [];
+ colTitles.forEach((columnTitle: string, colIndex: number) => {
+ displayVals.push(getWrappedValue(row[colIndex], colIndex, numericFieldIndexes));
+ });
+ const rowDataStr = displayVals.join(',');
+ content += `INSERT INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES (${rowDataStr});\n`;
+ } else {
+ const pairs: string[] = [];
+ colTitles.forEach((title: string, colIndex: number) => {
+ const colValue = getWrappedValue(row[colIndex], colIndex, numericFieldIndexes);
+ pairs.push(`${backquote}${title}${backquote} = ${colValue}`);
+ });
+ const pairsStr = pairs.join(', ');
+ const id = getCurrentRow(generationData.currentBatch, generationData.batchSize, rowIndex);
+ content += `UPDATE ${backquote}${sqlSettings.tableName}${backquote} SET ${pairsStr} WHERE ${backquote}id${backquote} = ${id};\n`;
+ }
+ });
+
+ if (rowDataStr.length && sqlSettings.statementType === 'insert') {
+ content += `INSERT INTO ${backquote}${sqlSettings.tableName}${backquote} (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ }
+
+ return content;
+};
+
+export const generateMSSQL = (generationData: ETMessageData): string => {
+ const sqlSettings: SQLSettings = generationData.settings;
+ const colTitles = generationData.columns.map(({ title }) => title);
+ const quote = sqlSettings.quotes === 'single' ? QuoteType.single : QuoteType.double;
+ let content = '';
+
+ const numericFieldIndexes = getNumericFieldColumnIndexes(generationData.columns);
+
+ if (generationData.isFirstBatch) {
+ if (sqlSettings.dropTable) {
+ content += `IF EXISTS(SELECT 1 FROM sys.tables WHERE object_id = OBJECT_ID(${quote}${sqlSettings.tableName}${quote}))\n`;
+ content += 'BEGIN;\n';
+ content += ` DROP TABLE [${sqlSettings.tableName}];\n`;
+ content += 'END;\n';
+ content += 'GO\n\n';
+ }
+
+ if (sqlSettings.createTable) {
+ content += `CREATE TABLE [${sqlSettings.tableName}] (\n`;
+ if (sqlSettings.addPrimaryKey) {
+ content += ` [${sqlSettings.tableName}ID] INTEGER NOT NULL IDENTITY(1, 1),\n`;
+ }
+
+ const cols: any[] = [];
+ generationData.columns.forEach(({ title, dataType, metadata }) => {
+ let columnTypeInfo = 'MEDIUMTEXT';
+ if (metadata && metadata.sql) {
+ if (metadata.sql.field_MSSQL) {
+ columnTypeInfo = metadata.sql.field_MSSQL;
+ } else if (metadata.sql.field) {
+ columnTypeInfo = metadata.sql.field;
+ }
+ }
+ cols.push(` [${title}] ${columnTypeInfo}`);
+ });
+
+ content += cols.join(',\n');
+ if (sqlSettings.addPrimaryKey) {
+ content += `,\n PRIMARY KEY ([${sqlSettings.tableName}ID])\n);\nGO\n\n`;
+ } else {
+ content += '\n);\nGO\n\n';
+ }
+ }
+ }
+
+ const colNamesStr = colTitles.join(',');
+
+ let rowDataStr: string[] = [];
+ generationData.rows.forEach((row: any, rowIndex: number) => {
+ if (sqlSettings.statementType === 'insert') {
+ const displayVals: any = [];
+ colTitles.forEach((columnTitle: string, colIndex: number) => {
+ displayVals.push(getWrappedValue(row[colIndex], colIndex, numericFieldIndexes, quote));
+ });
+ rowDataStr.push(displayVals.join(','));
+ if (rowDataStr.length === sqlSettings.insertBatchSize) {
+ content += `INSERT INTO [${sqlSettings.tableName}] (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ rowDataStr = [];
+ }
+
+ // TODO - need the current row
+ // if (($currentRow % 1000) == 0) {
+ // $content .= $endLineChar;
+ // $content .= "PRINT 'Row {$currentRow} inserted';$endLineChar";
+ // $content .= "GO";
+ // $content .= $endLineChar;
+ // }
+ } else {
+ const pairs: string[] = [];
+ colTitles.forEach((title: string, colIndex: number) => {
+ const colValue = getWrappedValue(row[colIndex], colIndex, numericFieldIndexes);
+ pairs.push(`[${title}] = ${colValue}`);
+ });
+ const pairsStr = pairs.join(', ');
+ const id = getCurrentRow(generationData.currentBatch, generationData.batchSize, rowIndex);
+ content += `UPDATE [${sqlSettings.tableName}] SET ${pairsStr} WHERE [{$this->tableName}ID] = ${id};\n`;
+
+ // if (($currentRow % 1000) == 0) {
+ // $content .= $endLineChar;
+ // $content .= "PRINT 'Row {$currentRow} updated';$endLineChar";
+ // $content .= "GO";
+ // $content .= $endLineChar;
+ // }
+ }
+ });
+
+ if (rowDataStr.length && sqlSettings.statementType === 'insert') {
+ content += `INSERT INTO [${sqlSettings.tableName}] (${colNamesStr})\nVALUES\n (${rowDataStr.join('),\n (')});\n`;
+ }
+
+ return content;
+};
+
+export const getNumericFieldColumnIndexes = (columns: ColumnData[]): number[] => {
+ const numericFieldColIndexes: number[] = [];
+
+ columns.forEach((col: ColumnData, colIndex: number) => {
+ const { metadata } = col;
+ const dataType = metadata.general && metadata.general.dataType;
+
+ if (dataType === 'number') {
+ numericFieldColIndexes.push(colIndex);
+ }
+ });
+
+ return numericFieldColIndexes;
+};
diff --git a/packages/plugins/src/exportTypes/SQL/SQL.scss b/packages/plugins/src/exportTypes/SQL/SQL.scss
new file mode 100644
index 000000000..c517b9615
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/SQL.scss
@@ -0,0 +1,65 @@
+.row {
+ display: flex;
+ margin: 12px 0 14px;
+ &>div {
+ flex: 1;
+
+ &>label {
+ height: 20px;
+ color: #555555;
+ display: block;
+ }
+ }
+ ul {
+ list-style: none;
+ padding: 0;
+ margin: 0 20px 0 0;
+ line-height: 30px;
+ li {
+ display: flex;
+ align-items: center;
+ input {
+ margin: 0 6px 0 0;
+ }
+ }
+ }
+}
+
+.batchSize {
+ display: flex;
+ align-items: center;
+ height: 31px;
+
+ &.withBrace {
+ height: 58px;
+ }
+
+ > span {
+ height: 38px;
+ margin-right: 4px;
+ }
+ > label {
+ margin: 0 4px 0 5px;
+ }
+ input {
+ margin-top: -2px;
+ }
+}
+
+.batchSizeHyphen {
+ color: #cccccc;
+}
+
+.brace {
+ font-size: 50px;
+ color: #dddddd;
+ margin-top: -40px;
+}
+
+.block {
+ margin-bottom: 10px;
+ .title {
+ margin: 0 0 8px;
+ color: #555555;
+ }
+}
diff --git a/packages/plugins/src/exportTypes/SQL/SQL.scss.d.ts b/packages/plugins/src/exportTypes/SQL/SQL.scss.d.ts
new file mode 100644
index 000000000..0fea05895
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/SQL.scss.d.ts
@@ -0,0 +1,18 @@
+declare namespace SqlScssNamespace {
+ export interface ISqlScss {
+ batchSize: string;
+ batchSizeHyphen: string;
+ block: string;
+ brace: string;
+ row: string;
+ title: string;
+ withBrace: string;
+ }
+}
+
+declare const SqlScssModule: SqlScssNamespace.ISqlScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: SqlScssNamespace.ISqlScss;
+};
+
+export = SqlScssModule;
diff --git a/packages/plugins/src/exportTypes/SQL/SQL.state.tsx b/packages/plugins/src/exportTypes/SQL/SQL.state.tsx
new file mode 100644
index 000000000..068a29ef8
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/SQL.state.tsx
@@ -0,0 +1,32 @@
+import { ETState } from '@generatedata/types';
+
+export type GenerationOptionsType = {
+ tableName: string;
+ databaseType: 'MySQL' | 'Postgres' | 'SQLite' | 'Oracle' | 'MSSQL';
+ createTable: boolean;
+ dropTable: boolean;
+ encloseInBackQuotes: boolean;
+ statementType: 'insert' | 'insertIgnore' | 'update';
+ insertBatchSize: number;
+ addPrimaryKey: boolean;
+ quotes: 'single' | 'double';
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ tableName: 'myTable',
+ databaseType: 'MySQL',
+ createTable: true,
+ dropTable: true,
+ encloseInBackQuotes: true,
+ statementType: 'insert',
+ insertBatchSize: 10,
+ addPrimaryKey: true,
+ quotes: 'single'
+};
+
+export interface SQLSettings extends ETState, GenerationOptionsType {}
+
+export const initialState: SQLSettings = {
+ ...defaultGenerationOptions,
+ isValid: true
+};
diff --git a/packages/plugins/src/exportTypes/SQL/SQL.tsx b/packages/plugins/src/exportTypes/SQL/SQL.tsx
new file mode 100644
index 000000000..9c052fa0d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/SQL.tsx
@@ -0,0 +1,293 @@
+import * as React from 'react';
+import Switch from '@mui/material/Switch';
+import Dropdown from '~components/dropdown/Dropdown';
+import TextField from '~components/TextField';
+import { SQLSettings } from './SQL.state';
+import { ETDownloadPacket, ETDownloadPacketResponse, ETSettings } from '@generatedata/types';
+import styles from './SQL.scss';
+
+export const Settings = ({ coreI18n, i18n, onUpdate, id, data }: ETSettings): any => {
+ const onChange = (field: string, value: any): void => {
+ onUpdate({
+ ...data,
+ [field]: value
+ });
+ };
+
+ const options = [
+ { value: 'MySQL', label: 'MySQL' },
+ { value: 'Postgres', label: 'Postgres' },
+ { value: 'SQLite', label: 'SQLite' },
+ { value: 'Oracle', label: 'Oracle' },
+ { value: 'MSSQL', label: 'MSSQL' }
+ ];
+
+ const getBackQuotesOption = (): React.ReactNode => {
+ if (data.databaseType === 'Postgres' || data.databaseType === 'MSSQL') {
+ return null;
+ }
+
+ return (
+
+ onChange('encloseInBackQuotes', !data.encloseInBackQuotes)}
+ />
+ {i18n.encloseTableBackquotes}
+
+ );
+ };
+
+ const getInsertIgnoreOption = (): React.ReactNode => {
+ if (data.databaseType !== 'MySQL') {
+ return null;
+ }
+ return (
+
+ onChange('statementType', 'insertIgnore')}
+ checked={data.statementType === 'insertIgnore'}
+ />
+ INSERT IGNORE
+
+ );
+ };
+
+ const getInsertBatchSize = (): React.ReactNode => {
+ if (data.databaseType === 'Oracle') {
+ return null;
+ }
+
+ let classes = styles.batchSize;
+ let brace = null;
+ let label = (
+
+ — {i18n.batchSize}
+
+ );
+ if (data.databaseType === 'MySQL') {
+ classes += ` ${styles.withBrace}`;
+ brace = } ;
+ label = i18n.batchSize;
+ }
+
+ return (
+
+ {brace}
+
+ {label}
+
+ onChange('insertBatchSize', parseInt(e.target.value, 10))}
+ />
+
+ );
+ };
+
+ const getQuotes = (): React.ReactNode => {
+ if (data.databaseType !== 'MSSQL') {
+ return null;
+ }
+
+ return (
+
+ );
+ };
+
+ // TODO - add title for TextField above... title={i18n.batchSizeDesc}
+
+ // TODO need better validation on the database table name
+
+ return (
+ <>
+
+
+
+
{i18n.dbTableName}
+
+ onChange('tableName', e.target.value)}
+ />
+
+
+
+
{i18n.dbType}
+
+ onChange('databaseType', i.value)}
+ />
+
+
+
+
+
+
+ {i18n.miscOptions}
+
+
+
+ onChange('dropTable', !data.dropTable)}
+ />
+ {i18n.includeDropTableQuery}
+
+
+ onChange('createTable', !data.createTable)}
+ />
+ {i18n.includeCreateTableQuery}
+
+ {getBackQuotesOption()}
+
+ onChange('addPrimaryKey', !data.addPrimaryKey)}
+ />
+ {i18n.addDefaultAutoIncrementPrimaryKey}
+
+
+
+
+
+
+
+ {i18n.statementType}
+
+
+
+
+
+ {getInsertBatchSize()}
+
+
+
+
+ {getQuotes()}
+ >
+ );
+};
+
+export const getCodeMirrorMode = (): string => 'text/x-sql';
+
+export const getExportTypeLabel = (data: SQLSettings): string => data.databaseType;
+
+export const validateTitleField = (title: string, i18n: any, settings: SQLSettings): null | string => {
+ // as noted in issues/262, SQL Server allows spaces in the db names, hence the separate regexp. issues/426 noted
+ // that MySQL tables can begin with _ (and 0-9 as it turns out).
+ const validTableCol = new RegExp('^[0-9a-zA-Z_$]*$');
+ const validTableColSQLServer = new RegExp('^[_a-zA-Z][0-9a-zA-Z_-\\s]*$');
+
+ if (settings.databaseType === 'MSSQL') {
+ if (!validTableColSQLServer.test(title)) {
+ return i18n.validationInvalidColName;
+ }
+ } else {
+ if (!validTableCol.test(title)) {
+ return i18n.validationInvalidColName;
+ }
+ }
+
+ return null;
+};
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.sql`,
+ fileType: 'text/x-sql'
+});
+
+export const isValid = (settings: SQLSettings): boolean => {
+ if (!settings.tableName) {
+ return false;
+ }
+
+ if (settings.databaseType !== 'Oracle' && !settings.insertBatchSize) {
+ return false;
+ }
+
+ return true;
+};
diff --git a/packages/plugins/src/exportTypes/SQL/SQL.worker.ts b/packages/plugins/src/exportTypes/SQL/SQL.worker.ts
new file mode 100644
index 000000000..a77002736
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/SQL.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './SQL.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/SQL/__tests__/SQL.generate.test.tsx b/packages/plugins/src/exportTypes/SQL/__tests__/SQL.generate.test.tsx
new file mode 100644
index 000000000..89c357d36
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/__tests__/SQL.generate.test.tsx
@@ -0,0 +1,27 @@
+import { getNumericFieldColumnIndexes } from '../SQL.generate';
+
+describe('getNumericFieldColumnIndexes', () => {
+
+ it('returns nothing when there are no numeric fields', () => {
+ expect(getNumericFieldColumnIndexes([])).toEqual([]);
+ expect(getNumericFieldColumnIndexes([
+ { title: 'Col 1', metadata: {}, dataType: 'Names' }
+ ])).toEqual([]);
+
+ expect(getNumericFieldColumnIndexes([
+ { title: 'Col 1', metadata: { general: { dataType: 'string' } }, dataType: 'Names' },
+ { title: 'Col 2', metadata: { general: { dataType: 'string' } }, dataType: 'Phone' },
+ ])).toEqual([]);
+ });
+
+ it('identifies numeric fields', () => {
+ expect(getNumericFieldColumnIndexes([
+ { title: 'Col 1', metadata: { general: { dataType: 'number' } }, dataType: 'Names' },
+ { title: 'Col 2', metadata: { general: { dataType: 'string' } }, dataType: 'Phone' },
+ { title: 'Col 3', metadata: { general: { dataType: 'string' } }, dataType: 'Phone' },
+ { title: 'Col 4', metadata: { general: { dataType: 'number' } }, dataType: 'List' },
+ { title: 'Col 5', metadata: { general: { dataType: 'infer' } }, dataType: 'Phone' },
+ ])).toEqual([0, 3]);
+ });
+
+});
diff --git a/packages/plugins/src/exportTypes/SQL/bundle.ts b/packages/plugins/src/exportTypes/SQL/bundle.ts
new file mode 100644
index 000000000..5835f9157
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/bundle.ts
@@ -0,0 +1,15 @@
+import { ETBundle } from '@generatedata/types';
+import { Settings, getCodeMirrorMode, getExportTypeLabel, validateTitleField, getDownloadFileInfo, isValid } from './SQL';
+import { initialState } from './SQL.state';
+
+const bundle: ETBundle = {
+ Settings,
+ initialState,
+ getExportTypeLabel,
+ getCodeMirrorMode,
+ validateTitleField,
+ isValid,
+ getDownloadFileInfo
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/SQL/config.ts b/packages/plugins/src/exportTypes/SQL/config.ts
new file mode 100644
index 000000000..9cebcdafe
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'core',
+ codeMirrorModes: ['sql/sql']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/ar.json b/packages/plugins/src/exportTypes/SQL/i18n/ar.json
new file mode 100644
index 000000000..d8b8286ab
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/ar.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "اسم العمود",
+ "rowLabel": "عمود الجدول",
+ "rowLabelPlural": "أعمدة الجدول",
+ "validationInvalidColName": "الرجاء إدخال أسماء أعمدة صالحة لقاعدة البيانات (من الألف إلى الياء والأحرف السفلية فقط).",
+ "validationInvalidTableName": "الرجاء إدخال اسم صالح لجدول قاعدة البيانات (من الألف إلى الياء والأحرف السفلية فقط).",
+ "dbTableName": "اسم جدول قاعدة البيانات",
+ "dbType": "نوع قاعدة البيانات",
+ "miscOptions": "خيارات متنوعة",
+ "includeCreateTableQuery": "تضمين CREATE TABLE الاستعلام",
+ "includeDropTableQuery": "تضمين استعلام DROP TABLE",
+ "encloseTableBackquotes": "قم بإرفاق أسماء الجداول والحقول بعلامات اقتباس خلفية",
+ "statementType": "نوع البيان",
+ "primaryKey": "المفتاح الأساسي",
+ "addDefaultAutoIncrementPrimaryKey": "أضف عمود المفتاح الأساسي الافتراضي للزيادة التلقائية",
+ "batchSize": "حجم الدفعة",
+ "batchSizeDesc": "عدد الصفوف المراد إدراجها لكل استعلام (1-300)"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/de.json b/packages/plugins/src/exportTypes/SQL/i18n/de.json
new file mode 100644
index 000000000..a3b6e97f9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/de.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "Spaltenname",
+ "dbTableName": "Datenbankname der Tabelle",
+ "dbType": "Datenbanktyp",
+ "encloseTableBackquotes": "Schließen Sie Tabellen-und Feldnamen mit Backquotes",
+ "includeCreateTableQuery": "Fügen Sie CREATE TABLE-Abfrage",
+ "includeDropTableQuery": "Fügen Sie DROP TABLE-Abfrage",
+ "miscOptions": "Verschiedene Optionen",
+ "primaryKey": "Primärschlüssel",
+ "rowLabel": "Tabellenspalte",
+ "rowLabelPlural": "Tabellenspalten",
+ "statementType": "Anweisungstyp",
+ "validationInvalidColName": "Bitte geben Sie einen gültigen Datenbankspaltennamen ein (nur a-Z und Unterstriche).",
+ "validationInvalidTableName": "Bitte geben Sie einen gültigen Datenbanktabellennamen ein (nur a-Z und Unterstriche).",
+ "batchSizeDesc": "Anzahl der Zeilen, die pro Abfrage einfügen (1 - 300)",
+ "addDefaultAutoIncrementPrimaryKey": "Fügen Sie die Standard-Primärschlüsselspalte für die automatische Inkrementierung hinzu",
+ "batchSize": "Chargengröße"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/en.json b/packages/plugins/src/exportTypes/SQL/i18n/en.json
new file mode 100644
index 000000000..8dd6847a8
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/en.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "Column Name",
+ "rowLabel": "Table Column",
+ "rowLabelPlural": "Table Columns",
+ "validationInvalidColName": "Please enter a valid database column name (a-Z and underscore characters only).",
+ "validationInvalidTableName": "Please enter a valid database table name (a-Z and underscore characters only).",
+ "dbTableName": "Database table name",
+ "dbType": "Database Type",
+ "miscOptions": "Misc Options",
+ "includeCreateTableQuery": "Include CREATE TABLE query",
+ "includeDropTableQuery": "Include DROP TABLE query",
+ "encloseTableBackquotes": "Enclose table and field names with backquotes",
+ "statementType": "Statement Type",
+ "primaryKey": "Primary Key",
+ "addDefaultAutoIncrementPrimaryKey": "Add default auto-increment primary key column",
+ "batchSize": "batch size",
+ "batchSizeDesc": "Number of rows to insert per query (1 - 300)"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/es.json b/packages/plugins/src/exportTypes/SQL/i18n/es.json
new file mode 100644
index 000000000..23460d538
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/es.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "Nombre de columna",
+ "rowLabel": "Columna de la tabla",
+ "rowLabelPlural": "Columnas de la tabla",
+ "validationInvalidColName": "Ingrese un nombre de columna de base de datos válido (a-Z y caracteres de subrayado solamente).",
+ "validationInvalidTableName": "Ingrese un nombre de tabla de base de datos válido (a-Z y caracteres de subrayado solamente).",
+ "dbTableName": "Nombre de tabla",
+ "dbType": "Tipo de base de datos",
+ "miscOptions": "Opciones varias",
+ "includeCreateTableQuery": "Incluir sentencia CREATE TABLE",
+ "includeDropTableQuery": "Incluir sentencia DROP TABLE",
+ "encloseTableBackquotes": "Entrecomillar nombres (tablas, columnas)",
+ "statementType": "Tipo de sentencia",
+ "primaryKey": "Clave primaria",
+ "batchSizeDesc": "Número de filas a insertar por una consulta (1-300)",
+ "addDefaultAutoIncrementPrimaryKey": "Agregar columna de clave primaria predeterminada de incremento automático",
+ "batchSize": "tamaño del lote"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/fr.json b/packages/plugins/src/exportTypes/SQL/i18n/fr.json
new file mode 100644
index 000000000..cbee81f65
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/fr.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "Column Name",
+ "dbTableName": "Nom de la table",
+ "dbType": "Type de base de données",
+ "encloseTableBackquotes": "Protéger les noms des tables et des champs avec des `backquotes`",
+ "includeCreateTableQuery": "Inclure une requête CREATE TABLE",
+ "includeDropTableQuery": "Inclure une requête DROP TABLE",
+ "miscOptions": "Options diverses",
+ "primaryKey": "Clé primaire",
+ "rowLabel": "Colonne",
+ "rowLabelPlural": "Colonnes",
+ "statementType": "Type de requêtes",
+ "validationInvalidColName": "Veuillez saisir un nom de colonne de base de données valide (a-Z et caractères de soulignement uniquement).",
+ "validationInvalidTableName": "Veuillez saisir un nom de table de base de données valide (a-Z et caractères de soulignement uniquement).",
+ "batchSizeDesc": "Nombre de lignes à insérer par une requête (1 - 300)",
+ "addDefaultAutoIncrementPrimaryKey": "Ajouter une colonne de clé primaire à incrémentation automatique par défaut",
+ "batchSize": "taille du lot"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/hi.json b/packages/plugins/src/exportTypes/SQL/i18n/hi.json
new file mode 100644
index 000000000..2e7776f53
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/hi.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "आम नाम",
+ "rowLabel": "टेबल कॉलम",
+ "rowLabelPlural": "टेबल कॉलम",
+ "validationInvalidColName": "कृपया एक मान्य डेटाबेस कॉलम नाम दर्ज करें (केवल a-Z और अंडरस्कोर वर्ण)।",
+ "validationInvalidTableName": "कृपया एक मान्य डेटाबेस तालिका नाम दर्ज करें (केवल a-Z और अंडरस्कोर वर्ण)।",
+ "dbTableName": "डेटाबेस तालिका का नाम",
+ "dbType": "डेटाबेस प्रकार",
+ "miscOptions": "विविध विकल्प",
+ "includeCreateTableQuery": "तालिका क्वेरी बनाएं शामिल करें",
+ "includeDropTableQuery": "ड्रॉप टेबल क्वेरी शामिल करें",
+ "encloseTableBackquotes": "बैककोट्स के साथ टेबल और फील्ड नाम संलग्न करें",
+ "statementType": "विवरण प्रकार",
+ "primaryKey": "प्राथमिक कुंजी",
+ "addDefaultAutoIncrementPrimaryKey": "डिफ़ॉल्ट ऑटो-इंक्रीमेंट प्राथमिक कुंजी कॉलम जोड़ें",
+ "batchSize": "बैच का आकार",
+ "batchSizeDesc": "प्रति क्वेरी सम्मिलित करने के लिए पंक्तियों की संख्या (1 - 300)"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/ja.json b/packages/plugins/src/exportTypes/SQL/i18n/ja.json
new file mode 100644
index 000000000..09635fd5b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/ja.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "列名",
+ "rowLabel": "テーブル列",
+ "rowLabelPlural": "テーブル列",
+ "validationInvalidColName": "有効なデータベース列名を入力してください (a-Z とアンダースコア文字のみ)。",
+ "validationInvalidTableName": "有効なデータベース テーブル名を入力してください (a ~ Z とアンダースコア文字のみ)。",
+ "dbTableName": "データベーステーブル名",
+ "dbType": "データベースタイプ",
+ "miscOptions": "その他のオプション",
+ "includeCreateTableQuery": "CREATETABLEクエリを含める",
+ "includeDropTableQuery": "DROPTABLEクエリを含める",
+ "encloseTableBackquotes": "テーブル名とフィールド名をバッククォートで囲みます",
+ "statementType": "ステートメントタイプ",
+ "primaryKey": "主キー",
+ "addDefaultAutoIncrementPrimaryKey": "デフォルトの自動インクリメント主キー列を追加します",
+ "batchSize": "バッチサイズ",
+ "batchSizeDesc": "クエリごとに挿入する行数(1〜300)"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/nl.json b/packages/plugins/src/exportTypes/SQL/i18n/nl.json
new file mode 100644
index 000000000..bf190b182
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/nl.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "Kolomnaam",
+ "dbTableName": "Database tabelnaam",
+ "dbType": "Databasetype",
+ "encloseTableBackquotes": "Omsluit tabel-en veldnamen met aanhalingstekens",
+ "includeCreateTableQuery": "Inclusief CREATE TABLE statement",
+ "includeDropTableQuery": "Inclusief DROP TABLE statement",
+ "miscOptions": "Overige Opties",
+ "primaryKey": "Primaire sleutel",
+ "rowLabel": "Tabelkolom",
+ "rowLabelPlural": "Tabel Kolommen",
+ "statementType": "Statement Type",
+ "validationInvalidColName": "Voer een geldige databasekolomnaam in (alleen a-Z en onderstrepingstekens).",
+ "validationInvalidTableName": "Voer een geldige databasetabelnaam in (alleen a-Z en onderstrepingstekens).",
+ "batchSizeDesc": "Aantal rijen dat per query moet worden ingevoegd (1-300)",
+ "addDefaultAutoIncrementPrimaryKey": "Standaard automatisch verhogen primaire sleutelkolom toevoegen",
+ "batchSize": "seriegrootte"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/pt.json b/packages/plugins/src/exportTypes/SQL/i18n/pt.json
new file mode 100644
index 000000000..61d0c9c7e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/pt.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "Nome da coluna",
+ "rowLabel": "Coluna da Tabela",
+ "rowLabelPlural": "Colunas da Tabela",
+ "validationInvalidColName": "Insira um nome de coluna de banco de dados válido (somente caracteres a-Z e sublinhado).",
+ "validationInvalidTableName": "Insira um nome de tabela de banco de dados válido (somente caracteres a-Z e sublinhado).",
+ "dbTableName": "Nome da tabela de banco de dados",
+ "dbType": "Tipo de banco de dados",
+ "miscOptions": "Opções diversas",
+ "includeCreateTableQuery": "Incluir consulta CREATE TABLE",
+ "includeDropTableQuery": "Incluir consulta DROP TABLE",
+ "encloseTableBackquotes": "Coloque nomes de tabelas e campos entre aspas",
+ "statementType": "Tipo de Declaração",
+ "primaryKey": "Chave primária",
+ "addDefaultAutoIncrementPrimaryKey": "Adicionar coluna de chave primária de incremento automático padrão",
+ "batchSize": "tamanho do batch",
+ "batchSizeDesc": "Número de linhas a inserir por consulta (1 - 300)"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/ru.json b/packages/plugins/src/exportTypes/SQL/i18n/ru.json
new file mode 100644
index 000000000..ec80a3908
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/ru.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "Имя столбца",
+ "rowLabel": "Столбец таблицы",
+ "rowLabelPlural": "Столбцы таблицы",
+ "validationInvalidColName": "Пожалуйста, введите допустимое имя столбца базы данных (только символы AZ и подчеркивания).",
+ "validationInvalidTableName": "Пожалуйста, введите допустимое имя таблицы базы данных (только символы AZ и подчеркивания).",
+ "dbTableName": "Имя таблицы базы данных",
+ "dbType": "Тип базы данных",
+ "miscOptions": "Разные параметры",
+ "includeCreateTableQuery": "Включить запрос CREATE TABLE",
+ "includeDropTableQuery": "Включить запрос DROP TABLE",
+ "encloseTableBackquotes": "Заключите имена таблиц и полей в обратные кавычки",
+ "statementType": "Тип заявления",
+ "primaryKey": "Первичный ключ",
+ "addDefaultAutoIncrementPrimaryKey": "Добавить столбец первичного ключа с автоинкрементом по умолчанию",
+ "batchSize": "размер партии",
+ "batchSizeDesc": "Количество строк для вставки на запрос (1–300)"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/ta.json b/packages/plugins/src/exportTypes/SQL/i18n/ta.json
new file mode 100644
index 000000000..49c9a09e3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/ta.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "நெடுவரிசை பெயர்",
+ "rowLabel": "அட்டவணை நெடுவரிசை",
+ "rowLabelPlural": "அட்டவணை நெடுவரிசைகள்",
+ "validationInvalidColName": "சரியான தரவுத்தள நெடுவரிசைப் பெயரை உள்ளிடவும் (a-Z மற்றும் அடிக்கோடிட்டு எழுத்துக்கள் மட்டும்).",
+ "validationInvalidTableName": "சரியான தரவுத்தள அட்டவணைப் பெயரை உள்ளிடவும் (a-Z மற்றும் அடிக்கோடிட்டு எழுத்துக்கள் மட்டும்).",
+ "dbTableName": "தரவுத்தள அட்டவணை பெயர்",
+ "dbType": "தரவுத்தள வகை",
+ "miscOptions": "தவறான விருப்பங்கள்",
+ "includeCreateTableQuery": "CREATE TABLE வினவலைச் சேர்க்கவும்",
+ "includeDropTableQuery": "டிராப் டேபிள் வினவலைச் சேர்க்கவும்",
+ "encloseTableBackquotes": "அட்டவணை மற்றும் புலம் பெயர்களை பேக் கோட்டுகளுடன் இணைக்கவும்",
+ "statementType": "அறிக்கை வகை",
+ "primaryKey": "முதன்மை விசை",
+ "addDefaultAutoIncrementPrimaryKey": "இயல்புநிலை தானாக அதிகரிக்கும் முதன்மை விசை நெடுவரிசையைச் சேர்க்கவும்",
+ "batchSize": "தொகுதி அளவு",
+ "batchSizeDesc": "வினவலுக்குச் செருக வரிசைகளின் எண்ணிக்கை (1 - 300)"
+}
diff --git a/packages/plugins/src/exportTypes/SQL/i18n/zh.json b/packages/plugins/src/exportTypes/SQL/i18n/zh.json
new file mode 100644
index 000000000..10801204e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/SQL/i18n/zh.json
@@ -0,0 +1,19 @@
+{
+ "EXPORT_TYPE_NAME": "SQL",
+ "COL_TITLE": "栏名",
+ "rowLabel": "表格栏",
+ "rowLabelPlural": "表格栏",
+ "validationInvalidColName": "请输入有效的数据库列名称(仅限 a-Z 和下划线字符)。",
+ "validationInvalidTableName": "请输入有效的数据库表名(仅限 a-Z 和下划线字符)。",
+ "dbTableName": "数据库表名称",
+ "dbType": "数据库类型",
+ "miscOptions": "杂项选项",
+ "includeCreateTableQuery": "包括CREATE TABLE查询",
+ "includeDropTableQuery": "包括DROP TABLE查询",
+ "encloseTableBackquotes": "用反引号将表和字段名称引起来",
+ "statementType": "报表类型",
+ "primaryKey": "首要的关键",
+ "addDefaultAutoIncrementPrimaryKey": "添加默认的自动增量主键列",
+ "batchSize": "批量",
+ "batchSizeDesc": "每个查询要插入的行数(1-300)"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/README.md b/packages/plugins/src/exportTypes/Typescript/README.md
new file mode 100644
index 000000000..93e3da805
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/README.md
@@ -0,0 +1,3 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » Typescript
+
+TODO.
diff --git a/packages/plugins/src/exportTypes/Typescript/Typescript.generate.ts b/packages/plugins/src/exportTypes/Typescript/Typescript.generate.ts
new file mode 100644
index 000000000..4d840fcb5
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/Typescript.generate.ts
@@ -0,0 +1,94 @@
+import { ETMessageData } from '../../';
+import { ColumnData } from '~types/general';
+import { WorkerUtils } from '../../';
+
+let utils: WorkerUtils;
+const maybeEnquote = (value: any) => {
+ const isNumeric = utils.numberUtils.isNumeric(value);
+
+ let startsWithZero = false;
+ if ((value || '').toString().length > 0) {
+ startsWithZero = value.toString()[0] === '0';
+ }
+ const isValidNumber = isNumeric && !startsWithZero;
+ if (!isValidNumber && !isJavascriptBoolean(value)) {
+ value = `"${value}"`;
+ }
+
+ return value;
+};
+
+export const generate = (
+ { stripWhitespace, isFirstBatch, isLastBatch, settings, columns, rows }: ETMessageData,
+ workerUtils: WorkerUtils
+): string => {
+ let content = '';
+ let comma = '';
+ utils = workerUtils;
+
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+ const space = stripWhitespace ? '' : ' ';
+
+ if (isFirstBatch) {
+ const { typeName, varName } = settings;
+
+ content += generateTypes(typeName, columns);
+ content += `export const ${varName}: ${typeName}[] = [`;
+ } else {
+ comma = ',';
+ }
+
+ rows.forEach((row: any) => {
+ content += `${comma}${newline}${tab}{`;
+ comma = '';
+
+ columns.forEach(({ title, metadata }: any, colIndex: number) => {
+ const propName: string = title.replace(/"/, '"');
+
+ let value = row[colIndex];
+
+ // if a DT has explicitly said it's a string, use a string
+ if (metadata && metadata.general && metadata.general.dataType && metadata.general.dataType === 'string') {
+ value = `"${value}"`;
+
+ // otherwise, do a safety check and encase it in double quote if necessary
+ } else {
+ value = maybeEnquote(value);
+ }
+
+ content += `${comma}${newline}${tab}${tab}${propName}:${space}${value}`;
+ comma = ',';
+ });
+
+ content += `${newline}${tab}}`;
+ });
+
+ if (isLastBatch) {
+ content += `${newline}];`;
+ }
+
+ return content;
+};
+
+const generateTypes = (typeName: string, colData: ColumnData[]): string => {
+ let typeBlock = `export type ${typeName} = {\n`;
+
+ colData.forEach(({ title, metadata }) => {
+ let type = 'string';
+ if (metadata && metadata.general) {
+ if (metadata.general.dataType === 'infer') {
+ type = 'number | string';
+ } else if (metadata.general.dataType !== 'string') {
+ type = metadata.general.dataType;
+ }
+ }
+ typeBlock += `\t${title}: ${type};\n`;
+ });
+
+ typeBlock += '};\n\n';
+
+ return typeBlock;
+};
+
+const isJavascriptBoolean = (n: any): boolean => n === 'true' || n === 'false' || n === true || n === false;
diff --git a/packages/plugins/src/exportTypes/Typescript/Typescript.scss b/packages/plugins/src/exportTypes/Typescript/Typescript.scss
new file mode 100644
index 000000000..22b3ec345
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/Typescript.scss
@@ -0,0 +1,7 @@
+.settingsBlock {
+ label {
+ display: block;
+ margin-bottom: 4px;
+ color: #666666;
+ }
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/Typescript.scss.d.ts b/packages/plugins/src/exportTypes/Typescript/Typescript.scss.d.ts
new file mode 100644
index 000000000..95ad35461
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/Typescript.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace TypescriptScssNamespace {
+ export interface ITypescriptScss {
+ settingsBlock: string;
+ }
+}
+
+declare const TypescriptScssModule: TypescriptScssNamespace.ITypescriptScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: TypescriptScssNamespace.ITypescriptScss;
+};
+
+export = TypescriptScssModule;
diff --git a/packages/plugins/src/exportTypes/Typescript/Typescript.state.tsx b/packages/plugins/src/exportTypes/Typescript/Typescript.state.tsx
new file mode 100644
index 000000000..2051e649c
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/Typescript.state.tsx
@@ -0,0 +1,18 @@
+import { ETState } from '@generatedata/types';
+
+export type GenerationOptionsType = {
+ typeName: string;
+ varName: string;
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ typeName: 'RandomData',
+ varName: 'data'
+};
+
+export interface TypescriptSettings extends ETState, GenerationOptionsType {}
+
+export const initialState: TypescriptSettings = {
+ ...defaultGenerationOptions,
+ isValid: true
+};
diff --git a/packages/plugins/src/exportTypes/Typescript/Typescript.tsx b/packages/plugins/src/exportTypes/Typescript/Typescript.tsx
new file mode 100644
index 000000000..a62cd1952
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/Typescript.tsx
@@ -0,0 +1,53 @@
+import * as React from 'react';
+import TextField from '~components/TextField';
+import { TypescriptSettings } from './Typescript.state';
+import { ETDownloadPacket, ETDownloadPacketResponse, ETSettings } from '@generatedata/types';
+import styles from './Typescript.scss';
+
+export const Settings = ({ i18n, data, id, coreI18n, onUpdate }: ETSettings) => {
+ const onChange = (field: string, value: string): void => {
+ const newValues = {
+ ...data,
+ [field]: value
+ };
+
+ const isValid = newValues.typeName.trim() !== '' && newValues.varName.trim() !== '';
+ onUpdate({
+ ...newValues,
+ isValid
+ });
+ };
+
+ return (
+
+ {i18n.typeName}
+ onChange('typeName', e.target.value)}
+ />
+
+ {i18n.exportedVarName}
+ onChange('varName', e.target.value)}
+ />
+
+ );
+};
+
+export const getCodeMirrorMode = (): string => 'text/typescript';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.ts`,
+ fileType: 'application/x-typescript'
+});
+
+export const isValid = (settings: TypescriptSettings): boolean => {
+ return settings.typeName.trim() !== '' && settings.varName.trim() !== '';
+};
diff --git a/packages/plugins/src/exportTypes/Typescript/Typescript.worker.ts b/packages/plugins/src/exportTypes/Typescript/Typescript.worker.ts
new file mode 100644
index 000000000..b6db4a40a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/Typescript.worker.ts
@@ -0,0 +1,15 @@
+import utils from '../../../utils';
+import { ETOnMessage } from '../../';
+import { generate } from './Typescript.generate';
+
+const context: Worker = self as any;
+
+let workerUtilsLoaded = false;
+context.onmessage = (e: ETOnMessage) => {
+ if (!workerUtilsLoaded) {
+ importScripts(e.data.workerUtilsUrl);
+ workerUtilsLoaded = true;
+ }
+
+ context.postMessage(generate(e.data, utils));
+};
diff --git a/packages/plugins/src/exportTypes/Typescript/__tests__/Typescript.generator.test.ts b/packages/plugins/src/exportTypes/Typescript/__tests__/Typescript.generator.test.ts
new file mode 100644
index 000000000..42fd71b23
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/__tests__/Typescript.generator.test.ts
@@ -0,0 +1,170 @@
+test.skip('skip', () => {});
+
+// import { generateSimple, isNested } from '../JSON.generator';
+// import { ETMessageData } from '../../';
+//
+//
+// describe('isNested', () => {
+// it('should return false when no columns have dots in them', () => {
+// const cols = [
+// 'one',
+// 'two',
+// 'three'
+// ];
+// expect(isNested(cols)).toEqual(false);
+// });
+//
+// it('should return false when there are no columns', () => {
+// expect(isNested([])).toEqual(false);
+// });
+//
+//
+// it('should return true when one item contains a dot', () => {
+// expect(isNested(['a.c', 'b', 'c'])).toEqual(true);
+// expect(isNested(['a', 'b.e', 'b.c'])).toEqual(true);
+// expect(isNested(['a', 'b', 'b.c'])).toEqual(true);
+// });
+// });
+//
+//
+// describe('generateSimple', () => {
+// const data: ETMessageData = {
+// columns: [
+// { title: 'One', dataType: 'Names' },
+// { title: 'Two', dataType: 'Names' }
+// ],
+// rows: [
+// ['Row #1, Cell #1', 'Row #1, Cell #2'],
+// ['Row #2, Cell #1', 'Row #2, Cell #2']
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// // my IDE likes to convert tabs to spaces & I'm not going to argue with it. This'll work across all developers
+// // systems, regardless of their preferences
+// it('should return expected JSON content', () => {
+// expect(generateSimple(data, false)).toEqual(
+// `[
+// \t{
+// \t\t"One": "Row #1, Cell #1",
+// \t\t"Two": "Row #1, Cell #2"
+// \t},
+// \t{
+// \t\t"One": "Row #2, Cell #1",
+// \t\t"Two": "Row #2, Cell #2"
+// \t}
+// ]`
+// );
+// });
+//
+// it('should return expected minified JSON content', () => {
+// expect(generateSimple(data, true)).toEqual(
+// `[{"One":"Row #1, Cell #1","Two":"Row #1, Cell #2"},{"One":"Row #2, Cell #1","Two":"Row #2, Cell #2"}]`
+// );
+// });
+//
+// it('should escape double quotes within property names', () => {
+// const doubleQuoteData: ETMessageData = {
+// columns: [
+// { title: 'Name', dataType: 'Names' },
+// { title: '"Phone" Number', dataType: 'Names' }
+// ],
+// rows: [
+// ['Tom', '(604) 123-1234'],
+// ['Susan', '(604) 733-1224'],
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(doubleQuoteData, false)).toEqual(
+// `[
+// \t{
+// \t\t"Name": "Tom",
+// \t\t"\"Phone\" Number": "(604) 123-1234"
+// \t},
+// \t{
+// \t\t"Name": "Susan",
+// \t\t"\"Phone\" Number": "(604) 733-1224"
+// \t}
+// ]`
+// );
+// });
+//
+// it('should escape double quotes within values', () => {
+// const doubleQuoteValueData: ETMessageData = {
+// columns: [
+// { title: 'Name', dataType: 'Names' },
+// { title: 'Phone', dataType: 'Names' }
+// ],
+// rows: [
+// ['Tom', '"Whoah" he said...'],
+// ['Susan', 'Like "TOTALLY!"'],
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(doubleQuoteValueData, false)).toEqual(
+// `[
+// \t{
+// \t\t"Name": "Tom",
+// \t\t"Phone": "\"Whoah\" he said..."
+// \t},
+// \t{
+// \t\t"Name": "Susan",
+// \t\t"Phone": "Like \"TOTALLY!\""
+// \t}
+// ]`
+// );
+// });
+//
+// it('should not put double quotes around numbers', () => {
+// const numData: ETMessageData = {
+// columns: [
+// { title: 'Num1', dataType: 'Names' },
+// { title: 'Num2', dataType: 'Names' },
+// { title: 'Num3', dataType: 'Names' },
+// { title: 'Num4', dataType: 'Names' },
+// { title: 'Num5', dataType: 'Names' },
+// { title: 'Num6', dataType: 'Names' }
+// ],
+// rows: [
+// ['0', 0, '1', 1, '1.23', 1.23]
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(numData, true)).toEqual(
+// `[{"Num1":0,"Num2":0,"Num3":1,"Num4":1,"Num5":1.23,"Num6":1.23}]`
+// );
+// });
+//
+// it('should not put double quotes around boolean values', () => {
+// const numData: ETMessageData = {
+// columns: [
+// { title: 'a', dataType: 'Names' },
+// { title: 'b', dataType: 'Names' },
+// { title: 'c', dataType: 'Names' },
+// { title: 'd', dataType: 'Names' }
+// ],
+// rows: [
+// ['false', false, 'true', true]
+// ],
+// isFirstBatch: true,
+// isLastBatch: true,
+// dataTypeMetadata: {}
+// };
+//
+// expect(generateSimple(numData, true)).toEqual(
+// `[{"a":false,"b":false,"c":true,"d":true}]`
+// );
+// });
+//
+// });
diff --git a/packages/plugins/src/exportTypes/Typescript/__tests__/Typescript.test.tsx b/packages/plugins/src/exportTypes/Typescript/__tests__/Typescript.test.tsx
new file mode 100644
index 000000000..5589a90b6
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/__tests__/Typescript.test.tsx
@@ -0,0 +1,24 @@
+import React from 'react';
+import { render } from '@testing-library/react';
+import { Settings } from '../Typescript';
+import { initialState } from '../Typescript.state';
+import { defaultETSettings } from '../../../../../tests/testHelpers';
+
+const i18n = require('../i18n/en.json');
+
+describe('Settings', () => {
+ it('renders', () => {
+ const data = { ...initialState };
+ const onUpdate = jest.fn();
+
+ const { container } = render(
+
+ );
+ expect(container).toBeTruthy();
+ });
+});
diff --git a/packages/plugins/src/exportTypes/Typescript/bundle.ts b/packages/plugins/src/exportTypes/Typescript/bundle.ts
new file mode 100644
index 000000000..a19584019
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/bundle.ts
@@ -0,0 +1,13 @@
+import { ETBundle } from '@generatedata/types';
+import { Settings, getCodeMirrorMode, getDownloadFileInfo, isValid } from './Typescript';
+import { initialState } from './Typescript.state';
+
+const bundle: ETBundle = {
+ initialState,
+ Settings,
+ getCodeMirrorMode,
+ getDownloadFileInfo,
+ isValid
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/Typescript/config.ts b/packages/plugins/src/exportTypes/Typescript/config.ts
new file mode 100644
index 000000000..654993b2a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'programmingLanguage',
+ codeMirrorModes: ['javascript/javascript', 'xml/xml', 'markdown/markdown']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/ar.json b/packages/plugins/src/exportTypes/Typescript/i18n/ar.json
new file mode 100644
index 000000000..39d25b19c
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/ar.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "اسم الخاصية",
+ "typeName": "أكتب اسم",
+ "exportedVarName": "تم تصدير اسم المتغير"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/de.json b/packages/plugins/src/exportTypes/Typescript/i18n/de.json
new file mode 100644
index 000000000..3c59ed1c7
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/de.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "Name des Anwesens",
+ "typeName": "Modellname",
+ "exportedVarName": "Name der exportierten Variablen"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/en.json b/packages/plugins/src/exportTypes/Typescript/i18n/en.json
new file mode 100644
index 000000000..bf2b63797
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/en.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "Property Name",
+ "typeName": "Type name",
+ "exportedVarName": "Exported variable name"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/es.json b/packages/plugins/src/exportTypes/Typescript/i18n/es.json
new file mode 100644
index 000000000..1b239fb17
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/es.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "Nombre de la propiedad",
+ "typeName": "Escribe un nombre",
+ "exportedVarName": "Nombre de variable exportada"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/fr.json b/packages/plugins/src/exportTypes/Typescript/i18n/fr.json
new file mode 100644
index 000000000..25e589f8b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/fr.json
@@ -0,0 +1,7 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "Nom de la propriété",
+ "typeName": "Nom du type",
+ "exportedVarName": "Nom de la variable exportée"
+}
+
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/hi.json b/packages/plugins/src/exportTypes/Typescript/i18n/hi.json
new file mode 100644
index 000000000..c04e7acfc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/hi.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "संपत्ति का नाम",
+ "typeName": "नाम लिखो",
+ "exportedVarName": "निर्यातित चर नाम"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/ja.json b/packages/plugins/src/exportTypes/Typescript/i18n/ja.json
new file mode 100644
index 000000000..b2b45f4a9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/ja.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "プロパティ名",
+ "typeName": "タイプ名",
+ "exportedVarName": "エクスポートされた変数名"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/nl.json b/packages/plugins/src/exportTypes/Typescript/i18n/nl.json
new file mode 100644
index 000000000..274d326ee
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/nl.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "Eigendomsnaam",
+ "typeName": "Typ de naam",
+ "exportedVarName": "Geëxporteerde variabelenaam"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/pt.json b/packages/plugins/src/exportTypes/Typescript/i18n/pt.json
new file mode 100644
index 000000000..8a71e1d80
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/pt.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "Nome da propriedade",
+ "typeName": "Digite o nome",
+ "exportedVarName": "Nome da variável exportada"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/ru.json b/packages/plugins/src/exportTypes/Typescript/i18n/ru.json
new file mode 100644
index 000000000..7b2fb03a9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/ru.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "Имя свойства",
+ "typeName": "Введите название",
+ "exportedVarName": "Имя экспортируемой переменной"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/ta.json b/packages/plugins/src/exportTypes/Typescript/i18n/ta.json
new file mode 100644
index 000000000..0f15b58dc
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/ta.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "சொத்தின் பெயர்",
+ "typeName": "பெயரைத் தட்டச்சு செய்க",
+ "exportedVarName": "ஏற்றுமதி செய்யப்பட்ட மாறி பெயர்"
+}
diff --git a/packages/plugins/src/exportTypes/Typescript/i18n/zh.json b/packages/plugins/src/exportTypes/Typescript/i18n/zh.json
new file mode 100644
index 000000000..a2b29d7f7
--- /dev/null
+++ b/packages/plugins/src/exportTypes/Typescript/i18n/zh.json
@@ -0,0 +1,6 @@
+{
+ "EXPORT_TYPE_NAME": "Typescript",
+ "COL_TITLE": "物业名称",
+ "typeName": "类型名称",
+ "exportedVarName": "导出的变量名称"
+}
diff --git a/packages/plugins/src/exportTypes/XML/README.md b/packages/plugins/src/exportTypes/XML/README.md
new file mode 100644
index 000000000..1484ed7a2
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/README.md
@@ -0,0 +1,85 @@
+# [Docs](../../../../../docs/README.md) » [Plugins](../../README.md) » [Export Types](../README.md) » XML
+
+This Export Type lets you generate your random data in XML format. You can use this plugin in one of two ways:
+
+1. Rely on the plugin generating the XML structure by just passing in `rootNodeName` and `recordNodeName` settings,
+which govern the key node names, or
+2. Pass in a `useCustomExportFormat` (true) and a `customTemplate` setting that contains a Smarty template to
+control the generated markup.
+
+See below for an example of each.
+
+
+### Example API Usage
+
+Here's a simple example that uses the `rootNodeName` and `recordNodeName` setting to generate the XM content. Just
+POST the following JSON content to the following API path:
+`http://[your site]/[generate data folder]/api/v1/data`
+
+```javascript
+{
+ "numRows": 15,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "RandomPassword",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "USZipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "XML",
+ "settings": {
+ "rootNodeName": "rows",
+ "recordNodeName": "row"
+ }
+ }
+}
+```
+
+Here's a second example. Like with the HTML Export Type, you can provide your own custom Smarty template to generate
+the XML with. It's ungainly and a pain, since you need to serialize your template into a JSON string, but the option is
+there if you want it.
+
+```javascript
+{
+ "numRows": 15,
+ "rows": [
+ {
+ "type": "AlphaNumeric",
+ "title": "RandomPassword",
+ "settings": {
+ "placeholder": "LLLxxLLLxLL"
+ }
+ },
+ {
+ "type": "AlphaNumeric",
+ "title": "USZipcode",
+ "settings": {
+ "placeholder": "xxxxx"
+ }
+ }
+ ],
+ "export": {
+ "type": "XML",
+ "settings": {
+ "useCustomExportFormat": true,
+ "customTemplate": "{if $isFirstBatch}\n\n{/if}{foreach $rowData as $row} {foreach from=$colData item=col name=c} <{$col}>{$row[$smarty.foreach.c.index]}{$col}>{/foreach} {/foreach}{if $isLastBatch} {/if}"
+ }
+ }
+}
+```
+
+
+### API help
+
+For more information about the API, check out:
+[http://benkeen.github.io/generatedata/api.html](http://benkeen.github.io/generatedata/api.html)
diff --git a/packages/plugins/src/exportTypes/XML/XML.generate.ts b/packages/plugins/src/exportTypes/XML/XML.generate.ts
new file mode 100644
index 000000000..3cb1cac87
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/XML.generate.ts
@@ -0,0 +1,30 @@
+import { ETMessageData } from '../../';
+import { XMLSettings } from './XML.state';
+
+export const generate = (data: ETMessageData): string => {
+ const { isFirstBatch, isLastBatch, rows, columns, stripWhitespace } = data;
+ const { rootNodeName, recordNodeName } = data.settings as XMLSettings;
+
+ const newline = stripWhitespace ? '' : '\n';
+ const tab = stripWhitespace ? '' : '\t';
+
+ let content = '';
+ if (isFirstBatch) {
+ content += `${newline}`;
+ content += `<${rootNodeName}>${newline}`;
+ }
+
+ rows.forEach((row: any) => {
+ content += `${tab}<${recordNodeName}>${newline}`;
+ columns.forEach(({ title }: any, colIndex: number) => {
+ content += `${tab}${tab}<${title}>${row[colIndex]}${title}>${newline}`;
+ });
+ content += `${tab}${recordNodeName}>${newline}`;
+ });
+
+ if (isLastBatch) {
+ content += `${rootNodeName}>`;
+ }
+
+ return content;
+};
diff --git a/packages/plugins/src/exportTypes/XML/XML.scss b/packages/plugins/src/exportTypes/XML/XML.scss
new file mode 100644
index 000000000..915958302
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/XML.scss
@@ -0,0 +1,13 @@
+.row {
+ display: flex;
+ align-items: center;
+ margin-bottom: 4px;
+
+ label {
+ flex: 0 0 150px;
+ color: #666666;
+ }
+ input {
+ flex: 1;
+ }
+}
diff --git a/packages/plugins/src/exportTypes/XML/XML.scss.d.ts b/packages/plugins/src/exportTypes/XML/XML.scss.d.ts
new file mode 100644
index 000000000..c307f9140
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/XML.scss.d.ts
@@ -0,0 +1,12 @@
+declare namespace XmlScssNamespace {
+ export interface IXmlScss {
+ row: string;
+ }
+}
+
+declare const XmlScssModule: XmlScssNamespace.IXmlScss & {
+ /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
+ locals: XmlScssNamespace.IXmlScss;
+};
+
+export = XmlScssModule;
diff --git a/packages/plugins/src/exportTypes/XML/XML.state.tsx b/packages/plugins/src/exportTypes/XML/XML.state.tsx
new file mode 100644
index 000000000..bff15c88e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/XML.state.tsx
@@ -0,0 +1,22 @@
+import { ETState } from '@generatedata/types';
+
+export type GenerationOptionsType = {
+ rootNodeName: string;
+ recordNodeName: string;
+ useCustomExportFormat?: boolean;
+ customFormat?: string;
+};
+
+export const defaultGenerationOptions: GenerationOptionsType = {
+ rootNodeName: 'records',
+ recordNodeName: 'record',
+ useCustomExportFormat: false,
+ customFormat: ''
+};
+
+export interface XMLSettings extends ETState, GenerationOptionsType {}
+
+export const initialState: XMLSettings = {
+ ...defaultGenerationOptions,
+ isValid: true
+};
diff --git a/packages/plugins/src/exportTypes/XML/XML.tsx b/packages/plugins/src/exportTypes/XML/XML.tsx
new file mode 100644
index 000000000..e61af8dcd
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/XML.tsx
@@ -0,0 +1,71 @@
+import * as React from 'react';
+import styles from './XML.scss';
+import { ETDownloadPacket, ETDownloadPacketResponse, ETSettings, ETValidateTitleField } from '@generatedata/types';
+import TextField from '~components/TextField';
+
+export const Settings = ({ data, i18n, coreI18n, id, onUpdate }: ETSettings) => {
+ const onChange = (prop: string, value: any): void => {
+ onUpdate({
+ ...data,
+ [prop]: value
+ });
+ };
+
+ let rootNodeNameError = '';
+ if (data.rootNodeName.trim() === '') {
+ rootNodeNameError = coreI18n.requiredField;
+ } else if (!isValidNodeName(data.rootNodeName)) {
+ rootNodeNameError = i18n.invalidNodeName;
+ }
+
+ let recordNodeNameError = '';
+ if (data.recordNodeName.trim() === '') {
+ recordNodeNameError = coreI18n.requiredField;
+ } else if (!isValidNodeName(data.recordNodeName)) {
+ recordNodeNameError = i18n.invalidNodeName;
+ }
+
+ return (
+ <>
+
+ {i18n.rootNodeName}
+ onChange('rootNodeName', e.target.value)}
+ />
+
+
+ {i18n.recordNodeName}
+ onChange('recordNodeName', e.target.value)}
+ />
+
+ >
+ );
+};
+
+export const getCodeMirrorMode = (): string => 'text/html';
+
+export const getDownloadFileInfo = ({ packetId }: ETDownloadPacket): ETDownloadPacketResponse => ({
+ filename: `data-${packetId}.xml`,
+ fileType: 'text/xml'
+});
+
+export const isValidNodeName = (str: string): boolean => {
+ const valid = new RegExp(
+ '^(:|[A-Z]|_|[a-z]|[\xC0-\xD6]|[\xD8-\xF6]|[\xF8-\u02FF]|[\u0370-\u037D]|[\u037F-\u1FFF]|[\u200C-\u200D]|[\u2070-\u218F]|[\u2C00-\u2FEF]|[\u3001-\uD7FF]|[\uF900-\uFDCF]|[\uFDF0-\uFFFD])(:|[A-Z]|_|[a-z]|[\xC0-\xD6]|[\xD8-\xF6]|[\xF8-\u02FF]|[\u0370-\u037D]|[\u037F-\u1FFF]|[\u200C-\u200D]|[\u2070-\u218F]|[\u2C00-\u2FEF]|[\u3001-\uD7FF]|[\uF900-\uFDCF]|[\uFDF0-\uFFFD]|-|\\\\.|[0-9]|\xB7|[\u0300-\u036F]|[\u203F-\u2040])*$'
+ );
+ return valid.test(str);
+};
+
+export const validateTitleField: ETValidateTitleField = (title, i18n) => {
+ if (!isValidNodeName(title)) {
+ return i18n.invalidNodeName;
+ }
+ return null;
+};
diff --git a/packages/plugins/src/exportTypes/XML/XML.worker.ts b/packages/plugins/src/exportTypes/XML/XML.worker.ts
new file mode 100644
index 000000000..6ce82597d
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/XML.worker.ts
@@ -0,0 +1,8 @@
+import { ETOnMessage } from '@generatedata/types';
+import { generate } from './XML.generate';
+
+const context: Worker = self as any;
+
+context.onmessage = (e: ETOnMessage) => {
+ context.postMessage(generate(e.data));
+};
diff --git a/packages/plugins/src/exportTypes/XML/bundle.ts b/packages/plugins/src/exportTypes/XML/bundle.ts
new file mode 100644
index 000000000..d79331243
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/bundle.ts
@@ -0,0 +1,13 @@
+import { ETBundle } from '@generatedata/types';
+import { Settings, getCodeMirrorMode, getDownloadFileInfo, validateTitleField } from './XML';
+import { initialState } from './XML.state';
+
+const bundle: ETBundle = {
+ Settings,
+ initialState,
+ getCodeMirrorMode,
+ getDownloadFileInfo,
+ validateTitleField
+};
+
+export default bundle;
diff --git a/packages/plugins/src/exportTypes/XML/config.ts b/packages/plugins/src/exportTypes/XML/config.ts
new file mode 100644
index 000000000..d93e779f1
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/config.ts
@@ -0,0 +1,8 @@
+import { ETDefinition } from '@generatedata/types';
+
+const definition: ETDefinition = {
+ fieldGroup: 'core',
+ codeMirrorModes: ['xml/xml']
+};
+
+export default definition;
diff --git a/packages/plugins/src/exportTypes/XML/i18n/ar.json b/packages/plugins/src/exportTypes/XML/i18n/ar.json
new file mode 100644
index 000000000..9f69b463e
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/ar.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "اسم العقدة",
+ "rowLabel": "اسم العقدة",
+ "rowLabelPlural": "أسماء العقدة",
+ "invalidNodeName": "الرجاء إدخال اسم صالح لعقدة XML.",
+ "rootNodeName": "اسم عقدة الجذر",
+ "recordNodeName": "تسجيل اسم العقدة"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/de.json b/packages/plugins/src/exportTypes/XML/i18n/de.json
new file mode 100644
index 000000000..64bc083c5
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/de.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "Knotenname",
+ "rowLabel": "Knotenname",
+ "rowLabelPlural": "Knotennamen",
+ "invalidNodeName": "Bitte geben Sie einen gültigen XML-Knotennamen ein.",
+ "rootNodeName": "Stammknotenname",
+ "recordNodeName": "Knotennamen aufzeichnen"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/en.json b/packages/plugins/src/exportTypes/XML/i18n/en.json
new file mode 100644
index 000000000..5636f339b
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/en.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "Node name",
+ "rowLabel": "Node Name",
+ "rowLabelPlural": "Node Names",
+ "invalidNodeName": "Please enter a valid XML node name.",
+ "rootNodeName": "Root node name",
+ "recordNodeName": "Record node name"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/es.json b/packages/plugins/src/exportTypes/XML/i18n/es.json
new file mode 100644
index 000000000..b8bf2c9c3
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/es.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "Nombre del nodo",
+ "rowLabel": "Nombre del nodo",
+ "rowLabelPlural": "Nombres de nodo",
+ "invalidNodeName": "Introduzca un nombre de nodo XML válido.",
+ "rootNodeName": "Nombre del nodo raíz",
+ "recordNodeName": "Registrar nombre de nodo"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/fr.json b/packages/plugins/src/exportTypes/XML/i18n/fr.json
new file mode 100644
index 000000000..81bce6ca6
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/fr.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "Nom du nœud",
+ "rowLabel": "Nom du nœud",
+ "rowLabelPlural": "Noms des nœuds",
+ "invalidNodeName": "Veuillez entrer un nom de nœud XML valide.",
+ "rootNodeName": "Nom du nœud racine",
+ "recordNodeName": "Enregistrer le nom du nœud"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/hi.json b/packages/plugins/src/exportTypes/XML/i18n/hi.json
new file mode 100644
index 000000000..ecc1c72a9
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/hi.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "नोड का नाम",
+ "rowLabel": "नोड का नाम",
+ "rowLabelPlural": "नोड नाम",
+ "invalidNodeName": "कृपया एक मान्य XML नोड नाम दर्ज करें।",
+ "rootNodeName": "रूट नोड नाम",
+ "recordNodeName": "रिकॉर्ड नोड नाम"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/ja.json b/packages/plugins/src/exportTypes/XML/i18n/ja.json
new file mode 100644
index 000000000..9ac7998a6
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/ja.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "ノード名",
+ "rowLabel": "ノード名",
+ "rowLabelPlural": "ノード名",
+ "invalidNodeName": "有効な XML ノード名を入力してください。",
+ "rootNodeName": "ルートノード名",
+ "recordNodeName": "レコードノード名"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/nl.json b/packages/plugins/src/exportTypes/XML/i18n/nl.json
new file mode 100644
index 000000000..cb75ff427
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/nl.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "Knooppuntnaam",
+ "rowLabel": "Knooppuntnaam",
+ "rowLabelPlural": "Node Namen",
+ "invalidNodeName": "Voer een geldige XML-knooppuntnaam in.",
+ "rootNodeName": "Naam van hoofdknooppunt",
+ "recordNodeName": "Knooppuntnaam opnemen"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/pt.json b/packages/plugins/src/exportTypes/XML/i18n/pt.json
new file mode 100644
index 000000000..7133dba2a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/pt.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "Nome do nó",
+ "rowLabel": "Nome do nó",
+ "rowLabelPlural": "Nomes de Nó",
+ "invalidNodeName": "Insira um nome de nó XML válido.",
+ "rootNodeName": "Nome do nó raiz",
+ "recordNodeName": "Nome do nó de registro"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/ru.json b/packages/plugins/src/exportTypes/XML/i18n/ru.json
new file mode 100644
index 000000000..8af01fb8a
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/ru.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "Имя узла",
+ "rowLabel": "Имя узла",
+ "rowLabelPlural": "Имена узлов",
+ "invalidNodeName": "Введите допустимое имя узла XML.",
+ "rootNodeName": "Имя корневого узла",
+ "recordNodeName": "Записать имя узла"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/ta.json b/packages/plugins/src/exportTypes/XML/i18n/ta.json
new file mode 100644
index 000000000..8f71c5f76
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/ta.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "முனை பெயர்",
+ "rowLabel": "முனை பெயர்",
+ "rowLabelPlural": "முனை பெயர்கள்",
+ "invalidNodeName": "சரியான XML நோட் பெயரை உள்ளிடவும்.",
+ "rootNodeName": "ரூட் முனை பெயர்",
+ "recordNodeName": "பதிவு முனை பெயர்"
+}
diff --git a/packages/plugins/src/exportTypes/XML/i18n/zh.json b/packages/plugins/src/exportTypes/XML/i18n/zh.json
new file mode 100644
index 000000000..1f43dd934
--- /dev/null
+++ b/packages/plugins/src/exportTypes/XML/i18n/zh.json
@@ -0,0 +1,9 @@
+{
+ "EXPORT_TYPE_NAME": "XML",
+ "COL_TITLE": "节点名称",
+ "rowLabel": "节点名称",
+ "rowLabelPlural": "节点名称",
+ "invalidNodeName": "请输入有效的 XML 节点名称。",
+ "rootNodeName": "根节点名称",
+ "recordNodeName": "记录节点名称"
+}
diff --git a/packages/plugins/src/index.ts b/packages/plugins/src/index.ts
new file mode 100644
index 000000000..2f51fc90e
--- /dev/null
+++ b/packages/plugins/src/index.ts
@@ -0,0 +1,779 @@
+import Alphanumeric from './dataTypes/Alphanumeric/config';
+import AutoIncrement from './dataTypes/AutoIncrement/config';
+import BitcoinAddress from './dataTypes/BitcoinAddress/config';
+import Boolean from './dataTypes/Boolean/config';
+import CVV from './dataTypes/CVV/config';
+import City from './dataTypes/City/config';
+import Colour from './dataTypes/Colour/config';
+import Company from './dataTypes/Company/config';
+import Computed from './dataTypes/Computed/config';
+import Constant from './dataTypes/Constant/config';
+import Country from './dataTypes/Country/config';
+import Currency from './dataTypes/Currency/config';
+import Date from './dataTypes/Date/config';
+import Email from './dataTypes/Email/config';
+import GUID from './dataTypes/GUID/config';
+import IBAN from './dataTypes/IBAN/config';
+import LatLng from './dataTypes/LatLng/config';
+import List from './dataTypes/List/config';
+import Names from './dataTypes/Names/config';
+import NormalDistribution from './dataTypes/NormalDistribution/config';
+import NumberRange from './dataTypes/NumberRange/config';
+import OrganizationNumber from './dataTypes/OrganizationNumber/config';
+import PAN from './dataTypes/PAN/config';
+import PIN from './dataTypes/PIN/config';
+import PersonalNumber from './dataTypes/PersonalNumber/config';
+import Phone from './dataTypes/Phone/config';
+import PostalZip from './dataTypes/PostalZip/config';
+import Region from './dataTypes/Region/config';
+import Rut from './dataTypes/Rut/config';
+import SIRET from './dataTypes/SIRET/config';
+import StreetAddress from './dataTypes/StreetAddress/config';
+import TextFixed from './dataTypes/TextFixed/config';
+import TextRandom from './dataTypes/TextRandom/config';
+import Time from './dataTypes/Time/config';
+import Track1 from './dataTypes/Track1/config';
+import Track2 from './dataTypes/Track2/config';
+import URLs from './dataTypes/URLs/config';
+import WeightedList from './dataTypes/WeightedList/config';
+import { CountryDataType, CountryNames, DatabaseTypes } from '@generatedata/types';
+import { getStrings } from '@generatedata/utils/lang';
+
+export type ETMessageData = {
+ action: 'generate' | 'pause' | 'continue' | 'abort';
+ columns: ColumnData[];
+ rows: any[];
+ isFirstBatch: boolean;
+ isLastBatch: boolean;
+ currentBatch: number;
+ batchSize: number;
+ settings: any; // TODO generic possible? This is the export type settings
+ stripWhitespace: boolean;
+ rowState: any;
+ workerUtilsUrl: string;
+ exportTypes: ExportTypeMap;
+ dataTypes: DataTypeMap;
+ countries: CountryMap;
+};
+
+export type ColumnData = {
+ title: string;
+ dataType: DataTypeFolder;
+ metadata: DTMetadata;
+};
+
+export type DTDefinition = {
+ fieldGroup: DTFieldGroup;
+ fieldGroupOrder: number;
+ dependencies?: DataTypeFolder[];
+};
+
+export type DTFieldGroup = 'numeric' | 'geo' | 'humanData' | 'other' | 'financial' | 'text' | 'countrySpecific';
+
+export type DTMetadataType = 'number' | 'string' | 'boolean' | 'date' | 'infer';
+
+export type GeneralMetadataTypes = {
+ dataType: DTMetadataType;
+};
+
+export type DTMetadata = {
+ general?: GeneralMetadataTypes;
+ sql?: DatabaseTypes;
+};
+
+export type ExportTypeMap = {
+ [exportType in ExportTypeFolder]?: string;
+};
+
+export type DataTypeMap = {
+ [dataType in DataTypeFolder]?: string;
+};
+
+export type CountryMap = {
+ [country in CountryType]?: string;
+};
+
+export type countryTuple = typeof countries;
+export type CountryType = countryTuple[number];
+export type CountryNamesMap = {
+ [country in CountryType]?: CountryNames;
+};
+
+export interface ETOnMessage extends MessageEvent {
+ data: ETMessageData;
+}
+
+export const dataTypes = {
+ Alphanumeric,
+ AutoIncrement,
+ BitcoinAddress,
+ Boolean,
+ CVV,
+ City,
+ Colour,
+ Company,
+ Computed,
+ Constant,
+ Country,
+ Currency,
+ Date,
+ Email,
+ GUID,
+ IBAN,
+ LatLng,
+ List,
+ Names,
+ NormalDistribution,
+ NumberRange,
+ OrganizationNumber,
+ PAN,
+ PIN,
+ PersonalNumber,
+ Phone,
+ PostalZip,
+ Region,
+ Rut,
+ SIRET,
+ StreetAddress,
+ TextFixed,
+ TextRandom,
+ Time,
+ Track1,
+ Track2,
+ URLs,
+ WeightedList
+};
+
+export type DataTypeFolder = keyof typeof dataTypes;
+
+export const blacklistedDataTypeFolders = ['BitcoinAddress', 'OrganizationNumber', 'PersonalNumber', 'SIRET'];
+
+import CSV from './exportTypes/CSV/config';
+import CSharp from './exportTypes/CSharp/config';
+import HTML from './exportTypes/HTML/config';
+import JSON from './exportTypes/JSON/config';
+import Javascript from './exportTypes/Javascript/config';
+import LDIF from './exportTypes/LDIF/config';
+import PHP from './exportTypes/PHP/config';
+import Perl from './exportTypes/Perl/config';
+import Python from './exportTypes/Python/config';
+import Ruby from './exportTypes/Ruby/config';
+import SQL from './exportTypes/SQL/config';
+import Typescript from './exportTypes/Typescript/config';
+import XML from './exportTypes/XML/config';
+
+export const exportTypes = {
+ CSV,
+ CSharp,
+ HTML,
+ JSON,
+ Javascript,
+ LDIF,
+ PHP,
+ Perl,
+ Python,
+ Ruby,
+ SQL,
+ Typescript,
+ XML
+};
+
+export type ExportTypeFolder = keyof typeof exportTypes;
+
+import CountryAustralia from './countries/Australia/bundle';
+import CountryAustria from './countries/Austria/bundle';
+import CountryBelgium from './countries/Belgium/bundle';
+import CountryBrazil from './countries/Brazil/bundle';
+import CountryCanada from './countries/Canada/bundle';
+import CountryChile from './countries/Chile/bundle';
+import CountryChina from './countries/China/bundle';
+import CountryColombia from './countries/Colombia/bundle';
+import CountryCostaRica from './countries/CostaRica/bundle';
+import CountryFrance from './countries/France/bundle';
+import CountryGermany from './countries/Germany/bundle';
+import CountryIndia from './countries/India/bundle';
+import CountryIndonesia from './countries/Indonesia/bundle';
+import CountryIreland from './countries/Ireland/bundle';
+import CountryItaly from './countries/Italy/bundle';
+import CountryMexico from './countries/Mexico/bundle';
+import CountryNetherlands from './countries/Netherlands/bundle';
+import CountryNewZealand from './countries/NewZealand/bundle';
+import CountryNigeria from './countries/Nigeria/bundle';
+import CountryNorway from './countries/Norway/bundle';
+import CountryPakistan from './countries/Pakistan/bundle';
+import CountryPeru from './countries/Peru/bundle';
+import CountryPhilippines from './countries/Philippines/bundle';
+import CountryPoland from './countries/Poland/bundle';
+import CountryRussia from './countries/Russia/bundle';
+import CountrySingapore from './countries/Singapore/bundle';
+import CountrySouthAfrica from './countries/SouthAfrica/bundle';
+import CountrySouthKorea from './countries/SouthKorea/bundle';
+import CountrySpain from './countries/Spain/bundle';
+import CountrySweden from './countries/Sweden/bundle';
+import CountryTurkey from './countries/Turkey/bundle';
+import CountryUK from './countries/UK/bundle';
+import CountryUS from './countries/US/bundle';
+import CountryUkraine from './countries/Ukraine/bundle';
+import CountryVietnam from './countries/Vietnam/bundle';
+
+export const countryList = [
+ 'Australia',
+ 'Austria',
+ 'Belgium',
+ 'Brazil',
+ 'Canada',
+ 'Chile',
+ 'China',
+ 'Colombia',
+ 'CostaRica',
+ 'France',
+ 'Germany',
+ 'India',
+ 'Indonesia',
+ 'Ireland',
+ 'Italy',
+ 'Mexico',
+ 'Netherlands',
+ 'NewZealand',
+ 'Nigeria',
+ 'Norway',
+ 'Pakistan',
+ 'Peru',
+ 'Philippines',
+ 'Poland',
+ 'Russia',
+ 'Singapore',
+ 'SouthAfrica',
+ 'SouthKorea',
+ 'Spain',
+ 'Sweden',
+ 'Turkey',
+ 'UK',
+ 'US',
+ 'Ukraine',
+ 'Vietnam'
+];
+export const countries = [
+ 'Australia',
+ 'Austria',
+ 'Belgium',
+ 'Brazil',
+ 'Canada',
+ 'Chile',
+ 'China',
+ 'Colombia',
+ 'CostaRica',
+ 'France',
+ 'Germany',
+ 'India',
+ 'Indonesia',
+ 'Ireland',
+ 'Italy',
+ 'Mexico',
+ 'Netherlands',
+ 'NewZealand',
+ 'Nigeria',
+ 'Norway',
+ 'Pakistan',
+ 'Peru',
+ 'Philippines',
+ 'Poland',
+ 'Russia',
+ 'Singapore',
+ 'SouthAfrica',
+ 'SouthKorea',
+ 'Spain',
+ 'Sweden',
+ 'Turkey',
+ 'UK',
+ 'US',
+ 'Ukraine',
+ 'Vietnam'
+] as const;
+export const countryMethods = {
+ Australia: CountryAustralia,
+ Austria: CountryAustria,
+ Belgium: CountryBelgium,
+ Brazil: CountryBrazil,
+ Canada: CountryCanada,
+ Chile: CountryChile,
+ China: CountryChina,
+ Colombia: CountryColombia,
+ CostaRica: CountryCostaRica,
+ France: CountryFrance,
+ Germany: CountryGermany,
+ India: CountryIndia,
+ Indonesia: CountryIndonesia,
+ Ireland: CountryIreland,
+ Italy: CountryItaly,
+ Mexico: CountryMexico,
+ Netherlands: CountryNetherlands,
+ NewZealand: CountryNewZealand,
+ Nigeria: CountryNigeria,
+ Norway: CountryNorway,
+ Pakistan: CountryPakistan,
+ Peru: CountryPeru,
+ Philippines: CountryPhilippines,
+ Poland: CountryPoland,
+ Russia: CountryRussia,
+ Singapore: CountrySingapore,
+ SouthAfrica: CountrySouthAfrica,
+ SouthKorea: CountrySouthKorea,
+ Spain: CountrySpain,
+ Sweden: CountrySweden,
+ Turkey: CountryTurkey,
+ UK: CountryUK,
+ US: CountryUS,
+ Ukraine: CountryUkraine,
+ Vietnam: CountryVietnam
+};
+
+export enum DataType {
+ Alphanumeric = 'Alphanumeric',
+ AutoIncrement = 'AutoIncrement',
+ Boolean = 'Boolean',
+ CVV = 'CVV',
+ City = 'City',
+ Colour = 'Colour',
+ Company = 'Company',
+ Computed = 'Computed',
+ Constant = 'Constant',
+ Country = 'Country',
+ Currency = 'Currency',
+ Date = 'Date',
+ Email = 'Email',
+ GUID = 'GUID',
+ IBAN = 'IBAN',
+ LatLng = 'LatLng',
+ List = 'List',
+ Names = 'Names',
+ NormalDistribution = 'NormalDistribution',
+ NumberRange = 'NumberRange',
+ PAN = 'PAN',
+ PIN = 'PIN',
+ Phone = 'Phone',
+ PostalZip = 'PostalZip',
+ Region = 'Region',
+ Rut = 'Rut',
+ StreetAddress = 'StreetAddress',
+ TextFixed = 'TextFixed',
+ TextRandom = 'TextRandom',
+ Time = 'Time',
+ Track1 = 'Track1',
+ Track2 = 'Track2',
+ URLs = 'URLs',
+ WeightedList = 'WeightedList'
+}
+
+import { GenerationOptionsType as AlphanumericGenerationOptions } from './dataTypes/Alphanumeric/Alphanumeric.state';
+import { GenerationOptionsType as AutoIncrementGenerationOptions } from './dataTypes/AutoIncrement/AutoIncrement.state';
+import { GenerationOptionsType as BooleanGenerationOptions } from './dataTypes/Boolean/Boolean.state';
+import { GenerationOptionsType as CVVGenerationOptions } from './dataTypes/CVV/CVV.state';
+import { GenerationOptionsType as CityGenerationOptions } from './dataTypes/City/City.state';
+import { GenerationOptionsType as ColourGenerationOptions } from './dataTypes/Colour/Colour.state';
+import { GenerationOptionsType as CompanyGenerationOptions } from './dataTypes/Company/Company.state';
+import { GenerationOptionsType as ComputedGenerationOptions } from './dataTypes/Computed/Computed.state';
+import { GenerationOptionsType as ConstantGenerationOptions } from './dataTypes/Constant/Constant.state';
+import { GenerationOptionsType as CountryGenerationOptions } from './dataTypes/Country/Country.state';
+import { GenerationOptionsType as CurrencyGenerationOptions } from './dataTypes/Currency/Currency.state';
+import { GenerationOptionsType as DateGenerationOptions } from './dataTypes/Date/Date.state';
+import { GenerationOptionsType as EmailGenerationOptions } from './dataTypes/Email/Email.state';
+import { GenerationOptionsType as GUIDGenerationOptions } from './dataTypes/GUID/GUID.state';
+import { GenerationOptionsType as IBANGenerationOptions } from './dataTypes/IBAN/IBAN.state';
+import { GenerationOptionsType as LatLngGenerationOptions } from './dataTypes/LatLng/LatLng.state';
+import { GenerationOptionsType as ListGenerationOptions } from './dataTypes/List/List.state';
+import { GenerationOptionsType as NamesGenerationOptions } from './dataTypes/Names/Names.state';
+import { GenerationOptionsType as NormalDistributionGenerationOptions } from './dataTypes/NormalDistribution/NormalDistribution.state';
+import { GenerationOptionsType as NumberRangeGenerationOptions } from './dataTypes/NumberRange/NumberRange.state';
+import { GenerationOptionsType as PANGenerationOptions } from './dataTypes/PAN/PAN.state';
+import { GenerationOptionsType as PINGenerationOptions } from './dataTypes/PIN/PIN.state';
+import { GenerationOptionsType as PhoneGenerationOptions } from './dataTypes/Phone/Phone.state';
+import { GenerationOptionsType as PostalZipGenerationOptions } from './dataTypes/PostalZip/PostalZip.state';
+import { GenerationOptionsType as RegionGenerationOptions } from './dataTypes/Region/Region.state';
+import { GenerationOptionsType as RutGenerationOptions } from './dataTypes/Rut/Rut.state';
+import { GenerationOptionsType as StreetAddressGenerationOptions } from './dataTypes/StreetAddress/StreetAddress.state';
+import { GenerationOptionsType as TextFixedGenerationOptions } from './dataTypes/TextFixed/TextFixed.state';
+import { GenerationOptionsType as TextRandomGenerationOptions } from './dataTypes/TextRandom/TextRandom.state';
+import { GenerationOptionsType as TimeGenerationOptions } from './dataTypes/Time/Time.state';
+import { GenerationOptionsType as Track1GenerationOptions } from './dataTypes/Track1/Track1.state';
+import { GenerationOptionsType as Track2GenerationOptions } from './dataTypes/Track2/Track2.state';
+import { GenerationOptionsType as URLsGenerationOptions } from './dataTypes/URLs/URLs.state';
+import { GenerationOptionsType as WeightedListGenerationOptions } from './dataTypes/WeightedList/WeightedList.state';
+interface AlphanumericDataTypeRow {
+ plugin: DataType.Alphanumeric | 'Alphanumeric';
+ title: string;
+ settings: AlphanumericGenerationOptions;
+ id?: string;
+}
+interface AutoIncrementDataTypeRow {
+ plugin: DataType.AutoIncrement | 'AutoIncrement';
+ title: string;
+ settings: AutoIncrementGenerationOptions;
+ id?: string;
+}
+interface BooleanDataTypeRow {
+ plugin: DataType.Boolean | 'Boolean';
+ title: string;
+ settings: BooleanGenerationOptions;
+ id?: string;
+}
+interface CVVDataTypeRow {
+ plugin: DataType.CVV | 'CVV';
+ title: string;
+ settings: CVVGenerationOptions;
+ id?: string;
+}
+interface CityDataTypeRow {
+ plugin: DataType.City | 'City';
+ title: string;
+ settings: CityGenerationOptions;
+ id?: string;
+}
+interface ColourDataTypeRow {
+ plugin: DataType.Colour | 'Colour';
+ title: string;
+ settings: ColourGenerationOptions;
+ id?: string;
+}
+interface CompanyDataTypeRow {
+ plugin: DataType.Company | 'Company';
+ title: string;
+ settings: CompanyGenerationOptions;
+ id?: string;
+}
+interface ComputedDataTypeRow {
+ plugin: DataType.Computed | 'Computed';
+ title: string;
+ settings: ComputedGenerationOptions;
+ id?: string;
+}
+interface ConstantDataTypeRow {
+ plugin: DataType.Constant | 'Constant';
+ title: string;
+ settings: ConstantGenerationOptions;
+ id?: string;
+}
+interface CountryDataTypeRow {
+ plugin: DataType.Country | 'Country';
+ title: string;
+ settings: CountryGenerationOptions;
+ id?: string;
+}
+interface CurrencyDataTypeRow {
+ plugin: DataType.Currency | 'Currency';
+ title: string;
+ settings: CurrencyGenerationOptions;
+ id?: string;
+}
+interface DateDataTypeRow {
+ plugin: DataType.Date | 'Date';
+ title: string;
+ settings: DateGenerationOptions;
+ id?: string;
+}
+interface EmailDataTypeRow {
+ plugin: DataType.Email | 'Email';
+ title: string;
+ settings: EmailGenerationOptions;
+ id?: string;
+}
+interface GUIDDataTypeRow {
+ plugin: DataType.GUID | 'GUID';
+ title: string;
+ settings: GUIDGenerationOptions;
+ id?: string;
+}
+interface IBANDataTypeRow {
+ plugin: DataType.IBAN | 'IBAN';
+ title: string;
+ settings: IBANGenerationOptions;
+ id?: string;
+}
+interface LatLngDataTypeRow {
+ plugin: DataType.LatLng | 'LatLng';
+ title: string;
+ settings: LatLngGenerationOptions;
+ id?: string;
+}
+interface ListDataTypeRow {
+ plugin: DataType.List | 'List';
+ title: string;
+ settings: ListGenerationOptions;
+ id?: string;
+}
+interface NamesDataTypeRow {
+ plugin: DataType.Names | 'Names';
+ title: string;
+ settings: NamesGenerationOptions;
+ id?: string;
+}
+interface NormalDistributionDataTypeRow {
+ plugin: DataType.NormalDistribution | 'NormalDistribution';
+ title: string;
+ settings: NormalDistributionGenerationOptions;
+ id?: string;
+}
+interface NumberRangeDataTypeRow {
+ plugin: DataType.NumberRange | 'NumberRange';
+ title: string;
+ settings: NumberRangeGenerationOptions;
+ id?: string;
+}
+interface PANDataTypeRow {
+ plugin: DataType.PAN | 'PAN';
+ title: string;
+ settings: PANGenerationOptions;
+ id?: string;
+}
+interface PINDataTypeRow {
+ plugin: DataType.PIN | 'PIN';
+ title: string;
+ settings: PINGenerationOptions;
+ id?: string;
+}
+interface PhoneDataTypeRow {
+ plugin: DataType.Phone | 'Phone';
+ title: string;
+ settings: PhoneGenerationOptions;
+ id?: string;
+}
+interface PostalZipDataTypeRow {
+ plugin: DataType.PostalZip | 'PostalZip';
+ title: string;
+ settings: PostalZipGenerationOptions;
+ id?: string;
+}
+interface RegionDataTypeRow {
+ plugin: DataType.Region | 'Region';
+ title: string;
+ settings: RegionGenerationOptions;
+ id?: string;
+}
+interface RutDataTypeRow {
+ plugin: DataType.Rut | 'Rut';
+ title: string;
+ settings: RutGenerationOptions;
+ id?: string;
+}
+interface StreetAddressDataTypeRow {
+ plugin: DataType.StreetAddress | 'StreetAddress';
+ title: string;
+ settings: StreetAddressGenerationOptions;
+ id?: string;
+}
+interface TextFixedDataTypeRow {
+ plugin: DataType.TextFixed | 'TextFixed';
+ title: string;
+ settings: TextFixedGenerationOptions;
+ id?: string;
+}
+interface TextRandomDataTypeRow {
+ plugin: DataType.TextRandom | 'TextRandom';
+ title: string;
+ settings: TextRandomGenerationOptions;
+ id?: string;
+}
+interface TimeDataTypeRow {
+ plugin: DataType.Time | 'Time';
+ title: string;
+ settings: TimeGenerationOptions;
+ id?: string;
+}
+interface Track1DataTypeRow {
+ plugin: DataType.Track1 | 'Track1';
+ title: string;
+ settings: Track1GenerationOptions;
+ id?: string;
+}
+interface Track2DataTypeRow {
+ plugin: DataType.Track2 | 'Track2';
+ title: string;
+ settings: Track2GenerationOptions;
+ id?: string;
+}
+interface URLsDataTypeRow {
+ plugin: DataType.URLs | 'URLs';
+ title: string;
+ settings: URLsGenerationOptions;
+ id?: string;
+}
+interface WeightedListDataTypeRow {
+ plugin: DataType.WeightedList | 'WeightedList';
+ title: string;
+ settings: WeightedListGenerationOptions;
+ id?: string;
+}
+
+export type DataTemplateRow =
+ | AlphanumericDataTypeRow
+ | AutoIncrementDataTypeRow
+ | BooleanDataTypeRow
+ | CVVDataTypeRow
+ | CityDataTypeRow
+ | ColourDataTypeRow
+ | CompanyDataTypeRow
+ | ComputedDataTypeRow
+ | ConstantDataTypeRow
+ | CountryDataTypeRow
+ | CurrencyDataTypeRow
+ | DateDataTypeRow
+ | EmailDataTypeRow
+ | GUIDDataTypeRow
+ | IBANDataTypeRow
+ | LatLngDataTypeRow
+ | ListDataTypeRow
+ | NamesDataTypeRow
+ | NormalDistributionDataTypeRow
+ | NumberRangeDataTypeRow
+ | PANDataTypeRow
+ | PINDataTypeRow
+ | PhoneDataTypeRow
+ | PostalZipDataTypeRow
+ | RegionDataTypeRow
+ | RutDataTypeRow
+ | StreetAddressDataTypeRow
+ | TextFixedDataTypeRow
+ | TextRandomDataTypeRow
+ | TimeDataTypeRow
+ | Track1DataTypeRow
+ | Track2DataTypeRow
+ | URLsDataTypeRow
+ | WeightedListDataTypeRow;
+
+import { GenerationOptionsType as CSVGenerationOptions } from './exportTypes/CSV/CSV.state';
+import { GenerationOptionsType as CSharpGenerationOptions } from './exportTypes/CSharp/CSharp.state';
+import { GenerationOptionsType as HTMLGenerationOptions } from './exportTypes/HTML/HTML.state';
+import { GenerationOptionsType as JSONGenerationOptions } from './exportTypes/JSON/JSON.state';
+import { GenerationOptionsType as JavascriptGenerationOptions } from './exportTypes/Javascript/Javascript.state';
+import { GenerationOptionsType as LDIFGenerationOptions } from './exportTypes/LDIF/LDIF.state';
+import { GenerationOptionsType as PHPGenerationOptions } from './exportTypes/PHP/PHP.state';
+import { GenerationOptionsType as PerlGenerationOptions } from './exportTypes/Perl/Perl.state';
+import { GenerationOptionsType as PythonGenerationOptions } from './exportTypes/Python/Python.state';
+import { GenerationOptionsType as RubyGenerationOptions } from './exportTypes/Ruby/Ruby.state';
+import { GenerationOptionsType as SQLGenerationOptions } from './exportTypes/SQL/SQL.state';
+import { GenerationOptionsType as TypescriptGenerationOptions } from './exportTypes/Typescript/Typescript.state';
+import { GenerationOptionsType as XMLGenerationOptions } from './exportTypes/XML/XML.state';
+interface CSVExportTypeConfig {
+ plugin: ExportType.CSV | 'CSV';
+ settings: CSVGenerationOptions;
+}
+interface CSharpExportTypeConfig {
+ plugin: ExportType.CSharp | 'CSharp';
+ settings: CSharpGenerationOptions;
+}
+interface HTMLExportTypeConfig {
+ plugin: ExportType.HTML | 'HTML';
+ settings: HTMLGenerationOptions;
+}
+interface JSONExportTypeConfig {
+ plugin: ExportType.JSON | 'JSON';
+ settings: JSONGenerationOptions;
+}
+interface JavascriptExportTypeConfig {
+ plugin: ExportType.Javascript | 'Javascript';
+ settings: JavascriptGenerationOptions;
+}
+interface LDIFExportTypeConfig {
+ plugin: ExportType.LDIF | 'LDIF';
+ settings: LDIFGenerationOptions;
+}
+interface PHPExportTypeConfig {
+ plugin: ExportType.PHP | 'PHP';
+ settings: PHPGenerationOptions;
+}
+interface PerlExportTypeConfig {
+ plugin: ExportType.Perl | 'Perl';
+ settings: PerlGenerationOptions;
+}
+interface PythonExportTypeConfig {
+ plugin: ExportType.Python | 'Python';
+ settings: PythonGenerationOptions;
+}
+interface RubyExportTypeConfig {
+ plugin: ExportType.Ruby | 'Ruby';
+ settings: RubyGenerationOptions;
+}
+interface SQLExportTypeConfig {
+ plugin: ExportType.SQL | 'SQL';
+ settings: SQLGenerationOptions;
+}
+interface TypescriptExportTypeConfig {
+ plugin: ExportType.Typescript | 'Typescript';
+ settings: TypescriptGenerationOptions;
+}
+interface XMLExportTypeConfig {
+ plugin: ExportType.XML | 'XML';
+ settings: XMLGenerationOptions;
+}
+
+export type ExportTypeConfig =
+ | CSVExportTypeConfig
+ | CSharpExportTypeConfig
+ | HTMLExportTypeConfig
+ | JSONExportTypeConfig
+ | JavascriptExportTypeConfig
+ | LDIFExportTypeConfig
+ | PHPExportTypeConfig
+ | PerlExportTypeConfig
+ | PythonExportTypeConfig
+ | RubyExportTypeConfig
+ | SQLExportTypeConfig
+ | TypescriptExportTypeConfig
+ | XMLExportTypeConfig;
+
+export enum ExportType {
+ CSV = 'CSV',
+ CSharp = 'CSharp',
+ HTML = 'HTML',
+ JSON = 'JSON',
+ Javascript = 'Javascript',
+ LDIF = 'LDIF',
+ PHP = 'PHP',
+ Perl = 'Perl',
+ Python = 'Python',
+ Ruby = 'Ruby',
+ SQL = 'SQL',
+ Typescript = 'Typescript',
+ XML = 'XML'
+}
+
+export const getCountryList = (): string[] => countryList;
+
+export const getCountryData = (): CountryDataType => {
+ const localeStrings = getStrings();
+
+ const data: any = {};
+ countryList.map((country: CountryType) => {
+ data[country] = countryMethods[country](localeStrings.countries[country]);
+ });
+
+ return data;
+};
+
+const countryRawData =
+ "AF|Afghanistan`AX|Åland Islands`AL|Albania`DZ|Algeria`AS|American Samoa`AD|Andorra`AO|Angola`AI|Anguilla`AQ|Antarctica`AG|Antigua and Barbuda`AR|Argentina`AM|Armenia`AW|Aruba`AU|Australia`AT|Austria`AZ|Azerbaijan`BS|Bahamas`BH|Bahrain`BD|Bangladesh`BB|Barbados`BY|Belarus`BE|Belgium`BZ|Belize`BJ|Benin`BM|Bermuda`BT|Bhutan`BO|Bolivia`BQ|Bonaire, Sint Eustatius and Saba`BA|Bosnia and Herzegovina`BW|Botswana`BV|Bouvet Island`BR|Brazil`IO|British Indian Ocean Territory`BN|Brunei Darussalam`BG|Bulgaria`BF|Burkina Faso`BI|Burundi`KH|Cambodia`CM|Cameroon`CA|Canada`CV|Cape Verde`KY|Cayman Islands`CF|Central African Republic`TD|Chad`CL|Chile`CN|China`CX|Christmas Island`CC|Cocos (Keeling) Islands`CO|Colombia`KM|Comoros`CG|Congo, Republic of the (Brazzaville)`CD|Congo, the Democratic Republic of the (Kinshasa)`CK|Cook Islands`CR|Costa Rica`CI|Côte d'Ivoire, Republic of`HR|Croatia`CU|Cuba`CW|Curaçao`CY|Cyprus`CZ|Czech Republic`DK|Denmark`DJ|Djibouti`DM|Dominica`DO|Dominican Republic`EC|Ecuador`EG|Egypt`SV|El Salvador`GQ|Equatorial Guinea`ER|Eritrea`EE|Estonia`ET|Ethiopia`FK|Falkland Islands (Islas Malvinas)`FO|Faroe Islands`FJ|Fiji`FI|Finland`FR|France`GF|French Guiana`PF|French Polynesia`TF|French Southern and Antarctic Lands`GA|Gabon`GM|Gambia, The`GE|Georgia`DE|Germany`GH|Ghana`GI|Gibraltar`GR|Greece`GL|Greenland`GD|Grenada`GP|Guadeloupe`GU|Guam`GT|Guatemala`GG|Guernsey`GN|Guinea`GW|Guinea-Bissau`GY|Guyana`HT|Haiti`HM|Heard Island and McDonald Islands`VA|Holy See (Vatican City)`HN|Honduras`HK|Hong Kong`HU|Hungary`IS|Iceland`IN|India`ID|Indonesia`IR|Iran, Islamic Republic of`IQ|Iraq`IE|Ireland`IM|Isle of Man`IL|Israel`IT|Italy`JM|Jamaica`JP|Japan`JE|Jersey`JO|Jordan`KZ|Kazakhstan`KE|Kenya`KI|Kiribati`KP|Korea, Democratic People's Republic of`KR|Korea, Republic of`KS|Kosovo`KW|Kuwait`KG|Kyrgyzstan`LA|Laos`LV|Latvia`LB|Lebanon`LS|Lesotho`LR|Liberia`LY|Libya`LI|Liechtenstein`LT|Lithuania`LU|Luxembourg`MO|Macao`MK|Macedonia, Republic of`MG|Madagascar`MW|Malawi`MY|Malaysia`MV|Maldives`ML|Mali`MT|Malta`MH|Marshall Islands`MQ|Martinique`MR|Mauritania`MU|Mauritius`YT|Mayotte`MX|Mexico`FM|Micronesia, Federated States of`MD|Moldova`MC|Monaco`MN|Mongolia`ME|Montenegro`MS|Montserrat`MA|Morocco`MZ|Mozambique`MM|Myanmar`NA|Namibia`NR|Nauru`NP|Nepal`NL|Netherlands`NC|New Caledonia`NZ|New Zealand`NI|Nicaragua`NE|Niger`NG|Nigeria`NU|Niue`NF|Norfolk Island`MP|Northern Mariana Islands`NO|Norway`OM|Oman`PK|Pakistan`PW|Palau`PS|Palestine, State of`PA|Panama`PG|Papua New Guinea`PY|Paraguay`PE|Peru`PH|Philippines`PN|Pitcairn`PL|Poland`PT|Portugal`PR|Puerto Rico`QA|Qatar`RE|Réunion`RO|Romania`RU|Russian Federation`RW|Rwanda`BL|Saint Barthélemy`SH|Saint Helena, Ascension and Tristan da Cunha`KN|Saint Kitts and Nevis`LC|Saint Lucia`MF|Saint Martin`PM|Saint Pierre and Miquelon`VC|Saint Vincent and the Grenadines`WS|Samoa`SM|San Marino`ST|Sao Tome and Principe`SA|Saudi Arabia`SN|Senegal`RS|Serbia`SC|Seychelles`SL|Sierra Leone`SG|Singapore`SX|Sint Maarten (Dutch part)`SK|Slovakia`SI|Slovenia`SB|Solomon Islands`SO|Somalia`ZA|South Africa`GS|South Georgia and South Sandwich Islands`SS|South Sudan`ES|Spain`LK|Sri Lanka`SD|Sudan`SR|Suriname`SZ|Swaziland`SE|Sweden`CH|Switzerland`SY|Syrian Arab Republic`TW|Taiwan`TJ|Tajikistan`TZ|Tanzania, United Republic of`TH|Thailand`TL|Timor-Leste`TG|Togo`TK|Tokelau`TO|Tonga`TT|Trinidad and Tobago`TN|Tunisia`TR|Turkey`TM|Turkmenistan`TC|Turks and Caicos Islands`TV|Tuvalu`UG|Uganda`UA|Ukraine`AE|United Arab Emirates`GB|United Kingdom`US|United States`UM|United States Minor Outlying Islands`UY|Uruguay`UZ|Uzbekistan`VU|Vanuatu`VE|Venezuela, Bolivarian Republic of`VN|Vietnam`VG|Virgin Islands, British`VI|Virgin Islands, U.S.`WF|Wallis and Futuna`EH|Western Sahara`YE|Yemen`ZM|Zambia`ZW|Zimbabwe";
+const regionRawData =
+ 'AB|Alberta`BC|British Columbia`MB|Manitoba`NB|New Brunswick`NL|Newfoundland and Labrador`NT|Northwest Territories`NS|Nova Scotia`NU|Nunavut`ON|Ontario`PE|Prince Edward Island`QC|Quebec`SK|Saskatchewan`YT|Yukon';
+
+const convert = (data: string): any[] => {
+ const pairs = data.split('`');
+
+ return pairs.map((pair) => {
+ const [value, label] = pair.split('|');
+ return { value, label };
+ });
+};
+
+export const countryDropdownOptions = convert(countryRawData);
+export const canadianProvinceOptions = convert(regionRawData);
+
+// export const getCountryNameMap = (): CountryMap => {
+// const localeStrings = getStrings();
+
+// const data: CountryMap = {};
+// countryList.map((country: CountryType) => {
+// data[country] = localeStrings.countries[country];
+// });
+
+// return data;
+// };
diff --git a/packages/plugins/src/names.ts b/packages/plugins/src/names.ts
new file mode 100644
index 000000000..c5cd45661
--- /dev/null
+++ b/packages/plugins/src/names.ts
@@ -0,0 +1,38 @@
+import Australia from './countries/Australia/names';
+import Austria from './countries/Austria/names';
+import Belgium from './countries/Belgium/names';
+import Brazil from './countries/Brazil/names';
+import Canada from './countries/Canada/names';
+import Chile from './countries/Chile/names';
+import China from './countries/China/names';
+import Germany from './countries/Germany/names';
+import India from './countries/India/names';
+import Netherlands from './countries/Netherlands/names';
+import Nigeria from './countries/Nigeria/names';
+import Singapore from './countries/Singapore/names';
+import Spain from './countries/Spain/names';
+import Turkey from './countries/Turkey/names';
+import US from './countries/US/names';
+import Vietnam from './countries/Vietnam/names';
+
+const nameFiles = {
+ Australia,
+ Austria,
+ Belgium,
+ Brazil,
+ Canada,
+ Chile,
+ China,
+ Germany,
+ India,
+ Netherlands,
+ Nigeria,
+ Singapore,
+ Spain,
+ Turkey,
+ US,
+ Vietnam
+};
+export default nameFiles;
+
+export type CountryNameFiles = keyof typeof nameFiles;
diff --git a/packages/plugins/tsconfig.json b/packages/plugins/tsconfig.json
new file mode 100644
index 000000000..088e0876e
--- /dev/null
+++ b/packages/plugins/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "target": "esnext",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "declaration": true,
+ "skipLibCheck": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src/*.ts", "./typings"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/types/countries.d.ts b/packages/types/countries.d.ts
new file mode 100644
index 000000000..6bc4f1634
--- /dev/null
+++ b/packages/types/countries.d.ts
@@ -0,0 +1,41 @@
+export interface GetCountryData {
+ (i18n: any): CountryDataType;
+}
+
+// TODO move to plugins package
+export type CountryDataType = {
+ countrySlug: string;
+ countryName: string;
+ regionNames: string;
+ continent: Continent;
+ regions: Region[];
+ extendedData: ExtendedData;
+};
+
+export type CountryNames = {
+ femaleNames: string[];
+ maleNames: string[];
+ lastNames: string[];
+};
+
+export type Continent = 'africa' | 'asia' | 'central_america' | 'europe' | 'oceania' | 'south_america' | 'north_america';
+
+export type ExtendedData = {
+ zipFormat: {
+ format: string;
+ replacements?: any;
+ };
+ phoneFormat?: {
+ areaCodes?: number[] | string[];
+ displayFormats?: string[];
+ };
+};
+
+export type Region = {
+ regionName: string;
+ regionShort: string;
+ regionSlug: string;
+ weight: number;
+ cities: string[];
+ extendedData?: ExtendedData;
+};
diff --git a/packages/types/dataSets.d.ts b/packages/types/dataSets.d.ts
new file mode 100644
index 000000000..4cdf52fad
--- /dev/null
+++ b/packages/types/dataSets.d.ts
@@ -0,0 +1,11 @@
+export type DataSetListItem = {
+ historyId: number;
+ dataSetId: number;
+ dataSetName: string;
+ status: 'private' | 'public';
+ accountId: number;
+ content: string;
+ numRowsGenerated: number;
+ dateCreatedUnix: string;
+ historyDateCreatedUnix: string;
+};
diff --git a/packages/types/generator.d.ts b/packages/types/generator.d.ts
new file mode 100644
index 000000000..df45f5272
--- /dev/null
+++ b/packages/types/generator.d.ts
@@ -0,0 +1,18 @@
+export const enum GeneratorLayout {
+ horizontal = 'horizontal',
+ vertical = 'vertical'
+}
+
+// this is for Data Types to describe how their field should be described for the SQL DB table creation statement
+export type DatabaseTypes = {
+ // e.g. "varchar(50)". This is the default value used for all DB types if they're not defined in one of the custom
+ // properties below
+ field?: string;
+
+ // database type-specific field descriptions
+ field_Oracle?: string;
+ field_MySQL?: string;
+ field_MSSQL?: string;
+ field_Postgres?: string;
+ field_SQLite?: string;
+};
diff --git a/packages/types/index.d.ts b/packages/types/index.d.ts
new file mode 100644
index 000000000..f2389aa74
--- /dev/null
+++ b/packages/types/index.d.ts
@@ -0,0 +1,7 @@
+export * from './countries';
+export * from './dataSets';
+export * from './dataTypes';
+export * from '../../apps/client/types/exportTypes';
+export * from './generator';
+
+export { GDLocale, GDLocaleMap } from '@generatedata/config';
diff --git a/packages/types/locales.d.ts b/packages/types/locales.d.ts
new file mode 100644
index 000000000..585bf186b
--- /dev/null
+++ b/packages/types/locales.d.ts
@@ -0,0 +1,2 @@
+// TODO
+type GDLocale = [];
diff --git a/packages/types/node_modules/@generatedata/config b/packages/types/node_modules/@generatedata/config
new file mode 120000
index 000000000..101d54382
--- /dev/null
+++ b/packages/types/node_modules/@generatedata/config
@@ -0,0 +1 @@
+../../../config
\ No newline at end of file
diff --git a/packages/types/package.json b/packages/types/package.json
new file mode 100644
index 000000000..39588b175
--- /dev/null
+++ b/packages/types/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@generatedata/types",
+ "description": "Shared types for the suite of @generatedata packages",
+ "version": "1.0.0",
+ "module": "./index.d.ts",
+ "devDependencies": {
+ "@generatedata/config": "workspace:*"
+ }
+}
diff --git a/packages/utils/.gitignore b/packages/utils/.gitignore
new file mode 100644
index 000000000..4f12aa03a
--- /dev/null
+++ b/packages/utils/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+dist/*
\ No newline at end of file
diff --git a/packages/utils/README.md b/packages/utils/README.md
new file mode 100644
index 000000000..ae79821f2
--- /dev/null
+++ b/packages/utils/README.md
@@ -0,0 +1,3 @@
+## @generatedata/utils
+
+General utility methods for use across the front and backend packages.
diff --git a/packages/utils/package.json b/packages/utils/package.json
new file mode 100644
index 000000000..e9a816e2a
--- /dev/null
+++ b/packages/utils/package.json
@@ -0,0 +1,72 @@
+{
+ "name": "@generatedata/utils",
+ "description": "Utility methods and code",
+ "scripts": {
+ "clean": "",
+ "build": "tsc"
+ },
+ "dependencies": {
+ "@mui/material": "^7.3.1",
+ "@mui/lab": "^7.0.0-beta.16",
+ "bcrypt": "^5.0.0",
+ "date-fns": "^2.17.0",
+ "nanoid": "^3.3.4"
+ },
+ "devDependencies": {
+ "@date-io/date-fns": "^1.3.13",
+ "@generatedata/config": "workspace:*",
+ "@generatedata/types": "workspace:*",
+ "@testing-library/react": "^10.4.7",
+ "@types/jest": "^29",
+ "@types/sinon": "^7.5.1",
+ "jest": "^29.4.3",
+ "sinon": "^8.0.4",
+ "typescript": "^5.7.3"
+ },
+ "exports": {
+ ".": {
+ "default": "./dist/index.js",
+ "types": "./dist/index.d.ts"
+ },
+ "./array": {
+ "default": "./dist/array.js",
+ "types": "./dist/array.d.ts"
+ },
+ "./auth": {
+ "default": "./dist/auth.js",
+ "types": "./dist/auth.d.ts"
+ },
+ "./country": {
+ "default": "./dist/country.js",
+ "types": "./dist/country.d.ts"
+ },
+ "./date": {
+ "default": "./dist/date.js",
+ "types": "./dist/date.d.ts"
+ },
+ "./extension": {
+ "default": "./dist/extension.js",
+ "types": "./dist/extension.d.ts"
+ },
+ "./general": {
+ "default": "./dist/general.js",
+ "types": "./dist/general.d.ts"
+ },
+ "./lang": {
+ "default": "./dist/lang.js",
+ "types": "./dist/lang.d.ts"
+ },
+ "./random": {
+ "default": "./dist/random.js",
+ "types": "./dist/random.d.ts"
+ },
+ "./number": {
+ "default": "./dist/number.js",
+ "types": "./dist/number.d.ts"
+ },
+ "./string": {
+ "default": "./dist/string.js",
+ "types": "./dist/string.d.ts"
+ }
+ }
+}
diff --git a/packages/utils/src/__tests__/arrayUtils.test.ts b/packages/utils/src/__tests__/arrayUtils.test.ts
new file mode 100644
index 000000000..53cf6c10e
--- /dev/null
+++ b/packages/utils/src/__tests__/arrayUtils.test.ts
@@ -0,0 +1,42 @@
+import * as arrayUtils from '../array';
+
+describe('getUnique', () => {
+ it('generates expected characters', () => {
+ expect(arrayUtils.getUnique([1, 1, 1, 2])).toEqual([1, 2]);
+ expect(arrayUtils.getUnique([])).toEqual([]);
+ });
+});
+
+describe('getArrayOfSize', () => {
+ it('generated iterable array of specific size', () => {
+ const arr = arrayUtils.getArrayOfSize(10);
+ const newArray = arr.map((i, index) => index);
+
+ expect(arr.length).toEqual(10);
+ expect(newArray).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ });
+});
+
+describe('removeItem', () => {
+ it('removes an item from an array', () => {
+ expect(arrayUtils.removeItem([1, 2, 3], 1)).toEqual([2, 3]);
+ });
+
+ it('removes an item from an array', () => {
+ expect(arrayUtils.removeItem([1, 2, 3], 1)).toEqual([2, 3]);
+ });
+
+ it('uses strict comparison check for comparison', () => {
+ expect(arrayUtils.removeItem(['1', '2', '3'], 1)).toEqual(['1', '2', '3']);
+ });
+
+ it('removes all items with the value', () => {
+ expect(arrayUtils.removeItem([5, 11, 2, 29, 2, 22, 2], 2)).toEqual([5, 11, 29, 22]);
+ });
+
+ it('bounds checking', () => {
+ expect(arrayUtils.removeItem([], 1)).toEqual([]);
+ expect(arrayUtils.removeItem([1], 1)).toEqual([]);
+ expect(arrayUtils.removeItem([1, 1, 1, 1, 1], 1)).toEqual([]);
+ });
+});
diff --git a/packages/utils/src/__tests__/dataTypeUtils.test.ts b/packages/utils/src/__tests__/dataTypeUtils.test.ts
new file mode 100644
index 000000000..07791d1db
--- /dev/null
+++ b/packages/utils/src/__tests__/dataTypeUtils.test.ts
@@ -0,0 +1,145 @@
+// import { getProcessBatches, getAffectedDataTypes } from '../dataTypeUtils';
+
+// describe('getProcessBatches', () => {
+// it('data types with no dependencies all get added to the first process batch', () => {
+// const testDataTypes = {
+// One: {},
+// Two: {},
+// Three: {}
+// };
+// expect(getProcessBatches(testDataTypes)).toEqual({ One: 1, Two: 1, Three: 1 });
+// });
+
+// it('data types with a dependency end up in the second batch', () => {
+// const testDataTypes = {
+// One: {
+// dependencies: ['Two']
+// },
+// Two: {},
+// Three: {}
+// };
+// expect(getProcessBatches(testDataTypes)).toEqual({ One: 2, Two: 1, Three: 1 });
+// });
+
+// it('a chain of dependencies results in separate batches for each one', () => {
+// const testDataTypes = {
+// One: {},
+// Two: {
+// dependencies: ['One']
+// },
+// Three: {
+// dependencies: ['Two']
+// },
+// Four: {
+// dependencies: ['Three']
+// }
+// };
+// expect(getProcessBatches(testDataTypes)).toEqual({ One: 1, Two: 2, Three: 3, Four: 4 });
+// });
+
+// it('multiple items with fulfilled items should end up the same batch', () => {
+// const testDataTypes = {
+// One: {},
+// Two: {
+// dependencies: ['One']
+// },
+// Three: {
+// dependencies: ['Two']
+// },
+// Four: {
+// dependencies: ['Two']
+// },
+// Five: {
+// dependencies: ['Three']
+// },
+// Six: {
+// dependencies: ['Four']
+// },
+// Seven: {
+// dependencies: ['Three']
+// }
+// };
+// expect(getProcessBatches(testDataTypes)).toEqual({
+// One: 1,
+// Two: 2,
+// Three: 3,
+// Four: 3,
+// Five: 4,
+// Six: 4,
+// Seven: 4
+// });
+// });
+
+// it('recursive dependencies throw an error', () => {
+// const testDataTypes = {
+// One: {
+// dependencies: ['Two']
+// },
+// Two: {
+// dependencies: ['One']
+// }
+// };
+// try {
+// getProcessBatches(testDataTypes);
+// } catch (e) {
+// expect(e.name).toEqual('Recursive dependency');
+// }
+// });
+
+// it('recursive dependencies triggered after first batch throw an error', () => {
+// const testDataTypes = {
+// One: {
+// dependencies: ['Two']
+// },
+// Two: {
+// dependencies: ['One']
+// },
+// Three: {}
+// };
+// try {
+// getProcessBatches(testDataTypes);
+// } catch (e) {
+// expect(e.name).toEqual('Recursive dependency');
+// }
+// });
+// });
+
+// describe('getAffectedDataTypes', () => {
+// it('calculates which Data Types are affected for each Data Type change #1', () => {
+// const testDataTypes = {
+// One: {
+// dependencies: ['Four']
+// },
+// Two: {},
+// Three: {},
+// Four: {}
+// };
+
+// expect(getAffectedDataTypes(testDataTypes)).toEqual({
+// One: [],
+// Two: [],
+// Three: [],
+// Four: ['One']
+// });
+// });
+
+// it('calculates which Data Types are affected for each Data Type change #2', () => {
+// const testDataTypes = {
+// One: {
+// dependencies: ['Four', 'Two', 'Three']
+// },
+// Two: {
+// dependencies: ['Three']
+// },
+// Three: {},
+// Four: {}
+// };
+
+// expect(getAffectedDataTypes(testDataTypes)).toEqual({
+// One: [],
+// Two: ['One'],
+// Three: ['One', 'Two'],
+// Four: ['One']
+// });
+// });
+// });
diff --git a/packages/utils/src/__tests__/dateUtils.test.ts b/packages/utils/src/__tests__/dateUtils.test.ts
new file mode 100644
index 000000000..1cde050d6
--- /dev/null
+++ b/packages/utils/src/__tests__/dateUtils.test.ts
@@ -0,0 +1,18 @@
+import { formatDuration } from '../date';
+
+describe('formatDuration', () => {
+ it('displays < 60 seconds as seconds only', () => {
+ expect(formatDuration(10)).toEqual('0:10');
+ expect(formatDuration(20)).toEqual('0:20');
+ expect(formatDuration(1)).toEqual('0:01');
+ expect(formatDuration(59)).toEqual('0:59');
+ });
+
+ it('displays < 60 minutes as minutes & seconds', () => {
+ expect(formatDuration(60)).toEqual('1:00');
+ expect(formatDuration(61)).toEqual('1:01');
+ expect(formatDuration(90)).toEqual('1:30');
+ expect(formatDuration(120)).toEqual('2:00');
+ expect(formatDuration(1000)).toEqual('16:40');
+ });
+});
diff --git a/packages/utils/src/__tests__/generalUtils.test.ts b/packages/utils/src/__tests__/generalUtils.test.ts
new file mode 100644
index 000000000..a4eae3ba0
--- /dev/null
+++ b/packages/utils/src/__tests__/generalUtils.test.ts
@@ -0,0 +1,50 @@
+import * as generalUtils from '../general';
+
+describe('isBoolean', () => {
+ it('evaluates as booleans', () => {
+ expect(generalUtils.isBoolean('one')).toEqual(false);
+ expect(generalUtils.isBoolean('true')).toEqual(false);
+ expect(generalUtils.isBoolean('false')).toEqual(false);
+ expect(generalUtils.isBoolean(false)).toEqual(true);
+ expect(generalUtils.isBoolean(true)).toEqual(true);
+ expect(generalUtils.isBoolean(1)).toEqual(false);
+ expect(generalUtils.isBoolean(0)).toEqual(false);
+ expect(generalUtils.isBoolean(undefined)).toEqual(false);
+ expect(generalUtils.isBoolean(null)).toEqual(false);
+ expect(generalUtils.isBoolean(NaN)).toEqual(false);
+ });
+});
+
+describe('cloneObj', () => {
+ it('clones an object', () => {
+ const obj = { one: 114 };
+ expect(obj === obj).toBeTruthy();
+
+ const clone: any = generalUtils.cloneObj(obj);
+ expect(obj === clone).toBeFalsy();
+ expect(obj.one === clone.one).toBeTruthy();
+ });
+});
+
+describe('template', () => {
+ it('renders plain string', () => {
+ expect(generalUtils.template('hello', {})).toEqual('hello');
+ });
+
+ it('renders placeholder', () => {
+ expect(generalUtils.template('Hello {{name}}', { name: 'Ben' })).toEqual('Hello Ben');
+ });
+
+ it('check basic math', () => {
+ expect(generalUtils.template('Hello {{value+1}}', { value: 1 })).toEqual('Hello 2');
+ expect(generalUtils.template('{{value/10}}', { value: 250 })).toEqual('25');
+ });
+});
+
+describe('isValidEmail', () => {
+ it('validates email as expected', () => {
+ expect(generalUtils.isValidEmail('nope')).toEqual(false);
+ expect(generalUtils.isValidEmail('')).toEqual(false);
+ expect(generalUtils.isValidEmail('a@b.com')).toEqual(true);
+ });
+});
diff --git a/packages/utils/src/__tests__/langUtils.test.ts b/packages/utils/src/__tests__/langUtils.test.ts
new file mode 100644
index 000000000..01535536c
--- /dev/null
+++ b/packages/utils/src/__tests__/langUtils.test.ts
@@ -0,0 +1,92 @@
+import { getI18nString, getCurrentLocalizedPath } from '../lang';
+
+describe('getI18nString', () => {
+ it('bounds checking', () => {
+ expect(getI18nString('test', [])).toEqual('test');
+ expect(getI18nString('test', ['whatever'])).toEqual('test');
+ expect(getI18nString('', [])).toEqual('');
+ });
+
+ it('replaces as expected', () => {
+ expect(getI18nString('Hello %1', ['world'])).toEqual('Hello world');
+ expect(getI18nString('Double %1 %1 test', ['tap'])).toEqual('Double tap tap test');
+ expect(getI18nString('Nonexistent placeholder %1 test', [])).toEqual('Nonexistent placeholder %1 test');
+ expect(getI18nString('Multiple %1 %2', ['placeholder', 'test'])).toEqual('Multiple placeholder test');
+ });
+});
+
+describe('getCurrentLocalizedPath', () => {
+ const { location } = window;
+
+ beforeAll(() => {
+ // @ts-ignore
+ delete window.location;
+ });
+
+ afterAll(() => {
+ window.location = location;
+ });
+
+ it('gets expected localized path from blank English path', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '' };
+
+ expect(getCurrentLocalizedPath('fr')).toEqual('/fr');
+ });
+
+ it('gets expected localized path from blank English path with trailing slash', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/' };
+
+ expect(getCurrentLocalizedPath('fr')).toEqual('/fr');
+ });
+
+ it('appends locale to existing path (single folder)', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/accounts' };
+
+ expect(getCurrentLocalizedPath('de')).toEqual('/de/accounts');
+ });
+
+ it('appends locale to existing path (single folder) with trailing slash', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/accounts/' };
+
+ expect(getCurrentLocalizedPath('de')).toEqual('/de/accounts');
+ });
+
+ it('appends locale to existing path (multiple folders)', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/accounts/subpagehere' };
+
+ expect(getCurrentLocalizedPath('de')).toEqual('/de/accounts/subpagehere');
+ });
+
+ it('appends locale to existing path (single folder) with trailing slash', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/accounts/subpagehere/' };
+
+ expect(getCurrentLocalizedPath('de')).toEqual('/de/accounts/subpagehere');
+ });
+
+ it('invalid localization strings just returns the original path', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/accounts' };
+
+ expect(getCurrentLocalizedPath('zz' as any)).toEqual('/accounts');
+ });
+
+ it('English does not change an existing english path', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/accounts' };
+
+ expect(getCurrentLocalizedPath('en')).toEqual('/accounts');
+ });
+
+ it('English removes an existing non-English path', () => {
+ // @ts-ignore-line
+ window.location = { pathname: '/fr/accounts' };
+
+ expect(getCurrentLocalizedPath('en')).toEqual('/accounts');
+ });
+});
diff --git a/packages/utils/src/__tests__/numberUtils.test.ts b/packages/utils/src/__tests__/numberUtils.test.ts
new file mode 100644
index 000000000..cc6c5210a
--- /dev/null
+++ b/packages/utils/src/__tests__/numberUtils.test.ts
@@ -0,0 +1,35 @@
+import * as numberUtils from '../number';
+
+describe('isNumeric', () => {
+ it('evaluates numeric values correctly', () => {
+ expect(numberUtils.isNumeric(0)).toEqual(true);
+ expect(numberUtils.isNumeric(1)).toEqual(true);
+ expect(numberUtils.isNumeric(1.5)).toEqual(true);
+ expect(numberUtils.isNumeric('0')).toEqual(true);
+ expect(numberUtils.isNumeric('0.5')).toEqual(true);
+ });
+
+ it('evaluates non-numeric values correctly', () => {
+ expect(numberUtils.isNumeric('0x')).toEqual(false);
+ expect(numberUtils.isNumeric('blah')).toEqual(false);
+ });
+});
+
+describe('numberFormat', () => {
+ it('formats numbers as expected', () => {
+ expect(numberUtils.numberFormat(1234.56)).toEqual('1,235');
+ expect(numberUtils.numberFormat(1234.56, 1)).toEqual('1,234.6');
+ expect(numberUtils.numberFormat(1234.56, 2, ',', ' ')).toEqual('1 234,56');
+ expect(numberUtils.numberFormat(1234.5678, 2, '.', '')).toEqual('1234.57');
+ expect(numberUtils.numberFormat(67, 2, ',', '.')).toEqual('67,00');
+ expect(numberUtils.numberFormat(1000)).toEqual('1,000');
+ expect(numberUtils.numberFormat(67.311, 2)).toEqual('67.31');
+ expect(numberUtils.numberFormat(1000.55, 1)).toEqual('1,000.6');
+ expect(numberUtils.numberFormat(67000, 5, ',', '.')).toEqual('67.000,00000');
+ expect(numberUtils.numberFormat(0.9, 0)).toEqual('1');
+ expect(numberUtils.numberFormat(1.2, 2)).toEqual('1.20');
+ expect(numberUtils.numberFormat(1.2, 4)).toEqual('1.2000');
+ expect(numberUtils.numberFormat(1.2, 3)).toEqual('1.200');
+ expect(numberUtils.numberFormat(1e-8, 8, '.', '')).toEqual('0.00000001');
+ });
+});
diff --git a/packages/utils/src/__tests__/randomUtils.test.ts b/packages/utils/src/__tests__/randomUtils.test.ts
new file mode 100644
index 000000000..ec31c0aa2
--- /dev/null
+++ b/packages/utils/src/__tests__/randomUtils.test.ts
@@ -0,0 +1,232 @@
+import * as sinon from 'sinon';
+import * as randomUtils from '../random';
+
+describe('getRandomNum', () => {
+ it('generates numbers in expected range #1', () => {
+ const expected = [1, 2, 3];
+ for (let i = 0; i < 50; i++) {
+ const val = randomUtils.getRandomNum(1, 3);
+ expect(expected.indexOf(val) !== -1).toBeTruthy();
+ }
+ });
+
+ it('generates numbers in expected range #2', () => {
+ const expected = [5, 6, 7];
+ for (let i = 0; i < 50; i++) {
+ expect(expected.indexOf(randomUtils.getRandomNum(5, 7)) !== -1).toBeTruthy();
+ }
+ });
+
+ it('generates numbers in expected range #3', () => {
+ const expected = [0, 1];
+ for (let i = 0; i < 50; i++) {
+ expect(expected.indexOf(randomUtils.getRandomNum(0, 1)) !== -1).toBeTruthy();
+ }
+ });
+
+ it('generates numbers in expected range #4', () => {
+ const expected = [-2, -1, 0, 1, 2];
+ for (let i = 0; i < 50; i++) {
+ expect(expected.indexOf(randomUtils.getRandomNum(-2, 2)) !== -1).toBeTruthy();
+ }
+ });
+
+ it('generates numbers when number range entirely negative', () => {
+ const expected = [-4, -3, -2, -1];
+ for (let i = 0; i < 50; i++) {
+ expect(expected.indexOf(randomUtils.getRandomNum(-4, -1)) !== -1).toBeTruthy();
+ }
+ });
+
+ it('generates the same number when min and max have same values', () => {
+ for (let i = 0; i < 50; i++) {
+ expect(randomUtils.getRandomNum(0, 0) === 0).toBeTruthy();
+ expect(randomUtils.getRandomNum(-800, -800) === -800).toBeTruthy();
+ }
+ });
+});
+
+describe('generateRandomTextStr', () => {
+ const words = ['one', 'two', 'three'];
+
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('generates a single word starting from the first word', () => {
+ expect(randomUtils.generateRandomTextStr(words, true, 1)).toEqual('one');
+ });
+
+ it('generates multiple words starting from the first word', () => {
+ expect(randomUtils.generateRandomTextStr(words, true, 2)).toEqual('one two');
+ expect(randomUtils.generateRandomTextStr(words, true, 3)).toEqual('one two three');
+ });
+
+ it('generates a random word', () => {
+ sinon.stub(randomUtils, 'getRandomNum').onCall(0).returns(0);
+ expect(randomUtils.generateRandomTextStr(words, false, 1)).toEqual('one');
+ });
+
+ it('generates another random word', () => {
+ sinon.stub(randomUtils, 'getRandomNum').onCall(0).returns(1);
+ expect(randomUtils.generateRandomTextStr(words, false, 1)).toEqual('two');
+ });
+
+ it('generates two random words', () => {
+ expect(randomUtils.generateRandomTextStr(words, false, 2)).toMatch(/[a-z]+\s[a-z]+/);
+ });
+
+ it('generates the same string every time when min num of words is the total number of words', () => {
+ for (let i = 0; i < 10; i++) {
+ expect(randomUtils.generateRandomTextStr(['a', 'b', 'c'], false, 3)).toEqual('a b c');
+ }
+ });
+});
+
+describe('generateRandomAlphanumericStr', () => {
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('whitespace is trimmed', () => {
+ expect(randomUtils.generateRandomAlphanumericStr(' ')).toEqual('');
+ });
+
+ it('special characters are converted as expected', () => {
+ const str = 'CcEVvFLlDXxH';
+ for (let i = 0; i < 50; i++) {
+ const generated = randomUtils.generateRandomAlphanumericStr(str);
+
+ // C = uppercase consonant
+ expect(randomUtils.consonants.indexOf(generated[0]) !== -1).toBeTruthy();
+
+ // c = lowercase consonant
+ expect(randomUtils.lowercaseConsonants.indexOf(generated[1]) !== -1).toBeTruthy();
+
+ // A = any case consonant
+ expect(randomUtils.consonants.concat(randomUtils.lowercaseConsonants).indexOf(generated[2]) !== -1).toBeTruthy();
+
+ // V = uppercase vowel
+ expect(randomUtils.vowels.indexOf(generated[3]) !== -1).toBeTruthy();
+
+ // v = uppercase vowel
+ expect(randomUtils.lowercaseVowels.indexOf(generated[4]) !== -1).toBeTruthy();
+
+ // F = any case vowel
+ expect(randomUtils.vowels.concat(randomUtils.lowercaseVowels).indexOf(generated[5]) !== -1).toBeTruthy();
+
+ // L = uppercase letter
+ expect(randomUtils.letters.indexOf(generated[6]) !== -1).toBeTruthy();
+
+ // l = lowercase letter
+ expect(randomUtils.lowercaseLetters.indexOf(generated[7]) !== -1).toBeTruthy();
+
+ // D = any case letter
+ expect(randomUtils.letters.concat(randomUtils.lowercaseLetters).indexOf(generated[8]) !== -1).toBeTruthy();
+
+ // X = 1-9
+ expect(['1', '2', '3', '4', '5', '6', '7', '8', '9'].indexOf(generated[9]) !== -1).toBeTruthy();
+
+ // x = 0-9
+ expect(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'].indexOf(generated[10]) !== -1).toBeTruthy();
+
+ // H = 0-F
+ expect(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'].indexOf(generated[11]) !== -1).toBeTruthy();
+ }
+ });
+});
+
+describe('getRandomCharInString', () => {
+ afterEach(function () {
+ sinon.restore();
+ });
+
+ it('returns a random char', () => {
+ sinon.stub(randomUtils, 'getRandomNum').onCall(0).returns(1);
+
+ expect(randomUtils.getRandomCharInString('abc')).toEqual('b');
+ });
+
+ it('returns a random char', () => {
+ sinon.stub(randomUtils, 'getRandomNum').onCall(0).returns(2);
+
+ expect(randomUtils.getRandomCharInString('abc')).toEqual('c');
+ });
+});
+
+describe('getRandomSubset', () => {
+ it('returns an array of the expected size', () => {
+ expect(randomUtils.getRandomSubset([1, 2, 3, 4, 5], 1).length).toEqual(1);
+ expect(randomUtils.getRandomSubset([1, 2, 3, 4, 5], 2).length).toEqual(2);
+ expect(randomUtils.getRandomSubset([1, 2, 3, 4, 5], 3).length).toEqual(3);
+ expect(randomUtils.getRandomSubset([1, 2, 3, 4, 5], 4).length).toEqual(4);
+ expect(randomUtils.getRandomSubset([1, 2, 3, 4, 5], 5).length).toEqual(5);
+ });
+
+ it('returns an array of size no greater than the original size', () => {
+ expect(randomUtils.getRandomSubset([1, 2, 3, 4, 5], 10).length).toEqual(5);
+ });
+
+ it('returns the same content when passing a full array', () => {
+ expect(randomUtils.getRandomSubset([1, 2, 3], 3).sort()).toEqual([1, 2, 3].sort());
+ });
+});
+
+describe('generatePlaceholderStr', () => {
+ it('generates string using custom placeholders', () => {
+ const defaultPlaceholders = {
+ A: '1',
+ B: '2',
+ C: '3'
+ };
+ expect(randomUtils.generatePlaceholderStr('ABC', defaultPlaceholders)).toEqual('123');
+ });
+});
+
+describe('getRandomWeightedValue', () => {
+ // Out of 1, the relative and cumulative values for these are:
+ // a: 0.1666 -> 0.16666
+ // b: 0.3333 -> 0.5
+ // c: 0.5 -> 1
+ const values = { a: 10, b: 20, c: 30 };
+
+ it('returns appropriate values for particular random value', () => {
+ // any random number under 0.166666 should return "a"
+ const stub1 = sinon.stub(Math, 'random').returns(0);
+ const result1 = randomUtils.getRandomWeightedValue(values);
+ expect(result1).toEqual('a');
+ stub1.restore();
+
+ const stub2 = sinon.stub(Math, 'random').returns(0.1666);
+ const result2 = randomUtils.getRandomWeightedValue(values);
+ expect(result2).toEqual('a');
+ stub2.restore();
+
+ // any random number between 0.166666 and 0.5 should return "b"
+ const stub3 = sinon.stub(Math, 'random').returns(0.17);
+ const result3 = randomUtils.getRandomWeightedValue(values);
+ expect(result3).toEqual('b');
+ stub3.restore();
+
+ const stub4 = sinon.stub(Math, 'random').returns(0.3333);
+ const result4 = randomUtils.getRandomWeightedValue(values);
+ expect(result4).toEqual('b');
+ stub4.restore();
+
+ const stub5 = sinon.stub(Math, 'random').returns(0.5);
+ const result5 = randomUtils.getRandomWeightedValue(values);
+ expect(result5).toEqual('b');
+ stub5.restore();
+
+ // any random number above 0.5 should return "c"
+ const stub6 = sinon.stub(Math, 'random').returns(0.500001);
+ const result6 = randomUtils.getRandomWeightedValue(values);
+ expect(result6).toEqual('c');
+ stub6.restore();
+
+ const stub7 = sinon.stub(Math, 'random').returns(1);
+ const result7 = randomUtils.getRandomWeightedValue(values);
+ expect(result7).toEqual('c');
+ stub7.restore();
+ });
+});
diff --git a/packages/utils/src/__tests__/routeUtils.test.ts b/packages/utils/src/__tests__/routeUtils.test.ts
new file mode 100644
index 000000000..4c97e9fb1
--- /dev/null
+++ b/packages/utils/src/__tests__/routeUtils.test.ts
@@ -0,0 +1,41 @@
+// import { getRoutes, removeLocale } from '../routeUtils';
+
+// describe('getRoutes', () => {
+// it('returns all available routes by default with the generator page the last one with the root path', () => {
+// const defaultPaths = [
+// '/account',
+// '/accounts',
+// '/login',
+// '/datasets',
+// '/register',
+// '/'
+// ];
+
+// const foundPaths = getRoutes().map(({ path }) => path);
+// expect(foundPaths).toEqual(defaultPaths);
+// });
+// });
+
+// describe('removeLocale', () => {
+// it('removes nothing for root', () => {
+// expect(removeLocale('/')).toEqual('/');
+// expect(removeLocale('')).toEqual('');
+// });
+
+// it('removes nothing for non-locale path', () => {
+// expect(removeLocale('/accounts')).toEqual('/accounts');
+// expect(removeLocale('/blah/de/blah')).toEqual('/blah/de/blah');
+// });
+
+// it('removes valid locales from root', () => {
+// expect(removeLocale('/de')).toEqual('');
+// expect(removeLocale('/en')).toEqual('');
+// expect(removeLocale('/zz')).toEqual('/zz');
+// });
+
+// it('removes valid locales from full path', () => {
+// expect(removeLocale('/de/accounts')).toEqual('/accounts');
+// expect(removeLocale('/ta/accounts/')).toEqual('/accounts/');
+// expect(removeLocale('/zz/accounts')).toEqual('/zz/accounts');
+// });
+// });
diff --git a/packages/utils/src/__tests__/stringUtils.test.ts b/packages/utils/src/__tests__/stringUtils.test.ts
new file mode 100644
index 000000000..054102d6f
--- /dev/null
+++ b/packages/utils/src/__tests__/stringUtils.test.ts
@@ -0,0 +1,47 @@
+import * as stringUtils from '../string';
+
+describe('uppercaseWords', () => {
+ it('uppercases a single word', () => {
+ expect(stringUtils.uppercaseWords('one')).toEqual('One');
+ expect(stringUtils.uppercaseWords('ONE')).toEqual('One');
+ });
+
+ it('uppercases multiple word', () => {
+ expect(stringUtils.uppercaseWords('one two')).toEqual('One Two');
+ expect(stringUtils.uppercaseWords('ONE TWO')).toEqual('One Two');
+ expect(stringUtils.uppercaseWords('ONE-TWO')).toEqual('One-two');
+ });
+});
+
+describe('getUniqueString', () => {
+ it('returns the original string if the string is not already taken', () => {
+ expect(stringUtils.getUniqueString('one', [])).toEqual('one');
+ expect(stringUtils.getUniqueString('one', ['two', 'three'])).toEqual('one');
+ });
+ it('returns the string plus 1 if it is taken', () => {
+ expect(stringUtils.getUniqueString('one', ['one'])).toEqual('one1');
+ expect(stringUtils.getUniqueString('one', ['one', 'one1'])).toEqual('one2');
+ expect(stringUtils.getUniqueString('one', ['one', 'one1', 'one2'])).toEqual('one3');
+ expect(stringUtils.getUniqueString('one', ['one1', 'one2'])).toEqual('one');
+ });
+});
+
+describe('toSentenceCase', () => {
+ it('converts strings to sentence case as expected', () => {
+ expect(stringUtils.toSentenceCase('')).toEqual('');
+ expect(stringUtils.toSentenceCase('hello')).toEqual('Hello');
+ expect(stringUtils.toSentenceCase('hello world')).toEqual('Hello world');
+ expect(stringUtils.toSentenceCase('HELLO WORLD')).toEqual('Hello world');
+ });
+});
+
+describe('trimChars', () => {
+ it('trims characters as expected', () => {
+ expect(stringUtils.trimChars('hello', '')).toEqual('hello');
+ expect(stringUtils.trimChars('hello', '/')).toEqual('hello');
+ expect(stringUtils.trimChars('hello/', '/')).toEqual('hello');
+ expect(stringUtils.trimChars('/hello/', '/')).toEqual('hello');
+ expect(stringUtils.trimChars('/hello!', '!')).toEqual('/hello');
+ expect(stringUtils.trimChars('/hello!!!', '!')).toEqual('/hello');
+ });
+});
diff --git a/packages/utils/src/array.ts b/packages/utils/src/array.ts
new file mode 100644
index 000000000..cdca4a590
--- /dev/null
+++ b/packages/utils/src/array.ts
@@ -0,0 +1,22 @@
+export const getUnique = (arr: T[]): T[] => arr.filter((v, i, a) => a.indexOf(v) === i);
+
+// returns an empty array of a particular size
+export const getArrayOfSize = (size: number): any[] => [...Array(size)];
+
+export const removeItem = (arr: T[], value: any): T[] => {
+ let i = 0;
+ while (i < arr.length) {
+ if (arr[i] === value) {
+ arr.splice(i, 1);
+ } else {
+ ++i;
+ }
+ }
+ return arr;
+};
+
+export const arrayMove = (array: any[], from: number, to: number): any[] => {
+ array = array.slice();
+ array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0]);
+ return array;
+};
diff --git a/packages/utils/src/auth.ts b/packages/utils/src/auth.ts
new file mode 100644
index 000000000..2e5422f43
--- /dev/null
+++ b/packages/utils/src/auth.ts
@@ -0,0 +1,43 @@
+import bcrypt from 'bcrypt';
+import clientConfig from '@generatedata/config/clientConfig';
+
+// TODO maybe invert this?
+// import { SignInWithGoogleButton, initGoogleAuth } from '~core/auth/google/google';
+
+// refresh the token 1 minute before it expires
+export const setAuthTokenRefresh = (tokenExpiry: number, onRefresh: any): void => {
+ const oneMinFromExpiry = clientConfig.auth.GD_JWT_LIFESPAN_MINS * 60 * 1000 - 60 * 1000;
+ const timeout = setTimeout((): void => {
+ clearTimeout(timeout);
+ onRefresh();
+ }, oneMinFromExpiry);
+};
+
+export const initAuthVendors = (): void => {
+ if (clientConfig.auth.GD_GOOGLE_AUTH_CLIENT_ID) {
+ // initGoogleAuth();
+ }
+};
+
+export const hasVendorLogin = (): boolean => {
+ if (clientConfig.auth.GD_GOOGLE_AUTH_CLIENT_ID) {
+ return true;
+ }
+ return false;
+};
+
+export const getVendorLoginButtons = (): React.ReactNode[] => {
+ const buttons: any[] = [];
+
+ if (clientConfig.auth.GD_GOOGLE_AUTH_CLIENT_ID) {
+ // buttons.push(SignInWithGoogleButton);
+ }
+
+ return buttons;
+};
+
+const saltRounds = 10;
+export const getPasswordHash = async (plainTextPassword) => {
+ const salt = await bcrypt.genSalt(saltRounds);
+ return await bcrypt.hash(plainTextPassword, salt);
+};
diff --git a/packages/utils/src/dataType.tsx b/packages/utils/src/dataType.tsx
new file mode 100644
index 000000000..e70d45170
--- /dev/null
+++ b/packages/utils/src/dataType.tsx
@@ -0,0 +1,231 @@
+// import React from 'react';
+// import { getLocale, getStrings } from './langUtils';
+// // import { dataTypes, DataTypeFolder, blacklistedDataTypeFolders } from '../../_plugins';
+// import { DTBundle, DTCustomProps, DTHelpProps } from '@generatedata/types';
+// // import { Store } from '~types/general';
+// import { constants as C } from '@generatedata/config';
+
+// type LoadedDataTypes = {
+// [name in DataTypeFolder]: DTBundle;
+// };
+
+// // this houses all Export Type code loaded async after the application starts
+// const loadedDataTypes: Partial = {};
+
+// export const dataTypeNames = Object.keys(dataTypes);
+
+// // used for the Data Type selection dropdown
+// let lastLocale: any;
+// let cachedSortedGroupedDataTypes: any;
+// export const getSortedGroupedDataTypes = (): any => {
+// const locale = getLocale();
+// const i18n = getStrings();
+
+// if (cachedSortedGroupedDataTypes && lastLocale == locale) {
+// return cachedSortedGroupedDataTypes;
+// }
+
+// lastLocale = locale;
+// cachedSortedGroupedDataTypes = C.DATA_TYPE_GROUPS.map((group: string) => {
+// const options = Object.keys(dataTypes)
+// .filter((dataType: DataTypeFolder) => dataTypes[dataType].fieldGroup === group)
+// .filter((dataType: DataTypeFolder) => blacklistedDataTypeFolders.indexOf(dataType) === -1)
+// .map((dataType: DataTypeFolder) => ({
+// dataType,
+// sortOrder: dataTypes[dataType].fieldGroupOrder
+// }));
+
+// options.sort((a: any, b: any) => {
+// if (a.sortOrder < b.sortOrder) {
+// return -1;
+// } else if (a.sortOrder > b.sortOrder) {
+// return 1;
+// }
+// return 0;
+// });
+
+// const sortedOptions = options.map(({ dataType }: { dataType: DataTypeFolder }) => ({
+// value: dataType,
+// label: i18n.dataTypes[dataType].NAME
+// }));
+
+// return {
+// label: i18n.core[group],
+// options: sortedOptions
+// };
+// });
+
+// return cachedSortedGroupedDataTypes;
+// };
+
+// export const DefaultHelpComponent = ({ i18n }: DTHelpProps) =>
;
+
+// const showNothing = (): null => null;
+
+// export const getDataType = (dataType: DataTypeFolder | null): any => {
+// // TODO return type is important here. Dense method!
+// if (!dataType || !loadedDataTypes[dataType]) {
+// return {
+// Example: !dataType ? showNothing : null,
+// Options: showNothing,
+// Help: null,
+// isLoaded: false
+// };
+// }
+
+// let Example = null;
+// let Options = null;
+// let Help;
+
+// if (loadedDataTypes[dataType]!.Example) {
+// Example = loadedDataTypes[dataType]!.Example;
+// }
+
+// if (loadedDataTypes[dataType]!.Options) {
+// Options = loadedDataTypes[dataType]!.Options;
+// }
+
+// if (dataType && loadedDataTypes[dataType]!.Help) {
+// Help = loadedDataTypes[dataType]!.Help;
+// } else {
+// Help = DefaultHelpComponent;
+// }
+
+// const customProps = dataType && loadedDataTypes[dataType]!.customProps ? loadedDataTypes[dataType]!.customProps : {};
+// const actionInterceptors = dataType && loadedDataTypes[dataType]!.actionInterceptors ? loadedDataTypes[dataType]!.actionInterceptors : {};
+
+// const { getMetadata, rowStateReducer } = loadedDataTypes[dataType] as DTBundle;
+// return {
+// isLoaded: true,
+// Options,
+// Help,
+// Example,
+// getMetadata,
+// rowStateReducer,
+// customProps,
+// actionInterceptors
+// };
+// };
+
+// export type ProcessBatches = {
+// [dt in DataTypeFolder]?: number;
+// };
+
+// export function RecursiveErrorException(remaining: string[]): void {
+// // @ts-ignore-line
+// this.possibleProblematicDataTypes = remaining;
+// // @ts-ignore-line
+// this.name = 'Recursive dependency';
+// }
+
+// /**
+// * Data Types can register dependencies on other Data Types (the `dependencies` property of their config.ts file) so
+// * that when the row data is generated, the script ensures the dependencies are generated first and available for use.
+// * For example:
+// *
+// * Country <- Region <- City
+// *
+// * Here, the City DT expects the Region DT to be generated first, so it can generate a random city within whatever
+// * random region was generated. Then the same goes for Region with Country.
+// *
+// * This method examines all the dependencies and creates a flat object of dataType => process batch. Any recursive
+// * dependencies throw an error.
+// */
+// export const getProcessBatches = (dataTypes: any): ProcessBatches => {
+// let dataTypesToProcess = Object.keys(dataTypes);
+
+// const processBatches: ProcessBatches = {};
+// let previousLength = dataTypesToProcess.length;
+// let currentBatch = 1;
+
+// while (dataTypesToProcess.length > 0) {
+// const resolvedDataTypes: any = [];
+// dataTypesToProcess.forEach((dataType) => {
+// // here, we're dealing with Data Types that have dependencies. Loop through them all and figure out if all
+// // of them have already been assigned to a process batch
+// const allDependenciesPositioned =
+// !dataTypes[dataType].dependencies ||
+// dataTypes[dataType].dependencies.every((dependency: string) => !!processBatches[dependency as DataTypeFolder]);
+// if (allDependenciesPositioned) {
+// resolvedDataTypes.push(dataType);
+// }
+// });
+
+// if (resolvedDataTypes.length) {
+// resolvedDataTypes.forEach((dataType: string) => {
+// processBatches[dataType as DataTypeFolder] = currentBatch;
+// });
+// dataTypesToProcess = dataTypesToProcess.filter((dataType) => resolvedDataTypes.indexOf(dataType) === -1);
+// currentBatch++;
+// }
+
+// // simple check: every single pass of the DataTypes should be able to resolve at least one of them to a
+// // new batch number. If none got resolved we have a problem.
+// if (dataTypesToProcess.length === previousLength) {
+// // @ts-ignore-line
+// throw new RecursiveErrorException(dataTypesToProcess);
+// }
+
+// previousLength = dataTypesToProcess.length;
+// }
+
+// return processBatches;
+// };
+
+// export const processBatches = getProcessBatches(dataTypes);
+
+// // returns a hash of { [data type] => [array of affected data types] }. This means that when the property Data Type
+// // is changed, the Data Types in the key array could be affected
+// export const getAffectedDataTypes = (dataTypes: any): any => {
+// const affectedDataTypes: any = {};
+
+// Object.keys(dataTypes).forEach((dataType: DataTypeFolder) => {
+// if (!affectedDataTypes[dataType]) {
+// affectedDataTypes[dataType] = [];
+// }
+
+// if (dataTypes[dataType].dependencies) {
+// dataTypes[dataType].dependencies.forEach((dep: DataTypeFolder) => {
+// if (!affectedDataTypes[dep]) {
+// affectedDataTypes[dep] = [];
+// }
+// affectedDataTypes[dep].push(dataType);
+// });
+// }
+// });
+
+// return affectedDataTypes;
+// };
+
+// export const affectedDataTypes = getAffectedDataTypes(dataTypes);
+
+// export const requestDataTypeBundle = (dataType: DataTypeFolder): any => {
+// return new Promise((resolve, reject) => {
+// import(
+// /* webpackChunkName: "DT-[request]" */
+// /* webpackMode: "lazy" */
+// `../plugins/dataTypes/${dataType}/bundle`
+// )
+// .then((definition: any) => {
+// loadedDataTypes[dataType] = definition.default;
+// resolve(definition.default);
+// })
+// .catch((e) => {
+// reject(e);
+// });
+// });
+// };
+
+// // TODO - this is causing a repaint on every method call, which is causing the most slowness in the UI. But using
+// // noValue below doesn't help: when the DT selectors called here return different values, it doesn't get reflected in the UI
+// // const noValue: any = {};
+// export const getCustomProps = (customProps: DTCustomProps, state: Store): object => {
+// const values: any = {};
+// if (customProps) {
+// Object.keys(customProps).map((propName: string) => {
+// values[propName] = customProps[propName](state);
+// });
+// }
+
+// return values;
+// };
diff --git a/packages/utils/src/date.ts b/packages/utils/src/date.ts
new file mode 100644
index 000000000..a090ad7db
--- /dev/null
+++ b/packages/utils/src/date.ts
@@ -0,0 +1,37 @@
+import { format, fromUnixTime } from 'date-fns';
+import C from '@generatedata/config/constants';
+
+export const formatDuration = (duration: number): string => {
+ const secondsStr = Math.floor(duration % 60).toString();
+ const minutes = Math.floor(duration / 60) % 60;
+ const minutesStr = minutes.toString();
+ const hours = Math.floor(duration / 60 / 60);
+
+ const paddedSecondsStr = secondsStr.length < 2 ? 0 + secondsStr : secondsStr;
+ const paddedMinutesStr = minutesStr.length < 2 ? 0 + minutesStr : minutesStr;
+
+ if (hours) {
+ return `${hours}:${paddedMinutesStr}:${paddedSecondsStr}`;
+ } else if (minutes) {
+ return `${minutes}:${paddedSecondsStr}`;
+ } else {
+ return `0:${paddedSecondsStr}`;
+ }
+};
+
+export const formatUnixTime = (time: number, formatStr: string = C.DATETIME_FORMAT): string => {
+ if (!time) {
+ return '';
+ }
+ return format(fromUnixTime(time), formatStr);
+};
+
+export const isValidDateFormat = (dateFormat: string) => {
+ let isValid = true;
+ try {
+ format(new Date(), dateFormat);
+ } catch (e) {
+ isValid = false;
+ }
+ return isValid;
+};
diff --git a/packages/utils/src/debug.tsx b/packages/utils/src/debug.tsx
new file mode 100644
index 000000000..5b3c8b03e
--- /dev/null
+++ b/packages/utils/src/debug.tsx
@@ -0,0 +1,26 @@
+/* istanbul ignore file */
+
+// debugging methods. These should never be included in the actual code
+import * as React from 'react';
+
+interface Props {
+ [key: string]: any;
+}
+
+// quick debugging HOC to find out what props are causing repaints
+export function withPropsCheck
(WrappedComponent: React.ComponentType
): React.ComponentClass> {
+ return class PropsChecker extends React.Component> {
+ componentWillReceiveProps(nextProps: Props): void {
+ Object.keys(nextProps)
+ .filter((key) => nextProps[key] !== this.props[key])
+ .map((key): void => {
+ console.log('changed property:', key, 'from', this.props[key], 'to', nextProps[key]);
+ });
+ }
+
+ render(): any {
+ // @ts-ignore-line
+ return ;
+ }
+ };
+}
diff --git a/packages/utils/src/extension.tsx b/packages/utils/src/extension.tsx
new file mode 100644
index 000000000..589d0d7ff
--- /dev/null
+++ b/packages/utils/src/extension.tsx
@@ -0,0 +1,11 @@
+import React from 'react';
+
+const customFooterLinks: any[] = [];
+
+// to keep things simple, a registered footer link should be wrapped in it's own
. That way it can
+// supply any additional styling for the whole link externally.
+export const registerCustomFooterLink = (link: any): void => {
+ customFooterLinks.push(link);
+};
+
+export const getCustomFooterLinks = (): any[] => customFooterLinks.map((Link: any, index: number) => );
diff --git a/packages/utils/src/general.ts b/packages/utils/src/general.ts
new file mode 100644
index 000000000..98552305f
--- /dev/null
+++ b/packages/utils/src/general.ts
@@ -0,0 +1,45 @@
+/* eslint max-len:0 */
+// @ts-ignore-line
+import { template as uTemplate, templateSettings } from 'underscore';
+import { AlertProps } from '@mui/material/Alert';
+import { SnackbarOrigin } from '@mui/material';
+
+templateSettings.interpolate = /\{\{(.+?)\}\}/g;
+
+export const isBoolean = (n: any): boolean => typeof n === 'boolean';
+
+export const cloneObj = (obj: object): T => JSON.parse(JSON.stringify(obj));
+
+export const template = (str: string, params: object): string => {
+ const compiled = uTemplate(str);
+ return compiled(params);
+};
+
+export const isValidEmail = (email: string): boolean =>
+ /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/.test(
+ email
+ );
+
+let toastRef: any;
+export const initToast = (ref: any): void => {
+ toastRef = ref;
+};
+
+export type ToastType = {
+ message: string;
+ type: AlertProps['severity'];
+ verticalPosition?: SnackbarOrigin['vertical'];
+ horizontalPosition?: SnackbarOrigin['horizontal'];
+ autoHideDuration?: number;
+};
+
+export const addToast = (toast: ToastType): void => {
+ toastRef.add(toast);
+};
+
+let tourComponents: any;
+export const setTourComponents = (tour: any): void => {
+ tourComponents = tour;
+};
+
+export const getTourComponents = (): any => tourComponents;
diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts
new file mode 100644
index 000000000..5644574a1
--- /dev/null
+++ b/packages/utils/src/index.ts
@@ -0,0 +1,3 @@
+export * as langUtils from './lang';
+
+export { WeightedOptions } from './random';
diff --git a/packages/utils/src/lang.ts b/packages/utils/src/lang.ts
new file mode 100644
index 000000000..cade1e239
--- /dev/null
+++ b/packages/utils/src/lang.ts
@@ -0,0 +1,97 @@
+import { GDLocale } from '@generatedata/types';
+import C from '@generatedata/config/constants';
+
+// standalone location for the selected locale. Keeping this out of redux lets us just import it wherever
+let currentLocale: GDLocale;
+const langStrings: any = {};
+
+export const setLocale = (locale: GDLocale, localeStrings: any): void => {
+ currentLocale = locale;
+ langStrings[locale] = localeStrings;
+};
+
+export const getLocale = (): GDLocale => currentLocale;
+
+export const getStrings = (locale?: GDLocale): any => langStrings[locale || currentLocale];
+
+// use this for constructing JSX
+export const getI18n = (i18nString: string, placeholders: any[]): any => {
+ const parts = i18nString.split(/(%\d+)/);
+ const parsed: any = [];
+
+ parts.forEach((part) => {
+ if (/%\d+/.test(part)) {
+ const index = parseInt(part.replace('%', ''), 10) - 1;
+
+ if (index < placeholders.length) {
+ parsed.push(placeholders[index]);
+
+ // if there's no placeholder for this, just add the original value which looked like a placeholder
+ } else {
+ parsed.push(part);
+ }
+ } else {
+ parsed.push(part);
+ }
+ });
+
+ return parsed;
+};
+
+// use this getting a string
+export const getI18nString = (i18nString: string, placeholders: any[]): string => getI18n(i18nString, placeholders).join('');
+
+// looks at the current URL and figures out what locale is being used
+export const getCurrentPageLocale = (): GDLocale => {
+ const availableLocaleMap = getLocaleMap();
+ const path = window.location.pathname.replace(/^\//, '').split('/');
+
+ let locale = 'en';
+ if (path.length > 0 && availableLocaleMap[path[0]]) {
+ locale = path[0];
+ }
+
+ return locale as GDLocale;
+};
+
+const localeMap = Object.keys(C.GD_ALL_SUPPORTED_LOCALES).reduce((map: any, locale) => {
+ map[locale] = true;
+ return map;
+}, {});
+
+// just for O(1) lookup of our list of locale shortcodes
+export const getLocaleMap = (): any => localeMap;
+
+// returns the localized version of the current page. Note: this strips out any query strings right now.
+export const getCurrentLocalizedPath = (targetLocale: GDLocale): string => {
+ const availableLocaleMap = getLocaleMap();
+
+ // first, if the target locale being passed isn't valid, just return the current URL
+ const targetLocaleIsValid = !!availableLocaleMap[targetLocale];
+ if (!targetLocaleIsValid) {
+ return window.location.pathname;
+ }
+
+ const path = window.location.pathname
+ .replace(/^\//, '')
+ .replace(/\/$/, '')
+ .split('/')
+ .filter((path) => !!path);
+
+ const currentPathHasLocale = !!availableLocaleMap[path[0]];
+ if (path.length > 0 && currentPathHasLocale) {
+ path.shift();
+ }
+
+ let newPath = '';
+ if (targetLocale === 'en') {
+ newPath = `/${path.join('/')}`;
+ } else {
+ newPath = `/${targetLocale}`;
+ if (path.length > 0) {
+ newPath += `/${path.join('/')}`;
+ }
+ }
+
+ return newPath;
+};
diff --git a/packages/utils/src/number.ts b/packages/utils/src/number.ts
new file mode 100644
index 000000000..404f48bf8
--- /dev/null
+++ b/packages/utils/src/number.ts
@@ -0,0 +1,41 @@
+import { GDLocale } from '@generatedata/types';
+import { getLocale } from './lang';
+
+export const getFormattedNum = (num: number, locale: GDLocale = getLocale()): string => {
+ return Intl.NumberFormat(locale).format(num);
+};
+
+// @ts-ignore
+export const isNumeric = (n: any): boolean => !isNaN(parseFloat(n)) && isFinite(n);
+
+// based on a JS port of the PHP number_format() method: https://locutus.io/php/strings/number_format/
+export const numberFormat = (number: number, decimals = 0, decPoint = '.', thousandsSep = ','): string => {
+ const n = !isFinite(+number) ? 0 : +number;
+ const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals);
+ const sep = typeof thousandsSep === 'undefined' ? ',' : thousandsSep;
+ const dec = typeof decPoint === 'undefined' ? '.' : decPoint;
+
+ const toFixedFix = (num: number, precision: number): any => {
+ if (('' + num).indexOf('e') === -1) {
+ return +(Math.round(Number(n + 'e+' + precision)) + 'e-' + precision);
+ } else {
+ const arr = ('' + n).split('e');
+ let sig = '';
+ if (+arr[1] + prec > 0) {
+ sig = '+';
+ }
+ return (+(Math.round(Number(+arr[0] + 'e' + sig + (+arr[1] + precision))) + 'e-' + precision)).toFixed(precision);
+ }
+ };
+
+ const s = (prec ? toFixedFix(n, prec).toString() : '' + Math.round(n)).split('.');
+ if (s[0].length > 3) {
+ s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
+ }
+ if ((s[1] || '').length < prec) {
+ s[1] = s[1] || '';
+ s[1] += new Array(prec - s[1].length + 1).join('0');
+ }
+
+ return s.join(dec);
+};
diff --git a/packages/utils/src/random.ts b/packages/utils/src/random.ts
new file mode 100644
index 000000000..8f6c39d39
--- /dev/null
+++ b/packages/utils/src/random.ts
@@ -0,0 +1,163 @@
+export const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+export const lowercaseLetters = letters.toLowerCase();
+export const consonants = 'BCDFGHJKLMNPQRSTVWXYZ';
+export const lowercaseConsonants = consonants.toLowerCase();
+export const vowels = 'AEIOU';
+export const lowercaseVowels = vowels.toLowerCase();
+export const hex = '0123456789ABCDEF';
+
+// TODO should accommodate negative numbers
+export const getRandomNum = (min: number, max: number): number => {
+ const range = Math.abs(max - min);
+ let val = Math.round(Math.random() * range);
+ if (min < 0) {
+ val -= Math.abs(min);
+ } else {
+ val += Math.abs(min);
+ }
+ return val;
+};
+
+export const getRandomBool = (): boolean => Math.random() < 0.5;
+export const getRandomArrayValue = (arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];
+export const getRandomCharInString = (str: string): string => {
+ const index = getRandomNum(0, str.length - 1);
+ return str[index];
+};
+
+export const defaultPlaceholders = {
+ X: '123456789',
+ x: '0123456789',
+ L: letters,
+ l: lowercaseLetters,
+ D: letters + lowercaseLetters,
+ C: consonants,
+ c: lowercaseConsonants,
+ E: consonants + lowercaseConsonants,
+ V: vowels,
+ v: lowercaseVowels,
+ F: vowels + lowercaseVowels,
+ H: hex
+};
+
+/**
+ * Converts the following characters in the parameter string and returns the random string.
+ * C, c, E - any consonant (Upper case, lower case, any)
+ * V, v, F - any vowel (Upper case, lower case, any)
+ * L, l, D - any letter (Upper case, lower case, any)
+ * X - 1-9
+ * x - 0-9
+ * H - 0-F
+ */
+export const generateRandomAlphanumericStr = (str: string, placeholders: any = defaultPlaceholders): string => {
+ if (!str) {
+ return '';
+ }
+ let newStr = '';
+ for (let i = 0, j = str.length; i < j; i++) {
+ if (placeholders.hasOwnProperty(str[i])) {
+ newStr += placeholders[str[i]][getRandomNum(0, placeholders[str[i]].length - 1)];
+ } else {
+ newStr += str[i];
+ }
+ }
+ return newStr.trim();
+};
+
+// Returns a random subset of an array of a particular size. The result may be empty, or the same set.
+export const getRandomSubset = (arr: T[], size: number): T[] => {
+ const shuffled = arr.slice(0);
+ let totalArrSize = arr.length;
+ const min = totalArrSize - size;
+
+ while (totalArrSize-- > min) {
+ const index = Math.floor((totalArrSize + 1) * Math.random());
+ const temp = shuffled[index];
+ shuffled[index] = shuffled[totalArrSize];
+ shuffled[totalArrSize] = temp;
+ }
+ return shuffled.slice(min);
+};
+
+export const getRandomWeightedSubset = (options: WeightedOptions, size: number, allowDuplicates: boolean): string[] => {
+ const subset: string[] = [];
+ const weightedOptions = {
+ ...options
+ };
+ let totalArrSize = Object.keys(weightedOptions).length;
+
+ if (!totalArrSize) {
+ return subset;
+ }
+
+ while (subset.length < size && totalArrSize > 0) {
+ const value = getRandomWeightedValue(weightedOptions);
+ subset.push(value);
+ if (!allowDuplicates) {
+ delete weightedOptions[value];
+ totalArrSize = totalArrSize - 1;
+ }
+ }
+
+ return subset;
+};
+
+/**
+ * Generates a string of words from a source array of strings.
+ */
+export const generateRandomTextStr = (words: string[], fromStart: boolean, min: number, max?: number): string => {
+ let numWords = max ? getRandomNum(min, max) : min;
+
+ const totalWords = words.length;
+ if (numWords > totalWords) {
+ numWords = totalWords;
+ }
+
+ if (numWords === 0) {
+ return '';
+ }
+
+ if (fromStart) {
+ return words.slice(0, numWords).join(' ');
+ } else {
+ const offset = getRandomNum(0, totalWords - numWords);
+ return words.slice(offset, offset + numWords).join(' ');
+ }
+};
+
+export const generatePlaceholderStr = (str: string, customPlaceholders: any): string => {
+ const placeholders = {
+ ...defaultPlaceholders,
+ ...customPlaceholders
+ };
+ return generateRandomAlphanumericStr(str, placeholders);
+};
+
+export type WeightedOptions = {
+ [option: string]: number;
+};
+
+// pass in an object like { a: 10, b: 4, c: 400 } and it'll return either "a", "b", or "c", factoring in their respective
+// weight. So in this example, "c" is likely to be returned 400 times out of 414
+export const getRandomWeightedValue = (options: WeightedOptions): string => {
+ const keys = Object.keys(options);
+ const totalSum = keys.reduce((acc, item) => acc + options[item], 0);
+
+ let runningTotal = 0;
+ const cumulativeValues = keys.map((key) => {
+ const relativeValue = options[key] / totalSum;
+ const cv = {
+ key,
+ value: relativeValue + runningTotal
+ };
+ runningTotal += relativeValue;
+ return cv;
+ });
+
+ if (keys.length === 0) {
+ return '';
+ }
+
+ const r = Math.random();
+ return cumulativeValues.find(({ value }) => r <= value)!.key;
+};
diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts
new file mode 100644
index 000000000..b69e8b998
--- /dev/null
+++ b/packages/utils/src/string.ts
@@ -0,0 +1,45 @@
+/* eslint max-len:0 */
+export const lipsum =
+ 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur sed tortor. Integer aliquam adipiscing lacus. Ut nec urna et arcu imperdiet ullamcorper. Duis at lacus. Quisque purus sapien, gravida non, sollicitudin a, malesuada id, erat. Etiam vestibulum massa rutrum magna. Cras convallis convallis dolor. Quisque tincidunt pede ac urna. Ut tincidunt vehicula risus. Nulla eget metus eu erat semper rutrum. Fusce dolor quam, elementum at, egestas a, scelerisque sed, sapien. Nunc pulvinar arcu et pede. Nunc sed orci lobortis augue scelerisque mollis. Phasellus libero mauris, aliquam eu, accumsan sed, facilisis vitae, orci. Phasellus dapibus quam quis diam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce aliquet magna a neque. Nullam ut nisi a odio semper cursus. Integer mollis. Integer tincidunt aliquam arcu. Aliquam ultrices iaculis odio. Nam interdum enim non nisi. Aenean eget metus. In nec orci. Donec nibh. Quisque nonummy ipsum non arcu. Vivamus sit amet risus. Donec egestas. Aliquam nec enim. Nunc ut erat. Sed nunc est, mollis non, cursus non, egestas a, dui. Cras pellentesque. Sed dictum. Proin eget odio. Aliquam vulputate ullamcorper magna. Sed eu eros. Nam consequat dolor vitae dolor. Donec fringilla. Donec feugiat metus sit amet ante. Vivamus non lorem vitae odio sagittis semper. Nam tempor diam dictum sapien. Aenean massa. Integer vitae nibh. Donec est mauris, rhoncus id, mollis nec, cursus a, enim. Suspendisse aliquet, sem ut cursus luctus, ipsum leo elementum sem, vitae aliquam eros turpis non enim. Mauris quis turpis vitae purus gravida sagittis. Duis gravida. Praesent eu nulla at sem molestie sodales. Mauris blandit enim consequat purus. Maecenas libero est, congue a, aliquet vel, vulputate eu, odio. Phasellus at augue id ante dictum cursus. Nunc mauris elit, dictum eu, eleifend nec, malesuada ut, sem. Nulla interdum. Curabitur dictum. Phasellus in felis. Nulla tempor augue ac ipsum. Phasellus vitae mauris sit amet lorem semper auctor. Mauris vel turpis. Aliquam adipiscing lobortis risus. In mi pede, nonummy ut, molestie in, tempus eu, ligula. Aenean euismod mauris eu elit. Nulla facilisi. Sed neque. Sed eget lacus. Mauris non dui nec urna suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae Phasellus ornare. Fusce mollis. Duis sit amet diam eu dolor egestas rhoncus. Proin nisl sem, consequat nec, mollis vitae, posuere at, velit. Cras lorem lorem, luctus ut, pellentesque eget, dictum placerat, augue. Sed molestie. Sed id risus quis diam luctus lobortis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Mauris ut quam vel sapien imperdiet ornare. In faucibus. Morbi vehicula. Pellentesque tincidunt tempus risus. Donec egestas. Duis ac arcu. Nunc mauris. Morbi non sapien molestie orci tincidunt adipiscing. Mauris molestie pharetra nibh. Aliquam ornare, libero at auctor ullamcorper, nisl arcu iaculis enim, sit amet ornare lectus justo eu arcu. Morbi sit amet massa. Quisque porttitor eros nec tellus. Nunc lectus pede, ultrices a, auctor non, feugiat nec, diam. Duis mi enim, condimentum eget, volutpat ornare, facilisis eget, ipsum. Donec sollicitudin adipiscing ligula. Aenean gravida nunc sed pede. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel arcu eu odio tristique pharetra. Quisque ac libero nec ligula consectetuer rhoncus. Nullam velit dui, semper et, lacinia vitae, sodales at, velit. Pellentesque ultricies dignissim lacus. Aliquam rutrum lorem ac risus. Morbi metus. Vivamus euismod urna. Nullam lobortis quam a felis ullamcorper viverra. Maecenas iaculis aliquet diam. Sed diam lorem, auctor quis, tristique ac, eleifend vitae, erat. Vivamus nisi. Mauris nulla. Integer urna. Vivamus molestie dapibus ligula. Aliquam erat volutpat. Nulla dignissim. Maecenas ornare egestas ligula. Nullam feugiat placerat velit. Quisque varius. Nam porttitor scelerisque neque. Nullam nisl. Maecenas malesuada fringilla est. Mauris eu turpis. Nulla aliquet. Proin velit. Sed malesuada augue ut lacus. Nulla tincidunt, neque vitae semper egestas, urna justo faucibus lectus, a sollicitudin orci sem eget massa. Suspendisse eleifend. Cras sed leo. Cras vehicula aliquet libero. Integer in magna. Phasellus dolor elit, pellentesque a, facilisis non, bibendum sed, est. Nunc laoreet lectus quis massa. Mauris vestibulum, neque sed dictum eleifend, nunc risus varius orci, in consequat enim diam vel arcu. Curabitur ut odio vel est tempor bibendum. Donec felis orci, adipiscing non, luctus sit amet, faucibus ut, nulla. Cras eu tellus eu augue porttitor interdum. Sed auctor odio a purus. Duis elementum, dui quis accumsan convallis, ante lectus convallis est, vitae sodales nisi magna sed dui. Fusce aliquam, enim nec tempus scelerisque, lorem ipsum sodales purus, in molestie tortor nibh sit amet orci. Ut sagittis lobortis mauris. Suspendisse aliquet molestie tellus. Aenean egestas hendrerit neque. In ornare sagittis felis. Donec tempor, est ac mattis semper, dui lectus rutrum urna, nec luctus felis purus ac tellus. Suspendisse sed dolor. Fusce mi lorem, vehicula et, rutrum eu, ultrices sit amet, risus. Donec nibh enim, gravida sit amet, dapibus id, blandit at, nisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin vel nisl. Quisque fringilla euismod enim. Etiam gravida molestie arcu. Sed eu nibh vulputate mauris sagittis placerat. Cras dictum ultricies ligula. Nullam enim. Sed nulla ante, iaculis nec, eleifend non, dapibus rutrum, justo. Praesent luctus. Curabitur egestas nunc sed libero. Proin sed turpis nec mauris blandit mattis. Cras eget nisi dictum augue malesuada malesuada. Integer id magna et ipsum cursus vestibulum. Mauris magna. Duis dignissim tempor arcu. Vestibulum ut eros non enim commodo hendrerit. Donec porttitor tellus non magna. Nam ligula elit, pretium et, rutrum non, hendrerit id, ante. Nunc mauris sapien, cursus in, hendrerit consectetuer, cursus et, magna. Praesent interdum ligula eu enim. Etiam imperdiet dictum magna. Ut tincidunt orci quis lectus. Nullam suscipit, est ac facilisis facilisis, magna tellus faucibus leo, in lobortis tellus justo sit amet nulla. Donec non justo. Proin non massa non ante bibendum ullamcorper. Duis cursus, diam at pretium aliquet, metus urna convallis erat, eget tincidunt dui augue eu tellus. Phasellus elit pede, malesuada vel, venenatis vel, faucibus id, libero. Donec consectetuer mauris id sapien. Cras dolor dolor, tempus non, lacinia at, iaculis quis, pede. Praesent eu dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eget magna. Suspendisse tristique neque venenatis lacus. Etiam bibendum fermentum metus. Aenean sed pede nec ante blandit viverra. Donec tempus, lorem fringilla ornare placerat, orci lacus vestibulum lorem, sit amet ultricies sem magna nec quam. Curabitur vel lectus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec dignissim magna a tortor. Nunc commodo auctor velit. Aliquam nisl. Nulla eu neque pellentesque massa lobortis ultrices. Vivamus rhoncus. Donec est. Nunc ullamcorper, velit in aliquet lobortis, nisi nibh lacinia orci, consectetuer euismod est arcu ac orci. Ut semper pretium neque. Morbi quis urna. Nunc quis arcu vel quam dignissim pharetra. Nam ac nulla. In tincidunt congue turpis. In condimentum. Donec at arcu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae Donec tincidunt. Donec vitae erat vel pede blandit congue. In scelerisque scelerisque dui. Suspendisse ac metus vitae velit egestas lacinia. Sed congue, elit sed consequat auctor, nunc nulla vulputate dui, nec tempus mauris erat eget ipsum. Suspendisse sagittis. Nullam vitae diam. Proin dolor. Nulla semper tellus id nunc interdum feugiat. Sed nec metus facilisis lorem tristique aliquet. Phasellus fermentum convallis ligula. Donec luctus aliquet odio. Etiam ligula tortor, dictum eu, placerat eget, venenatis a, magna. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Etiam laoreet, libero et tristique pellentesque, tellus sem mollis dui, in sodales elit erat vitae risus. Duis a mi fringilla mi lacinia mattis. Integer eu lacus. Quisque imperdiet, erat nonummy ultricies ornare, elit elit fermentum risus, at fringilla purus mauris a nunc. In at pede. Cras vulputate velit eu sem. Pellentesque ut ipsum ac mi eleifend egestas. Sed pharetra, felis eget varius ultrices, mauris ipsum porta elit, a feugiat tellus lorem eu metus. In lorem. Donec elementum, lorem ut aliquam iaculis, lacus pede sagittis augue, eu tempor erat neque non quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam fringilla cursus purus. Nullam scelerisque neque sed sem egestas blandit. Nam nulla magna, malesuada vel, convallis in, cursus et, eros. Proin ultrices. Duis volutpat nunc sit amet metus. Aliquam erat volutpat. Nulla facilisis. Suspendisse commodo tincidunt nibh. Phasellus nulla. Integer vulputate, risus a ultricies adipiscing, enim mi tempor lorem, eget mollis lectus pede et risus. Quisque libero lacus, varius et, euismod et, commodo at, libero. Morbi accumsan laoreet ipsum. Curabitur consequat, lectus sit amet luctus vulputate, nisi sem semper erat, in consectetuer ipsum nunc id enim. Curabitur massa. Vestibulum accumsan neque et nunc. Quisque ornare tortor at risus. Nunc ac sem ut dolor dapibus gravida. Aliquam tincidunt, nunc ac mattis ornare, lectus ante dictum mi, ac mattis velit justo nec ante. Maecenas mi felis, adipiscing fringilla, porttitor vulputate, posuere vulputate, lacus. Cras interdum. Nunc sollicitudin commodo ipsum. Suspendisse non leo. Vivamus nibh dolor, nonummy ac, feugiat non, lobortis quis, pede. Suspendisse dui. Fusce diam nunc, ullamcorper eu, euismod ac, fermentum vel, mauris. Integer sem elit, pharetra ut, pharetra sed, hendrerit a, arcu. Sed et libero. Proin mi. Aliquam gravida mauris ut mi. Duis risus odio, auctor vitae, aliquet nec, imperdiet nec, leo. Morbi neque tellus, imperdiet non, vestibulum nec, euismod in, dolor. Fusce feugiat. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam auctor, velit eget laoreet posuere, enim nisl elementum purus, accumsan interdum libero dui nec ipsum.';
+const words = lipsum.split(/\s+/);
+const numWords = words.length;
+
+export const getLipsumWords = (): { words: string[]; numWords: number } => ({
+ words,
+ numWords
+});
+
+export const uppercaseWords = (str: string): string =>
+ str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.slice(1).toLowerCase());
+
+export const toSentenceCase = (str: string): string =>
+ str
+ .toLowerCase()
+ .replace(/[a-z]/i, (letter) => letter.toUpperCase())
+ .trim();
+
+export const getUniqueString = (str: string, existingStrings: string[]): string => {
+ if (existingStrings.indexOf(str) === -1) {
+ return str;
+ }
+ let currNum = 1;
+ while (existingStrings.indexOf(`${str}${currNum}`) !== -1) {
+ currNum++;
+ }
+ return `${str}${currNum}`;
+};
+
+export const reverse = (s: string): string => s.split('').reverse().join('');
+
+export const padString = (val: number | string, length: number): string => {
+ let str = '' + val;
+ while (str.length < length) {
+ str = '0' + str;
+ }
+ return str;
+};
+
+export const trimChars = (str: string, c: string) => {
+ const re = new RegExp(`^[${c}]+|[${c}]+$`, 'g');
+ return str.replace(re, '');
+};
diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json
new file mode 100644
index 000000000..85b4b5469
--- /dev/null
+++ b/packages/utils/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "compilerOptions": {
+ "rootDir": "./src",
+ "outDir": "./dist",
+ "target": "esnext",
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "declaration": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true,
+ "types": ["node", "jest"],
+ "jsx": "react-jsx"
+ }
+}
diff --git a/plugins/countries/Australia/Australia.class.php b/plugins/countries/Australia/Australia.class.php
deleted file mode 100644
index cc312cd56..000000000
--- a/plugins/countries/Australia/Australia.class.php
+++ /dev/null
@@ -1,120 +0,0 @@
- array(
- "format" => "Xxxx",
- "replacements" => array(
- "X" => "123456789",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "displayFormats" => array(
- "xxxx-xxxx",
- "(0x) xxxx xxxx",
- "04xx xxx xxx"
- )
- )
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Australian Capital Territories",
- "regionShort" => "AC",
- "regionSlug" => "australian_capital_territories",
- "weight" => 3,
- "cities" => array(
- "Canberra"
- )
- ),
- array(
- "regionName" => "New South Wales",
- "regionShort" => "NS",
- "regionSlug" => "new_south_wales",
- "weight" => 69,
- "cities" => array(
- "Sydney", "Albury", "Armidale", "Bathurst", "Blue Mountains", "Broken Hill",
- "Campbelltown", "Cessnock", "Dubbo", "Goulburn", "Grafton", "Lithgow",
- "Liverpool", "Newcastle", "Orange", "Parramatta", "Penrith", "Queanbeyan",
- "Tamworth", "Wagga Wagga", "Wollongong"
- )
- ),
- array(
- "regionName" => "Northern Territory",
- "regionShort" => "NT",
- "regionSlug" => "northern_territory",
- "weight" => 2,
- "cities" => array(
- "Darwin", "Palmerston"
- )
- ),
- array(
- "regionName" => "Queensland",
- "regionShort" => "QL",
- "regionSlug" => "queensland",
- "weight" => 42,
- "cities" => array(
- "Brisbane", "Bundaberg", "Cairns", "Caloundra", "Charters Towers", "Gladstone", "Gold Coast",
- "Hervey Bay", "Ipswich", "Logan City", "Mackay", "Maryborough", "Mount Isa", "Redcliffe",
- "Redlands", "Rockhampton", "Toowoomba", "Townsville"
- )
- ),
- array(
- "regionName" => "South Australia",
- "regionShort" => "SA",
- "regionSlug" => "south_australia",
- "weight" => 16,
- "cities" => array(
- "Adelaide", "Mount Gambier", "Murray Bridge", "Port Augusta", "Port Pirie", "Port Lincoln",
- "Victor Harbor", "Whyalla"
- )
- ),
- array(
- "regionName" => "Tasmania",
- "regionShort" => "TA",
- "regionSlug" => "tasmania",
- "weight" => 5,
- "cities" => array(
- "Greater Hobart", "Burnie", "Devonport", "Launceston"
- )
- ),
- array(
- "regionName" => "Victoria",
- "regionShort" => "VI",
- "regionSlug" => "victoria",
- "weight" => 52,
- "cities" => array(
- "Melbourne", "Ararat", "Bairnsdale", "Benalla", "Ballarat", "Bendigo", "Belgrave",
- "Dandenong", "Frankston", "Geelong", "Hamilton", "Horsham", "Melton", "Moe", "Morwell",
- "Mildura", "Sale", "Shepparton", "Swan Hill", "Traralgon", "Wangaratta", "Warrnambool",
- "Wodonga"
- )
- ),
- array(
- "regionName" => "Western Australia",
- "regionShort" => "WA",
- "regionSlug" => "western_australia",
- "weight" => 21,
- "cities" => array(
- "Perth", "Albany", "Armadale", "Bayswater", "Belmont", "Bunbury", "Canning", "Cockburn",
- "Fremantle", "Geraldton-Greenough", "Gosnells", "Joondalup", "Kalgoorlie-Boulder", "Mandurah",
- "Melville", "Nedlands", "Rockingham", "South Perth", "Stirling", "Subiaco", "Swan", "Wanneroo"
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/Austria/Austria.class.php b/plugins/countries/Austria/Austria.class.php
deleted file mode 100644
index e5f24bb99..000000000
--- a/plugins/countries/Austria/Austria.class.php
+++ /dev/null
@@ -1,118 +0,0 @@
- "Xxxx"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Vienna",
- "regionShort" => "Wien",
- "regionSlug" => "vienna",
- "weight" => 4113,
- "cities" => array(
- "Vienna"
- )
- ),
- array(
- "regionName" => "Vorarlberg",
- "regionShort" => "Vbg.",
- "regionSlug" => "voralberg",
- "weight" => 142,
- "cities" => array(
- "Dornbirn", "Feldkirch", "Bregenz", "Lustenau", "Hohenems", "Bludenz", "Hard", "Rankweil",
- "Götzis", "Lauterach", "Wolfurt", "Höchst", "Altach"
- )
- ),
- array(
- "regionName" => "Upper Austria",
- "regionShort" => "OÖ.",
- "regionSlug" => "upper_austria",
- "weight" => 117,
- "cities" => array(
- "Linz", "Wels", "Steyr", "Leonding", "Traun", "Braunau am Inn", "Ansfelden", "Bad Ischl",
- "Gmunden", "Marchtrenk", "Vöcklabruck", "Ried im Innkreis", "Enns", "Altmünster", "Laakirchen",
- "Sierning"
- )
- ),
- array(
- "regionName" => "Lower Austria",
- "regionShort" => "NÖ.",
- "regionSlug" => "lower_austria",
- "weight" => 84,
- "cities" => array(
- "St. Pölten", "Wiener Neustadt", "Klosterneuburg", "Baden", "Krems an der Donau",
- "Amstetten", "Mödling", "Traiskirchen", "Schwechat", "Stockerau", "Tulln an der Donau",
- "Ternitz", "Perchtoldsdorf", "Korneuburg", "Neunkirchen", "Hollabrunn", "Waidhofen an der Ybbs",
- "Bad Vöslau", "Brunn am Gebirge", "Zwettl-Niederösterreich"
- )
- ),
- array(
- "regionName" => "Salzburg",
- "regionShort" => "Sbg.",
- "regionSlug" => "salzburg",
- "weight" => 74,
- "cities" => array(
- "Salzburg", "Hallein", "Saalfelden am Steinernen Meer", "Wals-Siezenheim", "Sankt Johann im Pongau",
- "Bischofshofen"
- )
- ),
- array(
- "regionName" => "Styria",
- "regionShort" => "Stm.",
- "regionSlug" => "styria",
- "weight" => 73,
- "cities" => array(
- "Graz", "Leoben", "Kapfenberg", "Bruck an der Mur", "Knittelfeld", "Köflach", "Voitsberg",
- "Judenburg", "Weiz"
- )
- ),
- array(
- "regionName" => "Burgenland",
- "regionShort" => "Bgl.",
- "regionSlug" => "burgenland",
- "weight" => 72,
- "cities" => array(
- "Eisenstadt", "Oberwart", "Neusiedl am See", "Mattersburg", "Pinkafeld", "Neudörfl", "Parndorf",
- "Jennersdorf", "Güssing", "Gols", "Großpetersdorf", "Neufeld an der Leitha", "Deutschkreutz",
- "Rechnitz", "Oberpullendorf", "Siegendorf", "Pöttsching", "Bruckneudorf", "Frauenkirchen",
- "Forchtenstein"
- )
- ),
- array(
- "regionName" => "Carinthia",
- "regionShort" => "Ktn.",
- "regionSlug" => "carinthia",
- "weight" => 59,
- "cities" => array(
- "Klagenfurt", "Villach", "Wolfsberg", "Spittal an der Drau", "Feldkirchen in Kärnten", "St. Veit an der Glan",
- "Völkermarkt", "St. Andrä", "Velden am Wörther See", "Finkenstein am Faaker See", "Ebenthal in Kärnten",
- "Ferlach"
- )
- ),
- array(
- "regionName" => "Tyrol",
- "regionShort" => "Tirol",
- "regionSlug" => "tyrol",
- "weight" => 56,
- "cities" => array(
- "Innsbruck", "Kufstein", "Telfs", "Schwaz", "Hall in Tirol", "Wörgl", "Lienz", "Imst", "Rum", "St. Johann in Tirol",
- "Kitzbühel", "Zirl", "Landeck"
- )
- ),
- );
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/Belgium/Belgium.class.php b/plugins/countries/Belgium/Belgium.class.php
deleted file mode 100644
index 7ff96aa31..000000000
--- a/plugins/countries/Belgium/Belgium.class.php
+++ /dev/null
@@ -1,225 +0,0 @@
- "Xxxx"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Antwerpen",
- "regionShort" => "AN",
- "regionSlug" => "antwerpen",
- "weight" => 1,
- "cities" => array(
- "Antwerpen", "Burcht", "Zwijndrecht", "Deurne", "Wijnegem", "Borgerhout", "Borsbeek", "Wommelgem", "Merksem",
- "Ekeren", "Herentals", "Morkhoven", "Noorderwijk", "Hallaar", "Heist-op-den-Berg", "Booischot", "Itegem",
- "Wiekevorst", "Schriek", "Herselt", "Ramsel", "Houtvenne", "Hulshout", "Westmeerbeek", "Massenhoven", "Viersel",
- "Zandhoven", "Pulderbos", "Pulle", "Olen", "Oevel", "Tongerlo", "Westerlo", "Zoerle-Parwijs", "Herenthout",
- "Gierle", "Lille", "Poederlee", "Wechelderzande", "Grobbendonk", "Bouwel", "Vorselaar", "Turnhout", "Rijkevorsel",
- "Hoogstraten", "Meer", "Minderhout", "Wortel", "Meerle", "Merksplas", "Beerse", "Vlimmeren", "Vosselaar",
- "Oud-Turnhout", "Arendonk", "Ravels", "Weelde", "Poppel", "Baarle-Hertog", "Malle", "Oostmalle", "Westmalle",
- "Mol", "Eindhout", "Laakdal", "Vorst", "Varendonk", "Veerle", "Geel", "Meerhout", "Kasterlee", "Lichtaart",
- "Tielen", "Retie", "Dessel", "Balen", "Olmen", "Koningshooikt", "Lier", "Broechem", "Emblem", "Oelegem", "Ranst",
- "Boechout", "Vremde", "Hove", "Lint", "Kontich", "Waarloos", "Bevel", "Kessel", "Nijlen", "Duffel", "Beerzel"
- )
- ),
- array(
- "regionName" => "Brussels Hoofdstedelijk Gewest",
- "regionShort" => "BU",
- "regionSlug" => "brussels_hoofdstedelijk_gewest",
- "weight" => 1,
- "cities" => array(
- "Brussel", "Laken", "Schaarbeek", "Etterbeek", "Elsene", "Sint-Gillis", "Anderlecht", "Sint-Jans-Molenbeek",
- "Koekelberg", "Sint-Agatha-Berchem", "Ganshoren", "Jette", "Neder-Over-Heembeek", "Haren", "Evere",
- "Sint-Pieters-Woluwe", "Oudergem", "Watermaal-Bosvoorde", "Ukkel", "Vorst", "Sint-Lambrechts-Woluwe",
- "Sint-Joost-ten-Node"
- )
- ),
- array(
- "regionName" => "Waals-Brabant",
- "regionShort" => "WB",
- "regionSlug" => "waals_brabant",
- "weight" => 1,
- "cities" => array(
- "Limal", "Waver", "Bierges", "La Hulpe", "Glimes", "Incourt", "Opprebais", "Pi�trebais", "Roux-Miroir",
- "Beauvechain", "Hamme-Mille", "l'Ecluse", "Nodebais", "Tourinnes-la-Grosse", "Bonlez", "Chaumont-Gistoux",
- "Corroy-le-Grand", "Dion-Valmont", "Longueville", "Rixensart", "Rosi�res", "Genval", "Ottignies",
- "Ottignies-Louvain-la-Neuve", "C�roux-Mousty", "Limelette", "Louvain-la-Neuve", "Enines", "Folx-les-Caves",
- "Jandrain-Jandrenouille", "Jauche", "Marilles", "Noduwez", "Orp-Jauche", "Orp-le-Grand", "H�l�cine",
- "Linsmeau", "Neerheylissem", "Opheylissem", "Mal�ves-Sainte-Marie-Wastines", "Orbais", "Perwez",
- "Thorembais-les-B�guines", "Thorembais-Saint-Trond", "Autre-Eglise", "Bomal", "Geest-G�rompont-Petit-Rosi�re",
- "G�rompont", "Grand-Rosi�re-Hottomont", "Huppaye", "Mont-Saint-Andr�", "Ramillies", "Dongelberg", "Jauchelette",
- "Jodoigne", "Jodoigne-Souveraine", "Lathuy", "M�lin", "Pi�train", "Saint-Jean-Geest", "Saint-Remy-Geest",
- "Z�trud-Lumay", "Couture-Saint-Germain", "Lasne", "Lasne-Chapelle-Saint-Lambert", "Maransart", "Ohain",
- "Plancenoit", "Archennes", "Graven Grez-Doiceau", "Biez", "Bossut-Gottechain", "Nethen", "Monstreux",
- "Nivelles", "Baulers", "Thines", "Bornival", "Waterloo", "Promo-Control", "Eigenbrakel",
- "Ophain-Bois-Seigneur-Isaac", "Lillois-Witterz�e", "Bierk Bierghes", "Roosbeek", "Quenast", "Rebecq",
- "Corbais", "H�villers", "Mont-Saint-Guibert", "Kasteelbrakel", "Woutersbrakel", "Chastre",
- "Chastre-Villeroux-Blanmont", "Cortil-Noirmont", "Gentinnes", "Saint-G�ry", "Nil-Saint-Vincent-Saint-Martin",
- "Tourinnes-Saint-Lambert", "Walhain", "Walhain-Saint-Paul", "Itter", "Virginal-Samme", "Haut-Ittre", "Genappe",
- "Baisy-Thy", "Bousval", "Loupoigne", "Vieux-Genappe", "Glabais", "Ways", "Houtain-le-Val", "Klabbeek",
- "Oostkerk", "Sint-Renelde Saintes", "Tubeke Tubize", "Court-Saint-Etienne", "Marbais", "Mellery",
- "Sart-Dames-Avelines", "Tilly", "Villers-la-Ville"
- )
- ),
- array(
- "regionName" => "Vlaams-Brabant",
- "regionShort" => "VB",
- "regionSlug" => "vlaams_brabant",
- "weight" => 1,
- "cities" => array(
- "Halle", "Buizingen", "Lembeek", "Herfelingen", "Herne", "Sint-Pieters-Kapelle", "Bever Bievene", "Hoeilaart",
- "Galmaarden", "Tollembeek", "Vollezele", "Oudenaken", "Sint-Laureins-Berchem", "Sint-Pieters-Leeuw", "Ruisbroek",
- "Vlezenbeek", "Drogenbos", "Linkebeek", "Sint-Genesius-Rode", "Beersel", "Lot", "Alsemberg", "Dworp", "Huizingen",
- "Bogaarden", "Heikruis", "Pepingen", "Elingen", "Beert", "Bellingen", "Dilbeek", "Sint-Martens-Bodegem",
- "Sint-Ulriks-Kapelle", "Itterbeek", "Groot-Bijgaarden", "Schepdaal", "Asse", "Bekkerzeel", "Kobbegem", "Mollem",
- "Relegem", "Zellik", "Ternat", "Wambeek", "Sint-Katherina-Lombeek", "Mazenzele", "Opwijk", "Gaasbeek", "Lennik",
- "Sint-Kwintens-Lennik", "Sint-Martens-Lennik", "Gooik", "Kester", "Leerbeek", "Oetingen", "Onze-Lieve-Vrouw-Lombeek",
- "Pamel", "Roosdaal", "Strijtem", "Borchtlombeek", "Liedekerke", "Wemmel", "Brussegem", "Hamme", "Merchtem", "Affligem",
- "Essene", "Hekelgem", "Teralfene", "Peutie", "Vilvoorde", "Cargovil", "VTM", "Melsbroek", "Perk", "Steenokkerzeel",
- "Machelen", "Diegem", "Londerzeel", "Malderen", "Steenhuffel", "Grimbergen", "Humbeek", "Beigem", "Strombeek-Bever",
- "Meise", "Wolvertem", "Kapelle-op-den-Bos", "Nieuwenrode", "Ramsdonk", "Berg", "Buken", "Kampenhout", "Nederokkerzeel",
- "Nossegem", "Zaventem", "Brucargo", "Sint-Stevens-Woluwe", "Sterrebeek", "Brussel X-Luchthaven Remailing"
- )
- ),
- array(
- "regionName" => "Luik",
- "regionShort" => "LU",
- "regionSlug" => "luik",
- "weight" => 1,
- "cities" => array(
- "Glain", "Luik", "Rocourt", "Bressoux", "Jupille-sur-Meuse", "Li�ge", "Wandre", "Grivegn�e", "Li�ge", "Angleur",
- "Herstal", "Milmort", "Vottem", "Liers", "Chaudfontaine", "Vaux-sous-Ch�vremont", "Beaufays", "Embourg", "B.S.D.",
- "Boncelles", "Seraing", "Jemeppe-sur-Meuse", "Ougr�e", "Ehein", "Neupr�", "Rotheux-Rimi�re", "Neuville-en-Condroz",
- "Plainevaux", "Esneux", "Tilff", "Dolembreux", "Gomz�-Andoumont", "Rouvreux", "Sprimont", "Louveign�", "Anthisnes",
- "Villers-aux-Tours", "Hody", "Tavier", "Comblain-au-Pont", "Poulseur", "Comblain-Fairon", "Comblain-la-Tour", "Hamoir",
- "Filot", "Ferri�res", "My", "Vieuxville", "Werbomont", "Xhoris", "Burdinne", "Hann�che", "Lamontz�e", "Marneffe",
- "Oteppe", "H�ron", "Lavoir", "Waret-l'Ev�que", "Couthuin", "Acosse", "Ambresin", "Meeffe", "Wasseiges", "Bo�lhe",
- "Geer", "Hollogne-sur-Geer", "Lens-Saint-Servais", "Omal", "Darion", "Ligney", "Berloz", "Corswarem", "Rosoux-Crenwick",
- "Avennes", "Braives", "Ciplet", "Fallais", "Fumal", "Ville-en-Hesbaye", "Latinne", "Tourinne", "Abolens",
- "Avernas-le-Bauduin", "Avin", "Bertr�e", "Blehen", "Cras-Avernas", "Crehen", "Grand-Hallet", "Hannut", "Lens-Saint-Remy",
- "Merdorp", "Moxhe", "Petit-Hallet", "Poucet", "Thisnes", "Trogn�e", "Villers-le-Peuplier", "Wansin"
- )
- ),
- array(
- "regionName" => "Namen",
- "regionShort" => "NA",
- "regionSlug" => "namen",
- "weight" => 1,
- "cities" => array(
- "Beez", "Namen", "Belgrade", "Saint-Servais", "Saint-Marc", "Bouge", "Champion", "Daussoulx", "Flawinne",
- "Malonne", "Suarlee", "Temploux", "Vedrin", "Boninne", "Cognelee", "Gelbressee", "Marche-les-Dames", "Beuzet",
- "Ernage", "Gembloux", "Grand-Manil", "Lonzee", "Sauveni�re", "Grand-Leez", "Bossi�re", "Bothey", "Corroy-le-Ch�teau",
- "Isnes", "Mazy", "Arsimont", "Auvelais", "Falisolle", "Keumiee", "Moignelee", "Sambreville", "Tamines",
- "Velaine-sur-Sambre", "Aisemont", "Fosses-la-Ville", "Sart-Eustache", "Vitrival", "Emines", "Rhisnes",
- "Villers-lez-Heest", "Warisoulx", "Bovesse", "Meux", "Saint-Denis-Bovesse", "Dave", "Jambes", "Naninne", "Wepion",
- "Wierde", "Erpe", "Lives-sur-Meuse", "Loy", "Boignee", "Ligny", "Sombreffe", "Tongrinne", "Floreffe", "Floriffoux",
- "Soye", "Arbre", "Bois-de-Villers", "Lesve", "Lustin", "Profondeville", "Rivi�re", "Bal�tre", "Ham-sur-Sambre",
- "Jemeppe-sur-Sambre", "Mornimont", "Moustier-sur-Sambre", "Onoz", "Saint-Martin", "Spy", "Andenne", "Bonneville",
- "Coutisse", "Landenne", "Maizeret", "Sclayn", "Seilles", "Thon", "Vezin", "Bolinne", "Boneffe", "Branchon", "Dhuy",
- "Eghezee", "Hanret", "Leuze", "Liernu"
- )
- ),
- array(
- "regionName" => "Henegouwen",
- "regionShort" => "HE",
- "regionSlug" => "henegouwen",
- "weight" => 1,
- "cities" => array(
- "Charleroi", "Marcinelle", "Couillet", "Dampremy", "Goutroux", "Marchienne-au-Pont", "Monceau-sur-Sambre",
- "Mont-sur-Marchienne", "Jumet", "Gosselies", "Lodelinsart", "Ransart", "Roux", "Gilly", "Montignies-sur-Sambre",
- "Montigny-le-Tilleul", "Landelies", "Cour-sur-Heure", "Ham-sur-Heure", "Ham-sur-Heure-Nalinnes", "Jamioulx",
- "Marbaix", "Nalinnes", "Fontaine-l'Ev�que", "Forchies-la-Marche", "Leernes", "Anderlues", "Courcelles",
- "Gouy-lez-Pi�ton", "Souvret", "Trazegnies", "Bouffioulx", "Ch�telet", "Ch�telineau", "Frasnes-lez-Gosselies",
- "Les Bons Villers", "R�ves", "Villers-Perwin", "Wayaux", "Mellet", "Fleurus", "Heppignies", "Lambusart", "Brye",
- "Wagnel�e", "Wanferc�e-Baulet", "Buzet", "Obaix", "Pont-�-Celles", "Thim�on", "Viesville", "Liberchies", "Luttre",
- "Farciennes", "Pironchamps", "Aiseau", "Aiseau-Presles", "Pont-de-Loup", "Presles", "Roselies", "Acoz", "Gerpinnes",
- "Gougnies", "Joncret", "Loverval", "Villers-Poterie", "Boussu-lez-Walcourt", "Fourbechies", "Froidchapelle",
- "Vergnies", "Erpion", "Baili�vre", "Chimay", "Robechies", "Saint-Remy", "Salles", "Villers-la-Tour", "Virelles",
- "Vaulx-lez-Chimay", "Lompret", "Baileux", "Bourlers", "Forges", "l'Escaill�re", "Ri�zes", "Grandrieu", "Montbliart",
- "Rance", "Sautin", "Sivry", "Sivry-Rance", "Beaumont", "Leugnies", "Leval-Chaudeville", "Renlies", "Solre-Saint-G�ry",
- "Thirimont", "Str�e", "Leers-et-Fosteau", "Thuin", "Biesme-sous-Thuin", "Ragnies", "Bierc�e", "Goz�e", "Donstiennes",
- "Thuillies", "Lobbes", "Mont-Sainte-Genevi�ve", "Sars-la-Buissi�re", "Bienne-lez-Happart", "Bersillies-l'Abbaye",
- "Erquelinnes", "Grand-Reng", "Hantes-Wih�ries", "Montignies-Saint-Christophe", "Solre-sur-Sambre", "Fontaine-Valmont",
- "Labuissi�re", "Merbes-le-Ch�teau", "Merbes-Sainte-Marie", "Momignies", "Macon", "Monceau-Imbrechies", "Macquenoise",
- "Beauwelz", "Forge-Philippe", "Seloignes", "Bergen Mons", "Ghlin", "Fl�nu", "Jemappes", "Maisi�res", "Nimy", "Havr�"
- )
- ),
- array(
- "regionName" => "Luxemburg",
- "regionShort" => "LX",
- "regionSlug" => "luxemburg",
- "weight" => 1,
- "cities" => array(
- "Bastogne", "Longvilly", "Noville", "Villers-la-Bonne-Eau", "Wardin", "Martelange", "Fauvillers", "Hollange",
- "Tintange", "Hompr�", "Morhet", "Nives", "Sibret", "Vaux-lez-Rosieres", "Vaux-sur-Sure", "Juseret", "Houffalize",
- "Nadrin", "Mont", "Tailles", "Tavigny", "Mabompr�", "Wibrin", "Gouvy", "Limerl�", "Bovigny", "Beho", "Cherain",
- "Montleban", "Amberloup", "Sainte-Ode", "Tillet", "Lavacherie", "Flamierge", "Bertogne", "Longchamps", "Bihain",
- "Vielsalm", "Petit-Thier", "Grand-Halleux", "Arlon", "Bonnert", "Heinsch", "Toernich", "Guirsch", "Autelbas",
- "Attert", "Nobressart", "Nothomb", "Thiaumont", "Tontelange", "Habay", "Habay-la-Neuve", "Hachy", "Anlier",
- "Habay-la-Vieille", "Houdemont", "Rulles", "Bellefontaine", "Rossignol", "Saint-Vincent", "Tintigny", "Etalle",
- "Sainte-Marie-sur-Semois", "Villers-sur-Semois", "Vance", "Chantemelle", "Buzenol", "Ch�tillon", "Meix-le-Tige",
- "Saint-L�ger", "Musson", "Mussy-la-Ville", "Signeulx", "Bleid", "Ethe", "Ruette", "Virton", "Latour", "Saint-Mard",
- "Dampicourt", "Harnoncourt", "Lamorteau", "Rouvroy", "Torgny", "G�rouville", "Meix-Devant-Virton", "Robelmont",
- "Sommethonne", "Villers-la-Loue", "Hondelange", "Messancy", "Wolkrange", "S�lange", "Habergy", "Aubange",
- "Athus", "Halanzy", "Rachecourt", "Bras", "Freux", "Libramont-Chevigny", "Moircy", "Recogne", "Remagne",
- "Sainte-Marie-Chevigny", "Saint-Pierre", "Chiny", "Izel", "Jamoigne", "Les Bulles", "Suxy", "Termes",
- "Florenville", "Fontenoille", "Muno"
- )
- ),
- array(
- "regionName" => "West-Vlaanderen",
- "regionShort" => "WV",
- "regionSlug" => "west_vlaanderen",
- "weight" => 1,
- "cities" => array(
- "Brugge Bruges", "Koolkerke", "Hertsberge", "Oostkamp", "Ruddervoorde", "Waardamme", "Sint-Andries",
- "Sint-Michiels", "Loppem", "Veldegem", "Zedelgem", "Aartrijke", "Knokke", "Knokke-Heist", "Westkapelle",
- "Heist-aan-Zee", "Ramskapelle", "Assebroek", "Sint-Kruis", "Damme", "Hoeke", "Lapscheure", "Moerkerke",
- "Oostkerke", "Sijsele", "Blankenberge", "Uitkerke", "Houtave", "Meetkerke", "Nieuwmunster", "Zuienkerke",
- "Dudzele", "Lissewege", "Zeebrugge", "Oostende", "Stene", "Zandvoorde", "De Haan", "Klemskerke", "Wenduine",
- "Vlissegem", "Middelkerke", "Wilskerke", "Leffinge", "Mannekensvere", "Schore", "Sint-Pieters-Kapelle",
- "Slijpe", "Spermalie", "Lombardsijde", "Westende", "Bredene", "Ettelgem", "Oudenburg", "Roksem", "Westkerke",
- "Gistel", "Moere", "Snaaskerke", "Zevekote", "Bekegem", "Eernegem", "Ichtegem", "Jabbeke", "Snellegem",
- "Stalhille", "Varsenare", "Zerkegem", "Kortrijk", "Bissegem", "Heule", "Bellegem", "Kooigem", "Marke",
- "Rollegem", "Aalbeke", "Kuurne", "Harelbeke", "Bavikhove", "Hulste", "Deerlijk", "Zwevegem", "Heestert", "Moen",
- "Otegem", "Sint-Denijs", "Gullegem", "Moorsele", "Wevelgem", "Anzegem", "Gijzelbrechtegem", "Ingooigem", "Vichte",
- "Kaster", "Tiegem", "Avelgem", "Kerkhove", "Waarmaarde", "Outrijve", "Bossuit", "Helkijn", "Spiere",
- "Spiere-Helkijn", "Beerst", "Diksmuide", "Driekapellen", "Esen", "Kaaskerke", "Keiem", "Lampernisse", "Leke",
- "Nieuwkapelle", "Oostkerke", "Oudekapelle"
- )
- ),
- array(
- "regionName" => "Oost-Vlaanderen",
- "regionShort" => "OV",
- "regionSlug" => "oost_vlaanderen",
- "weight" => 1,
- "cities" => array(
- "Gent", "Mariakerke", "Drongen", "Wondelgem", "Sint-Amandsberg", "Oostakker", "Desteldonk", "Mendonk", "Sint-Kruis-Winkel",
- "Gentbrugge", "Ledeberg", "Afsnee", "Sint-Denijs-Westrem", "Zwijnaarde", "Zelzate", "Destelbergen", "Heusden",
- "Beervelde", "Lochristi", "Zaffelare", "Zeveneken", "Gontrode", "Melle", "Nieuwkerken-Waas", "Sint-Niklaas",
- "Belsele", "Sinaai-Waas", "Beveren", "Haasdonk", "Kallo", "Melsele", "Vrasene", "Doel", "Kallo", "Kieldrecht",
- "Verrebroek", "Elversele", "Steendorp", "Temse", "Tielrode", "Bazel", "Kruibeke", "Rupelmonde", "Daknam", "Eksaarde",
- "Lokeren", "De Klinge", "Meerdonk", "Sint-Gillis-Waas", "Sint-Pauwels", "Moerbeke", "Wachtebeke", "Kemzeke", "Stekene",
- "Appels", "Baasrode", "Dendermonde", "Grembergen", "Mespelare", "Oudegem", "Schoonaarde", "Sint-Gillis-bij-Dendermonde",
- "Hamme", "Moerzeke", "Massemen", "Westrem", "Wetteren", "Zele", "Waasmunster", "Buggenhout", "Opdorp", "Schellebelle",
- "Serskamp", "Wichelen", "Kalken", "Laarne", "Denderbelle", "Lebbeke", "Wieze", "Berlare", "Overmere", "Uitbergen",
- "Aalst", "Gijzegem", "Hofstade", "Baardegem", "Herdersem", "Meldert", "Moorsel", "Erembodegem", "Nieuwerkerken", "Impe",
- "Lede", "Oordegem", "Smetlede", "Wanzele", "Appelterre-Eichem", "Denderwindeke", "Lieferinge", "Nederhasselt"
- )
- )
- );
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/Brazil/Brazil.class.php b/plugins/countries/Brazil/Brazil.class.php
deleted file mode 100644
index e2e15c2c4..000000000
--- a/plugins/countries/Brazil/Brazil.class.php
+++ /dev/null
@@ -1,234 +0,0 @@
- array(
- "format" => "xxxxx-xxx",
- "replacements" => array(
- "X" => "123456789",
- "x" => "0123456789",
- "Y" => "012345678",
- "W" => "01234567",
- "V" => "0123456",
- "U" => "012345",
- "T" => "01234",
- "S" => "0123",
- "R" => "678",
- "Q" => "89",
- "P" => "3456"
- )
- )
- );
-
-
- protected $countryData = array(
- array(
- "regionName" => "São Paulo",
- "regionShort" => "SP",
- "regionSlug" => "sau_paulo",
- "weight" => 41,
- "cities" => array(
- "Guarulhos", "Campinas", "Osasco", "Ribeirão Preto", "Mauá", "Mogi das Cruzes", "Diadema",
- "Jundiaí", "Carapicuíba", "Piracicaba"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "1Xxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Minas Gerais",
- "regionShort" => "MG",
- "regionSlug" => "minas_gerais",
- "weight" => 20,
- "cities" => array(
- "Belo Horizonte", "Uberlândia", "Contagem", "Juiz de Fora", "Betim", "Montes Claros",
- "Ribeirão das Neves", "Uberaba", "Governador Valadares", "Ipatinga", "Sete Lagoas",
- "Divinópolis", "Santa Luzia"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "3xxxxxxx"
- )
- )
- ),
- array(
- "regionName" => "Rio de Janeiro",
- "regionShort" => "RJ",
- "regionSlug" => "rio",
- "weight" => 16,
- "cities" => array(
- "Rio de Janeiro", "São Gonçalo", "Duque de Caxias", "Nova Iguaçu", "Niterói", "Belford Roxo",
- "Campos dos Goytacazes", "São João de Meriti", "Petrópolis"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "2Yxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Bahia",
- "regionShort" => "BA",
- "regionSlug" => "bahia",
- "weight" => 14,
- "cities" => array(
- "Salvador", "Feira de Santana", "Vitória da Conquista", "Camaçari", "Itabuna",
- "Juazeiro", "Ilhéus", "Lauro de Freitas"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "4Yxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Rio Grande do Sul",
- "regionShort" => "RS",
- "regionSlug" => "rio_grande",
- "weight" => 11,
- "cities" => array(
- "Porto Alegre", "Caxias do Sul", "Pelotas", "Canoas", "Santa Maria", "Gravataí",
- "Novo Hamburgo", "Rio Grande"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "9xxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Paraná",
- "regionShort" => "PR",
- "regionSlug" => "parana",
- "weight" => 11,
- "cities" => array(
- "Curitiba", "Londrina", "Maringá", "Ponta Grossa", "Cascavel", "São José dos Pinhais",
- "Foz do Iguaçu", "Colombo", "Guarapuava", "Paranaguá"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "8Wxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Pernambuco",
- "regionShort" => "PE",
- "regionSlug" => "pernambuco",
- "weight" => 9,
- "cities" => array(
- "Recife", "Jaboatão dos Guararapes", "Olinda", "Caruaru", "Paulista", "Petrolina",
- "Cabo de Santo Agostinho", "Camaragibe"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "5Vxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Ceará",
- "regionShort" => "CE",
- "regionSlug" => "ceara",
- "weight" => 9,
- "cities" => array(
- "Fortaleza", "Caucaia", "Juazeiro do Norte", "Maracanaú", "Sobral", "Crato",
- "Itapipoca", "Maranguape"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "6Sxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Pará",
- "regionShort" => "PA",
- "regionSlug" => "para",
- "weight" => 8,
- "cities" => array(
- "Belém", "Ananindeua", "Santarém", "Marabá", "Castanhal", "Parauapebas",
- "Abaetetuba", "Cametá", "Bragança"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "6RYxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Maranhão",
- "regionShort" => "MA",
- "regionSlug" => "maranhao",
- "weight" => 7,
- "cities" => array(
- "São Luís", "Imperatriz", "Timon", "Caxias", "Codó", "Paço do Lumiar", "Açailândia",
- "Bacabal", "Santa Inês", "Balsas", "Chapadinha", "Barra do Corda"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "65xxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Santa Catarina",
- "regionShort" => "SC",
- "regionSlug" => "santa_catarina",
- "weight" => 6,
- "cities" => array(
- "Joinville", "Florianópolis", "Blumenau", "São José", "Criciúma", "Chapecó", "Itajaí"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "8Qxxx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Goiás",
- "regionShort" => "GO",
- "regionSlug" => "goias",
- "weight" => 6,
- "cities" => array(
- "Goiânia", "Aparecida de Goiânia", "Anápolis", "Rio Verde", "Luziânia",
- "Águas Lindas de Goiás", "Valparaíso de Goiás"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "7P7xx-xxx"
- )
- )
- ),
- array(
- "regionName" => "Paraíba",
- "regionShort" => "PB",
- "regionSlug" => "paraiba",
- "weight" => 4,
- "cities" => array(
- "João Pessoa", "Campina Grande", "Santa Rita", "Patos", "Bayeux", "Sousa", "Cajazeiras"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "58xxx-xxx"
- )
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/Canada/Canada.class.php b/plugins/countries/Canada/Canada.class.php
deleted file mode 100644
index a74b576e8..000000000
--- a/plugins/countries/Canada/Canada.class.php
+++ /dev/null
@@ -1,323 +0,0 @@
- array(
- "format" => "%*@ *@*",
- "replacements" => array(
- "%" => "ABCEGHJKLMNPRSTVXY",
- "*" => "0123456789",
- "@" => "ABCEGHJKLMNPRSTVWXYZ",
-
- // used in individual provinces below
- "&" => "GHJ",
- "^" => "KLMNP"
- )
- ),
-
- // the general phone format and area codes for the country
- "phoneFormat" => array(
- "areaCodes" => array(587, 603, 780, 205, 604, 778, 204, 431, 506, 709, 902, 867, 416, 647, 437, 519, 226, 613,
- 343, 705, 249, 807, 905, 289, 365, 902, 418, 581, 450, 579, 514, 438, 819, 873, 306),
- "displayFormats" => array(
- "(AAA) Xxx-xxxx",
- "1 (AAA) Xxx-xxxx",
- "1-AAA-Xxx-xxxx"
- )
- )
- );
-
- // our country-wide data, with info separated into regions
- protected $countryData = array(
- array(
- "regionName" => "Alberta",
- "regionShort" => "AB",
- "regionSlug" => "alberta",
- "weight" => 11,
- "cities" => array(
- "Airdrie", "Alix", "Banff", "Barrhead", "Bearberry", "Beaumont", "Bon Accord", "Bonnyville",
- "Bonnyville Municipal District", "Bowden", "Breton", "Bruderheim", "Calgary", "Calmar", "Camrose",
- "Canmore", "Carstairs", "Castor", "Chestermere", "Clearwater Municipal District", "Coaldale", "Coalhurst",
- "Cochrane", "Crowsnest Pass", "Crystal Springs", "Devon", "Drayton Valley", "Drumheller", "Eckville",
- "Edmonton", "Fahler", "Fort Saskatchewan", "Gibbons", "Glendon", "Grande Prairie", "Grande Cache",
- "High Level", "Hines Creek", "Innisfail", "Irricana", "Jasper", "Kitscoty", "Lac La Biche County",
- "Lac Ste. Anne", "Lacombe", "Lacombe County", "Lakeland County", "Lamont", "Leduc", "Legal", "Lloydminster",
- "Lethbridge", "Mayerthorpe", "Medicine Hat", "Millet", "Morinville", "Mundare", "Nanton", "New Sarepta",
- "Okotoks", "Oyen", "Provost", "Parkland County", "Penhold", "Picture Butte", "Pincher Creek", "Ponoka",
- "Raymond", "Red Deer", "Redwater", "Rimbey", "Rocky Mountain House", "Rocky View", "Rycroft", "St. Albert",
- "St. Paul", "Sedgewick", "Smoky Lake", "Spruce Grove", "Stirling", "Strathcona County", "Stony Plain", "Sundrie",
- "Sunset Point", "Swan Hills", "Sylvan Lake", "Taber", "Tofield", "Trochu", "Valleyview", "Vegreville", "Vilna",
- "Wabamun", "Warburg", "Warspite", "Westlock", "Wetaskiwin", "Wood Buffalo", "Woodlands County", "Yellowhead County"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "T*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(587, 603, 780)
- )
- )
- ),
- array(
- "regionName" => "British Columbia",
- "regionShort" => "BC",
- "regionSlug" => "british_columbia",
- "weight" => 13,
- "cities" => array(
- "100 Mile House", "Abbotsford", "Alert Bay", "Armstrong", "Belcarra", "Burnaby", "Burns Lake", "Cache Creek",
- "Cariboo Regional District", "Castlegar", "Chetwynd", "Chilliwack", "Coldstream", "Colwood", "Comox", "Coquitlam",
- "Cranbrook", "Dawson Creek", "Delta", "Fernie", "Duncan", "Fort St. John", "Fraser Lake", "Fraser-Fort George",
- "Gibsons", "Harrison Hot Springs", "Hope", "Houston", "Hudson's Hope", "Kelowna", "Kent", "Kimberly", "Kitimat",
- "Lake Cowichan", "Langford", "Langley", "Lions Bay", "Mission", "Maple Ridge", "Merritt", "Midway", "Nanaimo",
- "Nakusp", "Nelson", "New Westminster", "North Cowichan", "North Saanich", "North Vancouver", "Oliver",
- "Pemberton", "Penticton", "Pitt Meadows", "Port Alice", "Port Coquitlam", "Port Moody", "Prince George",
- "Qualicum Beach", "Richmond", "Salt Spring Island", "Silverton", "Smithers", "Sooke", "Sparwood", "Stewart",
- "Sunshine Coast Regional District", "Surrey", "Terrance", "Tumbler Ridge", "Ucluelet", "Vancouver", "Vanderhoof",
- "Victoria", "West Vancouver", "White Rock", "Williams Lake"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "V*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(205, 604, 778)
- )
- )
- ),
- array(
- "regionName" => "Manitoba",
- "regionShort" => "MB",
- "regionSlug" => "manitoba",
- "weight" => 4,
- "cities" => array(
- 'Winnipeg', 'Stonewall', 'Minitonas', 'Lourdes', 'Flin Flon', 'Daly', 'Brandon', 'Beausejour'
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "R*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(204, 431)
- )
- )
- ),
- array(
- "regionName" => "New Brunswick",
- "regionShort" => "NB",
- "regionSlug" => "new_brunswick",
- "weight" => 2,
- "cities" => array(
- 'Tracadie-Shelia', 'Sh�diac', 'Shippagan', 'Saint-L�onard', 'Saint John', 'Saint Andr�', 'Rothesay', 'Rexton',
- 'Quispamsis', 'Oromocto', 'New Maryland', 'Moncton', 'Miramichi', 'Grand Falls', 'Fredericton', 'Edmundston',
- 'Dieppe', 'Clare', 'Carlton'
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "E*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(506)
- )
- )
- ),
- array(
- "regionName" => "Newfoundland and Labrador",
- "regionShort" => "NL",
- "regionSlug" => "newfoundland_and_labrador",
- "weight" => 2,
- "cities" => array(
- 'St. John\'s', 'Springdale', 'Spaniard\'s Bay', 'Rigolet', 'Paradise', 'Mount Pearl', 'McCallum', 'Marystown',
- 'Harbour Grace', 'Glovertown', 'Gander', 'Fogo', 'Fortune', 'Carbonear', 'Burin', 'Bonavista', 'Bay Roberts'
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "A*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(709)
- )
- )
- ),
- array(
- "regionName" => "Northwest Territories",
- "regionShort" => "NT",
- "regionSlug" => "northwest_territories",
- "weight" => 1,
- "cities" => array(
- "Yellowknife", "Wrigley", "Wha Ti", "Wekweti", "Tulita", "Tuktoyaktuk", "Tsiigehtchic", "Sachs Harbour",
- "Rae Lakes", "Rae-Edzo", "Paulatuk", "Norman Wells", "Lutsel K'e", "Kakisa", "Inuvik", "Holman", "Hay River",
- "Fort Smith", "Fort Simpson", "Fort Resolution", "Fort Providence", "Fort McPherson", "Fort Laird",
- "Fort Good Hope", "Enterprise", "Deline", "Coleville Lake", "Aklavik"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "X*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(867)
- )
- )
- ),
- array(
- "regionName" => "Nova Scotia",
- "regionShort" => "NS",
- "regionSlug" => "nova_scotia",
- "weight" => 1,
- "cities" => array(
- 'Municipal District', 'Town of Yarmouth', 'Wolfville', 'Pugwash', 'Pictou', 'New Glasgow',
- 'Halifax', 'Guysborough', 'Cumberland County', 'Cape Breton Island', 'Berwick', 'Baddeck', 'Argyle',
- 'Annapolis Royal', 'Annapolis County'
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "B*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(902)
- )
- )
- ),
- array(
- "regionName" => "Nunavut",
- "regionShort" => "NU",
- "regionSlug" => "nunavut",
- "weight" => 1,
- "cities" => array(
- "Arviat", "Cambridge Bay", "Gjoa Haven", "Pangnirtung", "Iqaluit"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "X*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(867)
- )
- )
- ),
- array(
- "regionName" => "Ontario",
- "regionShort" => "ON",
- "regionSlug" => "ontario",
- "weight" => 39,
- "cities" => array(
- "Ajax", "Aurora", "Ancaster Town", "Barrie", "Bath", "Blind River", "Burlington", "Caledon", "Cobourg",
- "Cornwall", "Cumberland", "East Gwillimbury", "Essex", "Etobicoke", "Gloucester", "Goderich", "Grey County",
- "Guelph", "Hamilton", "Hearst", "Kapuskasing", "Kawartha Lakes", "Kearny", "King Township", "Kingston",
- "Kitchener", "Lakeshore", "Lanark County", "LaSalle", "Leamington", "Malahide", "Markham",
- "Merrickville-Wolford", "Midlands", "Township of Minden Hills", "Minto", "Newbury", "Newmarket",
- "Norfolk County", "North Bay", "Northumberland", "Orangeville", "Orilla", "Osgoode", "Ottawa",
- "Ottawa-Carleton", "Owen Sound", "Oxford County", "Pickering", "Port Hope", "Quinte West", "Ramara",
- "Renfrew", "Richmond Hill", "Russell", "Scarborough", "St. Catharines", "St. Thomas", "Greater Sudbury",
- "Tay", "Thorold", "Thunder Bay", "Toronto", "Valley East", "Vanier", "Vaughan", "Warwick", "Welland",
- "Whitby", "Whitchurch-Stouffville", "Whitewater Region Township", "Wilmont", "Windsor", "Woodstock"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "^*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(416, 647, 437, 519, 226, 613, 343, 705, 249, 807, 905, 289, 365)
- )
- )
- ),
- array(
- "regionName" => "Prince Edward Island",
- "regionShort" => "PE",
- "regionSlug" => "prince_edward_island",
- "weight" => 1,
- "cities" => array(
- "Charlottetown", "Montague", "Stratford"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "C*@ *@*"
- ),
- "phoneFormats" => array(
- "areaCodes" => array(902)
- )
- )
- ),
- array(
- "regionName" => "Quebec",
- "regionShort" => "QC",
- "regionSlug" => "quebec",
- "weight" => 23,
- "cities" => array(
- "Amqui", "Cabano", "D�gelis", "La Matap�dia", "Price", "Rimouski", "Rivi�re-du-Loup", "Saint-Eug�ne-de-Ladri�re",
- "Sainte-Flavie", "Alma", "Chambord", "Chicoutimi", "Jonqui�re", "La Baie", "Labrecque", "Saguenay", "Saint-Honor�",
- "Saint-Prime", "Shipshaw", "Baie-Saint-Paul", "Beauport", "Cap-Rouge", "Deschambault", "Isle-aux-Coudres", "Lac-Serent",
- "Malbaie", "Neuville", "Pointe-au-Pic", "Qu�bec City", "Saint-Hilarion", "Saint-Urbain", "Batiscan",
- "Cap-de-la-Madeleine", "Champlain", "H�rouxville", "Pointe-du-Lac", "Saint-Georges", "Shawinigan", "Trois-Rivi�res",
- "Asbestos", "Richmond", "Sherbrooke", "Valcourt", "Anjou", "Baie-d'Urf�", "Beaconsfield", "C�te-Saint-Luc",
- "Dollard-des-Ormeaux", "Dorval", "Hampstead", "Kirkland", "Lachine", "LaSalle", "Montreal", "Outremont", "Pierrefonds",
- "Pointe-aux-Trembles", "Pointe-Claire", "Roxboro", "Saint-Laurent", "Saint-L�onard", "Saint-Pierre", "Senneville",
- "Verdun", "Westmount", "Aylmer", "Buckingham", "Cantley", "Chelsea", "Collines-de-l'Outaouais", "Gatineau", "Hull",
- "Ville de Maniwaki", "Mansfield-et-Pontefract", "Montebello", "Montpellier", "Namur", "Notre-Dame-de-la-Salette",
- "Shawville", "Thurso", "Dubuisson", "Malartic", "Notre-Dame-du-Nord", "Rouyn-Noranda", "Saint-Eug�ne-de-Guigues",
- "Baie-Comeau", "Fermont", "Kawawachikamach", "Matagami", "Caplan", "Carleton", "Gasp�", "Gespeg", "Maria",
- "Murdochville", "Cap-Saint-Ignace", "Charny", "L�vis"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "&*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(418, 581, 450, 579, 514, 438, 819, 873)
- )
- )
- ),
- array(
- "regionName" => "Saskatchewan",
- "regionShort" => "SK",
- "regionSlug" => "saskatchewan",
- "weight" => 4,
- "cities" => array(
- "Assiniboia", "Calder", "Canora", "Estevan", "Gravelbourg", "Hudson Bay", "Lang", "Langenburg", "Lloydminster",
- "Macklin", "Maple Creek", "Milestone", "Moose Jaw", "North Battleford", "Prince Albert", "Regina", "Saskatoon",
- "Weyburn", "Yorkton"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "S*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(306)
- )
- )
- ),
- array(
- "regionName" => "Yukon",
- "regionShort" => "YT",
- "regionSlug" => "yukon",
- "weight" => 1,
- "cities" => array(
- "Whitehorse", "Watson Lake"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "Y*@ *@*"
- ),
- "phoneFormat" => array(
- "areaCodes" => array(867)
- )
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/CostaRica/CostaRica.class.php b/plugins/countries/CostaRica/CostaRica.class.php
deleted file mode 100644
index f54d6ae4d..000000000
--- a/plugins/countries/CostaRica/CostaRica.class.php
+++ /dev/null
@@ -1,195 +0,0 @@
-
- */
-
-class Country_CostaRica extends CountryPlugin {
- protected $continent = "central_america";
- protected $countryName = "Costa Rica";
- protected $countrySlug = "CR";
- protected $regionNames = "Costa Rican Provinces";
-
- protected $extendedData = array(
- "zipFormat" => array(
- "format" => "ZYxYx",
- "replacements" => array(
- "Z" => "1234567",
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "displayFormats" => array(
- "xxxxxxxx",
- "xxxx-xxxx"
- )
- )
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Alajuela",
- "regionShort" => "A",
- "regionSlug" => "alajuela",
- "weight" => 20,
- "cities" => array(
- "Alajuela", "Quesada", "San José de Alajuela", "San Rafael"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "2zxYx",
- "replacements" => array(
- "z" => "01",
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "format" => "24xxxxxx"
- )
- )
- ),
- array(
- "regionName" => "Cartago",
- "regionShort" => "C",
- "regionSlug" => "cartago",
- "weight" => 11,
- "cities" => array(
- "Aguacaliente (San Francisco)", "Carmen", "Cartago", "Paraíso", "San Diego", "San Nicolás",
- "San Rafael", "Tejar", "Turrialba"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "30xYx",
- "replacements" => array(
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "format" => "25xxxxxx"
- )
- )
- ),
- array(
- "regionName" => "Guanacaste",
- "regionShort" => "G",
- "regionSlug" => "guanacaste",
- "weight" => 8,
- "cities" => array(
- "Cañas", "Liberia", "Nicoya"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "5zxYx",
- "replacements" => array(
- "z" => "01",
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "format" => "26xxxxxx"
- )
- )
- ),
- array(
- "regionName" => "Heredia",
- "regionShort" => "H",
- "regionSlug" => "heredia",
- "weight" => 10,
- "cities" => array(
- "Heredia", "Mercedes", "San Francisco", "San Pablo", "Ulloa (Barrial)"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "4zxYx",
- "replacements" => array(
- "z" => "01",
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "format" => "25xxxxxx"
- )
- )
- ),
- array(
- "regionName" => "Limón",
- "regionShort" => "L",
- "regionSlug" => "limón",
- "weight" => 2,
- "cities" => array(
- "Guápiles", "Limón (Puerto Limón)", "Siquirres"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "50yYx",
- "replacements" => array(
- "y" => "12",
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "format" => "27xxxxxx"
- )
- )
- ),
- array(
- "regionName" => "Puntarenas",
- "regionShort" => "P",
- "regionSlug" => "puntarenas",
- "weight" => 2,
- "cities" => array(
- "Barranca", "Puntarenas"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "ZYxYx",
- "replacements" => array(
- "Z" => "1234567",
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "format" => "27xxxxxx"
- )
- )
- ),
- array(
- "regionName" => "San José",
- "regionShort" => "SJ",
- "regionSlug" => "san_josé",
- "weight" => 33,
- "cities" => array(
- "Alajuelita", "Aserrí", "Calle Blancos", "Cinco Esquinas", "Concepción", "Curridabat", "Desamparados", "Gravilias", "Guadalupe", "Ipís",
- "Mata de Plátano", "Patalillo", "Patarrá", "Purral", "San Antonio", "San Felipe", "San Isidro", "San Isidro de El General", "San José",
- "San Juan (San Juan de Tibás)", "San Juan de Dios", "San Miguel", "San Pedro", "San Rafael", "San Rafael Abajo", "San Vicente", "Tirrases"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "1zxYx",
- "replacements" => array(
- "z" => "012",
- "Y" => "01",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "format" => "25xxxxxx"
- )
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/France/France.class.php b/plugins/countries/France/France.class.php
deleted file mode 100644
index 4b077f116..000000000
--- a/plugins/countries/France/France.class.php
+++ /dev/null
@@ -1,223 +0,0 @@
- "xxxxx"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Île-de-France",
- "regionShort" => "IL",
- "regionSlug" => "ile_de_france",
- "weight" => 18.3,
- "cities" => array(
- "Paris", "Boulogne-Billancourt", "Saint-Denis", "Argenteuil", "Montreuil", "Créteil", "Nanterre",
- "Courbevoie", "Versailles", "Vitry-sur-Seine", "Colombes", "Asnières-sur-Seine", "Aulnay-sous-Bois",
- "Rueil-Malmaison", "Aubervilliers", "Champigny-sur-Marne", "Saint-Maur-des-Fossés", "Drancy",
- "Issy-les-Moulineaux", "Levallois-Perret", "Noisy-le-Grand"
- )
- ),
- array(
- "regionName" => "Provence-Alpes-Côte d'Azur",
- "regionShort" => "PR",
- "regionSlug" => "provence_alpes",
- "weight" => 9.6,
- "cities" => array(
- "Marseille", "Nice", "Toulon", "Aix-en-Provence", "Avignon", "Antibes", "Cannes", "La Seyne-sur-Mer",
- "Hyères", "Arles", "Fréjus", "Grasse", "Martigues", "Cagnes-sur-Mer", "Aubagne", "Salon-de-Provence",
- "Istres", "Le Cannet", "Gap", "Draguignan", "Vitrolles"
- )
- ),
- array(
- "regionName" => "Nord-Pas-de-Calais",
- "regionShort" => "NO",
- "regionSlug" => "nord_pas_de_calais",
- "weight" => 7.6,
- "cities" => array(
- "Lille", "Roubaix", "Dunkerque", "Tourcoing", "Calais", "Villeneuve-d'Ascq", "Valenciennes", "Boulogne-sur-Mer",
- "Douai", "Arras", "Wattrelos", "Marcq-en-Baroeul", "Lens", "Cambrai", "Liévin", "Maubeuge", "Lambersart",
- "Hénin-Beaumont", "Béthune"
- )
- ),
- array(
- "regionName" => "Pays de la Loire",
- "regionShort" => "PA",
- "regionSlug" => "pays_de_la_loire",
- "weight" => 6.3,
- "cities" => array(
- "Nantes", "Angers", "Le Mans", "Saint-Nazaire", "Cholet", "La Roche-sur-Yon", "Laval", "Saint-Herblain",
- "Rezé", "Saumur", "Saint-Sébastien-sur-Loire", "Orvault", "Vertou"
- )
- ),
- array(
- "regionName" => "Aquitaine",
- "regionShort" => "AQ",
- "regionSlug" => "aquitaine",
- "weight" => 5.5,
- "cities" => array(
- "Bordeaux", "Pau", "Mérignac", "Pessac", "Bayonne", "Talence", "Anglet", "Agen", "Mont-de-Marsan",
- "Périgueux", "Villenave-d'Ornon", "Saint-Médard-en-Jalles", "Bergerac", "Biarritz", "Bègles"
- )
- ),
- array(
- "regionName" => "Bretagne",
- "regionShort" => "BR",
- "regionSlug" => "bretagne",
- "weight" => 4.9,
- "cities" => array(
- "Rennes", "Brest", "Quimper", "Lorient", "Vannes", "Saint-Malo", "Saint-Brieuc", "Lanester"
- )
- ),
- array(
- "regionName" => "Midi-Pyrénées",
- "regionShort" => "MI",
- "regionSlug" => "midi_pyrenees",
- "weight" => 4.4,
- "cities" => array(
- "Toulouse", "Montauban", "Albi", "Tarbes", "Castres", "Colomiers", "Tournefeuille", "Rodez"
- )
- ),
- array(
- "regionName" => "Languedoc-Roussillon",
- "regionShort" => "LA",
- "regionSlug" => "languedoc_rousillon",
- "weight" => 4.1,
- "cities" => array(
- "Montpellier", "Nîmes", "Perpignan", "Béziers", "Narbonne", "Carcassonne", "Sète", "Alès", "Lunel"
- )
- ),
- array(
- "regionName" => "Centre",
- "regionShort" => "CE",
- "regionSlug" => "centre",
- "weight" => 4,
- "cities" => array(
- "Tours", "Orléans", "Bourges", "Blois", "Châteauroux", "Chartres", "Joué-lès-Tours", "Dreux", "Vierzon"
- )
- ),
- array(
- "regionName" => "Lorraine",
- "regionShort" => "LO",
- "regionSlug" => "lorraine",
- "weight" => 3.7,
- "cities" => array(
- "Metz", "Nancy", "Thionville", "Épinal", "Vandoeuvre-lès-Nancy", "Montigny-lès-Metz", "Sarreguemines",
- "Forbach", "Saint-Dié-des-Vosges"
- )
- ),
- array(
- "regionName" => "Picardie",
- "regionShort" => "PI",
- "regionSlug" => "picardie",
- "weight" => 3,
- "cities" => array(
- "Amiens", "Saint-Quentin", "Beauvais", "Compiègne", "Creil", "Soissons", "Laon", "Abbeville"
- )
- ),
- array(
- "regionName" => "Alsace",
- "regionShort" => "AL",
- "regionSlug" => "alsace",
- "weight" => 2.9,
- "cities" => array(
- "Strasbourg", "Mulhouse", "Colmar", "Haguenau", "Schiltigheim", "Illkirch-Graffenstaden", "Saint-Louis"
- )
- ),
- array(
- "regionName" => "Haute-Normandie",
- "regionShort" => "HA",
- "regionSlug" => "haute_normandie",
- "weight" => 2.8,
- "cities" => array(
- "Le Havre", "Rouen", "Évreux", "Dieppe", "Sotteville-lès-Rouen", "Saint-Étienne-du-Rouvray", "Vernon",
- "Le Grand-Quevilly", "Le Petit-Quevilly"
- )
- ),
- array(
- "regionName" => "Poitou-Charentes",
- "regionShort" => "PO",
- "regionSlug" => "poitou_charentes",
- "weight" => 2.7,
- "cities" => array(
- "Poitiers", "La Rochelle", "Niort", "Angoulême", "Châtellerault", "Saintes"
- )
- ),
- array(
- "regionName" => "Bourgogne",
- "regionShort" => "BO",
- "regionSlug" => "bourgogne",
- "weight" => 2.6,
- "cities" => array(
- "Dijon", "Chalon-sur-Saône", "Nevers", "Auxerre", "Mâcon", "Sens"
- )
- ),
- array(
- "regionName" => "Basse-Normandie",
- "regionShort" => "BA",
- "regionSlug" => "basse_normandie",
- "weight" => 2.3,
- "cities" => array(
- "Caen", "Cherbourg-Octeville", "Alençon", "Lisieux", "Hérouville-Saint-Clair", "Saint-Lô"
- )
- ),
- array(
- "regionName" => "Auvergne",
- "regionShort" => "AU",
- "regionSlug" => "auvergne",
- "weight" => 2.1,
- "cities" => array(
- "Clermont-Ferrand", "Montluçon", "Aurillac", "Vichy", "Moulins", "Le Puy-en-Velay"
- )
- ),
- array(
- "regionName" => "Champagne-Ardenne",
- "regionShort" => "CH",
- "regionSlug" => "champagne_ardenne",
- "weight" => 2.1,
- "cities" => array(
- "Reims", "Troyes", "Charleville-Mézières", "Châlons-en-Champagne", "Saint-Dizier", "Épernay"
- )
- ),
- array(
- "regionName" => "Franche-Comté",
- "regionShort" => "FC",
- "regionSlug" => "franche_comte",
- "weight" => 1.8,
- "cities" => array(
- "Besançon", "Belfort", "Montbéliard", "Dole", "Pontarlier"
- )
- ),
- array(
- "regionName" => "Limousin",
- "regionShort" => "LI",
- "regionSlug" => "limousin",
- "weight" => 1.1,
- "cities" => array(
- "Limoges", "Brive-la-Gaillarde"
- )
- ),
- array(
- "regionName" => "Corse",
- "regionShort" => "CO",
- "regionSlug" => "corse",
- "weight" => 0.5,
- "cities" => array(
- "Ajaccio", "Bastia"
- )
- )
- );
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
\ No newline at end of file
diff --git a/plugins/countries/Germany/Germany.class.php b/plugins/countries/Germany/Germany.class.php
deleted file mode 100644
index 336518db7..000000000
--- a/plugins/countries/Germany/Germany.class.php
+++ /dev/null
@@ -1,185 +0,0 @@
- "xxxxx"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Baden",
- "regionShort" => "BW",
- "regionSlug" => "baden",
- "weight" => 301,
- "cities" => array(
- "Stuttgart", "Mannheim", "Karlsruhe", "Freiburg", "Heidelberg", "Heilbronn", "Ulm", "Pforzheim", "Reutlingen",
- "Esslingen", "T�bingen", "Ludwigsburg", "Konstanz", "Villingen-Schwennin", "Aalen", "Sindelfingen", "Schw�bisch Gm�nd",
- "Friedrichshafen", "Offenburg", "G�ppingen", "Baden-Baden", "Waiblingen", "Ravensburg", "L�rrach", "Heidenheim",
- "Rastatt", "B�blingen"
- )
- ),
- array(
- "regionName" => "Bavaria",
- "regionShort" => "BY",
- "regionSlug" => "bavaria",
- "weight" => 178,
- "cities" => array(
- "Munich", "Nuremberg", "Augsburg", "Regensburg", "W�rzburg", "Ingolstadt", "F�rth", "Erlangen",
- "Bayreuth", "Bamberg", "Aschaffenburg", "Landshut", "Kempten", "Rosenheim", "Neu-Ulm", "Schweinfurt",
- "Passau", "Hof", "Freising", "Straubing"
- )
- ),
- array(
- "regionName" => "Berlin",
- "regionShort" => "BE",
- "regionSlug" => "berlin",
- "weight" => 3890,
- "cities" => array(
- "Berlin"
- )
- ),
- array(
- "regionName" => "Brandenburg",
- "regionShort" => "BB",
- "regionSlug" => "brandenburg",
- "weight" => 85,
- "cities" => array(
- "Potsdam", "Cottbus", "Brandenburg", "Frankfurt", "Oranienburg", "Falkensee", "Eberswalde-Finow",
- "Bernau", "K�nigs Wusterhausen", "Schwedt", "F�rstenwalde", "Neuruppin", "Eisenh�ttenstadt", "Senftenberg",
- "Strausberg", "Hennigsdorf", "Blankenfelde-Mahlow", "Rathenow", "Hohen Neuendorf", "Ludwigsfelde", "Spremberg",
- "Werder", "Teltow", "Wandlitz", "Luckenwalde", "Forst", "Kleinmachnow", "Prenzlau", "Panketal", "Guben"
- )
- ),
- array(
- "regionName" => "Bremen",
- "regionShort" => "HB",
- "regionSlug" => "bremen",
- "weight" => 1577,
- "cities" => array(
- "Bremen", "Bremerhaven"
- )
- ),
- array(
- "regionName" => "Hamburg",
- "regionShort" => "HH",
- "regionSlug" => "hamburg",
- "weight" => 2368,
- "cities" => array(
- "Hamburg"
- )
- ),
- array(
- "regionName" => "Hesse",
- "regionShort" => "HE",
- "regionSlug" => "hesse",
- "weight" => 287,
- "cities" => array(
- "Frankfurt am Main", "Wiesbaden", "Kassel", "Darmstadt", "Offenbach am Main", "Hanau", "Marburg", "Gie�en", "Fulda",
- "R�sselsheim", "Wetzlar", "Bad Homburg v. d. H�he", "Rodgau", "Oberursel", "Dreieich", "Maintal", "Bensheim",
- "Hofheim am Taunus", "Neu-Isenburg", "Langen", "Limburg a.d. Lahn", "Dietzenbach", "Lampertheim", "M�rfelden-Walldorf",
- "Viernheim", "Bad Hersfeld", "Bad Nauheim", "Taunusstein", "Baunatal", "Kelkheim", "Bad Vilbel", "Friedberg",
- "M�hlheim am Main", "R�dermark", "Heppenheim", "Dillenburg", "Pfungstadt", "Hattersheim am Main", "Butzbach",
- "Friedrichsdorf", "Obertshausen", "Korbach", "Griesheim", "Gro�-Gerau", "Weiterstadt", "Eschwege"
- )
- ),
- array(
- "regionName" => "Lower Saxony",
- "regionShort" => "NI",
- "regionSlug" => "lower_saxony",
- "weight" => 166,
- "cities" => array(
- "Hannover", "Braunschweig", "Osnabr�ck", "Oldenburg", "Wolfsburg", "G�ttingen", "Hildesheim", "Salzgitter", "Wilhelmshaven",
- "Delmenhorst", "L�neburg", "Celle", "Garbsen", "Hameln", "Wolfenb�ttel", "Nordhorn", "Langenhagen", "Emden", "Lingen",
- "Cuxhaven", "Peine", "Stade", "Melle", "Neustadt am R�benberge", "Lehrte", "Seevetal", "Gifhorn", "Wunstorf", "Goslar"
- )
- ),
- array(
- "regionName" => "Mecklenburg-Vorpommern",
- "regionShort" => "MV",
- "regionSlug" => "meckleburg",
- "weight" => 71,
- "cities" => array(
- "Anklam", "Bergen", "Greifswald", "G�strow", "Neubrandenburg", "Neustrelitz", "Parchim City", "Ribnitz-Damgarten",
- "Rostock", "Schwerin", "Stralsund", "Waren", "Wismar"
- )
- ),
- array(
- "regionName" => "North Rhine-Westphalia",
- "regionShort" => "NW",
- "regionSlug" => "westphalia",
- "weight" => 523,
- "cities" => array(
- "K�ln", "D�sseldorf", "Dortmund", "Essen", "Duisburg", "Bochum", "Wuppertal", "Bonn", "Bielefeld", "M�nster", "Aachen",
- "M�nchengladbach", "Gelsenkirchen", "Krefeld", "Oberhausen", "Hagen", "Hamm", "M�lheim", "Herne", "Leverkusen", "Solingen",
- "Neuss", "Paderborn", "Recklinghausen", "Bottrop", "Remscheid", "Bergisch Gladbach"
- )
- ),
- array(
- "regionName" => "Rhineland-Palatinate",
- "regionShort" => "RP",
- "regionSlug" => "rhineland",
- "weight" => 202,
- "cities" => array(
- "Mainz", "Ludwigshafen", "Koblenz", "Trier", "Kaiserslauter", "Worms", "Neuwied", "Neustadt", "Speyer", "Frankenthal",
- "Bad Kreuznach", "Landau", "Pirmasens", "Zweibr�cken", "Idar-Oberstei", "Andernach", "Bad Neuenahr-Ahrweiler",
- "Bingen", "Ingelheim", "Germersheim", "Ha�loch", "Schifferstadt", "Bad D�rkheim"
- )
- ),
- array(
- "regionName" => "Saarland",
- "regionShort" => "SL",
- "regionSlug" => "saarland",
- "weight" => 400,
- "cities" => array(
- "Saarbr�cken", "Neunkirchen", "Homburg", "V�lklingen", "Sankt Ingbert", "Saarlouis", "Merzig", "Sankt Wendel",
- "Blieskastel", "Dillingen", "Lebach", "P�ttlingen", "Heusweiler", "Wadgassen", "Bexbach", "Schwalbach", "Sulzbach"
- )
- ),
- array(
- "regionName" => "Saxony",
- "regionShort" => "SN",
- "regionSlug" => "saxony",
- "weight" => 227,
- "cities" => array(
- "Leipzig", "Dresden", "Chemnitz", "Zwickau", "Plauen", "G�rlitz", "Freiberg", "Bautzen", "Freital", "Pirna",
- "Hoyerswerda", "Radebeul", "Riesa", "Grimma", "Zittau", "Mei�en", "Delitzsch", "Limbach-Oberfrohna", "Markkleeberg", "Glauchau"
- )
- ),
- array(
- "regionName" => "Saxony-Anhalt",
- "regionShort" => "ST",
- "regionSlug" => "saxony_anhalt",
- "weight" => 116,
- "cities" => array(
- "Halle", "Magdeburg", "Dessau", "Wittenberg", "Bitterfeld-Wolfen", "Stendal", "Halberstadt", "Wei�enfels", "Bernburg",
- "Merseburg", "Wernigerode", "Naumburg", "Sch�nebeck", "Zeitz", "Sangerhausen", "Aschersleben", "Quedlinburg", "Sta�furt",
- "K�then", "Eisleben", "Salzwedel", "Burg"
- )
- ),
- array(
- "regionName" => "Schleswig-Holstein",
- "regionShort" => "SH",
- "regionSlug" => "saarland",
- "weight" => 179,
- "cities" => array(
- "Kiel", "L�beck", "Flensburg", "Neum�nster", "Norderstedt", "Elmshorn", "Pinneberg", "Itzehoe", "Wedel", "Ahrensburg",
- "Geesthacht", "Rendsburg", "Henstedt-Ulzburg", "Reinbek", "Bad Oldesloe", "Schleswig", "Eckernf�rde", "Husum", "Heide",
- "Quickborn"
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
\ No newline at end of file
diff --git a/plugins/countries/India/India.class.php b/plugins/countries/India/India.class.php
deleted file mode 100644
index 1cee10099..000000000
--- a/plugins/countries/India/India.class.php
+++ /dev/null
@@ -1,398 +0,0 @@
-
- */
-
-class Country_India extends CountryPlugin {
- protected $countryName = "India";
- protected $countrySlug = "india";
- protected $regionNames = "Indian States & UT"; //Union Territories
- protected $continent = "asia";
-
- protected $extendedData = array(
- "zipFormat" => array(
- "format" => "Xxxxxx",
- "replacements" => array(
- "X" => "123456789",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "displayFormats" => array(
- "+91 xxxxxxxxxx",
- "0xx-xxxxxxxx",
- "0xxx-xxxxxxx",
- "0xxxx-xxxxxx",
- )
- )
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Andhra Pradesh",
- "regionShort" => "AP",
- "regionSlug" => "andhra_pradesh",
- "weight" => 1314,
- "cities" => array(
- "Adoni","Anantapur","Bhimavaram","Chittoor","Cuddapah","Eluru","Gudivada",
- "Guntakal","Guntur","Hindupur","Hyderabad","Kakinada","Karimnagar","Khammam",
- "Kukatpalle","Kurnool","Lalbahadur Nagar","Machilipatnam",
- "Mahbubnagar","Malkajgiri","Nandyal","Nellore","Nizamabad","Ongole",
- "Proddatur","Qutubullapur","Rajahmundry","Ramagundam","Secunderabad",
- "Tenali","Tirupati","Vijayawada","Vishakhapatnam","Vizianagaram","Warangal"
- )
- ),
- array(
- "regionName" => "Arunachal Pradesh",
- "regionShort" => "AR",
- "regionSlug" => "arunachal_pradesh",
- "weight" => 21,
- "cities" => array(
- "Itanagar"
- )
- ),
- array(
- "regionName" => "Assam",
- "regionShort" => "AS",
- "regionSlug" => "assam",
- "weight" => 484,
- "cities" => array(
- "Guwahati","Dibrugarh","Silchar","Nagaon",
- )
- ),
- array(
- "regionName" => "Bihar",
- "regionShort" => "BR",
- "regionSlug" => "bihar",
- "weight" => 1611,
- "cities" => array(
- "Patna","Gaya","Bhagalpur","Muzaffarpur","Darbhanga","Bihar Sharif",
- "Arrah","Katihar","Munger","Chapra","Sasaram","Dehri","Bettiah"
- )
- ),
- array(
- "regionName" => "Chhattisgarh",
- "regionShort" => "CT",
- "regionSlug" => "chhattisgarh",
- "weight" => 396,
- "cities" => array(
- "Raipur","Bhilai","Bilaspur","Durg","Raj Nandgaon","Korba","Raigarh"
- )
- ),
- array(
- "regionName" => "Goa",
- "regionShort" => "GA",
- "regionSlug" => "goa",
- "weight" => 23,
- "cities" => array(
- "Panjim"
- )
- ),
- array(
- "regionName" => "Gujarat",
- "regionShort" => "GJ",
- "regionSlug" => "gujarat",
- "weight" => 937,
- "cities" => array(
- "Ahmedabad","Surat","Vadodara","Rajkot","Bhavnagar","Jamnagar","Nadiad",
- "Bharuch","Junagadh","Navsari","Gandhinagar","Veraval","Porbandar","Anand",
- "Surendranagar","Gandhidham","Bhuj","Godhra","Patan","Morvi","Vejalpur"
- )
- ),
- array(
- "regionName" => "Haryana",
- "regionShort" => "HR",
- "regionSlug" => "haryana",
- "weight" => 394,
- "cities" => array(
- "Ambala","Ambala Sadar","Bhiwani","Faridabad","Gurgaon","Hisar","Karnal",
- "Panipat","Rohtak","Sirsa","Sonipat","Yamuna Nagar"
- )
- ),
- array(
- "regionName" => "Himachal Pradesh",
- "regionShort" => "HP",
- "regionSlug" => "himachal_pradesh",
- "weight" => 106,
- "cities" => array(
- "Shimla"
- )
- ),
- array(
- "regionName" => "Jammu and Kashmir",
- "regionShort" => "JK",
- "regionSlug" => "jammu_and_kashmir",
- "weight" => 195,
- "cities" => array(
- "Jammu", "Srinagar"
- )
- ),
- array(
- "regionName" => "Jharkhand",
- "regionShort" => "JH",
- "regionSlug" => "jharkhand",
- "weight" => 512,
- "cities" => array(
- "Bokaro Steel City","Dhanbad","Hazaribag","Jamshedpur","Mango","Purnea",
- "Purulia","Ranchi"
- )
- ),
- array(
- "regionName" => "Karnataka",
- "regionShort" => "KA",
- "regionSlug" => "karnataka",
- "weight" => 949,
- "cities" => array(
- "Bangalore","Belgaum","Bellary","Bidar","Bijapur","Davangere","Gadag Betigeri",
- "Gulbarga","Hassan","Hospet","Hubli","Mandya","Mangalore","Mysore","Raichur",
- "Shimoga","Timkur"
- )
- ),
- array(
- "regionName" => "Kerala",
- "regionShort" => "KL",
- "regionSlug" => "kerala",
- "weight" => 518,
- "cities" => array(
- "Allappuzha","Kozhikode","Cochin","Kollam","Palakkad","Thalassery","Trivandrum"
- )
- ),
- array(
- "regionName" => "Madhya Pradesh",
- "regionShort" => "MP",
- "regionSlug" => "madhya_pradesh",
- "weight" => 1127,
- "cities" => array(
- "Bhind","Bhopal","Burhanpur","Chhindwara","Damoh","Dewas","Guna","Gwalior",
- "Indore","Jabalpur","Khandwa","Mandasor","Morena","Murwara","Ratlam","Rewa",
- "Sagar","Satna","Shivapuri","Ujjain","Vidisha"
- )
- ),
- array(
- "regionName" => "Maharastra",
- "regionShort" => "MH",
- "regionSlug" => "maharastra",
- "weight" => 1744,
- "cities" => array(
- "Achalpur","Ahmadnagar","Akola","Amravati","Bhir","Bhiwandi","Bhusawal",
- "Chandrapur","Dhule","Gondiya","Ichalkaranji","Jalgaon","Jalna","Kalyan",
- "Kolhapur","Latur","Malegaon","Mira Bhayandar","Miraj","Mumbai","Nagpur",
- "Nanded","Nashik","New Bombay","Parbhani","Pimpri-Chinchwad","Pune","Sangli",
- "Satara","Aurangabad","Solapur","Thane","Ulhasnagar","Wardha","Yeotmal"
- )
- ),
- array(
- "regionName" => "Manipur",
- "regionShort" => "MN",
- "regionSlug" => "manipur",
- "weight" => 42,
- "cities" => array(
- "Imphal"
- )
- ),
- array(
- "regionName" => "Meghalaya",
- "regionShort" => "ML",
- "regionSlug" => "meghalaya",
- "weight" => 46,
- "cities" => array(
- "Shillong"
- )
- ),
- array(
- "regionName" => "Mizoram",
- "regionShort" => "MZ",
- "regionSlug" => "mizoram",
- "weight" => 17,
- "cities" => array(
- "Aizwal"
- )
- ),
- array(
- "regionName" => "Nagaland",
- "regionShort" => "NL",
- "regionSlug" => "nagaland",
- "weight" => 31,
- "cities" => array(
- "Kohima"
- )
- ),
- array(
- "regionName" => "Odisha",
- "regionShort" => "OR",
- "regionSlug" => "odisha",
- "weight" => 651,
- "cities" => array(
- "Bhubaneswar","Brahmapur","Cuttack","Puri","Raurkela","Raurkela Civil Township","Sambalpur"
- )
- ),
- array(
- "regionName" => "Punjab",
- "regionShort" => "PB",
- "regionSlug" => "punjab",
- "weight" => 430,
- "cities" => array(
- "Abohar","Ahmadpur East","Amritsar","Bahawalnagar","Bahawalpur","Bhatinda","Chiniot",
- "Chishtian Mandi","Daska","Dera Ghazi Khan","Faisalabad","Gojra","Gujranwala","Gujrat",
- "Hafizabad","Hoshiarpur","Jalandhar (Jullundur)","Jaranwala","Jhang","Jhelum","Kamalia",
- "Kamoke","Kasur","Khanewal","Khanpur","Lahore","Ludhiana","Mandi Bahauddin","Mandi Burewala",
- "Moga","Multan","Muridke","Muzaffargarh","Okara","Pak Pattan","Pathankot","Patiala",
- "Rahim Yar Khan","Rawalpindi","Sadiqabad","Sahiwal","Sargodha","Sheikhupura","Sialkot",
- "Vihari","Wah","Wazirabad"
- )
- ),
- array(
- "regionName" => "Rajasthan",
- "regionShort" => "RJ",
- "regionSlug" => "rajasthan",
- "weight" => 1065,
- "cities" => array(
- "Ajmer","Alwar","Beawar","Bharatpur","Bhilwara","Bikaner","Ganganagar",
- "Jaipur","Jodhpur","Kota","Pali","Sikar","Tonk","Udaipur"
- )
- ),
- array(
- "regionName" => "Sikkim",
- "regionShort" => "SK",
- "regionSlug" => "sikkim",
- "weight" => 9,
- "cities" => array(
- "Gangtok"
- )
- ),
- array(
- "regionName" => "Tamil Nadu",
- "regionShort" => "TN",
- "regionSlug" => "Tamil Nadu",
- "weight" => 1120,
- "cities" => array(
- "Alandur","Ambattur","Avadi","Chennai","Coimbatore","Cuddalore","Dindigul","Erode",
- "Kanchipuram","Kumbakonam","Madurai","Nagarcoil","Neyveli","Palayankottai","Pallavaram",
- "Pudukkottai","Rajapalaiyam","Salem","Tambaram","Thanjavur","Tiruchirapalli",
- "Tirunelveli","Tiruppur","Tiruvannamalai","Tiruvarur","Tiruvottiyur","Tuticorin",
- "Valparai","Vellore"
- )
- ),
- array(
- "regionName" => "Tripura",
- "regionShort" => "TR",
- "regionSlug" => "tripura",
- "weight" => 57,
- "cities" => array(
- "Agartala"
- )
- ),
- array(
- "regionName" => "Uttar Pradesh",
- "regionShort" => "UP",
- "regionSlug" => "uttar_pradesh",
- "weight" => 3098,
- "cities" => array(
- "Agra","Aligarh","Allahabad","Amroha","Bahraich","Banda","Bareilly","Budaun",
- "Bulandshahr","Etawah","Faizabad","Farrukhabad-cum-Fatehgarh","Fatehpur","Firozabad",
- "Ghaziabad","Gonda","Gorakhpur","Hapur","Hathras","Jaunpur","Jhansi","Kanpur",
- "Kanpur Cantonment","Lucknow","Mathura","Maunath Bhanjan","Meerut",
- "Meerut Cantonment","Mirzapur-cum-Vindhyachal","Modinagar","Moradabad",
- "Muzaffarnagar","Noida","Orai","Pilibhit","Rae Bareli","Rampur","Saharanpur",
- "Sambhal","Shahjahanpur","Sitapur","Unnao","Varanasi"
- )
- ),
- array(
- "regionName" => "Uttarakhand",
- "regionShort" => "UT",
- "regionSlug" => "uttarakhand",
- "weight" => 157,
- "cities" => array(
- "Dehradun", "Haridwar"
- )
- ),
- array(
- "regionName" => "West Bengal",
- "regionShort" => "WB",
- "regionSlug" => "west_bengal",
- "weight" => 1418,
- "cities" => array(
- "Asansol","Ashoknagar-Kalyangarh","Baidyabati","Bally","Balurghat","Bankura",
- "Bansberia","Barahanagar","Barasat","Barddhaman","Barrackpur","Basirhat",
- "Berhampore","Bhatpara","Burnpur","Kolkata","Champdani","Chandannagar","Dabgram",
- "Durgapur","Habra","Haldia","Halisahar","Howrah","Hugli-Chinsurah","Ingraj Bazar",
- "Kamarhati","Kanchrapara","Kharagpur","Krishnanagar","Kulti-Barakar","Midnapore",
- "Naihati","Navadwip","North Barrackpur","North Dum Dum","Panihati","Raiganj",
- "Rishra","Santipur","Serampore","Siliguri","South Dum Dum","Titagarh","Uluberia",
- "Uttarpara-Kotrung"
- )
- ),
- //Union Territories
- array(
- "regionName" => "Andaman and Nicobar Islands",
- "regionShort" => "AN",
- "regionSlug" => "andaman_and_nicobar_islands",
- "weight" => 6,
- "cities" => array(
- "Port Blair"
- )
- ),
- array(
- "regionName" => "Chandigarh",
- "regionShort" => "CH",
- "regionSlug" => "Chandigarh",
- "weight" => 16,
- "cities" => array(
- "Chandigarh"
- )
- ),
- array(
- "regionName" => "Dadra and Nagar Haveli",
- "regionShort" => "DN",
- "regionSlug" => "dadra_and_nagar_haveli",
- "weight" => 5,
- "cities" => array(
- "Silvassa"
- )
- ),
- array(
- "regionName" => "Daman and Diu",
- "regionShort" => "DD",
- "regionSlug" => "Daman and Diu",
- "weight" => 4,
- "cities" => array(
- "Daman"
- )
- ),
- array(
- "regionName" => "Lakshadweep",
- "regionShort" => "LD",
- "regionSlug" => "lakshadweep",
- "weight" => 1,
- "cities" => array(
- "Kavaratti"
- )
- ),
- array(
- "regionName" => "Delhi",
- "regionShort" => "DL",
- "regionSlug" => "delhi",
- "weight" => 260,
- "cities" => array(
- "Delhi"
- )
- ),
- array(
- "regionName" => "Pondicherry",
- "regionShort" => "PY",
- "regionSlug" => "pondicherry",
- "weight" => 19,
- "cities" => array(
- "Pondicherry"
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/Italy/Italy.class.php b/plugins/countries/Italy/Italy.class.php
deleted file mode 100644
index 3013c5768..000000000
--- a/plugins/countries/Italy/Italy.class.php
+++ /dev/null
@@ -1,1202 +0,0 @@
- "xxxxx"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Piemonte",
- "regionShort" => "PIE",
- "regionSlug" => "piemonte",
- "weight" => 7,
- "cities" => array(
- "Alessandria",
- "Asti",
- "Barbania",
- "Bonvicino",
- "Borghetto di Borbera",
- "Borgomasino",
- "Borgone Susa",
- "Borriana",
- "Caprauna",
- "Casanova Elvo",
- "Cassano Spinola",
- "Coassolo Torinese",
- "Colleretto Castelnuovo",
- "Crescentino",
- "Cressa",
- "Cuceglio",
- "Ferrere",
- "Ghislarengo",
- "Marentino",
- "Massello",
- "Melazzo",
- "Meugliano",
- "Molino dei Torti",
- "Mombaruzzo",
- "Moncrivello",
- "Montacuto",
- "Montaldo Bormida",
- "Monteu Roero",
- "Olcenengo",
- "Olivola",
- "Orta San Giulio",
- "Ponti",
- "Premeno",
- "Priero",
- "Quarona",
- "Rocca d'Arazzo",
- "Roccabruna",
- "Rueglio",
- "San Maurizio Canavese",
- "Serralunga d'Alba",
- "Sommariva Perno",
- "Strona",
- "Torino",
- "Valmacca",
- "Vauda Canavese",
- "Vespolate",
- "Villafalletto",
- "Villafranca d'Asti",
- "Villar Pellice",
- "Villata",
- )
- ),
-
- array(
- "regionName" => "Valle d'Aosta",
- "regionShort" => "VDA",
- "regionSlug" => "valledaosta",
- "weight" => 1,
- "cities" => array(
- "Allein",
- "Antey-Saint-Andrè",
- "Aosta",
- "Arvier",
- "Avise",
- "Ayas",
- "Bard",
- "Bionaz",
- "Brusson",
- "Challand-Saint-Victor",
- "Chambave",
- "Champorcher",
- "Chatillon",
- "Donnas",
- "Doues",
- "Emarèse",
- "Etroubles",
- "Gignod",
- "Gressan",
- "Gressoney-La-Trinitè",
- "Gressoney-Saint-Jean",
- "Introd",
- "Issime",
- "La Magdeleine",
- "La Salle",
- "La Thuile",
- "Lillianes",
- "Montjovet",
- "Morgex",
- "Nus",
- "Oyace",
- "Pollein",
- "Pont-Saint-Martin",
- "Pontboset",
- "Pontey",
- "Prè-Saint-Didier",
- "Rhemes-Notre-Dame",
- "Rhemes-Saint-Georges",
- "Saint-Denis",
- "Saint-Marcel",
- "Saint-Nicolas",
- "Saint-Oyen",
- "Saint-Pierre",
- "Saint-Rhémy-en-Bosses",
- "Sarre",
- "Torgnon",
- "Valpelline",
- "Valtournenche",
- "Verrayes",
- "Verrès",
- )
- ),
-
- array(
- "regionName" => "Lombardia",
- "regionShort" => "LOM",
- "regionSlug" => "lombardia",
- "weight" => 16,
- "cities" => array(
- "Acquafredda",
- "Annone di Brianza",
- "Asso",
- "Barghe",
- "Blevio",
- "Borno",
- "Brescia",
- "Calco",
- "Cambiago",
- "Caprino Bergamasco",
- "Casnate con Bernate",
- "Castello dell'Acqua",
- "Castelmarte",
- "Castelseprio",
- "Chiari",
- "Cisano Bergamasco",
- "Civo",
- "Colico",
- "Corvino San Quirico",
- "Crotta d'Adda",
- "Fino Mornasco",
- "Galbiate",
- "Gambolò",
- "Gavirate",
- "Gianico",
- "Lasnigo",
- "Lenna",
- "Luino",
- "Mantova",
- "Malgesso",
- "Milano",
- "Motta Visconti",
- "Ospedaletto Lodigiano",
- "Pagazzano",
- "Pavone del Mella",
- "Pero",
- "Pogliano Milanese",
- "Polpenazze del Garda",
- "Rea",
- "Ripalta Guerina",
- "Rudiano",
- "Salvirola",
- "San Damiano al Colle",
- "Sannazzaro de' Burgondi",
- "Somma Lombardo",
- "Sulzano",
- "Torno",
- "Val Rezzo",
- "Valera Fratta",
- "Villa Cortese",
- )
- ),
-
- array(
- "regionName" => "Trentino-Alto Adige",
- "regionShort" => "TAA",
- "regionSlug" => "trentinoaltoadige",
- "weight" => 2,
- "cities" => array(
- "Albiano",
- "Andalo",
- "Bedollo",
- "Bolzano/Bozen",
- "Bondo",
- "Borgo Valsugana",
- "Braies/Prags",
- "Campitello di Fassa",
- "Castello Tesino",
- "Centa San Nicolò",
- "Chiusa/Klausen",
- "Cles",
- "Curon Venosta/Graun im Vinschgau",
- "Daiano",
- "Dro",
- "Gargazzone/Gargazon",
- "Giustino",
- "La Valle/Wengen",
- "Laces/Latsch",
- "Lagundo/Algund",
- "Laives/Leifers",
- "Lauregno/Laurein",
- "Livo",
- "Martello/Martell",
- "Mezzana",
- "Monguelfo-Tesido/Welsberg-Taisten",
- "Panchià",
- "Pellizzano",
- "Pomarolo",
- "Preore",
- "Rabbi",
- "Rodengo/Rodeneck",
- "Rovereto",
- "San Martino in Badia/St. Martin in Thurn",
- "Sanzeno",
- "Scena/Schenna",
- "Sfruz",
- "Spormaggiore",
- "Terlago",
- "Termeno sulla strada del vino/Tramin an der Weinstrasse",
- "Terragnolo",
- "Tiarno di Sopra",
- "Tione di Trento",
- "Trento",
- "Valda",
- "Vandoies/Vintl",
- "Varena",
- "Varna/Vahrn",
- "Villa Agnedo",
- "Ziano di Fiemme",
- )
- ),
-
- array(
- "regionName" => "Veneto",
- "regionShort" => "VEN",
- "regionSlug" => "veneto",
- "weight" => 8,
- "cities" => array(
- "Adria",
- "Arsiè",
- "Asigliano Veneto",
- "Bevilacqua",
- "Bolzano Vicentino",
- "Bussolengo",
- "Camponogara",
- "Castelbaldo",
- "Castello di Godego",
- "Cavaion Veronese",
- "Cavaso del Tomba",
- "Codognè",
- "Feltre",
- "Ficarolo",
- "Follina",
- "Galzignano Terme",
- "Grezzana",
- "Limena",
- "Lozzo Atestino",
- "Maser",
- "Monfumo",
- "Montebelluna",
- "Oderzo",
- "Pescantina",
- "Pieve di Cadore",
- "Piovene Rocchette",
- "Polesella",
- "Ponte San Nicolò",
- "Ponte nelle Alpi",
- "Portobuffolè",
- "Posina",
- "Pramaggiore",
- "Romano d'Ezzelino",
- "San Gregorio nelle Alpi",
- "San Pietro Mussolino",
- "San Zenone degli Ezzelini",
- "Sant'Elena",
- "Sant'Urbano",
- "Selva di Cadore",
- "Solesino",
- "Sorgà",
- "Sossano",
- "Soverzene",
- "Sovizzo",
- "Spresiano",
- "Susegana",
- "Tarzo",
- "Tezze sul Brenta",
- "Venezia",
- "Verona",
- )
- ),
-
- array(
- "regionName" => "Friuli-Venezia Giulia",
- "regionShort" => "FVG",
- "regionSlug" => "friuliveneziagiulia",
- "weight" => 2,
- "cities" => array(
- "Amaro",
- "Attimis",
- "Bertiolo",
- "Bicinicco",
- "Castelnovo del Friuli",
- "Cavasso Nuovo",
- "Cimolais",
- "Clauzetto",
- "Colloredo di Monte Albano",
- "Comeglians",
- "Dignano",
- "Fogliano Redipuglia",
- "Fontanafredda",
- "Forgaria nel Friuli",
- "Gonars",
- "Grado",
- "Latisana",
- "Lauco",
- "Lestizza",
- "Ligosullo",
- "Lusevera",
- "Marano Lagunare",
- "Medea",
- "Meduno",
- "Paularo",
- "Polcenigo",
- "Pordenone",
- "Porpetto",
- "Pozzuolo del Friuli",
- "Pradamano",
- "Prato Carnico",
- "Precenicco",
- "Ragogna",
- "Reana del Rojale",
- "Roveredo in Piano",
- "Ruda",
- "San Floriano del Collio",
- "San Vito al Tagliamento",
- "Sauris",
- "Sesto al Reghena",
- "Sgonico",
- "Socchieve",
- "Tarcento",
- "Tarvisio",
- "Tramonti di Sopra",
- "Treppo Carnico",
- "Trieste",
- "Turriaco",
- "Verzegnis",
- "Vito d'Asio",
- )
- ),
-
- array(
- "regionName" => "Liguria",
- "regionShort" => "LIG",
- "regionSlug" => "liguria",
- "weight" => 3,
- "cities" => array(
- "Alassio",
- "Albisola Superiore",
- "Aquila d'Arroscia",
- "Armo",
- "Bajardo",
- "Bargagli",
- "Bergeggi",
- "Bolano",
- "Borghetto di Vara",
- "Borgomaro",
- "Cairo Montenotte",
- "Calice al Cornoviglio",
- "Campomorone",
- "Castelbianco",
- "Castelnuovo Magra",
- "Castelvecchio di Rocca Barbena",
- "Ceranesi",
- "Chiavari",
- "Chiusanico",
- "Cicagna",
- "Cisano sul Neva",
- "Diano Arentino",
- "Dolceacqua",
- "Dolcedo",
- "Erli",
- "Fontanigorda",
- "Genova",
- "La Spezia",
- "Masone",
- "Massimino",
- "Mignanego",
- "Millesimo",
- "Montoggio",
- "Nasino",
- "Ortonovo",
- "Perinaldo",
- "Portofino",
- "Recco",
- "Rezzoaglio",
- "Savona",
- "Seborga",
- "Terzorio",
- "Tribogna",
- "Urbe",
- "Valbrevenna",
- "Vezzi Portio",
- "Villa Faraldi",
- "Villanova d'Albenga",
- "Zignago",
- "Zuccarello",
- )
- ),
-
- array(
- "regionName" => "Emilia-Romagna",
- "regionShort" => "ERM",
- "regionSlug" => "emiliaromagna",
- "weight" => 7,
- "cities" => array(
- "Baiso",
- "Baricella",
- "Bazzano",
- "Berceto",
- "Bologna",
- "Calestano",
- "Casina",
- "Castel Guelfo di Bologna",
- "Castel Maggiore",
- "Codigoro",
- "Collecchio",
- "Colorno",
- "Compiano",
- "Conselice",
- "Corte Brugnatella",
- "Fontanellato",
- "Forlì",
- "Fusignano",
- "Gaggio Montano",
- "Gattatico",
- "Jolanda di Savoia",
- "Luzzara",
- "Maranello",
- "Marzabotto",
- "Modena",
- "Monghidoro",
- "Montese",
- "Monticelli d'Ongina",
- "Ostellato",
- "Palagano",
- "Palanzano",
- "Pievepelago",
- "Poggio Berni",
- "Porretta Terme",
- "Portico e San Benedetto",
- "Poviglio",
- "Reggio nell'Emilia",
- "Rio Saliceto",
- "Sala Baganza",
- "San Clemente",
- "San Lazzaro di Savena",
- "San Polo d'Enza",
- "Sant'Agata Bolognese",
- "Sant'Agata sul Santerno",
- "Santarcangelo di Romagna",
- "Sissa",
- "Travo",
- "Tresigallo",
- "Viano",
- "Zerba",
- )
- ),
-
- array(
- "regionName" => "Toscana",
- "regionShort" => "TOS",
- "regionSlug" => "toscana",
- "weight" => 6,
- "cities" => array(
- "Anghiari",
- "Bientina",
- "Buti",
- "Capannori",
- "Casciana Terme",
- "Casole d'Elsa",
- "Castel San Niccolò",
- "Castellina in Chianti",
- "Castiglione di Garfagnana",
- "Certaldo",
- "Comano",
- "Coreglia Antelminelli",
- "Empoli",
- "Fauglia",
- "Firenze",
- "Gavorrano",
- "Grosseto",
- "Livorno",
- "Massa e Cozzile",
- "Massarosa",
- "Minucciano",
- "Monte San Savino",
- "Montelupo Fiorentino",
- "Montemignaio",
- "Monteroni d'Arbia",
- "Montignoso",
- "Orciano Pisano",
- "Pelago",
- "Piancastagnaio",
- "Pietrasanta",
- "Pisa",
- "Ponsacco",
- "Pontedera",
- "Pratovecchio",
- "Radicofani",
- "Rio Marina",
- "Rio nell'Elba",
- "Riparbella",
- "Sambuca Pistoiese",
- "San Marcello Pistoiese",
- "San Piero a Sieve",
- "Santa Croce sull'Arno",
- "Santa Fiora",
- "Santa Maria a Monte",
- "Siena",
- "Subbiano",
- "Vagli Sotto",
- "Vergemoli",
- "Villafranca in Lunigiana",
- "Vinci",
- )
- ),
-
- array(
- "regionName" => "Umbria",
- "regionShort" => "UMB",
- "regionSlug" => "umbria",
- "weight" => 1,
- "cities" => array(
- "Acquasparta",
- "Allerona",
- "Alviano",
- "Amelia",
- "Arrone",
- "Attigliano",
- "Avigliano Umbro",
- "Baschi",
- "Bastia Umbra",
- "Bevagna",
- "Castel Giorgio",
- "Castel Ritaldi",
- "Castiglione del Lago",
- "Cerreto di Spoleto",
- "Fabro",
- "Ficulle",
- "Foligno",
- "Fossato di Vico",
- "Fratta Todina",
- "Giove",
- "Gualdo Cattaneo",
- "Gualdo Tadino",
- "Gubbio",
- "Lisciano Niccone",
- "Lugnano in Teverina",
- "Marsciano",
- "Massa Martana",
- "Monte Santa Maria Tiberina",
- "Monteleone di Spoleto",
- "Montone",
- "Nocera Umbra",
- "Norcia",
- "Orvieto",
- "Otricoli",
- "Parrano",
- "Penna in Teverina",
- "Perugia",
- "Piegaro",
- "Poggiodomo",
- "Polino",
- "San Venanzo",
- "Scheggino",
- "Sellano",
- "Sigillo",
- "Spoleto",
- "Terni",
- "Todi",
- "Torgiano",
- "Umbertide",
- "Valfabbrica",
- )
- ),
-
- array(
- "regionName" => "Marche",
- "regionShort" => "MAR",
- "regionSlug" => "marche",
- "weight" => 3,
- "cities" => array(
- "Acquasanta Terme",
- "Altidona",
- "Ancona",
- "Arquata del Tronto",
- "Barchi",
- "Belvedere Ostrense",
- "Cagli",
- "Caldarola",
- "Camerino",
- "Camporotondo di Fiastrone",
- "Castel Colonna",
- "Cossignano",
- "Falerone",
- "Fiuminata",
- "Loreto",
- "Macerata",
- "Maiolati Spontini",
- "Maltignano",
- "Mogliano",
- "Mondolfo",
- "Monte Giberto",
- "Monte San Pietrangeli",
- "Monte Vidon Corrado",
- "Montecarotto",
- "Montefiore dell'Aso",
- "Montegranaro",
- "Morro d'Alba",
- "Morrovalle",
- "Offida",
- "Osimo",
- "Ostra Vetere",
- "Palmiano",
- "Penna San Giovanni",
- "Pergola",
- "Piagge",
- "Pietrarubbia",
- "Poggio San Marcello",
- "Rapagnano",
- "Recanati",
- "Saltara",
- "San Benedetto del Tronto",
- "San Marcello",
- "Sant'Angelo in Pontano",
- "Sant'Elpidio a Mare",
- "Santa Vittoria in Matenano",
- "Sassocorvaro",
- "Sefro",
- "Serrungarina",
- "Tolentino",
- "Visso",
- )
- ),
-
- array(
- "regionName" => "Lazio",
- "regionShort" => "LAZ",
- "regionSlug" => "lazio",
- "weight" => 9,
- "cities" => array(
- "Acquafondata",
- "Allumiere",
- "Alvito",
- "Anzio",
- "Arsoli",
- "Artena",
- "Bassano in Teverina",
- "Bassiano",
- "Canino",
- "Cantalupo in Sabina",
- "Canterano",
- "Capena",
- "Casalvieri",
- "Casperia",
- "Castel di Tora",
- "Cisterna di Latina",
- "Filacciano",
- "Frascati",
- "Labico",
- "Labro",
- "Ladispoli",
- "Latera",
- "Mandela",
- "Mazzano Romano",
- "Minturno",
- "Monte San Giovanni in Sabina",
- "Moricone",
- "Morolo",
- "Morro Reatino",
- "Nemi",
- "Palestrina",
- "Pastena",
- "Picinisco",
- "Rocca Massima",
- "Rocca Santo Stefano",
- "Rocca d'Arce",
- "Roma",
- "Ronciglione",
- "San Lorenzo Nuovo",
- "Scandriglia",
- "Segni",
- "Sonnino",
- "Torre Cajetani",
- "Trevignano Romano",
- "Vallepietra",
- "Vejano",
- "Velletri",
- "Vico nel Lazio",
- "Villa Latina",
- "Villa Santo Stefano",
- )
- ),
-
- array(
- "regionName" => "Abruzzo",
- "regionShort" => "ABR",
- "regionSlug" => "abruzzo",
- "weight" => 2,
- "cities" => array(
- "Abbateggio",
- "Acciano",
- "Ancarano",
- "Bellante",
- "Campli",
- "Campotosto",
- "Capestrano",
- "Cappelle sul Tavo",
- "Caramanico Terme",
- "Carunchio",
- "Casoli",
- "Castellafiume",
- "Castelvecchio Calvisio",
- "Castiglione Messer Raimondo",
- "Castiglione a Casauria",
- "Cerchio",
- "Chieti",
- "Colledimacine",
- "Colonnella",
- "Crecchio",
- "Fallo",
- "Fontecchio",
- "Guilmi",
- "Isola del Gran Sasso d'Italia",
- "Manoppello",
- "Montebello sul Sangro",
- "Montereale",
- "Mosciano Sant'Angelo",
- "Nocciano",
- "Ofena",
- "Orsogna",
- "Paglieta",
- "Palombaro",
- "Pereto",
- "Pizzoferrato",
- "Pretoro",
- "Rocca di Cambio",
- "Roio del Sangro",
- "Rosciano",
- "San Giovanni Lipioni",
- "San Valentino in Abruzzo Citeriore",
- "San Vito Chietino",
- "Sant'Egidio alla Vibrata",
- "Sant'Eufemia a Maiella",
- "Sant'Eusanio Forconese",
- "Sant'Omero",
- "Serramonacesca",
- "Torrevecchia Teatina",
- "Treglio",
- "Vicoli",
- )
- ),
-
- array(
- "regionName" => "Molise",
- "regionShort" => "MOL",
- "regionSlug" => "molise",
- "weight" => 1,
- "cities" => array(
- "Baranello",
- "Belmonte del Sannio",
- "Bojano",
- "Bonefro",
- "Busso",
- "Campochiaro",
- "Campolieto",
- "Campomarino",
- "Carovilli",
- "Casacalenda",
- "Castel del Giudice",
- "Campobasso",
- "Cercemaggiore",
- "Cercepiccola",
- "Chiauci",
- "Civitacampomarano",
- "Colli a Volturno",
- "Conca Casale",
- "Ferrazzano",
- "Filignano",
- "Isernia",
- "Longano",
- "Macchia Valfortore",
- "Miranda",
- "Monacilioni",
- "Montefalcone nel Sannio",
- "Montenero Val Cocchiara",
- "Petacciato",
- "Pettoranello del Molise",
- "Pietracatella",
- "Ripabottoni",
- "Roccasicura",
- "Rotello",
- "Salcito",
- "San Giacomo degli Schiavoni",
- "San Giovanni in Galdo",
- "San Giuliano di Puglia",
- "San Martino in Pensilis",
- "San Massimo",
- "San Pietro Avellana",
- "Sant'Agapito",
- "Sant'Angelo Limosano",
- "Sant'Elia a Pianisi",
- "Sepino",
- "Sesto Campano",
- "Termoli",
- "Torella del Sannio",
- "Tufara",
- "Ururi",
- "Vastogirardi",
- )
- ),
-
- array(
- "regionName" => "Campania",
- "regionShort" => "CAM",
- "regionSlug" => "campania",
- "weight" => 10,
- "cities" => array(
- "Acerra",
- "Altavilla Irpina",
- "Arzano",
- "Avellino",
- "Calvi Risorta",
- "Campagna",
- "Cannalonga",
- "Casalbuono",
- "Castel Baronia",
- "Castel Volturno",
- "Castelvetere in Val Fortore",
- "Ceppaloni",
- "Cervinara",
- "Cervino",
- "Cetara",
- "Cimitile",
- "Corbara",
- "Cuccaro Vetere",
- "Ercolano",
- "Falciano del Massico",
- "Felitto",
- "Forio",
- "Frigento",
- "Frignano",
- "Giugliano in Campania",
- "Guardia Sanframondi",
- "Napoli",
- "Ospedaletto d'Alpinolo",
- "Paternopoli",
- "Paupisi",
- "Pellezzano",
- "Pietraroja",
- "Pollena Trocchia",
- "Portici",
- "Rocca San Felice",
- "Salerno",
- "San Felice a Cancello",
- "San Leucio del Sannio",
- "San Mauro Cilento",
- "San Pietro al Tanagro",
- "San Sebastiano al Vesuvio",
- "Sant'Angelo a Cupolo",
- "Sant'Angelo a Fasanella",
- "Sant'Arsenio",
- "Sant'Egidio del Monte Albino",
- "Santa Marina",
- "Santo Stefano del Sole",
- "Santomenna",
- "Sorbo Serpico",
- "Tufo",
- )
- ),
-
- array(
- "regionName" => "Puglia",
- "regionShort" => "PUG",
- "regionSlug" => "puglia",
- "weight" => 7,
- "cities" => array(
- "Accadia",
- "Altamura",
- "Arnesano",
- "Bari",
- "Brindisi",
- "Candela",
- "Cannole",
- "Carpignano Salentino",
- "Castelluccio Valmaggiore",
- "Castri di Lecce",
- "Cavallino",
- "Cerignola",
- "Copertino",
- "Erchie",
- "Francavilla Fontana",
- "Gagliano del Capo",
- "Giurdignano",
- "Grumo Appula",
- "Lecce",
- "Maglie",
- "Manfredonia",
- "Minervino di Lecce",
- "Molfetta",
- "Montemesola",
- "Mottola",
- "Noicattaro",
- "Novoli",
- "Oria",
- "Orsara di Puglia",
- "Otranto",
- "Palagianello",
- "Palmariggi",
- "Poggiorsini",
- "Porto Cesareo",
- "Putignano",
- "Rignano Garganico",
- "Rutigliano",
- "Salice Salentino",
- "San Cesario di Lecce",
- "San Pancrazio Salentino",
- "Santa Cesarea Terme",
- "Scorrano",
- "Sogliano Cavour",
- "Surbo",
- "Torchiarolo",
- "Trani",
- "Tuglie",
- "Vernole",
- "Vico del Gargano",
- "Vieste",
- )
- ),
-
- array(
- "regionName" => "Basilicata",
- "regionShort" => "BAS",
- "regionSlug" => "basilicata",
- "weight" => 1,
- "cities" => array(
- "Albano di Lucania",
- "Aliano",
- "Anzi",
- "Armento",
- "Balvano",
- "Bella",
- "Calvello",
- "Calvera",
- "Castelluccio Inferiore",
- "Castelluccio Superiore",
- "Castelmezzano",
- "Chiaromonte",
- "Colobraro",
- "Craco",
- "Episcopia",
- "Ferrandina",
- "Francavilla in Sinni",
- "Gallicchio",
- "Garaguso",
- "Genzano di Lucania",
- "Guardia Perticara",
- "Lagonegro",
- "Latronico",
- "Matera",
- "Moliterno",
- "Montemilone",
- "Nemoli",
- "Pescopagano",
- "Pietragalla",
- "Pomarico",
- "Potenza",
- "Rapone",
- "Rionero in Vulture",
- "Ripacandida",
- "Rivello",
- "Roccanova",
- "Ruoti",
- "Ruvo del Monte",
- "San Chirico Nuovo",
- "San Fele",
- "Satriano di Lucania",
- "Scanzano Jonico",
- "Spinoso",
- "Stigliano",
- "Tolve",
- "Tramutola",
- "Trivigno",
- "Vietri di Potenza",
- "Viggianello",
- "Viggiano",
- )
- ),
-
- array(
- "regionName" => "Calabria",
- "regionShort" => "CAL",
- "regionSlug" => "calabria",
- "weight" => 3,
- "cities" => array(
- "Aiello Calabro",
- "Aieta",
- "Benestare",
- "Bocchigliero",
- "Candidoni",
- "Catanzaro",
- "Cellara",
- "Cittanova",
- "Cropalati",
- "Davoli",
- "Delianuova",
- "Dipignano",
- "Filadelfia",
- "Fiumara",
- "Fossato Serralta",
- "Gagliato",
- "Girifalco",
- "Grimaldi",
- "Isca sullo Ionio",
- "Isola di Capo Rizzuto",
- "Laino Castello",
- "Marcedusa",
- "Marzi",
- "Mesoraca",
- "Montalto Uffugo",
- "Oppido Mamertina",
- "Papasidero",
- "Pedace",
- "Placanica",
- "Portigliola",
- "Rosarno",
- "San Calogero",
- "San Costantino Calabro",
- "San Demetrio Corone",
- "San Donato di Ninea",
- "San Giorgio Albanese",
- "San Lorenzo",
- "San Luca",
- "San Mauro Marchesato",
- "San Sostene",
- "Sant'Ilario dello Ionio",
- "Sant'Onofrio",
- "Scala Coeli",
- "Sellia Marina",
- "Serrata",
- "Squillace",
- "Stilo",
- "Strongoli",
- "Tarsia",
- "Tropea",
- )
- ),
-
- array(
- "regionName" => "Sicilia",
- "regionShort" => "SIC",
- "regionSlug" => "sicilia",
- "weight" => 8,
- "cities" => array(
- "Acireale",
- "Acquedolci",
- "Adrano",
- "Buccheri",
- "Caccamo",
- "Campofelice di Fitalia",
- "Cassaro",
- "Castelbuono",
- "Castellana Sicula",
- "Castiglione di Sicilia",
- "Cefalà Diana",
- "Cerami",
- "Chiusa Sclafani",
- "Cinisi",
- "Enna",
- "Francofonte",
- "Gallodoro",
- "Giardinello",
- "Giarratana",
- "Mascalucia",
- "Milazzo",
- "Milena",
- "Moio Alcantara",
- "Motta Camastra",
- "Motta Sant'Anastasia",
- "Naro",
- "Nicolosi",
- "Pace del Mela",
- "Palermo",
- "Pettineo",
- "Piana degli Albanesi",
- "Priolo Gargallo",
- "Randazzo",
- "Roccalumera",
- "Roccamena",
- "Rodì Milici",
- "Rosolini",
- "San Fratello",
- "San Giovanni la Punta",
- "San Piero Patti",
- "Santa Caterina Villarmosa",
- "Santa Flavia",
- "Santo Stefano Quisquina",
- "Sciacca",
- "Siculiana",
- "Siracusa",
- "Sperlinga",
- "Termini Imerese",
- "Valverde",
- "Villafranca Tirrena",
- )
- ),
-
- array(
- "regionName" => "Sardegna",
- "regionShort" => "SAR",
- "regionSlug" => "sardegna",
- "weight" => 3,
- "cities" => array(
- "Albagiara",
- "Armungia",
- "Birori",
- "Bosa",
- "Bottidda",
- "Bulzi",
- "Cabras",
- "Cagliari",
- "Cardedu",
- "Collinas",
- "Cuglieri",
- "Dorgali",
- "Esterzili",
- "Gonnosfanadiga",
- "Gonnosnò",
- "Ilbono",
- "Jerzu",
- "Lodine",
- "Maracalagonis",
- "Masullas",
- "Montresta",
- "Mores",
- "Narbolia",
- "Narcao",
- "Noragugume",
- "Nuragus",
- "Nurallao",
- "Nuraminis",
- "Ollolai",
- "Oristano",
- "Orosei",
- "Orroli",
- "Ortacesus",
- "Piscinas",
- "San Giovanni Suergiu",
- "Sanluri",
- "Santu Lussurgiu",
- "Sennariolo",
- "Siddi",
- "Silius",
- "Sorradile",
- "Stintino",
- "Tramatza",
- "Trinità d'Agultu e Vignola",
- "Tula",
- "Ussassai",
- "Viddalba",
- "Villa Verde",
- "Villamassargia",
- "Villanovafranca",
- )
- ),
- );
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
\ No newline at end of file
diff --git a/plugins/countries/Netherlands/Netherlands.class.php b/plugins/countries/Netherlands/Netherlands.class.php
deleted file mode 100644
index 2d287e1db..000000000
--- a/plugins/countries/Netherlands/Netherlands.class.php
+++ /dev/null
@@ -1,139 +0,0 @@
- "xxxxLL"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Drenthe",
- "regionShort" => "Dr",
- "regionSlug" => "drenthe",
- "weight" => "5",
- "cities" => array(
- "Assen", "Coevorden", "Emmen", "Hoogeveen", "Meppel"
- )
- ),
- array(
- "regionName" => "Flevoland",
- "regionShort" => "Fl",
- "regionSlug" => "flevoland",
- "weight" => "1",
- "cities" => array(
- "Almere", "Lelystad"
- )
- ),
- array(
- "regionName" => "Friesland",
- "regionShort" => "Fr",
- "regionSlug" => "friesland",
- "weight" => "6",
- "cities" => array(
- "Bolsward", "Dokkum", "Drachten", "Franeker", "Harlingen", "Heerenveen", "Hindeloopen", "IJlst",
- "Leeuwarden", "Sloten", "Sneek", "Stavoren", "Workum"
- )
- ),
- array(
- "regionName" => "Gelderland",
- "regionShort" => "Gl",
- "regionSlug" => "gelderland",
- "weight" => "20",
- "cities" => array(
- "Apeldoorn", "Arnhem", "Buren", "Culemborg", "Doetinchem", "Ede", "Groenlo", "Harderwijk", "Hattem",
- "Huissen", "Nijkerk", "Nijmegen", "Tiel", "Wageningen", "Winterswijk", "Zaltbommel", "Zutphen"
- )
- ),
- array(
- "regionName" => "Limburg",
- "regionShort" => "L.",
- "regionSlug" => "limburg",
- "weight" => "11",
- "cities" => array(
- "Geleen", "Heerlen", "Kerkrade", "Maastricht", "Roermond", "Sittard", "Thorn", "Valkenburg aan de Geul",
- "Venlo", "Weert", "Hasselt", "Sint-Lambrechts-Herk", "Wimmertingen", "Kermt", "Spalbeek", "Kuringen",
- "Stokrooie", "Stevoort", "Zonhoven", "Helchteren", "Houthalen", "Houthalen-Helchteren", "Berbroek", "Donk",
- "Herk-de-Stad", "Schulen", "Halen", "Loksbergen", "Zelem", "Heusden", "Heusden-Zolder", "Zolder", "Linkhout",
- "Lummen", "Meldert", "Alken", "Beringen", "Beverlo", "Koersel", "Paal", "Diepenbeek", "Genk", "Gellik",
- "Lanaken", "Neerharen", "Veldwezelt", "Rekem", "Eisden", "Leut", "Maasmechelen", "Mechelen-aan-de-Maas",
- "Meeswijk", "Opgrimbie", "Vucht", "Boorsem", "Uikhoven", "Kessenich", "Kinrooi", "Molenbeersel", "Ophoven",
- "Dilsen-Stokkem", "Elen", "Lanklaar", "Rotem", "Stokkem", "Opglabbeek", "As", "Niel-bij-As", "Ellikom",
- "Gruitrode", "Meeuwen", "Meeuwen-Gruitrode", "Neerglabbeek", "Wijshagen", "Maaseik", "Neeroeteren",
- "Opoeteren", "Zutendaal", "Berg", "Diets-Heur", "Haren", "Henis", "Kolmont", "Koninksem", "Lauw", "Mal",
- "Neerrepen", "Nerem", "Overrepen", "Piringen", "Riksingen", "Rutten", "s Herenelderen", "Sluizen", "Tongeren",
- "Vreren", "Widooie", "Herstappe", "Kortessem", "Vliermaalroot"
- )
- ),
- array(
- "regionName" => "Noord Brabant",
- "regionShort" => "N.",
- "regionSlug" => "noord_brabant",
- "weight" => "24",
- "cities" => array(
- "Bergen op Zoom", "Breda", "Eindhoven", "Geertruidenberg", "Grave", "Helmond", "Heusden", "Oosterhout",
- "Oss", "Ravenstein", "Roosendaal", "Tilburg", "Waalwijk"
- )
- ),
- array(
- "regionName" => "Noord Holland",
- "regionShort" => "N.",
- "regionSlug" => "noord_holland",
- "weight" => "26",
- "cities" => array(
- "Alkmaar", "Amstelveen", "Amsterdam", "Den Helder", "Edam", "Enkhuizen", "Haarlem", "Heerhugowaard", "Hilversum",
- "Hoofddorp", "Hoorn", "Laren", "Purmerend", "Medemblik", "Muiden", "Naarden", "Schagen", "Weesp", "Zaanstad"
- )
- ),
- array(
- "regionName" => "Overijssel",
- "regionShort" => "Ov",
- "regionSlug" => "overijssel",
- "weight" => "11",
- "cities" => array(
- "Almelo", "Deventer", "Enschede", "Hengelo", "Oldenzaal", "Zwolle"
- )
- ),
- array(
- "regionName" => "Zuid Holland",
- "regionShort" => "Z.",
- "regionSlug" => "zuid_holland",
- "weight" => "12",
- "cities" => array(
- "Alphen aan den Rijn", "Delft", "Dordrecht", "Gorinchem", "Gouda", "Leiden", "Rotterdam", "Spijkenisse",
- "The Hague", "Zoetermeer"
- )
- ),
- array(
- "regionName" => "Utrecht",
- "regionShort" => "U.",
- "regionSlug" => "utrecht",
- "weight" => "4",
- "cities" => array(
- "Amersfoort", "Leersum", "Nieuwegein", "Utrecht", "Veenendaal", "Woerden", "Zeist"
- )
- ),
- array(
- "regionName" => "Zeeland",
- "regionShort" => "Zl",
- "regionSlug" => "zeeland",
- "weight" => "35",
- "cities" => array(
- "Flushing", "Goes", "Hulst", "Middelburg", "Sluis", "Terneuzen", "Veere", "Zierikzee"
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/NewZealand/NewZealand.class.php b/plugins/countries/NewZealand/NewZealand.class.php
deleted file mode 100644
index 9c1e6d038..000000000
--- a/plugins/countries/NewZealand/NewZealand.class.php
+++ /dev/null
@@ -1,48 +0,0 @@
- "xxxx"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "North Island",
- "regionShort" => "NI",
- "regionSlug" => "north_island",
- "weight" => 3,
- "cities" => array(
- "Auckland", "Manukau", "North Shore", "Waitakere", "Wellington", "Hamilton", "Tauranga",
- "Lower Hutt", "Palmerston North", "Hastings", "Napier", "Rotorua", "New Plymouth", "Whangarei",
- "Porirua", "Wanganui", "Kapiti", "Upper Hutt", "Gisborne", "Pukekohe", "Taupo", "Masterton",
- "Levin", "Whakatane", "Cambridge", "Te Awamutu", "Feilding", "Tokoroa", "Hawera", "Waiuku",
- "Waiheke Island", "Te Puke", "Kawerau", "Huntly", "Thames", "Morrinsville", "Matamata", "Waitara",
- "Kerikeri", "Dannevirke"
- )
- ),
- array(
- "regionName" => "South Island",
- "regionShort" => "SI",
- "regionSlug" => "south_island",
- "weight" => 1,
- "cities" => array(
- "Christchurch", "Dunedin", "Nelson", "Invercargill", "Blenheim", "Timaru", "Ashburton",
- "Oamaru", "Rangiora", "Queenstown", "Greymouth", "Gore", "Motueka", "Wanaka", "Alexandra",
- "Picton", "Balclutha", "Temuka", "Westport"
- )
- )
- );
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/countries/Spain/Spain.class.php b/plugins/countries/Spain/Spain.class.php
deleted file mode 100644
index 9e7119a7a..000000000
--- a/plugins/countries/Spain/Spain.class.php
+++ /dev/null
@@ -1,257 +0,0 @@
-= 100.000 or province heads
-* Weight: percentage of population of the region to the total of Spain * 100
-* Source: https://es.wikipedia.org/wiki/Anexo%3AMunicipios_de_Espa%C3%B1a_por_poblaci%C3%B3n
-* @package Countries
-*/
-class Country_Spain extends CountryPlugin {
- protected $countryName = "Spain";
- protected $countrySlug = "spain";
- protected $regionNames = "Spanish Autonomies";
- protected $continent = "europe";
-
- protected $extendedData = array(
- "zipFormat" => "xxxxx"
- );
-
- protected $countryData = array(
- array(
- "regionName" => "Andalucía",
- "regionShort" => "AN",
- "regionSlug" => "andalucia",
- "weight" => 1788,
- "cities" => array(
- "Almería",
- "Cádiz",
- "Córdoba",
- "Granada",
- "Sevilla",
- "Huelva",
- "Jaén",
- "Málaga",
- "Jerez de la Frontera",
- "Marbella",
- "Dos Hermanas",
- "Algeciras"
- )
- ),
- array(
- "regionName" => "Aragón",
- "regionShort" => "AR",
- "regionSlug" => "aragon",
- "weight" => 286,
- "cities" => array(
- "Huesca",
- "Teruel",
- "Zaragoza"
- )
- ),
- array(
- "regionName" => "Principado de Asturias",
- "regionShort" => "AS",
- "regionSlug" => "asturias",
- "weight" => 228,
- "cities" => array(
- "Oviedo",
- "Gijón"
- )
- ),
- array(
- "regionName" => "Cantabria",
- "regionShort" => "CA",
- "regionSlug" => "cantabria",
- "weight" => 126,
- "cities" => array(
- "Santander"
- )
- ),
- array(
- "regionName" => "Castilla - La Mancha",
- "regionShort" => "CM",
- "regionSlug" => "clm",
- "weight" => 449,
- "cities" => array(
- "Ciudad Real",
- "Albacete",
- "Cuenca",
- "Toledo",
- "Guadalajara"
- )
- ),
- array(
- "regionName" => "Castilla y León",
- "regionShort" => "CL",
- "regionSlug" => "cle",
- "weight" => 539,
- "cities" => array(
- "Burgos",
- "León",
- "Palencia",
- "Valladolid",
- "Zamora",
- "Ávila",
- "Salamanca",
- "Segovia",
- "Soria"
- )
- ),
- array(
- "regionName" => "Catalunya",
- "regionShort" => "CA",
- "regionSlug" => "cataluña",
- "weight" => 1602,
- "cities" => array(
- "Barcelona",
- "Tarragona",
- "Girona",
- "Lleida",
- "L'Hospitalet de Llobregat",
- "Badalona",
- "Tarrasa",
- "Sabadell",
- "Mataró",
- "Santa Coloma de Gramenet",
- "Reus"
- )
- ),
- array(
- "regionName" => "Ceuta",
- "regionShort" => "CE",
- "regionSlug" => "ceuta",
- "weight" => 18,
- "cities" => array(
- "Ceuta"
- )
- ),
- array(
- "regionName" => "Comunitat Valenciana",
- "regionShort" => "CV",
- "regionSlug" => "valencia",
- "weight" => 1085,
- "cities" => array(
- "Castelló",
- "Valéncia",
- "Alacant",
- "Elx",
- "Torrevieja"
- )
- ),
- array(
- "regionName" => "Canarias",
- "regionShort" => "CN",
- "regionSlug" => "canarias",
- "weight" => 448,
- "cities" => array(
- "Santa Cruz de Tenerife",
- "Las Palmas",
- "San Cristóbal de la Laguna",
- "Telde"
- )
- ),
- array(
- "regionName" => "Illes Balears",
- "regionShort" => "BA",
- "regionSlug" => "baleares",
- "weight" => 237,
- "cities" => array(
- "Palma de Mallorca"
- )
- ),
- array(
- "regionName" => "Extremadura",
- "regionShort" => "EX",
- "regionSlug" => "extremadura",
- "weight" => 234,
- "cities" => array(
- "Badajoz",
- "Cáceres"
- )
- ),
- array(
- "regionName" => "Galicia",
- "regionShort" => "GA",
- "regionSlug" => "galicia",
- "weight" => 588,
- "cities" => array(
- "A Coruña",
- "Ourense",
- "Lugo",
- "Pontevedra",
- "Vigo"
- )
- ),
- array(
- "regionName" => "Madrid",
- "regionShort" => "MA",
- "regionSlug" => "madrid",
- "weight" => 1375,
- "cities" => array(
- "Madrid",
- "Móstoles",
- "Alcalá de Henares",
- "Fuenlabrada",
- "Leganés",
- "Getafe",
- "Alcorcón",
- "Torrejón de Ardoz",
- "Parla",
- "Alcobendas"
- )
- ),
- array(
- "regionName" => "Melilla",
- "regionShort" => "ME",
- "regionSlug" => "melilla",
- "weight" => 17,
- "cities" => array(
- "Melilla"
- )
- ),
- array(
- "regionName" => "Murcia",
- "regionShort" => "MU",
- "regionSlug" => "murcia",
- "weight" => 312,
- "cities" => array(
- "Murcia",
- "Cartagena"
- )
- ),
- array(
- "regionName" => "Navarra",
- "regionShort" => "NA",
- "regionSlug" => "navarra",
- "weight" => 136,
- "cities" => array(
- "Pamplona"
- )
- ),
- array(
- "regionName" => "Euskadi",
- "regionShort" => "PV",
- "regionSlug" => "paisvasco",
- "weight" => 464,
- "cities" => array(
- "Bilbo",
- "Donosti",
- "Gasteiz",
- "Baracaldo"
- )
- ),
- array(
- "regionName" => "La Rioja",
- "regionShort" => "LR",
- "regionSlug" => "larioja",
- "weight" => 68,
- "cities" => array(
- "Logroño"
- )
- )
- );
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
\ No newline at end of file
diff --git a/plugins/countries/UK/UK.class.php b/plugins/countries/UK/UK.class.php
deleted file mode 100644
index ad38e893d..000000000
--- a/plugins/countries/UK/UK.class.php
+++ /dev/null
@@ -1,770 +0,0 @@
- "Lx xLL|Lxx xLL|LxL xLL|LLx xLL|LLxx xLL|LLxL xLL",
- "phoneFormat" => array(
- "displayFormats" => array(
- "0xxxx xxxxxx"
- )
- )
- );
-
- protected $countryData = array(
- array(
- "regionName" => 'Aberdeenshire',
- "regionShort" => 'AB',
- "regionSlug" => 'aberdeenshire',
- "weight" => 1,
- "cities" => array(
- "Aberdeen", "Peterhead", "Fraserburgh", "Inverurie", "Huntley", "Ellon", "Turriff"
- )
- ),
- array(
- "regionName" => 'Anglesey',
- "regionShort" => 'AG',
- "regionSlug" => 'anglesey',
- "weight" => 1,
- "cities" => array(
- "Beaumaris", "Holyhead", "Llangefni", "Amlwch", "Menai Bridge"
- )
- ),
- array(
- "regionName" => 'Angus',
- "regionShort" => 'AN',
- "regionSlug" => 'angus',
- "weight" => 1,
- "cities" => array(
- "Forfar", "Dundee", "Arbroath", "Brechin", "Montrose", "Carnoustie", "Kirriemuir"
- )
- ),
- array(
- "regionName" => 'Argyllshire',
- "regionShort" => 'AR',
- "regionSlug" => 'argyllshire',
- "weight" => 1,
- "cities" => array(
- "Inveraray", "Oban", "Dunoon", "Campbeltown", "Lochgilphead", "Tobermory"
- )
- ),
- array(
- "regionName" => 'Ayrshire',
- "regionShort" => 'AY',
- "regionSlug" => 'ayrshire',
- "weight" => 1,
- "cities" => array(
- "Ayr", "Kilmarnock", "Irvine", "Saltcoats", "Kilwinning", "Largs", "Troon", "Cumnock"
- )
- ),
- array(
- "regionName" => 'Banffshire',
- "regionShort" => 'BA',
- "regionSlug" => 'banffshire',
- "weight" => 1,
- "cities" => array(
- "Banff", "Buckie", "Keith", "Macduff", "Portsoy", "Dufftown"
- )
- ),
- array(
- "regionName" => 'Bedfordshire',
- "regionShort" => 'BD',
- "regionSlug" => 'bedfordshire',
- "weight" => 1,
- "cities" => array(
- "Bedford", "Luton", "Dunstable", "Leighton Buzzard", "Biggleswade", "Sandy"
- )
- ),
- array(
- "regionName" => 'Berwickshire',
- "regionShort" => 'BE',
- "regionSlug" => 'berwickshire',
- "weight" => 1,
- "cities" => array(
- "Greenlaw", "Duns", "Eyemouth", "Lauder", "Coldstream"
- )
- ),
- array(
- "regionName" => 'Buckinghamshire',
- "regionShort" => 'BK',
- "regionSlug" => 'buckinghamshire',
- "weight" => 1,
- "cities" => array(
- "Aylesbury", "Milton Keynes", "Slough", "Buckingham", "High Wycombe"
- )
- ),
- array(
- "regionName" => 'Berkshire',
- "regionShort" => 'BR',
- "regionSlug" => 'berkshire',
- "weight" => 1,
- "cities" => array(
- "Reading", "Bracknell", "Maidenhead", "Newbury", "Windsor", "Wokingham", "Abingdon"
- )
- ),
- array(
- "regionName" => 'Caithness',
- "regionShort" => 'CA',
- "regionSlug" => 'caithness',
- "weight" => 1,
- "cities" => array(
- "Wick", "Thurso", "Halkirk", "Castletown"
- )
- ),
- array(
- "regionName" => 'Cambridgeshire',
- "regionShort" => 'CA',
- "regionSlug" => 'cambridgeshire',
- "weight" => 1,
- "cities" => array(
- "Cambridge", "Wisbech", "Ely", "March", "Whittlesey", "Chatteris", "Linton"
- )
- ),
- array(
- "regionName" => 'Cardiganshire',
- "regionShort" => 'CG',
- "regionSlug" => 'cardiganshire',
- "weight" => 1,
- "cities" => array(
- "Cardigan", "Aberystwyth", "Lampeter", "New Quay", "Tregaron"
- )
- ),
- array(
- "regionName" => 'Cheshire',
- "regionShort" => 'CH',
- "regionSlug" => 'cheshire',
- "weight" => 1,
- "cities" => array(
- "Chester", "Stockport", "Ellesmere Port", "Birkenhead", "Wallasey", "Runcorn", "Macclesfield", "Crewe"
- )
- ),
- array(
- "regionName" => 'Clackmannanshire',
- "regionShort" => 'CL',
- "regionSlug" => 'clackmannanshire',
- "weight" => 1,
- "cities" => array(
- "Clackmannan", "Alloa", "Tillicoultry", "Tullibody"
- )
- ),
- array(
- "regionName" => 'Carmarthenshire',
- "regionShort" => 'CM',
- "regionSlug" => 'carmarthenshire',
- "weight" => 1,
- "cities" => array(
- "Carmarthen", "Llanelli", "Ammanford", "Llandovery", "Kidwelly", "St. Clears"
- )
- ),
- array(
- "regionName" => 'Cornwall',
- "regionShort" => 'CO',
- "regionSlug" => 'cornwall',
- "weight" => 1,
- "cities" => array(
- "Bodmin", "Truro", "Camborne", "Redruth", "St. Austell", "Falmouth", "Penzance", "Newquay"
- )
- ),
- array(
- "regionName" => 'Cumberland',
- "regionShort" => 'CU',
- "regionSlug" => 'cumberland',
- "weight" => 1,
- "cities" => array(
- "Carlisle", "Whitehaven", "Workington", "Penrith", "Keswick", "Brampton"
- )
- ),
- array(
- "regionName" => 'Derbyshire',
- "regionShort" => 'DB',
- "regionSlug" => 'derbyshire',
- "weight" => 1,
- "cities" => array(
- "Derby", "Chesterfield", "Glossop", "Ilkeston", "Long Eaton", "Swadlincote", "Buxton", "Matlock", "Ashbourne"
- )
- ),
- array(
- "regionName" => 'Denbighshire',
- "regionShort" => 'DE',
- "regionSlug" => 'denbighshire',
- "weight" => 1,
- "cities" => array(
- "Denbigh", "Wrexham", "Ruthin", "Abergele", "Llangollen"
- )
- ),
- array(
- "regionName" => 'Devon',
- "regionShort" => 'DE',
- "regionSlug" => 'devon',
- "weight" => 1,
- "cities" => array(
- "Exeter", "Plymouth", "Torquay", "Paignton", "Barnstaple", "Tiverton", "Newton Abbot", "Tavistock"
- )
- ),
- array(
- "regionName" => 'Dunbartonshire',
- "regionShort" => 'DN',
- "regionSlug" => 'dunbartonshire',
- "weight" => 1,
- "cities" => array(
- "Dumbarton", "Clydebank", "Cumbernauld", "Helensburgh", "Alexandria", "Kirkintilloch"
- )
- ),
- array(
- "regionName" => 'Dorset',
- "regionShort" => 'DO',
- "regionSlug" => 'dorset',
- "weight" => 1,
- "cities" => array(
- "Dorchester", "Poole", "Weymouth", "Sherborne", "Wimborne Minster", "Shaftesbury"
- )
- ),
- array(
- "regionName" => 'East Lothian',
- "regionShort" => 'EL',
- "regionSlug" => 'east_lothian',
- "weight" => 1,
- "cities" => array(
- "Haddington", "North Berwick", "Dunbar", "Tranent", "East Linton"
- )
- ),
- array(
- "regionName" => 'Essex',
- "regionShort" => 'ES',
- "regionSlug" => 'essex',
- "weight" => 1,
- "cities" => array(
- "Chelmsford", "Basildon", "Romford", "Southend", "Colchester", "Harlow", "Brentwood", "West Ham"
- )
- ),
- array(
- "regionName" => 'Fife',
- "regionShort" => 'FI',
- "regionSlug" => 'fife',
- "weight" => 1,
- "cities" => array(
- "Cupar", "Dunfermline", "Glenrothes", "Kirkcaldy", "St. Andrews", "Cowdenbeath", "Burntisland"
- )
- ),
- array(
- "regionName" => 'Flintshire',
- "regionShort" => 'FL',
- "regionSlug" => 'flintshire',
- "weight" => 1,
- "cities" => array(
- "Mold", "Flint", "Rhyl", "Prestatyn", "Connah's Quay", "Holywell", "Buckley", "St. Asaph"
- )
- ),
- array(
- "regionName" => 'Glamorgan',
- "regionShort" => 'GL',
- "regionSlug" => 'glamorgan',
- "weight" => 1,
- "cities" => array(
- "Cardiff", "Swansea", "Merthyr Tydfil", "Barry", "Caerphilly", "Bridgend", "Neath", "Pontypridd"
- )
- ),
- array(
- "regionName" => 'Gloucestershire',
- "regionShort" => 'GL',
- "regionSlug" => 'gloucestershire',
- "weight" => 1,
- "cities" => array(
- "Gloucester", "Bristol", "Cheltenham", "Stroud", "Cirencester", "Tewkesbury"
- )
- ),
- array(
- "regionName" => 'Hampshire',
- "regionShort" => 'HA',
- "regionSlug" => 'hampshire',
- "weight" => 1,
- "cities" => array(
- "Winchester", "Southampton", "Portsmouth", "Bournemouth", "Basingstoke", "Newport"
- )
- ),
- array(
- "regionName" => 'Herefordshire',
- "regionShort" => 'HE',
- "regionSlug" => 'herefordshire',
- "weight" => 1,
- "cities" => array(
- "Hereford", "Ross-on-Wye", "Leominster", "Ledbury", "Bromyard", "Kington"
- )
- ),
- array(
- "regionName" => 'Hertfordshire',
- "regionShort" => 'HR',
- "regionSlug" => 'hertfordshire',
- "weight" => 1,
- "cities" => array(
- "Hertford", "Watford", "St. Albans", "Hemel Hempstead", "Stevenage", "Hatfield"
- )
- ),
- array(
- "regionName" => 'Huntingdonshire',
- "regionShort" => 'HU',
- "regionSlug" => 'huntingdonshire',
- "weight" => 1,
- "cities" => array(
- "Huntingdon", "St. Ives", "St. Neots", "Ramsey", "Yaxley"
- )
- ),
- array(
- "regionName" => 'Inverness-shire',
- "regionShort" => 'IN',
- "regionSlug" => 'inverness_shire',
- "weight" => 1,
- "cities" => array(
- "Inverness", "Fort William", "Kingussie", "Newtonmore", "Portree"
- )
- ),
- array(
- "regionName" => 'Kincardineshire',
- "regionShort" => 'KC',
- "regionSlug" => 'kincardineshire',
- "weight" => 1,
- "cities" => array(
- "Stonehaven", "Banchory", "Laurencekirk", "Inverbervie"
- )
- ),
- array(
- "regionName" => 'Kent',
- "regionShort" => 'KE',
- "regionSlug" => 'kent',
- "weight" => 1,
- "cities" => array(
- "Maidstone", "Canterbury", "Bromley", "Rochester", "Margate", "Folkestone", "Dover", "Greenwich"
- )
- ),
- array(
- "regionName" => 'Kirkcudbrightshire',
- "regionShort" => 'KK',
- "regionSlug" => 'kirkcudbrightshire',
- "weight" => 1,
- "cities" => array(
- "Kircudbright", "Castle Douglas", "Dalbeattie", "New Galloway"
- )
- ),
- array(
- "regionName" => 'Kinross-shire',
- "regionShort" => 'KR',
- "regionSlug" => 'kinross_shire',
- "weight" => 1,
- "cities" => array(
- "Kinross", "Milnathort"
- )
- ),
- array(
- "regionName" => 'Lancashire',
- "regionShort" => 'LA',
- "regionSlug" => 'lancashire',
- "weight" => 1,
- "cities" => array(
- "Lancaster", "Liverpool", "Manchester", "Preston", "Bolton", "Warrington", "Barrow-in-Furness"
- )
- ),
- array(
- "regionName" => 'Leicestershire',
- "regionShort" => 'LE',
- "regionSlug" => 'leicestershire',
- "weight" => 1,
- "cities" => array(
- "Leicester", "Loughborough", "Hinckley", "Melton Mowbray", "Coalville", "Lutterworth"
- )
- ),
- array(
- "regionName" => 'Lincolnshire',
- "regionShort" => 'LI',
- "regionSlug" => 'lincolnshire',
- "weight" => 1,
- "cities" => array(
- "Lincoln", "Grimsby", "Scunthorpe", "Boston", "Grantham", "Stamford", "Skegness", "Louth"
- )
- ),
- array(
- "regionName" => 'Lanarkshire',
- "regionShort" => 'LK',
- "regionSlug" => 'lanarkshire',
- "weight" => 1,
- "cities" => array(
- "Lanark", "Glasgow", "East Kilbride", "Hamilton", "Motherwell", "Coatbridge", "Carluke"
- )
- ),
- array(
- "regionName" => 'Merionethshire',
- "regionShort" => 'ME',
- "regionSlug" => 'merionethshire',
- "weight" => 1,
- "cities" => array(
- "Dolgellau", "Bala", "Tywyn", "Blaenau Ffestiniog", "Barmouth", "Harlech"
- )
- ),
- array(
- "regionName" => 'Montgomeryshire',
- "regionShort" => 'MG',
- "regionSlug" => 'montgomeryshire',
- "weight" => 1,
- "cities" => array(
- "Montgomery", "Newtown", "Welshpool", "Machynlleth", "Llanidloes"
- )
- ),
- array(
- "regionName" => 'Midlothian',
- "regionShort" => 'ML',
- "regionSlug" => 'midlothian',
- "weight" => 1,
- "cities" => array(
- "Edinburgh", "Musselburgh", "Penicuik", "Dalkeith", "Bonnyrigg"
- )
- ),
- array(
- "regionName" => 'Monmouthshire',
- "regionShort" => 'MO',
- "regionSlug" => 'monmouthshire',
- "weight" => 1,
- "cities" => array(
- "Monmouth", "Newport", "Blackwood", "Cwmbran", "Abergavenny", "Chepstow", "Tredegar"
- )
- ),
- array(
- "regionName" => 'Morayshire',
- "regionShort" => 'MO',
- "regionSlug" => 'morayshire',
- "weight" => 1,
- "cities" => array(
- "Elgin", "Forres", "Rothes", "Lossiemouth", "Fochabers"
- )
- ),
- array(
- "regionName" => 'Northumberland',
- "regionShort" => 'NB',
- "regionSlug" => 'northumberland',
- "weight" => 1,
- "cities" => array(
- "Alnwick", "Newcastle-upon-Tyne", "Morpeth", "Hexham", "Berwick-upon-Tweed"
- )
- ),
- array(
- "regionName" => 'Norfolk',
- "regionShort" => 'NF',
- "regionSlug" => 'norfolk',
- "weight" => 1,
- "cities" => array(
- "Norwich", "Great Yarmouth", "King's Lynn", "Dereham", "Cromer", "Hunstanton"
- )
- ),
- array(
- "regionName" => 'Northamptonshire',
- "regionShort" => 'NT',
- "regionSlug" => 'northamptonshire',
- "weight" => 1,
- "cities" => array(
- "Northampton", "Peterborough", "Corby", "Kettering", "Wellingborough"
- )
- ),
- array(
- "regionName" => 'Nottinghamshire',
- "regionShort" => 'NT',
- "regionSlug" => 'nottinghamshire',
- "weight" => 1,
- "cities" => array(
- "Nottingham", "Mansfield", "Worksop", "Newark", "Retford", "Southwell"
- )
- ),
- array(
- "regionName" => 'Orkney',
- "regionShort" => 'OK',
- "regionSlug" => 'orkney',
- "weight" => 1,
- "cities" => array(
- "Kirkwall", "Sromness", "Balfour"
- )
- ),
- array(
- "regionName" => 'Oxfordshire',
- "regionShort" => 'OX',
- "regionSlug" => 'oxfordshire',
- "weight" => 1,
- "cities" => array(
- "Oxford", "Banbury", "Witney", "Bicester", "Henley-on-Thames", "Carterton", "Thame", "Bloxham"
- )
- ),
- array(
- "regionName" => 'Pembrokeshire',
- "regionShort" => 'PE',
- "regionSlug" => 'pembrokeshire',
- "weight" => 1,
- "cities" => array(
- "Pembroke", "Milford Haven", "Haverfordwest", "Fishguard", "Tenby", "St. David's"
- )
- ),
- array(
- "regionName" => 'Radnorshire',
- "regionShort" => 'RA',
- "regionSlug" => 'radnorshire',
- "weight" => 1,
- "cities" => array(
- "Presteigne", "Llandrindod Wells", "Knighton", "Rhayader", "New Radnor"
- )
- ),
- array(
- "regionName" => 'Renfrewshire',
- "regionShort" => 'RF',
- "regionSlug" => 'renfrewshire',
- "weight" => 1,
- "cities" => array(
- "Renfrew", "Paisley", "Greenock", "Johnstone", "Port Glasgow", "Barrhead", "Kilmalcolm"
- )
- ),
- array(
- "regionName" => 'Roxburghshire',
- "regionShort" => 'RO',
- "regionSlug" => 'roxburghshire',
- "weight" => 1,
- "cities" => array(
- "Jedburgh", "Hawick", "Kelso", "Melrose", "Roxburgh"
- )
- ),
- array(
- "regionName" => 'Rutland',
- "regionShort" => 'RU',
- "regionSlug" => 'rutland',
- "weight" => 1,
- "cities" => array(
- "Oakham", "Uppingham. Cottesmore"
- )
- ),
- array(
- "regionName" => 'Shropshire',
- "regionShort" => 'SA',
- "regionSlug" => 'shropshire',
- "weight" => 1,
- "cities" => array(
- "Shrewsbury", "Telford", "Oswestry", "Bridgnorth", "Whitchurch", "Market Drayton", "Ludlow"
- )
- ),
- array(
- "regionName" => 'Selkirkshire',
- "regionShort" => 'SE',
- "regionSlug" => 'selkirkshire',
- "weight" => 1,
- "cities" => array(
- "Selkirk", "Clovenfords", "Galashiels"
- )
- ),
- array(
- "regionName" => 'Suffolk',
- "regionShort" => 'SF',
- "regionSlug" => 'suffolk',
- "weight" => 1,
- "cities" => array(
- "Ipswich", "Bury St. Edmunds", "Lowestoft", "Felixstowe", "Sudbury", "Haverhill", "Bungay"
- )
- ),
- array(
- "regionName" => 'Shetland',
- "regionShort" => 'SH',
- "regionSlug" => 'shetland',
- "weight" => 1,
- "cities" => array(
- "Lerwick", "Scalloway", "Baltasound"
- )
- ),
- array(
- "regionName" => 'Somerset',
- "regionShort" => 'SO',
- "regionSlug" => 'somerset',
- "weight" => 1,
- "cities" => array(
- "Taunton", "Bath", "Weston-super-Mare", "Yeovil", "Bridgwater", "Wells", "Glastonbury"
- )
- ),
- array(
- "regionName" => 'Surrey',
- "regionShort" => 'SR',
- "regionSlug" => 'surrey',
- "weight" => 1,
- "cities" => array(
- "Guildford", "Croydon", "Woking", "Sutton", "Kingston-on-Thames", "Wandsworth", "Wimbledon", "Brixton"
- )
- ),
- array(
- "regionName" => 'Sussex',
- "regionShort" => 'SS',
- "regionSlug" => 'sussex',
- "weight" => 1,
- "cities" => array(
- "Chichester", "Brighton", "Worthing", "Crawley", "Hastings", "Eastbourne", "Bognor Regis", "Horsham"
- )
- ),
- array(
- "regionName" => 'Stirlingshire',
- "regionShort" => 'ST',
- "regionSlug" => 'stirlingshire',
- "weight" => 1,
- "cities" => array(
- "Stirling", "Falkirk", "Grangemouth", "Kilsyth", "Bridge of Allan", "Denny", "Alva"
- )
- ),
- array(
- "regionName" => 'Staffordshire',
- "regionShort" => 'ST',
- "regionSlug" => 'staffordshire',
- "weight" => 1,
- "cities" => array(
- "Stafford", "Stoke-on-Trent", "Wolverhampton", "Walsall", "Cannock", "Lichfield"
- )
- ),
- array(
- "regionName" => 'Sutherland',
- "regionShort" => 'SU',
- "regionSlug" => 'sutherland',
- "weight" => 1,
- "cities" => array(
- "Dornoch", "Helmsdale", "Brora", "Golspie", "Lairg", "Durness", "Tongue"
- )
- ),
- array(
- "regionName" => 'Warwickshire',
- "regionShort" => 'WA',
- "regionSlug" => 'warwickshire',
- "weight" => 1,
- "cities" => array(
- "Warwick", "Birmingham", "Coventry", "Nuneaton", "Rugby", "Solihull", "Stratford-upon-Avon"
- )
- ),
- array(
- "regionName" => 'Westmorland',
- "regionShort" => 'WE',
- "regionSlug" => 'westmorland',
- "weight" => 1,
- "cities" => array(
- "Appleby", "Kendal", "Windermere", "Ambleside", "Kirkby Lonsdale"
- )
- ),
- array(
- "regionName" => 'Wigtownshire',
- "regionShort" => 'WI',
- "regionSlug" => 'wigtownshire',
- "weight" => 1,
- "cities" => array(
- "Wigtown", "Stranraer", "Newton Stewart", "Whithorn"
- )
- ),
- array(
- "regionName" => 'Wiltshire',
- "regionShort" => 'WI',
- "regionSlug" => 'wiltshire',
- "weight" => 1,
- "cities" => array(
- "Trowbridge", "Salisbury", "Swindon", "Chippenham", "Devizes", "Marlborough", "Warminster"
- )
- ),
- array(
- "regionName" => 'West Lothian',
- "regionShort" => 'WL',
- "regionSlug" => 'west_lothian',
- "weight" => 1,
- "cities" => array(
- "Linlithgow", "Livingston", "Bo'ness", "Broxburn", "Whitburn", "Armadale", "Bathgate"
- )
- ),
- array(
- "regionName" => 'Worcestershire',
- "regionShort" => 'WO',
- "regionSlug" => 'worcestershire',
- "weight" => 1,
- "cities" => array(
- "Worcester", "Dudley", "Kidderminster", "Stourbridge", "Halesowen", "Malvern", "Evesham"
- )
- ),
- array(
- "regionName" => 'Yorkshire',
- "regionShort" => 'YK',
- "regionSlug" => 'yorkshire',
- "weight" => 1,
- "cities" => array(
- "Northallerton", "Middlesbrough", "Scarborough", "Whitby", "Beverley", "Hull", "Bridlington",
- "Driffield", "Hornsea", "Filey", "Wakefield", "Leeds", "Sheffield", "Bradford", "Halifax",
- "Harrogate", "York"
- )
- ),
- array(
- "regionName" => 'Durham',
- "regionShort" => 'DU',
- "regionSlug" => 'durham',
- "weight" => 1,
- "cities" => array(
- "Durham", "Sunderland", "Stockton-on-Tees", "Darlington", "Hartlepool", "Gateshead", "Washington"
- )
- ),
- array(
- "regionName" => 'Brecknockshire',
- "regionShort" => 'BR',
- "regionSlug" => 'brecknockshire',
- "weight" => 1,
- "cities" => array(
- "Brecon", "Builth Wells", "Hay-on-Wye", "Talgarth", "Llanwrtwd Wells"
- )
- ),
- array(
- "regionName" => 'Buteshire',
- "regionShort" => 'BU',
- "regionSlug" => 'buteshire',
- "weight" => 1,
- "cities" => array(
- "Rothesay", "Millport", "Brodick", "Lochranza"
- )
- ),
- array(
- "regionName" => 'Dumfriesshire',
- "regionShort" => 'DF',
- "regionSlug" => 'dumfriesshire',
- "weight" => 1,
- "cities" => array(
- "Dumfries", "Annan", "Lockerbie", "Moffat", "Sanquhar", "Langholm", "Gretna"
- )
- ),
- array(
- "regionName" => 'Nairnshire',
- "regionShort" => 'NA',
- "regionSlug" => 'nairnshire',
- "weight" => 1,
- "cities" => array(
- "Nairn", "Auldearn", "Cawdor", "Ferness"
- )
- ),
- array(
- "regionName" => 'Perthshire',
- "regionShort" => 'PE',
- "regionSlug" => 'perthshire',
- "weight" => 1,
- "cities" => array(
- "Perth", "Crieff", "Pitlochry", "Callander", "Blairgowrie", "Rattray", "Coupar Angus", "Kincardine"
- )
- ),
- array(
- "regionName" => 'Ross-shire',
- "regionShort" => 'RO',
- "regionSlug" => 'ross_shire',
- "weight" => 1,
- "cities" => array(
- "Dingwall", "Stornaway", "Tain", "Alness", "Invergordon"
- )
- )
- );
-
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
-
diff --git a/plugins/countries/US/US.class.php b/plugins/countries/US/US.class.php
deleted file mode 100644
index 91bc86a9f..000000000
--- a/plugins/countries/US/US.class.php
+++ /dev/null
@@ -1,447 +0,0 @@
- array(
- "format" => "Xxxxx",
- "replacements" => array(
- "X" => "123456789",
- "x" => "0123456789"
- )
- ),
- "phoneFormat" => array(
- "displayFormats" => array(
- "(AAA) Xxx-xxxx",
- "1 (AAA) Xxx-xxxx",
- "1-AAA-Xxx-xxxx"
- )
- )
- );
-
- protected $countryData = array(
- array(
- "regionName" => 'Alabama',
- "regionShort" => 'AL',
- "regionSlug" => 'alabama',
- "weight" => 2,
- "cities" => array(
- "Birmingham", "Montgomery", "Mobile", "Huntsville", "Tuscaloosa", "Birmingham", "Montgomery", "Mobile", "Huntsville", "Tuscaloosa"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "ZYxxx",
- "replacements" => array(
- "Z" => "3",
- "Y" => "56",
- "x" => "0123456789"
- )
- )
- )
- ),
- array(
- "regionName" => 'Alaska',
- "regionShort" => 'AK',
- "regionSlug" => 'alaska',
- "weight" => 2,
- "cities" => array(
- "Anchorage", "Fairbanks", "Juneau", "College", "Anchorage", "Fairbanks", "Juneau", "College", "Ketchikan"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "ZZYxx",
- "replacements" => array(
- "Z" => "9",
- "Y" => "56789",
- "x" => "0123456789"
- )
- )
- )
- ),
- array(
- "regionName" => 'Arizona',
- "regionShort" => 'AZ',
- "regionSlug" => 'arizona',
- "weight" => 2,
- "cities" => array(
- "Phoenix", "Tucson", "Mesa", "Glendale", "Chandler"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "ZYxxx",
- "replacements" => array(
- "Z" => "8",
- "Y" => "56",
- "x" => "0123456789"
- )
- )
- )
- ),
- array(
- "regionName" => 'Arkansas',
- "regionShort" => 'AR',
- "regionSlug" => 'arkansas',
- "weight" => 2,
- "cities" => array(
- "Little Rock", "Fort Smith", "Fayetteville", "Springdale", "Jonesboro"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "format" => "ZYxxx",
- "replacements" => array(
- "Z" => "7",
- "Y" => "12",
- "x" => "0123456789"
- )
- )
- )
- ),
- array(
- "regionName" => 'California',
- "regionShort" => 'CA',
- "regionSlug" => 'california',
- "weight" => 2,
- "cities" => array(
- "Los Angeles", "San Diego", "San Jose", "San Francisco", "Fresno", "Sacramento"
- ),
- "extendedData" => array(
- "zipFormat" => array(
- "area" => "US-CA",
- "format" => "ZYxxx",
- "replacements" => array(
- "Z" => "9",
- "Y" => "0123456",
- "x" => "0123456789"
- )
- )
- )
- ),
- array(
- "regionName" => 'Colorado',
- "regionShort" => 'CO',
- "regionSlug" => 'colorado',
- "weight" => 2,
- "cities" => array(
- "Denver", "Colorado Springs", "Aurora", "Lakewood", "Fort Collins"
- )
- ),
- array(
- "regionName" => 'Connecticut',
- "regionShort" => 'CT',
- "regionSlug" => 'connecticut',
- "weight" => 2,
- "cities" => array(
- "Bridgeport", "New Haven", "Hartford", "Stamford", "Waterbury"
- )
- ),
- array(
- "regionName" => 'Delaware',
- "regionShort" => 'DE',
- "regionSlug" => 'delaware',
- "weight" => 2,
- "cities" => array(
- "Wilmington", "Dover", "Newark", "Pike Creek", "Bear"
- )
- ),
- array(
- "regionName" => 'Florida',
- "regionShort" => 'FL',
- "regionSlug" => 'florida',
- "weight" => 2,
- "cities" => array(
- "Jacksonville", "Miami", "Tampa", "St. Petersburg", "Orlando", "Tallahassee"
- )
- ),
- array(
- "regionName" => 'Georgia',
- "regionShort" => 'GA',
- "regionSlug" => 'georgia',
- "weight" => 2,
- "cities" => array(
- "Georgia", "Atlanta", "Augusta", "Columbus", "Savannah", "Athens"
- )
- ),
- array(
- "regionName" => 'Hawaii',
- "regionShort" => 'HI',
- "regionSlug" => 'hawaii',
- "weight" => 2,
- "cities" => array(
- "Honolulu", "Hilo", "Kailua", "Kaneohe", "Kapolei"
- )
- ),
- array(
- "regionName" => 'Idaho',
- "regionShort" => 'ID',
- "regionSlug" => 'idaho',
- "weight" => 2,
- "cities" => array(
- "Boise", "Nampa", "Meridian", "Pocatello", "Idaho Falls"
- )
- ),
- array(
- "regionName" => 'Illinois',
- "regionShort" => 'IL',
- "regionSlug" => 'illinois',
- "weight" => 2,
- "cities" => array(
- "Chicago", "Aurora", "Rockford", "Joliet", "Naperville", "Springfield"
- )
- ),
- array(
- "regionName" => 'Indiana',
- "regionShort" => 'IN',
- "regionSlug" => 'indiana',
- "weight" => 2,
- "cities" => array(
- "Indianapolis", "Fort Wayne", "Evansville", "South Bend", "Gary"
- )
- ),
- array(
- "regionName" => 'Iowa',
- "regionShort" => 'IA',
- "regionSlug" => 'iowa',
- "weight" => 2,
- "cities" => array(
- "Des Moines", "Cedar Rapids", "Davenport", "Sioux City", "Iowa City"
- )
- ),
- array(
- "regionName" => 'Kansas',
- "regionShort" => 'KS',
- "regionSlug" => 'kansas',
- "weight" => 2,
- "cities" => array(
- "Wichita", "Overland Park", "Kansas City", "Topeka", "Olathe"
- )
- ),
- array(
- "regionName" => 'Kentucky',
- "regionShort" => 'KY',
- "regionSlug" => 'kentucky',
- "weight" => 2,
- "cities" => array(
- "Louisville", "Lexington", "Owensboro", "Bowling Green", "Covington", "Frankfort"
- )
- ),
- array(
- "regionName" => 'Louisiana',
- "regionShort" => 'LA',
- "regionSlug" => 'louisiana',
- "weight" => 2,
- "cities" => array(
- "New Orleans", "Baton Rouge", "Shreveport", "Metairie", "Lafayette"
- )
- ),
- array(
- "regionName" => 'Maine',
- "regionShort" => 'ME',
- "regionSlug" => 'maine',
- "weight" => 2,
- "cities" => array(
- "Portland", "Lewiston", "Bangor", "South Portland", "Auburn", "Augusta"
- )
- ),
- array(
- "regionName" => 'Maryland',
- "regionShort" => 'MD',
- "regionSlug" => 'maryland',
- "weight" => 2,
- "cities" => array(
- "Baltimore", "Rockville", "Frederick", "Gaithersburg", "Columbia", "Annapolis"
- )
- ),
- array(
- "regionName" => 'Massachusetts',
- "regionShort" => 'MA',
- "regionSlug" => 'massachusetts',
- "weight" => 2,
- "cities" => array(
- "Boston", "Worcester", "Springfield", "Lowell", "Cambridge"
- )
- ),
- array(
- "regionName" => 'Michigan',
- "regionShort" => 'MI',
- "regionSlug" => 'michigan',
- "weight" => 2,
- "cities" => array(
- "Detroit", "Grand Rapids", "Warren", "Sterling Heights", "Flint", "Lansing"
- )
- ),
- array(
- "regionName" => 'Minnesota',
- "regionShort" => 'MN',
- "regionSlug" => 'minnesota',
- "weight" => 2,
- "cities" => array(
- "Minneapolis", "Saint Paul", "Rochester", "Duluth", "Bloomington"
- )
- ),
- array(
- "regionName" => 'Mississippi',
- "regionShort" => 'MS',
- "regionSlug" => 'mississippi',
- "weight" => 2,
- "cities" => array(
- "Jackson", "Gulfport", "Hattiesburg", "Biloxi", "Southaven"
- )
- ),
- array(
- "regionName" => 'Missouri',
- "regionShort" => 'MO',
- "regionSlug" => 'missouri',
- "weight" => 2,
- "cities" => array(
- "Kansas City", "Saint Louis", "Springfield", "Independence", "Columbia", "Jefferson City"
- )
- ),
- array(
- "regionName" => 'Montana',
- "regionShort" => 'MT',
- "regionSlug" => 'montana',
- "weight" => 2,
- "cities" => array(
- "Billings", "Missoula", "Great Falls", "Butte", "Bozeman", "Helena"
- )
- ),
- array(
- "regionName" => 'Nebraska',
- "regionShort" => 'NE',
- "regionSlug" => 'nebraska',
- "weight" => 2,
- "cities" => array(
- "Omaha", "Lincoln", "Bellevue", "Grand Island", "Kearney"
- )
- ),
- array(
- "regionName" => 'Nevada',
- "regionShort" => 'NV',
- "regionSlug" => 'nevada',
- "weight" => 2,
- "cities" => array(
- "Las Vegas", "Henderson", "North Las Vegas", "Reno", "Paradise", "Carson City"
- )
- ),
- array(
- "regionName" => 'Ohio',
- "regionShort" => 'OH',
- "regionSlug" => 'ohio',
- "weight" => 2,
- "cities" => array(
- "Columbus", "Cleveland", "Cincinnati", "Toledo", "Akron"
- )
- ),
- array(
- "regionName" => 'Oklahoma',
- "regionShort" => 'OK',
- "regionSlug" => 'oklahoma',
- "weight" => 2,
- "cities" => array(
- "Oklahoma City", "Tulsa", "Norman", "Lawton", "Broken Arrow"
- )
- ),
- array(
- "regionName" => 'Oregon',
- "regionShort" => 'OR',
- "regionSlug" => 'oregon',
- "weight" => 2,
- "cities" => array(
- "Portland", "Eugene", "Salem", "Gresham", "Hillsboro"
- )
- ),
- array(
- "regionName" => 'Pennsylvania',
- "regionShort" => 'PA',
- "regionSlug" => 'pennsylvania',
- "weight" => 2,
- "cities" => array(
- "Philadelphia", "Pittsburgh", "Allentown", "Erie", "Reading", "Harrisburg"
- )
- ),
- array(
- "regionName" => 'Tennessee',
- "regionShort" => 'TN',
- "regionSlug" => 'tennessee',
- "weight" => 2,
- "cities" => array(
- "Memphis", "Nashville", "Knoxville", "Chattanooga", "Clarksville"
- )
- ),
- array(
- "regionName" => 'Texas',
- "regionShort" => 'TX',
- "regionSlug" => 'texas',
- "weight" => 2,
- "cities" => array(
- "Houston", "San Antonio", "Dallas", "Austin", "Fort Worth"
- )
- ),
- array(
- "regionName" => 'Utah',
- "regionShort" => 'UT',
- "regionSlug" => 'utah',
- "weight" => 2,
- "cities" => array(
- "Salt Lake City", "West Valley City", "Provo", "West Jordan", "Sandy"
- )
- ),
- array(
- "regionName" => 'Vermont',
- "regionShort" => 'VT',
- "regionSlug" => 'vermont',
- "weight" => 2,
- "cities" => array(
- "Burlington", "Essex", "Rutland", "Colchester", "South Burlington", "Montpelier"
- )
- ),
- array(
- "regionName" => 'Virginia',
- "regionShort" => 'VA',
- "regionSlug" => 'virginia',
- "weight" => 2,
- "cities" => array(
- "Virginia Beach", "Norfolk", "Chesapeake", "Richmond", "Newport News"
- )
- ),
- array(
- "regionName" => 'Washington',
- "regionShort" => 'WA',
- "regionSlug" => 'washington',
- "weight" => 2,
- "cities" => array(
- "Seattle", "Spokane", "Tacoma", "Vancouver", "Bellevue", "Olympia"
- )
- ),
- array(
- "regionName" => 'Wisconsin',
- "regionShort" => 'WI',
- "regionSlug" => 'wisconsin',
- "weight" => 2,
- "cities" => array(
- "Milwaukee", "Madison", "Green Bay", "Kenosha", "Racine"
- )
- ),
- array(
- "regionName" => 'Wyoming',
- "regionShort" => 'WY',
- "regionSlug" => 'wyoming',
- "weight" => 2,
- "cities" => array(
- "Wyoming", "Cheyenne", "Casper", "Laramie", "Gillette", "Rock Springs"
- )
- )
- );
-
- public function install() {
- return CountryPluginHelper::populateDB($this->countryName, $this->countrySlug, $this->countryData);
- }
-}
diff --git a/plugins/dataTypes/AlphaNumeric/AlphaNumeric.class.php b/plugins/dataTypes/AlphaNumeric/AlphaNumeric.class.php
deleted file mode 100644
index 0ea560c7a..000000000
--- a/plugins/dataTypes/AlphaNumeric/AlphaNumeric.class.php
+++ /dev/null
@@ -1,120 +0,0 @@
-
- * @package DataTypes
- */
-class DataType_AlphaNumeric extends DataTypePlugin {
-
- /**#@+
- * @access protected
- */
- protected $isEnabled = true;
- protected $dataTypeName = "Alphanumeric";
- protected $dataTypeFieldGroup = "numeric";
- protected $dataTypeFieldGroupOrder = 10;
- protected $jsModules = array("AlphaNumeric.js");
-
-
- public function generate($generator, $generationContextData) {
- $formats = explode("|", $generationContextData["generationOptions"]);
- $chosenFormat = $formats[0];
- if (count($formats) > 1) {
- $chosenFormat = $formats[mt_rand(0, count($formats)-1)];
- }
- $val = Utils::generateRandomAlphanumericStr($chosenFormat);
- return array(
- "display" => $val
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- if (!isset($postdata["dtOption_$colNum"]) || empty($postdata["dtOption_$colNum"])) {
- return false;
- }
- return $postdata["dtOption_$colNum"];
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
- $html =<<< END
-
- {$L["please_select"]}
- V6M 4C1 {$this->L["example_CanPostalCode"]}
- 90210 {$this->L["example_USZipCode"]}
- eZg29gdF5K1 {$this->L["example_Password"]}
-
-END;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- return ' ';
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- $content =<<
- {$this->L["help_intro"]}
-
-
-
-
- L
- {$this->L["help_1"]}
- V
- {$this->L["help_2"]}
-
-
- l
- {$this->L["help_3"]}
- v
- {$this->L["help_4"]}
-
-
- D
- {$this->L["help_5"]}
- F
- {$this->L["help_6"]}
-
-
- C
- {$this->L["help_7"]}
- x
- {$this->L["help_8"]}
-
-
- c
- {$this->L["help_9"]}
- X
- {$this->L["help_10"]}
-
-
- E
- {$this->L["help_11"]}
- H
- {$this->L["help_12"]}
-
-
-EOF;
-
- return $content;
- }
-
-
- public function getRestOptionsFormat() {
- return array(
- "key" => "options",
- "required" => true,
- "type" => "mixed"
- );
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/AlphaNumeric/AlphaNumeric.js b/plugins/dataTypes/AlphaNumeric/AlphaNumeric.js
deleted file mode 100755
index a96ba0467..000000000
--- a/plugins/dataTypes/AlphaNumeric/AlphaNumeric.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name AlphaNumeric
- * @description JS code for the AlphaNumeric Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-AlphaNumeric";
- var LANG = L.dataTypePlugins.AlphaNumeric;
- var subscriptions = {};
-
- var _init = function() {
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _saveRow = function(rowNum) {
- return {
- "example": $("#dtExample_" + rowNum).val(),
- "option": $("#dtOption_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtOption_" + rowNum).val(data.option);
- },
- isComplete: function() { return $("#dtOption_" + rowNum).length > 0; }
- };
- };
-
- var _exampleChange = function(msg) {
- $("#dtOption_" + msg.rowID).val(msg.value);
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- saveRow: _saveRow,
- loadRow: _loadRow
- });
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/AlphaNumeric/lang/de.php b/plugins/dataTypes/AlphaNumeric/lang/de.php
deleted file mode 100644
index 5f6f85248..000000000
--- a/plugins/dataTypes/AlphaNumeric/lang/de.php
+++ /dev/null
@@ -1,23 +0,0 @@
-L etter.";
-$L["help_10"] = "Jede Anzahl, 1-9.";
-$L["help_11"] = "Ein Konsonant (obere oder untere).";
-$L["help_12"] = "Ein H exidecimal Nummer (0-F)";
-$L["help_2"] = "Ein Groß-V owel.";
-$L["help_3"] = "Ein Kleinbuchstaben l etter.";
-$L["help_4"] = "Ein Kleinbuchstaben v owel.";
-$L["help_5"] = "Ein Brief (oben oder unten).";
-$L["help_6"] = "Ein Vokal (obere oder untere).";
-$L["help_7"] = "Ein Groß-C onsonant.";
-$L["help_8"] = "Jede Zahl, 0-9.";
-$L["help_9"] = "Ein Kleinbuchstaben c onsonant.";
-$L["help_intro"] = "Dieser Datentyp können Sie zufällige alphanumerische Zeichenfolgen. Die folgende Tabelle enthält die Zeichenlegende für dieses Feld. Alle anderen Zeichen, die Sie in diesem Feld erscheint unescaped.";
-$L["incomplete_fields"] = "Alphanumerische Felder müssen Sie das Format, in dem Optionen Textfeld eingeben. Bitte beheben Sie die folgenden Zeilen:";
-
diff --git a/plugins/dataTypes/AlphaNumeric/lang/en.php b/plugins/dataTypes/AlphaNumeric/lang/en.php
deleted file mode 100644
index 4175be185..000000000
--- a/plugins/dataTypes/AlphaNumeric/lang/en.php
+++ /dev/null
@@ -1,22 +0,0 @@
-Letter.";
-$L["help_10"] = "Any number, 1-9.";
-$L["help_11"] = "A consonant (upper or lower).";
-$L["help_12"] = "An H exidecimal number (0-F)";
-$L["help_2"] = "An uppercase V owel.";
-$L["help_3"] = "A lowercase l etter.";
-$L["help_4"] = "A lowercase v owel.";
-$L["help_5"] = "A letter (upper or lower).";
-$L["help_6"] = "A vowel (upper or lower).";
-$L["help_7"] = "An uppercase C onsonant.";
-$L["help_8"] = "Any number, 0-9.";
-$L["help_9"] = "A lowercase c onsonant.";
-$L["help_intro"] = "This Data Type lets you generate random alpha-numeric strings. The following table contains the character legend for this field. Any other characters you enter into this field will appear unescaped.";
-$L["incomplete_fields"] = "Alphanumeric fields require you to enter the format in the Options text field. Please fix the following rows:";
diff --git a/plugins/dataTypes/AlphaNumeric/lang/es.php b/plugins/dataTypes/AlphaNumeric/lang/es.php
deleted file mode 100644
index f68337e85..000000000
--- a/plugins/dataTypes/AlphaNumeric/lang/es.php
+++ /dev/null
@@ -1,23 +0,0 @@
-Letra mayúscula.";
-$L["help_10"] = "Cualquier número, 1-9.";
-$L["help_11"] = "Una constante (mayúscula o minúscula).";
-$L["help_12"] = "Un número H exadecimal (0-F)";
-$L["help_2"] = "Una V ocal mayúscula.";
-$L["help_3"] = "Una l etra minúscula.";
-$L["help_4"] = "Una v ocal minúscula.";
-$L["help_5"] = "Una letra (mayúscula o minúscula).";
-$L["help_6"] = "Una vocal (mayúscula o minúscula).";
-$L["help_7"] = "Una C onstante mayúscula.";
-$L["help_8"] = "Cualquier número, 0-9.";
-$L["help_9"] = "Una c onstante minúscula.";
-$L["help_intro"] = "Estos tipos de datos te permiten generar cadenas alfanuméricas aleatorias. La siguiente tabla contiene la leyenda para este campo. Cualquier otro carácter que introduzcas en este campo aparecerá sin escapar.";
-$L["incomplete_fields"] = "Los campos alfanuméricos requieren que se introduzca el formato en el campo de texto Opciones. Por favor, corrija las siguientes filas:";
diff --git a/plugins/dataTypes/AlphaNumeric/lang/fr.php b/plugins/dataTypes/AlphaNumeric/lang/fr.php
deleted file mode 100644
index f64443b34..000000000
--- a/plugins/dataTypes/AlphaNumeric/lang/fr.php
+++ /dev/null
@@ -1,22 +0,0 @@
-Lettre majuscule.";
-$L["help_10"] = "N'importe quel nombre, 1-9.";
-$L["help_11"] = "Une consonne (majuscule ou minuscule).";
-$L["help_12"] = "Un nombre H exadécimal (0-F)";
-$L["help_2"] = "Une V oyelle majuscule.";
-$L["help_3"] = "Une l ettre minuscule.";
-$L["help_4"] = "Une v oyelle minuscule.";
-$L["help_5"] = "Une lettre (majuscule ou minuscule).";
-$L["help_6"] = "Une voyelle (majuscule ou minuscule).";
-$L["help_7"] = "Une C onsonne majuscule.";
-$L["help_8"] = "N'importe quel nombre, 0-9.";
-$L["help_9"] = "Une c onsonne minuscule.";
-$L["help_intro"] = "Ce type de données permet de générer au hasard des chaînes alphanumériques. Le tableau suivant contient la légende de caractères pour ce champ. Tous les autres caractères que vous entrez dans ce champ apparaîtront non-échappés.";
-$L["incomplete_fields"] = "Les champs alphanumériques nécessitent la saisie du format dans le champ de texte Options. Corrigez les lignes suivantes:";
diff --git a/plugins/dataTypes/AlphaNumeric/lang/nl.php b/plugins/dataTypes/AlphaNumeric/lang/nl.php
deleted file mode 100644
index eb96037df..000000000
--- a/plugins/dataTypes/AlphaNumeric/lang/nl.php
+++ /dev/null
@@ -1,22 +0,0 @@
-Letter.";
-$L["help_10"] = "Elk aantal, 1-9.";
-$L["help_11"] = "Een medeklinker (boven of onder).";
-$L["help_12"] = "Een H exidecimal nummer (0-F)";
-$L["help_2"] = "Een hoofdletter V Owel.";
-$L["help_3"] = "Een kleine letter l etter.";
-$L["help_4"] = "Een kleine v owel.";
-$L["help_5"] = "Een brief (boven of onder).";
-$L["help_6"] = "Een klinker (boven of onder).";
-$L["help_7"] = "Een hoofdletter C onsonant.";
-$L["help_8"] = "Elk nummer, 0-9.";
-$L["help_9"] = "Een kleine c onsonant.";
-$L["help_intro"] = "Dit gegevenstype kunt u het genereren van willekeurige alfanumerieke strings. De volgende tabel bevat het karakter legende voor dit veld. Alle andere tekens die u invoert in dit veld zal verschijnen beschermd en onbeschermd weergegeven.";
-$L["incomplete_fields"] = "Alfanumerieke velden moet u het formaat in het Opties tekstveld in te voeren. Please fix de volgende rijen:";
diff --git a/plugins/dataTypes/AutoIncrement/AutoIncrement.class.php b/plugins/dataTypes/AutoIncrement/AutoIncrement.class.php
deleted file mode 100644
index 9f58d9ff5..000000000
--- a/plugins/dataTypes/AutoIncrement/AutoIncrement.class.php
+++ /dev/null
@@ -1,103 +0,0 @@
- $val
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $col, $num_cols) {
- $start = isset($postdata["dtAutoIncrementStart_$col"]) ? $postdata["dtAutoIncrementStart_$col"] : null;
- $end = isset($postdata["dtAutoIncrementValue_$col"]) ? $postdata["dtAutoIncrementValue_$col"] : null;
-
- if ($start == null || $end == null || $start == "") {
- return false;
- }
-
- $options = array(
- "start" => $postdata["dtAutoIncrementStart_$col"],
- "increment" => $postdata["dtAutoIncrementValue_$col"],
- "placeholder" => $postdata["dtAutoIncrementPlaceholder_$col"]
- );
-
- return $options;
- }
-
- public function getExampleColumnHTML() {
- $html =<<< END
-
- 1, 2, 3, 4, 5, 6...
- 100, 101, 102, 103, 104...
- 0, 2, 4, 6, 8, 10...
- 0, 5, 10, 15, 20, 25...
- 1000, 999, 998, 997...
- 0, -1, -2, -3, -4...
- 0, 0.5, 1, 1.5, 2...
- ROW-1, ROW-2, ROW-3,...
- 2i, 4i, 6i, 8i...
-
-END;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- $html =<<< END
- {$this->L["start_at_c"]}
- {$this->L["increment_c"]}
- {$this->L["placeholder_str"]}
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "type" => "numeric",
- "SQLField" => "mediumint",
- "SQLField_Oracle" => "number default NULL",
- "SQLField_MSSQL" => "INTEGER NULL",
- "SQLField_Postgres" => "integer NULL"
- );
- }
-
- public function getHelpHTML() {
- $content =<<< END
-
- {$this->L["help_intro"]}
-
-
- {$this->L["help_para2"]}
-
-
- ROW-{\$INCR} -> ROW-1, ROW-2, ROW-3, ROW-4, ...
- {\$INCR}F -> 1F, 2F, 3F, 4F, ...
-
-END;
-
- return $content;
- }
-}
diff --git a/plugins/dataTypes/AutoIncrement/AutoIncrement.js b/plugins/dataTypes/AutoIncrement/AutoIncrement.js
deleted file mode 100755
index 1c49ed540..000000000
--- a/plugins/dataTypes/AutoIncrement/AutoIncrement.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name AutoIncrement
- * @description JS code for the AutoIncrement Data Type.
- * @see DataType
- * @namespace
- */
-
- /* @private */
- var MODULE_ID = "data-type-AutoIncrement";
-
- var LANG = L.dataTypePlugins.AutoIncrement;
- var subscriptions = {};
-
- var _init = function() {
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _saveRow = function(rowNum) {
- return {
- example: $("#dtExample_" + rowNum).val(),
- incrementStart: $("#dtAutoIncrementStart_" + rowNum).val(),
- incrementValue: $("#dtAutoIncrementValue_" + rowNum).val(),
- incrementPlaceholder: $("#dtAutoIncrementPlaceholder_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() { },
- isComplete: function() {
- if ($("#dtAutoIncrementPlaceholder_" + rowNum).length) {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtAutoIncrementStart_" + rowNum).val(data.incrementStart);
- $("#dtAutoIncrementValue_" + rowNum).val(data.incrementValue);
- $("#dtAutoIncrementPlaceholder_" + rowNum).val(data.incrementPlaceholder);
- return true;
- }
- return false;
- }
- };
- };
-
- var _exampleChange = function(msg) {
- var parts = msg.value.split(',');
- var rowNum = msg.rowID;
- $("#dtAutoIncrementStart_" + rowNum).val(parts[0]);
- $("#dtAutoIncrementValue_" + rowNum).val(parts[1]);
- $("#dtAutoIncrementPlaceholder_" + rowNum).val(parts[2]);
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-});
diff --git a/plugins/dataTypes/AutoIncrement/lang/de.php b/plugins/dataTypes/AutoIncrement/lang/de.php
deleted file mode 100644
index ebdd66866..000000000
--- a/plugins/dataTypes/AutoIncrement/lang/de.php
+++ /dev/null
@@ -1,12 +0,0 @@
-{\$ROW} Platzhalter. Zum Beispiel:";
-$L["incomplete_fields"] = "Bitte geben Sie die Start At und Increment Felder für alle Auto-increment Zeilen:";
-$L["increment_c"] = "Schrittweite:";
-$L["name"] = "Auto-Inkrement";
-$L["placeholder_str"] = "Platzhalter-String:";
-$L["start_at_c"] = "Start bei:";
diff --git a/plugins/dataTypes/AutoIncrement/lang/en.php b/plugins/dataTypes/AutoIncrement/lang/en.php
deleted file mode 100644
index 9a21555bc..000000000
--- a/plugins/dataTypes/AutoIncrement/lang/en.php
+++ /dev/null
@@ -1,11 +0,0 @@
-{\$INCR} placeholder. For example:";
-$L["increment_c"] = "Increment:";
-$L["name"] = "Auto-increment";
-$L["placeholder_str"] = "Placeholder string:";
-$L["start_at_c"] = "Start at:";
-$L["incomplete_fields"] = "Please enter the Start At and Increment fields for all Auto-increment rows:";
diff --git a/plugins/dataTypes/AutoIncrement/lang/es.php b/plugins/dataTypes/AutoIncrement/lang/es.php
deleted file mode 100644
index 8a037fb4d..000000000
--- a/plugins/dataTypes/AutoIncrement/lang/es.php
+++ /dev/null
@@ -1,12 +0,0 @@
-{\$INCR}. Por ejemplo:";
-$L["increment_c"] = "Incremento:";
-$L["name"] = "Auto-incremento";
-$L["placeholder_str"] = "Patrón de sustitución:";
-$L["start_at_c"] = "Comenzar en:";
-$L["incomplete_fields"] = "Por favor, introduce los campos "Empezar en" e "Incremento" para todas las filas auto-incrementadas:";
diff --git a/plugins/dataTypes/AutoIncrement/lang/fr.php b/plugins/dataTypes/AutoIncrement/lang/fr.php
deleted file mode 100644
index 409f6e5e6..000000000
--- a/plugins/dataTypes/AutoIncrement/lang/fr.php
+++ /dev/null
@@ -1,12 +0,0 @@
-{\$INCR}. Par exemple:";
-$L["incomplete_fields"] = "Renseignez l'option 'Début' des champs d'incrémentation pour toutes les lignes concernées:";
-$L["increment_c"] = "Pas:";
-$L["name"] = "Auto-incrémentation";
-$L["placeholder_str"] = "Chaîne complémentaire:";
-$L["start_at_c"] = "Début:";
diff --git a/plugins/dataTypes/AutoIncrement/lang/nl.php b/plugins/dataTypes/AutoIncrement/lang/nl.php
deleted file mode 100644
index 85799284b..000000000
--- a/plugins/dataTypes/AutoIncrement/lang/nl.php
+++ /dev/null
@@ -1,11 +0,0 @@
-{\$INCR} tijdelijke aanduiding. Bijvoorbeeld:";
-$L["incomplete_fields"] = "Vul de Start At en Increment velden voor alle Auto-increment rijen:";
-$L["increment_c"] = "Increment:";
-$L["name"] = "Auto-increment";
-$L["placeholder_str"] = "Placeholder string:";
-$L["start_at_c"] = "Start bij:";
\ No newline at end of file
diff --git a/plugins/dataTypes/CVV/CVV.class.php b/plugins/dataTypes/CVV/CVV.class.php
deleted file mode 100644
index d7e363cb0..000000000
--- a/plugins/dataTypes/CVV/CVV.class.php
+++ /dev/null
@@ -1,32 +0,0 @@
-, origin code Zeeshan Shaikh
- * @package DataTypes
- */
-class DataType_CVV extends DataTypePlugin {
- protected $isEnabled = true;
- protected $dataTypeName = "CVV";
- protected $hasHelpDialog = true;
- protected $dataTypeFieldGroup = "credit_card_data";
- protected $dataTypeFieldGroupOrder = 30;
-
-
- public function generate($generator, $generationContextData) {
- return array(
- "display" => rand(111, 999)
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/CVV/lang/de.php b/plugins/dataTypes/CVV/lang/de.php
deleted file mode 100644
index 135209bff..000000000
--- a/plugins/dataTypes/CVV/lang/de.php
+++ /dev/null
@@ -1,4 +0,0 @@
-111 bis 999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/CVV/lang/en.php b/plugins/dataTypes/CVV/lang/en.php
deleted file mode 100644
index a77817dc4..000000000
--- a/plugins/dataTypes/CVV/lang/en.php
+++ /dev/null
@@ -1,4 +0,0 @@
-111 to 999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/CVV/lang/es.php b/plugins/dataTypes/CVV/lang/es.php
deleted file mode 100644
index c2f89b3b1..000000000
--- a/plugins/dataTypes/CVV/lang/es.php
+++ /dev/null
@@ -1,4 +0,0 @@
-111-999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/CVV/lang/fr.php b/plugins/dataTypes/CVV/lang/fr.php
deleted file mode 100644
index 1d1860a7d..000000000
--- a/plugins/dataTypes/CVV/lang/fr.php
+++ /dev/null
@@ -1,4 +0,0 @@
-111 à 999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/CVV/lang/nl.php b/plugins/dataTypes/CVV/lang/nl.php
deleted file mode 100644
index ac9058eaa..000000000
--- a/plugins/dataTypes/CVV/lang/nl.php
+++ /dev/null
@@ -1,4 +0,0 @@
-111-999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/City/City.class.php b/plugins/dataTypes/City/City.class.php
deleted file mode 100644
index d59b665f6..000000000
--- a/plugins/dataTypes/City/City.class.php
+++ /dev/null
@@ -1,139 +0,0 @@
-countryRegions = Core::$geoData->getCountryRegionHash();
- }
- }
-
-
- public function generate($generator, $generationContextData) {
- // see if this row has a region [N.B. This is something that could be calculated ONCE on the first row]
- $rowRegionInfo = array();
- while (list($key, $info) = each($generationContextData["existingRowData"])) {
- if ($info["dataTypeFolder"] == "Region") {
- $rowRegionInfo = $info;
- break;
- }
- }
- reset($generationContextData["existingRowData"]);
-
- // if there was no region specified, see if this row has a country [N.B. This is also
- // something that could be calculated ONCE on the first row]
- $rowCountryInfo = array();
- if (empty($rowRegionInfo)) {
- while (list($key, $info) = each($generationContextData["existingRowData"])) {
- if ($info["dataTypeFolder"] == "Country") {
- $rowCountryInfo = $info;
- break;
- }
- }
- }
-
- $randomCity = "";
- if (!empty($rowRegionInfo)) {
- $countrySlug = $rowRegionInfo["randomData"]["country_slug"];
- $regionSlug = $rowRegionInfo["randomData"]["region_slug"];
-
- // now pick the random city in the country-region specified
- // TODO bug here (when user unselected particular row regions)
- $regionData = $this->citiesByCountryRegion[$countrySlug]["regions"][$regionSlug];
- $numCitiesInRegion = $this->citiesByCountryRegion[$countrySlug]["regions"][$regionSlug]["numCities"];
- $citiesInRegion = $this->citiesByCountryRegion[$countrySlug]["regions"][$regionSlug]["cities"];
- $randomCity = $citiesInRegion[mt_rand(0, $numCitiesInRegion-1)]["city"];
- } else if (!empty($rowCountryInfo)) {
- // pick a random region in this country. If the existing row data had a slug, it means there's
- // a country-plugin for that country, so we can intelligently load a City
- if (isset($rowCountryInfo["randomData"]["slug"])) {
- $countrySlug = $rowCountryInfo["randomData"]["slug"];
- $countryRegions = $this->citiesByCountryRegion[$countrySlug]["regions"];
- $key = array_rand($countryRegions);
- $cities = $countryRegions[$key]["cities"];
- $numCities = $countryRegions[$key]["numCities"];
- $randomCity = $cities[mt_rand(0, $numCities-1)]["city"];
- } else {
- $randomCity = $this->cities[mt_rand(0, $this->numCities-1)];
- }
- } else {
- $randomCity = $this->cities[mt_rand(0, $this->numCities-1)];
- }
-
- return array(
- "display" => $randomCity
- );
- }
-
-
- /**
- * Called when the plugin is initialized during data generation. Initializes the
- * $citiesByCountryRegion private var.
- */
- private function initCityList() {
- $prefix = Core::getDbTablePrefix();
- $response = Core::$db->query("
- SELECT *
- FROM {$prefix}cities
- ");
-
- if (!$response["success"]) {
- return;
- }
-
- $cities = array();
- $citiesByCountryRegion = array();
- while ($cityInfo = mysqli_fetch_assoc($response["results"])) {
- $currCountrySlug = $cityInfo["country_slug"];
- $currRegionSlug = $cityInfo["region_slug"];
- if (!array_key_exists($currCountrySlug, $citiesByCountryRegion)) {
- $citiesByCountryRegion[$currCountrySlug] = array(
- "numRegions" => 0,
- "regions" => array()
- );
- }
- if (!array_key_exists($currRegionSlug, $citiesByCountryRegion[$currCountrySlug]["regions"])) {
- $citiesByCountryRegion[$currCountrySlug]["regions"][$currRegionSlug] = array(
- "numCities" => 0,
- "cities" => array()
- );
- $citiesByCountryRegion[$currCountrySlug]["numRegions"]++;
- }
- $citiesByCountryRegion[$currCountrySlug]["regions"][$currRegionSlug]["cities"][] = array(
- "city" => $cityInfo["city"],
- "city_id" => $cityInfo["city_id"]
- );
- $citiesByCountryRegion[$currCountrySlug]["regions"][$currRegionSlug]["numCities"]++;
- $cities[] = $cityInfo["city"];
- }
-
- // now we've put together all the info, add in "numRegions" and "numCities" hash keys
- // at the appropriate spots in the data structure. This'll help speed up the data generation
- $this->citiesByCountryRegion = $citiesByCountryRegion;
- $this->cities = $cities;
- $this->numCities = count($cities);
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-}
diff --git a/plugins/dataTypes/City/lang/de.php b/plugins/dataTypes/City/lang/de.php
deleted file mode 100644
index 1f4e7dc9e..000000000
--- a/plugins/dataTypes/City/lang/de.php
+++ /dev/null
@@ -1,4 +0,0 @@
-words = Utils::getLipsum();
- $this->numWords = count($this->words);
- $this->numCompanyTypes = count($this->companyTypes);
- }
- }
-
- public function generate($generator, $generationContextData) {
- $numCompanyNameWords = mt_rand(1, 3);
- $offset = mt_rand(0, $this->numWords - ($numCompanyNameWords + 1));
- $words = array_slice($this->words, $offset, $numCompanyNameWords);
- $words = preg_replace("/[,.:]/", "", $words);
- $companyType = $this->companyTypes[mt_rand(0, $this->numCompanyTypes-1)];
-
- return array(
- "display" => ucwords(implode(" ", $words) . " " . $companyType)
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-}
diff --git a/plugins/dataTypes/Company/lang/de.php b/plugins/dataTypes/Company/lang/de.php
deleted file mode 100644
index 15c5cd5f7..000000000
--- a/plugins/dataTypes/Company/lang/de.php
+++ /dev/null
@@ -1,6 +0,0 @@
-Dolor Inc., oder Convallis Begrenzte .";
diff --git a/plugins/dataTypes/Company/lang/en.php b/plugins/dataTypes/Company/lang/en.php
deleted file mode 100644
index c04c5afc2..000000000
--- a/plugins/dataTypes/Company/lang/en.php
+++ /dev/null
@@ -1,5 +0,0 @@
-Dolor Inc., or Convallis Limited .";
\ No newline at end of file
diff --git a/plugins/dataTypes/Company/lang/es.php b/plugins/dataTypes/Company/lang/es.php
deleted file mode 100644
index 3b40a9bcb..000000000
--- a/plugins/dataTypes/Company/lang/es.php
+++ /dev/null
@@ -1,5 +0,0 @@
-lorem ipsum y de un sufijo apropiado como Dolor Inc. , o Convallis Limited .";
diff --git a/plugins/dataTypes/Company/lang/fr.php b/plugins/dataTypes/Company/lang/fr.php
deleted file mode 100644
index a18ed34cb..000000000
--- a/plugins/dataTypes/Company/lang/fr.php
+++ /dev/null
@@ -1,6 +0,0 @@
-Inc, ou Convallis limitée .";
\ No newline at end of file
diff --git a/plugins/dataTypes/Company/lang/nl.php b/plugins/dataTypes/Company/lang/nl.php
deleted file mode 100644
index edd5e7aeb..000000000
--- a/plugins/dataTypes/Company/lang/nl.php
+++ /dev/null
@@ -1,5 +0,0 @@
-Dolor Inc, of Convallis Limited .";
diff --git a/plugins/dataTypes/Composite/Composite.class.php b/plugins/dataTypes/Composite/Composite.class.php
deleted file mode 100644
index 7c36ae21d..000000000
--- a/plugins/dataTypes/Composite/Composite.class.php
+++ /dev/null
@@ -1,100 +0,0 @@
-smarty = new SecureSmarty();
- $this->smarty->template_dir = realpath(__DIR__ . "/../../../resources/libs/smarty");
- $this->smarty->compile_dir = realpath(__DIR__ . "/../../../cache");
- }
- }
-
- public function generate($generator, $generationContextData) {
- $placeholders = array();
- foreach ($generationContextData["existingRowData"] as $rowInfo) {
- $colNum = $rowInfo["colNum"];
- $randomData = is_array($rowInfo["randomData"]) ? $rowInfo["randomData"]["display"] : $rowInfo["randomData"];
- $placeholders["ROW{$colNum}"] = $randomData;
- }
- while (list($key, $value) = each($placeholders)) {
- $this->smarty->assign($key, $value);
- }
- $output = $this->smarty->fetch('string:' . $generationContextData["generationOptions"]);
-
- return array(
- "display" => $output
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $col, $num_cols) {
- if (!isset($postdata["dtOption_$col"]) || empty($postdata["dtOption_$col"])) {
- return false;
- }
- return $postdata["dtOption_$col"];
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
- return $L["see_help_dialog"];
- }
-
- public function getOptionsColumnHTML() {
- return '';
- }
-
- public function getHelpHTML() {
- $content =<<< END
-
- {$this->L["Composite_help_1"]}
-
-
- {$this->L["Composite_help_2"]}
-
-
- {$this->L["Composite_help_3"]}
-
-
- {$this->L["Composite_help_4"]}
- {$this->L["Composite_help_5"]}
-
- {\$ROW2-\$ROW1} - {$this->L["Composite_subtraction"]}
- {\$ROW2*\$ROW1} - {$this->L["Composite_multiplication"]}
- {\$ROW2/\$ROW1} - {$this->L["Composite_division"]}
-
-
-
- {$this->L["Composite_help_6"]}
- {if \$ROW1 == 5}{$this->L["Composite_na"]}{else}{\$ROW1}{/if}
-
-
-
- {$this->L["Composite_help_7"]}
-
-END;
-
- return $content;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "TEXT default NULL",
- "SQLField_Oracle" => "BLOB default NULL",
- "SQLField_MSSQL" => "VARCHAR(MAX) NULL"
- );
- }
-}
diff --git a/plugins/dataTypes/Composite/Composite.js b/plugins/dataTypes/Composite/Composite.js
deleted file mode 100755
index afe4af05c..000000000
--- a/plugins/dataTypes/Composite/Composite.js
+++ /dev/null
@@ -1,65 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name Composite
- * @description JS code for the Composite Data Type.
- * @see DataType
- * @namespace
- */
-
- /* @private */
- var MODULE_ID = "data-type-Composite";
- var LANG = L.dataTypePlugins.Composite;
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
-
- return errors;
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtOption_" + rowNum).val(data.option);
- },
- isComplete: function() { return $("#dtOption_" + rowNum).length > 0; }
- };
- };
-
- var _saveRow = function(rowNum) {
- return {
- "example": $("#dtExample_" + rowNum).val(),
- "option": $("#dtOption_" + rowNum).val()
- };
- };
-
-
- manager.registerDataType(MODULE_ID, {
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-});
-
diff --git a/plugins/dataTypes/Composite/lang/de.php b/plugins/dataTypes/Composite/lang/de.php
deleted file mode 100644
index 01dbeeadb..000000000
--- a/plugins/dataTypes/Composite/lang/de.php
+++ /dev/null
@@ -1,16 +0,0 @@
-{\$ROW1}, {\$ROW2} , etc. Sie können nicht auf der aktuellen Zeile beziehen - das würde entweder schmelzen der Server und / oder das Universum implodieren.";
-$L["Composite_help_3"] = "Hier sind ein paar Beispiele:";
-$L["Composite_help_4"] = "Anzeige einen Wert aus Zeile 6: {\$ROW6} ";
-$L["Composite_help_5"] = "Unter der Annahme, Zeile 1 und Zeile 2 enthalten Zufallszahlen, werden die folgenden Beispiele von einigen einfachen Mathematik:";
-$L["Composite_help_6"] = "Wenn Zeile 1 enthält die Nummer 5, die Anzeige \"N / A\", sonst nur zeigt die Anzahl.";
-$L["Composite_help_7"] = "Bitte lesen Sie die Smarty Homepage für weitere Informationen über die Syntax.";
-$L["Composite_multiplication"] = "Multiplikation";
-$L["Composite_na"] = "N / A";
-$L["Composite_subtraction"] = "Subtraktion";
\ No newline at end of file
diff --git a/plugins/dataTypes/Composite/lang/en.php b/plugins/dataTypes/Composite/lang/en.php
deleted file mode 100644
index 2e4697cd9..000000000
--- a/plugins/dataTypes/Composite/lang/en.php
+++ /dev/null
@@ -1,16 +0,0 @@
-{\$ROW1}, {\$ROW2} , etc. You cannot refer to the current row - that would either melt the server and/or make the universe implode.";
-$L["Composite_help_3"] = "Here are a few examples:";
-$L["Composite_help_4"] = "Display a value from row 6: {\$ROW6} ";
-$L["Composite_help_5"] = "Assuming row 1 and row 2 contain random numbers, the following are examples of some simple math:";
-$L["Composite_help_6"] = "If row 1 contains the number 5, display "N/A", otherwise just display the number.";
-$L["Composite_help_7"] = "Please see the Smarty website for more information on the syntax.";
-$L["Composite_multiplication"] = "multiplication";
-$L["Composite_na"] = "N/A";
-$L["Composite_subtraction"] = "subtraction";
\ No newline at end of file
diff --git a/plugins/dataTypes/Composite/lang/es.php b/plugins/dataTypes/Composite/lang/es.php
deleted file mode 100644
index c5eb00847..000000000
--- a/plugins/dataTypes/Composite/lang/es.php
+++ /dev/null
@@ -1,16 +0,0 @@
-{\$ROW1}, {\$ROW2} , etc. No puedes hacer referencia a la fila actual, lo que podría fundir el servidor y/o hacer que el Universo explotara.";
-$L["Composite_help_3"] = "Aquí tienes algunos ejemplos:";
-$L["Composite_help_4"] = "Mostrar un valor de la fila 6: {\$ROW6} ";
-$L["Composite_help_5"] = "Asumiendo que las filas 1 y 2 contienen números aleatorios, lo siguiente es un ejemplo de un cálculo sencillo:";
-$L["Composite_help_6"] = "Si la fila 1 contiene el número 5, mostrar "N/D", en caso contrario, mostrar el número.";
-$L["Composite_help_7"] = "Por favor, visita el sitio de Smarty para ampliar información de la sintaxis.";
-$L["Composite_multiplication"] = "multiplicación";
-$L["Composite_na"] = "N/D";
-$L["Composite_subtraction"] = "substracción";
diff --git a/plugins/dataTypes/Composite/lang/fr.php b/plugins/dataTypes/Composite/lang/fr.php
deleted file mode 100644
index 9cf653cfb..000000000
--- a/plugins/dataTypes/Composite/lang/fr.php
+++ /dev/null
@@ -1,17 +0,0 @@
-{\$ROW1}, {\$ROW2} , etc. Vous ne pouvez pas utiliser la ligne actuelle - ce qui ferait fondre le serveur et / ou imploser l'univers.";
-$L["Composite_help_3"] = "Voici quelques exemples:";
-$L["Composite_help_4"] = "Afficher la valeur de la ligne 6: {\$ROW6} ";
-$L["Composite_help_5"] = "En supposant que la ligne 1 et la ligne 2 contiennent des nombres aléatoires, les éléments suivants sont des exemples de quelques calculs simples:";
-$L["Composite_help_6"] = "Si la ligne 1 contient le nombre 5, afficher \"N/A\", sinon il suffit d'afficher le nombre.";
-$L["Composite_help_7"] = "Consultez le site Web de Smarty pour plus d'informations sur la syntaxe.";
-$L["Composite_multiplication"] = "Multiplication";
-$L["Composite_na"] = "N / A";
-$L["Composite_name"] = "Composite";
-$L["Composite_subtraction"] = "Soustraction";
diff --git a/plugins/dataTypes/Composite/lang/nl.php b/plugins/dataTypes/Composite/lang/nl.php
deleted file mode 100644
index 7cfd51e4a..000000000
--- a/plugins/dataTypes/Composite/lang/nl.php
+++ /dev/null
@@ -1,16 +0,0 @@
-{\$ROW1}, {\$ROW2} , enz. U kunt niet verwijzen naar de huidige rij - dat zou een smeltpunt de server en / of maak het universum imploderen.";
-$L["Composite_help_3"] = "Hier zijn een paar voorbeelden:";
-$L["Composite_help_4"] = "Geef een waarde van rij 6: {\$ROW6} ";
-$L["Composite_help_5"] = "Ervan uitgaande dat rij 1 en rij 2 bevatten willekeurige getallen, worden de volgende voorbeelden van een aantal eenvoudige wiskunde:";
-$L["Composite_help_6"] = "Als rij 1 bevat het nummer 5, weer te geven \"N / A\", anders gewoon weer het nummer.";
-$L["Composite_help_7"] = "Raadpleeg de Smarty website voor meer informatie over de syntaxis.";
-$L["Composite_multiplication"] = "vermenigvuldiging";
-$L["Composite_na"] = "N / A";
-$L["Composite_subtraction"] = "aftrekking";
diff --git a/plugins/dataTypes/Constant/Constant.class.php b/plugins/dataTypes/Constant/Constant.class.php
deleted file mode 100644
index 66ddf4cf4..000000000
--- a/plugins/dataTypes/Constant/Constant.class.php
+++ /dev/null
@@ -1,109 +0,0 @@
- ($numValues - 1)) {
- $itemIndex = ($itemIndex % $numValues);
- }
- $value = $options["values"][$itemIndex];
- }
- return array(
- "display" => $value
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- if (!isset($postdata["dtOption_$colNum"])) {
- return false;
- }
-
- // fix for https://github.com/benkeen/generatedata/issues/166
- $optionValue = trim($postdata["dtOption_$colNum"]);
- if ($optionValue == "") {
- return false;
- }
-
- if (!isset($postdata["dtConstantLoopCount_$colNum"]) || empty($postdata["dtConstantLoopCount_$colNum"])) {
- return false;
- }
- $loopCount = trim($postdata["dtConstantLoopCount_$colNum"]);
- if (!is_numeric($loopCount) || $loopCount <= 0) {
- return false;
- }
-
- $options = array(
- "loopCount" => $loopCount,
- "values" => explode("|", $postdata["dtOption_$colNum"])
- );
-
- return $options;
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
- return $L["see_help_dialog"];
- }
-
- public function getOptionsColumnHTML() {
- $html =<<
-
- {$this->L["loop_count"]}
-
-
-
- {$this->L["values"]}
-
-
-
-EOF;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "TEXT default NULL",
- "SQLField_Oracle" => "BLOB default NULL",
- "SQLField_MSSQL" => "VARCHAR(MAX) NULL",
- "SQLField_Postgres" => "TEXT NULL"
- );
- }
-
- public function getHelpHTML() {
- $html =<<< END
-
- {$this->L["help_1"]}
-
-
- {$this->L["help_2"]}
- {$this->L["help_3"]}
- {$this->L["help_4"]}
-
-
- {$this->L["help_5"]}
-
-END;
-
- return $html;
- }
-}
diff --git a/plugins/dataTypes/Constant/Constant.js b/plugins/dataTypes/Constant/Constant.js
deleted file mode 100755
index 242df6d26..000000000
--- a/plugins/dataTypes/Constant/Constant.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "generator",
- "constants",
- "lang"
-], function(manager, generator, C, L) {
-
- "use strict";
-
- /**
- * @name Constant
- * @description JS code for the Constant Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-Constant";
- var LANG = L.dataTypePlugins.Constant;
-
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {},
- isComplete: function() {
- if ($("#dtOption_" + rowNum).length) {
- $("#dtConstantLoopCount_" + rowNum).val(data.loopCount);
- $("#dtOption_" + rowNum).val(data.values);
- return true;
- } else {
- return false;
- }
- }
- };
- };
-
- var _saveRow = function(rowNum) {
- return {
- loopCount: $("#dtConstantLoopCount_" + rowNum).val(),
- values: $("#dtOption_" + rowNum).val()
- };
- };
-
- var _validate = function(rows) {
- var invalidLoopCountFields = [];
- var loopCountVisibleProblemRows = [];
-
- var emptyFields = [];
- var emptyFieldProblemRows = [];
-
- for (var i=0; i" + loopCountVisibleProblemRows.join(", ") + ""});
- }
- if (emptyFields.length) {
- errors.push({ els: emptyFields, error: LANG.incomplete_fields + " " + emptyFieldProblemRows.join(", ") + " "});
- }
-
- return errors;
- };
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- loadRow: _loadRow,
- saveRow: _saveRow,
- validate: _validate
- });
-
-});
-
-
-
-/*
-var Constant_ns = {
- validate: function(rows)
- {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- },
-}
-*/
\ No newline at end of file
diff --git a/plugins/dataTypes/Constant/lang/de.php b/plugins/dataTypes/Constant/lang/de.php
deleted file mode 100644
index ad610276c..000000000
--- a/plugins/dataTypes/Constant/lang/de.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 0) in der Loop Count Feld.";
-$L["help_3"] = "Wenn Sie möchten, dass 100 Zeilen des Strings \"Male\" von 100 Zeilen die Zeichenfolge \"Female\" und wiederholen Sie gefolgt sind, können Sie \"100\" in der Loop Count Feld und \"männlich | weiblich\" , in der Value (s) ein.";
-$L["help_4"] = "Wenn Sie 5 Reihen von 1 bis 10 möchten, geben Sie \"5\" für die Loop Count Feld, und \"1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10\" in der Wert (e) ein.";
-$L["help_5"] = "Versuchen Sie bastelt herum. Sie bekommen die Idee.";
-$L["incomplete_fields"] = "The Constant Datentyp muss die Konstanten in den Wert (e) eingegeben werden müssen. Bitte beheben Sie die folgenden Zeilen:";
-$L["invalid_loop_counts"] = "Bitte geben Sie Zahlen für Constant Schleifenzähler Felder. Korrigieren Sie die folgende Zeile (n):";
-$L["loop_count"] = "Loop count:";
-$L["name"] = "Konstante";
-$L["values"] = "Wert (e):";
diff --git a/plugins/dataTypes/Constant/lang/en.php b/plugins/dataTypes/Constant/lang/en.php
deleted file mode 100644
index e0b62b206..000000000
--- a/plugins/dataTypes/Constant/lang/en.php
+++ /dev/null
@@ -1,14 +0,0 @@
-0) in the Loop Count field.";
-$L["help_3"] = "If you'd like to have 100 rows of the string "Male" followed by 100 rows of the string "Female" and repeat, you can enter "100" in the Loop Count field and "Male|Female" in the Value(s) field.";
-$L["help_4"] = "If you'd like 5 rows of 1 through 10, enter "5" for the Loop Count field, and "1|2|3|4|5|6|7|8|9|10" in the Value(s) field.";
-$L["help_5"] = "Try tinkering around with it. You'll get the idea.";
-$L["loop_count"] = "Loop count:";
-$L["values"] = "Value(s):";
-$L["invalid_loop_counts"] = "Please enter numbers for Constant loop count fields. Please fix the following row(s):";
-$L["incomplete_fields"] = "The Constant data type needs to have the constants to be entered into the value(s) field. Please fix the following rows:";
diff --git a/plugins/dataTypes/Constant/lang/es.php b/plugins/dataTypes/Constant/lang/es.php
deleted file mode 100644
index 9fbbe8a79..000000000
--- a/plugins/dataTypes/Constant/lang/es.php
+++ /dev/null
@@ -1,14 +0,0 @@
-0) in el campo Contador de bucle.";
-$L["help_3"] = "Si quieres tener 100 filas de la cadena "Hombre" seguidas por 100 filas de la cadena "Mujer" y repetir, puedes introducir "100" en el campo de Contador de bucle y "Hombre|Mujer" en el campo Valores.";
-$L["help_4"] = "Si quieres 5 filas de 1 hasta 10, introduce "5" en el campo Contador de bucle y "1|2|3|4|5|6|7|8|9|10" en el campo Valores.";
-$L["help_5"] = "Prueba a jugar con esto. Te harás una idea.";
-$L["loop_count"] = "Contador del bucle:";
-$L["values"] = "Valores:";
-$L["invalid_loop_counts"] = "Por favor, introduce números en los campos de Constante de contador de bucle. Por favor, arregla las siguientes filas:";
-$L["incomplete_fields"] = "Es necesario que el tipo de dato Constante tenga introducidas constantes en el campo Valores. Por favor, corrija las siguientes filas:";
diff --git a/plugins/dataTypes/Constant/lang/fr.php b/plugins/dataTypes/Constant/lang/fr.php
deleted file mode 100644
index eeac8605c..000000000
--- a/plugins/dataTypes/Constant/lang/fr.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 0) dans le champ 'Boucles'.";
-$L["help_3"] = "Si vous voulez avoir 100 lignes contentant la chaîne \"Homme\" suivi par 100 lignes contentant la chaîne \"Femme\" et répéter cette opération jusqu'à la fin, vous pouvez entrer \"100\" dans le champ 'Valeur' et \"Homme | Femme\", dans le champ 'Boucles'.";
-$L["help_4"] = "Si vous souhaitez obtenir 5 rangées de 1 à 10, entrez \"5\" pour le champ 'Boucle', et «1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10» dans le champ 'Valeur'.";
-$L["help_5"] = "Essayez de bricoler un peu avec ce type de données, vous comprendrez vite.";
-$L["incomplete_fields"] = "Le type de données Constante nécessite un champ 'Valeur' renseigné. Corrigez les lignes suivantes:";
-$L["invalid_loop_counts"] = "Le type de données Constante nécessite un champ 'Boucles' renseigné. Corrigez les lignes suivantes:";
-$L["loop_count"] = "Boucles:";
-$L["name"] = "Constante";
-$L["values"] = "Valeur:";
diff --git a/plugins/dataTypes/Constant/lang/nl.php b/plugins/dataTypes/Constant/lang/nl.php
deleted file mode 100644
index 24a2280e2..000000000
--- a/plugins/dataTypes/Constant/lang/nl.php
+++ /dev/null
@@ -1,15 +0,0 @@
- 0) in de Loop veld Aantal.";
-$L["help_3"] = "Als u wilt 100 rijen van de string \"Male\", gevolgd door 100 rijen van de string \"Vrouw\" en herhaal, kunt u \"100\" in de Loop veld Aantal en \"Man | Vrouw\", in de Waarde (n) veld.";
-$L["help_4"] = "Als u wilt 5 rijen van 1 willen tot en met 10, voert u \"5\" voor de Loop veld Aantal en \"1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10\" in het Waarde (n) veld.";
-$L["help_5"] = "Probeer knutselen rond met het. Je krijgt het idee.";
-$L["incomplete_fields"] = "De Constant gegevenstype moet hebben de constanten te worden ingevoerd in de waarde (n) veld. Please fix de volgende rijen:";
-$L["invalid_loop_counts"] = "Geef cijfers voor constante lus tellen velden. Please fix de volgende rij (en):";
-$L["loop_count"] = "Loop count:";
-$L["values"] = "Waarde (n):";
-
diff --git a/plugins/dataTypes/Country/Country.class.php b/plugins/dataTypes/Country/Country.class.php
deleted file mode 100644
index 0d579193c..000000000
--- a/plugins/dataTypes/Country/Country.class.php
+++ /dev/null
@@ -1,186 +0,0 @@
-
- * @package DataTypes
- */
-class DataType_Country extends DataTypePlugin {
- protected $dataTypeName = "Country";
- protected $dataTypeFieldGroup = "geo";
- protected $dataTypeFieldGroupOrder = 50;
- protected $jsModules = array("Country.js");
- private $countries = array('Afghanistan', 'Åland Islands', 'Albania', 'Algeria', 'American Samoa', 'Andorra',
- 'Angola', 'Anguilla', 'Antarctica', 'Antigua and Barbuda', 'Argentina', 'Armenia', 'Aruba', 'Australia',
- 'Austria', 'Azerbaijan', 'Bahamas', 'Bahrain', 'Bangladesh', 'Barbados', 'Belarus', 'Belgium', 'Belize', 'Benin',
- 'Bermuda', 'Bhutan', 'Bolivia', 'Bonaire, Sint Eustatius and Saba', 'Bosnia and Herzegovina', 'Botswana',
- 'Bouvet Island', 'Brazil', 'British Indian Ocean Territory', 'Brunei', 'Bulgaria', 'Burkina Faso', 'Burundi',
- 'Cambodia', 'Cameroon', 'Canada', 'Cape Verde', 'Cayman Islands', 'Central African Republic', 'Chad', 'Chile',
- 'China', 'Christmas Island', 'Cocos (Keeling) Islands', 'Colombia', 'Comoros', 'Congo (Brazzaville)',
- 'Congo, the Democratic Republic of the', 'Cook Islands', 'Costa Rica', "Côte D'Ivoire (Ivory Coast)", 'Croatia',
- 'Cuba', 'Curaçao', 'Cyprus', 'Czech Republic', 'Denmark', 'Djibouti', 'Dominica', 'Dominican Republic',
- 'Ecuador', 'Egypt', 'El Salvador', 'Equatorial Guinea', 'Eritrea', 'Estonia', 'Ethiopia', 'Falkland Islands',
- 'Faroe Islands', 'Fiji', 'Finland', 'France', 'French Guiana', 'French Polynesia', 'French Southern Territories',
- 'Gabon', 'Gambia', 'Georgia', 'Germany', 'Ghana', 'Gibraltar', 'Greece', 'Greenland', 'Grenada', 'Guadeloupe',
- 'Guam', 'Guatemala', 'Guernsey', 'Guinea', 'Guinea-Bissau', 'Guyana', 'Haiti', 'Heard Island and Mcdonald Islands',
- 'Holy See (Vatican City State)', 'Honduras', 'Hong Kong', 'Hungary', 'Iceland', 'India', 'Indonesia',
- 'Iran', 'Iraq', 'Ireland', 'Isle of Man', 'Israel', 'Italy', 'Jamaica', 'Japan', 'Jersey', 'Jordan', 'Kazakhstan',
- 'Kenya', 'Kiribati', 'Korea, North', 'Korea, South', 'Kuwait', 'Kyrgyzstan', 'Laos', 'Latvia', 'Lebanon',
- 'Lesotho', 'Liberia', 'Libya', 'Liechtenstein', 'Lithuania', 'Luxembourg', 'Macao', 'Macedonia', 'Madagascar',
- 'Malawi', 'Malaysia', 'Maldives', 'Mali', 'Malta', 'Marshall Islands', 'Martinique', 'Mauritania', 'Mauritius',
- 'Mayotte', 'Mexico', 'Micronesia', 'Moldova', 'Monaco', 'Mongolia', 'Montenegro', 'Montserrat', 'Morocco',
- 'Mozambique', 'Myanmar', 'Namibia', 'Nauru', 'Nepal', 'Netherlands', 'New Caledonia', 'New Zealand', 'Nicaragua',
- 'Niger', 'Nigeria', 'Niue', 'Norfolk Island', 'Northern Mariana Islands', 'Norway', 'Oman', 'Pakistan', 'Palau',
- 'Palestine, State of', 'Panama', 'Papua New Guinea', 'Paraguay', 'Peru', 'Philippines', 'Pitcairn Islands',
- 'Poland', 'Portugal', 'Puerto Rico', 'Qatar', 'Reunion', 'Romania', 'Russian Federation', 'Rwanda',
- 'Saint Barthélemy', 'Saint Helena, Ascension and Tristan da Cunha', 'Saint Kitts and Nevis', 'Saint Lucia',
- 'Saint Martin', 'Saint Pierre and Miquelon', 'Saint Vincent and The Grenadines', 'Samoa', 'San Marino',
- 'Sao Tome and Principe', 'Saudi Arabia', 'Senegal', 'Serbia', 'Seychelles', 'Sierra Leone', 'Singapore',
- 'Sint Maarten', 'Slovakia', 'Slovenia', 'Solomon Islands', 'Somalia', 'South Africa',
- 'South Georgia and The South Sandwich Islands', 'Spain', 'Sri Lanka', 'Sudan', 'South Sudan', 'Suriname',
- 'Svalbard and Jan Mayen Islands', 'Swaziland', 'Sweden', 'Switzerland', 'Syria', 'Taiwan', 'Tajikistan',
- 'Tanzania', 'Thailand', 'Timor-Leste', 'Togo', 'Tokelau', 'Tonga', 'Trinidad and Tobago', 'Tunisia', 'Turkey',
- 'Turkmenistan', 'Turks and Caicos Islands', 'Tuvalu', 'Uganda', 'Ukraine', 'United Arab Emirates',
- 'United Kingdom (Great Britain)', 'United States', 'United States Minor Outlying Islands', 'Uruguay',
- 'Uzbekistan', 'Vanuatu', 'Venezuela', 'Viet Nam', 'Virgin Islands, British', 'Virgin Islands, United States',
- 'Wallis and Futuna', 'Western Sahara', 'Yemen', 'Zambia', 'Zimbabwe');
- private $numCountries;
- private $selectedCountrySlugs;
- private $numSelectedCountrySlugs;
- private $countryRegionData;
- private $numCountryRegionData;
-
-
- /**
- * For convenience, the constructor gets ALL country-plugin installed and stores all their data locally.
- * The individual generate() calls for each row filter out the country data it's not interested in.
- * @param string $runtimeContext
- */
- public function __construct($runtimeContext) {
- parent::__construct($runtimeContext);
- if ($runtimeContext == "generation") {
- $this->numCountries = count($this->countries);
- $this->countryRegionData = Core::$geoData->getCountryRegionHash();
- $this->numCountryRegionData = count($this->countryRegionData);
- }
- }
-
- public function generate($generator, $generationContextData) {
- $data = array();
- if ($generationContextData["generationOptions"] == "all") {
- $data["display"] = $this->countries[mt_rand(0, $this->numCountries-1)];
- } else {
- // pick a random country from whatever countries were selected
- $randomCountrySlug = $this->selectedCountrySlugs[mt_rand(0, $this->numSelectedCountrySlugs-1)];
- $randomCountry = $this->countryRegionData[$randomCountrySlug];
-
- $data = array(
- "display" => $randomCountry["country"],
- "slug" => $randomCountry["country_slug"],
- "id" => $randomCountry["id"]
- );
- }
- return $data;
- }
-
- /**
- * @see DataTypePlugin::getRowGenerationOptions()
- */
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- $selectedCountrySlugs = $generator->getCountries();
-
- $option = "all";
- if (isset($postdata["dtOption_$colNum"])) {
- $option = "countryPluginsOnly";
- }
-
- // if the user didn't select any countries and they checked the "limit to those countries selected above
- // option, there's nothing for us to generate. Just return false so the row is ignored. TODO: update the
- // JS code to throw an error
- if (empty($selectedCountrySlugs) && $option == "countryPluginsOnly") {
- return false;
- }
-
- $this->selectedCountrySlugs = $selectedCountrySlugs;
- $this->numSelectedCountrySlugs = count($selectedCountrySlugs);
-
- return $option;
- }
-
-
- public function getOptionsColumnHTML() {
- $html =<<< END
-{$this->L["limit_results"]}
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(100) default NULL",
- "SQLField_Oracle" => "varchar2(100) default NULL",
- "SQLField_MSSQL" => "VARCHAR(100) NULL"
- );
- }
-
- /**
- * Returns an array of countries.
- */
- public function getCountries($countrySlugs) {
- $whereClause = "";
- if (!empty($countrySlugs)) {
- $whereClause = "WHERE has_full_data_set = 'yes'";
- $slugClauses = array();
- foreach ($countrySlugs as $slug) {
- $slugClauses[] = "country_slug = '$slug'";
- }
-
- $slugClause = "(" . implode(" OR ", $slugClauses) . ")";
- $whereClause .= "AND $slugClause";
- }
-
- $prefix = Core::getDbTablePrefix();
- $response = Core::$db->query("
- SELECT *
- FROM {$prefix}countries
- $whereClause
- ");
-
- $countries = array();
- while ($countryInfo = mysqli_fetch_assoc($query)) {
- $countries[] = array(
- "country" => $countryInfo['country'],
- "id" => $countryInfo['id'],
- "slug" => $countryInfo["country_slug"]
- );
- }
- return $countries;
- }
-
- function getRegions($countryID) {
- $prefix = Core::getDbTablePrefix();
- $response = Core::$db->query("
- SELECT *
- FROM {$prefix}regions
- WHERE country_id = $countryID
- ");
-
- if ($response["success"]) {
- $regionInfo = array();
- while ($row = mysqli_fetch_assoc($response["results"])) {
- $regionInfo[] = $row;
- }
- }
-
- return $regionInfo;
- }
-}
diff --git a/plugins/dataTypes/Country/Country.js b/plugins/dataTypes/Country/Country.js
deleted file mode 100755
index cec8b7471..000000000
--- a/plugins/dataTypes/Country/Country.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "constants"
-], function(manager, C) {
-
- "use strict";
-
- /**
- * @name Country
- * @description JS code for the Country Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-Country";
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.COUNTRIES.CHANGE] = _countryChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {},
- isComplete: function() {
- var optionField = $("#dtOption_" + rowNum);
- if (optionField.length) {
- if (data.checked) {
- optionField.attr("checked", "checked");
- } else {
- optionField.removeAttr("checked");
- }
- return true;
- } else {
- return false;
- }
- }
- };
- };
-
- var _saveRow = function(rowNum) {
- return {
- "checked": ($("#dtOption_" + rowNum).attr("checked")) ? "checked" : ""
- };
- };
-
- /**
- * This function has two convenient side-effects:
- * 1. It runs on page load, so we don't need to do anything special.
- * 2. It also affects the hidden template, so we don't need to do anything special for Country
- * Data Types that are selected in the future - they'll already have the appropriate DOM changes.
- */
- var _countryChange = function(msg) {
- if (msg.countries.length > 0) {
- $(".dtCountry_allCountries").removeAttr("disabled");
- $(".dtCountry_allCountriesLabel").removeClass("gdDisabled");
- } else {
- $(".dtCountry_allCountries").attr("disabled", "disabled").removeAttr("checked");
- $(".dtCountry_allCountriesLabel").addClass("gdDisabled");
- }
- };
-
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- init: _init,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/Country/lang/de.php b/plugins/dataTypes/Country/lang/de.php
deleted file mode 100644
index ee216ef0b..000000000
--- a/plugins/dataTypes/Country/lang/de.php
+++ /dev/null
@@ -1,6 +0,0 @@
-
- * @package DataTypes
- */
-class DataType_Currency extends DataTypePlugin {
- protected $dataTypeName = "Currency";
- protected $dataTypeFieldGroup = "numeric";
- protected $dataTypeFieldGroupOrder = 60;
- protected $jsModules = array("Currency.js");
-
- public function __construct($runtimeContext) {
- parent::__construct($runtimeContext);
- if ($runtimeContext == "generation") {
-
- }
- }
-
- // meh. All this string manipulation code could be improved, I'm sure
- public function generate($generator, $generationContextData) {
-
- $rangeFrom = preg_replace("/\D/", "", $generationContextData["generationOptions"]["rangeFrom"]);
- $rangeTo = preg_replace("/\D/", "", $generationContextData["generationOptions"]["rangeTo"]);
- $format = $generationContextData["generationOptions"]["format"];
- $dollarSymbol = $generationContextData["generationOptions"]["symbol"];
- $dollarSymbolLocation = $generationContextData["generationOptions"]["symbolLocation"];
-
- $randString = (string) mt_rand($rangeFrom, $rangeTo);
- $randStringRev = strrev($randString);
- $randStringRevLength = strlen($randStringRev);
- $reversedFormat = strrev($format);
-
- $display = "";
-
- $randNumIndex = 0;
- for ($i=0; $i $randStringRevLength) {
- break;
- }
- if ($reversedFormat[$i] == "X") {
- $display .= $randStringRev[$randNumIndex];
- $randNumIndex++;
- } else {
- $display .= $reversedFormat[$i];
- }
- }
- $display = strrev($display);
-
- // if it's under 1 dollar (or whatever) and has cents, we need to fix really small generated
- // nums. Pretty feeble logic here, and I'm not 100% sure this will work for all currency formats
- $hasCents = preg_match("/\D/", $format[strlen($format)-3]);
- $numChars = strlen($display);
- if ($hasCents && $numChars < 4) {
- $truncatedFormat = preg_replace("/X/", "0", substr($format, -4));
-
- if ($numChars === 0) {
- $display = $truncatedFormat;
- } else if ($numChars == 1) {
- $display = substr($truncatedFormat, 0, 3) . $display;
- } else if ($numChars == 2) {
- $display = substr($truncatedFormat, 0, 2) . $display;
- } else if ($numChars == 3) {
- $display = "0" . $display;
- }
- }
-
- // if $display begins with a non-digit, we need to prefix it with a zero
- if (preg_match("/\D/", $display[0])) {
- $display = "0" . $display;
- }
-
- // apply the dollar symbol
- if (!empty($dollarSymbol)) {
- if ($dollarSymbolLocation == "prefix") {
- $display = $dollarSymbol . $display;
- } else {
- $display = $display . $dollarSymbol;
- }
- }
-
- return array(
- "display" => $display
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- $generationOptions = array(
- "format" => $postdata["dtCurrencyFormat_$colNum"],
- "rangeFrom" => $postdata["dtCurrencyRangeFrom_$colNum"],
- "rangeTo" => $postdata["dtCurrencyRangeTo_$colNum"],
- "symbol" => $postdata["dtCurrencySymbol_$colNum"],
- "symbolLocation" => $postdata["dtCurrencySymbolLocation_$colNum"]
- );
-
- return $generationOptions;
- }
-
-
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $html =<<< END
-
- {$L["please_select"]}
-
- $0.00 -> $100.00
- $5,000 -> $10,000 ({$this->L["no_cents"]})
- $1000.00 -> $10000.00 ({$this->L["no_thousand_delimiters"]})
- -$100,000.00 -> $100,000.00
- 0.01 -> 1.00 ({$this->L["no_dollar_sign"]})
- 100,00 $ -> 1.000,00 $ (French Canadian)
- 10 -> 100 000
-
-
- £0.00 -> £100.00
-
- €100,000 -> €200,000
-
-END;
- return $html;
- }
-
-
- public function getOptionsColumnHTML() {
- $html =<<< END
-
- {$this->L["format"]}:
-
-
- {$this->L["range"]}
- {$this->L["to"]}
-
-
- {$this->L["currency_symbol"]}
-
-
- {$this->L["prefix"]}
- {$this->L["suffix"]}
-
-
-
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(100) default NULL",
- "SQLField_Oracle" => "varchar2(100) default NULL",
- "SQLField_MSSQL" => "VARCHAR(100) NULL"
- );
- }
-
- public function getHelpHTML() {
- $content =<<
- {$this->L["help_intro"]}
-
-
-
-
- {$this->L["format"]}
- {$this->L["format_desc"]}
-
-
- {$this->L["range_from"]}
- {$this->L["range_from_desc"]}
-
-
- {$this->L["range_to"]}
- {$this->L["range_to_desc"]}
-
-
- {$this->L["currency_symbol"]}
- {$this->L["currency_symbol_desc"]}
-
-
- {$this->L["prefix_suffix"]}
- {$this->L["prefix_suffix_desc"]}
-
-
-EOF;
-
- return $content;
- }
-}
diff --git a/plugins/dataTypes/Currency/Currency.js b/plugins/dataTypes/Currency/Currency.js
deleted file mode 100644
index 2a90d3f30..000000000
--- a/plugins/dataTypes/Currency/Currency.js
+++ /dev/null
@@ -1,144 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name Currency
- * @description JS code for the Currency Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-Currency";
- var LANG = L.dataTypePlugins.Currency;
-
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _exampleChange = function(msg) {
- var format = "";
- var rangeFrom = "";
- var rangeTo = "";
- var symbol = "";
- var symbolLocation = "";
- if (msg.value) {
- var parts = msg.value.split("|");
- format = parts[0];
- rangeFrom = parts[1];
- rangeTo = parts[2];
- symbol = parts[3];
- symbolLocation = parts[4];
- }
- $("#dtCurrencyFormat_" + msg.rowID).val(format);
- $("#dtCurrencyRangeFrom_" + msg.rowID).val(rangeFrom);
- $("#dtCurrencyRangeTo_" + msg.rowID).val(rangeTo);
- $("#dtCurrencySymbol_" + msg.rowID).val(symbol);
- $("#dtCurrencySymbolLocation_" + msg.rowID).val(symbolLocation);
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtCurrencyFormat_" + rowNum).val(data.format);
- $("#dtCurrencyRangeFrom_" + rowNum).val(data.rangeFrom);
- $("#dtCurrencyRangeTo_" + rowNum).val(data.rangeTo);
- $("#dtCurrencySymbol_" + rowNum).val(data.symbol);
- $("#dtCurrencySymbolLocation_" + rowNum).val(data.symbolLocation);
- },
- isComplete: function() {
- return true;
- }
- };
- };
-
- var _saveRow = function(rowNum) {
- return {
- "format": $("#dtCurrencyFormat_" + rowNum).val(),
- "rangeFrom": $("#dtCurrencyRangeFrom_" + rowNum).val(),
- "rangeTo": $("#dtCurrencyRangeTo_" + rowNum).val(),
- "symbol": $("#dtCurrencySymbol_" + rowNum).val(),
- "symbolLocation": $("#dtCurrencySymbolLocation_" + rowNum).val()
- };
- };
-
- var _validate = function(rows) {
- var problemFields = [];
- var problemFields2 = [];
- var problemFields3 = [];
- var invalidFormatRows = [];
- var rowsWithInvalidRange = [];
- var fromRangeGreaterThanToRange = [];
-
- for (var i=0; i toNum) {
- fromRangeGreaterThanToRange.push(visibleRowNum);
- problemFields3.push(from);
- }
- }
- }
-
- var errors = [];
- if (invalidFormatRows.length) {
- errors.push({ els: problemFields, error: LANG.incomplete_fields + " " + invalidFormatRows.join(", ") + " "});
- }
- if (rowsWithInvalidRange.length) {
- errors.push({ els: problemFields2, error: LANG.invalid_range_fields + " " + rowsWithInvalidRange.join(", ") + " "});
- }
- if (fromRangeGreaterThanToRange.length) {
- errors.push({ els: problemFields3, error: LANG.invalid_range + " " + fromRangeGreaterThanToRange.join(", ") + " "});
- }
-
-
- return errors;
- };
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- init: _init,
- loadRow: _loadRow,
- saveRow: _saveRow,
- validate: _validate
- });
-
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/Currency/lang/de.php b/plugins/dataTypes/Currency/lang/de.php
deleted file mode 100644
index ecfb93fc8..000000000
--- a/plugins/dataTypes/Currency/lang/de.php
+++ /dev/null
@@ -1,26 +0,0 @@
-X's in eine Zahl umgewandelt: alle anderen Werte bleiben wie sie ist.";
-$L["range_from"] = "Reichweite - Von";
-$L["range_from_desc"] = "Gibt den unteren Bereich der Werte, was Sie generieren möchten. Hinweis: Dieses Feld darf nur Zahlen enthalten und (wenn Sie wollen) einen Dezimalpunkt und zwei folgenden Nummern Cent / Pence / etc darstellen.";
-$L["range_to"] = "Reichweite - Um";
-$L["range_to_desc"] = "Der obere Bereich der Zahlen zu erzeugen.";
-$L["currency_symbol"] = "Währungssymbol";
-$L["currency_symbol_desc"] = "Was auch immer Währungssymbol Sie verwenden möchten, z. B. $ , € , usw.";
-$L["prefix_suffix"] = "Präfix / Suffix";
-$L["prefix_suffix_desc"] = "Dieser legt fest, wo die Währung Symbol erscheinen soll.";
-$L["no_cents"] = "keine Cent";
-$L["no_thousand_delimiters"] = "keine tausend Trennzeichen";
-$L["no_dollar_sign"] = "keine Dollarzeichen";
-$L["range"] = "Reichweite";
-$L["to"] = "auf";
-$L["currency_symbol"] = "Währungssymbol";
-$L["prefix"] = "präfix";
-$L["suffix"] = "suffix";
-$L["incomplete_fields"] = "Der Currency-Datentyp haben muss das Format eingegeben. Bitte korrigieren Sie die folgenden Zeilen:";
-$L["invalid_range_fields"] = "Bitte geben Sie nur Zahlen in den Feldern Währung Bereich (0-9 und Dezimalpunkt). Bitte korrigieren Sie die folgenden Zeilen:";
-$L["invalid_range"] = "Bitte überprüfen Sie die Palette reicht von klein bis groß. Bitte korrigieren Sie die folgenden Zeilen:";
\ No newline at end of file
diff --git a/plugins/dataTypes/Currency/lang/en.php b/plugins/dataTypes/Currency/lang/en.php
deleted file mode 100644
index 4a29175cb..000000000
--- a/plugins/dataTypes/Currency/lang/en.php
+++ /dev/null
@@ -1,27 +0,0 @@
-X's are converted into a number: all other values are left as-is.";
-$L["range_from"] = "Range - From";
-$L["range_from_desc"] = "Specifies the lower range of whatever values you want to generate. Note: this field should only contain numbers and (if you want) a decimal point and two following numbers to represent cents/pence/etc.";
-$L["range_to"] = "Range - To";
-$L["range_to_desc"] = "The upper range of the numbers to generate.";
-$L["currency_symbol"] = "Currency Symbol";
-$L["currency_symbol_desc"] = "Whatever currency symbol you want to use, e.g. $ , € , etc.";
-$L["prefix_suffix"] = "Prefix/Suffix";
-$L["prefix_suffix_desc"] = "This determines where the currency symbol should appear.";
-
-$L["no_cents"] = "no cents";
-$L["no_thousand_delimiters"] = "no thousand delimiters";
-$L["no_dollar_sign"] = "no dollar sign";
-$L["range"] = "Range";
-$L["to"] = "To";
-$L["currency_symbol"] = "Currency Symbol";
-$L["prefix"] = "prefix";
-$L["suffix"] = "suffix";
-$L["incomplete_fields"] = "The Currency data type needs to have the format entered. Please fix the following rows:";
-$L["invalid_range_fields"] = "Please only enter numbers in the Currency range fields (0-9 and decimal point). Please fix the following rows: ";
-$L["invalid_range"] = "Please check the range goes from small to large. Please fix the following rows: ";
\ No newline at end of file
diff --git a/plugins/dataTypes/Currency/lang/es.php b/plugins/dataTypes/Currency/lang/es.php
deleted file mode 100644
index cb31b280e..000000000
--- a/plugins/dataTypes/Currency/lang/es.php
+++ /dev/null
@@ -1,26 +0,0 @@
-X's se convierte en un número: el resto de los valores se dejan tal cual.";
-$L["range_from"] = "Range - De";
-$L["range_from_desc"] = "Especifica el rango más bajo de lo que los valores que desea generar. Nota: Este campo sólo debe contener números y (si lo desea) un punto decimal y dos cifras siguientes para representar centavos / peniques / etc.";
-$L["range_to"] = "Range - Para";
-$L["range_to_desc"] = "El rango superior de los números de generar.";
-$L["currency_symbol"] = "Símbolo monetario";
-$L["currency_symbol_desc"] = "Cualquiera que sea moneda de símbolo que desea de usar, por ejemplo, $ , € , etc";
-$L["prefix_suffix"] = "Prefijo / Sufijo";
-$L["prefix_suffix_desc"] = "Esto determina dónde debe aparecer el símbolo de moneda.";
-$L["no_cents"] = "sin centavos";
-$L["no_thousand_delimiters"] = "hay miles de delimitadores";
-$L["no_dollar_sign"] = "ningún signo de dólar";
-$L["range"] = "Alcance";
-$L["to"] = "a";
-$L["currency_symbol"] = "Símbolo de moneda";
-$L["prefix"] = "prefijo";
-$L["suffix"] = "sufijo";
-$L["incomplete_fields"] = "El tipo de datos de moneda debe tener el formato introducido. Por favor, corrija los siguientes filas:";
-$L["invalid_range_fields"] = "Les rogamos introducir números en los campos de intervalo de Divisas (0-9 y el punto decimal). Por favor, corrija los siguientes filas: ";
-$L["invalid_range"] = "Por favor, compruebe el rango va desde pequeñas a grandes. Por favor, corrija los siguientes filas:";
\ No newline at end of file
diff --git a/plugins/dataTypes/Currency/lang/fr.php b/plugins/dataTypes/Currency/lang/fr.php
deleted file mode 100644
index 98d04e5b2..000000000
--- a/plugins/dataTypes/Currency/lang/fr.php
+++ /dev/null
@@ -1,26 +0,0 @@
-X sont remplacés par des chiffres et toutes les autres valeurs sont laissées telles quelles.";
-$L["range_from"] = "Fourchette - De";
-$L["range_from_desc"] = "Indique la fourchette basse des valeurs que vous souhaitez générer. Remarque: ce champ ne doit contenir que des chiffres et (si vous voulez) une virgule et les deux numéros suivants pour représenter les centimes.";
-$L["range_to"] = "Fourchette - A";
-$L["range_to_desc"] = "Indique la fourchette supérieure des nombres à générer.";
-$L["currency_symbol"] = "Symbole monétaire";
-$L["currency_symbol_desc"] = "Indique le symbole monétaire à utiliser, par exemple $ , € , etc";
-$L["prefix_suffix"] = "Préfixe / Suffixe";
-$L["prefix_suffix_desc"] = "Détermine la position du symbole monétaire.";
-$L["no_cents"] = "Pas de centime";
-$L["no_thousand_delimiters"] = "Pas de séparateur des milliers";
-$L["no_dollar_sign"] = "Pas de signe de dollar";
-$L["range"] = "Fourchette - De";
-$L["to"] = "à";
-$L["currency_symbol"] = "Symbole monétaire";
-$L["prefix"] = "Préfixe";
-$L["suffix"] = "Suffixe";
-$L["incomplete_fields"] = "Le type de données monétaire nécessite un format. Corrigez les lignes suivantes:";
-$L["invalid_range_fields"] = "N'entrez que des chiffres dans les champs 'Fourchette' (0-9 et séparateur décimal). Corrigez les lignes suivantes:";
-$L["invalid_range"] = "Veuillez vérifier la gamme va du petit au grand. Corrigez les lignes suivantes:";
\ No newline at end of file
diff --git a/plugins/dataTypes/Currency/lang/nl.php b/plugins/dataTypes/Currency/lang/nl.php
deleted file mode 100644
index 5f8ee7b47..000000000
--- a/plugins/dataTypes/Currency/lang/nl.php
+++ /dev/null
@@ -1,26 +0,0 @@
-X's worden omgezet in een getal: alle andere waarden worden gelaten voor wat het is.";
-$L["range_from"] = "Range - Van";
-$L["range_from_desc"] = "Geeft de ondergrens van welke waarden u wilt genereren. Opmerking: dit veld mag alleen nummers bevatten en (als je wilt) een decimale punt en twee volgende nummers te cent / pence / etc vertegenwoordigen.";
-$L["range_to"] = "Range - Om";
-$L["range_to_desc"] = "De bovengrens van de getallen te genereren.";
-$L["currency_symbol"] = "Valutasymbool";
-$L["currency_symbol_desc"] = "Ongeacht valuta symbool dat u wilt gebruiken, bijvoorbeeld $ , € , enz.";
-$L["prefix_suffix"] = "Prefix/Suffix";
-$L["prefix_suffix_desc"] = "Dit bepaalt waar de valuta symbool moet verschijnen.";
-$L["no_cents"] = "geen cent";
-$L["no_thousand_delimiters"] = "geen duizend scheidingsteken";
-$L["no_dollar_sign"] = "geen dollarteken";
-$L["range"] = "reeks";
-$L["to"] = "naar";
-$L["currency_symbol"] = "Valutasymbool";
-$L["prefix"] = "voorvoegsel";
-$L["suffix"] = "achtervoegsel";
-$L["incomplete_fields"] = "Het gegevenstype Valuta dient te hebben het formaat ingevoerd. Please fix de volgende regels:";
-$L["invalid_range_fields"] = "Gelieve enkel getallen in te voeren in de Valuta range velden (0-9 en decimale punt). Please fix de volgende regels:";
-$L["invalid_range"] = "Controleer het bereik gaat van klein tot groot. Please fix de volgende regels: ";
\ No newline at end of file
diff --git a/plugins/dataTypes/Date/Date.class.php b/plugins/dataTypes/Date/Date.class.php
deleted file mode 100644
index f13a6baf9..000000000
--- a/plugins/dataTypes/Date/Date.class.php
+++ /dev/null
@@ -1,228 +0,0 @@
- $date
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- if (empty($postdata["dtFromDate_$colNum"]) || empty($postdata["dtToDate_$colNum"]) || empty($postdata["dtOption_$colNum"])) {
- return false;
- }
-
- $options = array(
- "formatCode" => $postdata["dtOption_$colNum"],
- "from" => $postdata["dtFromDate_$colNum"],
- "to" => $postdata["dtToDate_$colNum"]
- );
-
- return $options;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "DATETIME NULL"
- );
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $html =<<
- {$L["please_select"]}
- Jan 1, 2012
- January 1st, 2012
- Mon, Jan 01
- Mon, Jan 1st, 2012
- 03.25.06
- 03-25-06
- 03/25/06
- 03/25/2012
- 25.03.06
- 25-03-06
- 25/03/06
- 25/03/2012
- MySQL datetime
- UNIX timestamp
- ISO 8601 date
- RFC 2822 formatted date
- A timezone
-
-EOF;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $nextYear = date("m/d/Y", mktime(0, 0, 0, date("m"), date("d"), date("Y")+1));
- $lastYear = date("m/d/Y", mktime(0, 0, 0, date("m"), date("d"), date("Y")-1));
-
- $html =<<< END
- {$this->L["from"]}
- {$this->L["to"]}
-
- {$this->L["format_code"]}
-
-END;
-
- return $html;
- }
-
- public function getHelpHTML() {
- $html =<<
- {$this->L["help_intro"]}
-
-
-
-
- {$this->L["char"]}
- {$this->L["description"]}
- {$this->L["example"]}
-
-
-
-
- {$this->L["day"]}
-
-
-
-
- d
- {$this->L["help_d"]}
- {$this->L["help_d_example"]}
-
-
- D
- {$this->L["help_D"]}
- {$this->L["help_D_example"]}
-
-
- j
- {$this->L["help_j"]}
- {$this->L["help_j_example"]}
-
-
- l
- {$this->L["help_l"]}
- {$this->L["help_l_example"]}
-
-
- S
- {$this->L["help_S"]}
- {$this->L["help_S_example"]}
-
-
- w
- {$this->L["help_w"]}
- {$this->L["help_w_example"]}
-
-
- z
- {$this->L["help_z"]}
- {$this->L["help_z_example"]}
-
-
-
- {$this->L["week"]}
-
-
-
-
- W
- {$this->L["help_W"]}
- {$this->L["help_W_example"]}
-
-
-
- {$this->L["month"]}
-
-
-
-
- F
- {$this->L["help_F"]}
- {$this->L["help_F_example"]}
-
-
- m
- {$this->L["help_m"]}
- {$this->L["help_m_example"]}
-
-
- M
- {$this->L["help_M"]}
- {$this->L["help_M_example"]}
-
-
- n
- {$this->L["help_n"]}
- {$this->L["help_n_example"]}
-
-
- t
- {$this->L["help_t"]}
- {$this->L["help_t_example"]}
-
-
-
-
- {$this->L["year"]}
-
-
-
-
- L
- {$this->L["help_L"]}
- {$this->L["help_L_example"]}
-
-
- Y
- {$this->L["help_Y"]}
- {$this->L["help_Y_example"]}
-
-
- y
- {$this->L["help_y"]}
- {$this->L["help_y_example"]}
-
-
-
-
-END;
-
- return $html;
- }
-
-}
diff --git a/plugins/dataTypes/Date/Date.js b/plugins/dataTypes/Date/Date.js
deleted file mode 100755
index 5352b5c5f..000000000
--- a/plugins/dataTypes/Date/Date.js
+++ /dev/null
@@ -1,109 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name Date
- * @description JS code for the Date Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-Date";
- var LANG = L.dataTypePlugins.Date;
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.DATA_TABLE.ROW.TYPE_CHANGE] = _dataTypeChange;
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _dataTypeChange = function(msg) {
- var currYear = _getCurrentYear();
- var yearRangeFrom = (currYear - 200);
- var yearRangeTo = (currYear + 200);
- var yearRange = yearRangeFrom + ":" + yearRangeTo;
- $("#dtFromDate_" + msg.rowID).datepicker({
- showOn: "both",
- buttonImage: "resources/themes/" + C.THEME + "/images/calendarIcon.gif",
- buttonImageOnly: true,
- buttonText: "Choose date",
- changeMonth: true,
- changeYear: true,
- yearRange: yearRange
- });
- $("#dtToDate_" + msg.rowID).datepicker({
- showOn: "both",
- buttonImage: "resources/themes/" + C.THEME + "/images/calendarIcon.gif",
- buttonImageOnly: true,
- buttonText: "Choose date",
- changeMonth: true,
- changeYear: true,
- yearRange: yearRange
- });
- };
-
- var _exampleChange = function(msg) {
- $("#dtOption_" + msg.rowID).val(msg.value);
- };
-
- var _saveRow = function(rowNum) {
- return {
- "fromDate": $("#dtFromDate_" + rowNum).val(),
- "toDate": $("#dtToDate_" + rowNum).val(),
- "example": $("#dtExample_" + rowNum).val(),
- "option": $("#dtOption_" + rowNum).val()
- };
- };
-
- var _getCurrentYear = function() {
- return new Date().getFullYear();
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() { },
- isComplete: function() {
- if ($("#dtOption_" + rowNum).length > 0) {
- $("#dtFromDate_" + rowNum).val(data.fromDate);
- $("#dtToDate_" + rowNum).val(data.toDate);
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtOption_" + rowNum).val(data.option);
- return true;
- }
- return false;
- }
- };
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- saveRow: _saveRow,
- loadRow: _loadRow
- });
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/Date/lang/de.php b/plugins/dataTypes/Date/lang/de.php
deleted file mode 100644
index d88c11c04..000000000
--- a/plugins/dataTypes/Date/lang/de.php
+++ /dev/null
@@ -1,49 +0,0 @@
-words = Utils::getLipsum();
- $this->numWords = count($this->words);
- }
- }
-
- public function generate($generator, $generationContextData) {
- // prefix
- $numPrefixWords = mt_rand(1, 3);
- $offset = mt_rand(0, $this->numWords - ($numPrefixWords + 1));
- $words = array_slice($this->words, $offset, $numPrefixWords);
- $words = preg_replace("/[,.:;]/", "", $words);
- $prefix = join(".", $words);
-
- // domain
- $numDomainWords = mt_rand(1, 3);
- $offset = mt_rand(0, $this->numWords - ($numDomainWords + 1));
- $words = array_slice($this->words, $offset, $numDomainWords);
- $words = preg_replace("/[,.:;]/", "", $words);
- $domain = join("", $words);
-
- // suffix
- $validSuffixes = array("edu", "com", "org", "ca", "net", "co.uk");
- $suffix = $validSuffixes[mt_rand(0, count($validSuffixes)-1)];
-
- return array(
- "display" => "$prefix@$domain.$suffix"
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255) default NULL",
- "SQLField_Oracle" => "varchar2(255) default NULL",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-}
diff --git a/plugins/dataTypes/Email/lang/de.php b/plugins/dataTypes/Email/lang/de.php
deleted file mode 100644
index 2fd7909dc..000000000
--- a/plugins/dataTypes/Email/lang/de.php
+++ /dev/null
@@ -1,4 +0,0 @@
-generatedGUIDs)) {
- $guid = Utils::generateRandomAlphanumericStr($placeholderStr);
- }
- $this->generatedGUIDs[] = $guid;
- return array(
- "display" => $guid
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(36) NOT NULL",
- "SQLField_Oracle" => "varchar2(36) NOT NULL",
- "SQLField_MSSQL" => "UNIQUEIDENTIFIER NULL"
- );
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/GUID/lang/de.php b/plugins/dataTypes/GUID/lang/de.php
deleted file mode 100644
index c8bf91e86..000000000
--- a/plugins/dataTypes/GUID/lang/de.php
+++ /dev/null
@@ -1,6 +0,0 @@
-
- */
-
-class DataType_IBAN extends DataTypePlugin {
-
- protected $isEnabled = true;
- protected $dataTypeName = "Bank Account Numbers (IBAN)";
- protected $hasHelpDialog = true;
- protected $dataTypeFieldGroup = "human_data";
- protected $dataTypeFieldGroupOrder = 100;
-
- /**
- * Template definition
- * b : NATIONAL_BANK_CODE
- * i : BIC_CODE
- * d : BRANCH_ID
- * c : ACCOUNT_NUMBER
- * k : IBAN_CHECKSUM
- * x : NATIONAL_CHECKSUM
- * m : MOD11_CHECKSUM
- * t : ACCOUNT_TYPE
- * p : PERSONAL_NUMBER
- * n : COUNTRY_CODE
- *
- * Based on http://en.wikipedia.org/wiki/International_Bank_Account_Number#IBAN_formats_by_country
- * Corrected using various sources
- * @var array
- */
- private $allCountryCodes = array(
- array('code'=>'AL', 'sepa'=>false, 'template'=>'ALkkbbbddddxcccccccccccccccc', 'name'=>'Albania'),
- array('code'=>'AD', 'sepa'=>false, 'template'=>'ADkkbbbbddddcccccccccccc', 'name'=>'Andorra'),
- array('code'=>'AT', 'sepa'=>true, 'template'=>'ATkkbbbbbccccccccccc', 'name'=>'Austria'),
- array('code'=>'AZ', 'sepa'=>false, 'template'=>'AZkkbbbbcccccccccccccccccccc', 'name'=>'Azerbaijan'),
- array('code'=>'BE', 'sepa'=>true, 'template'=>'BEkkbbbcccccccxx', 'name'=>'Belgium'),
- array('code'=>'BH', 'sepa'=>false, 'template'=>'BHkkbbbbcccccccccccccc', 'name'=>'Bahrain'),
- array('code'=>'BA', 'sepa'=>false, 'template'=>'BAkkbbbdddccccccccxx', 'name'=>'Bosnia and Herzegovina'),
- array('code'=>'BG', 'sepa'=>true, 'template'=>'BGkkiiiiddddttcccccccc', 'name'=>'Bulgaria'),
- array('code'=>'CR', 'sepa'=>false, 'template'=>'CRkkbbbcccccccccccccc', 'name'=>'Costa Rica'),
- array('code'=>'HR', 'sepa'=>false, 'template'=>'HRkkbbbbbbbcccccccccc', 'name'=>'Croatia'),
- array('code'=>'CY', 'sepa'=>true, 'template'=>'CYkkbbbdddddcccccccccccccccc', 'name'=>'Cyprus'),
- array('code'=>'CZ', 'sepa'=>true, 'template'=>'CZkkbbbbddddddcccccccccc', 'name'=>'Czech Republic'),
- array('code'=>'DK', 'sepa'=>true, 'template'=>'DKkkbbbbcccccccccc', 'name'=>'Denmark'),
- array('code'=>'DO', 'sepa'=>false, 'template'=>'DOkkbbbbcccccccccccccccccccc', 'name'=>'Dominican Republic'),
- array('code'=>'EE', 'sepa'=>true, 'template'=>'EEkkbbddcccccccccccx', 'name'=>'Estonia'),
- array('code'=>'FO', 'sepa'=>false, 'template'=>'FOkkbbbbcccccccccx', 'name'=>'Faroe Islands'),
- array('code'=>'FI', 'sepa'=>true, 'template'=>'FIkkbbbbbbcccccccx', 'name'=>'Finland'),
- array('code'=>'FR', 'sepa'=>true, 'template'=>'FRkkbbbbbdddddcccccccccccxx', 'name'=>'France'),
- array('code'=>'GE', 'sepa'=>false, 'template'=>'GEkkbbcccccccccccccccc', 'name'=>'Georgia'),
- array('code'=>'DE', 'sepa'=>true, 'template'=>'DEkkbbbbbbbbcccccccccc', 'name'=>'Germany'),
- array('code'=>'GI', 'sepa'=>false, 'template'=>'GIkkiiiiccccccccccccccc', 'name'=>'Gibraltar'),
- array('code'=>'GR', 'sepa'=>true, 'template'=>'GRkkbbbddddcccccccccccccccc', 'name'=>'Greece'),
- array('code'=>'GL', 'sepa'=>false, 'template'=>'GLkkbbbbcccccccccc', 'name'=>'Greenland'),
- array('code'=>'GT', 'sepa'=>false, 'template'=>'GTkkbbbbcccccccccccccccccccc', 'name'=>'Guatemala'),
- array('code'=>'HU', 'sepa'=>true, 'template'=>'HUkkbbbddddxcccccccccccccccx', 'name'=>'Hungary'),
- array('code'=>'IS', 'sepa'=>true, 'template'=>'ISkkbbbbddccccccpppppppppp', 'name'=>'Iceland'),
- array('code'=>'IE', 'sepa'=>true, 'template'=>'IEkkiiiibbbbbbcccccccc', 'name'=>'Ireland'),
- array('code'=>'IL', 'sepa'=>false, 'template'=>'ILkkbbbnnnccccccccccccc', 'name'=>'Israel'),
- array('code'=>'IT', 'sepa'=>true, 'template'=>'ITkkxiiiiibbbbbcccccccccccc', 'name'=>'Italy'),
- array('code'=>'KZ', 'sepa'=>false, 'template'=>'KZkkbbbccccccccccccc', 'name'=>'Kazakhstan'),
- array('code'=>'KW', 'sepa'=>false, 'template'=>'KWkkbbbbcccccccccccccccccccccc', 'name'=>'Kuwait'),
- array('code'=>'LV', 'sepa'=>true, 'template'=>'LVkkiiiiccccccccccccc', 'name'=>'Latvia'),
- array('code'=>'LB', 'sepa'=>false, 'template'=>'LBkkbbbbcccccccccccccccccccc', 'name'=>'Lebanon'),
- array('code'=>'LI', 'sepa'=>true, 'template'=>'LIkkbbbbbcccccccccccc', 'name'=>'Liechtenstein'),
- array('code'=>'LT', 'sepa'=>true, 'template'=>'LTkkbbbbbccccccccccc', 'name'=>'Lithuania'),
- array('code'=>'LU', 'sepa'=>true, 'template'=>'LUkkbbbccccccccccccc', 'name'=>'Luxembourg'),
- array('code'=>'MK', 'sepa'=>false, 'template'=>'MKkkbbbccccccccccxx', 'name'=>'Macedonia'),
- array('code'=>'MT', 'sepa'=>true, 'template'=>'MTkkiiiidddddcccccccccccccccccc', 'name'=>'Malta'),
- array('code'=>'MR', 'sepa'=>false, 'template'=>'MRkkbbbbbdddddcccccccccccxx', 'name'=>'Mauritania'),
- array('code'=>'MU', 'sepa'=>false, 'template'=>'MUkkbbbbbbddcccccccccccccccccc', 'name'=>'Mauritius'),
- array('code'=>'MC', 'sepa'=>true, 'template'=>'MCkkbbbbbdddddcccccccccccxx', 'name'=>'Monaco'),
- array('code'=>'MD', 'sepa'=>false, 'template'=>'MDkkbbcccccccccccccccccc', 'name'=>'Moldova'),
- array('code'=>'ME', 'sepa'=>false, 'template'=>'MEkkbbbcccccccccccccxx', 'name'=>'Montenegro'),
- array('code'=>'NL', 'sepa'=>true, 'template'=>'NLkkiiiicccccccccc', 'name'=>'Netherlands'),
- array('code'=>'NO', 'sepa'=>true, 'template'=>'NOkkbbbbccccccx', 'name'=>'Norway'),
- array('code'=>'PK', 'sepa'=>false, 'template'=>'PKkkbbbbcccccccccccccccc', 'name'=>'Pakistan'),
- array('code'=>'PS', 'sepa'=>false, 'template'=>'PSkkbbbbxxxxxxxxxcccccccccccc', 'name'=>'Palestinian Territory, Occupied'),
- array('code'=>'PL', 'sepa'=>true, 'template'=>'PLkkbbbddddxcccccccccccccccc', 'name'=>'Poland'),
- array('code'=>'PT', 'sepa'=>true, 'template'=>'PTkkbbbbddddcccccccccccxx', 'name'=>'Portugal'),
- array('code'=>'RO', 'sepa'=>true, 'template'=>'ROkkiiiicccccccccccccccc', 'name'=>'Romania'),
- array('code'=>'SM', 'sepa'=>false, 'template'=>'SMkkxbbbbbdddddcccccccccccc', 'name'=>'San Marino'),
- array('code'=>'SA', 'sepa'=>false, 'template'=>'SAkkbbcccccccccccccccccc', 'name'=>'Saudi Arabia'),
- array('code'=>'RS', 'sepa'=>false, 'template'=>'RSkkbbbcccccccccccccxx', 'name'=>'Serbia'),
- array('code'=>'SK', 'sepa'=>true, 'template'=>'SKkkbbbbddddddcccccccccc', 'name'=>'Slovakia'),
- array('code'=>'SI', 'sepa'=>true, 'template'=>'SIkkbbdddccccccccxx', 'name'=>'Slovenia'),
- array('code'=>'ES', 'sepa'=>true, 'template'=>'ESkkbbbbddddxxcccccccccc', 'name'=>'Spain'),
- array('code'=>'SE', 'sepa'=>true, 'template'=>'SEkkbbbccccccccccccccccx', 'name'=>'Sweden'),
- array('code'=>'CH', 'sepa'=>true, 'template'=>'CHkkbbbbbcccccccccccc', 'name'=>'Switzerland'),
- array('code'=>'TN', 'sepa'=>false, 'template'=>'TNkkbbdddccccccccccccccc', 'name'=>'Tunisia'),
- array('code'=>'TR', 'sepa'=>false, 'template'=>'TRkkbbbbbxcccccccccccccccc', 'name'=>'Turkey'),
- array('code'=>'AE', 'sepa'=>false, 'template'=>'AEkkbbbcccccccccccccccc', 'name'=>'United Arab Emirates'),
- array('code'=>'GB', 'sepa'=>true, 'template'=>'GBkkiiiiddddddcccccccc', 'name'=>'United Kingdom'),
- array('code'=>'VG', 'sepa'=>false, 'template'=>'VGkkbbbbcccccccccccccccc', 'name'=>'Virgin Islands, British'),
- );
-
- public $countryCodes;
-
-
- /**
- * @todo make the $onlySepa option variable
- * @param string $runtimeContext
- */
- public function __construct($runtimeContext) {
- parent::__construct($runtimeContext);
- $onlySepa = false;
- if ($runtimeContext == "generation") {
- foreach ($this->allCountryCodes as $details) {
- if (!$onlySepa || $details['sepa']) {
- $this->countryCodes[] = $details;
- }
- }
- }
- }
-
- public static function GenerateBic($countryCode) {
- $withBranchCode = mt_rand(0,1) == true;
- $branchCode = $withBranchCode ? 'xxX' : '';
- $format = 'LLLL'.$countryCode.'LL'.$branchCode;
-
- return Utils::generateRandomAlphanumericStr($format);
- }
-
- private static function FillTemplate($template, $countryCode) {
- $bic = self::GenerateBic($countryCode);
- $bicPos = 0;
- $len = strlen($template);
- $unsigned = '';
- for ($i=0; $i<$len; $i++) {
- $c = $template[$i];
- if (strtoupper($c) === $c) {
- $unsigned .= $c;
- continue;
- }
- if ($c === 'i') {
- $unsigned .= $bic{$bicPos++};
- continue;
- }
- if ($c === 'k') {
- $unsigned .= '_';
- continue;
- }
- $unsigned .= Utils::generateRandomAlphanumericStr('x');
- }
- return self::RecalculateChecksum($unsigned);
- }
-
- public function getRandomCountry() {
- return $this->countryCodes[mt_rand(0, count($this->countryCodes)-1)];
- }
-
- /**
- * @todo Respect the selected country.
- */
- public function generate($generator, $generationContextData) {
- $code = $this->getRandomCountry();
- $IBAN = self::FillTemplate($code['template'], $code['code']);
- return array(
- "display" => $IBAN
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(34)",
- "SQLField_Oracle" => "varchar2(34)",
- "SQLField_MSSQL" => "VARCHAR(34) NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-
- private static function Chr2Int($chr) {
- if (strlen($chr) != 1) {
- throw new Exception("Requires a single character");
- }
- $ord = ord($chr);
-
- if ($ord <=57 && $ord >=48) { //48 = '0', 57 = '9'
- return $ord-48;
- }
- if ($ord <= 90 && $ord >= 65) { //90 = 'Z', 65 = 'A'
- return 10 + ($ord - 65);
- }
- throw new Exception("Input character {$chr}({$ord}) does not map to an integer");
-
- }
-
- private static function BigMod( $x, $y ) {
- // how many numbers to take at once? carefull not to exceed (int)
- $take = 5;
- $mod = '';
-
- do {
- $a = intval($mod.substr($x, 0, $take));
- $x = substr( $x, $take );
- $mod = $a % $y;
- } while (strlen($x));
-
- return intval($mod);
- }
- /**
- * Removes the current checksum digits from an IBAN string, and replaces it with what it should have been.
- */
- private static function RecalculateChecksum($ibanString) {
- if (strlen($ibanString) < 6) {
- return $ibanString;
- }
-
- $reordered = substr($ibanString, 4).substr($ibanString, 0, 2);
- $numerical = '';
- for ($i=0; $i
- Die erzeugte IBAN hat eine gültige Prüfsumme, Landes-und Länge und die BIC ist an der richtigen Stelle.
- Die Zahl ist höchst unwahrscheinlich, dass wirklich "gültig" tho sein, da in der Regel eine Reihe von Kontrollen zu tun, die länderspezifisch sind.
-EOT;
-//meh. the page crashes on newlines in my help? odd.
-$L["help"] = str_replace(array("\r","\n","\t"), " ", $L["help"]);
\ No newline at end of file
diff --git a/plugins/dataTypes/IBAN/lang/en.php b/plugins/dataTypes/IBAN/lang/en.php
deleted file mode 100755
index c51d5e8ae..000000000
--- a/plugins/dataTypes/IBAN/lang/en.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
- The generated IBAN has a valid checksum, countrycode and length and the BIC is in the right place.
- The number is highly unlikely to be really "valid" tho, since there are usually a bunch of checks to do which are country specific.
-EOT;
-//meh. the page crashes on newlines in my help? odd.
-$L["help"] = str_replace(array("\r","\n","\t"), " ", $L["help"]);
\ No newline at end of file
diff --git a/plugins/dataTypes/IBAN/lang/es.php b/plugins/dataTypes/IBAN/lang/es.php
deleted file mode 100644
index c3ca1a1c1..000000000
--- a/plugins/dataTypes/IBAN/lang/es.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
- El IBAN generado tiene una suma de comprobación válida, código del país, la longitud, el BIC se encuentra en el lugar correcto.
- El número es muy poco probable que sea verdad aunque "válida", ya que por lo general hay un montón de cheques que hacer, que son específicos de cada país.
-EOT;
-//meh. the page crashes on newlines in my help? odd.
-$L["help"] = str_replace(array("\r","\n","\t"), " ", $L["help"]);
\ No newline at end of file
diff --git a/plugins/dataTypes/IBAN/lang/fr.php b/plugins/dataTypes/IBAN/lang/fr.php
deleted file mode 100644
index 39cdd03ff..000000000
--- a/plugins/dataTypes/IBAN/lang/fr.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
- L'IBAN a généré une somme de contrôle valide, countrycode la longueur et le BIC est au bon endroit.
- Le nombre est hautement improbable qu'il soit vraiment tho "valide", car il ya généralement un tas de vérifications à faire qui sont spécifiques des pays.
-EOT;
-//meh. the page crashes on newlines in my help? odd.
-$L["help"] = str_replace(array("\r","\n","\t"), " ", $L["help"]);
\ No newline at end of file
diff --git a/plugins/dataTypes/IBAN/lang/nl.php b/plugins/dataTypes/IBAN/lang/nl.php
deleted file mode 100644
index 4ae790b2d..000000000
--- a/plugins/dataTypes/IBAN/lang/nl.php
+++ /dev/null
@@ -1,11 +0,0 @@
-
- De gegenereerde IBAN heeft een geldige checksum, landcode en de lengte en de BIC is op de juiste plaats.
- Het nummer is zeer onwaarschijnlijk om echt "geldig" tho zijn, aangezien er meestal een heleboel controles te doen, tegen specifieke landen.
-EOT;
-//meh. the page crashes on newlines in my help? odd.
-$L["help"] = str_replace(array("\r","\n","\t"), " ", $L["help"]);
\ No newline at end of file
diff --git a/plugins/dataTypes/LatLng/LatLng.class.php b/plugins/dataTypes/LatLng/LatLng.class.php
deleted file mode 100644
index 22290544e..000000000
--- a/plugins/dataTypes/LatLng/LatLng.class.php
+++ /dev/null
@@ -1,96 +0,0 @@
-cachedMath = array();
- private $helpDialogWidth = 410;
- private $cachedMath;
-
-
- public function __construct($runtimeContext) {
- parent::__construct($runtimeContext);
- if ($runtimeContext == "generation") {
- self::initVars();
- }
- }
-
- /**
- * Valid ranges:
- * Lat: -90 -> + 90
- * Lng: -180 -> +180
- */
- public function generate($generator, $generationContextData) {
- $options = $generationContextData["generationOptions"];
-
- $info = array();
- if ($options["lat"] && $options["lng"]) {
- $info[] = (mt_rand($this->cachedMath["minLatCalc"], $this->cachedMath["maxLatCalc"]) / $this->cachedMath["divisor"]);
- $info[] = (mt_rand($this->cachedMath["minLngCalc"], $this->cachedMath["maxLngCalc"]) / $this->cachedMath["divisor"]);
- } else if ($options["lat"]) {
- $info[] = (mt_rand($this->cachedMath["minLatCalc"], $this->cachedMath["maxLatCalc"]) / $this->cachedMath["divisor"]);
- } else if ($options["lng"]) {
- $info[] = (mt_rand($this->cachedMath["minLngCalc"], $this->cachedMath["maxLngCalc"]) / $this->cachedMath["divisor"]);
- }
-
- return array(
- "display" => join(", ", $info)
- );
- }
-
-
- public function getRowGenerationOptions($generator, $postdata, $column, $numCols) {
- if (!isset($postdata["dtLatLng_Lat$column"]) && empty($postdata["dtLatLng_Lng$column"])) {
- return false;
- }
- $options = array(
- "lat" => isset($postdata["dtLatLng_Lat$column"]) ? true : false,
- "lng" => isset($postdata["dtLatLng_Lng$column"]) ? true : false
- );
- return $options;
- }
-
-
- public function getOptionsColumnHTML() {
- $html =<<< END
-
- {$this->L["latitude"]}
-
- {$this->L["longitude"]}
-END;
- return $html;
- }
-
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(30) default NULL",
- "SQLField_Oracle" => "varchar2(30) default NULL",
- "SQLField_MSSQL" => "VARCHAR(30) NULL"
- );
- }
-
- private function initVars() {
- // to 5 D.P. Arbitrary - should be configurable, but it should be good enough for most cases
- $decimalPlaces = 5;
- $this->cachedMath = array(
- "minLatCalc" => -90 * (pow(10, $decimalPlaces)),
- "maxLatCalc" => 90 * (pow(10, $decimalPlaces)),
- "minLngCalc" => -180 * (pow(10, $decimalPlaces)),
- "maxLngCalc" => 180 * (pow(10, $decimalPlaces)),
- "divisor" => pow(10, $decimalPlaces)
- );
- }
-}
diff --git a/plugins/dataTypes/LatLng/LatLng.js b/plugins/dataTypes/LatLng/LatLng.js
deleted file mode 100755
index e3023a699..000000000
--- a/plugins/dataTypes/LatLng/LatLng.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang"
-], function(manager, C, L) {
-
- "use strict";
-
- /**
- * @name LatLng
- * @description JS code for the LatLng Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-LatLng";
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {},
- isComplete: function() {
- if ($("#dtLatLng_Lng" + rowNum).length) {
- if (data.lat) {
- $("#dtLatLng_Lat" + rowNum).attr("checked", "checked");
- } else {
- $("#dtLatLng_Lat" + rowNum).removeAttr("checked");
- }
- if (data.lng) {
- $("#dtLatLng_Lng" + rowNum).attr("checked", "checked");
- } else {
- $("#dtLatLng_Lng" + rowNum).removeAttr("checked");
- }
- return true;
- } else {
- return false;
- }
- }
- };
- };
-
- var _saveRow = function(rowNum) {
- return {
- "lat": ($("#dtLatLng_Lat" + rowNum).attr("checked")) ? "checked" : "",
- "lng": ($("#dtLatLng_Lng" + rowNum).attr("checked")) ? "checked" : ""
- };
- };
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/LatLng/lang/de.php b/plugins/dataTypes/LatLng/lang/de.php
deleted file mode 100644
index 8995b5360..000000000
--- a/plugins/dataTypes/LatLng/lang/de.php
+++ /dev/null
@@ -1,8 +0,0 @@
- $val
- );
- }
-
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- if (empty($postdata["dtOption_$colNum"])) {
- return false;
- }
-
- $listType = $postdata["dtListType_$colNum"]; // Exactly or AtMost
- $number = ($listType == "Exactly") ? $postdata["dtListExactly_$colNum"] : $postdata["dtListAtMost_$colNum"];
- $options = array(
- "listType" => $listType,
- "number" => $number,
- "values" => $postdata["dtOption_$colNum"]
- );
-
- return $options;
- }
-
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
- $html =<<< END
-
- {$L["please_select"]}
- {$this->L["example_1"]}
- {$this->L["example_2"]}
- 1-10
- {$this->L["example_3"]}
- {$this->L["example_4"]}
- {$this->L["example_5"]}
- {$this->L["example_6"]}
- {$this->L["example_7"]}
- {$this->L["example_8"]}
- {$this->L["example_9"]}
- {$this->L["example_10"]}
-
-
- {$this->L["separated_by_pipe"]}
-
-END;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- $html =<<< END
-
-
- {$this->L["exactly"]}
-
-
- {$this->L["at_most"]}
-
-
-
-
-
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255) default NULL",
- "SQLField_Oracle" => "varchar2(255) default NULL",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-}
diff --git a/plugins/dataTypes/List/List.js b/plugins/dataTypes/List/List.js
deleted file mode 100644
index 0f3a1f19b..000000000
--- a/plugins/dataTypes/List/List.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name List
- * @description JS code for the List Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-List";
- var LANG = L.dataTypePlugins.List;
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _saveRow = function(rowNum) {
- return {
- example: $("#dtExample_" + rowNum).val(),
- listType1: $("#dtListType1_" + rowNum).attr("checked"),
- listType2: $("#dtListType2_" + rowNum).attr("checked"),
- exactly: $("#dtListExactly_" + rowNum).val(),
- atMost: $("#dtListAtMost_" + rowNum).val(),
- option: $("#dtOption_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtListType1_" + rowNum).attr("checked", data.listType1);
- $("#dtListType2_" + rowNum).attr("checked", data.listType2);
- $("#dtListExactly_" + rowNum).val(data.exactly);
- $("#dtListAtMost_" + rowNum).val(data.atMost);
- $("#dtOption_" + rowNum).val(data.option);
- },
- isComplete: function() {
- return $("#dtOption_" + rowNum).length > 0;
- }
- };
- };
-
- /**
- * Confirm the "Exactly" and "At Most" fields are all integers, and the Options field has been filled in.
- *
- */
- var _validate = function(rows) {
- var missingOptions = {
- fields: [],
- visibleProblemRows: []
- };
- var invalidIntFields = {
- fields: [],
- visibleProblemRows: []
- };
-
- var intOnly = /^\d+$/;
- for (var i=0; i" + missingOptions.visibleProblemRows.join(", ") + ""});
- }
- if (invalidIntFields.visibleProblemRows.length) {
- errors.push({ els: invalidIntFields.fields, error: LANG.invalid_int_fields + " " + invalidIntFields.visibleProblemRows.join(", ") + " "});
- }
- return errors;
- };
-
- var _exampleChange = function(msg) {
- $("#dtOption_" + msg.rowID).val(msg.value);
- };
-
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/List/lang/de.php b/plugins/dataTypes/List/lang/de.php
deleted file mode 100644
index 43f73ada5..000000000
--- a/plugins/dataTypes/List/lang/de.php
+++ /dev/null
@@ -1,28 +0,0 @@
-X Anzahl der Elemente oder Allenfalls X Elemente aus der Liste. Mehrere Artikel werden in einer durch Komma getrennte Liste in den Ergebnissen zurückgegeben. Wenn Sie möchten, können Sie Ihre Daten auf leere Werte sind, fügen Sie einfach ein oder mehrere Pipe-Zeichen am Ende - je mehr Rohre Sie eingeben, desto größer ist die Wahrscheinlichkeit, dass ein leerer Wert erzeugt wird.";
-$L["incomplete_fields"] = "Die Benutzerdefinierte Liste Datentyp haben muss das Format eingetragen im Options Textfeld. Bitte beheben Sie die folgenden Zeilen:";
-$L["invalid_int_fields"] = "Bitte geben Sie nur Zahlen in der \"Genau\" und \"höchstens\" Felder für die Benutzerdefinierte Liste Datentyp. Bitte beheben Sie die folgenden Zeilen:";
-$L["one_to_ten"] = "eins | zwei | drei | vier | five | sechs | sieben | acht | neun | zehn";
-$L["prefix"] = "Dr. | Herr | Frau | Frau |";
-$L["relationship_states"] = "Single | Verheiratet | Geschieden | Common-Law";
-$L["separated_by_pipe"] = "Geben Sie die Werte getrennt durch |";
-$L["drug_names"] = "Hydrocodone/APAP|Hydrocodone/APAP|LevothyroxineSodium|Lisinopril|Lipitor|Simvastatin|Plavix|Singulair|Azithromycin|Crestor|Nexium|LevothyroxineSodium|Metoprolol Tartrate |Hydrocodone/APAP|Synthroid|Lexapro|Proair HFA|Ibuprofen (Rx)|Trazodone HCl|Amoxicillin|Omeprazole (Rx)|Advair Diskus|Cymbalta|Azithromycin|Tramadol HCl|Omeprazole (Rx)|Amoxicillin|Sulfamethoxazole/Trimethoprim|Amlodipine Besylate|Fluticasone Propionate|Hydrochlorothiazide|Hydrochlorothiazide|Ventolin HFA|Diovan|Warfarin Sodium|Sertraline HCl|Alprazolam|Pravastatin Sodium|Simvastatin|Metoprolol Succinate|Lisinopril/Hydrochlorothiazide|Seroquel|Promethazine HCl|Metoprolol Succinate|Amoxicillin Trihydrate/Potassium Clavulanate|Simvastatin|Amlodipine Besylate|Oxycodone/APAP|Warfarin Sodium|Zolpidem Tartrate|Metformin HCl|Prednisone|Metformin HCl|Fluconazole|Clonazepam|Omeprazole (Rx)|Diovan HCT|Simvastatin|Actos|Sertraline HCl|Gabapentin|Lantus|Amlodipine Besylate|Vitamin D (Rx)|Fluoxetine HCl|Furosemide|Citalopram HBr|Alprazolam|Ciprofloxacin HCl|Atenolol|Meloxicam|Lorazepam|Prednisone|Celebrex|Venlafaxine HCl ER|Lisinopril|Lyrica|Nasonex|Spiriva Handihaler|Furosemide|Cyclobenzaprin HCl|Cephalexin|Abilify|Citalopram HBR|Cyclobenzaprin HCl|Carvedilol|Metformin HCl|Potassium Chloride|Viagra|Amlodipine Besylate|Furosemide|Ibuprofen (Rx)|Azithromycin|Vyvanse|Zetia|Albuterol|Zolpidem Tartrate|Tramadol HCl|Prednisone|Simvastatin|Clonazepam|Namenda|Januvia|Tricor|Diazepam|Simvastatin|Lisinopril|Lisinopril|Loestrin 24 Fe|Metformin HCl|Furosemide|Prednisone|Allopurinol|Amoxicillin Trihydrate/Potassium Clavulanate|Cialis|Alendronate Sodium|Losartan Potassium|Triamterene/Hydrochlorothiazide|Sertraline HCl|Amitriptyline HCl|Oxycodone HCl|Alprazolam|Alprazolam|Fluticasone Propionate|Fluticasone Propionate|Simvastatin|Triamterene/Hydrochlorothiazide|Suboxone|Oxycontin|Niaspan|Lorazepam|Doxycycline Hyclate|Amoxicillin|Digoxin|Potassium Chloride|Vytorin|Lantus Solostar|APAP/Codeine|Hydrocodone/APAP|Triamcinolone Acetonide|Atenolol|Premarin|Paroxetine HCl|Metoprolol Succinatee|Fluoxetine HCl|Flovent HFA|Promethazine HCl|Carisoprodol|Klor-Con M20|Lorazepam|Benicar|Lovastatin|Lisinopril|Amphetamine Salts|Ciprofloxacin HCl|Carvedilol|Risperidone|Levoxyl|Atenolol|Naproxen|Lovaza|Lisinopril|Alprazolam|Atenolol|Bystolic|Penicillin VK|Famotidine|Methylprednisolone|Gabapentin|Nuvaring|Alendronate Sodium|Amoxicillin|Cephalexin|Glyburide|Clindamycin HCl|Folic Acid|Enalapril Maleate|Tamsulosin HCl|Cheratussin AC|Prednisone|Benicar HCT|Gabapentin|Levaquin|TriNessa|Ranitidine HCl|Levothyroxine Sodium|Glipizide|Doxycycline Hyclate|Tri-Sprintec|Amlodipine Besylate|Symbicort|Lidoderm|Pantoprazole Sodium|Pantoprazole Sodium|Zyprexa|Endocet|Zolpidem Tartrate|Gianvi|Lisinopril|Diazepam";
diff --git a/plugins/dataTypes/List/lang/en.php b/plugins/dataTypes/List/lang/en.php
deleted file mode 100644
index 82a6e12f9..000000000
--- a/plugins/dataTypes/List/lang/en.php
+++ /dev/null
@@ -1,28 +0,0 @@
-Exactly X number of items, or At most X items from the list. Multiple items are returned in a comma-delimited list in the results. If you want your data set to include empty values, just add one or more pipe characters at the end - the more pipes you enter, the greater the probability of an empty value being generated.";
-$L["one_to_ten"] = "one|two|three|four|five|six|seven|eight|nine|ten";
-$L["prefix"] = "Dr.|Mr.|Mrs.|Ms.|";
-$L["relationship_states"] = "Single|Married|Divorced|Common-Law";
-$L["separated_by_pipe"] = "Enter values separated by |";
-$L["incomplete_fields"] = "The Custom List Data Type needs to have the format entered in the Options text field. Please fix the following rows:";
-$L["invalid_int_fields"] = "Please only enter numbers in the \"Exactly\" and \"At Most\" fields for the Custom List Data Type. Please fix the following rows:";
-$L["drug_names"] = "Hydrocodone/APAP|Hydrocodone/APAP|LevothyroxineSodium|Lisinopril|Lipitor|Simvastatin|Plavix|Singulair|Azithromycin|Crestor|Nexium|LevothyroxineSodium|Metoprolol Tartrate |Hydrocodone/APAP|Synthroid|Lexapro|Proair HFA|Ibuprofen (Rx)|Trazodone HCl|Amoxicillin|Omeprazole (Rx)|Advair Diskus|Cymbalta|Azithromycin|Tramadol HCl|Omeprazole (Rx)|Amoxicillin|Sulfamethoxazole/Trimethoprim|Amlodipine Besylate|Fluticasone Propionate|Hydrochlorothiazide|Hydrochlorothiazide|Ventolin HFA|Diovan|Warfarin Sodium|Sertraline HCl|Alprazolam|Pravastatin Sodium|Simvastatin|Metoprolol Succinate|Lisinopril/Hydrochlorothiazide|Seroquel|Promethazine HCl|Metoprolol Succinate|Amoxicillin Trihydrate/Potassium Clavulanate|Simvastatin|Amlodipine Besylate|Oxycodone/APAP|Warfarin Sodium|Zolpidem Tartrate|Metformin HCl|Prednisone|Metformin HCl|Fluconazole|Clonazepam|Omeprazole (Rx)|Diovan HCT|Simvastatin|Actos|Sertraline HCl|Gabapentin|Lantus|Amlodipine Besylate|Vitamin D (Rx)|Fluoxetine HCl|Furosemide|Citalopram HBr|Alprazolam|Ciprofloxacin HCl|Atenolol|Meloxicam|Lorazepam|Prednisone|Celebrex|Venlafaxine HCl ER|Lisinopril|Lyrica|Nasonex|Spiriva Handihaler|Furosemide|Cyclobenzaprin HCl|Cephalexin|Abilify|Citalopram HBR|Cyclobenzaprin HCl|Carvedilol|Metformin HCl|Potassium Chloride|Viagra|Amlodipine Besylate|Furosemide|Ibuprofen (Rx)|Azithromycin|Vyvanse|Zetia|Albuterol|Zolpidem Tartrate|Tramadol HCl|Prednisone|Simvastatin|Clonazepam|Namenda|Januvia|Tricor|Diazepam|Simvastatin|Lisinopril|Lisinopril|Loestrin 24 Fe|Metformin HCl|Furosemide|Prednisone|Allopurinol|Amoxicillin Trihydrate/Potassium Clavulanate|Cialis|Alendronate Sodium|Losartan Potassium|Triamterene/Hydrochlorothiazide|Sertraline HCl|Amitriptyline HCl|Oxycodone HCl|Alprazolam|Alprazolam|Fluticasone Propionate|Fluticasone Propionate|Simvastatin|Triamterene/Hydrochlorothiazide|Suboxone|Oxycontin|Niaspan|Lorazepam|Doxycycline Hyclate|Amoxicillin|Digoxin|Potassium Chloride|Vytorin|Lantus Solostar|APAP/Codeine|Hydrocodone/APAP|Triamcinolone Acetonide|Atenolol|Premarin|Paroxetine HCl|Metoprolol Succinatee|Fluoxetine HCl|Flovent HFA|Promethazine HCl|Carisoprodol|Klor-Con M20|Lorazepam|Benicar|Lovastatin|Lisinopril|Amphetamine Salts|Ciprofloxacin HCl|Carvedilol|Risperidone|Levoxyl|Atenolol|Naproxen|Lovaza|Lisinopril|Alprazolam|Atenolol|Bystolic|Penicillin VK|Famotidine|Methylprednisolone|Gabapentin|Nuvaring|Alendronate Sodium|Amoxicillin|Cephalexin|Glyburide|Clindamycin HCl|Folic Acid|Enalapril Maleate|Tamsulosin HCl|Cheratussin AC|Prednisone|Benicar HCT|Gabapentin|Levaquin|TriNessa|Ranitidine HCl|Levothyroxine Sodium|Glipizide|Doxycycline Hyclate|Tri-Sprintec|Amlodipine Besylate|Symbicort|Lidoderm|Pantoprazole Sodium|Pantoprazole Sodium|Zyprexa|Endocet|Zolpidem Tartrate|Gianvi|Lisinopril|Diazepam";
diff --git a/plugins/dataTypes/List/lang/es.php b/plugins/dataTypes/List/lang/es.php
deleted file mode 100644
index bfaa89b67..000000000
--- a/plugins/dataTypes/List/lang/es.php
+++ /dev/null
@@ -1,28 +0,0 @@
-Exactamente X número de ítems o Al menos X ítems de la lista. Se devolverán múltiples ítems separados por coma en el resultado. Si quiere que su conjunto de datos incluya valores vacíos, basta con añadir una o más caracteres tubería al final. Cuantas más tuberías incluya, más probabilidad tendrá de que se genere un valor vacío.";
-$L["one_to_ten"] = "uno|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez";
-$L["prefix"] = "Dr.|Sr.|Sra.|Srta.|";
-$L["relationship_states"] = "Soltero|Casado|Divorciado|Pareja de hecho|Viudo";
-$L["separated_by_pipe"] = "Introduzca valores separados por |";
-$L["incomplete_fields"] = "El tipo de dato Lista personalizada requiere que se informe el campo de texto Opciones con el formato. Por favor, arregle las siguientes filas:";
-$L["invalid_int_fields"] = "Por favor, introduzca sólo números en los campos \"Exactamente\" y \"Al Menos\" del tipo de dato Lista personalizada. Por favor, arregle las siguientes filas:";
-$L["drug_names"] = "Hydrocodone/APAP|Hydrocodone/APAP|LevothyroxineSodium|Lisinopril|Lipitor|Simvastatin|Plavix|Singulair|Azithromycin|Crestor|Nexium|LevothyroxineSodium|Metoprolol Tartrate |Hydrocodone/APAP|Synthroid|Lexapro|Proair HFA|Ibuprofen (Rx)|Trazodone HCl|Amoxicillin|Omeprazole (Rx)|Advair Diskus|Cymbalta|Azithromycin|Tramadol HCl|Omeprazole (Rx)|Amoxicillin|Sulfamethoxazole/Trimethoprim|Amlodipine Besylate|Fluticasone Propionate|Hydrochlorothiazide|Hydrochlorothiazide|Ventolin HFA|Diovan|Warfarin Sodium|Sertraline HCl|Alprazolam|Pravastatin Sodium|Simvastatin|Metoprolol Succinate|Lisinopril/Hydrochlorothiazide|Seroquel|Promethazine HCl|Metoprolol Succinate|Amoxicillin Trihydrate/Potassium Clavulanate|Simvastatin|Amlodipine Besylate|Oxycodone/APAP|Warfarin Sodium|Zolpidem Tartrate|Metformin HCl|Prednisone|Metformin HCl|Fluconazole|Clonazepam|Omeprazole (Rx)|Diovan HCT|Simvastatin|Actos|Sertraline HCl|Gabapentin|Lantus|Amlodipine Besylate|Vitamin D (Rx)|Fluoxetine HCl|Furosemide|Citalopram HBr|Alprazolam|Ciprofloxacin HCl|Atenolol|Meloxicam|Lorazepam|Prednisone|Celebrex|Venlafaxine HCl ER|Lisinopril|Lyrica|Nasonex|Spiriva Handihaler|Furosemide|Cyclobenzaprin HCl|Cephalexin|Abilify|Citalopram HBR|Cyclobenzaprin HCl|Carvedilol|Metformin HCl|Potassium Chloride|Viagra|Amlodipine Besylate|Furosemide|Ibuprofen (Rx)|Azithromycin|Vyvanse|Zetia|Albuterol|Zolpidem Tartrate|Tramadol HCl|Prednisone|Simvastatin|Clonazepam|Namenda|Januvia|Tricor|Diazepam|Simvastatin|Lisinopril|Lisinopril|Loestrin 24 Fe|Metformin HCl|Furosemide|Prednisone|Allopurinol|Amoxicillin Trihydrate/Potassium Clavulanate|Cialis|Alendronate Sodium|Losartan Potassium|Triamterene/Hydrochlorothiazide|Sertraline HCl|Amitriptyline HCl|Oxycodone HCl|Alprazolam|Alprazolam|Fluticasone Propionate|Fluticasone Propionate|Simvastatin|Triamterene/Hydrochlorothiazide|Suboxone|Oxycontin|Niaspan|Lorazepam|Doxycycline Hyclate|Amoxicillin|Digoxin|Potassium Chloride|Vytorin|Lantus Solostar|APAP/Codeine|Hydrocodone/APAP|Triamcinolone Acetonide|Atenolol|Premarin|Paroxetine HCl|Metoprolol Succinatee|Fluoxetine HCl|Flovent HFA|Promethazine HCl|Carisoprodol|Klor-Con M20|Lorazepam|Benicar|Lovastatin|Lisinopril|Amphetamine Salts|Ciprofloxacin HCl|Carvedilol|Risperidone|Levoxyl|Atenolol|Naproxen|Lovaza|Lisinopril|Alprazolam|Atenolol|Bystolic|Penicillin VK|Famotidine|Methylprednisolone|Gabapentin|Nuvaring|Alendronate Sodium|Amoxicillin|Cephalexin|Glyburide|Clindamycin HCl|Folic Acid|Enalapril Maleate|Tamsulosin HCl|Cheratussin AC|Prednisone|Benicar HCT|Gabapentin|Levaquin|TriNessa|Ranitidine HCl|Levothyroxine Sodium|Glipizide|Doxycycline Hyclate|Tri-Sprintec|Amlodipine Besylate|Symbicort|Lidoderm|Pantoprazole Sodium|Pantoprazole Sodium|Zyprexa|Endocet|Zolpidem Tartrate|Gianvi|Lisinopril|Diazepam";
diff --git a/plugins/dataTypes/List/lang/fr.php b/plugins/dataTypes/List/lang/fr.php
deleted file mode 100644
index 885f797bf..000000000
--- a/plugins/dataTypes/List/lang/fr.php
+++ /dev/null
@@ -1,28 +0,0 @@
-exactement X éléments (=), ou au maximum X éléments (<=) de la liste pour générer la valeur de la colonne; plusieurs éléments peuvent être retournés séparés par des virgules. Si vous voulez que vos données incluent des valeurs vides, il suffit d'ajouter un ou plusieurs caractères pipe à la fin: plus il y aura de | et plus la probabilité d'une valeur vide sera importante.";
-$L["one_to_ten"] = "un|deux|trois|quatre|cinq|six|sept|huit|neuf|dix";
-$L["prefix"] = "Dr.|M.|Mme|Mme|Mlle|";
-$L["relationship_states"] = "Célibataire|Marié|Veuf|";
-$L["separated_by_pipe"] = "Valeurs séparées par des |";
-$L["incomplete_fields"] = "La Type de données Liste doit avoir un format précisé. Corrigez les lignes suivantes:";
-$L["invalid_int_fields"] = "Entrez uniquement des numéros dans les champs \"=\" et \"<=\". Corrigez les lignes suivantes:";
-$L["drug_names"] = "Hydrocodone/APAP|Hydrocodone/APAP|LevothyroxineSodium|Lisinopril|Lipitor|Simvastatin|Plavix|Singulair|Azithromycin|Crestor|Nexium|LevothyroxineSodium|Metoprolol Tartrate |Hydrocodone/APAP|Synthroid|Lexapro|Proair HFA|Ibuprofen (Rx)|Trazodone HCl|Amoxicillin|Omeprazole (Rx)|Advair Diskus|Cymbalta|Azithromycin|Tramadol HCl|Omeprazole (Rx)|Amoxicillin|Sulfamethoxazole/Trimethoprim|Amlodipine Besylate|Fluticasone Propionate|Hydrochlorothiazide|Hydrochlorothiazide|Ventolin HFA|Diovan|Warfarin Sodium|Sertraline HCl|Alprazolam|Pravastatin Sodium|Simvastatin|Metoprolol Succinate|Lisinopril/Hydrochlorothiazide|Seroquel|Promethazine HCl|Metoprolol Succinate|Amoxicillin Trihydrate/Potassium Clavulanate|Simvastatin|Amlodipine Besylate|Oxycodone/APAP|Warfarin Sodium|Zolpidem Tartrate|Metformin HCl|Prednisone|Metformin HCl|Fluconazole|Clonazepam|Omeprazole (Rx)|Diovan HCT|Simvastatin|Actos|Sertraline HCl|Gabapentin|Lantus|Amlodipine Besylate|Vitamin D (Rx)|Fluoxetine HCl|Furosemide|Citalopram HBr|Alprazolam|Ciprofloxacin HCl|Atenolol|Meloxicam|Lorazepam|Prednisone|Celebrex|Venlafaxine HCl ER|Lisinopril|Lyrica|Nasonex|Spiriva Handihaler|Furosemide|Cyclobenzaprin HCl|Cephalexin|Abilify|Citalopram HBR|Cyclobenzaprin HCl|Carvedilol|Metformin HCl|Potassium Chloride|Viagra|Amlodipine Besylate|Furosemide|Ibuprofen (Rx)|Azithromycin|Vyvanse|Zetia|Albuterol|Zolpidem Tartrate|Tramadol HCl|Prednisone|Simvastatin|Clonazepam|Namenda|Januvia|Tricor|Diazepam|Simvastatin|Lisinopril|Lisinopril|Loestrin 24 Fe|Metformin HCl|Furosemide|Prednisone|Allopurinol|Amoxicillin Trihydrate/Potassium Clavulanate|Cialis|Alendronate Sodium|Losartan Potassium|Triamterene/Hydrochlorothiazide|Sertraline HCl|Amitriptyline HCl|Oxycodone HCl|Alprazolam|Alprazolam|Fluticasone Propionate|Fluticasone Propionate|Simvastatin|Triamterene/Hydrochlorothiazide|Suboxone|Oxycontin|Niaspan|Lorazepam|Doxycycline Hyclate|Amoxicillin|Digoxin|Potassium Chloride|Vytorin|Lantus Solostar|APAP/Codeine|Hydrocodone/APAP|Triamcinolone Acetonide|Atenolol|Premarin|Paroxetine HCl|Metoprolol Succinatee|Fluoxetine HCl|Flovent HFA|Promethazine HCl|Carisoprodol|Klor-Con M20|Lorazepam|Benicar|Lovastatin|Lisinopril|Amphetamine Salts|Ciprofloxacin HCl|Carvedilol|Risperidone|Levoxyl|Atenolol|Naproxen|Lovaza|Lisinopril|Alprazolam|Atenolol|Bystolic|Penicillin VK|Famotidine|Methylprednisolone|Gabapentin|Nuvaring|Alendronate Sodium|Amoxicillin|Cephalexin|Glyburide|Clindamycin HCl|Folic Acid|Enalapril Maleate|Tamsulosin HCl|Cheratussin AC|Prednisone|Benicar HCT|Gabapentin|Levaquin|TriNessa|Ranitidine HCl|Levothyroxine Sodium|Glipizide|Doxycycline Hyclate|Tri-Sprintec|Amlodipine Besylate|Symbicort|Lidoderm|Pantoprazole Sodium|Pantoprazole Sodium|Zyprexa|Endocet|Zolpidem Tartrate|Gianvi|Lisinopril|Diazepam";
diff --git a/plugins/dataTypes/List/lang/nl.php b/plugins/dataTypes/List/lang/nl.php
deleted file mode 100644
index f799ad6d6..000000000
--- a/plugins/dataTypes/List/lang/nl.php
+++ /dev/null
@@ -1,28 +0,0 @@
-Precies X aantal items, of Bij de meeste X items uit de lijst. Meerdere items worden geretourneerd in een door komma's gescheiden lijst in de resultaten. Als u wilt dat uw gegevens op lege waarden zijn, voeg dan een of meer pipe tekens aan het eind - de meer buizen die u invoert, des te groter de kans op een lege waarde wordt gegenereerd.";
-$L["incomplete_fields"] = "De Aangepaste lijst Gegevenstype dient te beschikken over het formaat ingevoerd in het Opties tekstveld. Please fix de volgende rijen:";
-$L["invalid_int_fields"] = "Gelieve enkel getallen in te voeren in de \"Precies\" en \"ten hoogste\" velden voor de Aangepaste lijst Gegevenstype. Please fix de volgende rijen:";
-$L["one_to_ten"] = "een | twee | drie | vier | vijf | zes | zeven | acht | negen | tien";
-$L["prefix"] = "Dr | De heer | Mevrouw | Ms |";
-$L["relationship_states"] = "Single | Getrouwd | Gescheiden | Common-Law";
-$L["separated_by_pipe"] = "Voer waarden gescheiden door |";
-$L["drug_names"] = "Hydrocodone/APAP|Hydrocodone/APAP|LevothyroxineSodium|Lisinopril|Lipitor|Simvastatin|Plavix|Singulair|Azithromycin|Crestor|Nexium|LevothyroxineSodium|Metoprolol Tartrate |Hydrocodone/APAP|Synthroid|Lexapro|Proair HFA|Ibuprofen (Rx)|Trazodone HCl|Amoxicillin|Omeprazole (Rx)|Advair Diskus|Cymbalta|Azithromycin|Tramadol HCl|Omeprazole (Rx)|Amoxicillin|Sulfamethoxazole/Trimethoprim|Amlodipine Besylate|Fluticasone Propionate|Hydrochlorothiazide|Hydrochlorothiazide|Ventolin HFA|Diovan|Warfarin Sodium|Sertraline HCl|Alprazolam|Pravastatin Sodium|Simvastatin|Metoprolol Succinate|Lisinopril/Hydrochlorothiazide|Seroquel|Promethazine HCl|Metoprolol Succinate|Amoxicillin Trihydrate/Potassium Clavulanate|Simvastatin|Amlodipine Besylate|Oxycodone/APAP|Warfarin Sodium|Zolpidem Tartrate|Metformin HCl|Prednisone|Metformin HCl|Fluconazole|Clonazepam|Omeprazole (Rx)|Diovan HCT|Simvastatin|Actos|Sertraline HCl|Gabapentin|Lantus|Amlodipine Besylate|Vitamin D (Rx)|Fluoxetine HCl|Furosemide|Citalopram HBr|Alprazolam|Ciprofloxacin HCl|Atenolol|Meloxicam|Lorazepam|Prednisone|Celebrex|Venlafaxine HCl ER|Lisinopril|Lyrica|Nasonex|Spiriva Handihaler|Furosemide|Cyclobenzaprin HCl|Cephalexin|Abilify|Citalopram HBR|Cyclobenzaprin HCl|Carvedilol|Metformin HCl|Potassium Chloride|Viagra|Amlodipine Besylate|Furosemide|Ibuprofen (Rx)|Azithromycin|Vyvanse|Zetia|Albuterol|Zolpidem Tartrate|Tramadol HCl|Prednisone|Simvastatin|Clonazepam|Namenda|Januvia|Tricor|Diazepam|Simvastatin|Lisinopril|Lisinopril|Loestrin 24 Fe|Metformin HCl|Furosemide|Prednisone|Allopurinol|Amoxicillin Trihydrate/Potassium Clavulanate|Cialis|Alendronate Sodium|Losartan Potassium|Triamterene/Hydrochlorothiazide|Sertraline HCl|Amitriptyline HCl|Oxycodone HCl|Alprazolam|Alprazolam|Fluticasone Propionate|Fluticasone Propionate|Simvastatin|Triamterene/Hydrochlorothiazide|Suboxone|Oxycontin|Niaspan|Lorazepam|Doxycycline Hyclate|Amoxicillin|Digoxin|Potassium Chloride|Vytorin|Lantus Solostar|APAP/Codeine|Hydrocodone/APAP|Triamcinolone Acetonide|Atenolol|Premarin|Paroxetine HCl|Metoprolol Succinatee|Fluoxetine HCl|Flovent HFA|Promethazine HCl|Carisoprodol|Klor-Con M20|Lorazepam|Benicar|Lovastatin|Lisinopril|Amphetamine Salts|Ciprofloxacin HCl|Carvedilol|Risperidone|Levoxyl|Atenolol|Naproxen|Lovaza|Lisinopril|Alprazolam|Atenolol|Bystolic|Penicillin VK|Famotidine|Methylprednisolone|Gabapentin|Nuvaring|Alendronate Sodium|Amoxicillin|Cephalexin|Glyburide|Clindamycin HCl|Folic Acid|Enalapril Maleate|Tamsulosin HCl|Cheratussin AC|Prednisone|Benicar HCT|Gabapentin|Levaquin|TriNessa|Ranitidine HCl|Levothyroxine Sodium|Glipizide|Doxycycline Hyclate|Tri-Sprintec|Amlodipine Besylate|Symbicort|Lidoderm|Pantoprazole Sodium|Pantoprazole Sodium|Zyprexa|Endocet|Zolpidem Tartrate|Gianvi|Lisinopril|Diazepam";
diff --git a/plugins/dataTypes/Names/Names.class.php b/plugins/dataTypes/Names/Names.class.php
deleted file mode 100644
index 4f473428d..000000000
--- a/plugins/dataTypes/Names/Names.class.php
+++ /dev/null
@@ -1,271 +0,0 @@
-getRandomFirstName($this->maleNames), $placeholderStr, 1);
- }
- while (preg_match("/FemaleName/", $placeholderStr)) {
- $placeholderStr = preg_replace("/FemaleName/", $this->getRandomFirstName($this->femaleNames), $placeholderStr, 1);
- }
- while (preg_match("/Name/", $placeholderStr)) {
- $placeholderStr = preg_replace("/Name/", $this->getRandomFirstName($this->firstNames), $placeholderStr, 1);
- }
- while (preg_match("/Surname/", $placeholderStr)) {
- $placeholderStr = preg_replace("/Surname/", $this->lastNames[mt_rand(0, count($this->lastNames)-1)], $placeholderStr, 1);
- }
- while (preg_match("/Initial/", $placeholderStr)) {
- $placeholderStr = preg_replace("/Initial/", $this->letters[mt_rand(0, strlen($this->letters)-1)], $placeholderStr, 1);
- }
-
- // in case the user entered multiple | separated formats, pick one
- $formats = explode("|", $placeholderStr);
- $chosenFormat = $formats[0];
- if (count($formats) > 1) {
- $chosenFormat = $formats[mt_rand(0, count($formats)-1)];
- }
-
- return array(
- "display" => trim($chosenFormat)
- );
- }
-
-
- public function getRowGenerationOptions($generator, $post, $colNum, $numCols) {
- if (!isset($post["dtOption_$colNum"]) || empty($post["dtOption_$colNum"])) {
- return false;
- }
- return $post["dtOption_$colNum"];
- }
-
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255) default NULL",
- "SQLField_Oracle" => "varchar2(255) default NULL",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $html =<<< END
-
- {$L["please_select"]}
- {$this->L["example_MaleName"]}
- {$this->L["example_FemaleName"]}
- {$this->L["example_Name"]}
- {$this->L["example_MaleName_Surname"]}
- {$this->L["example_FemaleName_Surname"]}
- {$this->L["example_Name_Surname"]}
- {$this->L["example_Name_Initial_Surname"]}
- {$this->L["example_surname"]}
- {$this->L["example_Surname_Name_Initial"]}
- {$this->L["example_Name4"]}
- {$this->L["example_fullnames"]}
-
-END;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- return ' ';
- }
-
- public function getNames() {
- return $this->firstNames;
- }
-
- public function getFirstNames() {
- return $this->firstNames;
- }
-
- public function getLastNames() {
- return $this->lastNames;
- }
-
-
- // -------- private member functions ---------
-
- /**
- * Called when instantiating the plugin during data generation. Set the firstNames, maleNames and
- * femaleNames.
- */
- private function initFirstNames() {
- $prefix = Core::getDbTablePrefix();
- $response = Core::$db->query("
- SELECT *
- FROM {$prefix}first_names
- ");
-
- if ($response["success"]) {
- $names = array();
- $maleNames = array();
- $femaleNames = array();
- while ($row = mysqli_fetch_assoc($response["results"])) {
- $gender = $row["gender"];
- $name = $row["first_name"];
-
- $names[] = $name;
- if ($gender == "male") {
- $maleNames[] = $name;
- } else {
- $femaleNames[] = $name;
- }
- }
-
- $this->firstNames = $names;
- $this->maleNames = $maleNames;
- $this->femaleNames = $femaleNames;
- }
- }
-
-
- private function initLastNames() {
- $prefix = Core::getDbTablePrefix();
- $response = Core::$db->query("
- SELECT *
- FROM {$prefix}last_names
- ");
-
- if ($response["success"]) {
- $lastNames = array();
- while ($row = mysqli_fetch_assoc($response["results"])) {
- $lastNames[] = $row["last_name"];
- }
- $this->lastNames = $lastNames;
- }
- }
-
-
- private function getRandomFirstName($nameArray) {
- return $nameArray[mt_rand(0, count($nameArray)-1)];
- }
-
-
- /**
- * Called during installation. This creates and populates the first_names and last_names DB tables.
- *
- * @return array [0] success / error (boolean)
- * [1] the error message, if there was an error
- */
- public static function install() {
- $prefix = Core::getDbTablePrefix();
-
- // always clear out the previous tables, just in case
- $rollbackQueries = array();
- $rollbackQueries[] = "DROP TABLE {$prefix}first_names";
- $rollbackQueries[] = "DROP TABLE {$prefix}last_names";
- Core::$db->query($rollbackQueries);
-
- $queries = array();
- $queries[] = "
- CREATE TABLE {$prefix}first_names (
- id mediumint(9) NOT NULL auto_increment,
- first_name varchar(50) NOT NULL default '',
- gender enum('male','female','both') NOT NULL default 'male',
- PRIMARY KEY (id)
- )
- ";
- $queries[] = "
- INSERT INTO {$prefix}first_names (first_name, gender)
- VALUES ('Aaron','male'),('Abbot','male'),('Abdul','male'),('Abel','male'),('Abigail','female'),('Abra','female'),('Abraham','male'),('Acton','male'),('Adam','male'),('Adara','female'),('Addison','male'),('Adele','female'),('Adena','female'),('Adria','female'),('Adrian','male'),('Adrienne','female'),('Ahmed','male'),('Aidan','male'),('Aiko','female'),('Aileen','female'),('Aimee','female'),('Ainsley','female'),('Akeem','male'),('Aladdin','male'),('Alan','male'),('Alana','female'),('Alden','male'),('Alea','female'),('Alec','male'),('Alexa','female'),('Alexander','male'),('Alexandra','female'),('Alexis','female'),('Alfonso','male'),('Alfreda','female'),('Ali','male'),('Alice','female'),('Alika','female'),('Aline','female'),('Alisa','female'),('Allegra','female'),('Allen','male'),('Allistair','male'),('Alma','female'),('Althea','female'),('Alvin','male'),('Alyssa','female'),('Amal','male'),('Amanda','female'),('Amaya','female'),('Amber','female'),('Amela','female'),('Amelia','female'),('Amena','female'),('Amery','male'),('Amethyst','female'),('Amir','male'),('Amity','female'),('Amos','male'),('Amy','female'),('Anastasia','female'),('Andrew','male'), ('Angela','female'),('Angelica','female'),('Anika','female'),('Anjolie','female'),('Ann','female'),('Anne','female'),('Anthony','male'),('Aphrodite','female'),('April','female'),('Aquila','male'),('Arden','male'),('Aretha','female'),('Ariana','female'),('Ariel','female'),('Aristotle','male'),('Armand','male'),('Armando','male'),('Arsenio','male'),('Arthur','male'),('Ashely','female'),('Asher','male'),('Ashton','male'),('Aspen','female'),('Astra','female'),('Athena','female'),('Aubrey','both'),('Audra','female'),('Audrey','female'),('August','male'),('Aurelia','female'),('Aurora','female'),('Austin','male'),('Autumn','female'),('Ava','female'),('Avram','male'),('Avye','female'),('Axel','male'),('Ayanna','female'),('Azalia','female'),('Baker','male'),('Barbara','female'),('Barclay','male'),('Barrett','male'),('Barry','male'),('Basia','female'),('Basil','male'),('Baxter','male'),('Beatrice','female'),('Beau','male'),('Beck','male'),('Bell','female'),('Belle','female'),('Benedict','male'),('Benjamin','male'),('Berk','male'),('Bernard','male'),('Bert','male'),('Bertha','female'),('Bethany','female'),('Beverly','female'),('Bevis','male'),('Bianca','female'),('Blaine','both'),('Blair','both'),('Blake','male'),('Blaze','male'),('Blossom','female'),('Blythe','female'),('Bo','female'),('Boris','male'),('Bradley','male'),('Brady','male'),('Branden','male'),('Brandon','male'),('Breanna','female'),('Bree','female'),('Brenda','female'),('Brendan','male'),('Brenden','male'),('Brenna','female'),('Brennan','male'),('Brent','male'),('Brett','male'),('Brian','male'),('Brianna','female'),('Briar','female'),('Brielle','female'),('Britanney','female'),('Britanni','female'),('Brittany','female'),('Brock','male'),('Brody','male'),('Brooke','female'),('Bruce','male'),('Bruno','male'),('Bryar','female'),('Brynn','female'),('Brynne','female'),('Buckminster','male'),('Buffy','female'),('Burke','male'),('Burton','male'),('Byron','male'),('Cade','male'),('Cadman','male'),('Caesar','male'),('Cailin','female'),('Cain','male'),('Cairo','male'),('Caldwell','male'),('Caleb','male'),('Calista','female'),('Callie','female'),('Callum','male'),('Cally','female'),('Calvin','male'),('Camden','male'),('Cameran','female'),('Cameron','female'),('Cameron','male'),('Camilla','female'),('Camille','female'),('Candace','female'),('Candice','female'),('Cara','female'),('Carissa','female'),('Carl','male'),('Carla','female'),('Carlos','male'),('Carly','female'),('Carol','female'),('Carolyn','female'),('Carson','male'),('Carter','male'),('Caryn','female'),('Casey','both'),('Cassady','female'),('Cassandra','female'),('Cassidy','female'),('Castor','male'),('Catherine','female'),('Cathleen','female'),('Cecilia','female'),('Cedric','male'),('Celeste','female'),('Chadwick','male'),('Chaim','male'),('Chancellor','male'),('Chanda','female'),('Chandler','male'),('Chaney','male'),('Channing','male'),('Chantale','female'),('Charde','female'),('Charissa','female'),('Charity','female'),('Charles','male'),('Charlotte','female'),('Chase','male'),('Chastity','female'),('Chava','female'),('Chelsea','female'),('Cherokee','female'),('Cheryl','female'),('Chester','male'),('Cheyenne','female'),('Chiquita','female'),('Chloe','female'),('Christen','female'),('Christian','male'),('Christine','female'),('Christopher','male'),('Ciara','female'),('Ciaran','male'),('Claire','female'),('Clare','female'),('Clark','male'),('Clarke','male'),('Claudia','female'),('Clayton','male'),('Clementine','female'),('Cleo','female'),('Clinton','male'),('Clio','female'),('Coby','male'),('Cody','male'),('Colby','male'),('Cole','male'),('Colette','female'),('Colin','male'),('Colleen','female'),('Colorado','male'),('Colt','male'),('Colton','male'),('Conan','male'),('Connor','male'),('Constance','female'),('Cooper','male'),('Cora','female'),('Courtney','female'),('Craig','male'),('Cruz','male'),('Cullen','male'),('Curran','male'),('Cynthia','female'),('Cyrus','male'),('Dacey','female'),('Dahlia','female'),('Dai','female'),('Dakota','both'),('Dale','male'),('Dalton','male'),('Damian','male'),('Damon','male'),('Dana','female'),('Dane','male'),('Daniel','male'),('Danielle','female'),('Dante','male'),('Daphne','female'),('Daquan','male'),('Dara','female'),('Daria','female'),('Darius','male'),('Darrel','female'),('Darryl','female'),('Daryl','female'),('David','male'),('Davis','male'),('Dawn','female'),('Deacon','male'),('Dean','male'),('Deanna','female'),('Deborah','female'),('Debra','female'),('Declan','male'),('Deirdre','female'),('Delilah','female'),('Demetria','female'),('Demetrius','male'),('Denise','female'),('Dennis','male'),('Denton','male'),('Derek','male'),('Desirae','female'),('Desiree','female'),('Destiny','female'),('Devin','male'),('Dexter','male'),('Diana','female'),('Dieter','male'),('Dillon','male'),('Dolan','male'),('Dominic','male'),('Dominique','female'),('Donna','female'),('Donovan','male'),('Dora','female'),('Dorian','male'),('Doris','female'),('Dorothy','female'),('Drake','male'),('Drew','male'),('Driscoll','male'),('Duncan','male'),('Dustin','male'),('Dylan','male'),('Eagan','male'),('Eaton','male'),('Ebony','female'),('Echo','female'),('Edan','male'),('Eden','both'),('Edward','male'),('Elaine','female'),('Eleanor','female'),('Eliana','female'),('Elijah','male'),('Elizabeth','female'),('Ella','female'),('Elliott','male'),('Elmo','male'),('Elton','male'),('Elvis','male'),('Emerald','female'),('Emerson','male'),('Emery','male'),('Emi','female'),('Emily','female'),('Emma','female'),('Emmanuel','male'),('Erasmus','male'),('Eric','male'),('Erica','female'),('Erich','male'),('Erin','female'),('Ethan','male'),('Eugenia','female'),('Evan','male'),('Evangeline','female'),('Eve','female'),('Evelyn','female'),('Ezekiel','male'),('Ezra','male'),('Faith','female'),('Fallon','female'),('Farrah','female'),('Fatima','female'),('Fay','female'),('Felicia','female'),('Felix','male'),('Ferdinand','male'),('Ferris','male'),('Finn','male'),('Fiona','female'),('Fitzgerald','male'),('Flavia','female'),('Fletcher','male'),('Fleur','female'),('Florence','female'),('Flynn','male'),('Forrest','male'),('Frances','female'),('Francesca','female'),('Francis','male'),('Fredericka','female'),('Freya','female'),('Fritz','male'),('Fuller','male'),('Fulton','male'),('Gabriel','male'),('Gage','male'),('Gail','female'),('Galena','female'),('Galvin','male'),('Gannon','male'),('Gareth','male'),('Garrett','male'),('Garrison','male'),('Garth','male'),('Gary','male'),('Gavin','male'),('Gay','female'),('Gemma','female'),('Genevieve','female'),('Geoffrey','male'),('George','male'),('Georgia','female'),('Geraldine','female'),('Germaine','female'),('Germane','female'),('Giacomo','male'),('Gil','male'),('Gillian','female'),('Ginger','female'),('Gisela','female'),('Giselle','female'),('Glenna','female'),('Gloria','female'),('Grace','female'),('Grady','male'),('Graham','male'),('Graiden','male'),('Grant','male'),('Gray','male'),('Gregory','male'),('Gretchen','female'),('Griffin','male'),('Griffith','male'),('Guinevere','female'),('Guy','male'),('Gwendolyn','female'),('Hadassah','female'),('Hadley','female'),('Hakeem','male'),('Halee','female'),('Haley','female'),('Hall','male'),('Halla','female'),('Hamilton','male'),('Hamish','male'),('Hammett','male'),('Hanae','female'),('Hanna','female'),('Hannah','female'),('Harding','male'),('Harlan','male'),('Harper','male'),('Harriet','female'),('Harrison','male'),('Hasad','male'),('Hashim','male'),('Haviva','female'),('Hayden','male'),('Hayes','male'),('Hayfa','female'),('Hayley','female'),('Heather','female'),('Hector','male'),('Hedda','female'),('Hedley','male'),('Hedwig','female'),('Hedy','female'),('Heidi','female'),('Helen','female'),('Henry','male'),('Herman','male'),('Hermione','female'),('Herrod','male'),('Hilary','female'),('Hilda','female'),('Hilel','male'),('Hillary','female'),('Hiram','male'),('Hiroko','female'),('Hollee','female'),('Holly','female'),('Holmes','male'),('Honorato','male'),('Hop','male'),('Hope','female'),('Howard','male'),('Hoyt','male'),('Hu','male'),('Hunter','male'),('Hyacinth','female'),('Hyatt','male'),('Ian','male'),('Idola','female'),('Idona','female'),('Ifeoma','female'),('Ignacia','female'),('Ignatius','male'),('Igor','male'),('Ila','female'),('Iliana','female'),('Illana','female'),('Illiana','female'),('Ima','female'),('Imani','female'),('Imelda','female'),('Imogene','female'),('Ina','female'),('India','female'),('Indigo','female'),('Indira','female'),('Inez','female'),('Inga','female'),('Ingrid','female'),('Iola','female'),('Iona','female'),('Ira','male'),('Irene','female'),('Iris','female'),('Irma','female'),('Isaac','male'),('Isabella','female'),('Isabelle','female'),('Isadora','female'),('Isaiah','male'),('Ishmael','male'),('Ivan','male'),('Ivana','female'),('Ivor','male'),('Ivory','female'),('Ivy','female'),('Jack','male'),('Jackson','male'),('Jacob','male'),('Jada','female'),('Jade','female'),('Jaden','female'),('Jael','female'),('Jaime','female'),('Jakeem','male'),('Jamal','male'),('Jamalia','female'),('James','male'),('Jameson','male'),('Jana','female'),('Jane','female'),('Janna','female'),('Jaquelyn','female'),('Jared','male'),('Jarrod','male'),('Jasmine','female'),('Jason','male'),('Jasper','male'),('Jayme','female'),('Jeanette','female'),('Jelani','male'),('Jemima','female'),('Jena','female'),('Jenette','female'),('Jenna','female'),('Jennifer','female'),('Jeremy','male'),('Jermaine','male'),('Jerome','male'),('Jerry','male'),('Jescie','female'),('Jessamine','female'),('Jesse','male'),('Jessica','female'),('Jillian','female'),('Jin','male'),('Joan','female'),('Jocelyn','female'),('Joel','male'),('Joelle','female'),('John','male'),('Jolene','female'),('Jolie','female'),('Jonah','male'),('Jonas','male'),('Jordan','female'),('Jordan','male'),('Jorden','female'),('Joseph','male'),('Josephine','female'),('Joshua','male'),('Josiah','male'),('Joy','female'),('Judah','male'),('Judith','female'),('Julian','male'),('Julie','female'),('Juliet','female'),('Justin','male'),('Justina','female'),('Justine','female'),('Kadeem','male'),('Kaden','both'),('Kai','female'),('Kaitlin','female'),('Kalia','female'),('Kamal','male'),('Kameko','female'),('Kane','male'),('Kareem','male'),('Karen','female'),('Karina','female'),('Karleigh','female'),('Karly','female'),('Karyn','female'),('Kaseem','male'),('Kasimir','male'),('Kasper','male'),('Katell','female'),('Katelyn','female'),('Kathleen','female'),('Kato','male'),('Kay','female'),('Kaye','female'),('Keane','male'),('Keaton','male'),('Keefe','male'),('Keegan','male'),('Keelie','female'),('Keely','female'),('Keiko','female'),('Keith','male'),('Kellie','female'),('Kelly','female'),('Kelly','male'),('Kelsey','female'),('Kelsie','female'),('Kendall','both'),('Kennan','male'),('Kennedy','male'),('Kenneth','male'),('Kenyon','male'),('Kermit','male'),('Kerry','female'),('Kessie','female'),('Kevin','male'),('Kevyn','female'),('Kiara','female'),('Kiayada','female'),('Kibo','male'),('Kieran','male'),('Kim','female'),('Kimberley','female'),('Kimberly','female'),('Kiona','female'),('Kirby','female'),('Kirestin','female'),('Kirk','male'),('Kirsten','female'),('Kitra','female'),('Knox','male'),('Kristen','female'),('Kuame','male'),('Kyla','female'),('Kylan','female'),('Kyle','male'),('Kylee','female'),('Kylie','female'),('Kylynn','female'),('Kyra','female'),('Lacey','female'),('Lacota','female'),('Lacy','female'),('Lael','female'),('Laith','male'),('Lamar','male'),('Lana','female'),('Lance','male'),('Lane','male'),('Lani','female'),('Lara','female'),('Lareina','female'),('Larissa','female'),('Lars','male'),('Latifah','female'),('Laura','female'),('Laurel','female'),('Lavinia','female'),('Lawrence','male'),('Leah','female'),('Leandra','female'),('Lee','female'),('Lee','male'),('Leigh','female'),('Leila','female'),('Leilani','female'),('Len','male'),('Lenore','female'),('Leo','male'),('Leonard','male'),('Leroy','male'),('Lesley','female'),('Leslie','female'),('Lester','male'),('Lev','male'),('Levi','male'),('Lewis','male'),('Libby','female'),('Liberty','female'),('Lila','female'),('Lilah','female'),('Lillian','female'),('Lillith','female'),('Linda','female'),('Linus','male'),('Lionel','male'),('Lisandra','female'),('Logan','male'),('Lois','female'),('Louis','male'),('Lucas','male'),('Lucian','male'),('Lucius','male'),('Lucy','female'),('Luke','male'),('Lunea','female'),('Lydia','female'),('Lyle','male'),('Lynn','female'),('Lysandra','female'),('MacKensie','female'),('MacKenzie','female'),('Macaulay','male'),('Macey','female'),('Macon','male'),('Macy','female'),('Madaline','female'),('Madeline','female'),('Madeson','female'),('Madison','female'),('Madonna','female'),('Magee','male'),('Maggie','female'),('Maggy','female'),('Maia','female'),('Maile','female'),('Maisie','female'),('Maite','female'),('Malachi','male'),('Malcolm','male'),('Malik','male'),('Mallory','female'),('Mannix','male'),('Mara','female'),('Marah','female'),('Marcia','female'),('Margaret','female'),('Mari','female'),('Mariam','female'),('Mariko','female'),('Maris','female'),('Mark','male'),('Marny','female'),('Marsden','male'),('Marshall','male'),('Martena','female'),('Martha','female'),('Martin','male'),('Martina','female'),('Marvin','male'),('Mary','female'),('Maryam','female'),('Mason','male'),('Matthew','male'),('Maxine','female'),('Maxwell','male'),('May','female'),('Maya','female'),('McKenzie','female'),('Mechelle','female'),('Medge','female'),('Megan','female'),('Meghan','female'),('Melanie','female'),('Melinda','female'),('Melissa','female'),('Melodie','female'),('Melvin','male'),('Melyssa','female'),('Mercedes','female'),('Meredith','female'),('Merrill','male'),('Merritt','male'),('Mia','female'),('Micah','male'),('Michael','male'),('Michelle','female'),('Mikayla','female'),('Minerva','female'),('Mira','female'),('Miranda','female'),('Miriam','female'),('Moana','female'),('Mohammad','male'),('Mollie','female'),('Molly','female'),('Mona','female'),('Montana','female'),('Morgan','female'),('Moses','male'),('Mufutau','male'),('Murphy','male'),('Myles','male'),('Myra','female'),('Nadine','female'),('Naida','female'),('Naomi','female'),('Nash','male'),('Nasim','male'),('Natalie','female'),('Nathan','male'),('Nathaniel','male'),('Nayda','female'),('Nehru','male'),('Neil','male'),('Nell','female'),('Nelle','female'),('Nerea','female'),('Nero','male'),('Nevada','female'),('Neve','female'),('Neville','male'),('Nicholas','male'),('Nichole','female'),('Nicole','female'),('Nigel','male'),('Nina','female'),('Nissim','male'),('Nita','female'),('Noah','male'),('Noble','male'),('Noel','female'),('Noelani','female'),('Noelle','female'),('Nola','female'),('Nolan','male'),('Nomlanga','female'),('Nora','female'),('Norman','male'),('Nyssa','female'),('Ocean','female'),('Octavia','female'),('Octavius','male'),('Odessa','female'),('Odette','female'),('Odysseus','male'),('Oleg','male'),('Olga','female'),('Oliver','male'),('Olivia','female'),('Olympia','female'),('Omar','male'),('Oprah','female'),('Ora','female'),('Oren','male'),('Ori','female'),('Orla','female'),('Orlando','male'),('Orli','female'),('Orson','male'),('Oscar','male'),('Otto','male'),('Owen','male'),('Paki','male'),('Palmer','male'),('Paloma','female'),('Pamela','female'),('Pandora','female'),('Pascale','female'),('Patience','female'),('Patricia','female'),('Patrick','male'),('Paul','male'),('Paula','female'),('Pearl','female'),('Penelope','female'),('Perry','male'),('Peter','male'),('Petra','female'),('Phelan','male'),('Philip','male'),('Phillip','male'),('Phoebe','female'),('Phyllis','female'),('Piper','female'),('Plato','male'),('Porter','male'),('Portia','female'),('Prescott','male'),('Preston','male'),('Price','male'),('Priscilla','female'),('Quail','female'),('Quamar','male'),('Quemby','female'),('Quentin','male'),('Quin','female'),('Quincy','both'),('Quinlan','male'),('Quinn','female'),('Quinn','male'),('Quintessa','female'),('Quon','female'),('Quyn','female'),('Quynn','female'),('Rachel','female'),('Rae','female'),('Rafael','male'),('Rahim','male'),('Raja','male'),('Rajah','male'),('Ralph','male'),('Rama','female'),('Ramona','female'),('Rana','female'),('Randall','male'),('Raphael','male'),('Rashad','male'),('Raven','female'),('Ray','male'),('Raya','female'),('Raymond','male'),('Reagan','female'),('Rebecca','female'),('Rebekah','female'),('Reece','male'),('Reed','male'),('Reese','male'),('Regan','female'),('Regina','female'),('Remedios','female'),('Renee','female'),('Reuben','male'),('Rhea','female'),('Rhiannon','female'),('Rhoda','female'),('Rhona','female'),('Rhonda','female'),('Ria','female'),('Richard','male'),('Rigel','male'),('Riley','female'),('Rina','female'),('Rinah','female'),('Risa','female'),('Roanna','female'),('Roary','female'),('Robert','male'),('Robin','female'),('Rogan','male'),('Ronan','male'),('Rooney','male'),('Rosalyn','female'),('Rose','female'),('Ross','male'),('Roth','male'),('Rowan','female'),('Ruby','female'),('Rudyard','male'),('Russell','male'),('Ruth','female'),('Ryan','male'),('Ryder','male'),('Rylee','female'),('Sacha','female'),('Sade','female'),('Sage','female'),('Salvador','male'),('Samantha','female'),('Samson','male'),('Samuel','male'),('Sandra','female'),('Sara','female'),('Sarah','female'),('Sasha','female'),('Savannah','female'),('Sawyer','male'),('Scarlet','female'),('Scarlett','female'),('Scott','male'),('Sean','male'),('Sebastian','male'),('Selma','female'),('September','female'),('Serena','female'),('Serina','female'),('Seth','male'),('Shad','male'),('Shaeleigh','female'),('Shafira','female'),('Shaine','female'),('Shana','female'),('Shannon','female'),('Sharon','female'),('Shay','female'),('Shea','female'),('Sheila','female'),('Shelby','female'),('Shelley','female'),('Shellie','female'),('Shelly','female'),('Shoshana','female'),('Sierra','female'),('Signe','female'),('Sigourney','female'),('Silas','male'),('Simon','male'),('Simone','female'),('Skyler','female'),('Slade','male'),('Sloane','both'),('Solomon','male'),('Sonia','female'),('Sonya','female'),('Sophia','female'),('Sopoline','female'),('Stacey','female'),('Stacy','female'),('Steel','male'),('Stella','female'),('Stephanie','female'),('Stephen','male'),('Steven','male'),('Stewart','male'),('Stone','male'),('Stuart','male'),('Suki','female'),('Summer','female'),('Susan','female'),('Sybil','female'),('Sybill','female'),('Sydnee','female'),('Sydney','female'),('Sylvester','male'),('Sylvia','female'),('TaShya','female'),('Tad','male'),('Tallulah','female'),('Talon','male'),('Tamara','female'),('Tamekah','female'),('Tana','female'),('Tanek','male'),('Tanisha','female'),('Tanner','male'),('Tanya','female'),('Tara','female'),('Tarik','male'),('Tasha','female'),('Tashya','female'),('Tate','male'),('Tatiana','female'),('Tatum','female'),('Tatyana','female'),('Taylor','both'),('Teagan','female'),('Teegan','female'),('Thaddeus','male'),('Thane','male'),('Theodore','male'),('Thomas','male'),('Thor','male'),('Tiger','male'),('Timon','male'),('Timothy','male'),('Tobias','male'),('Todd','male'),('Travis','male'),('Trevor','male'),('Troy','male'),('Tucker','male'),('Tyler','male'),('Tyrone','male'),('Ulla','female'),('Ulric','male'),('Ulysses','male'),('Uma','female'),('Unity','female'),('Upton','male'),('Uriah','male'),('Uriel','male'),('Urielle','female'),('Ursa','female'),('Ursula','female'),('Uta','female'),('Valentine','male'),('Vance','male'),('Vanna','female'),('Vaughan','male'),('Veda','female'),('Velma','female'),('Venus','female'),('Vera','female'),('Vernon','male'),('Veronica','female'),('Victor','male'),('Victoria','female'),('Vielka','female'),('Vincent','male'),('Violet','female'),('Virginia','female'),('Vivian','female'),('Vivien','female'),('Vladimir','male'),('Wade','male'),('Walker','male'),('Wallace','male'),('Walter','male'),('Wanda','female'),('Wang','male'),('Warren','male'),('Wayne','male'),('Wendy','female'),('Wesley','male'),('Whilemina','female'),('Whitney','female'),('Whoopi','female'),('Willa','female'),('William','male'),('Willow','female'),('Wilma','female'),('Wing','male'),('Winifred','female'),('Winter','female'),('Wyatt','male'),('Wylie','male'),('Wynne','female'),('Wynter','female'),('Wyoming','female'),('Xander','male'),('Xandra','female'),('Xantha','female'),('Xanthus','male'),('Xavier','male'),('Xaviera','female'),('Xena','female'),('Xenos','male'),('Xerxes','both'),('Xyla','female'),('Yael','female'),('Yardley','male'),('Yasir','male'),('Yen','female'),('Yeo','female'),('Yetta','female'),('Yoko','female'),('Yolanda','female'),('Yoshi','female'),('Yoshio','male'),('Yuli','male'),('Yuri','female'),('Yvette','female'),('Yvonne','female'),('Zachary','male'),('Zachery','male'),('Zahir','male'),('Zane','male'),('Zelda','female'),('Zelenia','female'),('Zena','female'),('Zenaida','female'),('Zenia','female'),('Zeph','male'),('Zephania','male'),('Zephr','female'),('Zeus','male'),('Zia','female'),('Zoe','female'),('Zorita','female'),('Jacqueline','female')
- ";
- $queries[] = "
- CREATE TABLE {$prefix}last_names (
- id mediumint(9) NOT NULL auto_increment,
- last_name varchar(100) NOT NULL default '',
- PRIMARY KEY (id)
- )
- ";
- $queries[] = "
- INSERT INTO {$prefix}last_names (last_name)
- VALUES ('Abbott'),('Acevedo'),('Acosta'),('Adams'),('Adkins'),('Aguilar'),('Aguirre'),('Albert'),('Alexander'),('Alford'),('Allen'),('Allison'),('Alston'),('Alvarado'),('Alvarez'),('Anderson'),('Andrews'),('Anthony'),('Armstrong'),('Arnold'),('Ashley'),('Atkins'),('Atkinson'),('Austin'),('Avery'),('Avila'),('Ayala'),('Ayers'),('Bailey'),('Baird'),('Baker'),('Baldwin'),('Ball'),('Ballard'),('Banks'),('Barber'),('Barker'),('Barlow'),('Barnes'),('Barnett'),('Barr'),('Barrera'),('Barrett'),('Barron'),('Barry'),('Bartlett'),('Barton'),('Bass'),('Bates'),('Battle'),('Bauer'),('Baxter'),('Beach'),('Bean'),('Beard'),('Beasley'),('Beck'),('Becker'),('Bell'),('Bender'),('Benjamin'),('Bennett'),('Benson'),('Bentley'),('Benton'),('Berg'),('Berger'),('Bernard'),('Berry'),('Best'),('Bird'),('Bishop'),('Black'),('Blackburn'),('Blackwell'),('Blair'),('Blake'),('Blanchard'),('Blankenship'),('Blevins'),('Bolton'),('Bond'),('Bonner'),('Booker'),('Boone'),('Booth'),('Bowen'),('Bowers'),('Bowman'),('Boyd'),('Boyer'),('Boyle'),('Bradford'),('Bradley'),('Bradshaw'),('Brady'),('Branch'),('Bray'),('Brennan'),('Brewer'),('Bridges'),('Briggs'),('Bright'),('Britt'),('Brock'),('Brooks'),('Brown'),('Browning'),('Bruce'),('Bryan'),('Bryant'),('Buchanan'),('Buck'),('Buckley'),('Buckner'),('Bullock'),('Burch'),('Burgess'),('Burke'),('Burks'),('Burnett'),('Burns'),('Burris'),('Burt'),('Burton'),('Bush'),('Butler'),('Byers'),('Byrd'),('Cabrera'),('Cain'),('Calderon'),('Caldwell'),('Calhoun'),('Callahan'),('Camacho'),('Cameron'),('Campbell'),('Campos'),('Cannon'),('Cantrell'),('Cantu'),('Cardenas'),('Carey'),('Carlson'),('Carney'),('Carpenter'),('Carr'),('Carrillo'),('Carroll'),('Carson'),('Carter'),('Carver'),('Case'),('Casey'),('Cash'),('Castaneda'),('Castillo'),('Castro'),('Cervantes'),('Chambers'),('Chan'),('Chandler'),('Chaney'),('Chang'),('Chapman'),('Charles'),('Chase'),('Chavez'),('Chen'),('Cherry'),('Christensen'),('Christian'),('Church'),('Clark'),('Clarke'),('Clay'),('Clayton'),('Clements'),('Clemons'),('Cleveland'),('Cline'),('Cobb'),('Cochran'),('Coffey'),('Cohen'),('Cole'),('Coleman'),('Collier'),('Collins'),('Colon'),('Combs'),('Compton'),('Conley'),('Conner'),('Conrad'),('Contreras'),('Conway'),('Cook'),('Cooke'),('Cooley'),('Cooper'),('Copeland'),('Cortez'),('Cote'),('Cotton'),('Cox'),('Craft'),('Craig'),('Crane'),('Crawford'),('Crosby'),('Cross'),('Cruz'),('Cummings'),('Cunningham'),('Curry'),('Curtis'),('Dale'),('Dalton'),('Daniel'),('Daniels'),('Daugherty'),('Davenport'),('David'),('Davidson'),('Davis'),('Dawson'),('Day'),('Dean'),('Decker'),('Dejesus'),('Delacruz'),('Delaney'),('Deleon'),('Delgado'),('Dennis'),('Diaz'),('Dickerson'),('Dickson'),('Dillard'),('Dillon'),('Dixon'),('Dodson'),('Dominguez'),('Donaldson'),('Donovan'),('Dorsey'),('Dotson'),('Douglas'),('Downs'),('Doyle'),('Drake'),('Dudley'),('Duffy'),('Duke'),('Duncan'),('Dunlap'),('Dunn'),('Duran'),('Durham'),('Dyer'),('Eaton'),('Edwards'),('Elliott'),('Ellis'),('Ellison'),('Emerson'),('England'),('English'),('Erickson'),('Espinoza'),('Estes'),('Estrada'),('Evans'),('Everett'),('Ewing'),('Farley'),('Farmer'),('Farrell'),('Faulkner'),('Ferguson'),('Fernandez'),('Ferrell'),('Fields'),('Figueroa'),('Finch'),('Finley'),('Fischer'),('Fisher'),('Fitzgerald'),('Fitzpatrick'),('Fleming'),('Fletcher'),('Flores'),('Flowers'),('Floyd'),('Flynn'),('Foley'),('Forbes'),('Ford'),('Foreman'),('Foster'),('Fowler'),('Fox'),('Francis'),('Franco'),('Frank'),('Franklin'),('Franks'),('Frazier'),('Frederick'),('Freeman'),('French'),('Frost'),('Fry'),('Frye'),('Fuentes'),('Fuller'),('Fulton'),('Gaines'),('Gallagher'),('Gallegos'),('Galloway'),('Gamble'),('Garcia'),('Gardner'),('Garner'),('Garrett'),('Garrison'),('Garza'),('Gates'),('Gay'),('Gentry'),('George'),('Gibbs'),('Gibson'),('Gilbert'),('Giles'),('Gill'),('Gillespie'),('Gilliam'),('Gilmore'),('Glass'),('Glenn'),('Glover'),('Goff'),('Golden'),('Gomez'),('Gonzales'),('Gonzalez'),('Good'),('Goodman'),('Goodwin'),('Gordon'),('Gould'),('Graham'),('Grant'),('Graves'),('Gray'),('Green'),('Greene'),('Greer'),('Gregory'),('Griffin'),('Griffith'),('Grimes'),('Gross'),('Guerra'),('Guerrero'),('Guthrie'),('Gutierrez'),('Guy'),('Guzman'),('Hahn'),('Hale'),('Haley'),('Hall'),('Hamilton'),('Hammond'),('Hampton'),('Hancock'),('Haney'),('Hansen'),('Hanson'),('Hardin'),('Harding'),('Hardy'),('Harmon'),('Harper'),('Harrell'),('Harrington'),('Harris'),('Harrison'),('Hart'),('Hartman'),('Harvey'),('Hatfield'),('Hawkins'),('Hayden'),('Hayes'),('Haynes'),('Hays'),('Head'),('Heath'),('Hebert'),('Henderson'),('Hendricks'),('Hendrix'),('Henry'),('Hensley'),('Henson'),('Herman'),('Hernandez'),('Herrera'),('Herring'),('Hess'),('Hester'),('Hewitt'),('Hickman'),('Hicks'),('Higgins'),('Hill'),('Hines'),('Hinton'),('Hobbs'),('Hodge'),('Hodges'),('Hoffman'),('Hogan'),('Holcomb'),('Holden'),('Holder'),('Holland'),('Holloway'),('Holman'),('Holmes'),('Holt'),('Hood'),('Hooper'),('Hoover'),('Hopkins'),('Hopper'),('Horn'),('Horne'),('Horton'),('House'),('Houston'),('Howard'),('Howe'),('Howell'),('Hubbard'),('Huber'),('Hudson'),('Huff'),('Huffman'),('Hughes'),('Hull'),('Humphrey'),('Hunt'),('Hunter'),('Hurley'),('Hurst'),('Hutchinson'),('Hyde'),('Ingram'),('Irwin'),('Jackson'),('Jacobs'),('Jacobson'),('James'),('Jarvis'),('Jefferson'),('Jenkins'),('Jennings'),('Jensen'),('Jimenez'),('Johns'),('Johnson'),('Johnston'),('Jones'),('Jordan'),('Joseph'),('Joyce'),('Joyner'),('Juarez'),('Justice'),('Kane'),('Kaufman'),('Keith'),('Keller'),('Kelley'),('Kelly'),('Kemp'),('Kennedy'),('Kent'),('Kerr'),('Key'),('Kidd'),('Kim'),('King'),('Kinney'),('Kirby'),('Kirk'),('Kirkland'),('Klein'),('Kline'),('Knapp'),('Knight'),('Knowles'),('Knox'),('Koch'),('Kramer'),('Lamb'),('Lambert'),('Lancaster'),('Landry'),('Lane'),('Lang'),('Langley'),('Lara'),('Larsen'),('Larson'),('Lawrence'),('Lawson'),('Le'),('Leach'),('Leblanc'),('Lee'),('Leon'),('Leonard'),('Lester'),('Levine'),('Levy'),('Lewis'),('Lindsay'),('Lindsey'),('Little'),('Livingston'),('Lloyd'),('Logan'),('Long'),('Lopez'),('Lott'),('Love'),('Lowe'),('Lowery'),('Lucas'),('Luna'),('Lynch'),('Lynn'),('Lyons'),('Macdonald'),('Macias'),('Mack'),('Madden'),('Maddox'),('Maldonado'),('Malone'),('Mann'),('Manning'),('Marks'),('Marquez'),('Marsh'),('Marshall'),('Martin'),('Martinez'),('Mason'),('Massey'),('Mathews'),('Mathis'),('Matthews'),('Maxwell'),('May'),('Mayer'),('Maynard'),('Mayo'),('Mays'),('Mcbride'),('Mccall'),('Mccarthy'),('Mccarty'),('Mcclain'),('Mcclure'),('Mcconnell'),('Mccormick'),('Mccoy'),('Mccray'),('Mccullough'),('Mcdaniel'),('Mcdonald'),('Mcdowell'),('Mcfadden'),('Mcfarland'),('Mcgee'),('Mcgowan'),('Mcguire'),('Mcintosh'),('Mcintyre'),('Mckay'),('Mckee'),('Mckenzie'),('Mckinney'),('Mcknight'),('Mclaughlin'),('Mclean'),('Mcleod'),('Mcmahon'),('Mcmillan'),('Mcneil'),('Mcpherson'),('Meadows'),('Medina'),('Mejia'),('Melendez'),('Melton'),('Mendez'),('Mendoza'),('Mercado'),('Mercer'),('Merrill'),('Merritt'),('Meyer'),('Meyers'),('Michael'),('Middleton'),('Miles'),('Miller'),('Mills'),('Miranda'),('Mitchell'),('Molina'),('Monroe'),('Montgomery'),('Montoya'),('Moody'),('Moon'),('Mooney'),('Moore'),('Morales'),('Moran'),('Moreno'),('Morgan'),('Morin'),('Morris'),('Morrison'),('Morrow'),('Morse'),('Morton'),('Moses'),('Mosley'),('Moss'),('Mueller'),('Mullen'),('Mullins'),('Munoz'),('Murphy'),('Murray'),('Myers'),('Nash'),('Navarro'),('Neal'),('Nelson'),('Newman'),('Newton'),('Nguyen'),('Nichols'),('Nicholson'),('Nielsen'),('Nieves'),('Nixon'),('Noble'),('Noel'),('Nolan'),('Norman'),('Norris'),('Norton'),('Nunez'),('Obrien'),('Ochoa'),('Oconnor'),('Odom'),('Odonnell'),('Oliver'),('Olsen'),('Olson'),('Oneal'),('Oneil'),('Oneill'),('Orr'),('Ortega'),('Ortiz'),('Osborn'),('Osborne'),('Owen'),('Owens'),('Pace'),('Pacheco'),('Padilla'),('Page'),('Palmer'),('Park'),('Parker'),('Parks'),('Parrish'),('Parsons'),('Pate'),('Patel'),('Patrick'),('Patterson'),('Patton'),('Paul'),('Payne'),('Pearson'),('Peck'),('Pena'),('Pennington'),('Perez'),('Perkins'),('Perry'),('Peters'),('Petersen'),('Peterson'),('Petty'),('Phelps'),('Phillips'),('Pickett'),('Pierce'),('Pittman'),('Pitts'),('Pollard'),('Poole'),('Pope'),('Porter'),('Potter'),('Potts'),('Powell'),('Powers'),('Pratt'),('Preston'),('Price'),('Prince'),('Pruitt'),('Puckett'),('Pugh'),('Quinn'),('Ramirez'),('Ramos'),('Ramsey'),('Randall'),('Randolph'),('Rasmussen'),('Ratliff'),('Ray'),('Raymond'),('Reed'),('Reese'),('Reeves'),('Reid'),('Reilly'),('Reyes'),('Reynolds'),('Rhodes'),('Rice'),('Rich'),('Richard'),('Richards'),('Richardson'),('Richmond'),('Riddle'),('Riggs'),('Riley'),('Rios'),('Rivas'),('Rivera'),('Rivers'),('Roach'),('Robbins'),('Roberson'),('Roberts'),('Robertson'),('Robinson'),('Robles'),('Rocha'),('Rodgers'),('Rodriguez'),('Rodriquez'),('Rogers'),('Rojas'),('Rollins'),('Roman'),('Romero'),('Rosa'),('Rosales'),('Rosario'),('Rose'),('Ross'),('Roth'),('Rowe'),('Rowland'),('Roy'),('Ruiz'),('Rush'),('Russell'),('Russo'),('Rutledge'),('Ryan'),('Salas'),('Salazar'),('Salinas'),('Sampson'),('Sanchez'),('Sanders'),('Sandoval'),('Sanford'),('Santana'),('Santiago'),('Santos'),('Sargent'),('Saunders'),('Savage'),('Sawyer'),('Schmidt'),('Schneider'),('Schroeder'),('Schultz'),('Schwartz'),('Scott'),('Sears'),('Sellers'),('Serrano'),('Sexton'),('Shaffer'),('Shannon'),('Sharp'),('Sharpe'),('Shaw'),('Shelton'),('Shepard'),('Shepherd'),('Sheppard'),('Sherman'),('Shields'),('Short'),('Silva'),('Simmons'),('Simon'),('Simpson'),('Sims'),('Singleton'),('Skinner'),('Slater'),('Sloan'),('Small'),('Smith'),('Snider'),('Snow'),('Snyder'),('Solis'),('Solomon'),('Sosa'),('Soto'),('Sparks'),('Spears'),('Spence'),('Spencer'),('Stafford'),('Stanley'),('Stanton'),('Stark'),('Steele'),('Stein'),('Stephens'),('Stephenson'),('Stevens'),('Stevenson'),('Stewart'),('Stokes'),('Stone'),('Stout'),('Strickland'),('Strong'),('Stuart'),('Suarez'),('Sullivan'),('Summers'),('Sutton'),('Swanson'),('Sweeney'),('Sweet'),('Sykes'),('Talley'),('Tanner'),('Tate'),('Taylor'),('Terrell'),('Terry'),('Thomas'),('Thompson'),('Thornton'),('Tillman'),('Todd'),('Torres'),('Townsend'),('Tran'),('Travis'),('Trevino'),('Trujillo'),('Tucker'),('Turner'),('Tyler'),('Tyson'),('Underwood'),('Valdez'),('Valencia'),('Valentine'),('Valenzuela'),('Vance'),('Vang'),('Vargas'),('Vasquez'),('Vaughan'),('Vaughn'),('Vazquez'),('Vega'),('Velasquez'),('Velazquez'),('Velez'),('Villarreal'),('Vincent'),('Vinson'),('Wade'),('Wagner'),('Walker'),('Wall'),('Wallace'),('Waller'),('Walls'),('Walsh'),('Walter'),('Walters'),('Walton'),('Ward'),('Ware'),('Warner'),('Warren'),('Washington'),('Waters'),('Watkins'),('Watson'),('Watts'),('Weaver'),('Webb'),('Weber'),('Webster'),('Weeks'),('Weiss'),('Welch'),('Wells'),('West'),('Wheeler'),('Whitaker'),('White'),('Whitehead'),('Whitfield'),('Whitley'),('Whitney'),('Wiggins'),('Wilcox'),('Wilder'),('Wiley'),('Wilkerson'),('Wilkins'),('Wilkinson'),('William'),('Williams'),('Williamson'),('Willis'),('Wilson'),('Winters'),('Wise'),('Witt'),('Wolf'),('Wolfe'),('Wong'),('Wood'),('Woodard'),('Woods'),('Woodward'),('Wooten'),('Workman'),('Wright'),('Wyatt'),('Wynn'),('Yang'),('Yates'),('York'),('Young'),('Zamora'),('Zimmerman')
- ";
-
- $response = Core::$db->query($queries, $rollbackQueries);
-
- if ($response["success"]) {
- return array(true, "");
- } else {
- return array(false, $response["errorMessage"]);
- }
- }
-
-
- public function getHelpHTML() {
- $content =<<
- {$this->L["help_intro"]}
-
-
-
-
- Name
- {$this->L["type_Name"]}
-
-
- MaleName
- {$this->L["type_MaleName"]}
-
-
- FemaleName
- {$this->L["type_FemaleName"]}
-
-
- Initial
- {$this->L["type_Initial"]}
-
-
- Surname
- {$this->L["type_Surname"]}
-
-
-EOF;
-
- return $content;
- }
-
- public function getRestOptionsFormat() {
- return array(
- "required" => true,
- "type" => "string"
- );
- }
-
-}
diff --git a/plugins/dataTypes/Names/Names.js b/plugins/dataTypes/Names/Names.js
deleted file mode 100755
index 1ee28875e..000000000
--- a/plugins/dataTypes/Names/Names.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- var MODULE_ID = "data-type-Names";
- var LANG = L.dataTypePlugins.Names;
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _exampleChange = function(msg) {
- $("#dtOption_" + msg.rowID).val(msg.value);
- };
-
- /**
- * Called when the user submits the form to generate some data. If the selected data set contains
- * one or more rows of this data type, this function is called with the list of row numbers. Note that
- * the row numbers passed are the *original* row numbers of the rows on creation. It's possible that the
- * user has re-sorted or deleted some rows. So to get the visible row number for a row, call
- * gen._getVisibleRowOrderByRowNum(row)
- */
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- /**
- * Called when the user saves a form. This function is passed the row number of the row to
- * save. It should return a JSON object (of whatever structure is relevant).
- */
- var _saveRow = function(rowNum) {
- return {
- "example": $("#dtExample_" + rowNum).val(),
- "option": $("#dtOption_" + rowNum).val()
- };
- };
-
- /**
- * Called when a form is loaded that contains this data type. This is passed the row number and
- * the custom data type data to populate the fields. loadRow functions all must return an array
- * with two indexes - both functions:
- * [0] code to execute (generally inserting data into fields)
- * [1] a boolean test to determine WHEN the content has been inserted.
- */
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() { },
- isComplete: function() {
- if ($("#dtOption_" + rowNum).length) {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtOption_" + rowNum).val(data.option);
- return true;
- } else {
- return false;
- }
- }
- };
- };
-
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/Names/lang/de.php b/plugins/dataTypes/Names/lang/de.php
deleted file mode 100644
index 33c95ef06..000000000
--- a/plugins/dataTypes/Names/lang/de.php
+++ /dev/null
@@ -1,25 +0,0 @@
- array(
- "firstNamesFemale" => array(
- "Alessandra", "Alessia", "Alice", "Angela", "Anna", "Arianna", "Aurora", "Beatrice", "Camilla",
- "Caterina", "Chiara", "Claudia", "Cristina", "Debora", "Elena", "Eleonora", "Elisa", "Emma", "Erica",
- "Erika", "Federica", "Francesca", "Gaia", "Giada", "Ginevra", "Giorgia", "Giulia", "Giulietta", "Greta",
- "Ilaria", "Irene", "Jessica", "Lara", "Laura", "Lisa", "Lucia", "Manuela", "Margherita", "Maria",
- "Marta", "Martina", "Matilde", "Michela", "Monica", "Nicole", "Nicoletta", "Noemi", "Paola", "Rebecca",
- "Roberta", "Sara", "Serena", "Silvia", "Simona", "Sofia", "Stefania", "Valentina", "Valeria", "Vanessa",
- "Veronica", "Viola", "Vittoria"
- ),
- "firstNamesMale" => array(
- "Alberto", "Alessandro", "Alessio", "Alex", "Andrea", "Angelo", "Antonio", "Armando", "Augusto",
- "Christian", "Claudio", "Cristian", "Cristiano", "Daniele", "Dario", "Davide", "Diego", "Domenico",
- "Edoardo", "Emanuele", "Enrico", "Fabio", "Federico", "Filippo", "Francesco", "Gabriele", "Giacomo",
- "Gianluca", "Gianni", "Gianpaolo", "Gianpiero", "Giorgio", "Giovanni", "Giulio", "Giuseppe", "Jacopo",
- "Leonardo", "Lorenzo", "Luca", "Lucio", "Luigi", "Manuel", "Marco", "Mario", "Marcello", "Matteo",
- "Mattia", "Michele", "Mirko", "Nicola", "Nicolò", "Paolo", "Pietro", "Riccardo", "Roberto", "Salvatore",
- "Samuel", "Samuele", "Simone", "Stefano", "Tommaso", "Valerio", "Vincenzo"
- ),
- "lastNames" => array(
- "Agostini", "Aiello", "Albanese", "Amato", "Antonelli", "Arena", "Baldi", "Barbieri", "Barone", "Basile",
- "Battaglia", "Bellini", "Benedetti", "Bernardi", "Bianchi", "Bianco", "Brambilla", "Bruni", "Bruno",
- "Calabrese", "Caputo", "Carbone", "Caruso", "Castelli", "Catalano", "Cattaneo", "Cavallo", "Ceccarelli",
- "Cirillo", "Colombo", "Conte", "Conti", "Coppola", "Costa", "Costantini", "De Angelis", "De Luca",
- "De Rosa", "De Santis", "De Simone", "Di Stefano", "Donati", "Esposito", "Fabbri", "Farina", "Ferrante",
- "Ferrara", "Ferrari", "Ferraro", "Ferrero", "Ferretti", "Ferri", "Ferro", "Fiore", "Fontana", "Franco",
- "Fumagalli", "Fusco", "Galli", "Gallo", "Gargiulo", "Garofalo", "Gatti", "Gentile", "Giordano", "Giorgi",
- "Giuliani", "Grassi", "Grasso", "Greco", "Grimaldi", "Guerra", "Guidi", "Leone", "Lombardi", "Lombardo",
- "Longo", "Lorusso", "Mancini", "Marchetti", "Marchi", "Mariani", "Marini", "Marino", "Marra", "Martinelli",
- "Martini", "Martino", "Mazza", "Mele", "Meloni", "Messina", "Milani", "Monaco", "Montanari", "Monti",
- "Morelli", "Moretti", "Moro", "Napolitano", "Neri", "Olivieri", "Orlando", "Pace", "Pagano", "Palmieri",
- "Palumbo", "Parisi", "Pastore", "Pellegrini", "Pellegrino", "Pepe", "Perrone", "Piazza", "Piccolo",
- "Pinna", "Piras", "Poli", "Pozzi", "Proietti", "Ricci", "Ricciardi", "Rinaldi", "Riva", "Rizzi", "Rizzo",
- "Romano", "Romeo", "Rossetti", "Rossi", "Ruggeri", "Ruggiero", "Russo", "Sala", "Sanna", "Santini",
- "Santoro", "Sartori", "Serra", "Silvestri", "Sorrentino", "Testa", "Valente", "Valentini", "Villa",
- "Villani", "Vitale", "Vitali", "Volpe", "Zanetti"
- )
- ),
- "france" => array(
- "firstNamesFemale" => array(
- "Agathe", "Alexandra", "Alexia", "Alice", "Alicia", "Amandine", "Ambre", "Amélie", "Anaël", "Anaëlle",
- "Anaïs", "Angelina", "Anna", "Bienvenue", "Candice", "Capucine", "Carla", "Catherine", "Charlotte",
- "Chaïma", "Chloé", "Clara", "Clotilde", "Cloé", "Clémence", "Célia", "Edwige", "Elsa", "Emma", "Eva",
- "Fanny", "Françoise", "Guillemette", "Inès", "Jade", "Jasmine", "Jeanne", "Julia", "Julie", "Juliette",
- "Justine", "Katell", "Kimberley", "Lamia", "Lana", "Laura", "Lauriane", "Lena", "Lilou", "Lily", "Lina",
- "Lisa", "Loane", "Lola", "Lou", "Louise", "Louna", "Lucie", "Luna", "Lutécia", "Léa", "Léane", "Léonie",
- "Maelys", "Manon", "Margaux", "Margot", "Marie", "Marine", "Marion", "Maryam", "Mathilde", "Maéva",
- "Maëlle", "Maïlé", "Maïwenn", "Mélanie", "Mélissa", "Nina", "Noémie", "Océane", "Pauline", "Romane",
- "Rosalie", "Rose", "Salomé", "Sara", "Sarah", "Solene", "Syrine", "Tatiana", "Valentine", "Yasmine",
- "Yüna", "Zoé", "Élisa", "Élise", "Éloïse", "Éléna", "Émilie"
- ),
- "firstNamesMale" => array(
- "Aaron", "Adam", "Adrian", "Adrien", "Alexandre", "Alexis", "Amine", "Anthony", "Antoine", "Antonin",
- "Arthur", "Baptiste", "Bastien", "Benjamin", "Bruno", "Clément", "Colin", "Constant", "Corentin",
- "Cédric", "Davy", "Diego", "Dimitri", "Dorian", "Dylan", "Enzo", "Erwan", "Esteban", "Ethan", "Evan",
- "Florentin", "Florian", "Félix", "Gabin", "Gabriel", "Gaspard", "Gilbert", "Grégory", "Guillaume",
- "Hugo", "Jordan", "Jules", "Julien", "Jérémy", "Kevin", "Kilian", "Killian", "Kylian", "Kyllian",
- "Lilian", "Loevan", "Lorenzo", "Louis", "Lucas", "Léo", "Léon", "Léonard", "Macéo", "Malik", "Malo",
- "Martin", "Marwane", "Mathieu", "Mathis", "Mathéo", "Mattéo", "Maxence", "Maxime", "Mehdi", "Mohamed",
- "Nathan", "Nicolas", "Noah", "Nolan", "Noë", "Paul", "Pierre", "Quentin", "Renaud", "Robin", "Romain",
- "Roméo", "Rémi", "Samuel", "Simon", "Thibault", "Thomas", "Théo", "Timothée", "Timéo", "Titouan",
- "Tom", "Tristan", "Valentin", "Victor", "Yanis", "Yohan", "Zacharis", "Élouan", "Émile"
- ),
- "lastNames" => array(
- "Adam", "Albert", "Andre", "Arnaud", "Aubert", "Aubry", "Bailly", "Barbier", "Baron", "Barre", "Benoit",
- "Berger", "Bernard", "Bertrand", "Blanc", "Blanchard", "Bonnet", "Boucher", "Boulanger", "Bourgeois",
- "Bouvier", "Boyer", "Breton", "Brun", "Brunet", "Caron", "Carpentier", "Carre", "Charles", "Charpentier",
- "Chevalier", "Chevallier", "Clement", "Colin", "Collet", "Collin", "Cordier", "Cousin", "Daniel",
- "David", "Denis", "Deschamps", "Dubois", "Dufour", "Dumas", "Dumont", "Dupont", "Dupuis", "Dupuy",
- "Durand", "Duval", "Etienne", "Evrard", "Fabre", "Faure", "Fernandez", "Fleury", "Fontaine", "Fournier",
- "Francois", "Gaillard", "Garcia", "Garnier", "Gauthier", "Gautier", "Gay", "Gerard", "Germain", "Gillet",
- "Girard", "Giraud", "Gomez", "Gonzalez", "Guerin", "Guillaume", "Guillot", "Guyot", "Henry", "Herve",
- "Hubert", "Huet", "Humbert", "Jacob", "Jacquet", "Jean", "Joly", "Julien", "Klein", "Lacroix", "Laine",
- "Lambert", "Laurent", "Le gall", "Le goff", "Le roux", "Lebrun", "Leclerc", "Leclercq", "Lecomte",
- "Lefebvre", "Lefevre", "Legrand", "Lemaire", "Lemoine", "Leroux", "Leroy", "Leveque", "Lopez", "Louis",
- "Lucas", "Maillard", "Mallet", "Marchal", "Marchand", "Marechal", "Marie", "Martin", "Martinez",
- "Marty", "Masson", "Mathieu", "Menard", "Mercier", "Meunier", "Meyer", "Michel", "Millet", "Moreau",
- "Morel", "Morin", "Moulin", "Muller", "Nguyen", "Nicolas", "Noel", "Olivier", "Paris", "Pasquier",
- "Paul", "Pereira", "Perez", "Perrin", "Perrot", "Petit", "Philippe", "Picard", "Pierre", "Poirier",
- "Pons", "Poulain", "Prevost", "Remy", "Renard", "Renaud", "Renault", "Rey", "Richard", "Riviere",
- "Robert", "Robin", "Roche", "Rodriguez", "Roger", "Rolland", "Rousseau", "Roussel", "Roux", "Roy",
- "Royer", "Sanchez", "Schmitt", "Schneider", "Simon", "Thomas", "Vasseur", "Vidal", "Vincent", "Weber"
- )
- )
- );
- private $generalMaleNames = array();
- private $generalFemaleNames = array();
- private $generalFirstNames = array();
- private $generalLastNames = array();
-
-
- /**
- * @param string $runtimeContext "generation" or "ui"
- */
- public function __construct($runtimeContext) {
- parent::__construct($runtimeContext);
- if ($runtimeContext == "generation") {
- self::initFirstNames();
- self::initLastNames();
- self::combineRegionalFirstNames();
- }
- }
-
- public function generate($generator, $generationContextData) {
- $placeholderStr = $generationContextData["generationOptions"];
- $selectedCountryPlugins = $generator->getCountries();
-
- $rowCountryInfo = array();
- while (list($key, $info) = each($generationContextData["existingRowData"])) {
- if ($info["dataTypeFolder"] == "Country") {
- $rowCountryInfo = $info;
- break;
- }
- }
-
- $maleNames = $this->generalMaleNames;
- $femaleNames = $this->generalFemaleNames;
- $firstNames = $this->generalFirstNames;
- $lastNames = $this->generalLastNames;
-
- // if there's a country for this row,
- $countrySlug = "";
- if (!empty($rowCountryInfo) && isset($rowCountryInfo["randomData"]["slug"])) {
- if (array_key_exists($rowCountryInfo["randomData"]["slug"], $this->regionalNames)) {
- $countrySlug = $rowCountryInfo["randomData"]["slug"];
- }
- } else if (!empty($selectedCountryPlugins)) {
- $availableRegionalCountries = array_keys($this->regionalNames);
- $inBoth = array_intersect($availableRegionalCountries, $selectedCountryPlugins);
-
- if (!empty($inBoth)) {
- $countrySlug = $inBoth[mt_rand(0, count($inBoth) - 1)];
- }
- }
-
- if (!empty($countrySlug)) {
- $maleNames = $this->regionalNames[$countrySlug]["firstNamesMale"];
- $femaleNames = $this->regionalNames[$countrySlug]["firstNamesFemale"];
- $firstNames = $this->regionalNames[$countrySlug]["firstNames"];
- $lastNames = $this->regionalNames[$countrySlug]["lastNames"];
- }
-
-
- while (preg_match("/MaleName/", $placeholderStr)) {
- $placeholderStr = preg_replace("/MaleName/", $this->getRandomFirstName($maleNames), $placeholderStr, 1);
- }
- while (preg_match("/FemaleName/", $placeholderStr)) {
- $placeholderStr = preg_replace("/FemaleName/", $this->getRandomFirstName($femaleNames), $placeholderStr, 1);
- }
- while (preg_match("/Name/", $placeholderStr)) {
- $placeholderStr = preg_replace("/Name/", $this->getRandomFirstName($firstNames), $placeholderStr, 1);
- }
- while (preg_match("/Surname/", $placeholderStr)) {
- $placeholderStr = preg_replace("/Surname/", $lastNames[mt_rand(0, count($lastNames)-1)], $placeholderStr, 1);
- }
- while (preg_match("/Initial/", $placeholderStr)) {
- $placeholderStr = preg_replace("/Initial/", $this->letters[mt_rand(0, strlen($this->letters)-1)], $placeholderStr, 1);
- }
-
- // in case the user entered multiple | separated formats, pick one
- $formats = explode("|", $placeholderStr);
- $chosenFormat = $formats[0];
- if (count($formats) > 1) {
- $chosenFormat = $formats[mt_rand(0, count($formats)-1)];
- }
-
- return array(
- "display" => trim($chosenFormat)
- );
- }
-
-
- public function getRowGenerationOptions($generator, $post, $colNum, $numCols) {
- if (!isset($post["dtOption_$colNum"]) || empty($post["dtOption_$colNum"])) {
- return false;
- }
- return $post["dtOption_$colNum"];
- }
-
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255) default NULL",
- "SQLField_Oracle" => "varchar2(255) default NULL",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $html =<<< END
-
- {$L["please_select"]}
- {$this->L["example_MaleName"]}
- {$this->L["example_FemaleName"]}
- {$this->L["example_Name"]}
- {$this->L["example_MaleName_Surname"]}
- {$this->L["example_FemaleName_Surname"]}
- {$this->L["example_Name_Surname"]}
- {$this->L["example_Name_Initial_Surname"]}
- {$this->L["example_surname"]}
- {$this->L["example_Surname_Name_Initial"]}
- {$this->L["example_Name4"]}
- {$this->L["example_fullnames"]}
-
-END;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- return ' ';
- }
-
- public function getNames() {
- return $this->firstNames;
- }
-
- public function getFirstNames() {
- return $this->firstNames;
- }
-
- public function getLastNames() {
- return $this->lastNames;
- }
-
- /**
- * Called when instantiating the plugin during data generation. Set the firstNames, maleNames and
- * femaleNames.
- */
- private function initFirstNames() {
- $prefix = Core::getDbTablePrefix();
- $response = Core::$db->query("
- SELECT *
- FROM {$prefix}first_names
- ");
-
- if ($response["success"]) {
- $names = array();
- $maleNames = array();
- $femaleNames = array();
- while ($row = mysqli_fetch_assoc($response["results"])) {
- $gender = $row["gender"];
- $name = $row["first_name"];
-
- $names[] = $name;
- if ($gender == "male") {
- $maleNames[] = $name;
- } else {
- $femaleNames[] = $name;
- }
- }
-
- $this->generalFirstNames = $names;
- $this->generalMaleNames = $maleNames;
- $this->generalFemaleNames = $femaleNames;
- }
- }
-
-
- private function initLastNames() {
- $prefix = Core::getDbTablePrefix();
- $response = Core::$db->query("
- SELECT *
- FROM {$prefix}last_names
- ");
-
- if ($response["success"]) {
- $lastNames = array();
- while ($row = mysqli_fetch_assoc($response["results"])) {
- $lastNames[] = $row["last_name"];
- }
- $this->generalLastNames = $lastNames;
- }
- }
-
- /**
- * Called on instantiation. This combines the male and female first names in the $regionalNames hash
- * into a single "first_names" key, for quick reference.
- */
- private function combineRegionalFirstNames() {
- $updatedRegionalNames = array();
- while (list($country, $content) = each($this->regionalNames)) {
- $content["firstNames"] = array_merge($content["firstNamesFemale"], $content["firstNamesMale"]);
- $updatedRegionalNames[$country] = $content;
- }
- $this->regionalNames = $updatedRegionalNames;
- }
-
-
- private function getRandomFirstName($nameArray) {
- return $nameArray[mt_rand(0, count($nameArray)-1)];
- }
-
- /**
- * Called during installation. This creates and populates the first_names and last_names DB tables.
- *
- * @return array [0] success / error (boolean)
- * [1] the error message, if there was an error
- */
- public static function install() {
- $prefix = Core::getDbTablePrefix();
-
- // always clear out the previous tables, just in case
- $rollbackQueries = array();
- $rollbackQueries[] = "DROP TABLE {$prefix}first_names";
- $rollbackQueries[] = "DROP TABLE {$prefix}last_names";
- Core::$db->query($rollbackQueries);
-
- $queries = array();
- $queries[] = "
- CREATE TABLE {$prefix}first_names (
- id mediumint(9) NOT NULL auto_increment,
- first_name varchar(50) NOT NULL default '',
- gender enum('male','female','both') NOT NULL default 'male',
- PRIMARY KEY (id)
- )
- ";
- $queries[] = "
- INSERT INTO {$prefix}first_names (first_name, gender)
- VALUES ('Aaron','male'),('Abbot','male'),('Abdul','male'),('Abel','male'),('Abigail','female'),('Abra','female'),('Abraham','male'),('Acton','male'),('Adam','male'),('Adara','female'),('Addison','male'),('Adele','female'),('Adena','female'),('Adria','female'),('Adrian','male'),('Adrienne','female'),('Ahmed','male'),('Aidan','male'),('Aiko','female'),('Aileen','female'),('Aimee','female'),('Ainsley','female'),('Akeem','male'),('Aladdin','male'),('Alan','male'),('Alana','female'),('Alden','male'),('Alea','female'),('Alec','male'),('Alexa','female'),('Alexander','male'),('Alexandra','female'),('Alexis','female'),('Alfonso','male'),('Alfreda','female'),('Ali','male'),('Alice','female'),('Alika','female'),('Aline','female'),('Alisa','female'),('Allegra','female'),('Allen','male'),('Allistair','male'),('Alma','female'),('Althea','female'),('Alvin','male'),('Alyssa','female'),('Amal','male'),('Amanda','female'),('Amaya','female'),('Amber','female'),('Amela','female'),('Amelia','female'),('Amena','female'),('Amery','male'),('Amethyst','female'),('Amir','male'),('Amity','female'),('Amos','male'),('Amy','female'),('Anastasia','female'),('Andrew','male'), ('Angela','female'),('Angelica','female'),('Anika','female'),('Anjolie','female'),('Ann','female'),('Anne','female'),('Anthony','male'),('Aphrodite','female'),('April','female'),('Aquila','male'),('Arden','male'),('Aretha','female'),('Ariana','female'),('Ariel','female'),('Aristotle','male'),('Armand','male'),('Armando','male'),('Arsenio','male'),('Arthur','male'),('Ashely','female'),('Asher','male'),('Ashton','male'),('Aspen','female'),('Astra','female'),('Athena','female'),('Aubrey','both'),('Audra','female'),('Audrey','female'),('August','male'),('Aurelia','female'),('Aurora','female'),('Austin','male'),('Autumn','female'),('Ava','female'),('Avram','male'),('Avye','female'),('Axel','male'),('Ayanna','female'),('Azalia','female'),('Baker','male'),('Barbara','female'),('Barclay','male'),('Barrett','male'),('Barry','male'),('Basia','female'),('Basil','male'),('Baxter','male'),('Beatrice','female'),('Beau','male'),('Beck','male'),('Bell','female'),('Belle','female'),('Benedict','male'),('Benjamin','male'),('Berk','male'),('Bernard','male'),('Bert','male'),('Bertha','female'),('Bethany','female'),('Beverly','female'),('Bevis','male'),('Bianca','female'),('Blaine','both'),('Blair','both'),('Blake','male'),('Blaze','male'),('Blossom','female'),('Blythe','female'),('Bo','female'),('Boris','male'),('Bradley','male'),('Brady','male'),('Branden','male'),('Brandon','male'),('Breanna','female'),('Bree','female'),('Brenda','female'),('Brendan','male'),('Brenden','male'),('Brenna','female'),('Brennan','male'),('Brent','male'),('Brett','male'),('Brian','male'),('Brianna','female'),('Briar','female'),('Brielle','female'),('Britanney','female'),('Britanni','female'),('Brittany','female'),('Brock','male'),('Brody','male'),('Brooke','female'),('Bruce','male'),('Bruno','male'),('Bryar','female'),('Brynn','female'),('Brynne','female'),('Buckminster','male'),('Buffy','female'),('Burke','male'),('Burton','male'),('Byron','male'),('Cade','male'),('Cadman','male'),('Caesar','male'),('Cailin','female'),('Cain','male'),('Cairo','male'),('Caldwell','male'),('Caleb','male'),('Calista','female'),('Callie','female'),('Callum','male'),('Cally','female'),('Calvin','male'),('Camden','male'),('Cameran','female'),('Cameron','female'),('Cameron','male'),('Camilla','female'),('Camille','female'),('Candace','female'),('Candice','female'),('Cara','female'),('Carissa','female'),('Carl','male'),('Carla','female'),('Carlos','male'),('Carly','female'),('Carol','female'),('Carolyn','female'),('Carson','male'),('Carter','male'),('Caryn','female'),('Casey','both'),('Cassady','female'),('Cassandra','female'),('Cassidy','female'),('Castor','male'),('Catherine','female'),('Cathleen','female'),('Cecilia','female'),('Cedric','male'),('Celeste','female'),('Chadwick','male'),('Chaim','male'),('Chancellor','male'),('Chanda','female'),('Chandler','male'),('Chaney','male'),('Channing','male'),('Chantale','female'),('Charde','female'),('Charissa','female'),('Charity','female'),('Charles','male'),('Charlotte','female'),('Chase','male'),('Chastity','female'),('Chava','female'),('Chelsea','female'),('Cherokee','female'),('Cheryl','female'),('Chester','male'),('Cheyenne','female'),('Chiquita','female'),('Chloe','female'),('Christen','female'),('Christian','male'),('Christine','female'),('Christopher','male'),('Ciara','female'),('Ciaran','male'),('Claire','female'),('Clare','female'),('Clark','male'),('Clarke','male'),('Claudia','female'),('Clayton','male'),('Clementine','female'),('Cleo','female'),('Clinton','male'),('Clio','female'),('Coby','male'),('Cody','male'),('Colby','male'),('Cole','male'),('Colette','female'),('Colin','male'),('Colleen','female'),('Colorado','male'),('Colt','male'),('Colton','male'),('Conan','male'),('Connor','male'),('Constance','female'),('Cooper','male'),('Cora','female'),('Courtney','female'),('Craig','male'),('Cruz','male'),('Cullen','male'),('Curran','male'),('Cynthia','female'),('Cyrus','male'),('Dacey','female'),('Dahlia','female'),('Dai','female'),('Dakota','both'),('Dale','male'),('Dalton','male'),('Damian','male'),('Damon','male'),('Dana','female'),('Dane','male'),('Daniel','male'),('Danielle','female'),('Dante','male'),('Daphne','female'),('Daquan','male'),('Dara','female'),('Daria','female'),('Darius','male'),('Darrel','female'),('Darryl','female'),('Daryl','female'),('David','male'),('Davis','male'),('Dawn','female'),('Deacon','male'),('Dean','male'),('Deanna','female'),('Deborah','female'),('Debra','female'),('Declan','male'),('Deirdre','female'),('Delilah','female'),('Demetria','female'),('Demetrius','male'),('Denise','female'),('Dennis','male'),('Denton','male'),('Derek','male'),('Desirae','female'),('Desiree','female'),('Destiny','female'),('Devin','male'),('Dexter','male'),('Diana','female'),('Dieter','male'),('Dillon','male'),('Dolan','male'),('Dominic','male'),('Dominique','female'),('Donna','female'),('Donovan','male'),('Dora','female'),('Dorian','male'),('Doris','female'),('Dorothy','female'),('Drake','male'),('Drew','male'),('Driscoll','male'),('Duncan','male'),('Dustin','male'),('Dylan','male'),('Eagan','male'),('Eaton','male'),('Ebony','female'),('Echo','female'),('Edan','male'),('Eden','both'),('Edward','male'),('Elaine','female'),('Eleanor','female'),('Eliana','female'),('Elijah','male'),('Elizabeth','female'),('Ella','female'),('Elliott','male'),('Elmo','male'),('Elton','male'),('Elvis','male'),('Emerald','female'),('Emerson','male'),('Emery','male'),('Emi','female'),('Emily','female'),('Emma','female'),('Emmanuel','male'),('Erasmus','male'),('Eric','male'),('Erica','female'),('Erich','male'),('Erin','female'),('Ethan','male'),('Eugenia','female'),('Evan','male'),('Evangeline','female'),('Eve','female'),('Evelyn','female'),('Ezekiel','male'),('Ezra','male'),('Faith','female'),('Fallon','female'),('Farrah','female'),('Fatima','female'),('Fay','female'),('Felicia','female'),('Felix','male'),('Ferdinand','male'),('Ferris','male'),('Finn','male'),('Fiona','female'),('Fitzgerald','male'),('Flavia','female'),('Fletcher','male'),('Fleur','female'),('Florence','female'),('Flynn','male'),('Forrest','male'),('Frances','female'),('Francesca','female'),('Francis','male'),('Fredericka','female'),('Freya','female'),('Fritz','male'),('Fuller','male'),('Fulton','male'),('Gabriel','male'),('Gage','male'),('Gail','female'),('Galena','female'),('Galvin','male'),('Gannon','male'),('Gareth','male'),('Garrett','male'),('Garrison','male'),('Garth','male'),('Gary','male'),('Gavin','male'),('Gay','female'),('Gemma','female'),('Genevieve','female'),('Geoffrey','male'),('George','male'),('Georgia','female'),('Geraldine','female'),('Germaine','female'),('Germane','female'),('Giacomo','male'),('Gil','male'),('Gillian','female'),('Ginger','female'),('Gisela','female'),('Giselle','female'),('Glenna','female'),('Gloria','female'),('Grace','female'),('Grady','male'),('Graham','male'),('Graiden','male'),('Grant','male'),('Gray','male'),('Gregory','male'),('Gretchen','female'),('Griffin','male'),('Griffith','male'),('Guinevere','female'),('Guy','male'),('Gwendolyn','female'),('Hadassah','female'),('Hadley','female'),('Hakeem','male'),('Halee','female'),('Haley','female'),('Hall','male'),('Halla','female'),('Hamilton','male'),('Hamish','male'),('Hammett','male'),('Hanae','female'),('Hanna','female'),('Hannah','female'),('Harding','male'),('Harlan','male'),('Harper','male'),('Harriet','female'),('Harrison','male'),('Hasad','male'),('Hashim','male'),('Haviva','female'),('Hayden','male'),('Hayes','male'),('Hayfa','female'),('Hayley','female'),('Heather','female'),('Hector','male'),('Hedda','female'),('Hedley','male'),('Hedwig','female'),('Hedy','female'),('Heidi','female'),('Helen','female'),('Henry','male'),('Herman','male'),('Hermione','female'),('Herrod','male'),('Hilary','female'),('Hilda','female'),('Hilel','male'),('Hillary','female'),('Hiram','male'),('Hiroko','female'),('Hollee','female'),('Holly','female'),('Holmes','male'),('Honorato','male'),('Hop','male'),('Hope','female'),('Howard','male'),('Hoyt','male'),('Hu','male'),('Hunter','male'),('Hyacinth','female'),('Hyatt','male'),('Ian','male'),('Idola','female'),('Idona','female'),('Ifeoma','female'),('Ignacia','female'),('Ignatius','male'),('Igor','male'),('Ila','female'),('Iliana','female'),('Illana','female'),('Illiana','female'),('Ima','female'),('Imani','female'),('Imelda','female'),('Imogene','female'),('Ina','female'),('India','female'),('Indigo','female'),('Indira','female'),('Inez','female'),('Inga','female'),('Ingrid','female'),('Iola','female'),('Iona','female'),('Ira','male'),('Irene','female'),('Iris','female'),('Irma','female'),('Isaac','male'),('Isabella','female'),('Isabelle','female'),('Isadora','female'),('Isaiah','male'),('Ishmael','male'),('Ivan','male'),('Ivana','female'),('Ivor','male'),('Ivory','female'),('Ivy','female'),('Jack','male'),('Jackson','male'),('Jacob','male'),('Jada','female'),('Jade','female'),('Jaden','female'),('Jael','female'),('Jaime','female'),('Jakeem','male'),('Jamal','male'),('Jamalia','female'),('James','male'),('Jameson','male'),('Jana','female'),('Jane','female'),('Janna','female'),('Jaquelyn','female'),('Jared','male'),('Jarrod','male'),('Jasmine','female'),('Jason','male'),('Jasper','male'),('Jayme','female'),('Jeanette','female'),('Jelani','male'),('Jemima','female'),('Jena','female'),('Jenette','female'),('Jenna','female'),('Jennifer','female'),('Jeremy','male'),('Jermaine','male'),('Jerome','male'),('Jerry','male'),('Jescie','female'),('Jessamine','female'),('Jesse','male'),('Jessica','female'),('Jillian','female'),('Jin','male'),('Joan','female'),('Jocelyn','female'),('Joel','male'),('Joelle','female'),('John','male'),('Jolene','female'),('Jolie','female'),('Jonah','male'),('Jonas','male'),('Jordan','female'),('Jordan','male'),('Jorden','female'),('Joseph','male'),('Josephine','female'),('Joshua','male'),('Josiah','male'),('Joy','female'),('Judah','male'),('Judith','female'),('Julian','male'),('Julie','female'),('Juliet','female'),('Justin','male'),('Justina','female'),('Justine','female'),('Kadeem','male'),('Kaden','both'),('Kai','female'),('Kaitlin','female'),('Kalia','female'),('Kamal','male'),('Kameko','female'),('Kane','male'),('Kareem','male'),('Karen','female'),('Karina','female'),('Karleigh','female'),('Karly','female'),('Karyn','female'),('Kaseem','male'),('Kasimir','male'),('Kasper','male'),('Katell','female'),('Katelyn','female'),('Kathleen','female'),('Kato','male'),('Kay','female'),('Kaye','female'),('Keane','male'),('Keaton','male'),('Keefe','male'),('Keegan','male'),('Keelie','female'),('Keely','female'),('Keiko','female'),('Keith','male'),('Kellie','female'),('Kelly','female'),('Kelly','male'),('Kelsey','female'),('Kelsie','female'),('Kendall','both'),('Kennan','male'),('Kennedy','male'),('Kenneth','male'),('Kenyon','male'),('Kermit','male'),('Kerry','female'),('Kessie','female'),('Kevin','male'),('Kevyn','female'),('Kiara','female'),('Kiayada','female'),('Kibo','male'),('Kieran','male'),('Kim','female'),('Kimberley','female'),('Kimberly','female'),('Kiona','female'),('Kirby','female'),('Kirestin','female'),('Kirk','male'),('Kirsten','female'),('Kitra','female'),('Knox','male'),('Kristen','female'),('Kuame','male'),('Kyla','female'),('Kylan','female'),('Kyle','male'),('Kylee','female'),('Kylie','female'),('Kylynn','female'),('Kyra','female'),('Lacey','female'),('Lacota','female'),('Lacy','female'),('Lael','female'),('Laith','male'),('Lamar','male'),('Lana','female'),('Lance','male'),('Lane','male'),('Lani','female'),('Lara','female'),('Lareina','female'),('Larissa','female'),('Lars','male'),('Latifah','female'),('Laura','female'),('Laurel','female'),('Lavinia','female'),('Lawrence','male'),('Leah','female'),('Leandra','female'),('Lee','female'),('Lee','male'),('Leigh','female'),('Leila','female'),('Leilani','female'),('Len','male'),('Lenore','female'),('Leo','male'),('Leonard','male'),('Leroy','male'),('Lesley','female'),('Leslie','female'),('Lester','male'),('Lev','male'),('Levi','male'),('Lewis','male'),('Libby','female'),('Liberty','female'),('Lila','female'),('Lilah','female'),('Lillian','female'),('Lillith','female'),('Linda','female'),('Linus','male'),('Lionel','male'),('Lisandra','female'),('Logan','male'),('Lois','female'),('Louis','male'),('Lucas','male'),('Lucian','male'),('Lucius','male'),('Lucy','female'),('Luke','male'),('Lunea','female'),('Lydia','female'),('Lyle','male'),('Lynn','female'),('Lysandra','female'),('MacKensie','female'),('MacKenzie','female'),('Macaulay','male'),('Macey','female'),('Macon','male'),('Macy','female'),('Madaline','female'),('Madeline','female'),('Madeson','female'),('Madison','female'),('Madonna','female'),('Magee','male'),('Maggie','female'),('Maggy','female'),('Maia','female'),('Maile','female'),('Maisie','female'),('Maite','female'),('Malachi','male'),('Malcolm','male'),('Malik','male'),('Mallory','female'),('Mannix','male'),('Mara','female'),('Marah','female'),('Marcia','female'),('Margaret','female'),('Mari','female'),('Mariam','female'),('Mariko','female'),('Maris','female'),('Mark','male'),('Marny','female'),('Marsden','male'),('Marshall','male'),('Martena','female'),('Martha','female'),('Martin','male'),('Martina','female'),('Marvin','male'),('Mary','female'),('Maryam','female'),('Mason','male'),('Matthew','male'),('Maxine','female'),('Maxwell','male'),('May','female'),('Maya','female'),('McKenzie','female'),('Mechelle','female'),('Medge','female'),('Megan','female'),('Meghan','female'),('Melanie','female'),('Melinda','female'),('Melissa','female'),('Melodie','female'),('Melvin','male'),('Melyssa','female'),('Mercedes','female'),('Meredith','female'),('Merrill','male'),('Merritt','male'),('Mia','female'),('Micah','male'),('Michael','male'),('Michelle','female'),('Mikayla','female'),('Minerva','female'),('Mira','female'),('Miranda','female'),('Miriam','female'),('Moana','female'),('Mohammad','male'),('Mollie','female'),('Molly','female'),('Mona','female'),('Montana','female'),('Morgan','female'),('Moses','male'),('Mufutau','male'),('Murphy','male'),('Myles','male'),('Myra','female'),('Nadine','female'),('Naida','female'),('Naomi','female'),('Nash','male'),('Nasim','male'),('Natalie','female'),('Nathan','male'),('Nathaniel','male'),('Nayda','female'),('Nehru','male'),('Neil','male'),('Nell','female'),('Nelle','female'),('Nerea','female'),('Nero','male'),('Nevada','female'),('Neve','female'),('Neville','male'),('Nicholas','male'),('Nichole','female'),('Nicole','female'),('Nigel','male'),('Nina','female'),('Nissim','male'),('Nita','female'),('Noah','male'),('Noble','male'),('Noel','female'),('Noelani','female'),('Noelle','female'),('Nola','female'),('Nolan','male'),('Nomlanga','female'),('Nora','female'),('Norman','male'),('Nyssa','female'),('Ocean','female'),('Octavia','female'),('Octavius','male'),('Odessa','female'),('Odette','female'),('Odysseus','male'),('Oleg','male'),('Olga','female'),('Oliver','male'),('Olivia','female'),('Olympia','female'),('Omar','male'),('Oprah','female'),('Ora','female'),('Oren','male'),('Ori','female'),('Orla','female'),('Orlando','male'),('Orli','female'),('Orson','male'),('Oscar','male'),('Otto','male'),('Owen','male'),('Paki','male'),('Palmer','male'),('Paloma','female'),('Pamela','female'),('Pandora','female'),('Pascale','female'),('Patience','female'),('Patricia','female'),('Patrick','male'),('Paul','male'),('Paula','female'),('Pearl','female'),('Penelope','female'),('Perry','male'),('Peter','male'),('Petra','female'),('Phelan','male'),('Philip','male'),('Phillip','male'),('Phoebe','female'),('Phyllis','female'),('Piper','female'),('Plato','male'),('Porter','male'),('Portia','female'),('Prescott','male'),('Preston','male'),('Price','male'),('Priscilla','female'),('Quail','female'),('Quamar','male'),('Quemby','female'),('Quentin','male'),('Quin','female'),('Quincy','both'),('Quinlan','male'),('Quinn','female'),('Quinn','male'),('Quintessa','female'),('Quon','female'),('Quyn','female'),('Quynn','female'),('Rachel','female'),('Rae','female'),('Rafael','male'),('Rahim','male'),('Raja','male'),('Rajah','male'),('Ralph','male'),('Rama','female'),('Ramona','female'),('Rana','female'),('Randall','male'),('Raphael','male'),('Rashad','male'),('Raven','female'),('Ray','male'),('Raya','female'),('Raymond','male'),('Reagan','female'),('Rebecca','female'),('Rebekah','female'),('Reece','male'),('Reed','male'),('Reese','male'),('Regan','female'),('Regina','female'),('Remedios','female'),('Renee','female'),('Reuben','male'),('Rhea','female'),('Rhiannon','female'),('Rhoda','female'),('Rhona','female'),('Rhonda','female'),('Ria','female'),('Richard','male'),('Rigel','male'),('Riley','female'),('Rina','female'),('Rinah','female'),('Risa','female'),('Roanna','female'),('Roary','female'),('Robert','male'),('Robin','female'),('Rogan','male'),('Ronan','male'),('Rooney','male'),('Rosalyn','female'),('Rose','female'),('Ross','male'),('Roth','male'),('Rowan','female'),('Ruby','female'),('Rudyard','male'),('Russell','male'),('Ruth','female'),('Ryan','male'),('Ryder','male'),('Rylee','female'),('Sacha','female'),('Sade','female'),('Sage','female'),('Salvador','male'),('Samantha','female'),('Samson','male'),('Samuel','male'),('Sandra','female'),('Sara','female'),('Sarah','female'),('Sasha','female'),('Savannah','female'),('Sawyer','male'),('Scarlet','female'),('Scarlett','female'),('Scott','male'),('Sean','male'),('Sebastian','male'),('Selma','female'),('September','female'),('Serena','female'),('Serina','female'),('Seth','male'),('Shad','male'),('Shaeleigh','female'),('Shafira','female'),('Shaine','female'),('Shana','female'),('Shannon','female'),('Sharon','female'),('Shay','female'),('Shea','female'),('Sheila','female'),('Shelby','female'),('Shelley','female'),('Shellie','female'),('Shelly','female'),('Shoshana','female'),('Sierra','female'),('Signe','female'),('Sigourney','female'),('Silas','male'),('Simon','male'),('Simone','female'),('Skyler','female'),('Slade','male'),('Sloane','both'),('Solomon','male'),('Sonia','female'),('Sonya','female'),('Sophia','female'),('Sopoline','female'),('Stacey','female'),('Stacy','female'),('Steel','male'),('Stella','female'),('Stephanie','female'),('Stephen','male'),('Steven','male'),('Stewart','male'),('Stone','male'),('Stuart','male'),('Suki','female'),('Summer','female'),('Susan','female'),('Sybil','female'),('Sybill','female'),('Sydnee','female'),('Sydney','female'),('Sylvester','male'),('Sylvia','female'),('TaShya','female'),('Tad','male'),('Tallulah','female'),('Talon','male'),('Tamara','female'),('Tamekah','female'),('Tana','female'),('Tanek','male'),('Tanisha','female'),('Tanner','male'),('Tanya','female'),('Tara','female'),('Tarik','male'),('Tasha','female'),('Tashya','female'),('Tate','male'),('Tatiana','female'),('Tatum','female'),('Tatyana','female'),('Taylor','both'),('Teagan','female'),('Teegan','female'),('Thaddeus','male'),('Thane','male'),('Theodore','male'),('Thomas','male'),('Thor','male'),('Tiger','male'),('Timon','male'),('Timothy','male'),('Tobias','male'),('Todd','male'),('Travis','male'),('Trevor','male'),('Troy','male'),('Tucker','male'),('Tyler','male'),('Tyrone','male'),('Ulla','female'),('Ulric','male'),('Ulysses','male'),('Uma','female'),('Unity','female'),('Upton','male'),('Uriah','male'),('Uriel','male'),('Urielle','female'),('Ursa','female'),('Ursula','female'),('Uta','female'),('Valentine','male'),('Vance','male'),('Vanna','female'),('Vaughan','male'),('Veda','female'),('Velma','female'),('Venus','female'),('Vera','female'),('Vernon','male'),('Veronica','female'),('Victor','male'),('Victoria','female'),('Vielka','female'),('Vincent','male'),('Violet','female'),('Virginia','female'),('Vivian','female'),('Vivien','female'),('Vladimir','male'),('Wade','male'),('Walker','male'),('Wallace','male'),('Walter','male'),('Wanda','female'),('Wang','male'),('Warren','male'),('Wayne','male'),('Wendy','female'),('Wesley','male'),('Whilemina','female'),('Whitney','female'),('Whoopi','female'),('Willa','female'),('William','male'),('Willow','female'),('Wilma','female'),('Wing','male'),('Winifred','female'),('Winter','female'),('Wyatt','male'),('Wylie','male'),('Wynne','female'),('Wynter','female'),('Wyoming','female'),('Xander','male'),('Xandra','female'),('Xantha','female'),('Xanthus','male'),('Xavier','male'),('Xaviera','female'),('Xena','female'),('Xenos','male'),('Xerxes','both'),('Xyla','female'),('Yael','female'),('Yardley','male'),('Yasir','male'),('Yen','female'),('Yeo','female'),('Yetta','female'),('Yoko','female'),('Yolanda','female'),('Yoshi','female'),('Yoshio','male'),('Yuli','male'),('Yuri','female'),('Yvette','female'),('Yvonne','female'),('Zachary','male'),('Zachery','male'),('Zahir','male'),('Zane','male'),('Zelda','female'),('Zelenia','female'),('Zena','female'),('Zenaida','female'),('Zenia','female'),('Zeph','male'),('Zephania','male'),('Zephr','female'),('Zeus','male'),('Zia','female'),('Zoe','female'),('Zorita','female'),('Jacqueline','female')
- ";
- $queries[] = "
- CREATE TABLE {$prefix}last_names (
- id mediumint(9) NOT NULL auto_increment,
- last_name varchar(100) NOT NULL default '',
- PRIMARY KEY (id)
- )
- ";
- $queries[] = "
- INSERT INTO {$prefix}last_names (last_name)
- VALUES ('Abbott'),('Acevedo'),('Acosta'),('Adams'),('Adkins'),('Aguilar'),('Aguirre'),('Albert'),('Alexander'),('Alford'),('Allen'),('Allison'),('Alston'),('Alvarado'),('Alvarez'),('Anderson'),('Andrews'),('Anthony'),('Armstrong'),('Arnold'),('Ashley'),('Atkins'),('Atkinson'),('Austin'),('Avery'),('Avila'),('Ayala'),('Ayers'),('Bailey'),('Baird'),('Baker'),('Baldwin'),('Ball'),('Ballard'),('Banks'),('Barber'),('Barker'),('Barlow'),('Barnes'),('Barnett'),('Barr'),('Barrera'),('Barrett'),('Barron'),('Barry'),('Bartlett'),('Barton'),('Bass'),('Bates'),('Battle'),('Bauer'),('Baxter'),('Beach'),('Bean'),('Beard'),('Beasley'),('Beck'),('Becker'),('Bell'),('Bender'),('Benjamin'),('Bennett'),('Benson'),('Bentley'),('Benton'),('Berg'),('Berger'),('Bernard'),('Berry'),('Best'),('Bird'),('Bishop'),('Black'),('Blackburn'),('Blackwell'),('Blair'),('Blake'),('Blanchard'),('Blankenship'),('Blevins'),('Bolton'),('Bond'),('Bonner'),('Booker'),('Boone'),('Booth'),('Bowen'),('Bowers'),('Bowman'),('Boyd'),('Boyer'),('Boyle'),('Bradford'),('Bradley'),('Bradshaw'),('Brady'),('Branch'),('Bray'),('Brennan'),('Brewer'),('Bridges'),('Briggs'),('Bright'),('Britt'),('Brock'),('Brooks'),('Brown'),('Browning'),('Bruce'),('Bryan'),('Bryant'),('Buchanan'),('Buck'),('Buckley'),('Buckner'),('Bullock'),('Burch'),('Burgess'),('Burke'),('Burks'),('Burnett'),('Burns'),('Burris'),('Burt'),('Burton'),('Bush'),('Butler'),('Byers'),('Byrd'),('Cabrera'),('Cain'),('Calderon'),('Caldwell'),('Calhoun'),('Callahan'),('Camacho'),('Cameron'),('Campbell'),('Campos'),('Cannon'),('Cantrell'),('Cantu'),('Cardenas'),('Carey'),('Carlson'),('Carney'),('Carpenter'),('Carr'),('Carrillo'),('Carroll'),('Carson'),('Carter'),('Carver'),('Case'),('Casey'),('Cash'),('Castaneda'),('Castillo'),('Castro'),('Cervantes'),('Chambers'),('Chan'),('Chandler'),('Chaney'),('Chang'),('Chapman'),('Charles'),('Chase'),('Chavez'),('Chen'),('Cherry'),('Christensen'),('Christian'),('Church'),('Clark'),('Clarke'),('Clay'),('Clayton'),('Clements'),('Clemons'),('Cleveland'),('Cline'),('Cobb'),('Cochran'),('Coffey'),('Cohen'),('Cole'),('Coleman'),('Collier'),('Collins'),('Colon'),('Combs'),('Compton'),('Conley'),('Conner'),('Conrad'),('Contreras'),('Conway'),('Cook'),('Cooke'),('Cooley'),('Cooper'),('Copeland'),('Cortez'),('Cote'),('Cotton'),('Cox'),('Craft'),('Craig'),('Crane'),('Crawford'),('Crosby'),('Cross'),('Cruz'),('Cummings'),('Cunningham'),('Curry'),('Curtis'),('Dale'),('Dalton'),('Daniel'),('Daniels'),('Daugherty'),('Davenport'),('David'),('Davidson'),('Davis'),('Dawson'),('Day'),('Dean'),('Decker'),('Dejesus'),('Delacruz'),('Delaney'),('Deleon'),('Delgado'),('Dennis'),('Diaz'),('Dickerson'),('Dickson'),('Dillard'),('Dillon'),('Dixon'),('Dodson'),('Dominguez'),('Donaldson'),('Donovan'),('Dorsey'),('Dotson'),('Douglas'),('Downs'),('Doyle'),('Drake'),('Dudley'),('Duffy'),('Duke'),('Duncan'),('Dunlap'),('Dunn'),('Duran'),('Durham'),('Dyer'),('Eaton'),('Edwards'),('Elliott'),('Ellis'),('Ellison'),('Emerson'),('England'),('English'),('Erickson'),('Espinoza'),('Estes'),('Estrada'),('Evans'),('Everett'),('Ewing'),('Farley'),('Farmer'),('Farrell'),('Faulkner'),('Ferguson'),('Fernandez'),('Ferrell'),('Fields'),('Figueroa'),('Finch'),('Finley'),('Fischer'),('Fisher'),('Fitzgerald'),('Fitzpatrick'),('Fleming'),('Fletcher'),('Flores'),('Flowers'),('Floyd'),('Flynn'),('Foley'),('Forbes'),('Ford'),('Foreman'),('Foster'),('Fowler'),('Fox'),('Francis'),('Franco'),('Frank'),('Franklin'),('Franks'),('Frazier'),('Frederick'),('Freeman'),('French'),('Frost'),('Fry'),('Frye'),('Fuentes'),('Fuller'),('Fulton'),('Gaines'),('Gallagher'),('Gallegos'),('Galloway'),('Gamble'),('Garcia'),('Gardner'),('Garner'),('Garrett'),('Garrison'),('Garza'),('Gates'),('Gay'),('Gentry'),('George'),('Gibbs'),('Gibson'),('Gilbert'),('Giles'),('Gill'),('Gillespie'),('Gilliam'),('Gilmore'),('Glass'),('Glenn'),('Glover'),('Goff'),('Golden'),('Gomez'),('Gonzales'),('Gonzalez'),('Good'),('Goodman'),('Goodwin'),('Gordon'),('Gould'),('Graham'),('Grant'),('Graves'),('Gray'),('Green'),('Greene'),('Greer'),('Gregory'),('Griffin'),('Griffith'),('Grimes'),('Gross'),('Guerra'),('Guerrero'),('Guthrie'),('Gutierrez'),('Guy'),('Guzman'),('Hahn'),('Hale'),('Haley'),('Hall'),('Hamilton'),('Hammond'),('Hampton'),('Hancock'),('Haney'),('Hansen'),('Hanson'),('Hardin'),('Harding'),('Hardy'),('Harmon'),('Harper'),('Harrell'),('Harrington'),('Harris'),('Harrison'),('Hart'),('Hartman'),('Harvey'),('Hatfield'),('Hawkins'),('Hayden'),('Hayes'),('Haynes'),('Hays'),('Head'),('Heath'),('Hebert'),('Henderson'),('Hendricks'),('Hendrix'),('Henry'),('Hensley'),('Henson'),('Herman'),('Hernandez'),('Herrera'),('Herring'),('Hess'),('Hester'),('Hewitt'),('Hickman'),('Hicks'),('Higgins'),('Hill'),('Hines'),('Hinton'),('Hobbs'),('Hodge'),('Hodges'),('Hoffman'),('Hogan'),('Holcomb'),('Holden'),('Holder'),('Holland'),('Holloway'),('Holman'),('Holmes'),('Holt'),('Hood'),('Hooper'),('Hoover'),('Hopkins'),('Hopper'),('Horn'),('Horne'),('Horton'),('House'),('Houston'),('Howard'),('Howe'),('Howell'),('Hubbard'),('Huber'),('Hudson'),('Huff'),('Huffman'),('Hughes'),('Hull'),('Humphrey'),('Hunt'),('Hunter'),('Hurley'),('Hurst'),('Hutchinson'),('Hyde'),('Ingram'),('Irwin'),('Jackson'),('Jacobs'),('Jacobson'),('James'),('Jarvis'),('Jefferson'),('Jenkins'),('Jennings'),('Jensen'),('Jimenez'),('Johns'),('Johnson'),('Johnston'),('Jones'),('Jordan'),('Joseph'),('Joyce'),('Joyner'),('Juarez'),('Justice'),('Kane'),('Kaufman'),('Keith'),('Keller'),('Kelley'),('Kelly'),('Kemp'),('Kennedy'),('Kent'),('Kerr'),('Key'),('Kidd'),('Kim'),('King'),('Kinney'),('Kirby'),('Kirk'),('Kirkland'),('Klein'),('Kline'),('Knapp'),('Knight'),('Knowles'),('Knox'),('Koch'),('Kramer'),('Lamb'),('Lambert'),('Lancaster'),('Landry'),('Lane'),('Lang'),('Langley'),('Lara'),('Larsen'),('Larson'),('Lawrence'),('Lawson'),('Le'),('Leach'),('Leblanc'),('Lee'),('Leon'),('Leonard'),('Lester'),('Levine'),('Levy'),('Lewis'),('Lindsay'),('Lindsey'),('Little'),('Livingston'),('Lloyd'),('Logan'),('Long'),('Lopez'),('Lott'),('Love'),('Lowe'),('Lowery'),('Lucas'),('Luna'),('Lynch'),('Lynn'),('Lyons'),('Macdonald'),('Macias'),('Mack'),('Madden'),('Maddox'),('Maldonado'),('Malone'),('Mann'),('Manning'),('Marks'),('Marquez'),('Marsh'),('Marshall'),('Martin'),('Martinez'),('Mason'),('Massey'),('Mathews'),('Mathis'),('Matthews'),('Maxwell'),('May'),('Mayer'),('Maynard'),('Mayo'),('Mays'),('Mcbride'),('Mccall'),('Mccarthy'),('Mccarty'),('Mcclain'),('Mcclure'),('Mcconnell'),('Mccormick'),('Mccoy'),('Mccray'),('Mccullough'),('Mcdaniel'),('Mcdonald'),('Mcdowell'),('Mcfadden'),('Mcfarland'),('Mcgee'),('Mcgowan'),('Mcguire'),('Mcintosh'),('Mcintyre'),('Mckay'),('Mckee'),('Mckenzie'),('Mckinney'),('Mcknight'),('Mclaughlin'),('Mclean'),('Mcleod'),('Mcmahon'),('Mcmillan'),('Mcneil'),('Mcpherson'),('Meadows'),('Medina'),('Mejia'),('Melendez'),('Melton'),('Mendez'),('Mendoza'),('Mercado'),('Mercer'),('Merrill'),('Merritt'),('Meyer'),('Meyers'),('Michael'),('Middleton'),('Miles'),('Miller'),('Mills'),('Miranda'),('Mitchell'),('Molina'),('Monroe'),('Montgomery'),('Montoya'),('Moody'),('Moon'),('Mooney'),('Moore'),('Morales'),('Moran'),('Moreno'),('Morgan'),('Morin'),('Morris'),('Morrison'),('Morrow'),('Morse'),('Morton'),('Moses'),('Mosley'),('Moss'),('Mueller'),('Mullen'),('Mullins'),('Munoz'),('Murphy'),('Murray'),('Myers'),('Nash'),('Navarro'),('Neal'),('Nelson'),('Newman'),('Newton'),('Nguyen'),('Nichols'),('Nicholson'),('Nielsen'),('Nieves'),('Nixon'),('Noble'),('Noel'),('Nolan'),('Norman'),('Norris'),('Norton'),('Nunez'),('Obrien'),('Ochoa'),('Oconnor'),('Odom'),('Odonnell'),('Oliver'),('Olsen'),('Olson'),('Oneal'),('Oneil'),('Oneill'),('Orr'),('Ortega'),('Ortiz'),('Osborn'),('Osborne'),('Owen'),('Owens'),('Pace'),('Pacheco'),('Padilla'),('Page'),('Palmer'),('Park'),('Parker'),('Parks'),('Parrish'),('Parsons'),('Pate'),('Patel'),('Patrick'),('Patterson'),('Patton'),('Paul'),('Payne'),('Pearson'),('Peck'),('Pena'),('Pennington'),('Perez'),('Perkins'),('Perry'),('Peters'),('Petersen'),('Peterson'),('Petty'),('Phelps'),('Phillips'),('Pickett'),('Pierce'),('Pittman'),('Pitts'),('Pollard'),('Poole'),('Pope'),('Porter'),('Potter'),('Potts'),('Powell'),('Powers'),('Pratt'),('Preston'),('Price'),('Prince'),('Pruitt'),('Puckett'),('Pugh'),('Quinn'),('Ramirez'),('Ramos'),('Ramsey'),('Randall'),('Randolph'),('Rasmussen'),('Ratliff'),('Ray'),('Raymond'),('Reed'),('Reese'),('Reeves'),('Reid'),('Reilly'),('Reyes'),('Reynolds'),('Rhodes'),('Rice'),('Rich'),('Richard'),('Richards'),('Richardson'),('Richmond'),('Riddle'),('Riggs'),('Riley'),('Rios'),('Rivas'),('Rivera'),('Rivers'),('Roach'),('Robbins'),('Roberson'),('Roberts'),('Robertson'),('Robinson'),('Robles'),('Rocha'),('Rodgers'),('Rodriguez'),('Rodriquez'),('Rogers'),('Rojas'),('Rollins'),('Roman'),('Romero'),('Rosa'),('Rosales'),('Rosario'),('Rose'),('Ross'),('Roth'),('Rowe'),('Rowland'),('Roy'),('Ruiz'),('Rush'),('Russell'),('Russo'),('Rutledge'),('Ryan'),('Salas'),('Salazar'),('Salinas'),('Sampson'),('Sanchez'),('Sanders'),('Sandoval'),('Sanford'),('Santana'),('Santiago'),('Santos'),('Sargent'),('Saunders'),('Savage'),('Sawyer'),('Schmidt'),('Schneider'),('Schroeder'),('Schultz'),('Schwartz'),('Scott'),('Sears'),('Sellers'),('Serrano'),('Sexton'),('Shaffer'),('Shannon'),('Sharp'),('Sharpe'),('Shaw'),('Shelton'),('Shepard'),('Shepherd'),('Sheppard'),('Sherman'),('Shields'),('Short'),('Silva'),('Simmons'),('Simon'),('Simpson'),('Sims'),('Singleton'),('Skinner'),('Slater'),('Sloan'),('Small'),('Smith'),('Snider'),('Snow'),('Snyder'),('Solis'),('Solomon'),('Sosa'),('Soto'),('Sparks'),('Spears'),('Spence'),('Spencer'),('Stafford'),('Stanley'),('Stanton'),('Stark'),('Steele'),('Stein'),('Stephens'),('Stephenson'),('Stevens'),('Stevenson'),('Stewart'),('Stokes'),('Stone'),('Stout'),('Strickland'),('Strong'),('Stuart'),('Suarez'),('Sullivan'),('Summers'),('Sutton'),('Swanson'),('Sweeney'),('Sweet'),('Sykes'),('Talley'),('Tanner'),('Tate'),('Taylor'),('Terrell'),('Terry'),('Thomas'),('Thompson'),('Thornton'),('Tillman'),('Todd'),('Torres'),('Townsend'),('Tran'),('Travis'),('Trevino'),('Trujillo'),('Tucker'),('Turner'),('Tyler'),('Tyson'),('Underwood'),('Valdez'),('Valencia'),('Valentine'),('Valenzuela'),('Vance'),('Vang'),('Vargas'),('Vasquez'),('Vaughan'),('Vaughn'),('Vazquez'),('Vega'),('Velasquez'),('Velazquez'),('Velez'),('Villarreal'),('Vincent'),('Vinson'),('Wade'),('Wagner'),('Walker'),('Wall'),('Wallace'),('Waller'),('Walls'),('Walsh'),('Walter'),('Walters'),('Walton'),('Ward'),('Ware'),('Warner'),('Warren'),('Washington'),('Waters'),('Watkins'),('Watson'),('Watts'),('Weaver'),('Webb'),('Weber'),('Webster'),('Weeks'),('Weiss'),('Welch'),('Wells'),('West'),('Wheeler'),('Whitaker'),('White'),('Whitehead'),('Whitfield'),('Whitley'),('Whitney'),('Wiggins'),('Wilcox'),('Wilder'),('Wiley'),('Wilkerson'),('Wilkins'),('Wilkinson'),('William'),('Williams'),('Williamson'),('Willis'),('Wilson'),('Winters'),('Wise'),('Witt'),('Wolf'),('Wolfe'),('Wong'),('Wood'),('Woodard'),('Woods'),('Woodward'),('Wooten'),('Workman'),('Wright'),('Wyatt'),('Wynn'),('Yang'),('Yates'),('York'),('Young'),('Zamora'),('Zimmerman')
- ";
-
- $response = Core::$db->query($queries, $rollbackQueries);
-
- if ($response["success"]) {
- return array(true, "");
- } else {
- return array(false, $response["errorMessage"]);
- }
- }
-
-
- public function getHelpHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $content =<<
- {$this->L["help_intro"]}
-
-
-
-
- Name
- {$this->L["type_Name"]}
-
-
- MaleName
- {$this->L["type_MaleName"]}
-
-
- FemaleName
- {$this->L["type_FemaleName"]}
-
-
- Initial
- {$this->L["type_Initial"]}
-
-
- Surname
- {$this->L["type_Surname"]}
-
-
-EOF;
-
- return $content;
- }
-}
diff --git a/plugins/dataTypes/NamesRegional/NamesRegional.js b/plugins/dataTypes/NamesRegional/NamesRegional.js
deleted file mode 100644
index 493175fee..000000000
--- a/plugins/dataTypes/NamesRegional/NamesRegional.js
+++ /dev/null
@@ -1,90 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- var MODULE_ID = "data-type-NamesRegional";
- var LANG = L.dataTypePlugins.NamesRegional;
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _exampleChange = function(msg) {
- $("#dtOption_" + msg.rowID).val(msg.value);
- };
-
- /**
- * Called when the user submits the form to generate some data. If the selected data set contains
- * one or more rows of this data type, this function is called with the list of row numbers. Note that
- * the row numbers passed are the *original* row numbers of the rows on creation. It's possible that the
- * user has re-sorted or deleted some rows. So to get the visible row number for a row, call
- * gen._getVisibleRowOrderByRowNum(row)
- */
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- /**
- * Called when the user saves a form. This function is passed the row number of the row to
- * save. It should return a JSON object (of whatever structure is relevant).
- */
- var _saveRow = function(rowNum) {
- return {
- "example": $("#dtExample_" + rowNum).val(),
- "option": $("#dtOption_" + rowNum).val()
- };
- };
-
- /**
- * Called when a form is loaded that contains this data type. This is passed the row number and
- * the custom data type data to populate the fields. loadRow functions all must return an array
- * with two indexes - both functions:
- * [0] code to execute (generally inserting data into fields)
- * [1] a boolean test to determine WHEN the content has been inserted.
- */
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() { },
- isComplete: function() {
- if ($("#dtOption_" + rowNum).length) {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtOption_" + rowNum).val(data.option);
- return true;
- } else {
- return false;
- }
- }
- };
- };
-
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/NamesRegional/lang/de.php b/plugins/dataTypes/NamesRegional/lang/de.php
deleted file mode 100644
index 33c95ef06..000000000
--- a/plugins/dataTypes/NamesRegional/lang/de.php
+++ /dev/null
@@ -1,25 +0,0 @@
-Names data type, except that it creates slightly more realistic data sets since the names are mapped to the country; e.g. Italian names appear when the data set row has Italy for the country field. Because of this additional complexity, however, the code runs slightly slower. You can specify multiple formats by separating them with the pipe (|) character. The following strings will be converted to their random name equivalent:";
-$L["incomplete_fields"] = "The Name data type needs to have the format entered in the Options text field. Please fix the following rows:";
-$L["name"] = "Name";
-$L["example_FemaleName"] = "Jane (Female Name)";
-$L["example_FemaleName_Surname"] = "Jane Smith";
-$L["example_MaleName"] = "John (Male Name)";
-$L["example_MaleName_Surname"] = "John Smith";
-$L["example_Name"] = "Alex (any gender)";
-$L["example_Name4"] = "Jenny, Toby, Ben, Peter";
-$L["example_Name_Initial_Surname"] = "Alex J. Smith";
-$L["example_Name_Surname"] = "Alex Smith";
-$L["example_Surname_Name_Initial"] = "Smith, John P.";
-$L["example_fullnames"] = "Alex Smith or Alex J. Smith";
-$L["example_surname"] = "Smith (surname)";
-$L["type_FemaleName"] = "A female first name.";
-$L["type_Initial"] = "An upper-case letter, A-Z.";
-$L["type_MaleName"] = "A male first name.";
-$L["type_Name"] = "A first name, male or female.";
-$L["type_Surname"] = "A random surname.";
diff --git a/plugins/dataTypes/NamesRegional/lang/es.php b/plugins/dataTypes/NamesRegional/lang/es.php
deleted file mode 100644
index 9d8d08535..000000000
--- a/plugins/dataTypes/NamesRegional/lang/es.php
+++ /dev/null
@@ -1,24 +0,0 @@
-Noms, à l'exception du fait qu'il génère des noms plus réalistes en fonction des pays précisés. Par exemple des noms italiens seront générés si le pays Italie est sélectionné dans le champs 'Pays'. A cause de cette difficulté supplémentaire le script risque de fonctionner un peut plus lentement. Vous pouvez spécifier les formats multiples en les séparant par le caractère pipe (|). Les chaînes suivantes seront converties en leur équivalent aléatoire:";
-$L["incomplete_fields"] = "Le type de données Nom doit avoir un format précisé dans le champ de texte Options. Corrigez les lignes suivantes:";
-$L["name"] = "Nom";
-$L["type_FemaleName"] = "Un prénom féminin.";
-$L["type_Initial"] = "Une lettre majuscule, AZ.";
-$L["type_MaleName"] = "Un prénom masculin.";
-$L["type_Name"] = "Un prénom majuscule ou féminin.";
-$L["type_Surname"] = "Un nom aléatoire.";
\ No newline at end of file
diff --git a/plugins/dataTypes/NamesRegional/lang/nl.php b/plugins/dataTypes/NamesRegional/lang/nl.php
deleted file mode 100644
index 578e0cf7d..000000000
--- a/plugins/dataTypes/NamesRegional/lang/nl.php
+++ /dev/null
@@ -1,24 +0,0 @@
-
- * @package DataTypes
- */
-class DataType_NormalDistribution extends DataTypePlugin {
-
- /**#@+
- * @access protected
- */
- protected $isEnabled = true;
- protected $dataTypeName = "Standard Normal Distribution";
- protected $dataTypeFieldGroup = "math";
- protected $dataTypeFieldGroupOrder = 10;
- protected $jsModules = array("NormalDistribution.js");
- protected $randMax = null;
-
-
- public function generate($generator, $generationContextData) {
- $mean = (float) $generationContextData["generationOptions"]["mean"];
- $stddev = (float) $generationContextData["generationOptions"]["stddev"];
-
- return array(
- "display" => $this->gauss_ms($mean, $stddev)
- );
- }
-
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- if ((empty($postdata["dtOptionMean_$colNum"]) && $postdata["dtOptionMean_$colNum"] !== "0") ||
- (empty($postdata["dtOptionSigma_$colNum"]) && $postdata["dtOptionSigma_$colNum"] !== "0")) {
- return false;
- }
- $this->randMax = (float) getrandmax();
-
- return array(
- "mean" => $postdata["dtOptionMean_$colNum"],
- "stddev" => $postdata["dtOptionSigma_$colNum"]
- );
- }
-
- public function getOptionsColumnHTML() {
- $options =<<< END
- {$this->L["mean"]}
-
- {$this->L["standard_deviation"]}
-
-END;
-
- return $options;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(100)"
- );
- }
-
- // returns random number using mt_rand() with a flat distribution from -1 to 1 inclusive
- public function random_PN() {
- return (2.0 * $this->random_0_1()) - 1.0;
- }
-
- public function random_0_1() {
- return (float) mt_rand() / (float) mt_getrandmax() ;
- }
-
- public function gauss() {
- static $useExists = false;
- static $useValue;
-
- if ($useExists) {
- // Use value from a previous call to this function
- $useExists = false;
- return $useValue;
- } else {
- // Polar form of the Box-Muller transformation
- $w = 2.0 ;
- while (($w >= 1.0) || ($w == 0.0)) {
- $x = $this->random_PN();
- $y = $this->random_PN();
- $w = ($x * $x) + ($y * $y);
- }
- $w = sqrt((-2.0 * log($w)) / $w);
-
- // Set value for next call to this function
- $useValue = $y * $w;
- $useExists = true;
-
- return $x * $w;
- }
- }
-
- public function gauss_ms($mean, $stddev) {
- // Adjust our gaussian random to fit the mean and standard deviation
- // The division by 4 is an arbitrary value to help fit the distribution
- // within our required range, and gives a best fit for $stddev = 1.0
- return $this->gauss() * ($stddev / 4) + $mean;
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/NormalDistribution/NormalDistribution.js b/plugins/dataTypes/NormalDistribution/NormalDistribution.js
deleted file mode 100644
index 9a41ef5fa..000000000
--- a/plugins/dataTypes/NormalDistribution/NormalDistribution.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name NormalDistribution
- * @description JS code for the NormalDistribution Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-NormalDistribution";
- var LANG = L.dataTypePlugins.NormalDistribution;
- var subscriptions = {};
-
- var _init = function() {
- };
-
- var _saveRow = function(rowNum) {
- return {
- "mean": $("#dtOptionMean_" + rowNum).val(),
- "sigma": $("#dtOptionSigma_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtOptionMean_" + rowNum).val(data.mean);
- $("#dtOptionSigma_" + rowNum).val(data.sigma);
- },
- isComplete: function() { return $("#dtOptionSigma_" + rowNum).length > 0; }
- };
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- saveRow: _saveRow,
- loadRow: _loadRow
- });
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/NormalDistribution/lang/de.php b/plugins/dataTypes/NormalDistribution/lang/de.php
deleted file mode 100644
index b6edb3620..000000000
--- a/plugins/dataTypes/NormalDistribution/lang/de.php
+++ /dev/null
@@ -1,7 +0,0 @@
- mt_rand($options["min"], $options["max"])
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $column, $numCols) {
- if ((empty($postdata["dtNumRangeMin_$column"]) && $postdata["dtNumRangeMin_$column"] !== "0") ||
- (empty($postdata["dtNumRangeMax_$column"]) && $postdata["dtNumRangeMax_$column"] !== "0")) {
- return false;
- }
- if (!is_numeric($postdata["dtNumRangeMin_$column"]) || !is_numeric($postdata["dtNumRangeMax_$column"])) {
- return false;
- }
- $options = array(
- "min" => $postdata["dtNumRangeMin_$column"],
- "max" => $postdata["dtNumRangeMax_$column"]
- );
- return $options;
- }
-
- public function getOptionsColumnHTML() {
- $html =<<L["between"]}
-{$this->L["and"]}
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "type" => "numeric",
- "SQLField" => "mediumint default NULL",
- "SQLField_Oracle" => "varchar2(50) default NULL",
- "SQLField_MSSQL" => "INTEGER NULL",
- "SQLField_Postgres" => "integer NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-}
diff --git a/plugins/dataTypes/NumberRange/NumberRange.js b/plugins/dataTypes/NumberRange/NumberRange.js
deleted file mode 100755
index 15184c1dd..000000000
--- a/plugins/dataTypes/NumberRange/NumberRange.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- var MODULE_ID = "data-type-NumberRange";
- var LANG = L.dataTypePlugins.NumberRange;
-
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtNumRangeMin_" + rowNum).val(data.rangeMin);
- $("#dtNumRangeMax_" + rowNum).val(data.rangeMax);
- },
- isComplete: function() { return true; }
- };
- };
-
- var _saveRow = function(rowNum) {
- return {
- rangeMin: $("#dtNumRangeMin_" + rowNum).val(),
- rangeMax: $("#dtNumRangeMax_" + rowNum).val()
- };
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
-
- var intOnly = /^\d+$/;
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- manager.registerDataType(MODULE_ID, {
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/NumberRange/lang/de.php b/plugins/dataTypes/NumberRange/lang/de.php
deleted file mode 100644
index 4936b3f7e..000000000
--- a/plugins/dataTypes/NumberRange/lang/de.php
+++ /dev/null
@@ -1,9 +0,0 @@
-, origin code Zeeshan Shaikh
- * @package DataTypes
- */
-class DataType_PAN extends DataTypePlugin {
-
- protected $isEnabled = true;
- protected $dataTypeName = "PAN";
- protected $dataTypeFieldGroup = "credit_card_data";
- protected $dataTypeFieldGroupOrder = 10;
- protected $jsModules = array("PAN.js");
-
-
- private static $creditCardData = array(
- "visa" => array(
- "prefix" => array(4539, 4556, 4916, 4532, 4929, 40240071, 4485, 4716, 4),
- "length" => "13,16",
- "formats" => array(
- "XXXXXXXXXXXXX",
- "XXXX XXX XX XXXX",
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- )
- ),
- "visaElectron" => array(
- "prefix" => array(4026, 417500, 4508, 4844, 4913, 4917),
- "length" => "16",
- "formats" => array(
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- )
- ),
- "mastercard" => array(
- "prefix" => array(51, 52, 53, 54, 55),
- "length" => "16",
- "formats" => array(
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- )
- ),
- "amex" => array(
- "prefix" => array(34, 37),
- "length" => "15",
- "formats" => array(
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX"
- )
- ),
- "discover" => array(
- "prefix" => array(6011, 644, 645, 646, 647, 648, 649, 65),
- "length" => "16",
- "formats" => array(
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- )
- ),
- "carteBlanche" => array(
- "prefix" => array(300, 301, 302, 303, 304, 305),
- "length" => "14",
- "formats" => array(
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX"
- )
- ),
- "dinersClubInt" => array(
- "prefix" => array(36),
- "length" => "14",
- "formats" => array(
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX"
- )
- ),
- "dinersClubEnRoute" => array(
- "prefix" => array(2014, 2149),
- "length" => "15",
- "formats" => array(
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX"
- )
- ),
- "jcb15" => array(
- "prefix" => array(2131, 1800),
- "length" => "15,16",
- "formats" => array(
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX",
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- )
- ),
- "jcb16" => array(
- "prefix" => array(31, 309),
- "length" => "15,16",
- "formats" => array(
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX",
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- )
- ),
- "maestro" => array(
- "prefix" => array(5018, 5038, 6304, 6759, 6761, 6762, 6763, 5893, 56, 57, 58),
- "length" => "12-19",
- "formats" => array(
- "XXXXXXXXXXXX",
- "XXXXXXXXXXXXX",
- "XXXX XXX XX XXXX",
- "XXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXX",
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX",
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX",
- "XXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXXX",
- "XXXXXX XX XXXX XXXX XXX"
- )
- ),
- "solo" => array(
- "prefix" => array(6334, 6767),
- "length" => "16,18,19",
- "formats" => array(
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXXX",
- "XXXXXX XX XXXX XXXX XXX"
- )
- ),
- "switch" => array(
- "prefix" => array(4903, 4905, 4905, 4911, 4936, 564182, 633110, 6333, 6759),
- "length" => "16,18,19",
- "formats" => array(
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXXX",
- "XXXXXX XX XXXX XXXX XXX"
- )
- ),
- "laser" => array(
- "prefix" => array(6304, 6706, 6771, 6709),
- "length" => "16-19",
- "formats" => array(
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX",
- "XXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXXX",
- "XXXXXX XX XXXX XXXX XXX"
- )
- )
- );
-
-
- public function __construct($runtimeContext) {
- for ($i=622126; $i<=622925; $i++) {
- $this->creditCardData["discover"][] = $i;
- }
- for ($i=3528; $i<=3589; $i++) {
- $this->creditCardData["jcb16"][] = $i;
- }
- parent::__construct($runtimeContext);
- }
-
-
- public function generate($generator, $generationContextData) {
- $options = $generationContextData["generationOptions"];
-
- if ($options["cc_brand"] == "rand_card") {
- $options = $this->setRandomCardInfo($options);
- }
-
- $ccLength = self::getRandomPANLength($options["cc_length"]);
- $ccFormat = self::getRandomPANFormat($options["cc_format"], $options["cc_length"]);
- $ccSeparator = self::getRandomPANSeparator($options["cc_separator"], $options["cc_format"]);
-
- $ccData = self::getCreditCardData($options["cc_brand"]);
- $card = self::generateCreditCardNumber($ccData["prefix"], $ccLength);
- $cardNumber = $this->convertFormat($ccLength, $ccFormat, $ccSeparator, $card);
-
- if (empty($cardNumber)) {
- $cardNumber = "$ccLength, $ccFormat, {$options["cc_brand"]}, {$options["cc_format"]}";
- }
- return array(
- "display" => $cardNumber
- );
- }
-
-
- public function setRandomCardInfo($options) {
- $selectedCard = $options["cc_random_card"][array_rand($options["cc_random_card"])];
-
- if ($selectedCard == "jcb") {
- $jcbCards = array("jcb15", "jcb16");
- $selectedCard = $jcbCards[mt_rand(0, 1)];
- }
-
- $cardData = self::getCreditCardData($selectedCard);
- $options["cc_format"] = $cardData["formats"][array_rand($cardData["formats"])];
- $options["cc_length"] = self::getRandomPANLength($cardData["length"]);
-
- return $options;
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- return array(
- "cc_brand" => $postdata["dtExample_$colNum"],
- "cc_separator" => $postdata["dtOptionPAN_sep_$colNum"],
- "cc_format" => $postdata["dtOption_$colNum"],
- "cc_length" => $postdata["dtOptionPAN_digit_$colNum"],
- "cc_random_card" => $postdata["dtOptionPAN_randomCardFormat_$colNum"]
- );
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $html =<<< END
-
- {$L["please_select"]}
- {$this->L["mastercard"]}
- {$this->L["visa"]}
- {$this->L["visa_electron"]}
- {$this->L["americanexpress"]}
- {$this->L["discover"]}
- {$this->L["carte_blanche"]}
- {$this->L["diners_club_international"]}
- {$this->L["enRoute"]}
- {$this->L["jcb"]}
- {$this->L["maestro"]}
- {$this->L["solo"]}
- {$this->L["switch"]}
- {$this->L["laser"]}
- {$this->L["rand_card"]}
-
-END;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- $html =<<< END
-
- {$this->L["length"]}
-
-
-
-
- {$this->L["separators"]}
-
-
-
-
- {$this->L["ccformats"]}
-
-
-
-
- {$this->L["ccrandom"]}
-
- {$this->L["mastercard"]}
- {$this->L["visa"]}
- {$this->L["visa_electron"]}
- {$this->L["americanexpress"]}
- {$this->L["discover"]}
- {$this->L["carte_blanche"]}
- {$this->L["diners_club_international"]}
- {$this->L["enRoute"]}
- {$this->L["jcb"]}
- {$this->L["maestro"]}
- {$this->L["solo"]}
- {$this->L["switch"]}
- {$this->L["laser"]}
-
-
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- $html =<<
- {$this->L["pan_help_intro"]}
- {$this->L["mastercard"]} , {$this->L["visa"]} , {$this->L["visa_electron"]} ,
- {$this->L["americanexpress"]} , {$this->L["discover"]} , {$this->L["american_diners"]} ,
- {$this->L["carte_blanche"]} , {$this->L["diners_club_international"]} , {$this->L["enroute"]} ,
- {$this->L["jcb"]} , {$this->L["maestro"]} , {$this->L["solo"]} ,
- {$this->L["switch"]} , {$this->L["laser"]} .
-
-EOF;
-
- return $html;
- }
-
-
- /**
- * @param $ccLength
- * @param $ccFormat
- * @param $ccSeparator
- * @param $ccNumber
- * @return array|bool|string
- */
- private static function convertFormat($ccLength, $ccFormat, $ccSeparator, $ccNumber) {
-
- // TODO pity we need this extra test on each call
- if ($ccLength == strlen($ccNumber)) {
- $a = self::convertXtoNumber($ccFormat, $ccNumber);
-
- if ($a == $ccNumber) {
- return $a;
- } else {
- return implode($ccSeparator, $a);
- }
- } else {
- return false;
- }
- }
-
- /**
- * Convert X's to the specified number
- */
- private static function convertXtoNumber($chosen_format, $ccnumber){
- $positions = array();
- $pos = -1;
- while (($pos = strpos($chosen_format, " ", $pos+1)) !== false) {
- $positions[] = $pos;
- }
-
- if (empty($positions)) {
- return $ccnumber;
- }
-
- $result = array();
- $result_f = array();
- $j = 1;
-
- for ($i=0; $i= 1) {
- $chosenFormat = $sortedFormat[mt_rand(0, count($sortedFormat)-1)];
- }
-
- return trim($chosenFormat);
- }
-
-
- // will give a random separator
- private static function getRandomPANSeparator($separators, $randCardFormat) {
-
- $chosenSep = "";
- if (preg_match("/[^X]/", $randCardFormat)) {
-
- $separatorList = explode("|", $separators);
- $chosenSep = $separatorList[rand(0, count($separatorList)-1)];
-
- // if no separator was entered
- if ($separators == "") {
- $chosenSep = " ";
- }
- }
-
- return $chosenSep;
- }
-
-
- private static function getRandomPANLength($userSelectedLength) {
-
- // this would be better
-// $groups = explode(",", $userSelectedLength);
-// for ($i=0; $i= 1) {
- $chosenLength = $lengths[mt_rand(0, count($lengths)-1)];
- }
-
- return $chosenLength;
- }
-
- // --------------------------------------------------------------------------------------------
- // Public functions
-
- public static function generateCreditCardNumber($prefixList, $length) {
- $ccNumber = $prefixList[array_rand($prefixList)];
-
- // generate digits
- while (strlen($ccNumber)<($length-1)) {
- $ccNumber .= mt_rand(0,9);
- }
-
- // calculate sum
- $sum = 0;
- $pos = 0;
-
- $reversedCCnumber = strrev($ccNumber);
- while ($pos < $length - 1) {
- $odd = $reversedCCnumber[$pos]*2;
- if ($odd > 9) {
- $odd -= 9;
- }
- $sum += $odd;
-
- if ($pos != ($length - 2)) {
- $sum += $reversedCCnumber[ $pos +1 ];
- }
- $pos += 2;
- }
-
- // calculate check digit
- $checkDigit = ((floor($sum/10) + 1) * 10 - $sum) % 10;
- $ccNumber .= $checkDigit;
-
- return $ccNumber;
- }
-
-
- public static function getCreditCardData($ccBrand) {
- $data = array();
- reset(self::$creditCardData);
- while (list($currBrand, $ccData) = each(self::$creditCardData)) {
- if ($ccBrand != $currBrand) {
- continue;
- }
- $data = $ccData;
- }
- return $data;
- }
-
- public static function getAllCreditCardData() {
- return self::$creditCardData;
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/PAN/PAN.js b/plugins/dataTypes/PAN/PAN.js
deleted file mode 100644
index c1fd3895d..000000000
--- a/plugins/dataTypes/PAN/PAN.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name PAN
- * @description JS code for the PAN Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-PAN";
- var LANG = L.dataTypePlugins.PAN;
- var subscriptions = {};
-
- var _init = function() {
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _saveRow = function(rowNum) {
- var randomBrands = $("#dtOptionPAN_randomCardFormat_" + rowNum).val();
- return {
- example: $("#dtExample_" + rowNum).val(),
- digit: $("#dtOptionPAN_digit_" + rowNum).val(),
- separator: $("#dtOptionPAN_sep_" + rowNum).val(),
- format: $("#dtOption_" + rowNum).val(),
- randomBrands: randomBrands
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtOptionPAN_digit_" + rowNum).val(data.digit);
- $("#dtOptionPAN_sep_" + rowNum).val(data.separator);
- $("#dtOption_" + rowNum).val(data.format);
-
- var $cardFormat = $("#dtOptionPAN_cardFormat_" + rowNum);
- var $digitSection = $("#dtOptionPAN_digitSection_" + rowNum);
- var $randCardFormatSection = $("#dtOptionPAN_randomCardFormatSection_" + rowNum);
-
- if (data.example === "rand_card") {
- $digitSection.hide();
- $cardFormat.hide();
- $randCardFormatSection.show();
-
- var options = $("#dtOptionPAN_randomCardFormat_" + rowNum).find("option");
- for (var i=0; i 0; }
- };
- };
-
-
- var _exampleChange = function(msg) {
- var rowID = msg.rowID;
- var selectedCard = msg.value;
-
- var $digitSection = $("#dtOptionPAN_digitSection_" + rowID);
- var $digitLengthField = $("#dtOptionPAN_digit_" + rowID);
- var $cardFormat = $("#dtOptionPAN_cardFormat_" + rowID);
- var $option = $("#dtOption_" + rowID);
- var $randCardFormatSection = $("#dtOptionPAN_randomCardFormatSection_" + rowID);
-
- // default states shared by most options
- $cardFormat.show();
- $digitSection.show();
- $randCardFormatSection.hide();
-
- var formats = [];
- switch (selectedCard) {
- case "mastercard":
- case "discover":
- case "visaElectron":
- $digitLengthField.val("16");
- formats = [
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- ];
- break;
-
- case "visa":
- $digitLengthField.val("13,16");
- formats = [
- "XXXXXXXXXXXXX",
- "XXXX XXX XX XXXX",
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- ];
- break;
-
- case "amex":
- case "dinersClubEnRoute":
- $digitLengthField.val("15");
- formats = [
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX"
- ];
- break;
-
- case "carteBlanche":
- case "dinersClubInt":
- $digitLengthField.val('14');
- formats = [
- "XXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXX"
- ];
- break;
-
- case "jcb":
- $digitLengthField.val("15,16");
- formats = [
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX",
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX"
- ];
- break;
-
- case "maestro":
- $digitLengthField.val("12-19");
- formats = [
- "XXXXXXXXXXXX",
- "XXXXXXXXXXXXX",
- "XXXX XXX XX XXXX",
- "XXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXX",
- "XXXXXXXXXXXXXXX",
- "XXXX XXXXXX XXXXX",
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX",
- "XXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXXX",
- "XXXXXX XX XXXX XXXX XXX"
- ];
- break;
-
- case "solo":
- case "switch":
- $digitLengthField.val("16,18,19");
- formats = [
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXXX",
- "XXXXXX XX XXXX XXXX XXX"
- ];
- break;
-
- case "laser":
- $digitLengthField.val("16-19");
- formats = [
- "XXXXXXXXXXXXXXXX",
- "XXXX XXXX XXXX XXXX",
- "XXXXXX XXXXXX XXXX",
- "XXX XXXXX XXXXX XXX",
- "XXXXXX XXXXXXXXXX",
- "XXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXX",
- "XXXXXXXXXXXXXXXXXXX",
- "XXXXXX XX XXXX XXXX XXX"
- ];
- break;
-
- case "rand_card":
- $digitSection.hide();
- $cardFormat.hide();
- $randCardFormatSection.show();
- break;
- }
-
- $option.val(formats.join("\n"));
- };
-
-
- var _validate = function(rows) {
- var cardTypeProblemVisibleRows = [];
- var cardTypeProblemFields = [];
- var cardFormatProblemVisibleRows = [];
- var cardFormatProblemFields = [];
-
- for (var i=0; i" + cardTypeProblemVisibleRows.join(", ") + ""});
- }
- if (cardFormatProblemVisibleRows.length) {
- errors.push({ els: cardFormatProblemFields, error: LANG.format_incomplete_fields + " " + cardFormatProblemVisibleRows.join(", ") + " "});
- }
-
- return errors;
- };
-
-
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- saveRow: _saveRow,
- loadRow: _loadRow
- });
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/PAN/lang/de.php b/plugins/dataTypes/PAN/lang/de.php
deleted file mode 100644
index 3f20be590..000000000
--- a/plugins/dataTypes/PAN/lang/de.php
+++ /dev/null
@@ -1,29 +0,0 @@
-X und für Abscheider Eingangs Räume nach der Kartenlänge. Bitte korrigieren Sie die folgenden Zeilen:";
-$L["pan_help_intro"] = "Dieser Datentyp generiert zufällige, gültige Kreditkartennummern nach dem Format, das Sie angeben. Es ist derzeit in der Lage ist Nummern für die folgenden Marken:";
-$L["mastercard"] = "Mastercard";
-$L["visa_electron"] = "Visa Electron";
-$L["visa"] = "Visa";
-$L["americanexpress"] = "American Express";
-$L["discover"] = "Discover";
-$L["american_diners"] = "American Diner's";
-$L["carte_blanche"] = "Carte Blanche";
-$L["diners_club_international"] = "Diner's Club International";
-$L["enroute"] = "enRoute";
-$L["jcb"] = "JCB";
-$L["maestro"] = "Maestro";
-$L["solo"] = "Solo";
-$L["switch"] = "Switch";
-$L["laser"] = "Laser";
-$L["rand_card"] = "Zufällige Kreditkarte";
-$L["ccrandom"] = "Wählen Karte Marke:";
-$L["format_title"] = "Wenn die Länge der X nicht gleich der Kartenlänge so dass Format nicht erzeugt zu werden.";
-$L["rand_brand_title"] = "Die ausgewählte Marke Kartenlänge und Format werden zufällig ausgewählt.";
-$L["length"] = "Länge:";
-$L["separators"] = "Abscheider:";
-$L["ccformats"] = "Kreditkartenformate:";
diff --git a/plugins/dataTypes/PAN/lang/en.php b/plugins/dataTypes/PAN/lang/en.php
deleted file mode 100644
index 2ebefa11c..000000000
--- a/plugins/dataTypes/PAN/lang/en.php
+++ /dev/null
@@ -1,29 +0,0 @@
-X's and for separators input spaces according to the card length. Please fix the following rows:";
-$L["pan_help_intro"] = "This data type generates random, valid credit card numbers according to the format you specify. It is currently capable of generating numbers for the following brands: ";
-$L["mastercard"] = "Mastercard";
-$L["visa_electron"] = "Visa Electron";
-$L["visa"] = "Visa";
-$L["americanexpress"] = "American Express";
-$L["discover"] = "Discover";
-$L["american_diners"] = "American Diner's";
-$L["carte_blanche"] = "Carte Blanche";
-$L["diners_club_international"] = "Diner's Club International";
-$L["enRoute"] = "enRoute";
-$L["jcb"] = "JCB";
-$L["maestro"] = "Maestro";
-$L["solo"] = "Solo";
-$L["switch"] = "Switch";
-$L["laser"] = "Laser";
-$L["rand_card"] = "Random Credit Card";
-$L["ccrandom"] = "Select Card Brand:";
-$L["format_title"] = "If length of X is not equal to the card length then that format will not get generated.";
-$L["rand_brand_title"] = "The selected brand's card length and format are picked randomly.";
-$L["length"] = "Length:";
-$L["separators"] = "Separators:";
-$L["ccformats"] = "Credit Card Formats:";
diff --git a/plugins/dataTypes/PAN/lang/es.php b/plugins/dataTypes/PAN/lang/es.php
deleted file mode 100644
index 16f171435..000000000
--- a/plugins/dataTypes/PAN/lang/es.php
+++ /dev/null
@@ -1,29 +0,0 @@
-X de y para los separadores de entrada espacios de acuerdo a la longitud de la tarjeta. Por favor, corrija los siguientes filas:";
-$L["pan_help_intro"] = "Este tipo de datos genera números, tarjeta de crédito válida al azar de acuerdo con el formato que se especifique. Actualmente es capaz de generar los números de las siguientes marcas: ";
-$L["mastercard"] = "Mastercard";
-$L["visa_electron"] = "Visa Electron";
-$L["visa"] = "Visa";
-$L["americanexpress"] = "American Express";
-$L["discover"] = "Discover";
-$L["american_diners"] = "American Diner's";
-$L["carte_blanche"] = "Carte Blanche";
-$L["diners_club_international"] = "Diner's Club International";
-$L["enroute"] = "enRoute";
-$L["jcb"] = "JCB";
-$L["maestro"] = "Maestro";
-$L["solo"] = "Solo";
-$L["switch"] = "Switch";
-$L["laser"] = "Laser";
-$L["rand_card"] = "Tarjeta de crédito aleatorio";
-$L["ccrandom"] = "Seleccione Tarjeta Marca:";
-$L["format_title"] = "Si la longitud de X no es igual a la longitud de la tarjeta, entonces ese formato no conseguirá generado.";
-$L["rand_brand_title"] = "Longitud de la tarjeta del marca seleccionada y el formato son recogidos al azar.";
-$L["length"] = "Largo:";
-$L["separators"] = "Separadores:";
-$L["ccformats"] = "Formatos de Tarjeta de Crédito:";
diff --git a/plugins/dataTypes/PAN/lang/fr.php b/plugins/dataTypes/PAN/lang/fr.php
deleted file mode 100644
index 5a58affc3..000000000
--- a/plugins/dataTypes/PAN/lang/fr.php
+++ /dev/null
@@ -1,29 +0,0 @@
-X de et pour les séparateurs entrée espaces en fonction de la longueur de la carte. S'il vous plaît corriger les lignes suivantes:";
-$L["pan_help_intro"] = "Ce type de données génère des nombres aléatoires, carte de crédit valide selon le format que vous spécifiez. Il est actuellement capable de générer des nombres pour les marques suivantes:";
-$L["mastercard"] = "Mastercard";
-$L["visa_electron"] = "Visa Electron";
-$L["visa"] = "Visa";
-$L["americanexpress"] = "American Express";
-$L["discover"] = "Discover";
-$L["american_diners"] = "American Diner's";
-$L["carte_blanche"] = "Carte Blanche";
-$L["diners_club_international"] = "Diner's Club International";
-$L["enroute"] = "enRoute";
-$L["jcb"] = "JCB";
-$L["maestro"] = "Maestro";
-$L["solo"] = "Solo";
-$L["switch"] = "Switch";
-$L["laser"] = "Laser";
-$L["rand_card"] = "Aléatoire de carte de crédit";
-$L["ccrandom"] = "Sélectionnez Card Marque:";
-$L["format_title"] = "Si la longueur de X n'est pas égale à la longueur de la carte alors que le format ne sera pas généré.";
-$L["rand_brand_title"] = "La longueur et le format de la carte de la marque sélectionnée sont choisis au hasard.";
-$L["length"] = "Longueur:";
-$L["separators"] = "Séparateurs:";
-$L["ccformats"] = "Formats de carte de crédit:";
diff --git a/plugins/dataTypes/PAN/lang/nl.php b/plugins/dataTypes/PAN/lang/nl.php
deleted file mode 100644
index 2d4de09cd..000000000
--- a/plugins/dataTypes/PAN/lang/nl.php
+++ /dev/null
@@ -1,29 +0,0 @@
-X en voor afscheider ingang ruimtes volgens de kaart lengte. Please fix de volgende regels:";
-$L["pan_help_intro"] = "Dit soort gegevens genereert willekeurig, geldige credit card nummers volgens de indeling die u opgeeft. Het is momenteel geschikt voor het genereren nummers voor de volgende merken: ";
-$L["mastercard"] = "Mastercard";
-$L["visa_electron"] = "Visa Electron";
-$L["visa"] = "Visa";
-$L["americanexpress"] = "American Express";
-$L["discover"] = "Discover";
-$L["american_diners"] = "American Diner's";
-$L["carte_blanche"] = "Carte Blanche";
-$L["diners_club_international"] = "Diner's Club International";
-$L["enroute"] = "enRoute";
-$L["jcb"] = "JCB";
-$L["maestro"] = "Maestro";
-$L["solo"] = "Solo";
-$L["switch"] = "Switch";
-$L["laser"] = "Laser";
-$L["rand_card"] = "Random Credit Card";
-$L["ccrandom"] = "Selecteer Card Merk:";
-$L["format_title"] = "Indien de lengte van X niet gelijk is aan de kaart lengte dan dat formaat niet gegenereerd.";
-$L["rand_brand_title"] = "De geselecteerde merk kaart lengte en het formaat zijn willekeurig gekozen.";
-$L["length"] = "Lengte:";
-$L["separators"] = "Scheiders:";
-$L["ccformats"] = "Credit Card formaten:";
diff --git a/plugins/dataTypes/PIN/PIN.class.php b/plugins/dataTypes/PIN/PIN.class.php
deleted file mode 100644
index e37d4e660..000000000
--- a/plugins/dataTypes/PIN/PIN.class.php
+++ /dev/null
@@ -1,32 +0,0 @@
-, origin code Zeeshan Shaikh
- * @package DataTypes
- */
-class DataType_PIN extends DataTypePlugin {
- protected $isEnabled = true;
- protected $dataTypeName = "PIN";
- protected $hasHelpDialog = true;
- protected $dataTypeFieldGroup = "credit_card_data";
- protected $dataTypeFieldGroupOrder = 20;
-
-
- public function generate($generator, $generationContextData) {
- return array(
- "display" => mt_rand(1111, 9999)
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-}
diff --git a/plugins/dataTypes/PIN/lang/de.php b/plugins/dataTypes/PIN/lang/de.php
deleted file mode 100644
index bf87402dd..000000000
--- a/plugins/dataTypes/PIN/lang/de.php
+++ /dev/null
@@ -1,4 +0,0 @@
-1111 bis 9999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/PIN/lang/en.php b/plugins/dataTypes/PIN/lang/en.php
deleted file mode 100644
index f12851951..000000000
--- a/plugins/dataTypes/PIN/lang/en.php
+++ /dev/null
@@ -1,5 +0,0 @@
-1111 to 9999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/PIN/lang/es.php b/plugins/dataTypes/PIN/lang/es.php
deleted file mode 100644
index 361f800d2..000000000
--- a/plugins/dataTypes/PIN/lang/es.php
+++ /dev/null
@@ -1,5 +0,0 @@
-1111 a 9999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/PIN/lang/fr.php b/plugins/dataTypes/PIN/lang/fr.php
deleted file mode 100644
index 6c5059d68..000000000
--- a/plugins/dataTypes/PIN/lang/fr.php
+++ /dev/null
@@ -1,5 +0,0 @@
-1111 à 9999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/PIN/lang/nl.php b/plugins/dataTypes/PIN/lang/nl.php
deleted file mode 100644
index db44352aa..000000000
--- a/plugins/dataTypes/PIN/lang/nl.php
+++ /dev/null
@@ -1,5 +0,0 @@
-fr.php1111 om 9999 .";
\ No newline at end of file
diff --git a/plugins/dataTypes/Phone/Phone.class.php b/plugins/dataTypes/Phone/Phone.class.php
deleted file mode 100644
index 0dcb7a7e6..000000000
--- a/plugins/dataTypes/Phone/Phone.class.php
+++ /dev/null
@@ -1,82 +0,0 @@
- 1) {
- $chosenFormat = $formats[mt_rand(0, count($formats)-1)];
- }
- return array(
- "display" => $chosenFormat
- );
- }
-
- public function getRowGenerationOptions($generator, $post, $colNum, $numCols) {
- if (!isset($post["dtOption_$colNum"]) || empty($post["dtOption_$colNum"])) {
- return false;
- }
- return $post["dtOption_$colNum"];
- }
-
- public function getExampleColumnHTML() {
- $L = Core::$language->getCurrentLanguageStrings();
-
- $html =<<
- {$L["please_select"]}
- {$this->L["example_1"]}
- {$this->L["example_2"]}
- {$this->L["uk"]}
- {$this->L["france"]}
- {$this->L["australia"]}
- {$this->L["germany"]}
- {$this->L["japan"]}
- {$this->L["different_formats"]}
-
-EOF;
- return $html;
- }
-
- public function getOptionsColumnHTML() {
- $html = ' ';
- return $html;
- }
-
- public function getHelpHTML() {
- $html =<<
- {$this->L["help_text1"]}
-
-
- {$this->L["help_text2"]}
-
-
- {$this->L["help_text3"]}
-
-END;
-
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(100) default NULL",
- "SQLField_Oracle" => "varchar2(100) default NULL",
- "SQLField_MSSQL" => "VARCHAR(100) NULL"
- );
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/Phone/Phone.js b/plugins/dataTypes/Phone/Phone.js
deleted file mode 100755
index a9c243c4c..000000000
--- a/plugins/dataTypes/Phone/Phone.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- var MODULE_ID = "data-type-Phone";
- var LANG = L.dataTypePlugins.Phone;
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.DATA_TABLE.ROW.EXAMPLE_CHANGE + "__" + MODULE_ID] = _exampleChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _saveRow = function(rowNum) {
- return {
- "example": $("#dtExample_" + rowNum).val(),
- "option": $("#dtOption_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() {
- $("#dtExample_" + rowNum).val(data.example);
- $("#dtOption_" + rowNum).val(data.option);
- },
- isComplete: function() { return $("#dtOption_" + rowNum).length > 0; }
- };
- };
-
- var _exampleChange = function(msg) {
- $("#dtOption_" + msg.rowID).val(msg.value);
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- manager.registerDataType(MODULE_ID, {
- init: _init,
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/Phone/lang/de.php b/plugins/dataTypes/Phone/lang/de.php
deleted file mode 100644
index 280c6cad9..000000000
--- a/plugins/dataTypes/Phone/lang/de.php
+++ /dev/null
@@ -1,17 +0,0 @@
-X's zu einer Zufallszahl zwischen 1 und 9 umgewandelt werden; Kleinbuchstaben x 's zu einer Zufallszahl umgewandelt werden zwischen 0 und 9.";
-$L["help_text2"] = "Wählen Sie einen der Werte im Beispiel Dropdown für einige Ideen. Denken Sie daran: alles andere als die X und x -Zeichen übrig unbekehrt.";
-$L["help_text3"] = "Wie bei vielen der anderen Datentypen zu generieren Telefonnummern in mehreren Formaten trennen Sie sie mit einem Rohr | Charakter.";
-$L["incomplete_fields"] = "The Phone / Fax Datentyp haben muss das Format eingetragen im Options Textfeld. Bitte beheben Sie die folgenden Zeilen:";
-$L["japan"] = "Japan";
-$L["uk"] = "Vereinigtes Königreich";
diff --git a/plugins/dataTypes/Phone/lang/en.php b/plugins/dataTypes/Phone/lang/en.php
deleted file mode 100644
index 0f3a6737c..000000000
--- a/plugins/dataTypes/Phone/lang/en.php
+++ /dev/null
@@ -1,18 +0,0 @@
-X's will be converted to a random number between 1 and 9; lower-case x 's will be converted to a random number between 0 and 9.";
-$L["help_text2"] = "Select one of the values in the example dropdown for some ideas. Remember: anything other than the X and x character are left unconverted.";
-$L["help_text3"] = "As with many of the other data types, to generate phone numbers in multiple format separate them with a pipe | character.";
-$L["incomplete_fields"] = "The Phone data type needs to have the format entered in the Options text field. Please fix the following rows:";
-$L["uk"] = "UK";
-$L["france"] = "France";
-$L["germany"] = "Germany";
-$L["australia"] = "Australia";
-$L["japan"] = "Japan";
-$L["incomplete_fields"] = "The Phone / Fax data type needs to have the format entered in the Options text field. Please fix the following rows:";
diff --git a/plugins/dataTypes/Phone/lang/es.php b/plugins/dataTypes/Phone/lang/es.php
deleted file mode 100644
index e66293538..000000000
--- a/plugins/dataTypes/Phone/lang/es.php
+++ /dev/null
@@ -1,17 +0,0 @@
-X mayúscula será convertida a un número aleatorio entre 1 y 9; Una x minúscula será convertida a un número entre 0 y 9.";
-$L["help_text2"] = "Elije uno de los valores del desplegable de ejemplos para ver algunas ideas. Recuerda: cualquier cosa distinta de los caracteres X y x no será convertida.";
-$L["help_text3"] = "Como con muchos de los otros tipos de dato, para generar números de teléfono in múltiples formatos, sepáralos con un carácter tubería, |.";
-$L["incomplete_fields"] = "El tipo de dato Teléfono requiere informar el formato en el campo de texto Opciones. Por favor, arregla las siguientes filas:";
-$L["uk"] = "Reino Unido";
-$L["france"] = "Francia";
-$L["germany"] = "Alemania";
-$L["australia"] = "Australia";
-$L["japan"] = "Japón";
diff --git a/plugins/dataTypes/Phone/lang/fr.php b/plugins/dataTypes/Phone/lang/fr.php
deleted file mode 100644
index bef9358c1..000000000
--- a/plugins/dataTypes/Phone/lang/fr.php
+++ /dev/null
@@ -1,18 +0,0 @@
-X majuscules seront converties en un nombre aléatoire compris entre 1 et 9 et les x minuscules seront converties en un nombre aléatoire entre 0 et 9.";
-$L["help_text2"] = "Sélectionnez l'une des valeurs de l'exemple pour avoir une idée du fonctionnement. Rappelez-vous: rien d'autre que les caractères X et x ne sera converti.";
-$L["help_text3"] = "Comme avec la plupart des autres types de données, il est possible de générer des numéros de téléphone au formats multiples: séparez-les par un caractère pipe |.";
-$L["incomplete_fields"] = "Le type de téléphone / fax de données doit avoir un format précisé dans le champ de texte Options. Corrigez les lignes suivantes:";
-$L["japan"] = "Japon";
-$L["name"] = "Téléphone / Fax";
-$L["uk"] = "Royaume-Uni";
\ No newline at end of file
diff --git a/plugins/dataTypes/Phone/lang/nl.php b/plugins/dataTypes/Phone/lang/nl.php
deleted file mode 100644
index 224567bae..000000000
--- a/plugins/dataTypes/Phone/lang/nl.php
+++ /dev/null
@@ -1,17 +0,0 @@
-X 's zal worden omgezet naar een willekeurig getal tussen 1 en 9; kleine letters x ' s zal worden omgezet naar een willekeurig getal tussen 0 en 9.";
-$L["help_text2"] = "Selecteer een van de waarden in het voorbeeld dropdown voor een aantal ideeën. Vergeet niet: iets anders dan de X en x karakter overblijven onbekeerd.";
-$L["help_text3"] = "Zoals met veel van de andere soorten gegevens, het genereren van telefoonnummers in meerdere formaat ze te scheiden met een pijp | karakter.";
-$L["incomplete_fields"] = "The Phone / Fax gegevenstype dient te beschikken over het formaat ingevoerd in het Opties tekstveld. Please fix de volgende rijen:";
-$L["japan"] = "Japan";
-$L["uk"] = "UK";
\ No newline at end of file
diff --git a/plugins/dataTypes/PhoneRegional/PhoneRegional.class.php b/plugins/dataTypes/PhoneRegional/PhoneRegional.class.php
deleted file mode 100644
index 343e84df5..000000000
--- a/plugins/dataTypes/PhoneRegional/PhoneRegional.class.php
+++ /dev/null
@@ -1,315 +0,0 @@
-getRowCountryField($generationContextData);
- $rowRegionField = $this->getRowRegionField($generationContextData);
-
- // 2. again check the existing data set to get a (maybe) random country and a (maybe) region-specific phone format
- list($countrySlug, $regionSlug, $customRegionalFormat) = $this->getCountryAndRegionalFormat($countryPhoneFormats, $rowCountryField, $rowRegionField);
-
- // 3. see if this country-region has any area codes to mess with. Returns an empty array if there are no area codes
- $areaCodes = $this->getAreaCodes($countrySlug, $regionSlug);
-
- // 4. now get the desired phone format entered in the UI
- $desiredFormat = $this->getDesiredPhoneFormat($countrySlug, $countryPhoneFormats);
-
- // now generate a phone number
- $phoneNumber = $this->generatePhoneNumber($desiredFormat, $customRegionalFormat, $areaCodes);
-
- return array(
- "display" => $phoneNumber
- );
- }
-
-
- /**
- * Returns three things based on the data passed:
- * - a random country slug
- * - a region slug
- * - the phone number format for the country-region. If one isn't defined, it returns an empty string.
- */
- private function getCountryAndRegionalFormat($countryPhoneFormats, $rowCountryField, $rowRegionField) {
- $customRegionalFormat = "";
- $countrySlug = "";
- $regionSlug = "";
-
- // now get the most specific format we have for this country-region
- if (empty($rowCountryField) && empty($rowRegionField)) {
- $countrySlug = array_rand($countryPhoneFormats);
- } else if (!empty($rowRegionField)) {
- $countrySlug = $rowRegionField["randomData"]["country_slug"];
- $regionSlug = $rowRegionField["randomData"]["region_slug"];
-
- // if there's a custom format for this region, use it. Otherwise use the default format for the country
- if (array_key_exists($countrySlug, $this->phoneFormats)) {
- if (array_key_exists($regionSlug, $this->phoneFormats[$countrySlug]["regionSpecificFormat"])) {
- if (array_key_exists("format", $this->phoneFormats[$countrySlug]["regionSpecificFormat"][$regionSlug])) {
- $customRegionalFormat = $this->phoneFormats[$countrySlug]["regionSpecificFormat"][$regionSlug]["format"];
- }
- }
- }
- } else {
- if (isset($rowCountryField["randomData"]["slug"]) && array_key_exists($rowCountryField["randomData"]["slug"], $countryPhoneFormats)) {
- $countrySlug = $rowCountryField["randomData"]["slug"];
- } else {
- $countrySlug = array_rand($countryPhoneFormats);
- }
- }
-
- return array($countrySlug, $regionSlug, $customRegionalFormat);
- }
-
- private function getDesiredPhoneFormat($countrySlug, $countryPhoneFormats) {
- $phoneFormat = "";
-
- if (array_key_exists($countrySlug, $countryPhoneFormats)) {
- $phoneFormat = $countryPhoneFormats[$countrySlug];
- } else {
- // here we're generating a row for a country whose format we have NO idea. The whole purpose of the regional phone
- // Data Type is for countries that we do, so in this case we simple
- $phoneFormat = $this->unknownCountryPhoneFormat;
- }
-
- return $phoneFormat;
- }
-
- public function getRowCountryField($generationContextData) {
- $rowCountryInfo = array();
- foreach ($generationContextData["existingRowData"] as $row) {
- if ($row["dataTypeFolder"] == "Country") {
- $rowCountryInfo = $row;
- break;
- }
- }
- return $rowCountryInfo;
- }
-
- public function getRowRegionField($generationContextData) {
- $rowRegionInfo = array();
- foreach ($generationContextData["existingRowData"] as $row) {
- if ($row["dataTypeFolder"] == "Region") {
- $rowRegionInfo = $row;
- break;
- }
- }
- return $rowRegionInfo;
- }
-
- // it blows my mind how awful this method is
- private function getAreaCodes($countrySlug, $regionSlug) {
- if (!array_key_exists($countrySlug, $this->phoneFormats)) {
- return array();
- }
- if (!array_key_exists("phoneFormat", $this->phoneFormats[$countrySlug])) {
- return array();
- }
- if (!array_key_exists("areaCodes", $this->phoneFormats[$countrySlug]["phoneFormat"])) {
- return array();
- }
- $areaCodes = $this->phoneFormats[$countrySlug]["phoneFormat"]["areaCodes"];
-
- if (array_key_exists($regionSlug, $this->phoneFormats[$countrySlug]["regionSpecificFormat"])) {
- if (array_key_exists("areaCodes", $this->phoneFormats[$countrySlug]["regionSpecificFormat"][$regionSlug])) {
- $areaCodes = $this->phoneFormats[$countrySlug]["regionSpecificFormat"][$regionSlug]["areaCodes"];
- }
- }
-
- return $areaCodes;
- }
-
- public function getOptionsColumnHTML() {
- $countryPlugins = Core::$countryPlugins;
-
- $html = "";
- foreach ($countryPlugins as $pluginInfo) {
- $slug = $pluginInfo->getSlug();
- $regionName = $pluginInfo->getRegionNames();
- $extendedData = $pluginInfo->getExtendedData();
-
- if (!isset($extendedData["phoneFormat"]) || !isset($extendedData["phoneFormat"]["displayFormats"])) {
- continue;
- }
-
- $options = $this->getDisplayFormatOptions($slug, $extendedData["phoneFormat"]["displayFormats"]);
- $html .= <<
- $regionName
- $options
-
-EOF;
- }
- $html .= '
';
-
- return $html;
- }
-
- public function getDisplayFormatOptions($countrySlug, $displayFormats) {
- $options = "";
- $id = "dtPhoneRegional_{$countrySlug}_%ROW%";
-
- if (is_string($displayFormats)) {
- $options = " $displayFormats";
- } else {
- if (is_array($displayFormats) && count($displayFormats) == 1) {
- $options = " {$displayFormats[0]}";
- } else {
- $options = "";
- for ($i=0; $i{$displayFormats[$i]}";
- }
- $options .= " ";
- }
- }
-
- return $options;
- }
-
- /**
- * Loop through the formats returned by the client for the supported country plugins and make a note of the
- * format chosen.
- * @param object $generator
- * @param $postdata
- * @param $colNum
- * @param $numCols
- * @return array|mixed
- */
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- $countries = $generator->getCountries();
-
- // if the user didn't select any Country plugins, they want ANY old region
- $countryPhoneFormats = array();
- foreach ($countries as $slug) {
- $key = "dtPhoneRegional_{$slug}_$colNum";
- if (array_key_exists($key, $postdata)) {
- $countryPhoneFormats[$slug] = $postdata[$key];
- }
- }
-
- return $countryPhoneFormats;
- }
-
-
- public function getHelpHTML() {
- $html =<<
- {$this->L["help_text"]}
-
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(100) default NULL",
- "SQLField_Oracle" => "varchar2(100) default NULL",
- "SQLField_MSSQL" => "VARCHAR(100) NULL"
- );
- }
-
- private function initPhoneFormats() {
- $countryPlugins = Core::$countryPlugins;
- $formats = array();
- foreach ($countryPlugins as $countryInfo) {
- $extendedData = $countryInfo->getExtendedData();
- if (!isset($extendedData["phoneFormat"]) || !isset($extendedData["phoneFormat"]["displayFormats"])) {
- continue;
- }
-
- $formats[$countryInfo->getSlug()] = array(
- "phoneFormat"=> $extendedData["phoneFormat"],
- "regionSpecificFormat" => $countryInfo->getRegionalExtendedData("phoneFormat")
- );
- }
- $this->phoneFormats = $formats;
- }
-
-
- private function generatePhoneNumber($desiredFormat, $customRegionFormat, $areaCodes) {
-
- // first off, replace any area code in the desired format
- $phoneNumber = $this->replaceRandomAreaCode($desiredFormat, $areaCodes);
-
- // if there isn't a custom region format, no problem - just switch out the X's and x's
- if (empty($customRegionFormat)) {
- $phoneNumber = Utils::generateRandomAlphanumericStr($phoneNumber);
- } else {
- // there IS a custom format. This is more complicated. We need to now merge $customRegionFormat (e.g. 123xxxx)
- // with the $desiredFormat (e.g. 1 (604) xxx-xxxx). The assumption here is that area codes always come first.
- $replacementChars = Utils::generateRandomAlphanumericStr($customRegionFormat);
-
- $phoneNumberRev = strrev($phoneNumber);
- $replacementCharsRev = strrev($replacementChars);
- $xIndex = 0;
- $newPhoneNumber = "";
- for ($i=0; $izipFormats);
- } else {
- $randCountrySlug = $selectedCountrySlugs[mt_rand(0, count($selectedCountrySlugs)-1)];
- }
- $randomZip = $this->convert($randCountrySlug, "");
- } else {
-
- $countrySlug = "";
- if (!empty($rowCountryInfo) && is_array($rowCountryInfo["randomData"]) && array_key_exists("slug", $rowCountryInfo["randomData"])) {
- $countrySlug = $rowCountryInfo["randomData"]["slug"];
- } else {
- if (isset($rowRegionInfo["randomData"]) && is_array($rowRegionInfo["randomData"]) && array_key_exists("country_slug", $rowRegionInfo["randomData"])) {
- $countrySlug = $rowRegionInfo["randomData"]["country_slug"];
- }
- }
-
- if (!empty($countrySlug) && in_array($countrySlug, $selectedCountrySlugs)) {
- $randomZip = $this->convert($countrySlug, $regionCode);
- } else {
- $randCountrySlug = array_rand($this->zipFormats);
- $randomZip = $this->convert($randCountrySlug, $regionCode);
- }
- }
-
- return array(
- "display" => $randomZip
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- $countries = $generator->getCountries();
- $options = array();
- foreach ($countries as $slug) {
- if (isset($postdata["dtCountryIncludeZip_{$slug}_$colNum"])) {
- $options[] = $slug;
- }
- }
- return $options;
- }
-
- public function getOptionsColumnHTML() {
- $countryPlugins = Core::$countryPlugins;
- $html = "";
- foreach ($countryPlugins as $pluginInfo) {
- $slug = $pluginInfo->getSlug();
- $regionName = $pluginInfo->getRegionNames();
-
- $html .= <<
- $regionName
-
-EOF;
- }
- $html .= '
';
-
- return $html;
- }
-
- public function getHelpHTML() {
- return "{$this->L["help_text"]}
";
- }
-
- /**
- * This is called when data generation starts. It does the work of generating a data structure containing all
- * the info we need to intelligently generate a zip format for the country-region.
- */
- private function initZipFormats() {
- $countryPlugins = Core::$countryPlugins;
- $formats = array();
- foreach ($countryPlugins as $countryInfo) {
- $extendedData = $countryInfo->getExtendedData();
-
- if (!isset($extendedData["zipFormat"])) {
- continue;
- }
-
- $format = "";
- $isAdvanced = false;
- $replacements = array();
- if (is_string($extendedData["zipFormat"])) {
- $format = $extendedData["zipFormat"];
- } else if (array_key_exists("format", $extendedData["zipFormat"]) && is_string($extendedData["zipFormat"]["format"])) {
- $format = $extendedData["zipFormat"]["format"];
- $replacements = isset($extendedData["zipFormat"]["replacements"]) ? $extendedData["zipFormat"]["replacements"] : array(); // TODO double check empty array is valid
- $isAdvanced = true;
- }
-
- if (empty($format)) {
- continue;
- }
-
- $returnInfo = array(
- "format" => $format,
- "replacements" => $replacements,
- "isAdvanced" => $isAdvanced,
- "regionSpecificFormat" => array()
- );
- if ($isAdvanced) {
- $returnInfo["regionSpecificFormat"] = $countryInfo->getRegionalExtendedData("zipFormat");
- }
- $formats[$countryInfo->getSlug()] = $returnInfo;
- }
- $this->zipFormats = $formats;
- }
-
-
- private function convert($countrySlug, $regionShort = "") {
- $zipInfo = $this->zipFormats[$countrySlug];
-
- $result = "";
- if ($zipInfo["isAdvanced"]) {
-
- // if the country plugin defined a custom zip format for this region, use it
- if (!empty($regionShort) && !empty($zipInfo["regionSpecificFormat"]) && array_key_exists($regionShort, $zipInfo["regionSpecificFormat"])) {
- $customFormat = isset($zipInfo["regionSpecificFormat"][$regionShort]["format"]) ? $zipInfo["regionSpecificFormat"][$regionShort]["format"]: "";
- $replacements = isset($zipInfo["regionSpecificFormat"][$regionShort]["replacements"]) ? $zipInfo["regionSpecificFormat"][$regionShort]["replacements"] : "";
- }
- $customFormat = !empty($customFormat) ? $customFormat : $zipInfo["format"];
- $replacements = !empty($replacements) ? $replacements : $zipInfo["replacements"];
-
- // now iterate over $customFormat and do whatever replacements have been specified
- for ($i=0; $i "varchar(10) default NULL",
- "SQLField_Oracle" => "varchar2(10) default NULL",
- "SQLField_MSSQL" => "VARCHAR(10) NULL"
- );
- }
-}
diff --git a/plugins/dataTypes/PostalZip/PostalZip.js b/plugins/dataTypes/PostalZip/PostalZip.js
deleted file mode 100755
index 0d5028da7..000000000
--- a/plugins/dataTypes/PostalZip/PostalZip.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/*global $:false*/
-define([
- "manager",
- "constants",
- "lang"
-], function(manager, C, L) {
-
- "use strict";
-
- var MODULE_ID = "data-type-PostalZip";
- var LANG = L.dataTypePlugins.PostalZip;
- var _currSelectedCountries = null;
-
- var _init = function() {
- var subscriptions = {};
- subscriptions[C.EVENT.COUNTRIES.CHANGE] = _countryChange;
- manager.subscribe(MODULE_ID, subscriptions);
- };
-
- var _saveRow = function(rowNum) {
- var shownClassesSelectors = [];
- for (var i=0; i<_currSelectedCountries.length; i++) {
- shownClassesSelectors.push(".dtCountry_" + _currSelectedCountries[i] + " input");
- }
- var shownClassesSelector = shownClassesSelectors.join(",");
-
- // find the checkboxes in this row
- var visible = $("#gdColOptions_" + rowNum).find(shownClassesSelector);
- var checked = [];
- for (var j=0; j
- function() { return ($("#options_" + rowNum + " input:checkbox").size() == =$num_countries ?>); }
- ];
- },
-
- saveRow: function(rowNum)
- {
- var zips = [];
- $("#options_" + rowNum + " input:checked").each(function() {
- // strip out any row numbers from the name attributes. We're not interested in them. When
- // it comes to reloading the content, they'll be in the way
- zips.push(this.name.replace(/\d/g, ""));
- });
-
- return { "zips": zips };
- }
-}
-*/
\ No newline at end of file
diff --git a/plugins/dataTypes/PostalZip/lang/de.php b/plugins/dataTypes/PostalZip/lang/de.php
deleted file mode 100644
index eefad7e6c..000000000
--- a/plugins/dataTypes/PostalZip/lang/de.php
+++ /dev/null
@@ -1,6 +0,0 @@
-countryRegionHash = Core::$geoData->getCountryRegionHash();
- }
- }
-
- /**
- * Generate a random region, and return the display string and additional meta data for use
- * by any other Data Type.
- */
- public function generate($generator, $generationContextData) {
- $generationOptions = $generationContextData["generationOptions"];
-
- $regionInfo = array();
- $keys = array("region", "region_short");
-
- // if no country plugins were defined, we just randomly grab a region from what's available
- if ($generationOptions["resultType"] == "any") {
-
- $randRegionInfo = $this->getRandRegion($this->countryRegionHash);
-
- $index = mt_rand(0, 1);
- $regionInfo["display"] = $randRegionInfo[$keys[$index]];
- $regionInfo["region_slug"] = $randRegionInfo["region_slug"];
- $regionInfo["country_slug"] = $randRegionInfo["countrySlug"];
-
- // here, one or more Country plugins were included
- // - if there's a country row, pick a region within it.
- // - if not, pick any region within the list of country plugins
- } else {
-
- // see if this row has a country - TODO memoize
- $rowCountryInfo = array();
- while (list($key, $info) = each($generationContextData["existingRowData"])) {
- if ($info["dataTypeFolder"] == "Country") {
- $rowCountryInfo = $info;
- break;
- }
- }
-
- // if the data set didn't include a Country, just generate any old region pulled from any of the
- // Country plugins selected
- if (empty($rowCountryInfo)) {
- $randRegionInfo = $this->getRandRegion($generationOptions["countries"]);
- $randCountrySlug = $randRegionInfo["countrySlug"];
-
- // pick a format (short / long) based on whatever the specified through the UI
- $formatIndex = $this->getRandIndex($generationOptions["countries"], $randCountrySlug);
- $regionInfo["display"] = $randRegionInfo[$keys[$formatIndex]];
- $regionInfo["region_slug"] = $randRegionInfo["region_slug"];
- $regionInfo["country_slug"] = $randCountrySlug;
-
- // here, there *was* a country Data Type chosen and the Country row is pulling from the subset of
- // Country plugins
- } else {
-
- // the slug exists if the row country was one of the Country plugins
- $currRowCountrySlug = array_key_exists("slug", $rowCountryInfo["randomData"]) ? $rowCountryInfo["randomData"]["slug"] : "";
-
- // here, we've gotten the slug of the country for this particular row, but the user may have unselected
- // it from the row's generation options. See if it's available and if so, use that; otherwise, display
- // any old region from the selected Country plugins
- if (array_key_exists($currRowCountrySlug, $generationOptions["countries"])) {
- $regions = $this->countryRegionHash[$currRowCountrySlug];
- $regionInfo = $regions["regions"][mt_rand(0, $regions["numRegions"]-1)];
- $index = $this->getRandIndex($generationOptions["countries"], $currRowCountrySlug);
- $regionInfo["display"] = $regionInfo[$keys[$index]];
- } else {
- $randRegionInfo = $this->getRandRegion($generationOptions["countries"]);
- $randCountrySlug = $randRegionInfo["countrySlug"];
- $formatIndex = $this->getRandIndex($generationOptions["countries"], $randCountrySlug);
- $randCountry = $this->countryRegionHash[$randCountrySlug];
- $regionInfo = $randCountry["regions"][mt_rand(0, $randCountry["numRegions"]-1)];
- $regionInfo["display"] = $regionInfo[$keys[$formatIndex]];
- }
-
- $regionInfo["country_slug"] = $currRowCountrySlug;
- }
- }
-
- return $regionInfo;
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- $countries = $generator->getCountries();
- $generationOptions = array();
-
- // if the user didn't select any Country plugins, they want ANY old region
- if (empty($countries)) {
- $generationOptions["resultType"] = "any";
- } else {
- $generationOptions["resultType"] = "specificCountries";
- $generationOptions["countries"] = array();
-
- foreach ($countries as $slug) {
- if (isset($postdata["dtIncludeRegion_{$slug}_$colNum"])) {
- $region_full = (isset($postdata["dtIncludeRegion_{$slug}_Full_$colNum"])) ? true : false;
- $region_short = (isset($postdata["dtIncludeRegion_{$slug}_Short_$colNum"])) ? true : false;
- $generationOptions["countries"][$slug] = array(
- "full" => $region_full,
- "short" => $region_short
- );
- }
- }
- }
-
- return $generationOptions;
- }
-
-
- public function getOptionsColumnHTML() {
- $countryPlugins = Core::$countryPlugins;
-
- $html = "{$this->L["no_countries_selected"]}
";
- foreach ($countryPlugins as $pluginInfo) {
- $slug = $pluginInfo->getSlug();
- $regionName = $pluginInfo->getRegionNames();
-
- $html .= <<
- $regionName
-
- {$this->L["full"]}
-
-
- {$this->L["short"]}
-
-
-EOF;
- }
- $html .= '
';
-
- return $html;
- }
-
- public function getHelpHTML() {
- return "{$this->L["help_text"]}
";
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(50) default NULL",
- "SQLField_Oracle" => "varchar2(50) default NULL",
- "SQLField_MSSQL" => "VARCHAR(50) NULL"
- );
- }
-
- private function getRandIndex($options, $randCountrySlug) {
- $index = null;
- if ($options[$randCountrySlug]["full"] == 1 && $options[$randCountrySlug]["short"] == 1) {
- $index = mt_rand(0, 1); // weird, mt_rand()&1 doesn't work - always returns 1 0 1 0 1 0...
- } else if ($options[$randCountrySlug]["full"] == 1) {
- $index = 0;
- } else if ($options[$randCountrySlug]["short"] == 1) {
- $index = 1;
- }
- return $index;
- }
-
- private function getRandRegion($countries) {
- $randCountrySlug = array_rand($countries);
- $randCountry = $this->countryRegionHash[$randCountrySlug];
-
- // now get a random region, taking into account it's weight
- $weightedValues = array();
- $regions = $randCountry["regions"];
- for ($i=0; $i 0) {
- $(".dtRegionCountry_noCountries").hide();
- } else {
- $(".dtRegionCountry_noCountries").show();
- }
- };
-
- var _toggleCountryRegion = function(e) {
- var el = e.target;
- var parent = $(el).parent();
- if (el.checked) {
- parent.find("span input").removeAttr("disabled");
- parent.find("span label").addClass("dtRegionSuboptionActive").removeClass("dtRegionSuboptionInactive");
- } else {
- $(el).parent().find("span input").attr("disabled", "disabled");
- parent.find("span label").addClass("dtRegionSuboptionInactive").removeClass("dtRegionSuboptionActive");
- }
- };
-
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- init: _init,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-
-});
diff --git a/plugins/dataTypes/Region/lang/de.php b/plugins/dataTypes/Region/lang/de.php
deleted file mode 100644
index 370f1e73f..000000000
--- a/plugins/dataTypes/Region/lang/de.php
+++ /dev/null
@@ -1,8 +0,0 @@
-Vollständiger Name und Abkürzung sub-Optionen bestimmen, ob die Ausgabe die vollständige Zeichenfolge (zB \"British Columbia\") oder dessen Abkürzung (zB \"BC\") enthalten. Für UK Grafschaften, ist die Abkürzung der Standard 3-Zeichen-Chapman-Code.";
-$L["short"] = "Kurz";
-$L["no_countries_selected"] = "Keine Ländern ausgewählt.";
\ No newline at end of file
diff --git a/plugins/dataTypes/Region/lang/en.php b/plugins/dataTypes/Region/lang/en.php
deleted file mode 100644
index f4cfb6928..000000000
--- a/plugins/dataTypes/Region/lang/en.php
+++ /dev/null
@@ -1,8 +0,0 @@
-Full Name and Abbreviation sub-options determine whether the output will contain the full string (e.g. "British Columbia") or its abbreviation (e.g. "BC"). For UK counties, the abbreviation is the standard 3-character Chapman code.";
-$L["full"] = "Full";
-$L["short"] = "Short";
-$L["no_countries_selected"] = "No countries selected.";
\ No newline at end of file
diff --git a/plugins/dataTypes/Region/lang/es.php b/plugins/dataTypes/Region/lang/es.php
deleted file mode 100644
index 1ea9b16f4..000000000
--- a/plugins/dataTypes/Region/lang/es.php
+++ /dev/null
@@ -1,8 +0,0 @@
-Nombre completo y la Abreviatura determinan si la salid contendrá la cadena completa (p.e. "Columbia Británica") o su abreviatura (p.e. "BC"). Para los condados de UK, la abreviatura es el código Chapman estándar de tres caracteres.";
-$L["full"] = "Completo";
-$L["short"] = "Corto";
-$L["no_countries_selected"] = "No hay países seleccionados.";
\ No newline at end of file
diff --git a/plugins/dataTypes/Region/lang/fr.php b/plugins/dataTypes/Region/lang/fr.php
deleted file mode 100644
index f6125775c..000000000
--- a/plugins/dataTypes/Region/lang/fr.php
+++ /dev/null
@@ -1,8 +0,0 @@
-Complet et Abréviation déterminent si la sortie contiendra la chaîne complète (par exemple, «Colombie-Britannique») ou son abréviation (par exemple «BC»). Pour les comtés du Royaume-Uni, l'abréviation suit la norme 3-caractères du code Chapman.";
-$L["short"] = "Abbréviation";
-$L["no_countries_selected"] = "Aucun pays sélectionnés.";
\ No newline at end of file
diff --git a/plugins/dataTypes/Region/lang/nl.php b/plugins/dataTypes/Region/lang/nl.php
deleted file mode 100644
index 9e9947e2c..000000000
--- a/plugins/dataTypes/Region/lang/nl.php
+++ /dev/null
@@ -1,8 +0,0 @@
-Volledige naam en Afkorting sub-opties bepalen of de uitgang zal de volledige string (bijv. \"British Columbia\") of de afkorting (bijv. \"BC\") bevatten. Voor het Verenigd Koninkrijk provincies, de afkorting is van de standaard 3-tekens Chapman code.";
-$L["short"] = "Kort";
-$L["no_countries_selected"] = "Geen landen geselecteerd.";
\ No newline at end of file
diff --git a/plugins/dataTypes/StreetAddress/StreetAddress.class.php b/plugins/dataTypes/StreetAddress/StreetAddress.class.php
deleted file mode 100644
index 2430f7e8d..000000000
--- a/plugins/dataTypes/StreetAddress/StreetAddress.class.php
+++ /dev/null
@@ -1,60 +0,0 @@
-words = Utils::getLipsum();
- $this->validStreetTypes = explode(",", $this->L["street_types"]);
- $this->numValidStreetTypes = count($this->validStreetTypes);
- }
- }
-
-
- public function generate($generator, $generationContextData) {
- $streetName = ucwords(Utils::generateRandomTextStr($this->words, false, "fixed", 1));
- $streetType = $this->validStreetTypes[mt_rand(0, $this->numValidStreetTypes-1)];
-
- $format = mt_rand(1, 4);
- $streetAddress = "";
- switch($format) {
- case "1":
- $streetAddress = $this->L["po_box"] . " " . mt_rand(100, 999) . ", " . mt_rand(100, 9999) . " $streetName " . $streetType;
- break;
- case "2":
- $streetAddress = mt_rand(100, 999) . "-" . mt_rand(100, 9999) . " $streetName $streetType";
- break;
- case "3":
- $streetAddress = $this->L["ap_num"] . mt_rand(100, 999) . "-" . mt_rand(100, 9999) . " $streetName " . $streetType;
- break;
- case "4":
- $streetAddress = mt_rand(100, 9999) . " $streetName " . $streetType;
- break;
- }
-
- return array(
- "display" => $streetAddress
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255) default NULL",
- "SQLField_Oracle" => "varchar2(255) default NULL",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-}
diff --git a/plugins/dataTypes/StreetAddress/lang/de.php b/plugins/dataTypes/StreetAddress/lang/de.php
deleted file mode 100644
index 5b2a24f0a..000000000
--- a/plugins/dataTypes/StreetAddress/lang/de.php
+++ /dev/null
@@ -1,9 +0,0 @@
-words = Utils::getLipsum();
- }
- }
-
- public function generate($generator, $generationContextData) {
- $options = $generationContextData["generationOptions"];
- $textStr = Utils::generateRandomTextStr($this->words, false, "fixed", $options);
- return array(
- "display" => $textStr
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- if (empty($postdata["dtNumWords_$colNum"])) {
- return false;
- }
- return $postdata["dtNumWords_$colNum"];
- }
-
- public function getOptionsColumnHTML() {
- $html =<<L["TextFixed_generate"]} #
-{$this->L["TextFixed_words"]}
-END;
- return $html;
- }
-
- public function getHelpHTML() {
- return "{$this->L["TextFixed_help"]}
";
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "TEXT default NULL",
- "SQLField_Oracle" => "BLOB default NULL",
- "SQLField_MSSQL" => "VARCHAR(MAX) NULL"
- );
- }
-}
diff --git a/plugins/dataTypes/TextFixed/TextFixed.js b/plugins/dataTypes/TextFixed/TextFixed.js
deleted file mode 100755
index 08e263ce8..000000000
--- a/plugins/dataTypes/TextFixed/TextFixed.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name TextFixed
- * @description JS code for the TextFixed Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-TextFixed";
- var LANG = L.dataTypePlugins.TextFixed;
-
- var _saveRow = function(rowNum) {
- return {
- numWords: $("#dtNumWords_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() { },
- isComplete: function() {
- if (data && $("#dtNumWords_" + rowNum).length) {
- $("#dtNumWords_" + rowNum).val(data.numWords);
- return true;
- } else {
- return false;
- }
- }
- };
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- var isInt = /^\d+$/;
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
- // register our module
- manager.registerDataType(MODULE_ID, {
- validate: _validate,
- loadRow: _loadRow,
- saveRow: _saveRow
- });
-});
diff --git a/plugins/dataTypes/TextFixed/lang/de.php b/plugins/dataTypes/TextFixed/lang/de.php
deleted file mode 100644
index 841681dfb..000000000
--- a/plugins/dataTypes/TextFixed/lang/de.php
+++ /dev/null
@@ -1,9 +0,0 @@
-Lorem ipsum lateinischen Text.";
-$L["TextFixed_words"] = "Text";
-$L["incomplete_fields"] = "Bitte geben Sie die Anzahl der Wörter, die Sie für alle Feste Wortzahl Felder erzeugen. Siehe Zeilen:";
diff --git a/plugins/dataTypes/TextFixed/lang/en.php b/plugins/dataTypes/TextFixed/lang/en.php
deleted file mode 100644
index 6d27683ae..000000000
--- a/plugins/dataTypes/TextFixed/lang/en.php
+++ /dev/null
@@ -1,8 +0,0 @@
-lorem ipsum latin text.";
-$L["TextFixed_words"] = "words";
-$L["incomplete_fields"] = "Please enter the number of words you want to generate for all Fixed Number of Words fields. See rows: ";
\ No newline at end of file
diff --git a/plugins/dataTypes/TextFixed/lang/es.php b/plugins/dataTypes/TextFixed/lang/es.php
deleted file mode 100644
index 5d692d903..000000000
--- a/plugins/dataTypes/TextFixed/lang/es.php
+++ /dev/null
@@ -1,8 +0,0 @@
-lorem ipsum.";
-$L["TextFixed_words"] = "palabras";
-$L["incomplete_fields"] = "Por favor, introduce el número de palabras que quieres general para todos los campos Número fijo de palabras. Revisa las filas:";
diff --git a/plugins/dataTypes/TextFixed/lang/fr.php b/plugins/dataTypes/TextFixed/lang/fr.php
deleted file mode 100644
index 2e10e9d12..000000000
--- a/plugins/dataTypes/TextFixed/lang/fr.php
+++ /dev/null
@@ -1,10 +0,0 @@
-lorem ipsum (faux texte latin).";
-$L["TextFixed_name"] = "Nombre fixe de mots";
-$L["TextFixed_words"] = "mots";
-$L["incomplete_fields"] = "Entrez le nombre de mots que vous souhaitez générer. Voir les lignes:";
diff --git a/plugins/dataTypes/TextFixed/lang/nl.php b/plugins/dataTypes/TextFixed/lang/nl.php
deleted file mode 100644
index ba2c704eb..000000000
--- a/plugins/dataTypes/TextFixed/lang/nl.php
+++ /dev/null
@@ -1,8 +0,0 @@
-Lorem ipsum Latijnse tekst.";
-$L["TextFixed_words"] = "tekst";
-$L["incomplete_fields"] = "Vul het aantal woorden in dat u wilt genereren voor alle vaste Aantal woorden velden. Zie rijen:";
diff --git a/plugins/dataTypes/TextRandom/TextRandom.class.php b/plugins/dataTypes/TextRandom/TextRandom.class.php
deleted file mode 100644
index a3d1b6101..000000000
--- a/plugins/dataTypes/TextRandom/TextRandom.class.php
+++ /dev/null
@@ -1,70 +0,0 @@
-words = Utils::getLipsum();
- }
- }
-
- public function generate($generator, $generationContextData) {
- $options = $generationContextData["generationOptions"];
- $textStr = Utils::generateRandomTextStr($this->words, $options["startsWithLipsum"], "range", $options["numWordsMin"], $options["numWordsMax"]);
- return array(
- "display" => $textStr
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "TEXT default NULL",
- "SQLField_Oracle" => "BLOB default NULL",
- "SQLField_MSSQL" => "VARCHAR(MAX) NULL"
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $column, $numCols) {
- if (empty($postdata["dtNumWordsMin_$column"]) || empty($postdata["dtNumWordsMin_$column"])) {
- return false;
- }
-
- $options = array(
- "numWordsMin" => $postdata["dtNumWordsMin_$column"],
- "numWordsMax" => $postdata["dtNumWordsMax_$column"],
- "startsWithLipsum" => isset($postdata["dtStartsWithLipsum_$column"]) ? true : false
- );
-
- return $options;
- }
-
- public function getOptionsColumnHTML() {
- $html =<<< END
-
-
- {$this->L["start_with_lipsum"]}
-
-
- {$this->L["generate"]} #
- {$this->L["to"]} #
- {$this->L["words"]}
-
-END;
- return $html;
- }
-
- public function getHelpHTML() {
- return "{$this->L["help"]}
";
- }
-}
diff --git a/plugins/dataTypes/TextRandom/TextRandom.js b/plugins/dataTypes/TextRandom/TextRandom.js
deleted file mode 100644
index 481aaf603..000000000
--- a/plugins/dataTypes/TextRandom/TextRandom.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name TextRandom
- * @description JS code for the TextRandom Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-TextRandom";
- var LANG = L.dataTypePlugins.TextRandom;
-
- var _saveRow = function(rowNum) {
- return {
- startsWithLipsum: $("#dtStartsWithLipsum_" + rowNum).attr("checked") ? 1 : 0,
- minWords: $("#dtNumWordsMin_" + rowNum).val(),
- maxWords: $("#dtNumWordsMax_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() { },
- isComplete: function() {
- if ($("#dtNumWordsMax_" + rowNum).length) {
- if (data.startsWithLipsum == "1") {
- $("#dtStartsWithLipsum_" + rowNum).attr("checked", "checked");
- } else {
- $("#dtStartsWithLipsum_" + rowNum).removeAttr("checked");
- }
- $("#dtNumWordsMin_" + rowNum).val(data.minWords);
- $("#dtNumWordsMax_" + rowNum).val(data.maxWords);
- return true;
- }
- return false;
- }
- };
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- var isInt = /^\d+$/;
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
-
- manager.registerDataType(MODULE_ID, {
- validate: _validate,
- saveRow: _saveRow,
- loadRow: _loadRow
- });
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/TextRandom/lang/de.php b/plugins/dataTypes/TextRandom/lang/de.php
deleted file mode 100644
index 42cf829b0..000000000
--- a/plugins/dataTypes/TextRandom/lang/de.php
+++ /dev/null
@@ -1,11 +0,0 @@
-lorem ipsum.";
diff --git a/plugins/dataTypes/TextRandom/lang/fr.php b/plugins/dataTypes/TextRandom/lang/fr.php
deleted file mode 100644
index 755d942b6..000000000
--- a/plugins/dataTypes/TextRandom/lang/fr.php
+++ /dev/null
@@ -1,11 +0,0 @@
-lorem ipsum (faux texte latin).";
-$L["incomplete_fields"] = "Entrez les nombres de mots minimum et maximum que vous souhaitez générer. Voir les lignes:";
-$L["start_with_lipsum"] = "Commencez par \"Lorem Ipsum...\"";
-$L["to"] = "et";
-$L["words"] = "mots";
diff --git a/plugins/dataTypes/TextRandom/lang/nl.php b/plugins/dataTypes/TextRandom/lang/nl.php
deleted file mode 100644
index 6a395c1a6..000000000
--- a/plugins/dataTypes/TextRandom/lang/nl.php
+++ /dev/null
@@ -1,10 +0,0 @@
-, origin code Zeeshan Shaikh
- * @package DataTypes
- * @description this class has a hard dependency on the PAN class. That class contains a few public
- * helper functions.
- */
-class DataType_Track1 extends DataTypePlugin {
- protected $isEnabled = true;
- protected $dataTypeName = "Track 1";
- protected $hasHelpDialog = true;
- protected $dataTypeFieldGroup = "credit_card_data";
- protected $dataTypeFieldGroupOrder = 40;
-
-
- private $cardData;
-
- public function __construct($runtimeContext) {
- for ($i=622126; $i<=622925; $i++){
- $this->prefixList["prefix"][] = $i;
- }
- for ($i=3528; $i<=3589; $i++){
- $this->prefixList["jcb16"][] = $i;
- }
- parent::__construct($runtimeContext);
-
- if (class_exists("DataType_PAN")) {
- $this->cardData = DataType_PAN::getAllCreditCardData();
- }
- }
-
- public function generate($generator, $generationContextData) {
- $cardData = $this->cardData[array_rand($this->cardData)];
- $generatedCardNumber = DataType_PAN::generateCreditCardNumber($cardData["prefix"], $cardData["length"]);
-
- $characters = array("A","B","C","D","E","F","G","H","J","K","L","M","N","P","Q","R","S","T","U","V","W","X","Y","Z");
- $chars = array();
- while (count($chars) < 4) {
- $char = $characters[mt_rand(0, count($characters)-1)];
- if (!in_array($char, $chars)) {
- $chars[] = $char;
- }
- }
- $randomChars = implode("", $chars);
-
- $calendar = date("ym", mt_rand());
- $serviceCode = mt_rand(111, 999);
- $num1 = Utils::generateRandomAlphanumericStr(str_repeat("x", 26));
-
- $track1 = "%B$generatedCardNumber^CardUser/$randomChars^$calendar$serviceCode$num1?";
-
- return array(
- "display" => $track1
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["track1_help_intro"]}
";
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/Track1/lang/de.php b/plugins/dataTypes/Track1/lang/de.php
deleted file mode 100644
index 1f12846e9..000000000
--- a/plugins/dataTypes/Track1/lang/de.php
+++ /dev/null
@@ -1,4 +0,0 @@
-, origin code Zeeshan Shaikh
- * @package DataTypes
- * @description this class has a hard dependency on the PAN class. That class contains a few public
- * helper functions.
- */
-class DataType_Track2 extends DataTypePlugin {
- protected $isEnabled = true;
- protected $dataTypeName = "Track 2";
- protected $hasHelpDialog = true;
- protected $dataTypeFieldGroup = "credit_card_data";
- protected $dataTypeFieldGroupOrder = 50;
-
-
- private $cardData;
-
- public function __construct($runtimeContext) {
- for ($i=622126; $i<=622925; $i++){
- $this->prefixList["prefix"][] = $i;
- }
- for ($i=3528; $i<=3589; $i++){
- $this->prefixList["jcb16"][] = $i;
- }
- parent::__construct($runtimeContext);
-
- if (class_exists("DataType_PAN")) {
- $this->cardData = DataType_PAN::getAllCreditCardData();
- }
- }
-
-
- public function generate($generator, $generationContextData) {
- $cardData = $this->cardData[array_rand($this->cardData)];
- $generatedCardNumber = DataType_PAN::generateCreditCardNumber($cardData["prefix"], $cardData["length"]);
-
- $calendar = date("ym", mt_rand());
- $serviceCode = mt_rand(111, 999);
- $num = Utils::generateRandomAlphanumericStr(str_repeat("x", 26));
-
- $track2 = ";$generatedCardNumber=$calendar$serviceCode$num?";
-
- return array(
- "display" => $track2
- );
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "varchar(255)",
- "SQLField_Oracle" => "varchar2(255)",
- "SQLField_MSSQL" => "VARCHAR(255) NULL"
- );
- }
-
- public function getHelpHTML() {
- return "{$this->L["track2_help_intro"]}
";
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/Track2/lang/de.php b/plugins/dataTypes/Track2/lang/de.php
deleted file mode 100644
index bd32547c1..000000000
--- a/plugins/dataTypes/Track2/lang/de.php
+++ /dev/null
@@ -1,4 +0,0 @@
-isFirstBatch() && $generator->isLastBatch()) ? "privateVar" : "sessions";
-
- // find the auto-increment column
- $parentRowData = array();
- foreach ($generationContextData["existingRowData"] as $data) {
- if ($data["colNum"] == $autoIncrementRowNum) {
- $parentRowData = $data;
- break;
- }
- }
-
- // hmm... needs fixing
- if (empty($parentRowData)) {
- return array("display" => "");
- }
-
- // awesome var name, hey?
- $probablyUniqueParentRowValue = $parentRowData["randomData"]["display"];
-
- if ($storageType == "privateVar") {
- if ($probablyUniqueParentRowValue == 1) {
- $this->openTreeNodes[] = array($probablyUniqueParentRowValue, 1);
- return "0";
- }
-
- // randomly pick an open (non-full) node
- $randIndex = mt_rand(0, count($this->openTreeNodes)-1);
- $randRow = $this->openTreeNodes[$randIndex];
- $randParentRowValue = $randRow[0];
-
- // increment this node. If it's full, remove it from the array
- $this->openTreeNodes[$randIndex][1]++;
- if ($this->openTreeNodes[$randIndex][1] > $options["maxSiblings"]) {
- array_splice($this->openTreeNodes, $randIndex, 1);
- }
-
- // finally, add the new index
- $this->openTreeNodes[] = array($probablyUniqueParentRowValue, 1);
-
- } else {
-
- $sessions_openTreeNodes = unserialize($_SESSION["gdTree_openTreeNodes"]);
- if ($probablyUniqueParentRowValue == 1) {
- $sessions_openTreeNodes[] = array($probablyUniqueParentRowValue, 1);
- $_SESSION["gdTree_openTreeNodes"] = serialize($sessions_openTreeNodes);
- return "0";
- }
-
- // randomly pick an open (non-full) node
- $randIndex = mt_rand(0, count($sessions_openTreeNodes)-1);
- $randRow = $sessions_openTreeNodes[$randIndex];
- $randParentRowValue = $randRow[0];
-
- // increment this node. If it's full, remove it from the array
- $sessions_openTreeNodes[$randIndex][1]++;
- if ($sessions_openTreeNodes[$randIndex][1] > $options["maxSiblings"]) {
- array_splice($sessions_openTreeNodes, $randIndex, 1);
- }
-
- // finally, add the new index
- $sessions_openTreeNodes[] = array($probablyUniqueParentRowValue, 1);
- $_SESSION["gdTree_openTreeNodes"] = serialize($sessions_openTreeNodes);
- }
-
- return array(
- "display" => $randParentRowValue
- );
- }
-
- public function getRowGenerationOptions($generator, $postdata, $colNum, $numCols) {
- if (empty($postdata["dtTreeAutoIncrementRowNum_$colNum"]) || empty($postdata["dtTreeMaxSiblings_$colNum"])) {
- return false;
- }
-
- // note that we don't bother confirming that the AutoIncrement row specified actually exists. The
- // reason being perhaps the user wants to use another field, like an alpha-numeric or GUID
- $options = array(
- "autoIncrementRowNum" => $postdata["dtTreeAutoIncrementRowNum_$colNum"],
- "maxSiblings" => $postdata["dtTreeMaxSiblings_$colNum"]
- );
-
- return $options;
- }
-
- public function getOptionsColumnHTML() {
- $html =<<{$this->L["auto_increment_row_num"]}
-{$this->L["max_num_sibling_nodes"]}
-END;
- return $html;
- }
-
- public function getDataTypeMetadata() {
- return array(
- "SQLField" => "mediumint default NULL",
- "SQLField_Oracle" => "number",
- "SQLField_MSSQL" => "INTEGER NULL",
- "SQLField_Postgres" => "integer NULL",
- );
- }
-
- public function getHelpHTML() {
- $html =<<
- {$this->L["help_1"]}
-
-
- {$this->L["help_2"]}
-
-END;
- return $html;
- }
-}
\ No newline at end of file
diff --git a/plugins/dataTypes/Tree/Tree.js b/plugins/dataTypes/Tree/Tree.js
deleted file mode 100755
index 20596204e..000000000
--- a/plugins/dataTypes/Tree/Tree.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/*global $:false,define:false*/
-define([
- "manager",
- "constants",
- "lang",
- "generator"
-], function(manager, C, L, generator) {
-
- "use strict";
-
- /**
- * @name Tree
- * @description JS code for the Tree Data Type.
- * @see DataType
- * @namespace
- */
-
- var MODULE_ID = "data-type-Tree";
- var LANG = L.dataTypePlugins.Tree;
-
-
- var _saveRow = function(rowNum) {
- return {
- autoIncRowNum: $("#dtTreeAutoIncrementRowNum_" + rowNum).val(),
- maxSiblings: $("#dtTreeMaxSiblings_" + rowNum).val()
- };
- };
-
- var _loadRow = function(rowNum, data) {
- return {
- execute: function() { },
- isComplete: function() {
- if ($("#dtTreeMaxSiblings_" + rowNum).length) {
- $("#dtTreeAutoIncrementRowNum_" + rowNum).val(data.autoIncRowNum);
- $("#dtTreeMaxSiblings_" + rowNum).val(data.maxSiblings);
- return true;
- }
- return false;
- }
- };
- };
-
- var _validate = function(rows) {
- var visibleProblemRows = [];
- var problemFields = [];
- var isInt = /^\d+$/;
- for (var i=0; i" + visibleProblemRows.join(", ") + ""});
- }
- return errors;
- };
-
-
- manager.registerDataType(MODULE_ID, {
- validate: _validate,
- saveRow: _saveRow,
- loadRow: _loadRow
- });
-
-});
\ No newline at end of file
diff --git a/plugins/dataTypes/Tree/lang/de.php b/plugins/dataTypes/Tree/lang/de.php
deleted file mode 100644
index 03915b51d..000000000
--- a/plugins/dataTypes/Tree/lang/de.php
+++ /dev/null
@@ -1,11 +0,0 @@
-