Repository: highcharts/highcharts
Pull request #22786: grid/lang-and-a11y-demo
Author: login: ddragula
Labels: name: Grid
description: Not used anymore
Created: 2025-03-12T17:24:39Z | Merged: 2025-03-20T10:32:11Z

Added ***Internationalization** and **Accessibility*** Grid demos.

---
- To see the specific tasks where the Asana app for GitHub is being used, see below:
  - https://app.asana.com/0/0/1209622101623359


Diff:
diff --git a/samples/grid-lite/demo/internationalization/demo.css b/samples/grid-lite/demo/internationalization/demo.css
new file mode 100644
index 00000000000..dc898b865d4
--- /dev/null
+++ b/samples/grid-lite/demo/internationalization/demo.css
@@ -0,0 +1,14 @@
+@import url("https://code.highcharts.com/grid/css/grid.css");
+
+.lang-options {
+    display: flex;
+    justify-content: end;
+}
+
+.demo {
+    background-color: var(--hcg-background);
+    padding: 8px;
+    color: var(--hcg-color);
+    font-family: Arial, Helvetica, sans-serif;
+}
+
diff --git a/samples/grid-lite/demo/internationalization/demo.details b/samples/grid-lite/demo/internationalization/demo.details
new file mode 100644
index 00000000000..1590f471e26
--- /dev/null
+++ b/samples/grid-lite/demo/internationalization/demo.details
@@ -0,0 +1,11 @@
+---
+ name: Grid Internationalization Demo
+ authors:
+   - Dawid Dragula
+ js_wrap: b
+ tags:
+  - Highcharts Grid demo
+ categories:
+  - General:
+        priority: 20
+...
\ No newline at end of file
diff --git a/samples/grid-lite/demo/internationalization/demo.html b/samples/grid-lite/demo/internationalization/demo.html
new file mode 100644
index 00000000000..b8e360eff57
--- /dev/null
+++ b/samples/grid-lite/demo/internationalization/demo.html
@@ -0,0 +1,16 @@
+<script src="https://code.highcharts.com/grid/grid-lite.js"></script>
+
+<div class="demo hcg-theme-default">
+    <div class="lang-options">
+        <label>
+            <span id="lang-span"></span>
+            <select id="lang-select">
+                <option value="en" selected>English</option>
+                <option value="no">Norsk</option>
+                <option value="pl">polski</option>
+                <option value="zh">中文</option>
+            </select>
+        </label>
+    </div>
+    <div id="container"></div>
+</div>
diff --git a/samples/grid-lite/demo/internationalization/demo.js b/samples/grid-lite/demo/internationalization/demo.js
new file mode 100644
index 00000000000..ddcfe7d4904
--- /dev/null
+++ b/samples/grid-lite/demo/internationalization/demo.js
@@ -0,0 +1,199 @@
+const grid = Grid.grid('container', {
+    dataTable: {
+        columns: {
+            id: ['1', '2', '3', '4'],
+            productEN: ['Apple', 'Pear', 'Plum', 'Banana'],
+            productNO: ['Eple', 'Pære', 'Plomme', 'Banan'],
+            productPL: ['Jabłko', 'Gruszka', 'Śliwka', 'Banan'],
+            productZH: ['苹果', '梨', '李子', '香蕉'],
+            weight: [100, 60, 30, 200],
+            price: [1.5, 2.53, 5, 4.5]
+        }
+    },
+    rendering: {
+        rows: {
+            minVisibleRows: 4
+        }
+    },
+    columns: [{
+        id: 'id',
+        header: {
+            format: 'ID'
+        }
+    }, {
+        id: 'weight',
+        cells: {
+            format: '{value} g'
+        }
+    }, {
+        id: 'price',
+        cells: {
+            format: '{value:.2f} €'
+        }
+    }]
+});
+
+const languages = {
+    en: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sortable.',
+                    announcements: {
+                        ascending: 'Sorted ascending.',
+                        descending: 'Sorted descending.',
+                        none: 'Not sorted.'
+                    }
+                }
+            },
+            decimalPoint: '.'
+        },
+        header: ['id', 'productEN', 'weight', 'price'],
+        columns: [{
+            id: 'productEN',
+            header: {
+                format: 'Product'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Weight'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Price'
+            }
+        }],
+        caption: {
+            text: 'Fruits weight and price'
+        }
+    },
+    no: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sorterbar.',
+                    announcements: {
+                        ascending: 'Sortert stigende.',
+                        descending: 'Sortert synkende.',
+                        none: 'Ikke sortert.'
+                    }
+                }
+            },
+            decimalPoint: ','
+        },
+        header: ['id', 'productNO', 'weight', 'price'],
+        columns: [{
+            id: 'productNO',
+            header: {
+                format: 'Produkt'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Vekt'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Pris'
+            }
+        }],
+        caption: {
+            text: 'Frukters vekt og pris'
+        }
+    },
+    pl: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sortowalne.',
+                    announcements: {
+                        ascending: 'Posortowano rosnąco.',
+                        descending: 'Posortowano malejąco.',
+                        none: 'Nie posortowano.'
+                    }
+                }
+            },
+            decimalPoint: ','
+        },
+        header: ['id', 'productPL', 'weight', 'price'],
+        columns: [{
+            id: 'productPL',
+            header: {
+                format: 'Produkt'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Waga'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Cena'
+            }
+        }],
+        caption: {
+            text: 'Waga i cena owoców'
+        }
+    },
+    zh: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: '可排序。',
+                    announcements: {
+                        ascending: '已按升序排序。',
+                        descending: '已按降序排序。',
+                        none: '未排序。'
+                    }
+                }
+            },
+            decimalPoint: '.'
+        },
+        header: ['id', 'productZH', 'weight', 'price'],
+        columns: [{
+            id: 'productZH',
+            header: {
+                format: '产品'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: '重量'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: '价格'
+            }
+        }],
+        caption: {
+            text: '水果的重量和价格'
+        }
+    }
+};
+
+const languageSelect = document.getElementById('lang-select');
+const languageSpan = document.getElementById('lang-span');
+const uiTranslations = {
+    en: 'Language',
+    no: 'Språk',
+    pl: 'Język',
+    zh: '语言'
+};
+
+function setLanguage(lang) {
+    grid.update(languages[lang]);
+    grid.container.setAttribute('lang', lang);
+    grid.container.setAttribute('aria-lang', lang);
+    languageSpan.textContent = `${uiTranslations[lang]}:`;
+}
+
+languageSelect.addEventListener('change', () => {
+    setLanguage(languageSelect.value);
+});
+
+setLanguage('en');
diff --git a/samples/grid-pro/demo/accessibility/demo.css b/samples/grid-pro/demo/accessibility/demo.css
new file mode 100644
index 00000000000..cd1fe52d7ed
--- /dev/null
+++ b/samples/grid-pro/demo/accessibility/demo.css
@@ -0,0 +1,55 @@
+@import url("https://code.highcharts.com/dashboards/css/datagrid.css");
+
+.demo {
+    background-color: var(--hcg-background);
+    padding: 8px;
+    color: var(--hcg-color);
+    font-family: Arial, Helvetica, sans-serif;
+}
+
+.editor h2 {
+    margin: 3px 0 16px;
+}
+
+.editor {
+    margin-top: 8px;
+    padding: 8px;
+    border: 1px solid var(--hcg-border-color);
+    border-radius: 6px;
+}
+
+.editor details {
+    border: 1px solid var(--hcg-border-color);
+    border-radius: 6px;
+    padding: 8px;
+    margin-top: 6px;
+}
+
+.editor summary {
+    font-size: 1em;
+    font-weight: bold;
+    cursor: pointer;
+}
+
+.editor .option-group {
+    margin-left: 20px;
+    margin-top: 6px;
+}
+
+.editor .option {
+    margin-bottom: 6px;
+}
+
+.option label {
+    display: inline-block;
+}
+
+.description {
+    font-style: italic;
+    font-size: 0.8em;
+    color: var(--hcg-loader-color);
+}
+
+.last {
+    margin: 8px 0 0;
+}
diff --git a/samples/grid-pro/demo/accessibility/demo.details b/samples/grid-pro/demo/accessibility/demo.details
new file mode 100644
index 00000000000..9b06491d569
--- /dev/null
+++ b/samples/grid-pro/demo/accessibility/demo.details
@@ -0,0 +1,11 @@
+---
+ name: Grid Accessibility Demo
+ authors:
+   - Dawid Dragula
+ js_wrap: b
+ tags:
+  - Highcharts Grid demo
+ categories:
+  - General:
+        priority: 20
+...
\ No newline at end of file
diff --git a/samples/grid-pro/demo/accessibility/demo.html b/samples/grid-pro/demo/accessibility/demo.html
new file mode 100644
index 00000000000..605c7a115e7
--- /dev/null
+++ b/samples/grid-pro/demo/accessibility/demo.html
@@ -0,0 +1,99 @@
+<script src="https://code.highcharts.com/dashboards/datagrid.js"></script>
+
+<div class="demo hcg-theme-default">
+    <div id="container"></div>
+    <div class="editor">
+        <h2>Options Editor</h2>
+        <form>
+            <details>
+                <summary>accessibility</summary>
+                <div class="option-group">
+                    <div class="option">
+                        <label for="a11y-enabled">enabled:</label>
+                        <input type="checkbox" id="a11y-enabled" name="accessibility.enabled" />
+                        <span class="description">[ Enable accessibility features for the Grid ]</span>
+                    </div>
+                    <details>
+                        <summary>announcements</summary>
+                        <div class="option-group">
+                            <div class="option">
+                                <label for="sorting-enabled">sorting:</label>
+                                <input type="checkbox" id="sorting-enabled" name="accessibility.announcements.sorting" />
+                                <span class="description">[ Enable accessibility announcements for the sorting ]</span>
+                            </div>
+                            <div class="option">
+                                <label for="editing-enabled">cellEditing:</label>
+                                <input type="checkbox" id="editing-enabled" name="accessibility.announcements.cellEditing" />
+                                <span class="description">[ Enable accessibility announcements for the cell editing ]</span>
+                            </div>
+                        </div>
+                    </details>
+                </div>
+            </details>
+            <details>
+                <summary>lang.accessibility</summary>
+                <details>
+                    <summary>sorting</summary>
+                    <div class="option-group">
+                        <div class="option">
+                            <label for="sortable">sortable:</label>
+                            <input type="text" id="sortable" name="lang.accessibility.sorting.sortable"/>
+                            <span class="description">[ An additional hint read by the voice over after a sortable column name ]</span>
+                        </div>
+                    </div>
+                    <details>
+                        <summary>announcements</summary>
+                        <div class="option-group">
+                            <div class="option">
+                                <label for="sort-ascending">ascending:</label>
+                                <input type="text" id="sort-ascending" name="lang.accessibility.sorting.announcements.ascending" />
+                                <span class="description">[ Announcement for ascending sorting ]</span>
+                            </div>
+                            <div class="option">
+                                <label for="sort-descending">descending:</label>
+                                <input type="text" id="sort-descending" name="lang.accessibility.sorting.announcements.descending" />
+                                <span class="description">[ Announcement for descending sorting ]</span>
+                            </div>
+                            <div class="option">
+                                <label for="sort-none">none:</label>
+                                <input type="text" id="sort-none" name="lang.accessibility.sorting.announcements.none" />
+                                <span class="description">[ Announcement for no sorting ]</span>
+                            </div>
+                        </div>
+                    </details>
+                </details>
+                <details>
+                    <summary>cellEditing</summary>
+                    <div class="option-group">
+                        <div class="option">
+                            <label for="editable">editable:</label>
+                            <input type="text" id="editable" name="lang.accessibility.cellEditing.editable" />
+                            <span class="description">[ An additional hint read by the voice over after an editable cell value ]</span>
+                        </div>
+                    </div>
+                    <details>
+                        <summary>announcements</summary>
+                        <div class="option-group">
+                            <div class="option">
+                                <label for="editing-started">started:</label>
+                                <input type="text" id="editing-started" name="lang.accessibility.cellEditing.announcements.started" />
+                                <span class="description">[ Announcement for starting editing ]</span>
+                            </div>
+                            <div class="option">
+                                <label for="editing-edited">edited:</label>
+                                <input type="text" id="editing-edited" name="lang.accessibility.cellEditing.announcements.edited" />
+                                <span class="description">[ Announcement for edited cell value ]</span>
+                            </div>
+                            <div class="option">
+                                <label for="editing-cancelled">cancelled:</label>
+                                <input type="text" id="editing-cancelled" name="lang.accessibility.cellEditing.announcements.cancelled" />
+                                <span class="description">[ Announcement for cancelled editing ]</span>
+                            </div>
+                        </div>
+                    </details>
+                </details>
+            </details>
+        </form>
+        <div class="last description">Changes are applied as you type. Use a screen reader to observe updates in real time.</div>
+    </div>
+</div>
diff --git a/samples/grid-pro/demo/accessibility/demo.js b/samples/grid-pro/demo/accessibility/demo.js
new file mode 100644
index 00000000000..06a276b0f62
--- /dev/null
+++ b/samples/grid-pro/demo/accessibility/demo.js
@@ -0,0 +1,98 @@
+const grid = Grid.grid('container', {
+    dataTable: {
+        columns: {
+            id: ['1', '2', '3', '4'],
+            product: ['Apple', 'Pear', 'Plum', 'Banana'],
+            weight: [100, 60, 30, 200],
+            price: [1.5, 2.53, 5, 4.5]
+        }
+    },
+    rendering: {
+        rows: {
+            minVisibleRows: 4
+        }
+    },
+    columnDefaults: {
+        cells: {
+            editable: true
+        }
+    },
+    credits: {
+        text: 'Highcharts Grid'
+    },
+    caption: {
+        text: 'Fruits weight and price'
+    },
+    columns: [{
+        id: 'id',
+        header: {
+            format: 'ID'
+        },
+        cells: {
+            editable: false
+        }
+    }, {
+        id: 'product',
+        header: {
+            format: 'Product'
+        }
+    }, {
+        id: 'weight',
+        header: {
+            format: 'Weight'
+        },
+        cells: {
+            format: '{value} g'
+        }
+    }, {
+        id: 'price',
+        header: {
+            format: 'Price'
+        },
+        cells: {
+            format: '{value:.2f} €'
+        }
+    }]
+});
+
+const editorInputs = document.querySelectorAll('.editor input');
+
+function getOptionValue(path) {
+    let cursor = grid.options;
+    for (const key of path.split('.')) {
+        cursor = cursor[key];
+    }
+    return cursor;
+}
+
+function setOption(input) {
+    const result = {};
+    const path = input.name.split('.');
+    let cursor = result;
+
+    for (let i = 0, iEnd = path.length - 1; i < iEnd; i++) {
+        cursor[path[i]] = cursor = {};
+    }
+
+    cursor[path[path.length - 1]] =
+        input.type === 'checkbox' ? input.checked : input.value;
+
+    grid.update(result);
+}
+
+function setInputValue(input) {
+    const value = getOptionValue(input.name);
+    if (input.type === 'checkbox') {
+        input.checked = value;
+    } else {
+        input.value = value;
+    }
+}
+
+for (const input of editorInputs) {
+    setInputValue(input);
+
+    input.addEventListener('change', () => {
+        setOption(input);
+    });
+}
diff --git a/ts/Grid/Core/Accessibility/Accessibility.ts b/ts/Grid/Core/Accessibility/Accessibility.ts
index 9058f77018b..21dd325da99 100644
--- a/ts/Grid/Core/Accessibility/Accessibility.ts
+++ b/ts/Grid/Core/Accessibility/Accessibility.ts
@@ -276,6 +276,15 @@ class Accessibility {
 
         this.addHighContrast();
     }
