Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
95ff5eb
dbeaver/pro#7855 feat: Add setting to use the user's OS formatting fo…
SychevAndrey Dec 24, 2025
880ff06
dbeaver/pro#7855 refactor: update OS formatting option value in DataG…
SychevAndrey Dec 25, 2025
507f577
dbeaver/pro#7855 feat: implement OS formatting for date and time in D…
SychevAndrey Dec 25, 2025
cf92bab
dbeaver/pro#7855 feat: implement user-specific formatting for numbers…
SychevAndrey Dec 25, 2025
96a044d
dbeaver/pro#7855 feat: add translations
SychevAndrey Dec 25, 2025
94f5493
dbeaver/pro#7855 style: update date formatter
SychevAndrey Dec 25, 2025
48b74d8
dbeaver/pro#7855 feat: add locale-specific formatting options for Dat…
SychevAndrey Dec 25, 2025
fa54ba5
dbeaver/pro#7855 feat: add support for locale-specific formatting wit…
SychevAndrey Dec 26, 2025
efdac7c
dbeaver/pro#7855 refactor: move date type detection to the grid level
SychevAndrey Dec 29, 2025
27240c5
dbeaver/pro#7855 refactor: simplify cell formatter selection logic
SychevAndrey Jan 5, 2026
f391586
Merge branch 'devel' into 7855-cb-add-setting---use-the-users-os-form…
SychevAndrey Jan 5, 2026
9902a6e
Merge branch '7855-cb-add-setting---use-the-users-os-formatting-for-n…
SychevAndrey Jan 5, 2026
543c687
dbeaver/pro#7855 refactor: move formatting to a separate context
SychevAndrey Jan 5, 2026
01b1054
dbeaver/pro#7855 refactor: optimize extended date kind retrieval logic
SychevAndrey Jan 6, 2026
7ce2168
Merge branch 'devel' into 7855-cb-add-setting---use-the-users-os-form…
SychevAndrey Jan 6, 2026
bf3dc5e
dbeaver/pro#7855 fix: tw class names
SychevAndrey Jan 6, 2026
af2257b
Merge branch 'devel' into 7855-cb-add-setting---use-the-users-os-form…
SychevAndrey Jan 8, 2026
c3694e7
Merge branch 'devel' into 7855-cb-add-setting---use-the-users-os-form…
SychevAndrey Jan 8, 2026
3966098
dbeaver/pro#7855 refactor: change caching mechanism and other improve…
SychevAndrey Jan 9, 2026
167de1e
Merge branch 'devel' into 7855-cb-add-setting---use-the-users-os-form…
mr-anton-t Jan 9, 2026
7c82e1a
Merge branch 'devel' into 7855-cb-add-setting---use-the-users-os-form…
mr-anton-t Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { BooleanFormatter } from './CellFormatters/BooleanFormatter.js';
import { TextFormatter } from './CellFormatters/TextFormatter.js';
import type { ICellFormatterProps } from './ICellFormatterProps.js';
import { IndexFormatter } from './IndexFormatter.js';
import { DateTimeFormatter } from './CellFormatters/DateTimeFormatter.js';
import { NumberFormatter } from './CellFormatters/NumberFormatter.js';

