diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..b3fce8c03 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Slickgrid-Universal Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when +an individual is representing the project or its community in public spaces. +Examples of representing a project or community include using an official +project e-mail address, posting via an official social media account, or acting +as an appointed representative at an online or offline event. Representation of +a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 000000000..c744ba09a --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,12 @@ +# Contributing + +We'd love for you to contribute and to make this project even better than it is today! If this interests you, please begin by reading the project [Wiki documentation](https://github.com/ghiscoding/slickgrid-universal/wiki). Once you consulted them and you believe that you can help us with new features, improvement or even fixes then go ahead and submit a Pull Request. + +When we mention `VSCode`, we mean `Visual Studio Code` editor which can be downloaded [here](https://code.visualstudio.com) + +Before accepting any Pull Request, we need to make sure that you followed these steps: +1. Install `Yarn` globally since it is used in all `package.json` scripts and VSCode Tasks if you want to use them. +2. Have you tested all your changes with Jest? +3. Have you run the TypeScript Build? + - you can run the build with `yarn run build` from the root of the project +4. If you did step 2 and 3, then the final step would be the Pull Request... but wait! For readability purposes, we would like you to only submit the relevant pieces of code that you changed. We are basically asking you to do a Build and make sure there's no errors (Yes please) but to not include the produced `dist` folder. We just want to see the real changes, nothing else (but we still want to make sure it Builds before creating a PR). \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..eb4f538b6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: N4N679OT +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +custom: # Replace with a single custom sponsorship URL diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..820cec8fa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,46 @@ +--- +name: Bug report +about: Create a report to help us improve the library +title: '' +labels: '' +assignees: '' + +--- + + + + + +## I'm submitting a Bug report + +### Your Environment + +| Software | Version(s) | +| -------------------- | ---------- | +| Slickgrid-Universal | x.y | +| TypeScript | x.y | +| Package Name | x.y | + +### Describe the Bug + + +### Steps to Reproduce + + +### Expected Behavior + + +### Current Behavior + + + +### Possible Solution + + +### Code Sample + \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..0b5cf3391 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,27 @@ +--- +name: Feature request +about: Suggest a new Feature or Enhancement +title: '' +labels: '' +assignees: '' + +--- + + + + + +## I'm submitting a Feature request + +### Motivation / Use Case + + +### Expected Behavior + + +### Other Information + diff --git a/.github/ISSUE_TEMPLATE/general.md b/.github/ISSUE_TEMPLATE/general.md new file mode 100644 index 000000000..1bbad4fdb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/general.md @@ -0,0 +1,39 @@ +--- +name: General +about: Anything other topics +title: '' +labels: '' +assignees: '' + +--- + + + + + +## General Topic + +### Your Environment + +| Software | Version(s) | +| -------------------- | ---------- | +| Slickgrid-Universal | x.y | +| TypeScript | x.y | +| Package Name | x.y | + +### Context or Topic + + +### Expected Behavior + + +### Current Behavior + + +### Possible Solution + diff --git a/.vscode/launch.json b/.vscode/launch.json index 555caa3f7..731bdd5a6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -40,4 +40,4 @@ } } ] -} +} \ No newline at end of file diff --git a/README.md b/README.md index 9b0f28c7c..39d227aba 100644 --- a/README.md +++ b/README.md @@ -40,55 +40,61 @@ and it is also used to test with the UI portion. The Vanilla bundle is also used ### Available Public Packages | Package Name | Description | -| --------| ----------- | -| [@slickgrid-universal/common](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/common) | commonly used Formatters/Editors/Filters/Services/... | -| [@slickgrid-universal/excel-export](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/excel-export) | Export to Excel Service (xls/xlsx) | -| [@slickgrid-universal/file-export](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/file-export) | Export to Text File Service (csv/txt) | -| [@slickgrid-universal/graphql](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/graphql) | GraphQL Query Service (support Filter/Sort/Pagination) | -| [@slickgrid-universal/odata](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/odata) | OData Query Service (support Filter/Sort/Pagination) | +| --------| ----------- | +| [@slickgrid-universal/common](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/common) | commonly used Formatters/Editors/Filters/Services/... | +| [@slickgrid-universal/excel-export](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/excel-export) | Export to Excel Service (xls/xlsx) | +| [@slickgrid-universal/file-export](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/file-export) | Export to Text File Service (csv/txt) | +| [@slickgrid-universal/graphql](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/graphql) | GraphQL Query Service (support Filter/Sort/Pagination) | +| [@slickgrid-universal/odata](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/odata) | OData Query Service (support Filter/Sort/Pagination) | | [@slickgrid-universal/vanilla-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/packages/vanilla-bundle) | a vanilla TypeScript/JavaScript implementation | ### Available Demos -| Package Name | Description | -| --------| ----------- | -| [slickgrid-universal/webpack-demo-vanilla-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/examples/webpack-demo-vanilla-bundle) | standalone package written in plain TypeScript for demo & UI testing. | +| Package Name | Description | +| --------| ----------- | +| [slickgrid-universal/webpack-demo-vanilla-bundle](https://github.com/ghiscoding/slickgrid-universal/tree/master/examples/webpack-demo-vanilla-bundle) | standalone package written in plain TypeScript for demo & UI testing. | ## Installation To get going with this monorepo, you will need to clone the repo and then follow the steps below -1. Lerna Bootstrap +1. Install npm packages with Yarn +This lib uses Yarn workspaces and so you need to use Yarn to install packages +```bash +yarn install +``` + +2. Lerna Bootstrap Run it **only once**, this will install all dependencies and add necessary monorepo symlinks ```bash -npm run bootstrap +yarn run bootstrap ``` -2. Build +3. Build To get started you must run (also once) an initial TS build so that all necessary `dist` are created for all the packages to work together. ```bash -npm run build +yarn run build ``` -3. Run Dev (Vanilla Implementation) +4. Run Dev (Vanilla Implementation) There is a Vanilla flavour implementation of this monorepo, vanilla means that it is not associated to any framework and is written in plain TypeScript without being bound to any framework. The implementation is very similar to Angular and Aurelia. It could be used as a guideline to implement it with other frameworks. ```bash -npm run dev:watch +yarn run dev:watch ``` ### Tests To run all packages Jest unit tests, you can run this command ```bash -npm run test +yarn run test # or as a watch -npm run test:watch +yarn run test:watch ``` ## TODOs @@ -114,19 +120,19 @@ npm run test:watch - [x] Grouping Formatters (12) - [x] SortComparers (5) - [x] Services (14) -- [ ] Others / Vanilla Implementation +- [x] Others / Vanilla Implementation - [x] Custom Footer - [x] Backend Services + Pagination - [x] Local Pagination - [x] Grid Presets - [x] Preset Row Selections - [x] Should work even after initializing the dataset later (SF) - - [x] Preset Filters not working with Tree Data View + - [x] Preset Filters not working with Tree Data View - [x] Dynamically Add Columns - [x] Tree Data - [x] add missing `collectionAsync` for Editors, Filters (autoCompleteFilter, selectFilter) - [x] Grid Service should use SlickGrid transactions `beginUpdate`, `endUpdate` for performance reason whenever possible - - [ ] Translations Support + - [x] Translations Support #### Other Todos - [x] VScode Chrome Debugger @@ -149,4 +155,5 @@ npm run test:watch - [x] Add interfaces to all SlickGrid core lib classes & plugins (basically add Types to everything) - [x] Copy cell text (context menu) doesn't work in SF - [x] Remove all Services init method 2nd argument (we can get DataView directly from the Grid object) +- [ ] Check why `DOM Purify` doesn't work in SF - [ ] Search for any left "todo" in the entire solution diff --git a/examples/webpack-demo-vanilla-bundle/assets/i18n/en.json b/examples/webpack-demo-vanilla-bundle/assets/i18n/en.json new file mode 100644 index 000000000..6700de229 --- /dev/null +++ b/examples/webpack-demo-vanilla-bundle/assets/i18n/en.json @@ -0,0 +1,80 @@ +{ + "ALL_SELECTED": "All Selected", + "CANCEL": "Cancel", + "CLEAR_ALL_FILTERS": "Clear all Filters", + "CLEAR_ALL_GROUPING": "Clear all Grouping", + "CLEAR_ALL_SORTING": "Clear all Sorting", + "CLEAR_FROZEN_COLUMNS": "Clear Frozen Columns", + "COLLAPSE_ALL_GROUPS": "Collapse all Groups", + "COLUMNS": "Columns", + "COMMANDS": "Commands", + "CONTAINS": "Contains", + "COPY": "Copy", + "ENDS_WITH": "Ends With", + "EQUALS": "Equals", + "EXPAND_ALL_GROUPS": "Expand all Groups", + "EXPORT_TO_CSV": "Export in CSV format", + "EXPORT_TO_EXCEL": "Export to Excel", + "EXPORT_TO_TAB_DELIMITED": "Export in Text format (Tab delimited)", + "EXPORT_TO_TEXT_FORMAT": "Export in Text format", + "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} of {{totalItems}} items", + "FORCE_FIT_COLUMNS": "Force fit columns", + "FREEZE_COLUMNS": "Freeze Columns", + "INVALID_FLOAT": "The number must be valid and have a maximum of {{maxDecimal}} decimals.", + "GROUP_BY": "Group by", + "HIDE_COLUMN": "Hide Column", + "IN_COLLECTION_SEPERATED_BY_COMMA": "Search items in a collection, must be separated by a comma (a,b)", + "ITEMS": "items", + "ITEMS_PER_PAGE": "items per page", + "LAST_UPDATE": "Last Update", + "NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Search items not in a collection, must be separated by a comma (a,b)", + "OF": "of", + "OK": "OK", + "PAGE": "Page", + "PAGE_X_OF_Y": "page {{x}} of {{y}}", + "REFRESH_DATASET": "Refresh Dataset", + "REMOVE_FILTER": "Remove Filter", + "REMOVE_SORT": "Remove Sort", + "SAVE": "Save", + "SELECT_ALL": "Select All", + "SORT_ASCENDING": "Sort Ascending", + "SORT_DESCENDING": "Sort Descending", + "STARTS_WITH": "Starts With", + "SYNCHRONOUS_RESIZE": "Synchronous resize", + "TOGGLE_FILTER_ROW": "Toggle Filter Row", + "TOGGLE_PRE_HEADER_ROW": "Toggle Pre-Header Row", + "X_OF_Y_SELECTED": "# of % selected", + "BILLING": { + "ADDRESS": { + "STREET": "Billing Address Street", + "ZIP": "Billing Address Zip" + }, + "INFORMATION": "Billing Information" + }, + "CUSTOM_COMMANDS": "Custom Commands", + "DURATION": "Duration", + "COMPANY": "Company", + "COMPLETED": "Completed", + "CHANGE_COMPLETED_FLAG": "Change Completed Flag", + "CHANGE_PRIORITY": "Change Priority", + "CUSTOMER_INFORMATION": "Customer Information", + "DELETE_ROW": "Delete Row", + "DISABLED_COMMAND": "Disabled Command", + "FALSE": "False", + "FEMALE": "Female", + "FINISH": "Finish", + "GENDER": "Gender", + "HELP": "Help", + "HIGH": "High", + "LOW": "Low", + "MEDIUM": "Medium", + "MALE": "Male", + "NAME": "Name", + "NONE": "None", + "PERCENT_COMPLETE": "% Complete", + "PRIORITY": "Priority", + "START": "Start", + "TASK_X": "Task {{x}}", + "TITLE": "Title", + "TRUE": "True" +} diff --git a/examples/webpack-demo-vanilla-bundle/assets/i18n/fr.json b/examples/webpack-demo-vanilla-bundle/assets/i18n/fr.json new file mode 100644 index 000000000..4fe309138 --- /dev/null +++ b/examples/webpack-demo-vanilla-bundle/assets/i18n/fr.json @@ -0,0 +1,81 @@ +{ + "ALL_SELECTED": "Tout sélectionnés", + "CANCEL": "Annuler", + "CLEAR_ALL_FILTERS": "Supprimer tous les filtres", + "CLEAR_ALL_GROUPING": "Supprimer tous les groupes", + "CLEAR_ALL_SORTING": "Supprimer tous les tris", + "CLEAR_FROZEN_COLUMNS": "Libérer les colonnes gelées", + "COLLAPSE_ALL_GROUPS": "Réduire tous les groupes", + "COLUMNS": "Colonnes", + "COMMANDS": "Commandes", + "CONTAINS": "Contient", + "COPY": "Copier", + "ENDS_WITH": "Se termine par", + "EQUALS": "Égale", + "EXPAND_ALL_GROUPS": "Étendre tous les groupes", + "EXPORT_TO_CSV": "Exporter en format CSV", + "EXPORT_TO_EXCEL": "Exporter vers Excel", + "EXPORT_TO_TAB_DELIMITED": "Exporter en format texte (délimité par tabulation)", + "EXPORT_TO_TEXT_FORMAT": "Exporter en format texte", + "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} de {{totalItems}} éléments", + "FORCE_FIT_COLUMNS": "Ajustement forcé des colonnes", + "FREEZE_COLUMNS": "Geler les colonnes", + "GROUP_BY": "Grouper par", + "HIDE_COLUMN": "Cacher la colonne", + "IN_COLLECTION_SEPERATED_BY_COMMA": "Recherche incluant certain éléments d'une collection, doit être séparé par une virgule (a,b)", + "INVALID_FLOAT": "Le nombre doit être valide et avoir un maximum de {{maxDecimal}} décimales.", + "ITEMS": "éléments", + "ITEMS_PER_PAGE": "éléments par page", + "LAST_UPDATE": "Dernière mise à jour", + "NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Recherche excluant certain éléments d'une collection, doit être séparé par une virgule (a,b)", + "OF": "de", + "OK": "Terminé", + "PAGE": "Page", + "PAGE_X_OF_Y": "page {{x}} de {{y}}", + "REFRESH_DATASET": "Rafraîchir les données", + "REMOVE_FILTER": "Supprimer le filtre", + "REMOVE_SORT": "Supprimer le tri", + "SAVE": "Sauvegarder", + "SELECT_ALL": "Sélectionner tout", + "SORT_ASCENDING": "Trier par ordre croissant", + "SORT_DESCENDING": "Trier par ordre décroissant", + "STARTS_WITH": "Commence par", + "SYNCHRONOUS_RESIZE": "Redimension synchrone", + "TOGGLE_FILTER_ROW": "Basculer la ligne des filtres", + "TOGGLE_PRE_HEADER_ROW": "Basculer la ligne de pré-en-tête", + "X_OF_Y_SELECTED": "# de % sélectionnés", + "BILLING": { + "ADDRESS": { + "STREET": "Adresse de facturation", + "ZIP": "Code zip de facturation" + }, + "INFORMATION": "Information de Facturation" + }, + "DURATION": "Durée", + "COMPANY": "Compagnie", + "COMPLETED": "Terminé", + "CHANGE_COMPLETED_FLAG": "Changer l'indicateur terminé", + "CHANGE_PRIORITY": "Changer la priorité", + "CUSTOM_COMMANDS": "Commandes Personnalisées", + "CUSTOMER_INFORMATION": "Information Client", + "DELETE_ROW": "Supprimer la ligne", + "DISABLED_COMMAND": "Commande désactivée", + "FALSE": "Faux", + "FEMALE": "Féminin", + "FINISH": "Fin", + "GENDER": "Sexe", + "HELP": "Aide", + "HIGH": "Haut", + "LOW": "Bas", + "MEDIUM": "Moyen", + "MALE": "Masculin", + "NAME": "Nom", + "NONE": "Aucun", + "PERCENT_COMPLETE": "% Achevée", + "PRIORITY": "Priorité", + "START": "Début", + "TASK_X": "Tâche {{x}}", + "TITLE": "Titre", + "TITLE.NAME": "Nom du Titre", + "TRUE": "Vrai" +} diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example04.html b/examples/webpack-demo-vanilla-bundle/src/examples/example04.html index 6d9229477..8010e55ec 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example04.html +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example04.html @@ -27,7 +27,7 @@