+
+    /**
+     * Destroy the accessibility controller.
+     */
+    public destroy(): void {
+        this.element.remove();
+        this.announcerElement.remove();
+        clearTimeout(this.announcerTimeout);
+    }
 }
 
 
diff --git a/ts/Grid/Core/Grid.ts b/ts/Grid/Core/Grid.ts
index f84c95db1af..70f04f21906 100644
--- a/ts/Grid/Core/Grid.ts
+++ b/ts/Grid/Core/Grid.ts
@@ -329,12 +329,12 @@ class Grid {
      * Initializes the accessibility controller.
      */
     private initAccessibility(): void {
-        if (!this.options?.accessibility?.enabled) {
-            return;
-        }
+        this.accessibility?.destroy();
+        delete this.accessibility;
 
-        // Can be moved to a separate module in the future (if needed).
-        this.accessibility = new Accessibility(this);
+        if (this.options?.accessibility?.enabled) {
+            this.accessibility = new Accessibility(this);
+        }
     }
 
     /**
@@ -531,6 +531,7 @@ class Grid {
         oneToOne = false
     ): Promise<void> {
         this.loadUserOptions(options, oneToOne);
+        this.initAccessibility();
 
         let newDataTable = false;
         if (!this.dataTable || options.dataTable) {
diff --git a/ts/Grid/Pro/CellEditing/CellEditingComposition.ts b/ts/Grid/Pro/CellEditing/CellEditingComposition.ts
index 57f50a02a80..787a4e3bb85 100644
--- a/ts/Grid/Pro/CellEditing/CellEditingComposition.ts
+++ b/ts/Grid/Pro/CellEditing/CellEditingComposition.ts
@@ -164,6 +164,11 @@ namespace CellEditingComposition {
      * Add the 'editable' hint span element for the editable cell.
      */
     function addEditableCellA11yHint(this: TableCell): void {
+        const a11y = this.row.viewport.grid.accessibility;
+        if (!a11y) {
+            return;
+        }
+
         const editableLang = this.row.viewport.grid.options
             ?.lang?.accessibility?.cellEditing?.editable;
 
@@ -253,7 +258,7 @@ export interface CellEditingLangA11yOptions {
         /**
          * The message when the cell editing was cancelled.
          *
-         * @default 'Editing canceled.'
+         * @default 'Editing cancelled.'
          */
         cancelled?: string;
     }


Reviews:
user: login: karolkolodziej
body: Looks and works well. I would only expand all the options to show them how they change without taking any action. 
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-13T07:48:34Z
user: login: KamilKubik
body: Have you thought about changing the currency (probably values as well) in the i18n demo? 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T14:36:58Z
user: login: sebastianbochan
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-13T15:01:11Z
user: login: stitot
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-14T11:43:37Z
user: login: ddragula
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:17:47Z
user: login: ddragula
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-18T16:20:17Z
user: login: stitot
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T10:11:34Z

Review comments:
diff_hunk: @@ -0,0 +1,11 @@
+---
+ name: Highcharts Grid Demo
path: samples/grid-pro/demo/lang-and-accessibility/demo.details
user: login: karolkolodziej
body: Give a more minigfull name, like `Accessible grid` 
created_at: 2025-03-13T07:44:01Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,191 @@
+const grid = Grid.grid('container', {
+    dataTable: {
+        columns: {
+            id: ['1', '2', '3', '4'],
+            productEN: ['Apple', 'Pear', 'Plum', 'Banana'],
+            productNO: ['Eple', 'Pære', 'Plomme', 'Banan'],
+            productPL: ['Jabłko', 'Gruszka', 'Śliwka', 'Banan'],
+            productZH: ['苹果', '梨', '李子', '香蕉'],
+            weight: [100, 60, 30, 200],
+            price: [1.5, 2.53, 5, 4.5]
+        }
+    },
+    rendering: {
+        rows: {
+            minVisibleRows: 4
+        }
+    },
+    columns: [{
+        id: 'id',
+        header: {
+            format: 'ID'
+        }
+    }, {
+        id: 'weight',
+        cells: {
+            format: '{value} g'
+        }
+    }, {
+        id: 'price',
+        cells: {
+            format: '{value:.2f} €'
+        }
+    }]
+});
+
+const languages = {
+    en: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sortable.',
+                    announcements: {
+                        ascending: 'Sorted ascending.',
+                        descending: 'Sorted descending.',
+                        none: 'Not sorted.'
+                    }
+                }
+            },
+            decimalPoint: '.'
+        },
+        header: ['id', 'productEN', 'weight', 'price'],
+        columns: [{
+            id: 'productEN',
+            header: {
+                format: 'Product'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Weight'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Price'
+            }
+        }],
+        caption: {
+            text: 'Fruits Table - Internationalization Demo'
path: samples/grid-lite/demo/internationalization/demo.js
user: login: stitot
body: Let's not use the `caption` for demo name. Suggest we change `caption` to just "Fruits weight and price"
created_at: 2025-03-14T11:31:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,191 @@
+const grid = Grid.grid('container', {
+    dataTable: {
+        columns: {
+            id: ['1', '2', '3', '4'],
+            productEN: ['Apple', 'Pear', 'Plum', 'Banana'],
+            productNO: ['Eple', 'Pære', 'Plomme', 'Banan'],
+            productPL: ['Jabłko', 'Gruszka', 'Śliwka', 'Banan'],
+            productZH: ['苹果', '梨', '李子', '香蕉'],
+            weight: [100, 60, 30, 200],
+            price: [1.5, 2.53, 5, 4.5]
+        }
+    },
+    rendering: {
+        rows: {
+            minVisibleRows: 4
+        }
+    },
+    columns: [{
+        id: 'id',
+        header: {
+            format: 'ID'
+        }
+    }, {
+        id: 'weight',
+        cells: {
+            format: '{value} g'
+        }
+    }, {
+        id: 'price',
+        cells: {
+            format: '{value:.2f} €'
+        }
+    }]
+});
+
+const languages = {
+    en: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sortable.',
+                    announcements: {
+                        ascending: 'Sorted ascending.',
+                        descending: 'Sorted descending.',
+                        none: 'Not sorted.'
+                    }
+                }
+            },
+            decimalPoint: '.'
+        },
+        header: ['id', 'productEN', 'weight', 'price'],
+        columns: [{
+            id: 'productEN',
+            header: {
+                format: 'Product'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Weight'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Price'
+            }
+        }],
+        caption: {
+            text: 'Fruits Table - Internationalization Demo'
+        }
+    },
+    no: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sorterbar.',
+                    announcements: {
+                        ascending: 'Sortert stigende.',
+                        descending: 'Sortert synkende.',
+                        none: 'Ikke sortert.'
+                    }
+                }
+            },
+            decimalPoint: ','
+        },
+        header: ['id', 'productNO', 'weight', 'price'],
+        columns: [{
+            id: 'productNO',
+            header: {
+                format: 'Produkt'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Vekt'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Pris'
+            }
+        }],
+        caption: {
+            text: 'Frukttabell - Internasjonaliseringsdemo'
+        }
+    },
+    pl: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sortowalne.',
+                    announcements: {
+                        ascending: 'Posortowano rosnąco.',
+                        descending: 'Posortowano malejąco.',
+                        none: 'Nie posortowano.'
+                    }
+                }
+            },
+            decimalPoint: ','
+        },
+        header: ['id', 'productPL', 'weight', 'price'],
+        columns: [{
+            id: 'productPL',
+            header: {
+                format: 'Produkt'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Waga'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Cena'
+            }
+        }],
+        caption: {
+            text: 'Tabela Owoców - Demo internacjonalizacji'
+        }
+    },
+    zh: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: '可排序。',
+                    announcements: {
+                        ascending: '已按升序排序。',
+                        descending: '已按降序排序。',
+                        none: '未排序。'
+                    }
+                }
+            },
+            decimalPoint: '.'
+        },
+        header: ['id', 'productZH', 'weight', 'price'],
+        columns: [{
+            id: 'productZH',
+            header: {
+                format: '产品'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: '重量'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: '价格'
+            }
+        }],
+        caption: {
+            text: '水果表 - 国际化演示'
+        }
+    }
+};
+
+const languageSelect = document.getElementById('lang-select');
+
+function setLanguage(lang) {
+    grid.update(languages[lang]);
+    grid.container.setAttribute('lang', lang);
path: samples/grid-lite/demo/internationalization/demo.js
user: login: stitot
body: Is the short `en` a valid value here? Shouldn't it be e.g. `en-GB `?
created_at: 2025-03-14T11:32:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,16 @@
+<script src="https://code.highcharts.com/grid/grid-lite.js"></script>
+
+<div class="demo hcg-theme-default">
+    <div class="lang-options">
+        <label>
+            <span>Language:</span>
path: samples/grid-lite/demo/internationalization/demo.html
user: login: stitot
body: Perhaps we should translate this as well?
created_at: 2025-03-14T11:33:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,98 @@
+const grid = Grid.grid('container', {
+    dataTable: {
+        columns: {
+            id: ['1', '2', '3', '4'],
+            product: ['Apple', 'Pear', 'Plum', 'Banana'],
+            weight: [100, 60, 30, 200],
+            price: [1.5, 2.53, 5, 4.5]
+        }
+    },
+    rendering: {
+        rows: {
+            minVisibleRows: 4
+        }
+    },
+    columnDefaults: {
+        cells: {
+            editable: true
+        }
+    },
+    credits: {
+        text: 'Highcharts Grid'
+    },
+    caption: {
+        text: 'Fruits Table - Accessibility Demo'
path: samples/grid-pro/demo/accessibility/demo.js
user: login: stitot
body: Suggest we change to "Fruits weight and price"
created_at: 2025-03-14T11:39:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,98 @@
+<script src="https://code.highcharts.com/dashboards/datagrid.js"></script>
+
+<div class="demo hcg-theme-default">
+    <div id="container"></div>
+    <div class="editor">
+        <h2>Options Editor</h2>
path: samples/grid-pro/demo/accessibility/demo.html
user: login: stitot
body: Since there is no "Update" button we need to add a small description that values are changed as you type. And perhaps something about "use screen reader to observe changes"?
created_at: 2025-03-14T11:41:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,98 @@
+const grid = Grid.grid('container', {
path: samples/grid-pro/demo/accessibility/demo.js
user: login: stitot
body: IMO we should insert the full `lang` configuration here. If not it's difficult to get what values are actually configurable and where.
created_at: 2025-03-14T11:43:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,191 @@
+const grid = Grid.grid('container', {
+    dataTable: {
+        columns: {
+            id: ['1', '2', '3', '4'],
+            productEN: ['Apple', 'Pear', 'Plum', 'Banana'],
+            productNO: ['Eple', 'Pære', 'Plomme', 'Banan'],
+            productPL: ['Jabłko', 'Gruszka', 'Śliwka', 'Banan'],
+            productZH: ['苹果', '梨', '李子', '香蕉'],
+            weight: [100, 60, 30, 200],
+            price: [1.5, 2.53, 5, 4.5]
+        }
+    },
+    rendering: {
+        rows: {
+            minVisibleRows: 4
+        }
+    },
+    columns: [{
+        id: 'id',
+        header: {
+            format: 'ID'
+        }
+    }, {
+        id: 'weight',
+        cells: {
+            format: '{value} g'
+        }
+    }, {
+        id: 'price',
+        cells: {
+            format: '{value:.2f} €'
+        }
+    }]
+});
+
+const languages = {
+    en: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sortable.',
+                    announcements: {
+                        ascending: 'Sorted ascending.',
+                        descending: 'Sorted descending.',
+                        none: 'Not sorted.'
+                    }
+                }
+            },
+            decimalPoint: '.'
+        },
+        header: ['id', 'productEN', 'weight', 'price'],
+        columns: [{
+            id: 'productEN',
+            header: {
+                format: 'Product'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Weight'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Price'
+            }
+        }],
+        caption: {
+            text: 'Fruits Table - Internationalization Demo'
+        }
+    },
+    no: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sorterbar.',
+                    announcements: {
+                        ascending: 'Sortert stigende.',
+                        descending: 'Sortert synkende.',
+                        none: 'Ikke sortert.'
+                    }
+                }
+            },
+            decimalPoint: ','
+        },
+        header: ['id', 'productNO', 'weight', 'price'],
+        columns: [{
+            id: 'productNO',
+            header: {
+                format: 'Produkt'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Vekt'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Pris'
+            }
+        }],
+        caption: {
+            text: 'Frukttabell - Internasjonaliseringsdemo'
+        }
+    },
+    pl: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: 'Sortowalne.',
+                    announcements: {
+                        ascending: 'Posortowano rosnąco.',
+                        descending: 'Posortowano malejąco.',
+                        none: 'Nie posortowano.'
+                    }
+                }
+            },
+            decimalPoint: ','
+        },
+        header: ['id', 'productPL', 'weight', 'price'],
+        columns: [{
+            id: 'productPL',
+            header: {
+                format: 'Produkt'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: 'Waga'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: 'Cena'
+            }
+        }],
+        caption: {
+            text: 'Tabela Owoców - Demo internacjonalizacji'
+        }
+    },
+    zh: {
+        lang: {
+            accessibility: {
+                sorting: {
+                    sortable: '可排序。',
+                    announcements: {
+                        ascending: '已按升序排序。',
+                        descending: '已按降序排序。',
+                        none: '未排序。'
+                    }
+                }
+            },
+            decimalPoint: '.'
+        },
+        header: ['id', 'productZH', 'weight', 'price'],
+        columns: [{
+            id: 'productZH',
+            header: {
+                format: '产品'
+            }
+        }, {
+            id: 'weight',
+            header: {
+                format: '重量'
+            }
+        }, {
+            id: 'price',
+            header: {
+                format: '价格'
+            }
+        }],
+        caption: {
+            text: '水果表 - 国际化演示'
+        }
+    }
+};
+
+const languageSelect = document.getElementById('lang-select');
+
+function setLanguage(lang) {
+    grid.update(languages[lang]);
+    grid.container.setAttribute('lang', lang);
path: samples/grid-lite/demo/internationalization/demo.js
user: login: ddragula
body: There is usually no need to specify the exact language variant, and I think it is the same in this case.
created_at: 2025-03-18T16:17:46Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,98 @@
+const grid = Grid.grid('container', {
path: samples/grid-pro/demo/accessibility/demo.js
user: login: ddragula
body: We have several variants of the `lang` option object's content that can be changed and this is done by the `update` method. 
created_at: 2025-03-18T16:20:17Z
author_association: MEMBER

PR comments:
user: login: highsoft-bot
created_at: 2025-03-12T17:29:30Z
body: ### File size comparison
Sizes for compiled+gzipped (bold) and compiled files.
| | master | candidate | difference |
|-------------|-------------:|-------------:|-------------:|
| dashboards/dashboards.js | **47.9 kB**<br>164.0 kB | **47.9 kB**<br>164.1 kB | **93 B**<br>79 B |
| dashboards/modules/layout.js | **13.3 kB**<br>52.1 kB | **13.3 kB**<br>52.0 kB | **1 B**<br>-16 B |
| datagrid/datagrid.js | **34.4 kB**<br>112.1 kB | **35.4 kB**<br>114.7 kB | **995 B**<br>2638 B |
author_association: COLLABORATOR
user: login: highsoft-bot
created_at: 2025-03-12T17:30:49Z
body: ## Lighthouse report
### dashboards-demo-minimal.json
|   | Reference | Proposed | Diff |
| :---- | ------ | ----- | ----- |
| performance – score | 1 | 1 | 0.00 |
| first-contentful-paint – score | 1 | 1 | 0.00 |
| first-contentful-paint – milliseconds | 191.26 | 189.33 | -1.93 |
| first-meaningful-paint – score | 1 | 1 | 0.00 |
| first-meaningful-paint – milliseconds | 479.26 | 486.15 | 6.89 |
| dom-size – score | 1 | 1 | 0.00 |
| dom-size – elements | 350 | 350 | 0.00 |

author_association: COLLABORATOR
user: login: highsoft-bot
created_at: 2025-03-12T17:31:01Z
body: ## Dashboard visual diffs
No differences found
author_association: COLLABORATOR
user: login: highsoft-bot
created_at: 2025-03-12T17:33:32Z
body: ## Visual test results - No difference found

---
<details>
<summary>Samples changed</summary><p>

| Change type | Sample |
| --- | --- |


</p>
</details>

author_association: COLLABORATOR
user: login: ddragula
created_at: 2025-03-13T07:29:12Z
body: It would be ideal to add the ability to switch between the high contrast theme and the default one to this demo, but that will be when it's ready.
author_association: MEMBER
user: login: sebastianbochan
created_at: 2025-03-13T12:17:17Z
body: Honestly, I'm not convinced about "design", it's much more dedicated to "developers" than end-user.
author_association: CONTRIBUTOR
user: login: ddragula
created_at: 2025-03-13T15:19:11Z
body: >Have you thought about changing the currency (probably values as well) in the i18n demo?

Yep, but I would do it the same way as changing the product name (there is no point in changing the unit with the formatter, because the exchange rate is different) - it is not drastically important, and we know that the simpler the demos, the better. Let's assume the price is in euros for everyone. 😄 
author_association: MEMBER