export const CellFormatterFactory = observer<ICellFormatterProps>(function CellFormatterFactory(props) {
const formatterRef = useRef<React.FC<ICellFormatterProps> | null>(null);
Expand All @@ -38,6 +40,14 @@ export const CellFormatterFactory = observer<ICellFormatterProps>(function CellF
if (resultColumn && isBooleanValuePresentationAvailable(holder.value, resultColumn)) {
formatterRef.current = BooleanFormatter;
}

const dataKind = resultColumn?.dataKind?.toUpperCase();
if (dataKind === 'DATETIME' && tableDataContext.useUserFormatting) {
formatterRef.current = DateTimeFormatter;
}
if (dataKind === 'NUMERIC' && tableDataContext.useUserFormatting) {
formatterRef.current = NumberFormatter;
}
}
} else {
formatterRef.current = IndexFormatter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

.dateFormatter {
display: flex;
align-items: center;
overflow: hidden;
}

.dateFormatterValue {
overflow: hidden;
text-overflow: ellipsis;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';

import { getComputed, s, useS } from '@cloudbeaver/core-blocks';
import { NullFormatter as GridNullFormatter } from '@cloudbeaver/plugin-data-grid';

import { CellContext } from '../../CellRenderer/CellContext.js';
import { TableDataContext } from '../../TableDataContext.js';
import styles from './DateTimeFormatter.module.css';
import type { ICellFormatterProps } from '../ICellFormatterProps.js';

export const DateTimeFormatter = observer<ICellFormatterProps>(function DateTimeFormatter() {
const tableDataContext = useContext(TableDataContext);
const cellContext = useContext(CellContext);
const style = useS(styles);

if (!cellContext.cell) {
return null;
}

const formatter = tableDataContext.format;
const valueHolder = getComputed(() => formatter.get(cellContext.cell!));
const nullValue = getComputed(() => formatter.isNull(valueHolder));
const displayValue = getComputed(() => formatter.getDisplayString(valueHolder));

if (nullValue) {
return <GridNullFormatter />;
}

let value = displayValue;

if (tableDataContext.useUserFormatting) {
const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(displayValue);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(displayValue);
Is it really required? This will hit performance in the grid noticeably, at least create a regex outside of the render function.
new Date() should be able to parse a date correctly without time. Maybe you can simplify the check by checking the resulting Date object (probably it will have some constant time)

(or just use unified formatter)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it's probably enough to check this format only once for one of the cells because they all will have the same format, or maybe you can check it by column type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we can put it on the data context level and set something like "extended datakind" for every column with TIME/DATE/DATETIME values. In the cell we will just get the datakind of the column and use the according formatter. Do you want me refactor like that?

Image

Unfortunately, we can't rely on new Date to check the shape, so testing by regex or splitting is required. However, if we do it only on the TableDataContext level it should be not a problem.

const date = new Date(displayValue);

if (!isNaN(date.getTime())) {
let formatter = tableDataContext.useUserFormatting.dateTime;

if (isDateOnly) {
formatter = tableDataContext.useUserFormatting.dateOnly;
}

value = formatter.format(date);
}
}

return (
<div className={s(style, { dateFormatter: true })}>
<div className={s(style, { dateFormatterValue: true })}>{value}</div>
</div>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

.numberFormatter {
display: flex;
align-items: center;
overflow: hidden;
}

.numberFormatterValue {
overflow: hidden;
text-overflow: ellipsis;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';

import { getComputed, s, useS } from '@cloudbeaver/core-blocks';
import { NullFormatter as GridNullFormatter } from '@cloudbeaver/plugin-data-grid';

import { CellContext } from '../../CellRenderer/CellContext.js';
import { TableDataContext } from '../../TableDataContext.js';
import type { ICellFormatterProps } from '../ICellFormatterProps.js';

import styles from './NumberFormatter.module.css';

export const NumberFormatter = observer<ICellFormatterProps>(function NumberFormatter() {
const tableDataContext = useContext(TableDataContext);
const cellContext = useContext(CellContext);
const style = useS(styles);

if (!cellContext.cell) {
return null;
}

const formatter = tableDataContext.format;
const valueHolder = getComputed(() => formatter.get(cellContext.cell!));
const nullValue = getComputed(() => formatter.isNull(valueHolder));
const displayValue = getComputed(() => formatter.getDisplayString(valueHolder));

if (nullValue) {
return <GridNullFormatter />;
}

let value = displayValue;

if (tableDataContext.useUserFormatting) {
const numberValue = Number(displayValue);

if (!isNaN(numberValue) && displayValue.trim() !== '') {
value = tableDataContext.useUserFormatting.number.format(numberValue);
}
}

return (
<div className={s(style, { numberFormatter: true })}>
<div className={s(style, { numberFormatterValue: true })}>{value}</div>
</div>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export interface IColumnInfo {
key: IGridColumnKey | null;
}

export interface IDataGridFormatters {
locale: string;
dateTime: Intl.DateTimeFormat;
dateOnly: Intl.DateTimeFormat;
number: Intl.NumberFormat;
}

export interface ITableData {
formatting: GridConditionalFormattingAction;
format: IDatabaseDataFormatAction<Partial<IGridDataKey>, IResultSetValue, IDatabaseResultSet>;
Expand All @@ -54,6 +61,7 @@ export interface ITableData {
isIndexColumn: (columnKey: IColumnInfo) => boolean;
isIndexColumnInRange: (columnsRange: Array<IColumnInfo>) => boolean;
isCellReadonly: (key: IGridDataKey) => boolean;
useUserFormatting: IDataGridFormatters | null;
}

export function isColumnInfo(column: IColumnInfo | undefined): column is IColumnInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import {
type IGridDataKey,
} from '@cloudbeaver/plugin-data-viewer';

import type { IColumnInfo, ITableData } from './TableDataContext.js';
import type { IColumnInfo, ITableData, IDataGridFormatters } from './TableDataContext.js';
import { useService } from '@cloudbeaver/core-di';
import { DataGridSettingsService } from '../DataGridSettingsService.js';
import { DataGridSettingsService, NO_FORMAT, OS_FORMAT } from '../DataGridSettingsService.js';
import type { SqlResultColumn } from '@cloudbeaver/core-sdk';
import { GridConditionalFormattingAction } from '@cloudbeaver/plugin-data-viewer-conditional-formatting';

Expand Down Expand Up @@ -83,6 +83,33 @@ export function useTableData(
// TODO: fix column abstraction
return Boolean(this.data?.columns?.some(column => (column as SqlResultColumn).description));
},
get useUserFormatting(): IDataGridFormatters | null {
const setting = this.dataGridSettingsService.useUserFormatting;

if (setting === NO_FORMAT) {
return null;
}

const locale = setting === OS_FORMAT ? new Intl.DateTimeFormat().resolvedOptions().locale : setting;
return {
locale,
dateTime: new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
}),
dateOnly: new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
timeZone: 'UTC',
}),
number: new Intl.NumberFormat(locale),
};
},
getRow(rowIndex) {
return this.rows[rowIndex];
},
Expand Down Expand Up @@ -146,6 +173,7 @@ export function useTableData(
rows: computed,
columnKeys: computed,
hasDescription: computed,
useUserFormatting: computed,
formatting: observable.ref,
format: observable.ref,
dataContent: observable.ref,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ import {
import { schema, schemaExtra } from '@cloudbeaver/core-utils';
import { DATA_EDITOR_SETTINGS_GROUP } from '@cloudbeaver/plugin-data-viewer';

export const NO_FORMAT = 'default';
export const OS_FORMAT = '_OS';

const defaultSettings = schema.object({
'plugin.data-spreadsheet.hidden': schemaExtra.stringedBoolean().default(false),
'plugin.data-spreadsheet.showDescriptionInHeader': schemaExtra.stringedBoolean().default(true),
'plugin.data-spreadsheet.useUserFormatting': schema.string().default(NO_FORMAT),
});

export type DataGridSettingsSchema = typeof defaultSettings;
Expand All @@ -36,7 +40,12 @@ export class DataGridSettingsService {
return this.settings.getValue('plugin.data-spreadsheet.showDescriptionInHeader');
}

get useUserFormatting(): string {
return this.settings.getValue('plugin.data-spreadsheet.useUserFormatting');
}

readonly settings: SettingsProvider<DataGridSettingsSchema>;
readonly supportedLocales = this.getSupportedLocales();

constructor(
private readonly settingsProviderService: SettingsProviderService,
Expand All @@ -55,6 +64,33 @@ export class DataGridSettingsService {
this.registerSettings();
}

private getSupportedLocales() {
const codes = [];
const letters = 'abcdefghijklmnopqrstuvwxyz';

function isLanguageCodeSupported(code: string | Intl.Locale) {
const locale = new Intl.Locale(code);
return locale.maximize().region !== undefined;
}

for (let i = 0; i < letters.length; i++) {
for (let j = 0; j < letters.length; j++) {
const code = letters[i]! + letters[j]!;
if (isLanguageCodeSupported(code)) {
codes.push(code);
}
}
}

return codes;
}

private getLocaleName(localeCode: string) {
const languageNameInItsOwnLang = new Intl.DisplayNames(localeCode, { type: 'language' });
const name = languageNameInItsOwnLang.of(localeCode);
return name && name?.length > 2 ? name.slice(0, 1).toLocaleUpperCase() + name.slice(1) : localeCode;
}

private registerSettings() {
this.settingsManagerService.registerSettings<typeof defaultSettings>(() => [
{
Expand All @@ -77,6 +113,24 @@ export class DataGridSettingsService {
name: 'plugin_data_spreadsheet_new_settings_description_label',
description: 'plugin_data_spreadsheet_new_settings_description_label_description',
},
{
group: DATA_EDITOR_SETTINGS_GROUP,
key: 'plugin.data-spreadsheet.useUserFormatting',
access: {
scope: ['client'],
},
type: ESettingsValueType.Select,
options: [
{ value: NO_FORMAT, name: 'plugin_data_spreadsheet_new_settings_use_locale_formatting_none' },
{ value: OS_FORMAT, name: 'plugin_data_spreadsheet_new_settings_use_locale_formatting_os' },
...this.supportedLocales
.map(locale => ({ value: locale, name: this.getLocaleName(locale) }))
.filter(locale => locale.name.length > 2)
.sort((a, b) => a.name.localeCompare(b.name)),
],
name: 'plugin_data_spreadsheet_new_settings_use_locale_formatting_title',
description: 'plugin_data_spreadsheet_new_settings_use_locale_formatting_description',
},
]);
}
}
7 changes: 7 additions & 0 deletions webapp/packages/plugin-data-spreadsheet-new/src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ export default [
['plugin_data_spreadsheet_new_settings_disable_description', 'Deaktivieren Sie die Tabellenpräsentation von Daten für alle Benutzer'],
['plugin_data_spreadsheet_new_settings_description_label', 'Show columns description'],
['plugin_data_spreadsheet_new_settings_description_label_description', 'Description will be shown under the column names in the table header'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_title', 'Gebietsschema-Formatierung verwenden'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_os', 'OS-Formatierung verwenden'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_none', 'Keine'],
[
'plugin_data_spreadsheet_new_settings_use_locale_formatting_description',
'Verwenden Sie die Formatierung für Zahlen und Daten entsprechend dem ausgewählten Gebietsschema. (!) Dies betrifft nur die Datendarstellung; für die Bearbeitung sollten Rohwerte verwendet werden.',
],
];
7 changes: 7 additions & 0 deletions webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,11 @@ export default [
['plugin_data_spreadsheet_new_settings_disable_description', 'Disable table presentation of data for all users'],
['plugin_data_spreadsheet_new_settings_description_label', 'Show columns description'],
['plugin_data_spreadsheet_new_settings_description_label_description', 'Description will be shown under the column names in the table header'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_title', 'Use locale formatting'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_os', 'Use OS formatting'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_none', 'None'],
[
'plugin_data_spreadsheet_new_settings_use_locale_formatting_description',
'Use formatting for numbers and dates according to the selected locale. (!)This only affects data representation; raw values should be used for editing.',
],
];
7 changes: 7 additions & 0 deletions webapp/packages/plugin-data-spreadsheet-new/src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,11 @@ export default [
['plugin_data_spreadsheet_new_settings_disable', 'Désactiver la présentation de la table'],
['plugin_data_spreadsheet_new_settings_description_label', 'Show columns description'],
['plugin_data_spreadsheet_new_settings_description_label_description', 'Description will be shown under the column names in the table header'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_title', 'Utiliser le formatage régional'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_os', "Utiliser le formatage du système d'exploitation"],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_none', 'Aucun'],
[
'plugin_data_spreadsheet_new_settings_use_locale_formatting_description',
"Utilisez le formatage pour les nombres et les dates selon les paramètres régionaux sélectionnés. (!) Cela n'affecte que la représentation des données ; les valeurs brutes doivent être utilisées pour l'édition.",
],
];
7 changes: 7 additions & 0 deletions webapp/packages/plugin-data-spreadsheet-new/src/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,11 @@ export default [
['plugin_data_spreadsheet_new_settings_disable', 'Disable Table presentation'],
['plugin_data_spreadsheet_new_settings_description_label', 'Show columns description'],
['plugin_data_spreadsheet_new_settings_description_label_description', 'Description will be shown under the column names in the table header'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_title', 'Usa formattazione locale'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_os', 'Usa formattazione del sistema operativo'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_none', 'Nessuno'],
[
'plugin_data_spreadsheet_new_settings_use_locale_formatting_description',
'Usa la formattazione per numeri e date in base alla locale selezionata. (!) Questo influisce solo sulla rappresentazione dei dati; i valori grezzi devono essere usati per la modifica.',
],
];
7 changes: 7 additions & 0 deletions webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,11 @@ export default [
['plugin_data_spreadsheet_new_settings_disable_description', 'Отключить табличное представление данных для всех пользователей'],
['plugin_data_spreadsheet_new_settings_description_label', 'Показать описание колонки'],
['plugin_data_spreadsheet_new_settings_description_label_description', 'Описание будет показано под именами колонок в заголовке таблицы'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_title', 'Использовать локальное форматирование'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_os', 'Использовать системное форматирование'],
['plugin_data_spreadsheet_new_settings_use_locale_formatting_none', 'Нет'],
[
'plugin_data_spreadsheet_new_settings_use_locale_formatting_description',
'Использовать форматирование для чисел и дат в соответствии с выбранной локалью. (!) Это влияет только на представление данных; для редактирования должны использоваться исходные значения.',
],
];
Loading
Loading