Example 04 - Pinned (frozen) Columns/Rows

@@ -37,4 +37,4 @@

Example 04 - Pinned (frozen) Columns/Rows

-
+ \ No newline at end of file diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example10.html b/examples/webpack-demo-vanilla-bundle/src/examples/example10.html index 43da73c78..6f86fdd10 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example10.html +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example10.html @@ -22,15 +22,29 @@
Set Sorting Dynamically - - - - - +
+ +
+ + + + + + + + + Locale: + + + +
+
diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts index a7dc5f66b..6bdb2e8e0 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example10.ts @@ -15,6 +15,7 @@ import { GraphqlService, GraphqlPaginatedResult, GraphqlServiceApi, } from '@sli import { Slicker } from '@slickgrid-universal/vanilla-bundle'; import * as moment from 'moment-mini'; import { ExampleGridOptions } from './example-grid-options'; +import { TranslateService } from '../translate.service'; const defaultPageSize = 20; const GRAPHQL_QUERY_DATASET_NAME = 'users'; @@ -32,8 +33,19 @@ export class Example10 { graphqlQuery = '...'; processing = false; selectedLanguage: string; + selectedLanguageFile: string; status = ''; statusClass = 'is-success'; + translateService: TranslateService; + + constructor() { + // get the Translate Service from the window object, + // it might be better with proper Dependency Injection but this project doesn't have any at this point + this.translateService = (window).TranslateService; + this.selectedLanguage = this.translateService.getCurrentLanguage(); + this.selectedLanguageFile = `${this.selectedLanguage}.json`; + console.log('TranslateService', this.translateService.translate('ALL_SELECTED')); + } dispose() { if (this.slickgridLwc) { @@ -55,7 +67,7 @@ export class Example10 { initializeGrid() { this.columnDefinitions = [ { - id: 'name', field: 'name', name: 'Name', width: 60, columnGroup: 'Customer Information', + id: 'name', field: 'name', nameKey: 'NAME', width: 60, columnGroupKey: 'CUSTOMER_INFORMATION', type: FieldType.string, sortable: true, filterable: true, @@ -64,14 +76,14 @@ export class Example10 { } }, { - id: 'gender', field: 'gender', name: 'Gender', filterable: true, sortable: true, width: 60, columnGroup: 'Customer Information', + id: 'gender', field: 'gender', nameKey: 'GENDER', filterable: true, sortable: true, width: 60, columnGroupKey: 'CUSTOMER_INFORMATION', filter: { model: Filters.singleSelect, collection: [{ value: '', label: '' }, { value: 'male', label: 'Male', }, { value: 'female', label: 'Female', }] } }, { - id: 'company', field: 'company', name: 'Company', width: 60, columnGroup: 'Customer Information', + id: 'company', field: 'company', nameKey: 'COMPANY', width: 60, columnGroupKey: 'CUSTOMER_INFORMATION', sortable: true, filterable: true, filter: { @@ -83,13 +95,13 @@ export class Example10 { } }, { - id: 'billingAddressStreet', field: 'billing.address.street', name: 'Street', - width: 60, filterable: true, sortable: true, columnGroup: 'Billing Information', + id: 'billingAddressStreet', field: 'billing.address.street', nameKey: 'BILLING.ADDRESS.STREET', + width: 60, filterable: true, sortable: true, columnGroupKey: 'BILLING.INFORMATION', }, { - id: 'billingAddressZip', field: 'billing.address.zip', name: 'Zip', width: 60, + id: 'billingAddressZip', field: 'billing.address.zip', nameKey: 'BILLING.ADDRESS.ZIP', width: 60, type: FieldType.number, - columnGroup: 'Billing Information', + columnGroupKey: 'BILLING.INFORMATION', filterable: true, sortable: true, filter: { model: Filters.compoundInput @@ -98,7 +110,7 @@ export class Example10 { }, { id: 'finish', field: 'finish', name: 'Date', formatter: Formatters.dateIso, sortable: true, minWidth: 90, width: 120, exportWithFormatter: true, - columnGroup: 'Billing Information', + columnGroupKey: 'BILLING.INFORMATION', type: FieldType.date, filterable: true, filter: { @@ -111,6 +123,8 @@ export class Example10 { const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD'); this.gridOptions = { + enableTranslate: true, + i18n: this.translateService, // pass the TranslateService instance to the grid enableAutoResize: false, gridHeight: 275, gridWidth: 900, @@ -261,4 +275,11 @@ export class Example10 { { columnId: 'company', direction: 'ASC' }, ]); } + + async switchLanguage() { + const nextLanguage = (this.selectedLanguage === 'en') ? 'fr' : 'en'; + await this.translateService.use(nextLanguage); + this.selectedLanguage = nextLanguage; + this.selectedLanguageFile = `${this.selectedLanguage}.json`; + } } diff --git a/examples/webpack-demo-vanilla-bundle/src/main.ts b/examples/webpack-demo-vanilla-bundle/src/main.ts index 2d85ffb1b..12c753f43 100644 --- a/examples/webpack-demo-vanilla-bundle/src/main.ts +++ b/examples/webpack-demo-vanilla-bundle/src/main.ts @@ -9,14 +9,28 @@ import 'jquery-ui-dist/jquery-ui'; import { Renderer } from './renderer'; import * as SlickerModule from '@slickgrid-universal/vanilla-bundle'; import { App } from './app'; +import { TranslateService } from 'translate.service'; class Main { app: App; constructor(private renderer: Renderer) { } - loadApp() { + async loadApp() { this.app = this.renderer.loadViewModel(require('./app.ts')); this.renderer.loadView(require('./app.html')); + + const translate = new TranslateService(); + translate.setup({ + loadPath: 'assets/i18n/{{lang}}.json', + lang: 'en' + }); + await translate.use('en'); + + // it might be better to use proper Dependency Injection + // but for now let's use the window object to save keep a reference to our instantiated service + (window).TranslateService = translate; + + // finally attached (render) the app this.app.attached(); } } @@ -31,4 +45,3 @@ main.loadApp(); // Yes, there are other ways but this gets the job done for this demo. (window).main = main; (window).Slicker = SlickerModule && SlickerModule.Slicker || SlickerModule; - diff --git a/examples/webpack-demo-vanilla-bundle/src/translate.service.ts b/examples/webpack-demo-vanilla-bundle/src/translate.service.ts new file mode 100644 index 000000000..eae6a1c4c --- /dev/null +++ b/examples/webpack-demo-vanilla-bundle/src/translate.service.ts @@ -0,0 +1,87 @@ +import { getDescendantProperty, PubSubService, TranslaterService, TranslateServiceEventName } from '@slickgrid-universal/common'; +import * as fetch from 'isomorphic-fetch'; +import * as e6p from 'es6-promise'; + +interface Locales { + [locale: string]: string; +} + +interface TranslateOptions { + loadPath: string; + lang: string; +} + +export class TranslateService implements TranslaterService { + eventName = 'onLanguageChange' as TranslateServiceEventName; + private _currentLanguage = 'en'; + private _locales: { [language: string]: Locales } = {}; + private _pubSubServices: PubSubService[] = []; + private _options; + + constructor() { + (e6p as any).polyfill(); + } + + /** + * Add an optional Pub/Sub Messaging Service, + * when defined the Translate Service will call the publish method with "onLanguageChanged" event name whenever the "use()" method is called + * @param {PubSubService} pubSub + */ + addPubSubMessaging(pubSubService: PubSubService) { + this._pubSubServices.push(pubSubService); + } + + getCurrentLanguage(): string { + return this._currentLanguage; + } + + fetchLocales(loadPath: string, language: string): Promise { + return new Promise(async (resolve, reject) => { + try { + const response = await fetch(loadPath); + if (!response.ok) { + throw new Error(`HTTP Fetch error ${response.status}`); + } + const localeSet = await response.json(); + this._locales[language] = localeSet; + resolve(localeSet); + } catch (error) { + console.log('fetch error', error); + reject(error); + } + }); + } + + async use(newLang: string): Promise { + this._currentLanguage = newLang; + + // if it's already loaded in the cache, then resolve the locale set, else fetch it + if (this._locales?.hasOwnProperty(newLang)) { + this.publishLanguageChangeEvent(newLang); + return Promise.resolve(this._locales[newLang]); + } + + const path = this._options.loadPath.replace(/{{lang}}/gi, newLang); + const localeSet = await this.fetchLocales(path, newLang); + this.publishLanguageChangeEvent(newLang); + + return localeSet; + } + + setup(options: TranslateOptions) { + this._options = options; + } + + translate(translationKey: string): string { + if (this._locales?.hasOwnProperty(this._currentLanguage)) { + return getDescendantProperty(this._locales[this._currentLanguage], translationKey); + } + return translationKey; + } + + private publishLanguageChangeEvent(newLanguage: string) { + for (const pubSub of this._pubSubServices) { + pubSub.publish(this.eventName, { language: newLanguage }); + } + } +} diff --git a/examples/webpack-demo-vanilla-bundle/webpack.config.js b/examples/webpack-demo-vanilla-bundle/webpack.config.js index 1167726d0..71fb72527 100644 --- a/examples/webpack-demo-vanilla-bundle/webpack.config.js +++ b/examples/webpack-demo-vanilla-bundle/webpack.config.js @@ -90,7 +90,7 @@ module.exports = ({ production } = {}, { extractCss, analyze, tests, hmr, port, patterns: [ // { from: 'static', to: outDir, ignore: ['.*'] }, // ignore dot (hidden) files { from: `${srcDir}/favicon.ico`, to: 'favicon.ico' }, - // { from: 'assets', to: 'assets' } + { from: 'assets', to: 'assets' } ] }), ...when(extractCss, new MiniCssExtractPlugin({ // updated to match the naming conventions for the js files diff --git a/package.json b/package.json index 50741497c..78cbfe83c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "bundle": "lerna run bundle --stream", "build": "lerna run build --stream", "build:demo": "lerna run build:demo --stream", - "rebuild": "npm run clean && npm run build", + "rebuild": "run-s clean build", "clean": "rimraf packages/*/dist dist", "cypress": "cypress open --config-file test/cypress.json", "cypress:ci": "cypress run --config-file test/cypress.json --reporter mochawesome", @@ -39,7 +39,7 @@ "@types/node": "^14.0.19", "@typescript-eslint/eslint-plugin": "^3.6.0", "@typescript-eslint/parser": "^3.6.0", - "cypress": "^4.10.0", + "cypress": "^4.11.0", "eslint": "^7.4.0", "eslint-plugin-import": "^2.22.0", "eslint-plugin-prefer-arrow": "^1.2.1", @@ -54,6 +54,7 @@ "lerna": "^3.22.1", "mocha": "^8.0.1", "mochawesome": "^6.1.1", + "npm-run-all": "^4.1.5", "ts-jest": "^26.1.3", "typescript": "^3.9.7" }, @@ -61,4 +62,4 @@ "node": ">=12.13.1", "npm": ">=6.12.1" } -} +} \ No newline at end of file diff --git a/packages/common/src/editors/__tests__/dateEditor.spec.ts b/packages/common/src/editors/__tests__/dateEditor.spec.ts index f8722e5d9..532fcb072 100644 --- a/packages/common/src/editors/__tests__/dateEditor.spec.ts +++ b/packages/common/src/editors/__tests__/dateEditor.spec.ts @@ -397,7 +397,7 @@ describe('DateEditor', () => { it('should display text in new locale', (done) => { gridOptionMock.i18n = translateService; - translateService.setLocale('fr-CA'); // will be trimmed to "fr" + translateService.use('fr-CA'); // will be trimmed to "fr" editor = new DateEditor(editorArguments); const spy = jest.spyOn(editor.flatInstance, 'open'); diff --git a/packages/common/src/editors/__tests__/longTextEditor.spec.ts b/packages/common/src/editors/__tests__/longTextEditor.spec.ts index 4a61393a8..1b33bc614 100644 --- a/packages/common/src/editors/__tests__/longTextEditor.spec.ts +++ b/packages/common/src/editors/__tests__/longTextEditor.spec.ts @@ -44,7 +44,7 @@ describe('LongTextEditor', () => { beforeEach(() => { translateService = new TranslateServiceStub(); - translateService.setLocale('fr'); + translateService.use('fr'); divContainer = document.createElement('div'); divContainer.innerHTML = template; diff --git a/packages/common/src/editors/dateEditor.ts b/packages/common/src/editors/dateEditor.ts index 12bf31b62..3b0c3d846 100644 --- a/packages/common/src/editors/dateEditor.ts +++ b/packages/common/src/editors/dateEditor.ts @@ -97,7 +97,7 @@ export class DateEditor implements Editor { this.defaultDate = (this.args.item) ? this.args.item[this.columnDef.field] : null; const inputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.type || FieldType.dateUtc); const outputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.outputType || this.columnDef.type || FieldType.dateUtc); - let currentLocale = this._translaterService && this._translaterService.getCurrentLocale && this._translaterService.getCurrentLocale() || gridOptions.locale || 'en'; + let currentLocale = this._translaterService && this._translaterService.getCurrentLanguage && this._translaterService.getCurrentLanguage() || gridOptions.locale || 'en'; if (currentLocale.length > 2) { currentLocale = currentLocale.substring(0, 2); } diff --git a/packages/common/src/editors/longTextEditor.ts b/packages/common/src/editors/longTextEditor.ts index 546c79814..d57cfc3c5 100644 --- a/packages/common/src/editors/longTextEditor.ts +++ b/packages/common/src/editors/longTextEditor.ts @@ -80,7 +80,7 @@ export class LongTextEditor implements Editor { init(): void { let cancelText = ''; let saveText = ''; - if (this._translater && this._translater.translate && this._translater.getCurrentLocale && this._translater.getCurrentLocale()) { + if (this._translater && this._translater.translate && this._translater.getCurrentLanguage && this._translater.getCurrentLanguage()) { const translationPrefix = getTranslationPrefix(this.gridOptions); cancelText = this._translater.translate(`${translationPrefix}CANCEL`); saveText = this._translater.translate(`${translationPrefix}SAVE`); diff --git a/packages/common/src/editors/selectEditor.ts b/packages/common/src/editors/selectEditor.ts index 212fe7bf5..ff8eb1747 100644 --- a/packages/common/src/editors/selectEditor.ts +++ b/packages/common/src/editors/selectEditor.ts @@ -114,7 +114,7 @@ export class SelectEditor implements Editor { libOptions.okButton = true; libOptions.selectAllDelimiter = ['', '']; - if (this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLocale && this._translaterService.getCurrentLocale()) { + if (this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLanguage && this._translaterService.getCurrentLanguage()) { const translationPrefix = getTranslationPrefix(this.gridOptions); libOptions.countSelected = this._translaterService.translate(`${translationPrefix}X_OF_Y_SELECTED`); libOptions.allSelected = this._translaterService.translate(`${translationPrefix}ALL_SELECTED`); diff --git a/packages/common/src/extensions/__tests__/cellMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/cellMenuExtension.spec.ts index 29ec3e1d0..fd8d03e8e 100644 --- a/packages/common/src/extensions/__tests__/cellMenuExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/cellMenuExtension.spec.ts @@ -91,7 +91,7 @@ describe('CellMenuExtension', () => { translateService = new TranslateServiceStub(); extensionUtility = new ExtensionUtility(sharedService, translateService); extension = new CellMenuExtension(extensionUtility, sharedService, translateService); - translateService.setLocale('fr'); + translateService.use('fr'); }); afterEach(() => { @@ -285,7 +285,7 @@ describe('CellMenuExtension', () => { }]; jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(mockColumns); - translateService.setLocale('en'); + translateService.use('en'); extension.translateCellMenu(); expect(mockColumns).toEqual([{ @@ -322,7 +322,7 @@ describe('CellMenuExtension', () => { }]; jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(mockColumns); - translateService.setLocale('en'); + translateService.use('en'); extension.translateCellMenu(); expect(mockColumns).toEqual([{ diff --git a/packages/common/src/extensions/__tests__/columnPickerExtension.spec.ts b/packages/common/src/extensions/__tests__/columnPickerExtension.spec.ts index 41dab134b..bf58ba1d2 100644 --- a/packages/common/src/extensions/__tests__/columnPickerExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/columnPickerExtension.spec.ts @@ -47,7 +47,7 @@ describe('columnPickerExtension', () => { translateService = new TranslateServiceStub(); extensionUtility = new ExtensionUtility(sharedService, translateService); extension = new ColumnPickerExtension(extensionUtility, sharedService); - translateService.setLocale('fr'); + translateService.use('fr'); }); it('should return null when either the grid object or the grid options is missing', () => { diff --git a/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts index 124348e97..32d96dfcb 100644 --- a/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts @@ -127,8 +127,8 @@ describe('contextMenuExtension', () => { sharedService = new SharedService(); translateService = new TranslateServiceStub(); extensionUtility = new ExtensionUtility(sharedService, translateService); - extension = new ContextMenuExtension(extensionUtility, sharedService, translateService, treeDataServiceStub); - translateService.setLocale('fr'); + extension = new ContextMenuExtension(extensionUtility, sharedService, treeDataServiceStub, translateService); + translateService.use('fr'); }); afterEach(() => { @@ -1021,7 +1021,7 @@ describe('contextMenuExtension', () => { jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); extension.register(); - translateService.setLocale('en'); + translateService.use('en'); extension.translateContextMenu(); expect(copyGridOptionsMock.contextMenu).toEqual({ @@ -1061,7 +1061,7 @@ describe('contextMenuExtension', () => { jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); extension.register(); - translateService.setLocale('en'); + translateService.use('en'); extension.translateContextMenu(); expect(copyGridOptionsMock.contextMenu).toEqual({ @@ -1086,7 +1086,7 @@ describe('contextMenuExtension', () => { describe('without Translate Service', () => { beforeEach(() => { translateService = null; - extension = new ContextMenuExtension({} as ExtensionUtility, { gridOptions: { enableTranslate: true } } as SharedService, translateService, treeDataServiceStub); + extension = new ContextMenuExtension({} as ExtensionUtility, { gridOptions: { enableTranslate: true } } as SharedService, treeDataServiceStub, translateService, treeDataServiceStub); }); it('should throw an error if "enableTranslate" is set but the I18N Service is null', () => { diff --git a/packages/common/src/extensions/__tests__/extensionUtility.spec.ts b/packages/common/src/extensions/__tests__/extensionUtility.spec.ts index d683b5373..501248100 100644 --- a/packages/common/src/extensions/__tests__/extensionUtility.spec.ts +++ b/packages/common/src/extensions/__tests__/extensionUtility.spec.ts @@ -55,7 +55,7 @@ describe('extensionUtility', () => { sharedService = new SharedService(); translateService = new TranslateServiceStub(); utility = new ExtensionUtility(sharedService, translateService); - await translateService.setLocale('fr'); + await translateService.use('fr'); }); describe('arrayRemoveItemByIndex method', () => { diff --git a/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts index 03da74c4d..62a69e4c4 100644 --- a/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/gridMenuExtension.spec.ts @@ -129,7 +129,7 @@ describe('gridMenuExtension', () => { translateService = new TranslateServiceStub(); extensionUtility = new ExtensionUtility(sharedService, translateService); extension = new GridMenuExtension(extensionUtility, filterServiceStub, sharedService, sortServiceStub, translateService); - translateService.setLocale('fr'); + translateService.use('fr'); }); afterEach(() => { diff --git a/packages/common/src/extensions/__tests__/headerMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/headerMenuExtension.spec.ts index 0bb518c67..f9a96d5ac 100644 --- a/packages/common/src/extensions/__tests__/headerMenuExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/headerMenuExtension.spec.ts @@ -108,7 +108,7 @@ describe('headerMenuExtension', () => { translateService = new TranslateServiceStub(); extensionUtility = new ExtensionUtility(sharedService, translateService); extension = new HeaderMenuExtension(extensionUtility, filterServiceStub, pubSubServiceStub, sharedService, sortServiceStub, translateService); - translateService.setLocale('fr'); + translateService.use('fr'); }); afterEach(() => { @@ -359,7 +359,7 @@ describe('headerMenuExtension', () => { }]; jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(mockColumns); - translateService.setLocale('en'); + translateService.use('en'); extension.translateHeaderMenu(); expect(mockColumns).toEqual([{ diff --git a/packages/common/src/extensions/cellMenuExtension.ts b/packages/common/src/extensions/cellMenuExtension.ts index 62f780403..0b309e259 100644 --- a/packages/common/src/extensions/cellMenuExtension.ts +++ b/packages/common/src/extensions/cellMenuExtension.ts @@ -27,7 +27,7 @@ export class CellMenuExtension implements Extension { constructor( private extensionUtility: ExtensionUtility, private sharedService: SharedService, - private translaterService: TranslaterService, + private translaterService?: TranslaterService, ) { this._eventHandler = new Slick.EventHandler(); } @@ -161,10 +161,10 @@ export class CellMenuExtension implements Extension { // translate their titles only if they have a titleKey defined if (columnDef.cellMenu.commandTitleKey) { - columnDef.cellMenu.commandTitle = this.translaterService && this.translaterService.getCurrentLocale && this.translaterService.translate && this.translaterService.translate(columnDef.cellMenu.commandTitleKey) || this._locales && this._locales.TEXT_COMMANDS || columnDef.cellMenu.commandTitle; + columnDef.cellMenu.commandTitle = this.translaterService && this.translaterService.getCurrentLanguage && this.translaterService.translate && this.translaterService.translate(columnDef.cellMenu.commandTitleKey) || this._locales && this._locales.TEXT_COMMANDS || columnDef.cellMenu.commandTitle; } if (columnDef.cellMenu.optionTitleKey) { - columnDef.cellMenu.optionTitle = this.translaterService && this.translaterService.getCurrentLocale && this.translaterService.translate && this.translaterService.translate(columnDef.cellMenu.optionTitleKey) || columnDef.cellMenu.optionTitle; + columnDef.cellMenu.optionTitle = this.translaterService && this.translaterService.getCurrentLanguage && this.translaterService.translate && this.translaterService.translate(columnDef.cellMenu.optionTitleKey) || columnDef.cellMenu.optionTitle; } // translate both command/option items (whichever is provided) diff --git a/packages/common/src/extensions/contextMenuExtension.ts b/packages/common/src/extensions/contextMenuExtension.ts index da8c45c39..36a9d14fb 100644 --- a/packages/common/src/extensions/contextMenuExtension.ts +++ b/packages/common/src/extensions/contextMenuExtension.ts @@ -28,8 +28,8 @@ export class ContextMenuExtension implements Extension { constructor( private extensionUtility: ExtensionUtility, private sharedService: SharedService, - private translaterService: TranslaterService, private treeDataService: TreeDataService, + private translaterService?: TranslaterService, ) { this._eventHandler = new Slick.EventHandler(); } @@ -143,11 +143,11 @@ export class ContextMenuExtension implements Extension { const menuOptions: Partial = {}; if (contextMenu.commandTitleKey) { - contextMenu.commandTitle = this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(contextMenu.commandTitleKey) || contextMenu.commandTitle; + contextMenu.commandTitle = this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(contextMenu.commandTitleKey) || contextMenu.commandTitle; menuOptions.commandTitle = contextMenu.commandTitle; } if (contextMenu.optionTitleKey) { - contextMenu.optionTitle = this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(contextMenu.optionTitleKey) || contextMenu.optionTitle; + contextMenu.optionTitle = this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(contextMenu.optionTitleKey) || contextMenu.optionTitle; menuOptions.optionTitle = contextMenu.optionTitle; } const originalCommandItems = this._userOriginalContextMenu && Array.isArray(this._userOriginalContextMenu.commandItems) ? this._userOriginalContextMenu.commandItems : []; diff --git a/packages/common/src/extensions/extensionUtility.ts b/packages/common/src/extensions/extensionUtility.ts index 6bac14d4e..49b5c5b70 100644 --- a/packages/common/src/extensions/extensionUtility.ts +++ b/packages/common/src/extensions/extensionUtility.ts @@ -6,7 +6,7 @@ import { TranslaterService } from '../services'; import { getTranslationPrefix } from '../services/utilities'; export class ExtensionUtility { - constructor(private sharedService: SharedService, private translaterService: TranslaterService) { } + constructor(private sharedService: SharedService, private translaterService?: TranslaterService) { } /** * Remove a column from the grid by it's index in the grid @@ -96,21 +96,21 @@ export class ExtensionUtility { const gridOptions = this.sharedService.gridOptions; const translationPrefix = getTranslationPrefix(gridOptions); - if (titleKey && this.translaterService && this.translaterService.translate) { + if (titleKey && this.translaterService?.translate) { output = this.translaterService.translate(titleKey || ' '); } else { switch (propName) { case 'customTitle': - output = title || enableTranslate && this.translaterService?.getCurrentLocale && this.translaterService?.translate(`${translationPrefix}COMMANDS` || ' ') || locales?.TEXT_COMMANDS; + output = title || enableTranslate && this.translaterService?.getCurrentLanguage && this.translaterService?.translate(`${translationPrefix}COMMANDS` || ' ') || locales?.TEXT_COMMANDS; break; case 'columnTitle': - output = title || enableTranslate && this.translaterService?.getCurrentLocale && this.translaterService?.translate(`${translationPrefix}COLUMNS` || ' ') || locales?.TEXT_COLUMNS; + output = title || enableTranslate && this.translaterService?.getCurrentLanguage && this.translaterService?.translate(`${translationPrefix}COLUMNS` || ' ') || locales?.TEXT_COLUMNS; break; case 'forceFitTitle': - output = title || enableTranslate && this.translaterService?.getCurrentLocale && this.translaterService?.translate(`${translationPrefix}FORCE_FIT_COLUMNS` || ' ') || locales?.TEXT_FORCE_FIT_COLUMNS; + output = title || enableTranslate && this.translaterService?.getCurrentLanguage && this.translaterService?.translate(`${translationPrefix}FORCE_FIT_COLUMNS` || ' ') || locales?.TEXT_FORCE_FIT_COLUMNS; break; case 'syncResizeTitle': - output = title || enableTranslate && this.translaterService?.getCurrentLocale && this.translaterService?.translate(`${translationPrefix}SYNCHRONOUS_RESIZE` || ' ') || locales?.TEXT_SYNCHRONOUS_RESIZE; + output = title || enableTranslate && this.translaterService?.getCurrentLanguage && this.translaterService?.translate(`${translationPrefix}SYNCHRONOUS_RESIZE` || ' ') || locales?.TEXT_SYNCHRONOUS_RESIZE; break; default: output = title; @@ -142,7 +142,7 @@ export class ExtensionUtility { if (Array.isArray(items)) { for (const item of items) { if (item[inputKey]) { - item[outputKey] = this.translaterService && this.translaterService.getCurrentLocale && this.translaterService.translate && this.translaterService.translate(item[inputKey]); + item[outputKey] = this.translaterService && this.translaterService.getCurrentLanguage && this.translaterService.translate && this.translaterService.translate(item[inputKey]); } } } @@ -160,7 +160,7 @@ export class ExtensionUtility { // get locales provided by user in main file or else use default English locales via the Constants const locales = gridOptions && gridOptions.locales || Constants.locales; - if (gridOptions.enableTranslate && this.translaterService && this.translaterService.getCurrentLocale && this.translaterService.translate) { + if (gridOptions.enableTranslate && this.translaterService && this.translaterService.getCurrentLanguage && this.translaterService.translate) { text = this.translaterService.translate(translationKey || ' '); } else if (locales && locales.hasOwnProperty(localeKey)) { text = locales[localeKey]; diff --git a/packages/common/src/extensions/gridMenuExtension.ts b/packages/common/src/extensions/gridMenuExtension.ts index 47771e8bd..a7c4ac338 100644 --- a/packages/common/src/extensions/gridMenuExtension.ts +++ b/packages/common/src/extensions/gridMenuExtension.ts @@ -41,7 +41,7 @@ export class GridMenuExtension implements Extension { private filterService: FilterService, private sharedService: SharedService, private sortService: SortService, - private translaterService: TranslaterService, + private translaterService?: TranslaterService, ) { this._eventHandler = new Slick.EventHandler(); } @@ -230,7 +230,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconClearFrozenColumnsCommand || 'fa fa-times', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}CLEAR_FROZEN_COLUMNS`) : this._locales && this._locales.TEXT_CLEAR_FROZEN_COLUMNS, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}CLEAR_FROZEN_COLUMNS`, 'TEXT_CLEAR_FROZEN_COLUMNS'), disabled: false, command: commandName, positionOrder: 49 @@ -247,7 +247,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconClearAllFiltersCommand || 'fa fa-filter text-danger', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}CLEAR_ALL_FILTERS`) : this._locales && this._locales.TEXT_CLEAR_ALL_FILTERS, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}CLEAR_ALL_FILTERS`, 'TEXT_CLEAR_ALL_FILTERS'), disabled: false, command: commandName, positionOrder: 50 @@ -263,7 +263,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconToggleFilterCommand || 'fa fa-random', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}TOGGLE_FILTER_ROW`) : this._locales && this._locales.TEXT_TOGGLE_FILTER_ROW, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}TOGGLE_FILTER_ROW`, 'TEXT_TOGGLE_FILTER_ROW'), disabled: false, command: commandName, positionOrder: 52 @@ -279,7 +279,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconRefreshDatasetCommand || 'fa fa-refresh', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}REFRESH_DATASET`) : this._locales && this._locales.TEXT_REFRESH_DATASET, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}REFRESH_DATASET`, 'TEXT_REFRESH_DATASET'), disabled: false, command: commandName, positionOrder: 56 @@ -297,7 +297,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconTogglePreHeaderCommand || 'fa fa-random', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}TOGGLE_PRE_HEADER_ROW`) : this._locales && this._locales.TEXT_TOGGLE_PRE_HEADER_ROW, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}TOGGLE_PRE_HEADER_ROW`, 'TEXT_TOGGLE_PRE_HEADER_ROW'), disabled: false, command: commandName, positionOrder: 52 @@ -315,7 +315,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconClearAllSortingCommand || 'fa fa-unsorted text-danger', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}CLEAR_ALL_SORTING`) : this._locales && this._locales.TEXT_CLEAR_ALL_SORTING, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}CLEAR_ALL_SORTING`, 'TEXT_CLEAR_ALL_SORTING'), disabled: false, command: commandName, positionOrder: 51 @@ -332,7 +332,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconExportCsvCommand || 'fa fa-download', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}EXPORT_TO_CSV`) : this._locales && this._locales.TEXT_EXPORT_TO_CSV, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_CSV`, 'TEXT_EXPORT_TO_CSV'), disabled: false, command: commandName, positionOrder: 53 @@ -348,7 +348,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconExportExcelCommand || 'fa fa-file-excel-o text-success', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}EXPORT_TO_EXCEL`) : this._locales && this._locales.TEXT_EXPORT_TO_EXCEL, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_EXCEL`, 'TEXT_EXPORT_TO_EXCEL'), disabled: false, command: commandName, positionOrder: 54 @@ -364,7 +364,7 @@ export class GridMenuExtension implements Extension { gridMenuCustomItems.push( { iconCssClass: this.sharedService.gridOptions.gridMenu.iconExportTextDelimitedCommand || 'fa fa-download', - title: this.sharedService.gridOptions.enableTranslate ? this.translaterService.translate(`${translationPrefix}EXPORT_TO_TAB_DELIMITED`) : this._locales && this._locales.TEXT_EXPORT_TO_TAB_DELIMITED, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_TAB_DELIMITED`, 'TEXT_EXPORT_TO_TAB_DELIMITED'), disabled: false, command: commandName, positionOrder: 55 diff --git a/packages/common/src/extensions/headerMenuExtension.ts b/packages/common/src/extensions/headerMenuExtension.ts index 507fd57c0..1a37dfa79 100644 --- a/packages/common/src/extensions/headerMenuExtension.ts +++ b/packages/common/src/extensions/headerMenuExtension.ts @@ -39,7 +39,7 @@ export class HeaderMenuExtension implements Extension { private pubSubService: PubSubService, private sharedService: SharedService, private sortService: SortService, - private translaterService: TranslaterService, + private translaterService?: TranslaterService, ) { this._eventHandler = new Slick.EventHandler(); } @@ -155,7 +155,7 @@ export class HeaderMenuExtension implements Extension { if (columnHeaderMenuItems.filter((item: MenuCommandItem) => item.hasOwnProperty('command') && item.command === 'freeze-columns').length === 0) { columnHeaderMenuItems.push({ iconCssClass: headerMenuOptions.iconFreezeColumns || 'fa fa-thumb-tack', - title: options.enableTranslate ? this.translaterService.translate(`${translationPrefix}FREEZE_COLUMNS`) : this._locales && this._locales.TEXT_FREEZE_COLUMNS, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}FREEZE_COLUMNS`, 'TEXT_FREEZE_COLUMNS'), command: 'freeze-columns', positionOrder: 48 }); @@ -172,7 +172,7 @@ export class HeaderMenuExtension implements Extension { if (columnHeaderMenuItems.filter((item: MenuCommandItem) => item.hasOwnProperty('command') && item.command === 'sort-asc').length === 0) { columnHeaderMenuItems.push({ iconCssClass: headerMenuOptions.iconSortAscCommand || 'fa fa-sort-asc', - title: options.enableTranslate ? this.translaterService.translate(`${translationPrefix}SORT_ASCENDING`) : this._locales && this._locales.TEXT_SORT_ASCENDING, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}SORT_ASCENDING`, 'TEXT_SORT_ASCENDING'), command: 'sort-asc', positionOrder: 50 }); @@ -180,7 +180,7 @@ export class HeaderMenuExtension implements Extension { if (columnHeaderMenuItems.filter((item: MenuCommandItem) => item.hasOwnProperty('command') && item.command === 'sort-desc').length === 0) { columnHeaderMenuItems.push({ iconCssClass: headerMenuOptions.iconSortDescCommand || 'fa fa-sort-desc', - title: options.enableTranslate ? this.translaterService.translate(`${translationPrefix}SORT_DESCENDING`) : this._locales && this._locales.TEXT_SORT_DESCENDING, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}SORT_DESCENDING`, 'TEXT_SORT_DESCENDING'), command: 'sort-desc', positionOrder: 51 }); @@ -194,7 +194,7 @@ export class HeaderMenuExtension implements Extension { if (!headerMenuOptions.hideClearSortCommand && columnHeaderMenuItems.filter((item: MenuCommandItem) => item.hasOwnProperty('command') && item.command === 'clear-sort').length === 0) { columnHeaderMenuItems.push({ iconCssClass: headerMenuOptions.iconClearSortCommand || 'fa fa-unsorted', - title: options.enableTranslate ? this.translaterService.translate(`${translationPrefix}REMOVE_SORT`) : this._locales && this._locales.TEXT_REMOVE_SORT, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}REMOVE_SORT`, 'TEXT_REMOVE_SORT'), command: 'clear-sort', positionOrder: 54 }); @@ -206,7 +206,7 @@ export class HeaderMenuExtension implements Extension { if (!headerMenuOptions.hideClearFilterCommand && columnHeaderMenuItems.filter((item: MenuCommandItem) => item.hasOwnProperty('command') && item.command === 'clear-filter').length === 0) { columnHeaderMenuItems.push({ iconCssClass: headerMenuOptions.iconClearFilterCommand || 'fa fa-filter', - title: options.enableTranslate ? this.translaterService.translate(`${translationPrefix}REMOVE_FILTER`) : this._locales && this._locales.TEXT_REMOVE_FILTER, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}REMOVE_FILTER`, 'TEXT_REMOVE_FILTER'), command: 'clear-filter', positionOrder: 53 }); @@ -217,7 +217,7 @@ export class HeaderMenuExtension implements Extension { if (headerMenuOptions && !headerMenuOptions.hideColumnHideCommand && columnHeaderMenuItems.filter((item: MenuCommandItem) => item.hasOwnProperty('command') && item.command === 'hide').length === 0) { columnHeaderMenuItems.push({ iconCssClass: headerMenuOptions.iconColumnHideCommand || 'fa fa-times', - title: options.enableTranslate ? this.translaterService.translate(`${translationPrefix}HIDE_COLUMN`) : this._locales && this._locales.TEXT_HIDE_COLUMN, + title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}HIDE_COLUMN`, 'TEXT_HIDE_COLUMN'), command: 'hide', positionOrder: 55 }); @@ -281,22 +281,22 @@ export class HeaderMenuExtension implements Extension { if (item.hasOwnProperty('command')) { switch (item.command) { case 'clear-filter': - item.title = this.translaterService.translate(`${translationPrefix}REMOVE_FILTER`) || this._locales && this._locales.TEXT_REMOVE_FILTER; + item.title = this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}REMOVE_FILTER`, 'TEXT_REMOVE_FILTER'); break; case 'clear-sort': - item.title = this.translaterService.translate(`${translationPrefix}REMOVE_SORT`) || this._locales && this._locales.TEXT_REMOVE_SORT; + item.title = this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}REMOVE_SORT`, 'TEXT_REMOVE_SORT'); break; case 'freeze-columns': - item.title = this.translaterService.translate(`${translationPrefix}FREEZE_COLUMNS`) || this._locales && this._locales.TEXT_FREEZE_COLUMNS; + item.title = this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}FREEZE_COLUMNS`, 'TEXT_FREEZE_COLUMNS'); break; case 'sort-asc': - item.title = this.translaterService.translate(`${translationPrefix}SORT_ASCENDING`) || this._locales && this._locales.TEXT_SORT_ASCENDING; + item.title = this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}SORT_ASCENDING`, 'TEXT_SORT_ASCENDING'); break; case 'sort-desc': - item.title = this.translaterService.translate(`${translationPrefix}SORT_DESCENDING`) || this._locales && this._locales.TEXT_SORT_DESCENDING; + item.title = this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}SORT_DESCENDING`, 'TEXT_SORT_DESCENDING'); break; case 'hide': - item.title = this.translaterService.translate(`${translationPrefix}HIDE_COLUMN`) || this._locales && this._locales.TEXT_HIDE_COLUMN; + item.title = this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}HIDE_COLUMN`, 'TEXT_HIDE_COLUMN'); break; } } diff --git a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts index 76761095a..4af2d684f 100644 --- a/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts +++ b/packages/common/src/filters/__tests__/compoundDateFilter.spec.ts @@ -204,7 +204,7 @@ describe('CompoundDateFilter', () => { }); it('should work with different locale when locale is changed', () => { - translateService.setLocale('fr-CA'); // will be trimmed to "fr" + translateService.use('fr-CA'); // will be trimmed to "fr" filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z']; mockColumn.filter.operator = '<='; const spyCallback = jest.spyOn(filterArguments, 'callback'); @@ -230,7 +230,7 @@ describe('CompoundDateFilter', () => { }); it('should throw an error and use English locale when user tries to load an unsupported Flatpickr locale', () => { - translateService.setLocale('zx'); + translateService.use('zx'); const consoleSpy = jest.spyOn(global.console, 'warn').mockReturnValue(); filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z']; diff --git a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts index 09e20b6f6..7ba936855 100644 --- a/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts +++ b/packages/common/src/filters/__tests__/dateRangeFilter.spec.ts @@ -194,7 +194,7 @@ describe('DateRangeFilter', () => { }); it('should work with different locale when locale is changed', () => { - translateService.setLocale('fr-CA'); // will be trimmed to "fr" + translateService.use('fr-CA'); // will be trimmed to "fr" filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z', '2000-01-31T05:00:00.000Z']; mockColumn.filter.operator = 'RangeInclusive'; const spyCallback = jest.spyOn(filterArguments, 'callback'); @@ -219,7 +219,7 @@ describe('DateRangeFilter', () => { }); it('should throw an error and use English locale when user tries to load an unsupported Flatpickr locale', () => { - translateService.setLocale('zx'); + translateService.use('zx'); const consoleSpy = jest.spyOn(global.console, 'warn').mockReturnValue(); filterArguments.searchTerms = ['2000-01-01T05:00:00.000Z', '2000-01-31T05:00:00.000Z']; diff --git a/packages/common/src/filters/__tests__/nativeSelectFilter.spec.ts b/packages/common/src/filters/__tests__/nativeSelectFilter.spec.ts index a3a36f8d9..a46074324 100644 --- a/packages/common/src/filters/__tests__/nativeSelectFilter.spec.ts +++ b/packages/common/src/filters/__tests__/nativeSelectFilter.spec.ts @@ -361,7 +361,7 @@ describe('NativeSelectFilter', () => { }); it('should work with English locale when locale is changed', () => { - translateService.setLocale('en'); + translateService.use('en'); gridOptionMock.enableTranslate = true; mockColumn.filter = { enableTranslateLabel: true, @@ -386,7 +386,7 @@ describe('NativeSelectFilter', () => { }); it('should work with French locale when locale is changed', () => { - translateService.setLocale('fr'); + translateService.use('fr'); gridOptionMock.enableTranslate = true; mockColumn.filter = { enableTranslateLabel: true, diff --git a/packages/common/src/filters/__tests__/selectFilter.spec.ts b/packages/common/src/filters/__tests__/selectFilter.spec.ts index 6a19422bc..edd76a908 100644 --- a/packages/common/src/filters/__tests__/selectFilter.spec.ts +++ b/packages/common/src/filters/__tests__/selectFilter.spec.ts @@ -518,7 +518,7 @@ describe('SelectFilter', () => { }); it('should work with English locale when locale is changed', () => { - translateService.setLocale('en'); + translateService.use('en'); gridOptionMock.enableTranslate = true; mockColumn.filter = { enableTranslateLabel: true, @@ -551,7 +551,7 @@ describe('SelectFilter', () => { }); it('should work with French locale when locale is changed', () => { - translateService.setLocale('fr'); + translateService.use('fr'); gridOptionMock.enableTranslate = true; mockColumn.filter = { enableTranslateLabel: true, diff --git a/packages/common/src/filters/__tests__/singleSelectFilter.spec.ts b/packages/common/src/filters/__tests__/singleSelectFilter.spec.ts index 59c8fdd48..d124fa4c0 100644 --- a/packages/common/src/filters/__tests__/singleSelectFilter.spec.ts +++ b/packages/common/src/filters/__tests__/singleSelectFilter.spec.ts @@ -107,7 +107,7 @@ describe('SelectFilter', () => { }); it('should work with English locale when locale is changed', () => { - translateService.setLocale('en'); + translateService.use('en'); gridOptionMock.enableTranslate = true; mockColumn.filter = { enableTranslateLabel: true, @@ -138,7 +138,7 @@ describe('SelectFilter', () => { }); it('should work with French locale when locale is changed', () => { - translateService.setLocale('fr'); + translateService.use('fr'); gridOptionMock.enableTranslate = true; mockColumn.filter = { enableTranslateLabel: true, diff --git a/packages/common/src/filters/compoundDateFilter.ts b/packages/common/src/filters/compoundDateFilter.ts index 0e03d2048..c4b7cb929 100644 --- a/packages/common/src/filters/compoundDateFilter.ts +++ b/packages/common/src/filters/compoundDateFilter.ts @@ -169,7 +169,7 @@ export class CompoundDateFilter implements Filter { const userFilterOptions = (this.columnFilter && this.columnFilter.filterOptions || {}) as FlatpickrOption; // get current locale, if user defined a custom locale just use or get it the Translate Service if it exist else just use English - let currentLocale = (userFilterOptions && userFilterOptions.locale) || (this.translaterService && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale()) || this.gridOptions.locale || 'en'; + let currentLocale = (userFilterOptions && userFilterOptions.locale) || (this.translaterService && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage()) || this.gridOptions.locale || 'en'; if (currentLocale && currentLocale.length > 2) { currentLocale = currentLocale.substring(0, 2); } diff --git a/packages/common/src/filters/dateRangeFilter.ts b/packages/common/src/filters/dateRangeFilter.ts index 6ad3255bf..5d1ad5236 100644 --- a/packages/common/src/filters/dateRangeFilter.ts +++ b/packages/common/src/filters/dateRangeFilter.ts @@ -168,7 +168,7 @@ export class DateRangeFilter implements Filter { const userFilterOptions = (this.columnFilter && this.columnFilter.filterOptions || {}) as FlatpickrOption; // get current locale, if user defined a custom locale just use or get it the Translate Service if it exist else just use English - let currentLocale = (userFilterOptions && userFilterOptions.locale) || (this.translaterService && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale()) || this.gridOptions.locale || 'en'; + let currentLocale = (userFilterOptions && userFilterOptions.locale) || (this.translaterService && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage()) || this.gridOptions.locale || 'en'; if (currentLocale.length > 2) { currentLocale = currentLocale.substring(0, 2); } diff --git a/packages/common/src/filters/filterFactory.ts b/packages/common/src/filters/filterFactory.ts index 4e5f7e03e..71f6de387 100644 --- a/packages/common/src/filters/filterFactory.ts +++ b/packages/common/src/filters/filterFactory.ts @@ -9,7 +9,7 @@ export class FilterFactory { */ private _options: any; - constructor(private config: SlickgridConfig, private collectionService: CollectionService, private translaterService: TranslaterService) { + constructor(private config: SlickgridConfig, private collectionService: CollectionService, private translaterService?: TranslaterService) { this._options = this.config.options; } diff --git a/packages/common/src/filters/nativeSelectFilter.ts b/packages/common/src/filters/nativeSelectFilter.ts index 73a05914d..5310a5e2d 100644 --- a/packages/common/src/filters/nativeSelectFilter.ts +++ b/packages/common/src/filters/nativeSelectFilter.ts @@ -173,7 +173,7 @@ export class NativeSelectFilter implements Filter { throw new Error(`A collection with value/label (or value/labelKey when using Locale) is required to populate the Native Select Filter list, for example:: { filter: model: Filters.select, collection: [ { value: '1', label: 'One' } ]')`); } const labelKey = option.labelKey || option[labelName]; - const textLabel = ((option.labelKey || isEnabledTranslate) && this.translater && this.translater.translate && this.translater.getCurrentLocale && this.translater.getCurrentLocale()) ? this.translater.translate(labelKey || ' ') : labelKey; + const textLabel = ((option.labelKey || isEnabledTranslate) && this.translater && this.translater.translate && this.translater.getCurrentLanguage && this.translater.getCurrentLanguage()) ? this.translater.translate(labelKey || ' ') : labelKey; options += ``; }); } diff --git a/packages/common/src/filters/selectFilter.ts b/packages/common/src/filters/selectFilter.ts index 1f858e70c..ba1a338bb 100644 --- a/packages/common/src/filters/selectFilter.ts +++ b/packages/common/src/filters/selectFilter.ts @@ -308,16 +308,16 @@ export class SelectFilter implements Filter { } const labelKey = (option.labelKey || option[this.labelName]) as string; const selected = (searchTerms.findIndex((term) => term === option[this.valueName]) >= 0) ? 'selected' : ''; - const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey && isTranslateEnabled) ? (this.translaterService?.getCurrentLocale && this.translaterService?.getCurrentLocale() && this.translaterService.translate(labelKey) || '') : labelKey; + const labelText = ((option.labelKey || this.enableTranslateLabel) && labelKey && isTranslateEnabled) ? (this.translaterService?.getCurrentLanguage && this.translaterService?.getCurrentLanguage() && this.translaterService.translate(labelKey) || '') : labelKey; let prefixText = option[this.labelPrefixName] || ''; let suffixText = option[this.labelSuffixName] || ''; let optionLabel = option.hasOwnProperty(this.optionLabel) ? option[this.optionLabel] : ''; optionLabel = optionLabel.toString().replace(/\"/g, '\''); // replace double quotes by single quotes to avoid interfering with regular html // also translate prefix/suffix if enableTranslateLabel is true and text is a string - prefixText = (this.enableTranslateLabel && isTranslateEnabled && prefixText && typeof prefixText === 'string') ? this.translaterService?.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(prefixText || ' ') : prefixText; - suffixText = (this.enableTranslateLabel && isTranslateEnabled && suffixText && typeof suffixText === 'string') ? this.translaterService?.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(suffixText || ' ') : suffixText; - optionLabel = (this.enableTranslateLabel && isTranslateEnabled && optionLabel && typeof optionLabel === 'string') ? this.translaterService?.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(optionLabel || ' ') : optionLabel; + prefixText = (this.enableTranslateLabel && isTranslateEnabled && prefixText && typeof prefixText === 'string') ? this.translaterService?.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(prefixText || ' ') : prefixText; + suffixText = (this.enableTranslateLabel && isTranslateEnabled && suffixText && typeof suffixText === 'string') ? this.translaterService?.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(suffixText || ' ') : suffixText; + optionLabel = (this.enableTranslateLabel && isTranslateEnabled && optionLabel && typeof optionLabel === 'string') ? this.translaterService?.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(optionLabel || ' ') : optionLabel; // add to a temp array for joining purpose and filter out empty text const tmpOptionArray = [prefixText, (typeof labelText === 'string' || typeof labelText === 'number') ? labelText.toString() : labelText, suffixText].filter((text) => text); let optionText = tmpOptionArray.join(separatorBetweenLabels); diff --git a/packages/common/src/formatters/__tests__/translateBooleanFormatter.spec.ts b/packages/common/src/formatters/__tests__/translateBooleanFormatter.spec.ts index 8a6c0e650..ab6e0d6b6 100644 --- a/packages/common/src/formatters/__tests__/translateBooleanFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/translateBooleanFormatter.spec.ts @@ -15,48 +15,48 @@ describe('the Translate Boolean Formatter', () => { }); it('should return an empty string when null value is passed', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateBooleanFormatter(1, 1, null, {} as Column, {}, gridStub); expect(output).toBe(''); }); it('should return an empty string when empty string value is passed', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateBooleanFormatter(1, 1, '', {} as Column, {}, gridStub); expect(output).toBe(''); }); it('should return the translated value when value passed is boolean', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateBooleanFormatter(1, 1, 'TRUE', {} as Column, {}, gridStub); expect(output).toBe('Vrai'); }); it('should return the translated value when value passed is a string', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateBooleanFormatter(1, 1, 'TRUE', {} as Column, {}, gridStub); expect(output).toBe('Vrai'); }); it('should return the translated value when value passed is a string and i18n service is passed as a ColumnDef Params', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({}); const output = translateBooleanFormatter(1, 1, 'TRUE', { params: { i18n: translateService } } as Column, {}, gridStub); expect(output).toBe('Vrai'); }); it('should return the translated value when value passed is a string and i18n service is passed as a ColumnDef Params without any Grid object', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); const output = translateBooleanFormatter(1, 1, 'TRUE', { params: { i18n: translateService } } as Column, {}); expect(output).toBe('Vrai'); }); it('should convert any type of value to string', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateBooleanFormatter(1, 1, 99, {} as Column, {}, gridStub); expect(output).toBe('99'); diff --git a/packages/common/src/formatters/__tests__/translateFormatter.spec.ts b/packages/common/src/formatters/__tests__/translateFormatter.spec.ts index 2d85727a9..113764117 100644 --- a/packages/common/src/formatters/__tests__/translateFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/translateFormatter.spec.ts @@ -16,43 +16,43 @@ describe('the Translate Formatter', () => { }); it('should return an empty string when null value is passed', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateFormatter(1, 1, null, {} as Column, {}, gridStub); - expect(translateService.getCurrentLocale()).toBe('fr'); + expect(translateService.getCurrentLanguage()).toBe('fr'); expect(output).toBe(''); }); it('should return an empty string when no value is passed', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateFormatter(1, 1, '', {} as Column, {}, gridStub); - expect(translateService.getCurrentLocale()).toBe('fr'); + expect(translateService.getCurrentLanguage()).toBe('fr'); expect(output).toBe(''); }); it('should return the translated value as string', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateFormatter(1, 1, 'HELLO', {} as Column, {}, gridStub); expect(output).toBe('Bonjour'); }); it('should return the translated value when value passed is a string and i18n service is passed as a ColumnDef Params', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({}); const output = translateFormatter(1, 1, 'HELLO', { params: { i18n: translateService } } as Column, {}, gridStub); expect(output).toBe('Bonjour'); }); it('should return the translated value when value passed is a string and i18n service is passed as a ColumnDef Params without any Grid object', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); const output = translateFormatter(1, 1, 'HELLO', { params: { i18n: translateService } } as Column, {}); expect(output).toBe('Bonjour'); }); it('should convert any type of value to string', async () => { - await translateService.setLocale('fr'); + await translateService.use('fr'); (gridStub.getOptions as jest.Mock).mockReturnValueOnce({ i18n: translateService }); const output = translateFormatter(1, 1, 99, {} as Column, {}, gridStub); expect(output).toBe('99'); diff --git a/packages/common/src/services/__tests__/collection.service.spec.ts b/packages/common/src/services/__tests__/collection.service.spec.ts index e6027ad66..7ff052033 100644 --- a/packages/common/src/services/__tests__/collection.service.spec.ts +++ b/packages/common/src/services/__tests__/collection.service.spec.ts @@ -186,7 +186,7 @@ describe('CollectionService', () => { }); it('should return a collection sorted by multiple sortBy entities and their translated value', () => { - translateService.setLocale('fr'); + translateService.use('fr'); const columnDef = { id: 'users', field: 'users', dataKey: 'lastName' } as Column; const sortBy = [ { property: 'firstName', sortDesc: false, fieldType: FieldType.string }, @@ -209,7 +209,7 @@ describe('CollectionService', () => { }); it('should return a collection sorted by a single sortBy entity and their translated value', () => { - translateService.setLocale('en'); + translateService.use('en'); const columnDef = { id: 'users', field: 'users' } as Column; const sortBy = { property: 'position', sortDesc: false } as CollectionSortBy; // fieldType is string by default @@ -287,7 +287,7 @@ describe('CollectionService', () => { describe('sortCollection method', () => { it('should return a collection of numbers sorted', () => { - translateService.setLocale('en'); + translateService.use('en'); const columnDef = { id: 'count', field: 'count', fieldType: FieldType.number } as Column; const result1 = service.sortCollection(columnDef, [0, -11, 3, 99999, -200], { sortDesc: false } as CollectionSortBy); @@ -298,7 +298,7 @@ describe('CollectionService', () => { }); it('should return a collection of translation values sorted', () => { - translateService.setLocale('en'); + translateService.use('en'); const roleCollection = ['SALES_REP', 'DEVELOPER', 'SALES_REP', null, 'HUMAN_RESOURCES', 'FINANCE_MANAGER', 'UNKNOWN']; const columnDef = { id: 'count', field: 'count', fieldType: FieldType.string } as Column; diff --git a/packages/common/src/services/__tests__/extension.service.spec.ts b/packages/common/src/services/__tests__/extension.service.spec.ts index c29451e2d..f6362f141 100644 --- a/packages/common/src/services/__tests__/extension.service.spec.ts +++ b/packages/common/src/services/__tests__/extension.service.spec.ts @@ -72,7 +72,7 @@ describe('ExtensionService', () => { beforeEach(() => { sharedService = new SharedService(); translateService = new TranslateServiceStub(); - translateService.setLocale('fr'); + translateService.use('fr'); service = new ExtensionService( // extensions diff --git a/packages/common/src/services/__tests__/groupingAndColspan.service.spec.ts b/packages/common/src/services/__tests__/groupingAndColspan.service.spec.ts index 52533b6f5..f9037eea7 100644 --- a/packages/common/src/services/__tests__/groupingAndColspan.service.spec.ts +++ b/packages/common/src/services/__tests__/groupingAndColspan.service.spec.ts @@ -10,8 +10,9 @@ const gridUid = 'slickgrid_124343'; const containerId = 'demo-container'; const gridOptionMock = { - enablePagination: true, createPreHeaderPanel: true, + enablePagination: true, + enableTranslate: false, } as GridOption; const dataViewStub = { @@ -91,6 +92,7 @@ describe('GroupingAndColspanService', () => { afterEach(() => { jest.clearAllMocks(); service.dispose(); + gridOptionMock.enableTranslate = false; gridStub.getOptions = () => gridOptionMock; }); @@ -209,7 +211,7 @@ describe('GroupingAndColspanService', () => { const slickEvent2 = new Slick.Event(); const instanceMock = { onColumnsChanged: slickEvent1, onMenuClose: slickEvent2 }; const columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }] as Column[]; - const extensionMock = { name: ExtensionName.columnPicker, addon: instanceMock, instance: instanceMock as SlickColumnPicker, class: null }; + const extensionMock = { name: ExtensionName.columnPicker, addon: instanceMock, instance: instanceMock as unknown as SlickColumnPicker, class: null }; jest.spyOn(extensionServiceStub, 'getExtensionByName').mockReturnValue(extensionMock); service.init(gridStub); @@ -270,7 +272,7 @@ describe('GroupingAndColspanService', () => { expect(getColSpy).toHaveBeenCalled(); expect(setColSpy).toHaveBeenCalled(); expect(translateSpy).toHaveBeenCalled(); - expect(renderSpy).toHaveBeenCalled(); + expect(renderSpy).toHaveBeenCalledTimes(2); // 1x by the init, 1x by translateGroupingAndColSpan }); it('should render the pre-header row grouping title DOM element', () => { diff --git a/packages/common/src/services/__tests__/translater.service.spec.ts b/packages/common/src/services/__tests__/translater.service.spec.ts index d642bbf5e..204690a1f 100644 --- a/packages/common/src/services/__tests__/translater.service.spec.ts +++ b/packages/common/src/services/__tests__/translater.service.spec.ts @@ -1,12 +1,17 @@ +import { PubSubService } from '../pubSub.service'; import { TranslaterService } from '../translater.service'; describe('Export Service', () => { - it('should display a not implemented when calling "getCurrentLocale" method', () => { - expect(() => TranslaterService.prototype.getCurrentLocale()).toThrow('TranslaterService "getCurrentLocale" method must be implemented'); + it('should display a not implemented when calling "addPubSubMessaging" method', () => { + expect(() => TranslaterService.prototype.addPubSubMessaging({} as unknown as PubSubService)).toThrow('TranslaterService "addPubSubMessaging" method must be implemented'); }); - it('should display a not implemented when calling "setLocale" method', () => { - expect(() => TranslaterService.prototype.setLocale('fr')).toThrow('TranslaterService "setLocale" method must be implemented'); + it('should display a not implemented when calling "getCurrentLanguage" method', () => { + expect(() => TranslaterService.prototype.getCurrentLanguage()).toThrow('TranslaterService "getCurrentLanguage" method must be implemented'); + }); + + it('should display a not implemented when calling "use" method', () => { + expect(() => TranslaterService.prototype.use('fr')).toThrow('TranslaterService "use" method must be implemented'); }); it('should display a not implemented when calling "translate" method', () => { diff --git a/packages/common/src/services/collection.service.ts b/packages/common/src/services/collection.service.ts index ab33e9b91..fdd9811a2 100644 --- a/packages/common/src/services/collection.service.ts +++ b/packages/common/src/services/collection.service.ts @@ -11,7 +11,7 @@ import { uniqueArray } from './utilities'; import { TranslaterService } from './translater.service'; export class CollectionService { - constructor(private translaterService: TranslaterService) { } + constructor(private translaterService?: TranslaterService) { } /** * Filter 1 or more items from a collection @@ -115,8 +115,8 @@ export class CollectionService { const sortDirection = sortBy.sortDesc ? SortDirectionNumber.desc : SortDirectionNumber.asc; const objectProperty = sortBy.property; const fieldType = sortBy.fieldType || FieldType.string; - const value1 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(dataRow1[objectProperty] || ' ') : dataRow1[objectProperty]; - const value2 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(dataRow2[objectProperty] || ' ') : dataRow2[objectProperty]; + const value1 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(dataRow1[objectProperty] || ' ') : dataRow1[objectProperty]; + const value2 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(dataRow2[objectProperty] || ' ') : dataRow2[objectProperty]; const sortResult = sortByFieldType(fieldType, value1, value2, sortDirection, columnDef); if (sortResult !== SortDirectionNumber.neutral) { @@ -134,8 +134,8 @@ export class CollectionService { const fieldType = sortByOptions.fieldType || FieldType.string; sortedCollection = collection.sort((dataRow1: T, dataRow2: T) => { - const value1 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(dataRow1[objectProperty] || ' ') : dataRow1[objectProperty]; - const value2 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(dataRow2[objectProperty] || ' ') : dataRow2[objectProperty]; + const value1 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(dataRow1[objectProperty] || ' ') : dataRow1[objectProperty]; + const value2 = (enableTranslateLabel) ? this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(dataRow2[objectProperty] || ' ') : dataRow2[objectProperty]; const sortResult = sortByFieldType(fieldType, value1, value2, sortDirection, columnDef); if (sortResult !== SortDirectionNumber.neutral) { return sortResult; @@ -147,8 +147,8 @@ export class CollectionService { const fieldType = sortByOptions.fieldType || FieldType.string; sortedCollection = collection.sort((dataRow1: any, dataRow2: any) => { - const value1 = (enableTranslateLabel) ? this.translaterService?.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(dataRow1 || ' ') : dataRow1; - const value2 = (enableTranslateLabel) ? this.translaterService?.translate && this.translaterService.getCurrentLocale && this.translaterService.getCurrentLocale() && this.translaterService.translate(dataRow2 || ' ') : dataRow2; + const value1 = (enableTranslateLabel) ? this.translaterService?.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(dataRow1 || ' ') : dataRow1; + const value2 = (enableTranslateLabel) ? this.translaterService?.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(dataRow2 || ' ') : dataRow2; const sortResult = sortByFieldType(fieldType, value1, value2, sortDirection, columnDef); if (sortResult !== SortDirectionNumber.neutral) { return sortResult; diff --git a/packages/common/src/services/extension.service.ts b/packages/common/src/services/extension.service.ts index 98915c1db..04b0e6bd7 100644 --- a/packages/common/src/services/extension.service.ts +++ b/packages/common/src/services/extension.service.ts @@ -44,7 +44,7 @@ export class ExtensionService { private rowMoveManagerExtension: RowMoveManagerExtension, private rowSelectionExtension: RowSelectionExtension, private sharedService: SharedService, - private translaterService: TranslaterService, + private translaterService?: TranslaterService, ) { } /** Dispose of all the controls & plugins */ @@ -363,8 +363,8 @@ export class ExtensionService { throw new Error('[Slickgrid-Universal] requires a Translate Service to be installed and configured when the grid option "enableTranslate" is enabled.'); } - if (locale && this.translaterService && this.translaterService.setLocale) { - this.translaterService.setLocale(locale as string); + if (locale && this.translaterService?.use) { + this.translaterService.use(locale as string); } let columnDefinitions = newColumnDefinitions; @@ -438,7 +438,7 @@ export class ExtensionService { if (Array.isArray(items)) { for (const item of items) { if (item[inputKey]) { - item[outputKey] = this.translaterService && this.translaterService.getCurrentLocale && this.translaterService.translate(item[inputKey]); + item[outputKey] = this.translaterService?.translate(item[inputKey]); } } } diff --git a/packages/common/src/services/groupingAndColspan.service.ts b/packages/common/src/services/groupingAndColspan.service.ts index d84c48aec..b6fdac012 100644 --- a/packages/common/src/services/groupingAndColspan.service.ts +++ b/packages/common/src/services/groupingAndColspan.service.ts @@ -56,6 +56,11 @@ export class GroupingAndColspanService { // When dealing with Pre-Header Grouping colspan, we need to re-create the pre-header in multiple occasions // for all these events, we have to trigger a re-create if (this._gridOptions.createPreHeaderPanel) { + // if we use Translation, then we need to translate the first time + if (this._gridOptions.enableTranslate) { + this.translateGroupingAndColSpan(); + } + this._eventHandler.subscribe(grid.onSort, () => this.renderPreHeaderRowGroupingTitles()); this._eventHandler.subscribe(grid.onColumnsResized, () => this.renderPreHeaderRowGroupingTitles()); this._eventHandler.subscribe(grid.onColumnsReordered, () => this.renderPreHeaderRowGroupingTitles()); diff --git a/packages/common/src/services/translater.service.ts b/packages/common/src/services/translater.service.ts index 5087d86a9..02627a5a3 100644 --- a/packages/common/src/services/translater.service.ts +++ b/packages/common/src/services/translater.service.ts @@ -1,18 +1,25 @@ +import { PubSubService } from './pubSub.service'; + +export type TranslateServiceEventName = 'onLanguageChanged'; + export abstract class TranslaterService { + eventName: TranslateServiceEventName; + /** - * Method to return the current locale used by the App - * @return {string} current locale + * Add an optional Pub/Sub Messaging Service, + * when defined the Translate Service will call the publish method with "onLanguageChanged" event name whenever the "use()" method is called + * @param {PubSubService} pubSub */ - getCurrentLocale(): string { - throw new Error('TranslaterService "getCurrentLocale" method must be implemented'); + addPubSubMessaging?(pubSubService: PubSubService) { + throw new Error('TranslaterService "addPubSubMessaging" method must be implemented'); } /** - * Method to set the locale to use in the App - * @param locale + * Method to return the current language used by the App + * @return {string} current language */ - setLocale(locale: string): Promise | any { - throw new Error('TranslaterService "setLocale" method must be implemented'); + getCurrentLanguage(): string { + throw new Error('TranslaterService "getCurrentLanguage" method must be implemented'); } /** @@ -23,4 +30,13 @@ export abstract class TranslaterService { translate(translationKey: string): string { throw new Error('TranslaterService "translate" method must be implemented'); } + + /** + * Method to set the language to use in the App and Translate Service + * @param {string} language + * @return {object} output - returns a Promise with the locale set (typically a JSON object) + */ + use(language: string): Promise | any { + throw new Error('TranslaterService "use" method must be implemented'); + } } diff --git a/packages/file-export/src/fileExport.service.ts b/packages/file-export/src/fileExport.service.ts index eb6b5e043..f01e3c359 100644 --- a/packages/file-export/src/fileExport.service.ts +++ b/packages/file-export/src/fileExport.service.ts @@ -184,7 +184,7 @@ export class FileExportService implements BaseFileExportService { // Group By text, it could be set in the export options or from translation or if nothing is found then use the English constant text let groupByColumnHeader = this._exportOptions.groupingColumnHeaderTitle; - if (!groupByColumnHeader && this._gridOptions.enableTranslate && this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLocale && this._translaterService.getCurrentLocale()) { + if (!groupByColumnHeader && this._gridOptions.enableTranslate && this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLanguage && this._translaterService.getCurrentLanguage()) { groupByColumnHeader = this._translaterService.translate(`${getTranslationPrefix(this._gridOptions)}GROUP_BY`); } else if (!groupByColumnHeader) { groupByColumnHeader = this._locales && this._locales.TEXT_GROUP_BY; @@ -270,7 +270,7 @@ export class FileExportService implements BaseFileExportService { // Populate the Grouped Column Header, pull the columnGroup(Key) defined columns.forEach((columnDef) => { let groupedHeaderTitle = ''; - if ((columnDef.columnGroupKey || columnDef.columnGroupKey) && this._gridOptions.enableTranslate && this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLocale && this._translaterService.getCurrentLocale()) { + if ((columnDef.columnGroupKey || columnDef.columnGroupKey) && this._gridOptions.enableTranslate && this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLanguage && this._translaterService.getCurrentLanguage()) { groupedHeaderTitle = this._translaterService.translate((columnDef.columnGroupKey || columnDef.columnGroupKey)); } else { groupedHeaderTitle = columnDef.columnGroup || ''; @@ -300,7 +300,7 @@ export class FileExportService implements BaseFileExportService { // Populate the Column Header, pull the name defined columns.forEach((columnDef) => { let headerTitle = ''; - if ((columnDef.nameKey || columnDef.nameKey) && this._gridOptions.enableTranslate && this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLocale && this._translaterService.getCurrentLocale()) { + if ((columnDef.nameKey || columnDef.nameKey) && this._gridOptions.enableTranslate && this._translaterService && this._translaterService.translate && this._translaterService.getCurrentLanguage && this._translaterService.getCurrentLanguage()) { headerTitle = this._translaterService.translate((columnDef.nameKey || columnDef.nameKey)); } else { headerTitle = columnDef.name || titleCase(columnDef.field); diff --git a/packages/graphql/src/services/__tests__/graphql.service.spec.ts b/packages/graphql/src/services/__tests__/graphql.service.spec.ts index 6ec596577..a8b971ecf 100644 --- a/packages/graphql/src/services/__tests__/graphql.service.spec.ts +++ b/packages/graphql/src/services/__tests__/graphql.service.spec.ts @@ -352,7 +352,7 @@ describe('GraphqlService', () => { const columns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }]; jest.spyOn(gridStub, 'getColumns').mockReturnValue(columns); - gridOptionMock.i18n = { getCurrentLocale: () => 'fr-CA' } as TranslaterService; + gridOptionMock.i18n = { getCurrentLanguage: () => 'fr-CA' } as TranslaterService; service.init({ datasetName: 'users', addLocaleIntoQuery: true }, paginationOptions, gridStub); const query = service.buildQuery(); diff --git a/packages/graphql/src/services/graphql.service.ts b/packages/graphql/src/services/graphql.service.ts index d1d15baa0..f8dc20ebc 100644 --- a/packages/graphql/src/services/graphql.service.ts +++ b/packages/graphql/src/services/graphql.service.ts @@ -160,7 +160,7 @@ export class GraphqlService implements BackendService { } if (this.options.addLocaleIntoQuery) { // first: 20, ... locale: "en-CA" - datasetFilters.locale = this._gridOptions.i18n && this._gridOptions.i18n.getCurrentLocale() || this._gridOptions.locale || 'en'; + datasetFilters.locale = this._gridOptions.i18n && this._gridOptions.i18n.getCurrentLanguage() || this._gridOptions.locale || 'en'; } if (this.options.extraQueryArguments) { // first: 20, ... userId: 123 diff --git a/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip b/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip index b62ef29d2..5e177d155 100644 Binary files a/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip and b/packages/vanilla-bundle/dist-grid-bundle-zip/slickgrid-vanilla-bundle.zip differ diff --git a/packages/vanilla-bundle/package.json b/packages/vanilla-bundle/package.json index 4bc2ea76f..aad638b51 100644 --- a/packages/vanilla-bundle/package.json +++ b/packages/vanilla-bundle/package.json @@ -48,6 +48,7 @@ "@slickgrid-universal/excel-export": "*", "@slickgrid-universal/file-export": "*", "dompurify": "^2.0.12", + "es6-promise": "^4.2.8", "isomorphic-fetch": "^2.2.1" }, "devDependencies": { diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts index e4adeb569..316fc4e9b 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-footer.spec.ts @@ -66,7 +66,7 @@ describe('Slick-Footer Component', () => { const leftFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.left-footer'); const rightFooterElm = document.querySelector('div.slick-custom-footer.slickgrid_123456 > div.metrics'); - expect(translateService.getCurrentLocale()).toBe('en'); + expect(translateService.getCurrentLanguage()).toBe('en'); expect(footerContainerElm).toBeTruthy(); expect(leftFooterElm).toBeTruthy(); expect(rightFooterElm).toBeTruthy(); @@ -183,7 +183,7 @@ describe('Slick-Footer Component', () => { it('should create a the Slick-Footer component in the DOM and use different locale when enableTranslate is enabled', () => { mockGridOptions.customFooterOptions.metricTexts = { itemsKey: 'ITEMS', lastUpdateKey: 'LAST_UPDATE', ofKey: 'OF' }; mockGridOptions.enableTranslate = true; - translateService.setLocale('fr'); + translateService.use('fr'); component = new SlickFooterComponent(gridStub, mockGridOptions.customFooterOptions, translateService); component.renderFooter(div); diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-pagination-without-i18n.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-pagination-without-i18n.spec.ts index 0624aab24..048abfbd4 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-pagination-without-i18n.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-pagination-without-i18n.spec.ts @@ -104,7 +104,7 @@ describe('Slick-Pagination Component', () => { const pageInfoFromTo = document.querySelector('.page-info-from-to'); const pageInfoTotalItems = document.querySelector('.page-info-total-items'); - expect(translateService.getCurrentLocale()).toBe('en'); + expect(translateService.getCurrentLanguage()).toBe('en'); expect(removeExtraSpaces(pageInfoFromTo.innerHTML)).toBe('10-15of'); expect(removeExtraSpaces(pageInfoTotalItems.innerHTML)).toBe('95items'); component.dispose(); diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-pagination.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-pagination.spec.ts index 52240afac..6a40a579b 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-pagination.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-pagination.spec.ts @@ -93,7 +93,7 @@ describe('Slick-Pagination Component', () => { const pageInfoTotalItems = document.querySelector('.page-info-total-items'); const itemsPerPage = document.querySelector('.items-per-page'); - expect(translateService.getCurrentLocale()).toBe('en'); + expect(translateService.getCurrentLanguage()).toBe('en'); expect(removeExtraSpaces(pageInfoFromTo.innerHTML)).toBe('10-15of'); expect(removeExtraSpaces(pageInfoTotalItems.innerHTML)).toBe('95items'); expect(itemsPerPage.selectedOptions[0].value).toBe('5'); @@ -253,13 +253,13 @@ describe('with different i18n locale', () => { }); it('should create a the Slick-Pagination component in the DOM and expect different locale when changed', (done) => { - translateService.setLocale('fr'); - eventPubSubService.publish('onLocaleChanged', 'fr'); + translateService.use('fr'); + eventPubSubService.publish('onLanguageChange', 'fr'); setTimeout(() => { const pageInfoFromTo = document.querySelector('.page-info-from-to'); const pageInfoTotalItems = document.querySelector('.page-info-total-items'); - expect(translateService.getCurrentLocale()).toBe('fr'); + expect(translateService.getCurrentLanguage()).toBe('fr'); expect(removeExtraSpaces(pageInfoFromTo.innerHTML)).toBe(`10-15de`); expect(removeExtraSpaces(pageInfoTotalItems.innerHTML)).toBe(`95éléments`); done(); diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid-constructor.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid-constructor.spec.ts index 4519e6702..61d817359 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid-constructor.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid-constructor.spec.ts @@ -25,13 +25,13 @@ import { SlickGrid, SortService, TreeDataService, + TranslaterService, } from '@slickgrid-universal/common'; import { GraphqlService, GraphqlPaginatedResult, GraphqlServiceApi, GraphqlServiceOption } from '@slickgrid-universal/graphql'; import * as utilities from '@slickgrid-universal/common/dist/commonjs/services/backend-utilities'; import { SlickVanillaGridBundle } from '../slick-vanilla-grid-bundle'; import { EventPubSubService } from '../../services/eventPubSub.service'; -import { TranslateService } from '../../services'; import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; import { HttpStub } from '../../../../../test/httpClientStub'; @@ -302,8 +302,8 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () paginationServiceStub, sharedService, sortServiceStub, - translateService as unknown as TranslateService, treeDataServiceStub, + translateService as unknown as TranslaterService, ); }); @@ -331,17 +331,26 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () }); describe('initialization method', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + describe('columns definitions changed', () => { it('should expect "translateColumnHeaders" being called when "enableTranslate" is set', () => { const translateSpy = jest.spyOn(extensionServiceStub, 'translateColumnHeaders'); const autosizeSpy = jest.spyOn(mockGrid, 'autosizeColumns'); const updateSpy = jest.spyOn(component, 'updateColumnDefinitionsList'); + const eventSpy = jest.spyOn(eventPubSubService, 'publish'); + const addPubSubSpy = jest.spyOn(component.translaterService, 'addPubSubMessaging'); const mockColDefs = [{ id: 'name', field: 'name', editor: undefined, internalColumnEditor: {} }]; component.columnDefinitions = mockColDefs; component.gridOptions = { enableTranslate: true }; component.initialization(divContainer); + expect(component.translaterService).toBeTruthy(); + expect(addPubSubSpy).toHaveBeenCalled(); + expect(eventSpy).toHaveBeenCalledTimes(4); expect(translateSpy).toHaveBeenCalled(); expect(autosizeSpy).toHaveBeenCalled(); expect(updateSpy).toHaveBeenCalledWith(mockColDefs); @@ -352,13 +361,18 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () const autosizeSpy = jest.spyOn(mockGrid, 'autosizeColumns'); const updateSpy = jest.spyOn(component, 'updateColumnDefinitionsList'); const renderSpy = jest.spyOn(extensionServiceStub, 'renderColumnHeaders'); + const eventSpy = jest.spyOn(eventPubSubService, 'publish'); + const addPubSubSpy = jest.spyOn(component.translaterService, 'addPubSubMessaging'); const mockColDefs = [{ id: 'name', field: 'name', editor: undefined, internalColumnEditor: {} }]; component.columnDefinitions = mockColDefs; + component.gridOptions = { enableTranslate: false }; component.initialization(divContainer); - expect(translateSpy).toHaveBeenCalled(); + expect(translateSpy).not.toHaveBeenCalled(); expect(autosizeSpy).toHaveBeenCalled(); + expect(addPubSubSpy).not.toHaveBeenCalled(); + expect(eventSpy).toHaveBeenCalledTimes(4); expect(updateSpy).toHaveBeenCalledWith(mockColDefs); expect(renderSpy).toHaveBeenCalledWith(mockColDefs, true); }); @@ -977,7 +991,7 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () component.gridOptions = { enableTranslate: true, createPreHeaderPanel: false, enableDraggableGrouping: false } as GridOption; component.initialization(divContainer); - eventPubSubService.publish('onLocaleChanged', {}); + eventPubSubService.publish('onLanguageChange', { language: 'fr' }); setTimeout(() => { expect(setHeaderRowSpy).not.toHaveBeenCalled(); @@ -1005,7 +1019,7 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () component.gridOptions = { enableTranslate: true, createPreHeaderPanel: true, enableDraggableGrouping: false } as GridOption; component.initialization(divContainer); - eventPubSubService.publish('onLocaleChanged', {}); + eventPubSubService.publish('onLanguageChange', {}); setTimeout(() => { expect(transGroupingColSpanSpy).toHaveBeenCalled(); @@ -1025,7 +1039,7 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () component.gridOptions = { enableTranslate: true, createPreHeaderPanel: true, enableDraggableGrouping: false } as GridOption; component.initialization(divContainer); - eventPubSubService.publish('onLocaleChanged', {}); + eventPubSubService.publish('onLanguageChange', {}); setTimeout(() => { expect(groupColSpanSpy).toHaveBeenCalled(); @@ -1332,7 +1346,7 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () const mockGridOptions = { enableTranslate: true, showCustomFooter: true, }; jest.spyOn(mockGrid, 'getOptions').mockReturnValue(mockGridOptions); - translateService.setLocale('fr'); + translateService.use('fr'); component.gridOptions = mockGridOptions; component.initialization(divContainer); component.columnDefinitions = mockColDefs; diff --git a/packages/vanilla-bundle/src/components/slick-pagination.ts b/packages/vanilla-bundle/src/components/slick-pagination.ts index 198a6fff0..e068dd411 100644 --- a/packages/vanilla-bundle/src/components/slick-pagination.ts +++ b/packages/vanilla-bundle/src/components/slick-pagination.ts @@ -46,7 +46,7 @@ export class SlickPaginationComponent { if (this._enableTranslate && this.pubSubService && this.pubSubService.subscribe) { this._subscriptions.push( - this.pubSubService.subscribe('onLocaleChanged', () => this.translatePaginationTexts(this._locales)) + this.pubSubService.subscribe('onLanguageChange', () => this.translatePaginationTexts(this._locales)) ); } diff --git a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts index 815ab2f17..7824446c6 100644 --- a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts +++ b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts @@ -58,6 +58,7 @@ import { SharedService, SortService, SlickgridConfig, + TranslaterService, TreeDataService, // utilities @@ -67,7 +68,6 @@ import { } from '@slickgrid-universal/common'; import { FileExportService } from '../services/fileExport.service'; -import { TranslateService } from '../services/translate.service'; import { EventPubSubService } from '../services/eventPubSub.service'; import { SlickFooterComponent } from './slick-footer'; import { SlickPaginationComponent } from './slick-pagination'; @@ -124,7 +124,7 @@ export class SlickVanillaGridBundle { paginationService: PaginationService; sharedService: SharedService; sortService: SortService; - translateService: TranslateService; + translaterService: TranslaterService | undefined; treeDataService: TreeDataService; slickFooter: SlickFooterComponent | undefined; @@ -239,6 +239,9 @@ export class SlickVanillaGridBundle { this._gridOptions = this.mergeGridOptions(options || {}); const isDeepCopyDataOnPageLoadEnabled = !!(this._gridOptions && this._gridOptions.enableDeepCopyDatasetOnPageLoad); + // if user is providing a Translate Service, it has to be passed under the "i18n" grid option + this.translaterService = this._gridOptions.i18n; + // initialize and assign all Service Dependencies this.constructorDependenciesInit(gridParentContainerElm); @@ -258,10 +261,9 @@ export class SlickVanillaGridBundle { this.gridEventService = new GridEventService(); const slickgridConfig = new SlickgridConfig(); this.sharedService = new SharedService(); - this.translateService = new TranslateService(); - this.collectionService = new CollectionService(this.translateService); - this.extensionUtility = new ExtensionUtility(this.sharedService, this.translateService); - const filterFactory = new FilterFactory(slickgridConfig, this.collectionService, this.translateService); + this.collectionService = new CollectionService(this.translaterService); + this.extensionUtility = new ExtensionUtility(this.sharedService, this.translaterService); + const filterFactory = new FilterFactory(slickgridConfig, this.collectionService, this.translaterService); this.filterService = new FilterService(filterFactory, this._eventPubSubService, this.sharedService); this.sortService = new SortService(this.sharedService, this._eventPubSubService); this.treeDataService = new TreeDataService(this.sharedService); @@ -272,15 +274,15 @@ export class SlickVanillaGridBundle { // extensions const autoTooltipExtension = new AutoTooltipExtension(this.extensionUtility, this.sharedService); const cellExternalCopyManagerExtension = new CellExternalCopyManagerExtension(this.extensionUtility, this.sharedService); - const cellMenuExtension = new CellMenuExtension(this.extensionUtility, this.sharedService, this.translateService); - const contextMenuExtension = new ContextMenuExtension(this.extensionUtility, this.sharedService, this.translateService, this.treeDataService); + const cellMenuExtension = new CellMenuExtension(this.extensionUtility, this.sharedService, this.translaterService); + const contextMenuExtension = new ContextMenuExtension(this.extensionUtility, this.sharedService, this.treeDataService, this.translaterService); const columnPickerExtension = new ColumnPickerExtension(this.extensionUtility, this.sharedService); const checkboxExtension = new CheckboxSelectorExtension(this.extensionUtility, this.sharedService); const draggableGroupingExtension = new DraggableGroupingExtension(this.extensionUtility, this.sharedService); - const gridMenuExtension = new GridMenuExtension(this.extensionUtility, this.filterService, this.sharedService, this.sortService, this.translateService); + const gridMenuExtension = new GridMenuExtension(this.extensionUtility, this.filterService, this.sharedService, this.sortService, this.translaterService); const groupItemMetaProviderExtension = new GroupItemMetaProviderExtension(this.sharedService); const headerButtonExtension = new HeaderButtonExtension(this.extensionUtility, this.sharedService); - const headerMenuExtension = new HeaderMenuExtension(this.extensionUtility, this.filterService, this._eventPubSubService, this.sharedService, this.sortService, this.translateService); + const headerMenuExtension = new HeaderMenuExtension(this.extensionUtility, this.filterService, this._eventPubSubService, this.sharedService, this.sortService, this.translaterService); const rowMoveManagerExtension = new RowMoveManagerExtension(this.extensionUtility, this.sharedService); const rowSelectionExtension = new RowSelectionExtension(this.extensionUtility, this.sharedService); @@ -299,7 +301,7 @@ export class SlickVanillaGridBundle { rowMoveManagerExtension, rowSelectionExtension, this.sharedService, - this.translateService, + this.translaterService, ); this.groupingAndColspanService = new GroupingAndColspanService(this.extensionUtility, this.extensionService); } @@ -317,8 +319,8 @@ export class SlickVanillaGridBundle { paginationService: PaginationService, sharedService: SharedService, sortService: SortService, - translateService: TranslateService, treeDataService: TreeDataService, + translateService?: TranslaterService, ) { this._eventPubSubService = eventPubSubService; this._eventPubSubService.eventNamingStyle = this._gridOptions && this._gridOptions.eventNamingStyle || EventNamingStyle.camelCase; @@ -334,7 +336,7 @@ export class SlickVanillaGridBundle { this.paginationService = paginationService; this.sharedService = sharedService; this.sortService = sortService; - this.translateService = translateService; + this.translaterService = translateService; this.treeDataService = treeDataService; } @@ -469,7 +471,7 @@ export class SlickVanillaGridBundle { // user could show a custom footer with the data metrics (dataset length and last updated timestamp) if (!this.gridOptions.enablePagination && this.gridOptions.showCustomFooter && this.gridOptions.customFooterOptions) { - this.slickFooter = new SlickFooterComponent(this.grid, this.gridOptions.customFooterOptions, this.translateService); + this.slickFooter = new SlickFooterComponent(this.grid, this.gridOptions.customFooterOptions, this.translaterService); this.slickFooter.renderFooter(this._gridParentContainerElm); } @@ -648,6 +650,12 @@ export class SlickVanillaGridBundle { } bindDifferentHooks(grid: SlickGrid, gridOptions: GridOption, dataView: SlickDataView) { + // if user is providing a Translate Service, we need to add our PubSub Service (but only after creating all dependencies) + // so that we can later subscribe to the "onLanguageChange" event and translate any texts whenever that get triggered + if (gridOptions.enableTranslate && this.translaterService?.addPubSubMessaging) { + this.translaterService.addPubSubMessaging(this._eventPubSubService); + } + // translate some of them on first load, then on each language change if (gridOptions.enableTranslate) { this.translateColumnHeaderTitleKeys(); @@ -657,7 +665,7 @@ export class SlickVanillaGridBundle { // on locale change, we have to manually translate the Headers, GridMenu this.subscriptions.push( - this._eventPubSubService.subscribe('onLocaleChanged', () => { + this._eventPubSubService.subscribe('onLanguageChange', () => { if (gridOptions.enableTranslate) { this.extensionService.translateCellMenu(); this.extensionService.translateColumnHeaders(); @@ -1013,7 +1021,7 @@ export class SlickVanillaGridBundle { // also initialize (render) the pagination component if (this._gridOptions.enablePagination && !this._isPaginationInitialized) { - this.slickPagination = new SlickPaginationComponent(this.paginationService, this._eventPubSubService, this.sharedService, this.translateService); + this.slickPagination = new SlickPaginationComponent(this.paginationService, this._eventPubSubService, this.sharedService, this.translaterService); this.slickPagination.renderPagination(this._gridParentContainerElm); } @@ -1195,13 +1203,13 @@ export class SlickVanillaGridBundle { /** Translate all Custom Footer Texts (footer with metrics) */ private translateCustomFooterTexts() { - if (this.translateService?.translate) { + if (this.translaterService?.translate) { const customFooterOptions = this.gridOptions && this.gridOptions.customFooterOptions || {}; customFooterOptions.metricTexts = customFooterOptions.metricTexts || {}; for (const propName of Object.keys(customFooterOptions.metricTexts)) { if (propName.lastIndexOf('Key') > 0) { const propNameWithoutKey = propName.substring(0, propName.lastIndexOf('Key')); - customFooterOptions.metricTexts[propNameWithoutKey] = this.translateService.translate(customFooterOptions.metricTexts[propName] || ' '); + customFooterOptions.metricTexts[propNameWithoutKey] = this.translaterService.translate(customFooterOptions.metricTexts[propName] || ' '); } } } diff --git a/packages/vanilla-bundle/src/services/__tests__/binding.helper.spec.ts b/packages/vanilla-bundle/src/services/__tests__/binding.helper.spec.ts index 01691e6e1..16ecf9341 100644 --- a/packages/vanilla-bundle/src/services/__tests__/binding.helper.spec.ts +++ b/packages/vanilla-bundle/src/services/__tests__/binding.helper.spec.ts @@ -28,6 +28,7 @@ describe('Binding Helper', () => { const mockEvent = new CustomEvent('change', { bubbles: true, detail: { target: { value: 'Jane' } } }); elm.dispatchEvent(mockEvent); + expect(helper.observers.length).toBe(1); expect(helper.querySelectorPrefix).toBe(''); expect(mockObj.name).toBe('Jane'); expect(mockCallback).toHaveBeenCalled(); @@ -81,8 +82,10 @@ describe('Binding Helper', () => { helper.addElementBinding(mockObj, 'age', 'span.custom', 'textContent'); mockObj.age = 30; + expect(helper.observers.length).toBe(1); + expect(helper.observers[0].elementBindings.length).toBe(2); expect(helper.querySelectorPrefix).toBe('.grid '); - // expect(elm1.textContent).toBe('30'); // not sure why this doesn't work in unit test, however it works in real life, so we'll leave at that + expect(elm1.textContent).toBe('30'); expect(elm2.textContent).toBe('30'); expect(document.querySelectorAll('.grid span.custom').length).toBe(2); }); diff --git a/packages/vanilla-bundle/src/services/__tests__/translate.service.spec.ts b/packages/vanilla-bundle/src/services/__tests__/translate.service.spec.ts deleted file mode 100644 index 0ebad82ba..000000000 --- a/packages/vanilla-bundle/src/services/__tests__/translate.service.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { TranslateService } from '../translate.service'; - -describe('Export Service', () => { - let service: TranslateService; - - beforeEach(() => { - service = new TranslateService(); - }); - - it('should return "en" when calling "getCurrentLocale" method', () => { - const output = service.getCurrentLocale(); - expect(output).toBe('en'); - }); - - it('should return a promise with same locale returned as the one passed as argument', async () => { - const output = await service.setLocale('fr'); - expect(output).toBe('fr'); - }); - - it('should return same translation as argument provided', () => { - const output = service.translate('HELLO'); - expect(output).toBe('HELLO'); - }); -}); diff --git a/packages/vanilla-bundle/src/services/binding.helper.ts b/packages/vanilla-bundle/src/services/binding.helper.ts index 4df73fd49..e39c7d84e 100644 --- a/packages/vanilla-bundle/src/services/binding.helper.ts +++ b/packages/vanilla-bundle/src/services/binding.helper.ts @@ -11,6 +11,10 @@ export class BindingHelper { this._querySelectorPrefix = prefix; } + get observers() { + return this._observers; + } + constructor() { } dispose() { @@ -27,22 +31,22 @@ export class BindingHelper { addElementBinding(variable: any, property: string, selector: string, attribute: string, events?: string | string[], callback?: (val: any) => void) { const elements = document.querySelectorAll(`${this.querySelectorPrefix}${selector}`); - elements.forEach(elm => { - // before creating a new observer, first check if the variable already has an associated observer - // if we can't find an observer then we'll create a new one for it - let observer = this._observers.find((bind) => bind.property === variable); - if (!observer) { - observer = new BindingService({ variable, property }); - if (Array.isArray(events)) { - for (const eventName of events) { - observer.bind(elm, attribute, eventName, callback); - } - } else { - observer.bind(elm, attribute, events, callback); - } - this._observers.push(observer); - } - }); + // before creating a new observer, first check if the variable already has an associated observer + // if we can't find an observer then we'll create a new one for it + let observer = this._observers.find((bind) => bind.property === variable); + if (!observer) { + observer = new BindingService({ variable, property }); + } + + // add event(s) binding + // when having multiple events, we'll loop through through them and add a binding for each + if (Array.isArray(events)) { + events.forEach(eventName => observer?.bind(elements, attribute, eventName, callback)); + } else { + observer?.bind(elements, attribute, events, callback); + } + + this._observers.push(observer); } /** From a DOM element selector, which could be zero or multiple elements, add an event listener */ diff --git a/packages/vanilla-bundle/src/services/binding.service.ts b/packages/vanilla-bundle/src/services/binding.service.ts index 70bde47c3..a6da4e3e7 100644 --- a/packages/vanilla-bundle/src/services/binding.service.ts +++ b/packages/vanilla-bundle/src/services/binding.service.ts @@ -65,14 +65,38 @@ export class BindingService { } /** - * Add binding to an element by an object attribute and optionally on an event, we can do it in couple ways + * Add binding to 1 or more DOM Element by an object attribute and optionally on an event, we can do it in couple ways * 1- if there's no event provided, it will simply replace the DOM elemnt (by an attribute), for example an innerHTML * 2- when an event is provided, we will replace the DOM element (by an attribute) every time an event is triggered * 2.1- we could also provide an extra callback method to execute when the event gets triggered */ - bind(element: Element | null, attribute: string, eventName?: string, callback?: (val: any) => any) { - const binding: ElementBinding | ElementBindingWithListener = { element, attribute }; + bind(elements: Element | NodeListOf | null, attribute: string, eventName?: string, callback?: (val: any) => any) { + if (elements && (elements as NodeListOf).forEach) { + // multiple DOM elements coming from a querySelectorAll() call + (elements as NodeListOf).forEach(elm => this.bindSingleElement(elm, attribute, eventName, callback)); + } else if (elements) { + // single DOM element coming from a querySelector() call + this.bindSingleElement(elements as Element, attribute, eventName, callback); + } + return this; + } + + /** Unbind (remove) an event from an element */ + unbind(element: Element | null, eventName: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) { + if (element) { + element.removeEventListener(eventName, listener, options); + } + } + + /** + * Add binding to a single element by an object attribute and optionally on an event, we can do it in couple ways + * 1- if there's no event provided, it will simply replace the DOM elemnt (by an attribute), for example an innerHTML + * 2- when an event is provided, we will replace the DOM element (by an attribute) every time an event is triggered + * 2.1- we could also provide an extra callback method to execute when the event gets triggered + */ + private bindSingleElement(element: Element | null, attribute: string, eventName?: string, callback?: (val: any) => any) { + const binding: ElementBinding | ElementBindingWithListener = { element, attribute }; if (element) { if (eventName) { const listener = () => { @@ -94,14 +118,6 @@ export class BindingService { this.elementBindings.push(binding); element[attribute] = typeof this._value === 'string' ? this.sanitizeText(this._value) : this._value; } - return this; - } - - /** Unbind (remove) an event from an element */ - unbind(element: Element | null, eventName: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions) { - if (element) { - element.removeEventListener(eventName, listener, options); - } } private sanitizeText(dirtyText: string): string { diff --git a/packages/vanilla-bundle/src/services/index.ts b/packages/vanilla-bundle/src/services/index.ts index 25213cdf8..34548df39 100644 --- a/packages/vanilla-bundle/src/services/index.ts +++ b/packages/vanilla-bundle/src/services/index.ts @@ -2,4 +2,3 @@ export * from './binding.service'; export * from './binding.helper'; export * from './eventPubSub.service'; export * from './fileExport.service'; -export * from './translate.service'; diff --git a/packages/vanilla-bundle/src/services/translate.service.ts b/packages/vanilla-bundle/src/services/translate.service.ts deleted file mode 100644 index cd564b638..000000000 --- a/packages/vanilla-bundle/src/services/translate.service.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { TranslaterService } from '@slickgrid-universal/common'; - -export class TranslateService implements TranslaterService { - private _currentLocale = 'en'; - - getCurrentLocale(): string { - return this._currentLocale; - } - - setLocale(locale: string): Promise { - this._currentLocale = locale; - return new Promise((resolve) => resolve(this._currentLocale)); - } - - translate(translationKey: string): string { - // TODO implement translation with `translations = require(jsonFilePath)`, then use `translations[translationKey]` - return translationKey; - } -} diff --git a/test/cypress/integration/example10.spec.js b/test/cypress/integration/example10.spec.js index 018e52f5c..2ee070d8c 100644 --- a/test/cypress/integration/example10.spec.js +++ b/test/cypress/integration/example10.spec.js @@ -365,7 +365,7 @@ describe('Example 10 - GraphQL Grid', () => { }); }); - xdescribe('Translate by Language', () => { + describe('Translate by Language', () => { it('should Clear all Filters & Sorts', () => { cy.contains('Clear all Filter & Sorts').click(); @@ -374,7 +374,7 @@ describe('Example 10 - GraphQL Grid', () => { }); it('should have English Column Titles in the grid after switching locale', () => { - const expectedColumnTitles = ['Name', 'Gender', 'Company', 'Zip', 'Street', 'Date']; + const expectedColumnTitles = ['Name', 'Gender', 'Company', 'Billing Address Zip', 'Billing Address Street', 'Date']; cy.get('.grid10') .find('.slick-header-left .slick-header-columns') diff --git a/test/translateServiceStub.ts b/test/translateServiceStub.ts index 82d751900..62f39d2ef 100644 --- a/test/translateServiceStub.ts +++ b/test/translateServiceStub.ts @@ -1,9 +1,12 @@ -import { TranslaterService } from '../packages/common/src/services/translater.service'; +import { PubSubService, TranslaterService } from '../packages/common'; export class TranslateServiceStub implements TranslaterService { + eventName = 'onLanguageChange'; private _locale = 'en'; - getCurrentLocale(): string { + addPubSubMessaging(pubSubService: PubSubService) { } + + getCurrentLanguage(): string { return this._locale; } @@ -72,7 +75,7 @@ export class TranslateServiceStub implements TranslaterService { return output; } - setLocale(locale: string) { + use(locale: string) { return new Promise(resolve => resolve(this._locale = locale)); } } diff --git a/yarn.lock b/yarn.lock index 05ee3c54d..6442728d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3878,10 +3878,10 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= -cypress@^4.10.0: - version "4.10.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.10.0.tgz#6b507f4637af6a65ea285953f899951d65e82416" - integrity sha512-eFv1WPp4zFrAgZ6mwherBGVsTpHvay/hEF5F7U7yfAkTxsUQn/ZG/LdX67fIi3bKDTQXYzFv/CvywlQSeug8Bg== +cypress@^4.11.0: + version "4.11.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-4.11.0.tgz#054b0b85fd3aea793f186249ee1216126d5f0a7e" + integrity sha512-6Yd598+KPATM+dU1Ig0g2hbA+R/o1MAKt0xIejw4nZBVLSplCouBzqeKve6XsxGU6n4HMSt/+QYsWfFcoQeSEw== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/request" "2.88.5" @@ -3907,7 +3907,7 @@ cypress@^4.10.0: is-installed-globally "0.3.2" lazy-ass "1.6.0" listr "0.14.3" - lodash "4.17.15" + lodash "4.17.19" log-symbols "3.0.0" minimist "1.2.5" moment "2.26.0" @@ -4619,7 +4619,7 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es6-promise@^4.0.3: +es6-promise@^4.0.3, es6-promise@^4.2.8: version "4.2.8" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== @@ -7923,7 +7923,12 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.15, lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.2.1, lodash@~4.17.12: +lodash@4.17.19: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + +lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.2.1, lodash@~4.17.12: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==