diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/DataEntry.tsx b/specifyweb/frontend/js_src/lib/components/Atoms/DataEntry.tsx index 1b8db504a00..25526a789fd 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/DataEntry.tsx +++ b/specifyweb/frontend/js_src/lib/components/Atoms/DataEntry.tsx @@ -4,10 +4,12 @@ import type { LocalizedString } from 'typesafe-i18n'; import { commonText } from '../../localization/common'; import { formsText } from '../../localization/forms'; import type { RA } from '../../utils/types'; +import { localized } from '../../utils/types'; import type { AnySchema } from '../DataModel/helperTypes'; import type { SpecifyResource } from '../DataModel/legacyTypes'; import type { ViewDescription } from '../FormParse'; import type { cellAlign, cellVerticalAlign } from '../FormParse/cells'; +import { userPreferences } from '../Preferences/userPreferences'; import { Button } from './Button'; import { className } from './className'; import type { icons } from './Icons'; @@ -19,23 +21,24 @@ const dataEntryButton = ( className: string, title: LocalizedString, icon: keyof typeof icons -) => - function ( +) => { + const component = ( props: Omit, 'children' | 'type'> & { readonly onClick: | ((event: React.MouseEvent) => void) | undefined; } - ): JSX.Element { - return ( - - ); - }; + ): JSX.Element => ( + + ); + Object.defineProperty(component, 'name', { value: icon }); + return component; +}; export const columnDefinitionsToCss = ( columns: RA, @@ -54,6 +57,12 @@ export const columnDefinitionsToCss = ( * This is called DataEntry instead of Form because "Form" is already taken */ +const DataEntryAdd = dataEntryButton( + className.dataEntryAdd, + commonText.add(), + 'plus' +); + export const DataEntry = { Grid: wrap< 'div', @@ -145,7 +154,29 @@ export const DataEntry = { }) ), SubFormTitle: wrap('DataEntry.SubFormTitle', 'h3', className.formTitle), - Add: dataEntryButton(className.dataEntryAdd, commonText.add(), 'plus'), + Add({ + enableShortcut, + onClick: handleClick, + title = commonText.add(), + ...rest + }: Omit[0], 'onClick'> & { + readonly onClick: (() => void) | undefined; + readonly enableShortcut: boolean; + }): JSX.Element { + const addButtonShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'recordSet', + 'addResource', + enableShortcut && rest.disabled !== true ? handleClick : undefined + ); + return ( + + ); + }, View: dataEntryButton(className.dataEntryView, commonText.view(), 'eye'), Edit: dataEntryButton(className.dataEntryEdit, commonText.edit(), 'pencil'), Clone: dataEntryButton( @@ -170,14 +201,21 @@ export const DataEntry = { readonly className?: string; readonly resource: SpecifyResource | undefined; }): JSX.Element | null { + const ref = React.useRef(null); + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'queryComboBox', + 'openRelatedRecordInNewTab', + resource === undefined ? undefined : (): void => ref.current?.click() + ); return typeof resource === 'object' && !resource.isNew() ? ( ) : null; }, }; -/* eslint-enable @typescript-eslint/naming-convention */ diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx b/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx index 8a7eddee8ab..2e667bf441c 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx +++ b/specifyweb/frontend/js_src/lib/components/Atoms/Icons.tsx @@ -74,6 +74,7 @@ export const icons = { collection: , cube: , cubeTransparent: , + cursorClick: , database: , document: , documentReport: , @@ -85,13 +86,10 @@ export const icons = { exclamationCircle: , externalLink: , eye: , + filter: , fingerPrint: , - gallery: - -, - globe: - -, + gallery:, + globe: , hashtag: , // This icon is not from Heroicons. It was drawn by @grantfitzsimmons history: , diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/Link.tsx b/specifyweb/frontend/js_src/lib/components/Atoms/Link.tsx index 9497a48c842..76d5840ae83 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/Link.tsx +++ b/specifyweb/frontend/js_src/lib/components/Atoms/Link.tsx @@ -42,7 +42,12 @@ export const Link = { children: ( <> {props.children} - + {commonText.opensInNewTab()} {icons.externalLink} diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/DataEntry.test.ts b/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/DataEntry.test.ts index 9ecd6142915..a642b17faf0 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/DataEntry.test.ts +++ b/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/DataEntry.test.ts @@ -68,7 +68,7 @@ snapshot(DataEntry.Footer, { children: 'Test' }); snapshot(DataEntry.SubForm, { children: 'Test' }); snapshot(DataEntry.SubFormHeader, { children: 'Test' }); snapshot(DataEntry.SubFormTitle, { children: 'Test' }); -snapshot(DataEntry.Add, { onClick: f.never }); +snapshot(DataEntry.Add, { onClick: f.never, enableShortcut: true }); snapshot(DataEntry.View, { onClick: f.never }); snapshot(DataEntry.Edit, { onClick: f.never }); snapshot(DataEntry.Clone, { onClick: f.never }); diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/DataEntry.test.ts.snap b/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/DataEntry.test.ts.snap index 51fd74180ec..3bd1a1b0408 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/DataEntry.test.ts.snap +++ b/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/DataEntry.test.ts.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[` renders without errors 1`] = ` +exports[`Add renders without errors 1`] = ` + +`; + +exports[`eye renders without errors 1`] = ` + + + +`; + +exports[`minus renders without errors 1`] = ` + + + +`; + +exports[`pencil renders without errors 1`] = ` + + + +`; + +exports[`search renders without errors 1`] = ` + + + +`; diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/index.test.tsx.snap b/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/index.test.tsx.snap index bce11478917..89cca86fe31 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/index.test.tsx.snap +++ b/specifyweb/frontend/js_src/lib/components/Atoms/__tests__/__snapshots__/index.test.tsx.snap @@ -91,7 +91,7 @@ exports[`H3 renders without errors 1`] = ` exports[`Key renders without errors 1`] = ` View diff --git a/specifyweb/frontend/js_src/lib/components/Atoms/index.tsx b/specifyweb/frontend/js_src/lib/components/Atoms/index.tsx index 805ddb91797..5dd9336c596 100644 --- a/specifyweb/frontend/js_src/lib/components/Atoms/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/Atoms/index.tsx @@ -91,7 +91,7 @@ export const Summary = wrap< export const Key = wrap( 'Key', 'kbd', - 'bg-gray-200 border-1 dark:border-none dark:bg-neutral-700 rounded-sm mx-1 p-0.5' + 'bg-gray-200 border-1 dark:border-none dark:bg-neutral-700 rounded-sm mx-1 p-0.5 text-xl' ); const defaultOneRem = 16; diff --git a/specifyweb/frontend/js_src/lib/components/FormCells/COJODialog.tsx b/specifyweb/frontend/js_src/lib/components/FormCells/COJODialog.tsx index 698b347b4e3..00daccd2708 100644 --- a/specifyweb/frontend/js_src/lib/components/FormCells/COJODialog.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormCells/COJODialog.tsx @@ -86,7 +86,7 @@ export function COJODialog({ return ( <> - + {isOpen && ( {table.label} { setState('Add'); setResourceTable(table); diff --git a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx index ffd0a716fa8..475b58a7e93 100644 --- a/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormCells/FormTable.tsx @@ -507,6 +507,7 @@ export function FormTable({ {isExpanded[resource.cid] === true && ( @@ -547,6 +548,7 @@ export function FormTable({ ) : undefined} {hasTablePermission(relationship.relatedTable.name, 'create') ? ( { const resource = new relationship.relatedTable.Resource(); handleAddResources([resource]); diff --git a/specifyweb/frontend/js_src/lib/components/FormMeta/index.tsx b/specifyweb/frontend/js_src/lib/components/FormMeta/index.tsx index b7f5d50a3cc..2bca0e45890 100644 --- a/specifyweb/frontend/js_src/lib/components/FormMeta/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormMeta/index.tsx @@ -29,6 +29,7 @@ import { ProtectedAction, ProtectedTool, } from '../Permissions/PermissionDenied'; +import { userPreferences } from '../Preferences/userPreferences'; import { canMerge } from '../QueryBuilder/Results'; import { UnloadProtectsContext } from '../Router/UnloadProtect'; import { AutoNumbering } from './AutoNumbering'; @@ -44,22 +45,32 @@ import { ShareRecord } from './ShareRecord'; import { SubViewMeta } from './SubViewMeta'; /** - * Form preferences host context aware user preferences and other meta-actions. + * Form preferences, context aware user preferences, and other meta-actions. * List of available features: https://github.com/specify/specify7/issues/1330 */ export function FormMeta({ resource, className, viewDescription, + enableKeyboardShortcut, }: { readonly resource: SpecifyResource | undefined; readonly className?: string; readonly viewDescription: ViewDescription | undefined; + readonly enableKeyboardShortcut: boolean; }): JSX.Element | null { const [isOpen, _, handleClose, handleToggle] = useBooleanState(); const [isReadOnly = false] = useCachedState('forms', 'readOnlyMode'); const subView = React.useContext(SubViewContext); const isInFormEditor = React.useContext(InFormEditorContext); + + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'actions', + 'openFormMeta', + enableKeyboardShortcut && resource !== undefined ? handleToggle : undefined + ); + return isInFormEditor && typeof viewDescription === 'object' ? ( ) : typeof resource === 'object' ? ( @@ -67,7 +78,7 @@ export function FormMeta({ {icons.cog} diff --git a/specifyweb/frontend/js_src/lib/components/FormPlugins/CollectionRelOneToMany.tsx b/specifyweb/frontend/js_src/lib/components/FormPlugins/CollectionRelOneToMany.tsx index 81da6994c87..8fe7f2ef350 100644 --- a/specifyweb/frontend/js_src/lib/components/FormPlugins/CollectionRelOneToMany.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormPlugins/CollectionRelOneToMany.tsx @@ -164,6 +164,7 @@ export function CollectionOneToManyPlugin({ typeof data === 'object' ? ( setState( state.type === 'SearchState' diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx index 08a0e65e2f7..f6f2afc0397 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/IntegratedRecordSelector.tsx @@ -53,7 +53,7 @@ export function IntegratedRecordSelector({ ...rest }: Omit< Parameters[0], - 'children' | 'onSlide' | 'table' + 'children' | 'enableKeyboardShortcuts' | 'onSlide' | 'table' > & { readonly dialog: 'modal' | 'nonModal' | false; readonly formType: FormType; @@ -176,6 +176,7 @@ export function IntegratedRecordSelector({ { if (isInteraction) { @@ -302,6 +303,7 @@ export function IntegratedRecordSelector({ handleAdd, }); }} + enableShortcut={dialog !== false} /> ) ) : undefined} diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx index e013ec223a0..865977bd96f 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSelector.tsx @@ -34,6 +34,7 @@ export type RecordSelectorProps = { | ((newIndex: number, replace: boolean, callback?: () => void) => void) | undefined; readonly isCollapsed?: boolean; + readonly enableKeyboardShortcuts: boolean; }; export type RecordSelectorState = { @@ -70,6 +71,7 @@ export function useRecordSelector({ index, onSlide: handleSlide, totalCount, + enableKeyboardShortcuts, }: RecordSelectorProps & { // Total number of elements in the collection readonly totalCount: number; @@ -116,6 +118,7 @@ export function useRecordSelector({ slider: ( ({ onFetch: handleFetch, hasSeveralResourceType, ...rest -}: Omit, 'index' | 'records'> & { +}: Omit< + RecordSelectorProps, + 'enableKeyboardShortcuts' | 'index' | 'records' +> & { /* * Undefined IDs are placeholders for items with unknown IDs (e.g in record * sets or query results with thousands of items) @@ -125,6 +128,7 @@ export function RecordSelectorFromIds({ isLoading, } = useRecordSelector({ ...rest, + enableKeyboardShortcuts: true, index, table, records: @@ -208,6 +212,7 @@ export function RecordSelectorFromIds({ { const resource = new table.Resource(); diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx index f6b2375301b..f944e22847f 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx @@ -181,6 +181,7 @@ function RecordSet({ }: Omit< RecordSelectorProps, | 'defaultIndex' + | 'enableKeyboardShortcuts' | 'field' | 'index' | 'onDelete' diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/Slider.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/Slider.tsx index 6b5d9d90705..73d7c84793c 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/Slider.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/Slider.tsx @@ -5,15 +5,21 @@ import { clamp } from '../../utils/utils'; import { Button } from '../Atoms/Button'; import { Input } from '../Atoms/Form'; import { icons } from '../Atoms/Icons'; +import { userPreferences } from '../Preferences/userPreferences'; export function Slider({ value, count, onChange: handleChange, + enableKeyboardShortcuts, }: { readonly value: number; readonly count: number; readonly onChange: ((newValue: number) => void) | undefined; + /** + * If true, keyboard shortcuts will be enabled for this slider + */ + readonly enableKeyboardShortcuts: boolean; }): JSX.Element | null { const [pendingValue, setPendingValue] = React.useState(value); const inputRef = React.useRef(null); @@ -26,22 +32,63 @@ export function Slider({ ); const max = Math.max(1, count); const resolvedValue = Number.isNaN(pendingValue) ? '' : pendingValue + 1; + + const goToFirstRecord = + value === 0 || handleChange === undefined + ? undefined + : (): void => handleChange?.(0); + const goToPreviousRecord = + value === 0 || handleChange === undefined + ? undefined + : (): void => handleChange(value - 1); + const goToNextRecord = + value + 1 === count || handleChange === undefined + ? undefined + : (): void => handleChange?.(value + 1); + const goToLastRecord = + value + 1 === count || handleChange === undefined + ? undefined + : (): void => handleChange?.(count - 1); + + const goToFirstRecordShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'recordSet', + 'goToFirstRecord', + enableKeyboardShortcuts ? goToFirstRecord : undefined + ); + const goToPreviousRecordShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'recordSet', + 'goToPreviousRecord', + enableKeyboardShortcuts ? goToPreviousRecord : undefined + ); + const goToNextRecordShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'recordSet', + 'goToNextRecord', + enableKeyboardShortcuts ? goToNextRecord : undefined + ); + const goToLastRecordShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'recordSet', + 'goToLastRecord', + enableKeyboardShortcuts ? goToLastRecord : undefined + ); + return count > 0 ? ( ); } export function SearchForm({ formId, + autoFocus, }: { readonly formId: string; + readonly autoFocus: boolean; }): JSX.Element { const navigate = useNavigate(); const [query = ''] = useSearchParameter('q'); @@ -75,28 +77,31 @@ export function SearchForm({ navigate(formatUrl('/specify/express-search/', { q: pendingQuery })) } > - + ); } function SearchField({ value: [value, setValue], + autoFocus, }: { readonly value: GetSet; + readonly autoFocus: boolean; }): JSX.Element { return ( ); } @@ -123,7 +128,7 @@ export function ExpressSearchView(): JSX.Element {

{headerText.simpleSearch()}

setQuery(pendingQuery)}> - + {commonText.search()}
diff --git a/specifyweb/frontend/js_src/lib/components/Header/index.tsx b/specifyweb/frontend/js_src/lib/components/Header/index.tsx index 98923ab758c..b636a627b66 100644 --- a/specifyweb/frontend/js_src/lib/components/Header/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/Header/index.tsx @@ -18,6 +18,7 @@ import type { TagProps } from '../Atoms/wrapper'; import type { MenuItem } from '../Core/Main'; import { schema } from '../DataModel/schema'; import { userInformation } from '../InitialContext/userInformation'; +import { useUrlShortcuts } from '../KeyboardShortcuts/useUrlShortcuts'; import { titleDelay, titlePosition } from '../Molecules/Tooltips'; import { Notifications } from '../Notifications/Notifications'; import { useDarkMode } from '../Preferences/Hooks'; @@ -95,6 +96,8 @@ export function Header({ [] ); + useUrlShortcuts(); + const activeMenuItem = React.useContext(MenuContext); return (
>>>()({ url: '/specify/user-preferences/', icon: icons.cog, }, + userKeyboardShortcuts: { + title: preferencesText.keyboardShortcuts(), + url: '/specify/user-keyboard-shortcuts/', + icon: icons.cursorClick, + }, schemaConfig: { title: schemaText.schemaConfig(), url: '/specify/schema-config/', diff --git a/specifyweb/frontend/js_src/lib/components/HomePage/TaxonTiles.tsx b/specifyweb/frontend/js_src/lib/components/HomePage/TaxonTiles.tsx index c124889b5f8..816e6222eb9 100644 --- a/specifyweb/frontend/js_src/lib/components/HomePage/TaxonTiles.tsx +++ b/specifyweb/frontend/js_src/lib/components/HomePage/TaxonTiles.tsx @@ -49,7 +49,7 @@ export function TaxonTiles(): JSX.Element { .attr('title', titleGenerator) .on('mouseover', (_event, node) => setTitle(titleGenerator(node))) .on('click', (_event, node) => - window.open( + globalThis.open( `/specify/query/fromtree/taxon/${node.data.id}/`, '_blank' ) diff --git a/specifyweb/frontend/js_src/lib/components/HomePage/index.tsx b/specifyweb/frontend/js_src/lib/components/HomePage/index.tsx index ce31c82e59c..86debb1dd0e 100644 --- a/specifyweb/frontend/js_src/lib/components/HomePage/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/HomePage/index.tsx @@ -28,7 +28,7 @@ export function WelcomeView(): JSX.Element {
{displaySearchBar && (
- + {commonText.search()} diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/README.md b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/README.md new file mode 100644 index 00000000000..3a0afd492ab --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/README.md @@ -0,0 +1,36 @@ +# Keyboard shortcuts + +## Steps to add a new keyboard shortcut: + +1. Add a new preference item like this: + ```ts + goToLastRecord: defineKeyboardShortcut( + formsText.goToLastRecord(), + 'Ctrl+Shift+ArrowDown' + ), + ``` + - We use event.code as key name (see + [mdn list](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values). + [playground](https://www.toptal.com/developers/keycode)). This is so that + it works regardless of keyboard language. In preferences, use "Ctrl" even + on a mac - the system will automatically interpret Ctrl as Cmd if user is + on a mac. + - Most of the time the keyboard shortcuts between the platforms will be the + same, but if different ones are needed to avoid OS conflicts, the 2nd + argument to defineKeyboardShortcut could be an object like + `{ windows: "", mac: "", linux: "" }`. ios devices are treated as mac. In + spirit of sp6, all "other" devices are treated as linux. +2. Inside react component, use the following hook to handle a specific keyboard + shortcut: + ```tsx + const goToLastRecordShortcut = userPreferences.useKeyboardShortcut( + 'form', + 'recordSet', + 'goToLastRecord', + handleGoToLastRecord + ); + ``` + - callback can be undefined if you need to conditionally disable the keyboard + shortcut + - callback can be an arrow function - does not need a stable function between + re-renders - no need for useCallback diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/Shortcuts.tsx b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/Shortcuts.tsx new file mode 100644 index 00000000000..91461fd59fc --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/Shortcuts.tsx @@ -0,0 +1,226 @@ +/** + * Logic for setting and listening to keyboard shortcuts + */ + +import React from 'react'; + +import { useTriggerState } from '../../hooks/useTriggerState'; +import { commonText } from '../../localization/common'; +import { preferencesText } from '../../localization/preferences'; +import { eventListener } from '../../utils/events'; +import { f } from '../../utils/functools'; +import type { RA } from '../../utils/types'; +import { removeItem, replaceItem, replaceKey } from '../../utils/utils'; +import { Key } from '../Atoms'; +import { Button } from '../Atoms/Button'; +import { className } from '../Atoms/className'; +import type { PreferenceRendererProps } from '../Preferences/types'; +import type { KeyboardShortcuts } from './config'; +import { keyboardPlatform } from './config'; +import { keyJoinSymbol, setKeyboardEventInterceptor } from './context'; +import { + localizedKeyJoinSymbol, + localizeKeyboardShortcut, + resolvePlatformShortcuts, +} from './utils'; + +/* + * If any component instance is editing a keyboard shortcut, don't let any + * other start editing it. + */ +let currentlyEditingShortcut: Record | undefined = undefined; +const editingEvent = eventListener<{ readonly change: undefined }>(); + +export const emptyShortcuts = {}; +export function KeyboardShortcutPreferenceItem({ + value = emptyShortcuts, + onChange: handleChange, +}: Partial< + Pick, 'onChange' | 'value'> +>): JSX.Element { + const [localValue, setLocalValue] = useTriggerState(value); + + const key = React.useMemo>(() => ({}), []); + const globalIsEditingElsewhere = + currentlyEditingShortcut !== undefined && currentlyEditingShortcut !== key; + + const [editingIndex, setEditingIndex] = React.useState(false); + const isEditing = typeof editingIndex === 'number'; + const shortcuts = resolvePlatformShortcuts(localValue) ?? []; + const setShortcuts = + handleChange === undefined + ? undefined + : (shortcuts: RA): void => + setLocalValue(replaceKey(localValue, keyboardPlatform, shortcuts)); + + const valueRef = React.useRef(localValue); + valueRef.current = localValue; + + const [_, triggerReRender] = React.useState(false); + React.useEffect( + () => editingEvent.on('change', () => triggerReRender((flip) => !flip)), + [] + ); + + // Cleanup empty when we finish editing + React.useEffect( + () => + isEditing + ? (): void => { + currentlyEditingShortcut = undefined; + editingEvent.trigger('change'); + handleChange?.(cleanupEmpty(valueRef.current)); + } + : undefined, + [isEditing, handleChange] + ); + + return ( +
+ {shortcuts.map((shortcut, index) => ( + { + currentlyEditingShortcut = key; + editingEvent.trigger('change'); + setEditingIndex(index); + } + : undefined + } + onSave={ + editingIndex === index && + setShortcuts !== undefined && + !globalIsEditingElsewhere + ? (shortcut): void => { + setShortcuts( + f.unique( + shortcut === undefined + ? removeItem(shortcuts, index) + : replaceItem(shortcuts, index, shortcut) + ) + ); + setEditingIndex(false); + } + : undefined + } + /> + ))} + {setShortcuts !== undefined && ( + { + currentlyEditingShortcut = key; + editingEvent.trigger('change'); + setShortcuts([...shortcuts, '']); + setEditingIndex(shortcuts.length); + } + } + > + {commonText.add()} + + )} +
+ ); +} + +function cleanupEmpty(value: KeyboardShortcuts): KeyboardShortcuts { + const shortcuts = Object.fromEntries( + Object.entries(value).map(([platform, shortcuts]) => [ + platform, + // Drop empty strings + shortcuts?.filter((shortcut) => shortcut.length > 0), + ]) + ); + const isCompletelyEmpty = Object.values(shortcuts).every( + (shortcuts) => shortcuts === undefined || shortcuts.length === 0 + ); + return isCompletelyEmpty ? {} : value; +} + +function EditKeyboardShortcut({ + shortcut, + onSave: handleSave, + onEditStart: handleEditStart, +}: { + readonly shortcut: string; + readonly onSave: ((shortcut: string | undefined) => void) | undefined; + readonly onEditStart: (() => void) | undefined; +}): JSX.Element { + const [localState, setLocalState] = useTriggerState(shortcut); + const parts = localState.length === 0 ? [] : localState.split(keyJoinSymbol); + const isEditing = typeof handleSave === 'function'; + + React.useEffect(() => { + if (isEditing) { + // Allows user to press Enter to finish setting keyboard shortcut. + saveButtonRef.current?.focus(); + setLocalState(''); + return setKeyboardEventInterceptor(setLocalState); + } + return undefined; + }, [isEditing, setLocalState]); + + const isEmpty = parts.length === 0; + const activeValue = React.useRef(localState); + activeValue.current = isEmpty ? shortcut : localState; + + const saveButtonRef = React.useRef(null); + + const localizedParts = React.useMemo( + () => localizeKeyboardShortcut(localState).split(localizedKeyJoinSymbol), + [localState] + ); + + return ( +
+
+ {isEmpty ? ( + isEditing ? ( + preferencesText.pressKeys() + ) : ( + preferencesText.noKeyAssigned() + ) + ) : ( + + {localizedParts.map((key, index) => ( + + {key} + + ))} + + )} +
+ {isEditing && ( + handleSave(undefined)}> + {commonText.remove()} + + )} + + handleSave( + activeValue.current.length === 0 + ? shortcut + : activeValue.current + ) + : handleEditStart + } + > + {isEditing ? commonText.save() : commonText.edit()} + +
+ ); +} diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/UrlShortcuts.tsx b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/UrlShortcuts.tsx new file mode 100644 index 00000000000..829c36b6fc1 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/UrlShortcuts.tsx @@ -0,0 +1,323 @@ +import React from 'react'; +import type { LocalizedString } from 'typesafe-i18n'; + +import { useAsyncState } from '../../hooks/useAsyncState'; +import { useBooleanState } from '../../hooks/useBooleanState'; +import { useTriggerState } from '../../hooks/useTriggerState'; +import { commonText } from '../../localization/common'; +import { preferencesText } from '../../localization/preferences'; +import { f } from '../../utils/functools'; +import type { GetSet, RA, WritableArray } from '../../utils/types'; +import { GET, removeKey } from '../../utils/utils'; +import { H3, Ul } from '../Atoms'; +import { Button } from '../Atoms/Button'; +import { Input } from '../Atoms/Form'; +import { ReadOnlyContext } from '../Core/Contexts'; +import { Dialog, dialogClassNames } from '../Molecules/Dialog'; +import type { PreferenceRendererProps } from '../Preferences/types'; +import type { EnhancedRoute } from '../Router/RouterUtils'; +import type { KeyboardShortcuts } from './config'; +import { emptyShortcuts, KeyboardShortcutPreferenceItem } from './Shortcuts'; + +export type UrlShortcuts = Partial>; + +export function UrlShortcutsEditor( + props: PreferenceRendererProps +): JSX.Element { + const [isOpen, _, __, handleToggle] = useBooleanState(false); + const isReadOnly = React.useContext(ReadOnlyContext); + + return ( + <> +
+ + {isReadOnly ? commonText.view() : commonText.edit()} + +
+ {isOpen && ( + { + props.onChange(newValue); + handleToggle(); + }} + /> + )} + + ); +} + +/** + * This is used in BasePreferences to filter preferences down to keyboard vs + * non-keyboard preference. + * (have to set the name explicitly to survive minification) + */ +Object.defineProperty(UrlShortcutsEditor, 'name', { + value: 'UrlShortcutsEditor', +}); + +function EditorDialog({ + value, + onChange: handleChange, +}: PreferenceRendererProps): JSX.Element | null { + const [categorizedRoutes] = useAsyncState(getCategorizedRoutes, true); + const localValue = useTriggerState(value); + return categorizedRoutes ? ( + + handleChange( + localValue[GET] === value ? value : cleanupShortcuts(localValue[GET]) + ) + } + > +

{preferencesText.pages()}

+ +

{preferencesText.overlays()}

+ +

{preferencesText.customPages()}

+ +
+ ) : null; +} + +const cleanupShortcuts = (shortcuts: UrlShortcuts): UrlShortcuts => + Object.fromEntries( + Object.entries(shortcuts).filter( + ([path, shortcuts]) => + path.length > 0 && + (shortcuts === undefined || Object.keys(shortcuts).length > 0) + ) + ); + +type CategorizedRoutes = Record<'overlays' | 'pages', CategoryRoutes>; +type CategoryRoutes = Record; + +const getCategorizedRoutes = f.store(async (): Promise => { + const [entrypointRoutes, routes, overlayRoutes] = await Promise.all([ + /* + * Routes import a lot of files. To avoid cycle imports, we import + * them dynamically. + */ + import('../Router/EntrypointRouter').then( + ({ entrypointRoutes }) => entrypointRoutes + ), + import('../Router/Routes').then(({ routes }) => routes), + import('../Router/OverlayRoutes').then( + ({ overlayRoutes }) => overlayRoutes + ), + ]); + return { + pages: Object.fromEntries([ + ...scoutTree(entrypointRoutes, ''), + ...scoutTree(routes, '/specify'), + ]), + overlays: Object.fromEntries(scoutTree(overlayRoutes, '/specify')), + }; +}); + +const scoutTree = ( + routes: RA, + prefix: string +): RA => + routes.flatMap((route) => { + let path = prefix; + const entries: WritableArray< + readonly [string, LocalizedString | undefined] + > = []; + + /* + * Don't include routes without element as they have nothing to render. + * Don't include routes where element is a JSX.Element (Redirect, + * Navigate, NotFound - not interesting for keyboard shortcuts). + */ + const hasContent = typeof route.element === 'function'; + + if (route.path !== undefined) { + const isDynamic = route.path.includes('*') || route.path.includes(':'); + if (isDynamic) return entries; + + const trimmedPath = route.path.endsWith('/') + ? route.path.slice(0, -1) + : route.path; + path = `${prefix}/${trimmedPath}`; + if (hasContent) entries.push([path, route.title]); + } else if (route.index === true && hasContent) + entries.push([path, route.title]); + + if (route.children) entries.push(...scoutTree(route.children, path)); + + return entries; + }); + +function RouteBrowser({ + routes, + value, +}: { + readonly routes: CategoryRoutes; + readonly value: GetSet; +}): JSX.Element { + return ( +
    + {Object.entries(routes).map(([path, title]) => ( + + ))} +
+ ); +} + +function RouteShortcut({ + path, + title, + value: [value, setValue], +}: { + readonly path: string; + readonly title: LocalizedString | undefined; + readonly value: GetSet; +}): JSX.Element { + return ( + + {path} + {title !== undefined && ` (${title})`} +
+ } + > + setValue({ ...value, [path]: newValue })} + /> + + ); +} + +function KeyboardShortcutContainer({ + label, + children, +}: { + readonly label: JSX.Element; + readonly children: JSX.Element; +}): JSX.Element { + return ( +
  • + {label} + + {children} +
  • + ); +} + +function CustomRouteBrowser({ + value: [value, setValue], + categorized, +}: { + readonly value: GetSet; + readonly categorized: CategorizedRoutes; +}): JSX.Element { + const isReadOnly = React.useContext(ReadOnlyContext); + return ( +
      + {Object.entries(value).map(([path, shortcuts], index) => + path in categorized.pages || + path in categorized.overlays ? undefined : ( + + setValue({ + ...removeKey(value, path), + [newPath]: newShortcuts, + }) + } + /> + ) + )} + {!isReadOnly && ( +
    • + + setValue({ + ...value, + '': emptyShortcuts, + }) + } + > + {commonText.add()} + +
    • + )} +
    + ); +} + +function CustomRouteShortcut({ + path, + shortcuts, + onChange: handleChange, +}: { + readonly path: string; + readonly shortcuts: KeyboardShortcuts; + readonly onChange: (path: string, newValue: KeyboardShortcuts) => void; +}): JSX.Element { + return ( + handleChange(path, shortcuts)} + /> + } + > + handleChange(path, shortcuts)} + /> + + ); +} + +function UrlField({ + value, + onChange: handleChange, +}: { + readonly value: string; + readonly onChange: (newValue: string) => void; +}): JSX.Element { + const isReadOnly = React.useContext(ReadOnlyContext); + const [pendingValue, setPendingValue] = useTriggerState(value); + return ( + { + try { + const trimmed = target.value.trim(); + const url = new URL(trimmed, globalThis.location.href); + const isSameOrigin = url.origin === globalThis.location.origin; + const normalized = isSameOrigin + ? `${url.pathname.endsWith('/') ? url.pathname.slice(0, -1) : url.pathname}${url.search}${url.hash}` + : url.href; + handleChange(normalized); + target.setCustomValidity(''); + } catch (error: unknown) { + target.setCustomValidity(String(error)); + target.reportValidity(); + } + }} + onValueChange={setPendingValue} + /> + ); +} diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/__tests__/UserDefinitions.test.ts b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/__tests__/UserDefinitions.test.ts new file mode 100644 index 00000000000..3dcc35b5de9 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/__tests__/UserDefinitions.test.ts @@ -0,0 +1,120 @@ +import { requireContext } from '../../../tests/helpers'; +import type { GenericPreferences } from '../../Preferences/types'; +import { userPreferenceDefinitions } from '../../Preferences/UserDefinitions'; +import type { KeyboardShortcuts, ModifierKey } from '../config'; +import { keyLocalizations, modifierKeys, specialKeyboardKeys } from '../config'; +import { exportsForTests, keyJoinSymbol } from '../context'; + +requireContext(); + +const { keysToString } = exportsForTests; + +test('Validate default keyboard shortcuts in userPreferenceDefinitions', () => { + Object.entries(userPreferenceDefinitions as GenericPreferences).forEach( + ([category, definition]) => + Object.entries(definition.subCategories).forEach( + ([subCategory, subDefinition]) => + Object.entries(subDefinition.items).forEach( + ([item, itemDefinition]) => { + const isKeyboardShortcut = + 'renderer' in itemDefinition && + itemDefinition.renderer.name === + 'KeyboardShortcutPreferenceItem'; + if (!isKeyboardShortcut) return; + + const defaultValue = + itemDefinition.defaultValue as KeyboardShortcuts; + + console.log(category, subCategory, item, defaultValue); + if (typeof defaultValue !== 'object') return; + + Object.entries(defaultValue).forEach(([platform, shortcuts]) => + shortcuts?.forEach((shortcut) => { + const error = validateShortcut(shortcut, platform); + if (error !== undefined) + // eslint-disable-next-line functional/no-throw-statement + throw new Error( + `Invalid default value for a keyboard shortcut for ${category}.${subCategory}.${item} for platform "${platform}" (value: ${shortcut}). Error: ${String( + error + )}` + ); + }) + ); + } + ) + ) + ); + + // Useless assertion to have at least one assertion in the test + expect(1).toBe(1); +}); + +function validateShortcut( + shortcut: string, + platform: keyof KeyboardShortcuts +): string | undefined { + const parts = shortcut.split(keyJoinSymbol); + if (parts.length === 0) return 'unexpected empty shortcut'; + + const shortcutModifierKeys = parts + .map((part) => part as ModifierKey) + .filter((part) => modifierKeys.includes(part)) + .sort(); + const nonModifierKeys = parts + .filter((part) => !modifierKeys.includes(part as ModifierKey)) + .sort(); + const normalizedShortcut = keysToString( + shortcutModifierKeys, + nonModifierKeys + ); + if (normalizedShortcut !== shortcut) + return `shortcut is not normalized: ${normalizedShortcut} (expected keys to be sorted, with modifier keys before non-modifiers)`; + + if (shortcutModifierKeys.length === 0) { + const specialKey = nonModifierKeys.find((key) => + specialKeyboardKeys.has(key) + ); + if (typeof specialKey === 'string') + return `can't use special reserved keys as default shortcuts, unless prefixed with a modifier key. Found ${specialKey}`; + } + + if (platform !== 'mac' && shortcutModifierKeys.includes('Meta')) + return `can't use meta (cmd) key in non-mac platform as those are reserved for system shortcuts`; + + if (nonModifierKeys.length === 0) + return 'shortcut must contain at least one non-modifier key'; + + const disallowedKey = nonModifierKeys.find((key) => !allowed.has(key)); + if (typeof disallowedKey === 'string') + return `found an unknown key code: ${disallowedKey}. Make sure you are using key code rather than key name (e.g. use "KeyA" instead of "a"). To test, see what "keydown" event gives you for event.code (rather than event.key). If you are sure you have the key code, then you can extend the list of allowed keys in this test`; + + return undefined; +} + +/** + * To guard against typos, this list defines the list of keys that may appear in + * default keyboard shortcuts. If some key is missing, feel free to add it to + * this list + * + * See a full list of possible keys browsers could capture here: + * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#special_values + */ +const allowed = new Set([ + // Not including modifier keys as the above check will only check non-modifiers + ...Array.from(specialKeyboardKeys), + ...Object.keys(keyLocalizations), + 'BrowserBack', + 'BrowserForward', + 'BrowserHome', + 'BrowserRefresh', + 'BrowserSearch', + 'Add', + 'Multiply', + 'Subtract', + 'Decimal', + 'Divide', + /* + * This list doesn't include more obscure keys, but we probably shouldn't be + * using those in key shortcuts anyway + */ +]); diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/config.ts b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/config.ts new file mode 100644 index 00000000000..1304b899872 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/config.ts @@ -0,0 +1,214 @@ +import { preferencesText } from '../../localization/preferences'; +import type { IR, RA, RR } from '../../utils/types'; + +/** + * Because operating systems, browsers and browser extensions define many + * keyboard shortcuts, many of which differ between operating systems, the set + * of free keyboard shortcuts is quite small so it's difficult to have one + * shortcut that works on all 3 platforms. + * + * To provide flexibility, without complicating the UI for people who only use + * Specify on a single platform, we do the following: + * - UI allows you to set keyboard shortcuts for the current platform only + * - If you set keyboard shortcut on any platform, that shortcut is used on all + * platforms, unless you explicitly edited the shortcut on the other platform + * - If keyboard shortcut was not explicitly set, the default shortcut, if any + * will be used + */ +export type KeyboardShortcuts = Partial< + RR | undefined> +>; + +type KeyboardPlatform = 'mac' | 'other' | 'windows'; +export const keyboardPlatform: KeyboardPlatform = + process.env.NODE_ENV === 'test' + ? 'other' + : navigator.platform.toLowerCase().includes('mac') || + // Check for iphone || ipad || ipod + navigator.platform.toLowerCase().includes('ip') + ? 'mac' + : navigator.platform.toLowerCase().includes('win') + ? 'windows' + : 'other'; + +export const modifierKeys = ['Alt', 'Ctrl', 'Meta', 'Shift'] as const; +export type ModifierKey = typeof modifierKeys[number]; +export const allModifierKeys = new Set([ + ...modifierKeys, + 'AltGraph', + 'CapsLock', + 'MetaLeft', + 'MetaRight', + 'ShiftLeft', + 'ShiftRight', + 'ControlLeft', + 'ControlRight', + 'AltLeft', + 'AltRight', +]); + +export const keyboardModifierLocalization: RR = { + Alt: + keyboardPlatform === 'mac' + ? preferencesText.macOption() + : preferencesText.alt(), + Ctrl: + keyboardPlatform === 'mac' + ? preferencesText.macControl() + : preferencesText.ctrl(), + // This key should never appear in non-mac platforms + Meta: preferencesText.macMeta(), + Shift: + keyboardPlatform === 'mac' + ? preferencesText.macShift() + : preferencesText.shift(), +}; + +/** + * Do not allow binding a keyboard shortcut that includes only one of these + * keys, without any modifier. + * + * For example, do not allow binding keyboard shortcuts to Tab key. That key is + * important for accessibility and for keyboard navigation. Without it + * you won't be able to tab your way to the "Save" button to save the + * keyboard shortcut) + */ +export const specialKeyboardKeys = new Set([ + 'Enter', + 'Tab', + 'Space', + 'Escape', + 'Backspace', +]); + +/** + * Because we are listening to key codes that correspond to US English letters, + * we should show keys in the UI in US English to avoid confusion. + * (otherwise Cmd+O is ambiguous as it's not clear if it refers to English O or + * local language О). + * See https://github.com/specify/specify7/issues/1746#issuecomment-2227113839 + * + * For some keys, it is less confusing to see a symbol (like arrow keys), rather + * than 'ArrowUp', thus symbols are used for those keys. + * See http://xahlee.info/comp/unicode_computing_symbols.html + * + * Try not to define keyboard shortcuts for keys that may be in a different + * place in other keyboard layouts (the positions of special symbols wary a lot) + * + * See full list of key codes: + * https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values + */ +export const keyLocalizations: IR = { + ArrowDown: '↓', + ArrowLeft: '←', + ArrowRight: '→', + ArrowUp: '↑', + Backquote: '`', + Backslash: '\\', + Backspace: '⌫', + BracketLeft: '[', + BracketRight: ']', + Comma: ',', + Digit0: '0', + Digit1: '1', + Digit2: '2', + Digit3: '3', + Digit4: '4', + Digit5: '5', + Digit6: '6', + Digit7: '7', + Digit8: '8', + Digit9: '9', + // "return" key is used over Enter on macOS keyboards + Enter: keyboardPlatform === 'mac' ? '↵' : 'Enter', + Equal: '=', + KeyA: 'a', + KeyB: 'b', + KeyC: 'c', + KeyD: 'd', + KeyE: 'e', + KeyF: 'f', + KeyG: 'g', + KeyH: 'h', + KeyI: 'i', + KeyJ: 'j', + KeyK: 'k', + KeyL: 'l', + KeyM: 'm', + KeyN: 'n', + KeyO: 'o', + KeyP: 'p', + KeyQ: 'q', + KeyR: 'r', + KeyS: 's', + KeyT: 't', + KeyU: 'u', + KeyV: 'v', + KeyW: 'w', + KeyX: 'x', + KeyY: 'y', + KeyZ: 'z', + Minus: '-', + Period: '.', + Quote: "'", + Semicolon: ';', + Slash: '/', +}; + +/** + * Like keyLocalizations, but applied if keyboard shortcut involves Shift key + */ +export const shiftKeyLocalizations: Record = { + // FEATURE: decide if these should display differently when shift is pressed or not: + /* + * "Backquote": "~", + * "Backslash": "|", + * "BracketLeft": "{", + * "BracketRight": "}", + * "Comma": "<", + * "Digit0": ")", + * "Digit1": "!", + * "Digit2": "@", + * "Digit3": "#", + * "Digit4": "$", + * "Digit5": "%", + * "Digit6": "^", + * "Digit7": "&", + * "Digit8": "*", + * "Digit9": "(", + * "Equal": "+", + */ + KeyA: 'A', + KeyB: 'B', + KeyC: 'C', + KeyD: 'D', + KeyE: 'E', + KeyF: 'F', + KeyG: 'G', + KeyH: 'H', + KeyI: 'I', + KeyJ: 'J', + KeyK: 'K', + KeyL: 'L', + KeyM: 'M', + KeyN: 'N', + KeyO: 'O', + KeyP: 'P', + KeyQ: 'Q', + KeyR: 'R', + KeyS: 'S', + KeyT: 'T', + KeyU: 'U', + KeyV: 'V', + KeyW: 'W', + KeyX: 'X', + KeyY: 'Y', + KeyZ: 'Z', + /* + * "Minus": "_", + * "Period": ">", + * "Quote": "\"", + * "Semicolon": ":", + * "Slash": "?" + */ +}; diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/context.ts b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/context.ts new file mode 100644 index 00000000000..42b5780913c --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/context.ts @@ -0,0 +1,204 @@ +/** + * Allows to register a key listener + */ + +import type { RA, WritableArray } from '../../utils/types'; +import type { KeyboardShortcuts, ModifierKey } from './config'; +import { allModifierKeys, specialKeyboardKeys } from './config'; +import { resolvePlatformShortcuts } from './utils'; + +/** + * To keep the event listener as fast as possible, we are not looping though all + * set keyboard shortcuts and checking if any matches the set value - instead, + * the registered shortcuts are stored in this hashmap, making it very easy + * to check if a listener for current key combination exists. + * + * At the same time, some of our UI can be nested (imagine the record selector + * in a record set listening for next/previous record keyboard shortcut, and + * then the user opens a modal that also listens for the same shortcut) - to + * have things work correctly, we store listeners as a stack, with the most + * recently added listener (last one) being the active one. + */ +const listeners = new Map void>>(); + +/** + * When setting a keyboard shortcut in user preferences, we want to: + * - Prevent any other shortcut from reacting + * - Read what keys were pressed + */ +let interceptor: ((keys: string) => void) | undefined; +export function setKeyboardEventInterceptor( + callback: typeof interceptor +): () => void { + interceptor = callback; + return (): void => { + if (interceptor === callback) interceptor = undefined; + }; +} + +export function bindKeyboardShortcut( + shortcut: KeyboardShortcuts, + callback: () => void +): () => void { + const shortcuts = resolvePlatformShortcuts(shortcut) ?? []; + shortcuts.forEach((string) => { + const shortcutListeners = listeners.get(string); + if (shortcutListeners === undefined) listeners.set(string, [callback]); + else shortcutListeners.push(callback); + }); + return () => + shortcuts.forEach((string) => { + const activeListeners = listeners.get(string)!; + const lastIndex = activeListeners.lastIndexOf(callback); + if (lastIndex !== -1) activeListeners.splice(lastIndex, 1); + if (activeListeners.length === 0) listeners.delete(string); + }); +} + +/** + * Assumes keys and modifiers are sorted + */ +const keysToString = (modifiers: RA, keys: RA): string => + [...modifiers, ...keys].join(keyJoinSymbol); +export const keyJoinSymbol = '+'; + +// eslint-disable-next-line functional/prefer-readonly-type +const pressedKeys: string[] = []; + +// Keep this code fast as it's in the hot path +document.addEventListener('keydown', (event) => { + if (shouldIgnoreKeyPress(event)) return; + + const modifiers = resolveModifiers(event); + const isEntering = isInInput(event); + const isPrintable = isPrintableModifier(modifiers); + // Ignore shortcuts that result in printed characters when in an input field + const ignore = isPrintable && isEntering; + if (ignore) return; + const isSpecial = specialKeyboardKeys.has(event.code); + if ( + isSpecial && + (modifiers.length === 0 || + (modifiers[0] === 'Shift' && event.code === 'Tab')) + ) + return; + + if (!pressedKeys.includes(event.code)) { + pressedKeys.push(event.code); + pressedKeys.sort(); + } + + const keyString = keysToString(modifiers, pressedKeys); + const handler = interceptor ?? listeners.get(keyString)?.at(-1); + if (typeof handler === 'function') { + handler(keyString); + /* + * Do this only after calling the handler, so that if handler throws an + * exception, the event can still be handled normally by the browser + */ + event.preventDefault(); + event.stopPropagation(); + } + + /** + * For key combinations involving arrows, the keyup is not fired reliably + * on macOS (i.e for Cmd+Shift+ArrowUp), thus we need to clear the pressed + * keys. This means you can't have a shortcut like Cmd+Shift+KeyQ+ArrowUp + */ + if (keyString.includes('Arrow')) pressedKeys.length = 0; +}); + +function shouldIgnoreKeyPress(event: KeyboardEvent): boolean { + const code = event.code; + + if (event.isComposing || event.repeat) return true; + + // See https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_code_values + if ( + code === 'Dead' || + code === 'Unidentified' || + code === 'Unidentified' || + code === '' + ) + return true; + + // Do not allow binding a key shortcut directly to a modifier key + return allModifierKeys.has(event.code); +} + +export const resolveModifiers = (event: KeyboardEvent): RA => + Object.entries({ + // This order is important - keep it alphabetical + Alt: event.altKey, + Ctrl: event.ctrlKey, + Meta: event.metaKey, + Shift: event.shiftKey, + }) + .filter(([_, isPressed]) => isPressed) + .map(([modifier]) => modifier); + +function isInInput(event: KeyboardEvent): boolean { + // Check if the event target is an editable element. + const target = event.target as HTMLElement; + return ( + target.tagName === 'INPUT' || + target.tagName === 'TEXTAREA' || + target.isContentEditable + ); +} + +/** + * On all platforms, shift key + letter produces a printable character (i.e shift+a = A) + * + * On mac, option (alt) key is used for producing printable characters too, but + * according to ChatGPT, in browser applications it is expected that keyboard + * shortcuts take precedence over printing characters. + */ +function isPrintableModifier(modifiers: RA): boolean { + if (modifiers.length === 0) return true; + + if (modifiers.length === 1) return modifiers[0] === 'Shift'; + + return false; +} + +document.addEventListener( + 'keyup', + (event) => { + const index = pressedKeys.indexOf(event.code); + if (index !== -1) pressedKeys.splice(index, 1); + + /* + * If un-pressed any modifier, consider current shortcut finished. + * + * This is a workaround for an issue on macOS where in a shortcut like + * Cmd+Shift+ArrowUp, the keyup even is fired for Cmd and Shift, but not + * for ArrowUp + */ + const isModifier = allModifierKeys.has(event.code); + if (isModifier) pressedKeys.length = 0; + }, + { passive: true } +); + +/** + * While key up should normally catch key release, that may not always be the + * case: + * - If key up occurred outside the browser window + * - If key up occurred inside of browser devtools + * - If key up was intercepted by something else (i.e browser extension) + */ +window.addEventListener( + 'blur', + () => { + pressedKeys.length = 0; + }, + { passive: true } +); +document.addEventListener('visibilitychange', () => { + if (document.hidden) pressedKeys.length = 0; +}); + +export const exportsForTests = { + keysToString, +}; diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/hooks.tsx b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/hooks.tsx new file mode 100644 index 00000000000..f273c80c5ce --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/hooks.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import type { LocalizedString } from 'typesafe-i18n'; + +import { localized } from '../../utils/types'; +import { formatDisjunction } from '../Atoms/Internationalization'; +import type { KeyboardShortcuts } from './config'; +import { bindKeyboardShortcut } from './context'; +import { localizeKeyboardShortcut, resolvePlatformShortcuts } from './utils'; + +/** + * Prefer `userPreferences.useKeyboardShortcut()` over directly using this hook + */ +export function useManualKeyboardShortcut( + shortcuts: KeyboardShortcuts | undefined, + callback: (() => void) | undefined +): void { + const callbackRef = React.useRef(callback); + callbackRef.current = callback; + const hasCallback = typeof callback === 'function'; + React.useEffect( + () => + typeof shortcuts === 'object' && hasCallback + ? bindKeyboardShortcut(shortcuts, () => callbackRef.current?.()) + : undefined, + [hasCallback, shortcuts] + ); +} + +/** + * Prefer `userPreferences.useKeyboardShortcut()` over directly using this hook + * + * Provides a localized keyboard shortcut string, which can be used in the UI + * in the "title" attribute. + */ +export function useKeyboardShortcutLabel( + shortcuts: KeyboardShortcuts | undefined +): LocalizedString { + return React.useMemo(() => { + const platformShortcuts = + shortcuts === undefined ? [] : resolvePlatformShortcuts(shortcuts) ?? []; + return localized( + platformShortcuts.length > 0 + ? ` (${formatDisjunction( + platformShortcuts.map(localizeKeyboardShortcut) + )})` + : '' + ); + }, [shortcuts]); +} diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/useUrlShortcuts.tsx b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/useUrlShortcuts.tsx new file mode 100644 index 00000000000..84592d6da61 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/useUrlShortcuts.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +import { isExternalUrl } from '../../utils/ajax/helpers'; +import { userPreferences } from '../Preferences/userPreferences'; +import { bindKeyboardShortcut } from './context'; + +export function useUrlShortcuts(): void { + const [shortcuts] = userPreferences.use('header', 'actions', 'urlShortcuts'); + const navigate = useNavigate(); + React.useEffect(() => { + const cleanup = Object.entries(shortcuts).map(([path, shortcuts]) => + shortcuts === undefined + ? undefined + : bindKeyboardShortcut(shortcuts, () => { + if (isExternalUrl(path)) globalThis.open(path, '_blank'); + else navigate(path); + }) + ); + return (): void => cleanup.forEach((cleanup) => cleanup?.()); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [shortcuts]); +} diff --git a/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/utils.ts b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/utils.ts new file mode 100644 index 00000000000..416b6fdad61 --- /dev/null +++ b/specifyweb/frontend/js_src/lib/components/KeyboardShortcuts/utils.ts @@ -0,0 +1,77 @@ +import type { LocalizedString } from 'typesafe-i18n'; + +import type { RA, WritableArray } from '../../utils/types'; +import { localized } from '../../utils/types'; +import type { KeyboardShortcuts, ModifierKey } from './config'; +import { + keyboardModifierLocalization, + keyboardPlatform, + keyLocalizations, + shiftKeyLocalizations, +} from './config'; +import { keyJoinSymbol } from './context'; + +export const localizedKeyJoinSymbol = ' + '; +export function localizeKeyboardShortcut(shortcut: string): LocalizedString { + const parts = shortcut.split(keyJoinSymbol); + const hasShift = parts.includes('Shift'); + + const modifiers: WritableArray = []; + const nonModifiers: WritableArray = []; + // eslint-disable-next-line functional/no-loop-statement + for (const key of parts) { + const localizedModifier = keyboardModifierLocalization[key as ModifierKey]; + if (typeof localizedModifier === 'string') + modifiers.push(localizedModifier); + else { + nonModifiers.push( + (hasShift ? shiftKeyLocalizations[key] : undefined) ?? + keyLocalizations[key] ?? + key + ); + } + } + + // If there is only one non-modifier key, then join the keys without separator + const resolved = + keyboardPlatform === 'mac' && nonModifiers.length === 1 + ? `${modifiers.join('')}${nonModifiers[0]}` + : [...modifiers, ...nonModifiers].join(localizedKeyJoinSymbol); + + return localized(resolved); +} + +/** + * If there is a keyboard shortcut defined for current system, use it + * (also, if current system explicitly has empty array of shortcuts, use it). + * + * Otherwise, use the keyboard shortcut from one of the other platforms if set, + * but change meta to ctrl and vice versa as necessary. + */ +export function resolvePlatformShortcuts( + shortcut: KeyboardShortcuts +): RA | undefined { + if (keyboardPlatform in shortcut) return shortcut[keyboardPlatform]; + else if ('other' in shortcut) + return keyboardPlatform === 'windows' + ? shortcut.other + : shortcut.other?.map(replaceCtrlWithMeta); + else if ('windows' in shortcut) + return keyboardPlatform === 'other' + ? shortcut.other + : shortcut.other?.map(replaceCtrlWithMeta); + else if ('mac' in shortcut) return shortcut.other?.map(replaceMetaWithCtrl); + else return undefined; +} + +const replaceCtrlWithMeta = (shortcut: string): string => + shortcut + .split(keyJoinSymbol) + .map((key) => (key === 'Ctrl' ? 'Meta' : key)) + .join(keyJoinSymbol); + +const replaceMetaWithCtrl = (shortcut: string): string => + shortcut + .split(keyJoinSymbol) + .map((key) => (key === 'Meta' ? 'Ctrl' : key)) + .join(keyJoinSymbol); diff --git a/specifyweb/frontend/js_src/lib/components/LocalityUpdate/Status.tsx b/specifyweb/frontend/js_src/lib/components/LocalityUpdate/Status.tsx index 2c3d307e3da..9e9830e97fc 100644 --- a/specifyweb/frontend/js_src/lib/components/LocalityUpdate/Status.tsx +++ b/specifyweb/frontend/js_src/lib/components/LocalityUpdate/Status.tsx @@ -32,6 +32,7 @@ import { Dialog } from '../Molecules/Dialog'; import { downloadFile } from '../Molecules/FilePicker'; import { TableIcon } from '../Molecules/TableIcon'; import { hasToolPermission } from '../Permissions/helpers'; +import { userPreferences } from '../Preferences/userPreferences'; import { CreateRecordSet } from '../QueryBuilder/CreateRecordSet'; import { downloadDataSet } from '../WorkBench/helpers'; import { RemainingLoadingTime } from '../WorkBench/RemainingLoadingTime'; @@ -314,9 +315,7 @@ export function LocalityUpdateSuccess({ )} {recordSet === undefined && (
    - - {queryText.browseInForms()} - + {formsOpened && ( void; +}): JSX.Element { + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'queryBuilder', + 'actions', + 'browseInForms', + handleClick + ); + return ( + + {queryText.browseInForms()} + + ); +} + export function LocalityUpdateParseErrors({ errors, onClose: handleClose, diff --git a/specifyweb/frontend/js_src/lib/components/Molecules/Paginator.tsx b/specifyweb/frontend/js_src/lib/components/Molecules/Paginator.tsx index 800b7e30a2e..5d9502f86e4 100644 --- a/specifyweb/frontend/js_src/lib/components/Molecules/Paginator.tsx +++ b/specifyweb/frontend/js_src/lib/components/Molecules/Paginator.tsx @@ -41,6 +41,7 @@ export function usePaginator(
    diff --git a/specifyweb/frontend/js_src/lib/components/Molecules/ResourceLink.tsx b/specifyweb/frontend/js_src/lib/components/Molecules/ResourceLink.tsx index 76cfc94dc10..c79050b5ce4 100644 --- a/specifyweb/frontend/js_src/lib/components/Molecules/ResourceLink.tsx +++ b/specifyweb/frontend/js_src/lib/components/Molecules/ResourceLink.tsx @@ -32,6 +32,7 @@ export function ResourceLink({ resourceView, children, autoClose = true, + toggleRef, }: { readonly resource: SpecifyResource | undefined; readonly component: COMPONENT; @@ -47,8 +48,11 @@ export function ResourceLink({ 'dialog' | 'onAdd' | 'onClose' | 'onSaved' >; readonly autoClose?: boolean; + readonly toggleRef?: React.MutableRefObject<(() => void) | undefined>; }): JSX.Element { const [isOpen, _, handleClose, handleToggle] = useBooleanState(); + if (toggleRef) toggleRef.current = handleToggle; + React.useEffect(() => { if (autoClose) handleClose(); }, [resource]); @@ -66,7 +70,7 @@ export function ResourceLink({ 'aria-pressed': isOpen, href: resource?.isNew() ? getResourceViewUrl(resource.specifyTable.name, undefined) - : resource?.viewUrl()!, + : resource?.viewUrl() ?? '', title: props.title ?? commonText.view(), onClick: (event): void => { event.preventDefault(); @@ -76,6 +80,7 @@ export function ResourceLink({ }, children, }; + const AnyComponent = Component as (props: typeof allProps) => JSX.Element; return ( diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/Aside.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/Aside.tsx index 806575fafad..5bc5682e82b 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/Aside.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/Aside.tsx @@ -7,18 +7,19 @@ import type { GetSet, WritableArray } from '../../utils/types'; import { Link } from '../Atoms/Link'; import { pathIsOverlay } from '../Router/UnloadProtect'; import { scrollIntoView } from '../TreeView/helpers'; -import { usePrefDefinitions } from './index'; +import type { usePrefDefinitions } from './index'; export function PreferencesAside({ activeCategory, setActiveCategory, references, + definitions, }: { readonly activeCategory: number | undefined; readonly setActiveCategory: (activeCategory: number | undefined) => void; readonly references: React.RefObject>; + readonly definitions: ReturnType; }): JSX.Element { - const definitions = usePrefDefinitions(); const navigate = useNavigate(); const location = useLocation(); const isInOverlay = pathIsOverlay(location.pathname); diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/BasePreferences.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/BasePreferences.tsx index bc308aaabd7..244a806b552 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/BasePreferences.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/BasePreferences.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import type { LocalizedString } from 'typesafe-i18n'; import { ajax } from '../../utils/ajax'; import { Http } from '../../utils/ajax/definitions'; @@ -8,7 +9,11 @@ import { f } from '../../utils/functools'; import { mergeParsers, parserFromType } from '../../utils/parser/definitions'; import { parseValue } from '../../utils/parser/parse'; import type { GetOrSet, RA } from '../../utils/types'; -import { filterArray, setDevelopmentGlobal } from '../../utils/types'; +import { + filterArray, + localized, + setDevelopmentGlobal, +} from '../../utils/types'; import { keysToLowerCase, replaceKey } from '../../utils/utils'; import { SECOND } from '../Atoms/timeUnits'; import { softFail } from '../Errors/Crash'; @@ -17,6 +22,10 @@ import { contextUnlockedPromise, foreverFetch, } from '../InitialContext'; +import { + useKeyboardShortcutLabel, + useManualKeyboardShortcut, +} from '../KeyboardShortcuts/hooks'; import { formatUrl } from '../Router/queryString'; import type { GenericPreferences, PreferenceItem } from './types'; @@ -88,7 +97,7 @@ export class BasePreferences { /** * Fetch preferences from back-end and update local cache with fetched values */ - async fetch(): Promise { + public async fetch(): Promise { const entryPoint = await contextUnlockedPromise; if (entryPoint === 'main') { if (typeof this.resourcePromise === 'object') return this.resourcePromise; @@ -365,12 +374,15 @@ export class BasePreferences { ): GetOrSet< DEFINITIONS[CATEGORY]['subCategories'][SUBCATEGORY]['items'][ITEM]['defaultValue'] > { + // eslint-disable-next-line react-hooks/rules-of-hooks const preferences = React.useContext(this.Context) ?? this; + // eslint-disable-next-line react-hooks/rules-of-hooks const [localValue, setLocalValue] = React.useState< DEFINITIONS[CATEGORY]['subCategories'][SUBCATEGORY]['items'][ITEM]['defaultValue'] >(() => preferences.get(category, subcategory, item)); + // eslint-disable-next-line react-hooks/rules-of-hooks React.useEffect( () => preferences.events.on('update', (payload) => { @@ -387,6 +399,7 @@ export class BasePreferences { [category, subcategory, item, preferences] ); + // eslint-disable-next-line react-hooks/rules-of-hooks const updatePref = React.useCallback( ( newPref: @@ -414,6 +427,59 @@ export class BasePreferences { return [localValue, updatePref] as const; } + + /** + * React Hook for reacting to keyboard shortcuts user pressed for a given + * action. + * + * The hook also returns a localized string representing the keyboard + * shortcut - this string can be displayed in UI tooltips. + */ + // eslint-disable-next-line max-params + public useKeyboardShortcut< + CATEGORY extends string & keyof DEFINITIONS, + SUBCATEGORY extends CATEGORY extends keyof DEFINITIONS + ? string & keyof DEFINITIONS[CATEGORY]['subCategories'] + : never, + ITEM extends string & + keyof DEFINITIONS[CATEGORY]['subCategories'][SUBCATEGORY]['items'] + >( + category: CATEGORY, + subcategory: SUBCATEGORY, + item: ITEM, + callback: (() => void) | undefined + ): LocalizedString { + const [shortcuts] = this.use(category, subcategory, item); + + const hasCallback = typeof callback === 'function'; + + // Calling hook conditionally like this is fine as this condition is constant during runtime of a page + if (process.env.NODE_ENV === 'development') { + // eslint-disable-next-line react-hooks/rules-of-hooks + React.useEffect(() => { + const definition = this.definition( + category, + subcategory, + item + ) as PreferenceItem; + if ( + !('renderer' in definition) || + definition.renderer.name !== 'KeyboardShortcutPreferenceItem' + ) + throw new Error( + `Trying to listen to keyboard shortcut on a non-keyboard shortcut preference item '${category}.${subcategory}.${item}'` + ); + }); + } + + // eslint-disable-next-line react-hooks/rules-of-hooks + useManualKeyboardShortcut(shortcuts, callback); + + // eslint-disable-next-line react-hooks/rules-of-hooks + const localizedShortcut = useKeyboardShortcutLabel(shortcuts); + + return hasCallback ? localizedShortcut : localized(''); + } } /* eslint-enable functional/no-this-expression */ diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/Editor.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/Editor.tsx index bac46be2988..66e5d76bbd8 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/Editor.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/Editor.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { useLiveState } from '../../hooks/useLiveState'; import type { AppResourceTabProps } from '../AppResources/TabDefinitions'; -import { PreferencesContent } from '../Preferences'; +import { PreferencesContent, usePrefDefinitions } from '../Preferences'; import { BasePreferences } from '../Preferences/BasePreferences'; import { userPreferenceDefinitions } from '../Preferences/UserDefinitions'; import { userPreferences } from '../Preferences/userPreferences'; @@ -34,9 +34,10 @@ export function UserPreferencesEditor({ ); const Context = userPreferences.Context; + const definitions = usePrefDefinitions('allUserPreferences'); return ( - + ); } diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index b8b0626bf75..b9cbc5425ca 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -19,6 +19,7 @@ import { reportsText } from '../../localization/report'; import { resourcesText } from '../../localization/resources'; import { schemaText } from '../../localization/schema'; import { statsText } from '../../localization/stats'; +import { treeText } from '../../localization/tree'; import type { Language } from '../../localization/utils/config'; import { LANGUAGE } from '../../localization/utils/config'; import { wbPlanText } from '../../localization/wbPlan'; @@ -38,6 +39,10 @@ import type { TableFields } from '../DataModel/helperTypes'; import { genericTables } from '../DataModel/tables'; import type { Collection, Tables } from '../DataModel/types'; import { error, softError } from '../Errors/assert'; +import type { KeyboardShortcuts } from '../KeyboardShortcuts/config'; +import { KeyboardShortcutPreferenceItem } from '../KeyboardShortcuts/Shortcuts'; +import type { UrlShortcuts } from '../KeyboardShortcuts/UrlShortcuts'; +import { UrlShortcutsEditor } from '../KeyboardShortcuts/UrlShortcuts'; import type { StatLayout } from '../Statistics/types'; import { LanguagePreferencesItem, @@ -52,7 +57,11 @@ import { HeaderItemsPreferenceItem, WelcomePageModePreferenceItem, } from './Renderers'; -import type { GenericPreferences, PreferencesVisibilityContext } from './types'; +import type { + GenericPreferences, + PreferenceItem, + PreferencesVisibilityContext, +} from './types'; import { definePref } from './types'; const isLightMode = ({ @@ -77,6 +86,30 @@ const altKeyName = const tableLabel = (tableName: keyof Tables): LocalizedString => genericTables[tableName]?.label ?? camelToHuman(tableName); +const defineKeyboardShortcut = ( + title: LocalizedString, + /** + * If defined a keyboard shortcut for one platform, it will be automatically + * transformed (`ctrl -> cmd`) for the other platforms. + * + * Thus, you should define keyboard shortcuts for the "other" platform only, + * unless you actually want to use different keyboard shortcuts on different + * systems. + */ + defaultValue: KeyboardShortcuts | string +): PreferenceItem => + definePref({ + title, + requiresReload: false, + visible: true, + defaultValue: + typeof defaultValue === 'string' + ? { other: [defaultValue] } + : defaultValue, + renderer: KeyboardShortcutPreferenceItem, + container: 'div', + }); + export const userPreferenceDefinitions = { general: { title: preferencesText.general(), @@ -581,7 +614,7 @@ export const userPreferenceDefinitions = { ], }), items: definePref({ - title: preferencesText.position(), + title: preferencesText.header(), requiresReload: false, visible: true, defaultValue: { @@ -608,6 +641,23 @@ export const userPreferenceDefinitions = { }), }, }, + actions: { + title: commonText.actions(), + items: { + urlShortcuts: definePref({ + title: preferencesText.urlShortcuts(), + requiresReload: false, + visible: true, + defaultValue: { + '/specify/user-keyboard-shortcuts': { + other: ['Ctrl+Slash'], + }, + }, + renderer: UrlShortcutsEditor, + container: 'div', + }), + }, + }, }, }, interactions: { @@ -802,6 +852,15 @@ export const userPreferenceDefinitions = { }), }, }, + actions: { + title: commonText.actions(), + items: { + openFormMeta: defineKeyboardShortcut( + preferencesText.openFormMeta(), + 'KeyM' + ), + }, + }, definition: { title: resourcesText.formDefinition(), items: { @@ -1103,6 +1162,10 @@ export const userPreferenceDefinitions = { defaultValue: false, type: 'java.lang.Boolean', }), + openRelatedRecordInNewTab: defineKeyboardShortcut( + preferencesText.openRelatedRecordInNewTab(), + 'KeyO' + ), }, }, recordSet: { @@ -1125,6 +1188,26 @@ export const userPreferenceDefinitions = { }, ], }), + goToFirstRecord: defineKeyboardShortcut( + formsText.goToFirstRecord(), + 'Ctrl+Shift+ArrowUp' + ), + goToPreviousRecord: defineKeyboardShortcut( + formsText.goToPreviousRecord(), + 'Ctrl+Shift+ArrowLeft' + ), + goToNextRecord: defineKeyboardShortcut( + formsText.goToNextRecord(), + 'Ctrl+Shift+ArrowRight' + ), + goToLastRecord: defineKeyboardShortcut( + formsText.goToLastRecord(), + 'Ctrl+Shift+ArrowDown' + ), + addResource: defineKeyboardShortcut( + resourcesText.addResource(), + 'KeyA' + ), }, }, formTable: { @@ -1428,6 +1511,19 @@ export const userPreferenceDefinitions = { }), }, }, + actions: { + title: commonText.actions(), + items: { + search: defineKeyboardShortcut(commonText.search(), 'KeyS'), + query: defineKeyboardShortcut(queryText.query(), 'KeyQ'), + edit: defineKeyboardShortcut(commonText.edit(), 'KeyE'), + add: defineKeyboardShortcut(treeText.addChild(), 'KeyA'), + move: defineKeyboardShortcut(treeText.moveNode(), 'KeyM'), + bulkMove: defineKeyboardShortcut(treeText.moveItems(), 'KeyB'), + merge: defineKeyboardShortcut(treeText.mergeNode(), 'KeyC'), + synonymize: defineKeyboardShortcut(treeText.synonymizeNode(), 'KeyY'), + }, + }, geography: { /* * This would be replaced with labels from schema once @@ -1691,6 +1787,18 @@ export const userPreferenceDefinitions = { }), }, }, + actions: { + title: commonText.actions(), + items: { + query: defineKeyboardShortcut(queryText.query(), 'KeyR'), + count: defineKeyboardShortcut(queryText.countOnly(), 'KeyC'), + distinct: defineKeyboardShortcut(queryText.distinct(), 'KeyD'), + browseInForms: defineKeyboardShortcut( + queryText.browseInForms(), + 'KeyF' + ), + }, + }, }, }, recordMerging: { diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/index.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/index.tsx index dff12b62910..8a43c66afea 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/index.tsx @@ -42,7 +42,16 @@ const preferencesPromise = Promise.all([ collectionPreferences.fetch(), ]).then(f.true); -function Preferences(): JSX.Element { +export type PreferencesFilter = + | 'allUserPreferences' + | 'userKeyboardShortcuts' + | 'userPreferences'; + +function Preferences({ + filter, +}: { + readonly filter: PreferencesFilter; +}): JSX.Element { const [changesMade, handleChangesMade] = useBooleanState(); const [needsRestart, handleRestartNeeded] = useBooleanState(); @@ -66,6 +75,8 @@ function Preferences(): JSX.Element { references, } = useTopChild(); + const definitions = usePrefDefinitions(filter); + return (

    {preferencesText.preferences()}

    @@ -89,10 +100,14 @@ function Preferences(): JSX.Element { > - +
    @@ -109,8 +124,8 @@ function Preferences(): JSX.Element { ); } -/** Hide invisible preferences. Remote empty categories and subCategories */ -export function usePrefDefinitions() { +/** Hide invisible preferences. Remove empty categories and subCategories */ +export function usePrefDefinitions(filter: PreferencesFilter) { const isDarkMode = useDarkMode(); const isRedirecting = React.useContext(userPreferences.Context) !== undefined; const preferencesVisibilityContext = React.useMemo( @@ -138,10 +153,30 @@ export function usePrefDefinitions() { { ...subCategoryData, items: Object.entries(items).filter( - ([_name, { visible }]) => - typeof visible === 'function' - ? visible(preferencesVisibilityContext) - : visible !== false + ([_name, item]) => { + const visible = + typeof item.visible === 'function' + ? item.visible(preferencesVisibilityContext) + : item.visible !== false; + if (!visible) return false; + + if (filter !== 'allUserPreferences') { + const isKeyboardShortcut = + 'renderer' in item && + (item.renderer.name === + 'KeyboardShortcutPreferenceItem' || + item.renderer.name === + 'UrlShortcutsEditor'); + const showKeyboardShortcuts = + filter === 'userKeyboardShortcuts'; + + return ( + isKeyboardShortcut === showKeyboardShortcuts + ); + } + + return true; + } ), }, ] as const @@ -151,24 +186,25 @@ export function usePrefDefinitions() { ] as const ) .filter(([_name, { subCategories }]) => subCategories.length > 0), - [preferencesVisibilityContext] + [preferencesVisibilityContext, filter] ); } export function PreferencesContent({ forwardRefs, + definitions, }: { readonly forwardRefs?: (index: number, element: HTMLElement | null) => void; + readonly definitions: ReturnType; }): JSX.Element { const isReadOnly = React.useContext(ReadOnlyContext); - const definitions = usePrefDefinitions(); return ( -
    +
    {definitions.map( ( [category, { title, description = undefined, subCategories }], index - ) => ( + ): JSX.Element => ( {key}, + // eslint-disable-next-line react/no-unstable-nested-components + key: (key): JSX.Element => {key}, }} string={text} /> @@ -364,7 +401,14 @@ function Item({ ); } -export function PreferencesWrapper(): JSX.Element | null { +export function UserPreferencesWrapper(): JSX.Element | null { + const [hasFetched] = usePromise(preferencesPromise, true); + return hasFetched === true ? : null; +} + +export function KeyboardShortcutsWrapper(): JSX.Element | null { const [hasFetched] = usePromise(preferencesPromise, true); - return hasFetched === true ? : null; + return hasFetched === true ? ( + + ) : null; } diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/ToForms.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/ToForms.tsx index 26c79100014..9f97856251f 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/ToForms.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/ToForms.tsx @@ -8,6 +8,7 @@ import type { RA } from '../../utils/types'; import { Button } from '../Atoms/Button'; import type { SpecifyTable } from '../DataModel/specifyTable'; import { RecordSelectorFromIds } from '../FormSliders/RecordSelectorFromIds'; +import { userPreferences } from '../Preferences/userPreferences'; import type { QueryResultRow } from './Results'; import { queryIdField } from './Results'; @@ -34,12 +35,17 @@ export function QueryToForms({ ? (results[index]![queryIdField] as number) : Array.from(selectedRows)[index]; + const handleStart = (totalCount ?? 0) > 0 ? handleOpen : undefined; + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'queryBuilder', + 'actions', + 'browseInForms', + handleStart + ); + return ( <> - + {queryText.browseInForms()} {isOpen && typeof totalCount === 'number' ? ( diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Toolbar.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Toolbar.tsx index 7a609c0c19e..05f360c14e2 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Toolbar.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Toolbar.tsx @@ -8,6 +8,7 @@ import { Submit } from '../Atoms/Submit'; import type { Tables } from '../DataModel/types'; import { isTreeTable } from '../InitialContext/treeRanks'; import { hasPermission } from '../Permissions/helpers'; +import { userPreferences } from '../Preferences/userPreferences'; export function QueryToolbar({ showHiddenFields, @@ -18,7 +19,8 @@ export function QueryToolbar({ onToggleHidden: handleToggleHidden, onToggleDistinct: handleToggleDistinct, onToggleSeries: handleToggleSeries, - onRunCountOnly: handleRunCountOnly, + onScheduleCountOnlyRun: handleScheduleCountOnlyRun, + onScheduleRun: handleScheduleRun, onSubmitClick: handleSubmitClick, }: { readonly showHiddenFields: boolean; @@ -29,9 +31,36 @@ export function QueryToolbar({ readonly onToggleHidden: (value: boolean) => void; readonly onToggleDistinct: () => void; readonly onToggleSeries: () => void; - readonly onRunCountOnly: () => void; + readonly onScheduleCountOnlyRun: () => void; + readonly onScheduleRun: () => void; readonly onSubmitClick: () => void; }): JSX.Element { + const canRun = hasPermission('/querybuilder/query', 'execute'); + /* + * Query Distinct for trees is disabled because of + * https://github.com/specify/specify7/pull/1019#issuecomment-973525594 + */ + const canRunDistinct = canRun && !isTreeTable(tableName); + + const runDistinctKeyboardShortcut = userPreferences.useKeyboardShortcut( + 'queryBuilder', + 'actions', + 'distinct', + canRunDistinct ? handleToggleDistinct : undefined + ); + const runCountOnlyKeyboardShortcut = userPreferences.useKeyboardShortcut( + 'queryBuilder', + 'actions', + 'count', + canRun ? handleScheduleCountOnlyRun : undefined + ); + const runQueryKeyboardShortcut = userPreferences.useKeyboardShortcut( + 'queryBuilder', + 'actions', + 'query', + canRun ? handleScheduleRun : undefined + ); + return (
    @@ -42,7 +71,7 @@ export function QueryToolbar({ {wbPlanText.revealHiddenFormFields()} - {hasPermission('/querybuilder/query', 'execute') && ( + {canRun && ( <> {showSeries && ( @@ -58,8 +87,8 @@ export function QueryToolbar({ * Query Distinct for trees is disabled because of * https://github.com/specify/specify7/pull/1019#issuecomment-973525594 */} - {!isTreeTable(tableName) && ( - + {canRunDistinct && ( + )} - + {queryText.countOnly()} - + {queryText.query()} diff --git a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Wrapped.tsx b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Wrapped.tsx index 3e374e330b5..17138d0202c 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryBuilder/Wrapped.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryBuilder/Wrapped.tsx @@ -240,6 +240,36 @@ function Wrapped({ globalThis.setTimeout(() => dispatch({ type: 'RunQueryAction' }), 0); } + function scheduleRun(type: 'count' | 'regular'): void { + /* + * If a filter for a query field was changed, and the is + * still focused, the new value is not yet in global state. + * The value would be in global state after onBlur on . + * If user hits "Enter", the form submission event is fired before + * onBlur (at least in Chrome and Firefox), and the query is run + * with the stale query field filter. This does not happen if query + * is run by pressing the "Query" button as that triggers onBlur + * + * The workaround is to check if input field is focused before + * submitting the query, and if it is, trigger blur, wait for + * global state to get updated and only then re run the query. + * + * See more: https://github.com/specify/specify7/issues/1647 + */ + const focusedInput = + document.activeElement?.tagName === 'INPUT' + ? (document.activeElement as HTMLInputElement) + : undefined; + if (typeof focusedInput === 'object' && focusedInput.type !== 'submit') { + // Trigger onBlur handler that parses the filter field value + focusedInput.blur(); + // Return focus back to the field + focusedInput.focus(); + // ReRun the query after React propagates the change + setPendingRun(type); + } else runQuery(type); + } + /* * Require only one of these permissions as query builder could be useful with * just one of these @@ -271,15 +301,16 @@ function Wrapped({ useTitle(localized(query.name)); - const [isQueryRunPending, handleQueryRunPending, handleNoQueryRunPending] = - useBooleanState(); + const [pendingRun, setPendingRun] = React.useState< + 'count' | 'regular' | undefined + >(undefined); React.useEffect(() => { - if (!isQueryRunPending) return; - handleNoQueryRunPending(); - runQuery('regular'); - // Only reRun when isQueryRunPending is true, not when runQuery changes + if (pendingRun === undefined) return; + runQuery(pendingRun); + setPendingRun(undefined); + // Only reRun when pendingRun, not when runQuery changes // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isQueryRunPending, handleNoQueryRunPending]); + }, [pendingRun]); const [isScrolledTop, handleScrollTop, handleScrolledDown] = useBooleanState(true); @@ -376,10 +407,10 @@ function Wrapped({
    /* @@ -390,38 +421,7 @@ function Wrapped({ ? handleScrollTop() : handleScrolledDown() } - onSubmit={(): void => { - /* - * If a filter for a query field was changed, and the is - * still focused, the new value is not yet in global state. - * The value would be in global state after onBlur on . - * If user hits "Enter", the form submission event is fired before - * onBlur (at least in Chrome and Firefox), and the query is run - * with the stale query field filter. This does not happen if query - * is run by pressing the "Query" button as that triggers onBlur - * - * The workaround is to check if input field is focused before - * submitting the query, and if it is, trigger blur, wait for - * global state to get updated and only then re run the query. - * - * See more: https://github.com/specify/specify7/issues/1647 - */ - const focusedInput = - document.activeElement?.tagName === 'INPUT' - ? (document.activeElement as HTMLInputElement) - : undefined; - if ( - typeof focusedInput === 'object' && - focusedInput.type !== 'submit' - ) { - // Trigger onBlur handler that parses the filter field value - focusedInput.blur(); - // Return focus back to the field - focusedInput.focus(); - // ReRun the query after React propagates the change - handleQueryRunPending(); - } else runQuery('regular'); - }} + onSubmit={(): void => scheduleRun('regular')} >
    {showMappingView && ( @@ -582,8 +582,15 @@ function Wrapped({ showHiddenFields={showHiddenFields} showSeries={showSeries} tableName={table.name} - onRunCountOnly={(): void => runQuery('count')} + onScheduleCountOnlyRun={(): void => scheduleRun('count')} + onScheduleRun={(): void => scheduleRun('regular')} onSubmitClick={(): void => + /* + * If form has no validation issues, the will + * take care of running the query. If form has issues, we + * still want to execute the query (Form onSubmit is not fired + * in that case so we have to do it manually) + */ form?.checkValidity() === false ? runQuery('regular') : undefined diff --git a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx index f905818de76..2587e3fa92b 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx @@ -460,6 +460,7 @@ export function QueryComboBox({ {canAdd && hasNewButton ? ( state.type === 'AddResourceState' ? setState({ type: 'MainState' }) diff --git a/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx b/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx index 0dd82a1e8f0..290b3c339fe 100644 --- a/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx +++ b/specifyweb/frontend/js_src/lib/components/Router/OverlayRoutes.tsx @@ -111,7 +111,6 @@ export const overlayRoutes: RA = [ }, { path: 'queries', - title: queryText.queries(), element: () => import('../Toolbar/Query').then( ({ QueriesOverlay }) => QueriesOverlay @@ -119,6 +118,7 @@ export const overlayRoutes: RA = [ children: [ { index: true, + title: queryText.queries(), element: () => import('../Toolbar/Query').then( ({ QueryListOutlet }) => QueryListOutlet diff --git a/specifyweb/frontend/js_src/lib/components/Router/RouterUtils.tsx b/specifyweb/frontend/js_src/lib/components/Router/RouterUtils.tsx index 79c9a96c848..0ef3f085288 100644 --- a/specifyweb/frontend/js_src/lib/components/Router/RouterUtils.tsx +++ b/specifyweb/frontend/js_src/lib/components/Router/RouterUtils.tsx @@ -57,7 +57,7 @@ let index = 0; /** Convert EnhancedRoutes to RouteObjects */ export const toReactRoutes = ( enhancedRoutes: RA, - title?: LocalizedString, + parentTitle?: LocalizedString, dismissible: boolean = true ): WritableArray => enhancedRoutes.map((data) => { @@ -79,9 +79,15 @@ export const toReactRoutes = ( ) ); + const resolvedTitle = + data.title === undefined + ? data.index === true + ? parentTitle + : undefined + : data.title; const titleComponent = - typeof title === 'string' ? ( - + typeof resolvedTitle === 'string' ? ( + ) : undefined; const resolvedElement = @@ -91,7 +97,7 @@ export const toReactRoutes = ( {ReactLazy(rawElement)({})} ) : rawElement === undefined ? ( - enhancedRoute.index ? ( + enhancedRoute.index === true ? ( <> ) : undefined ) : ( @@ -108,7 +114,7 @@ export const toReactRoutes = ( ...enhancedRoute, index: enhancedRoute.index as unknown as false, children: Array.isArray(children) - ? toReactRoutes(children, title) + ? toReactRoutes(children, resolvedTitle) : undefined, element: process.env.NODE_ENV === 'test' || diff --git a/specifyweb/frontend/js_src/lib/components/Router/Routes.tsx b/specifyweb/frontend/js_src/lib/components/Router/Routes.tsx index e7fef4d8f33..18e87138a01 100644 --- a/specifyweb/frontend/js_src/lib/components/Router/Routes.tsx +++ b/specifyweb/frontend/js_src/lib/components/Router/Routes.tsx @@ -6,6 +6,7 @@ import { headerText } from '../../localization/header'; import { preferencesText } from '../../localization/preferences'; import { resourcesText } from '../../localization/resources'; import { schemaText } from '../../localization/schema'; +import { specifyNetworkText } from '../../localization/specifyNetwork'; import { statsText } from '../../localization/stats'; import { userText } from '../../localization/user'; import { welcomeText } from '../../localization/welcome'; @@ -14,11 +15,11 @@ import type { RA } from '../../utils/types'; import { Redirect } from './Redirect'; import type { EnhancedRoute } from './RouterUtils'; -// FEATURE: go over non-dynamic routes in all routers to make sure they have titles /* eslint-disable @typescript-eslint/promise-function-async */ export const routes: RA = [ { path: 'specify-network-collection', + title: specifyNetworkText.specifyNetwork(), element: () => import('../SpecifyNetworkCollection').then( ({ SpecifyNetworkCollection }) => SpecifyNetworkCollection @@ -89,6 +90,7 @@ export const routes: RA = [ children: [ { path: 'create', + title: userText.createRole(), element: () => import('../Security/CreateLibraryRole').then( ({ CreateLibraryRole }) => CreateLibraryRole @@ -229,11 +231,11 @@ export const routes: RA = [ path: 'import', children: [ { + title: headerText.localityUpdateTool(), path: 'locality-dataset', element: () => import('../LocalityUpdate').then( - ({ LocalityUpdateFromDataSet: ImportLocalitySet }) => - ImportLocalitySet + ({ LocalityUpdateFromDataSet }) => LocalityUpdateFromDataSet ), }, ], @@ -365,12 +367,19 @@ export const routes: RA = [ title: preferencesText.preferences(), element: () => import('../Preferences').then( - ({ PreferencesWrapper }) => PreferencesWrapper + ({ UserPreferencesWrapper }) => UserPreferencesWrapper + ), + }, + { + path: 'user-keyboard-shortcuts', + title: preferencesText.keyboardShortcuts(), + element: () => + import('../Preferences').then( + ({ KeyboardShortcutsWrapper }) => KeyboardShortcutsWrapper ), }, { path: 'schema-config', - title: schemaText.schemaConfig(), element: () => import('../Toolbar/SchemaConfig').then( ({ SchemaConfig }) => SchemaConfig @@ -378,6 +387,7 @@ export const routes: RA = [ children: [ { index: true, + title: schemaText.schemaConfig(), element: () => import('../SchemaConfig/Languages').then( ({ ChooseSchemaLanguage }) => ChooseSchemaLanguage diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/Actions.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/Actions.tsx index 52c6f2d4086..fc6bdfd983e 100644 --- a/specifyweb/frontend/js_src/lib/components/TreeView/Actions.tsx +++ b/specifyweb/frontend/js_src/lib/components/TreeView/Actions.tsx @@ -8,6 +8,7 @@ import { treeText } from '../../localization/tree'; import { formData } from '../../utils/ajax/helpers'; import { ping } from '../../utils/ajax/ping'; import type { GetSet, RA, RR } from '../../utils/types'; +import { localized } from '../../utils/types'; import { toLowerCase } from '../../utils/utils'; import { Button } from '../Atoms/Button'; import { Link } from '../Atoms/Link'; @@ -21,6 +22,7 @@ import { getPref } from '../InitialContext/remotePrefs'; import { Dialog } from '../Molecules/Dialog'; import { ResourceLink } from '../Molecules/ResourceLink'; import { hasPermission, hasTablePermission } from '../Permissions/helpers'; +import { userPreferences } from '../Preferences/userPreferences'; import type { Row } from './helpers'; import { checkMoveViolatesEnforced } from './helpers'; @@ -78,11 +80,14 @@ export function TreeViewActions({ const isReadOnly = React.useContext(ReadOnlyContext); - return currentAction === undefined || + const displayButtons = + currentAction === undefined || actionRow === undefined || focusedRow === undefined || currentAction === 'add' || - currentAction === 'edit' ? ( + currentAction === 'edit'; + + return displayButtons ? ( {hasPermission('/querybuilder/query', 'execute') && (
  • @@ -94,14 +99,10 @@ export function TreeViewActions({ onClick={undefined} /> ) : ( - ) ) : ( @@ -130,7 +131,7 @@ export function TreeViewActions({ disabled={disableButtons} nodeId={focusedRow?.nodeId} tableName={tableName} - onDeleted={() => { + onDeleted={(): void => { handleRefresh(); setFocusPath(focusPath?.slice(0, -1)); }} @@ -157,32 +158,35 @@ export function TreeViewActions({ )} {hasPermission(resourceName, 'move') && (
  • - setAction('move')} + setAction('move') + } />
  • )} {tableName === 'Storage' && hasPermission(resourceName as '/tree/edit/storage', 'bulk_move') ? (
  • - setAction('bulkMove')} + setAction('bulkMove') + } />
  • ) : null} {hasPermission(resourceName, 'merge') && (
  • - setAction('merge')} + setAction('merge') + } />
  • )} @@ -191,19 +195,18 @@ export function TreeViewActions({ isSynonym ? 'desynonymize' : 'synonymize' ) && (
  • - 0) - } - icon={isSynonym ? 'undoSynonym' : 'synonym'} - title={isSynonym ? treeText.undoSynonymy() : treeText.synonymize()} - onClick={(): void => - setAction(isSynonym ? 'desynonymize' : 'synonymize') + ? undefined + : (): void => + setAction(isSynonym ? 'desynonymize' : 'synonymize') } />
  • @@ -225,6 +228,123 @@ export function TreeViewActions({ ); } +function QueryButton({ + focusRef, + focusedRow, + tableName, +}: { + readonly focusRef: React.MutableRefObject; + readonly focusedRow: Row; + readonly tableName: AnyTree['tableName']; +}): JSX.Element { + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + 'query', + () => window.open(href, '_blank') + ); + + const href = `/specify/query/fromtree/${tableName.toLowerCase()}/${ + focusedRow.nodeId + }/`; + + return ( + + ); +} + +function MoveButton({ + onClick: handleClick, +}: { + readonly onClick: (() => void) | undefined; +}): JSX.Element { + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + 'move', + handleClick + ); + return ( + + ); +} + +function BulkMoveButton({ + onClick: handleClick, +}: { + readonly onClick: (() => void) | undefined; +}): JSX.Element { + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + 'bulkMove', + handleClick + ); + return ( + + ); +} + +function MergeButton({ + onClick: handleClick, +}: { + readonly onClick: (() => void) | undefined; +}): JSX.Element { + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + 'merge', + handleClick + ); + return ( + + ); +} + +function SynonymizeButton({ + isSynonym, + onClick: handleClick, +}: { + readonly isSynonym: boolean; + readonly onClick: (() => void) | undefined; +}): JSX.Element { + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + 'synonymize', + handleClick + ); + return ( + + ); +} + function EditRecordDialog({ nodeId, disabled, @@ -259,6 +379,14 @@ function EditRecordDialog({ const isViewMode = !hasTablePermission(tableName, 'update'); + const toggleRef = React.useRef<() => void>(); + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + addNew ? 'add' : 'edit', + disabled ? undefined : (): void => toggleRef.current?.() + ); + return ( <> {disabled ? ( @@ -274,15 +402,18 @@ function EditRecordDialog({ props={{ 'aria-disabled': disabled, icon: isViewMode ? 'eye' : addNew ? 'plus' : 'pencil', - title: label, + title: localized(`${label}${keyboardShortcut}`), }} resource={resource} resourceView={{ dialog: 'nonModal', - onAdd: isRoot ? undefined : ([resource]) => setResource(resource), + onAdd: isRoot + ? undefined + : ([resource]): void => setResource(resource), onDeleted: handleRefresh, onSaved: handleRefresh, }} + toggleRef={toggleRef} /> )} @@ -369,16 +500,25 @@ function ActiveAction({ if (isSameRecord) disabled = title; else if (isSynonym) disabled = treeText.cantSynonymizeSynonym(); } + + const handleClick = + disabled === false ? (): void => setShowPrompt(true) : undefined; + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + type === 'desynonymize' ? 'synonymize' : type, + handleClick + ); + return ( { focusRef.current = element; }} - title={title} - onClick={(): void => setShowPrompt(true)} + title={`${title}${keyboardShortcut}`} + onClick={handleClick} > {typeof disabled === 'string' ? disabled diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/Search.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/Search.tsx index 59468149d3e..cdbd5a2ba1c 100644 --- a/specifyweb/frontend/js_src/lib/components/TreeView/Search.tsx +++ b/specifyweb/frontend/js_src/lib/components/TreeView/Search.tsx @@ -34,7 +34,7 @@ export function TreeViewSearch({ readonly treeDefinitionItems: RA< SerializedResource> >; - readonly forwardRef: React.RefObject; + readonly forwardRef: React.MutableRefObject; readonly onFocusPath: (focusPath: RA) => void; }): JSX.Element { const [searchValue, setSearchValue] = React.useState(''); @@ -60,16 +60,27 @@ export function TreeViewSearch({ searchAlgorithm ); + const searchBoxRef = React.useRef(null); + const keyboardShortcut = userPreferences.useKeyboardShortcut( + 'treeEditor', + 'actions', + 'search', + (): void => searchBoxRef.current?.focus() + ); + return (
    {/* A React component that is also a TypeScript generic */} > filterItems={false} - forwardRef={forwardRef} + forwardRef={(element): void => { + forwardRef.current = element; + searchBoxRef.current = element; + }} inputProps={{ 'aria-label': treeText.searchTreePlaceholder(), placeholder: treeText.searchTreePlaceholder(), - title: treeText.searchTreePlaceholder(), + title: `${treeText.searchTreePlaceholder()}${keyboardShortcut}`, }} source={async (value) => fetchCollection( diff --git a/specifyweb/frontend/js_src/lib/components/TreeView/index.tsx b/specifyweb/frontend/js_src/lib/components/TreeView/index.tsx index 61cc612aa62..3d88d4012cd 100644 --- a/specifyweb/frontend/js_src/lib/components/TreeView/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/TreeView/index.tsx @@ -15,7 +15,7 @@ import { type GetSet, type RA } from '../../utils/types'; import { caseInsensitiveHash } from '../../utils/utils'; import { Container, H2 } from '../Atoms'; import { Button } from '../Atoms/Button'; -import { Input, Label, Select } from '../Atoms/Form'; +import { Select } from '../Atoms/Form'; import { ChronoChart } from '../Attachments/ChronoChart'; import type { AnyTree, @@ -254,7 +254,7 @@ function TreeView({ rows={rows} searchBoxRef={searchBoxRef} setFocusedRow={type === lastFocusedTree ? setLastFocusedRow : undefined} - setLastFocusedTree={() => setLastFocusedTree(type)} + setLastFocusedTree={(): void => setLastFocusedTree(type)} tableName={tableName} treeDefinitionItems={treeDefinitionItems} onToggleEditingRanks={handleToggleEditingRanks} @@ -317,32 +317,46 @@ function TreeView({ onFocusPath={currentStates.focusPath[1]} /> {tableName === 'GeologicTimePeriod' ? : undefined} + setHideEmptyNodes(!hideEmptyNodes)} + /> setRawIsSplit(!rawIsSplit)} - /> - { - setIsHorizontal(!isHorizontal); - if (!isHorizontal) resetDimensions(); - }} - /> - { - lastFocusedTree === 'first' - ? states.second.focusPath[1](states[lastFocusedTree].focusPath[0]) - : states.first.focusPath[1](states[lastFocusedTree].focusPath[0]); - }} + onClick={(): void => setRawIsSplit(!rawIsSplit)} /> + {isSplit && ( + <> + { + setIsHorizontal(!isHorizontal); + if (!isHorizontal) resetDimensions(); + }} + /> + + lastFocusedTree === 'first' + ? states.second.focusPath[1]( + states[lastFocusedTree].focusPath[0] + ) + : states.first.focusPath[1]( + states[lastFocusedTree].focusPath[0] + ) + } + /> + + )} ({ ) : ( treeContainer('first') )} - - - {treeText.associatedNodesOnly()} - ); } diff --git a/specifyweb/frontend/js_src/lib/components/WebLinks/index.tsx b/specifyweb/frontend/js_src/lib/components/WebLinks/index.tsx index 89ecaaa0237..fa2bb0e6416 100644 --- a/specifyweb/frontend/js_src/lib/components/WebLinks/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/WebLinks/index.tsx @@ -53,13 +53,10 @@ export function WebLinkField({ const url = builtUrl ?.map((part) => (typeof part === 'string' ? part : '')) .join(''); - const isExternal = React.useMemo(() => { - try { - return url !== undefined && isExternalUrl(url); - } catch { - return true; - } - }, [url]); + const isExternal = React.useMemo( + () => url !== undefined && isExternalUrl(url), + [url] + ); React.useEffect(() => { if ( diff --git a/specifyweb/frontend/js_src/lib/localization/forms.ts b/specifyweb/frontend/js_src/lib/localization/forms.ts index 3f3fff1ee75..edaa1f5c115 100644 --- a/specifyweb/frontend/js_src/lib/localization/forms.ts +++ b/specifyweb/frontend/js_src/lib/localization/forms.ts @@ -5,1283 +5,1297 @@ * @module */ -import { createDictionary } from "./utils"; +import { createDictionary } from './utils'; // Refer to "Guidelines for Programmers" in ./README.md before editing this file export const formsText = createDictionary({ forms: { - "en-us": "Forms", - "ru-ru": "Формы", - "es-es": "Formularios", - "fr-fr": "Formulaires", - "uk-ua": "Форми", - "de-ch": "Formulare", - "pt-br": "Formulários", + 'en-us': 'Forms', + 'ru-ru': 'Формы', + 'es-es': 'Formularios', + 'fr-fr': 'Formulaires', + 'uk-ua': 'Форми', + 'de-ch': 'Formulare', + 'pt-br': 'Formulários', }, clone: { - "en-us": "Clone", - "ru-ru": "Клон", - "es-es": "Clon", - "fr-fr": "Cloner", - "uk-ua": "Клон", - "de-ch": "Klone", - "pt-br": "Clone", + 'en-us': 'Clone', + 'ru-ru': 'Клон', + 'es-es': 'Clon', + 'fr-fr': 'Cloner', + 'uk-ua': 'Клон', + 'de-ch': 'Klone', + 'pt-br': 'Clone', }, cloneDescription: { - "en-us": "Create a full copy of current record", - "ru-ru": "Создать полную копию текущей записи", - "es-es": "Crear una copia completa del registro actual", - "fr-fr": "Créer une copie complète de l'enregistrement actuel", - "uk-ua": "Створити повну копію поточного запису", - "de-ch": "Erstellen einer kompletten Kopie des aktuellen Datensatzes", - "pt-br": "Crie uma cópia completa do registro atual", + 'en-us': 'Create a full copy of current record', + 'ru-ru': 'Создать полную копию текущей записи', + 'es-es': 'Crear una copia completa del registro actual', + 'fr-fr': "Créer une copie complète de l'enregistrement actuel", + 'uk-ua': 'Створити повну копію поточного запису', + 'de-ch': 'Erstellen einer kompletten Kopie des aktuellen Datensatzes', + 'pt-br': 'Crie uma cópia completa do registro atual', }, valueMustBeUniqueToField: { - "en-us": "Value must be unique to {fieldName:string}", - "ru-ru": "Значение должно быть уникальным для {fieldName:string}", - "es-es": "El valor debe ser exclusivo de {fieldName:string}", - "fr-fr": "La valeur doit être unique à {fieldName:string}", - "uk-ua": "Значення має бути унікальним для {fieldName:string}", - "de-ch": "Der Wert muss für {fieldName:string} eindeutig sein", - "pt-br": "O valor deve ser exclusivo para {fieldName:string}", + 'en-us': 'Value must be unique to {fieldName:string}', + 'ru-ru': 'Значение должно быть уникальным для {fieldName:string}', + 'es-es': 'El valor debe ser exclusivo de {fieldName:string}', + 'fr-fr': 'La valeur doit être unique à {fieldName:string}', + 'uk-ua': 'Значення має бути унікальним для {fieldName:string}', + 'de-ch': 'Der Wert muss für {fieldName:string} eindeutig sein', + 'pt-br': 'O valor deve ser exclusivo para {fieldName:string}', }, valueMustBeUniqueToDatabase: { - "en-us": "Value must be unique to database", - "ru-ru": "Значение должно быть уникальным в базе данных.", - "es-es": "El valor debe ser exclusivo de la base de datos.", - "fr-fr": "La valeur doit être unique dans la base de données", - "uk-ua": "Значення має бути унікальним для бази даних", - "de-ch": "Der Wert muss in der Datenbank eindeutig sein", - "pt-br": "O valor deve ser exclusivo para o banco de dados", + 'en-us': 'Value must be unique to database', + 'ru-ru': 'Значение должно быть уникальным в базе данных.', + 'es-es': 'El valor debe ser exclusivo de la base de datos.', + 'fr-fr': 'La valeur doit être unique dans la base de données', + 'uk-ua': 'Значення має бути унікальним для бази даних', + 'de-ch': 'Der Wert muss in der Datenbank eindeutig sein', + 'pt-br': 'O valor deve ser exclusivo para o banco de dados', }, valuesOfMustBeUniqueToField: { - "en-us": "Values of {values:string} must be unique to {fieldName:string}", - "ru-ru": - "Значения {values:string} должны быть уникальными для {fieldName:string}", - "es-es": - "Los valores de {values:string} deben ser únicos para {fieldName:string}", - "fr-fr": - "Les valeurs de {values:string} doivent être uniques à {fieldName:string}", - "uk-ua": - "Значення {values:string} мають бути унікальними для {fieldName:string}", - "de-ch": - "Werte von {values:string} müssen für {fieldName:string} eindeutig sein.", - "pt-br": - "Os valores de {values:string} devem ser exclusivos de {fieldName:string}", + 'en-us': 'Values of {values:string} must be unique to {fieldName:string}', + 'ru-ru': + 'Значения {values:string} должны быть уникальными для {fieldName:string}', + 'es-es': + 'Los valores de {values:string} deben ser únicos para {fieldName:string}', + 'fr-fr': + 'Les valeurs de {values:string} doivent être uniques à {fieldName:string}', + 'uk-ua': + 'Значення {values:string} мають бути унікальними для {fieldName:string}', + 'de-ch': + 'Werte von {values:string} müssen für {fieldName:string} eindeutig sein.', + 'pt-br': + 'Os valores de {values:string} devem ser exclusivos de {fieldName:string}', }, valuesOfMustBeUniqueToDatabase: { - "en-us": "Values of {values:string} must be unique to database", - "ru-ru": "Значения {values:string} должны быть уникальными в базе данных.", - "es-es": - "Los valores de {values:string} deben ser únicos para la base de datos.", - "fr-fr": - "Les valeurs de {values:string} doivent être uniques à la base de données", - "uk-ua": "Значення {values:string} мають бути унікальними для бази даних", - "de-ch": "Werte von {values:string} müssen in der Datenbank eindeutig sein", - "pt-br": - "Os valores de {values:string} devem ser exclusivos do banco de dados", + 'en-us': 'Values of {values:string} must be unique to database', + 'ru-ru': 'Значения {values:string} должны быть уникальными в базе данных.', + 'es-es': + 'Los valores de {values:string} deben ser únicos para la base de datos.', + 'fr-fr': + 'Les valeurs de {values:string} doivent être uniques à la base de données', + 'uk-ua': 'Значення {values:string} мають бути унікальними для бази даних', + 'de-ch': 'Werte von {values:string} müssen in der Datenbank eindeutig sein', + 'pt-br': + 'Os valores de {values:string} devem ser exclusivos do banco de dados', }, checkingIfResourceCanBeDeleted: { - "en-us": "Checking if resource can be deleted…", - "ru-ru": "Проверка возможности удаления ресурса…", - "es-es": "Comprobando si el recurso se puede eliminar…", - "fr-fr": "Vérification si la ressource peut être supprimée…", - "uk-ua": "Перевірка можливості видалення ресурсу…", - "de-ch": "Überprüfen, ob die Ressource gelöscht werden kann …", - "pt-br": "Verificando se o recurso pode ser excluído…", + 'en-us': 'Checking if resource can be deleted…', + 'ru-ru': 'Проверка возможности удаления ресурса…', + 'es-es': 'Comprobando si el recurso se puede eliminar…', + 'fr-fr': 'Vérification si la ressource peut être supprimée…', + 'uk-ua': 'Перевірка можливості видалення ресурсу…', + 'de-ch': 'Überprüfen, ob die Ressource gelöscht werden kann …', + 'pt-br': 'Verificando se o recurso pode ser excluído…', }, deleteBlocked: { - "en-us": "Delete blocked", - "ru-ru": "Удалить заблокированный", - "es-es": "Eliminar bloqueado", - "fr-fr": "Supprimer bloqué", - "uk-ua": "Видалити заблоковано", - "de-ch": "Gesperrte löschen", - "pt-br": "Excluir bloqueado", + 'en-us': 'Delete blocked', + 'ru-ru': 'Удалить заблокированный', + 'es-es': 'Eliminar bloqueado', + 'fr-fr': 'Supprimer bloqué', + 'uk-ua': 'Видалити заблоковано', + 'de-ch': 'Gesperrte löschen', + 'pt-br': 'Excluir bloqueado', }, deleteBlockedDescription: { - "en-us": - "The resource cannot be deleted because it is referenced by the following resources:", - "de-ch": - "Die Ressource kann nicht gelöscht werden, da sie von den folgenden Ressourcen referenziert wird:", - "es-es": "encontrar usos", - "fr-fr": - "La ressource ne peut pas être supprimée car elle est référencée par les ressources suivantes :", - "ru-ru": - "Ресурс не может быть удален, поскольку на него ссылаются следующие ресурсы:", - "uk-ua": - "Ресурс не можна видалити, оскільки на нього посилаються такі ресурси:", - "pt-br": - "O recurso não pode ser excluído porque é referenciado pelos seguintes recursos:", + 'en-us': + 'The resource cannot be deleted because it is referenced by the following resources:', + 'de-ch': + 'Die Ressource kann nicht gelöscht werden, da sie von den folgenden Ressourcen referenziert wird:', + 'es-es': 'encontrar usos', + 'fr-fr': + 'La ressource ne peut pas être supprimée car elle est référencée par les ressources suivantes :', + 'ru-ru': + 'Ресурс не может быть удален, поскольку на него ссылаются следующие ресурсы:', + 'uk-ua': + 'Ресурс не можна видалити, оскільки на нього посилаються такі ресурси:', + 'pt-br': + 'O recurso não pode ser excluído porque é referenciado pelos seguintes recursos:', }, relationship: { - "en-us": "Relationship", - "ru-ru": "Отношение", - "es-es": "Relación", - "fr-fr": "Relation", - "uk-ua": "Стосунків", - "de-ch": "Beziehung", - "pt-br": "Relação", + 'en-us': 'Relationship', + 'ru-ru': 'Отношение', + 'es-es': 'Relación', + 'fr-fr': 'Relation', + 'uk-ua': 'Стосунків', + 'de-ch': 'Beziehung', + 'pt-br': 'Relação', }, paleoMap: { - "en-us": "Paleo Map", - "ru-ru": "Палеокарта", - "es-es": "Mapa Paleo", - "fr-fr": "Carte paléo", - "uk-ua": "Палео-мапа", - "de-ch": "Paläo-Karte", - "pt-br": "Mapa Paleo", + 'en-us': 'Paleo Map', + 'ru-ru': 'Палеокарта', + 'es-es': 'Mapa Paleo', + 'fr-fr': 'Carte paléo', + 'uk-ua': 'Палео-мапа', + 'de-ch': 'Paläo-Karte', + 'pt-br': 'Mapa Paleo', }, paleoRequiresGeography: { - comment: "Example: Geography Required", - "en-us": "{geographyTable:string} Required", - "ru-ru": "{geographyTable:string} Требуется", - "es-es": "{geographyTable:string} Requerido", - "fr-fr": "{geographyTable:string} Obligatoire", - "uk-ua": "{geographyTable:string} Обов'язково", - "de-ch": "{geographyTable:string} Erforderlich", - "pt-br": "{geographyTable:string} Obrigatório", + comment: 'Example: Geography Required', + 'en-us': '{geographyTable:string} Required', + 'ru-ru': '{geographyTable:string} Требуется', + 'es-es': '{geographyTable:string} Requerido', + 'fr-fr': '{geographyTable:string} Obligatoire', + 'uk-ua': "{geographyTable:string} Обов'язково", + 'de-ch': '{geographyTable:string} Erforderlich', + 'pt-br': '{geographyTable:string} Obrigatório', }, paleoRequiresGeographyDescription: { - "en-us": - "The Paleo Map plugin requires that the {localityTable:string} have geographic coordinates and that the paleo context have a geographic age with at least a start time or and end time populated.", - "de-ch": - "Das Paleo Map-Plugin erfordert, dass {localityTable:string} geografische Koordinaten hat und dass der Paläo-Kontext ein geografisches Alter mit mindestens einer ausgefüllten Start- oder Endzeit hat.", - "es-es": "Seleccionar fuente de tablas", - "fr-fr": - "Le plugin Paleo Map nécessite que les {localityTable:string} aient des coordonnées géographiques et que le contexte paléo ait un âge géographique avec au moins une heure de début ou une heure de fin renseignée.", - "ru-ru": - "Для работы плагина Paleo Map требуется, чтобы {localityTable:string} имел географические координаты, а палеоконтекст имел географический возраст с указанием как минимум начального и конечного времени.", - "uk-ua": - "Плагін Paleo Map вимагає, щоб {localityTable:string} мав географічні координати, а палеодієнтний контекст мав географічний вік із зазначенням принаймні часу початку або часу завершення.", - "pt-br": - "O plugin Paleo Map requer que o {localityTable:string} tenha coordenadas geográficas e que o contexto paleo tenha uma idade geográfica com pelo menos um horário de início ou término preenchidos.", + 'en-us': + 'The Paleo Map plugin requires that the {localityTable:string} have geographic coordinates and that the paleo context have a geographic age with at least a start time or and end time populated.', + 'de-ch': + 'Das Paleo Map-Plugin erfordert, dass {localityTable:string} geografische Koordinaten hat und dass der Paläo-Kontext ein geografisches Alter mit mindestens einer ausgefüllten Start- oder Endzeit hat.', + 'es-es': 'Seleccionar fuente de tablas', + 'fr-fr': + 'Le plugin Paleo Map nécessite que les {localityTable:string} aient des coordonnées géographiques et que le contexte paléo ait un âge géographique avec au moins une heure de début ou une heure de fin renseignée.', + 'ru-ru': + 'Для работы плагина Paleo Map требуется, чтобы {localityTable:string} имел географические координаты, а палеоконтекст имел географический возраст с указанием как минимум начального и конечного времени.', + 'uk-ua': + 'Плагін Paleo Map вимагає, щоб {localityTable:string} мав географічні координати, а палеодієнтний контекст мав географічний вік із зазначенням принаймні часу початку або часу завершення.', + 'pt-br': + 'O plugin Paleo Map requer que o {localityTable:string} tenha coordenadas geográficas e que o contexto paleo tenha uma idade geográfica com pelo menos um horário de início ou término preenchidos.', }, invalidDate: { - "en-us": "Invalid Date", - "ru-ru": "Неверная дата", - "es-es": "Fecha invalida", - "fr-fr": "Date invalide", - "uk-ua": "Недійсна дата", - "de-ch": "Ungültiges Datum", - "pt-br": "Data inválida", + 'en-us': 'Invalid Date', + 'ru-ru': 'Неверная дата', + 'es-es': 'Fecha invalida', + 'fr-fr': 'Date invalide', + 'uk-ua': 'Недійсна дата', + 'de-ch': 'Ungültiges Datum', + 'pt-br': 'Data inválida', }, deleteConfirmation: { - "en-us": - "Are you sure you want to permanently delete this {tableName:string} from the database?", - "de-ch": - "Sind Sie sicher, dass Sie diesen {tableName:string} dauerhaft aus der Datenbank löschen möchten?", - "es-es": "El valor debe ser exclusivo de la base de datos.", - "fr-fr": - "Êtes-vous sûr de vouloir supprimer définitivement ce {tableName:string} de la base de données ?", - "ru-ru": - "Вы уверены, что хотите навсегда удалить {tableName:string} из базы данных?", - "uk-ua": - "Ви впевнені, що хочете остаточно видалити цей {tableName:string} з бази даних?", - "pt-br": - "Tem certeza de que deseja excluir permanentemente este {tableName:string} do banco de dados?", + 'en-us': + 'Are you sure you want to permanently delete this {tableName:string} from the database?', + 'de-ch': + 'Sind Sie sicher, dass Sie diesen {tableName:string} dauerhaft aus der Datenbank löschen möchten?', + 'es-es': 'El valor debe ser exclusivo de la base de datos.', + 'fr-fr': + 'Êtes-vous sûr de vouloir supprimer définitivement ce {tableName:string} de la base de données ?', + 'ru-ru': + 'Вы уверены, что хотите навсегда удалить {tableName:string} из базы данных?', + 'uk-ua': + 'Ви впевнені, що хочете остаточно видалити цей {tableName:string} з бази даних?', + 'pt-br': + 'Tem certeza de que deseja excluir permanentemente este {tableName:string} do banco de dados?', }, deleteConfirmationDescription: { - "en-us": "This action cannot be undone.", - "ru-ru": "Это действие не может быть отменено.", - "es-es": "Esta acción no se puede deshacer.", - "fr-fr": "Cette action ne peut pas être annulée.", - "uk-ua": "Цю дію не можна скасувати.", - "de-ch": "Diese Aktion kann nicht rückgängig gemacht werden.", - "pt-br": "Esta ação não pode ser desfeita.", + 'en-us': 'This action cannot be undone.', + 'ru-ru': 'Это действие не может быть отменено.', + 'es-es': 'Esta acción no se puede deshacer.', + 'fr-fr': 'Cette action ne peut pas être annulée.', + 'uk-ua': 'Цю дію не можна скасувати.', + 'de-ch': 'Diese Aktion kann nicht rückgängig gemacht werden.', + 'pt-br': 'Esta ação não pode ser desfeita.', }, datePrecision: { - "en-us": "Date Precision", - "ru-ru": "Точность даты", - "es-es": "Precisión de fecha", - "fr-fr": "Précision de la date", - "uk-ua": "Точність дати", - "de-ch": "Datumsgenauigkeit", - "pt-br": "Precisão de data", + 'en-us': 'Date Precision', + 'ru-ru': 'Точность даты', + 'es-es': 'Precisión de fecha', + 'fr-fr': 'Précision de la date', + 'uk-ua': 'Точність дати', + 'de-ch': 'Datumsgenauigkeit', + 'pt-br': 'Precisão de data', }, monthYear: { comment: ` A placeholder for partial date field when "month /year" type is selected. - Visible only in browsers that don\'t support the "month" input type. + Visible only in browsers that don't support the "month" input type. `, - "en-us": "Mon / Year", - "ru-ru": "Пн / Год", - "es-es": "Usar configuraciones personalizadas", - "fr-fr": "Lun / Année", - "uk-ua": "Пн / Рік", - "de-ch": "Mo / Jahr", - "pt-br": "Seg / Ano", + 'en-us': 'Mon / Year', + 'ru-ru': 'Пн / Год', + 'es-es': 'Usar configuraciones personalizadas', + 'fr-fr': 'Lun / Année', + 'uk-ua': 'Пн / Рік', + 'de-ch': 'Mo / Jahr', + 'pt-br': 'Seg / Ano', }, yearPlaceholder: { comment: 'A placeholder for partial date field when "year" type is selected', - "en-us": "YYYY", - "ru-ru": "ГГГГ", - "es-es": "AAAA", - "fr-fr": "AAAA", - "uk-ua": "РРРР", - "de-ch": "JJJJ", - "pt-br": "AAAA", + 'en-us': 'YYYY', + 'ru-ru': 'ГГГГ', + 'es-es': 'AAAA', + 'fr-fr': 'AAAA', + 'uk-ua': 'РРРР', + 'de-ch': 'JJJJ', + 'pt-br': 'AAAA', }, today: { - "en-us": "Today", - "ru-ru": "Сегодня", - "es-es": "Hoy", - "fr-fr": "Aujourd'hui", - "uk-ua": "Сьогодні", - "de-ch": "Heute", - "pt-br": "Hoje", + 'en-us': 'Today', + 'ru-ru': 'Сегодня', + 'es-es': 'Hoy', + 'fr-fr': "Aujourd'hui", + 'uk-ua': 'Сьогодні', + 'de-ch': 'Heute', + 'pt-br': 'Hoje', }, todayButtonDescription: { - "en-us": "Set to current date", - "ru-ru": "Установить на текущую дату", - "es-es": "Establecer en fecha actual", - "fr-fr": "Définir sur la date du jour", - "uk-ua": "Встановити на поточну дату", - "de-ch": "Auf aktuelles Datum einstellen", - "pt-br": "Definir para a data atual", + 'en-us': 'Set to current date', + 'ru-ru': 'Установить на текущую дату', + 'es-es': 'Establecer en fecha actual', + 'fr-fr': 'Définir sur la date du jour', + 'uk-ua': 'Встановити на поточну дату', + 'de-ch': 'Auf aktuelles Datum einstellen', + 'pt-br': 'Definir para a data atual', }, addToPickListConfirmation: { - "en-us": "Add to {pickListTable:string}?", - "ru-ru": "Добавить в {pickListTable:string}?", - "es-es": "¿Añadir a {pickListTable:string}?", - "fr-fr": "Ajouter à {pickListTable:string} ?", - "uk-ua": "Додати до {pickListTable:string}?", - "de-ch": "Zu {pickListTable:string} hinzufügen?", - "pt-br": "Adicionar a {pickListTable:string}?", + 'en-us': 'Add to {pickListTable:string}?', + 'ru-ru': 'Добавить в {pickListTable:string}?', + 'es-es': '¿Añadir a {pickListTable:string}?', + 'fr-fr': 'Ajouter à {pickListTable:string} ?', + 'uk-ua': 'Додати до {pickListTable:string}?', + 'de-ch': 'Zu {pickListTable:string} hinzufügen?', + 'pt-br': 'Adicionar a {pickListTable:string}?', }, addToPickListConfirmationDescription: { - "en-us": + 'en-us': 'Add value "{value:string}" to the {pickListTable:string} named "{pickListName:string}"?', - "de-ch": - "Wert „{value:string}“ zum {pickListTable:string} mit dem Namen „{pickListName:string}“ hinzufügen?", - "es-es": + 'de-ch': + 'Wert „{value:string}“ zum {pickListTable:string} mit dem Namen „{pickListName:string}“ hinzufügen?', + 'es-es': '¿Agregar valor "{value:string}" al {pickListTable:string} llamado "{pickListName:string}"?', - "fr-fr": - "Ajouter la valeur « {value:string} » au {pickListTable:string} nommé « {pickListName:string} » ?", - "ru-ru": - "Добавить значение «{value:string}» к {pickListTable:string} с именем «{pickListName:string}»?", - "uk-ua": + 'fr-fr': + 'Ajouter la valeur « {value:string} » au {pickListTable:string} nommé « {pickListName:string} » ?', + 'ru-ru': + 'Добавить значение «{value:string}» к {pickListTable:string} с именем «{pickListName:string}»?', + 'uk-ua': 'Додати значення "{value:string}" до {pickListTable:string} з назвою "{pickListName:string}"?', - "pt-br": + 'pt-br': 'Adicionar valor "{value:string}" ao {pickListTable:string} chamado "{pickListName:string}"?', }, invalidType: { - "en-us": "Invalid Type", - "ru-ru": "Неверный тип", - "es-es": "Tipo inválido", - "fr-fr": "Type invalide", - "uk-ua": "Недійсний тип", - "de-ch": "Ungültiger Typ", - "pt-br": "Tipo inválido", + 'en-us': 'Invalid Type', + 'ru-ru': 'Неверный тип', + 'es-es': 'Tipo inválido', + 'fr-fr': 'Type invalide', + 'uk-ua': 'Недійсний тип', + 'de-ch': 'Ungültiger Typ', + 'pt-br': 'Tipo inválido', }, invalidNumericPicklistValue: { - "en-us": "Only numeric values are supported in this {pickListTable:string}", - "de-ch": - "Es werden nur numerische Werte unterstützt {pickListTable:string}", - "es-es": "En este {pickListTable:string} solo se admiten valores numéricos", - "fr-fr": - "Seules les valeurs numériques sont prises en charge dans ce {pickListTable:string}", - "ru-ru": - "В этом {pickListTable:string} поддерживаются только числовые значения.", - "uk-ua": - "У цьому {pickListTable:string} підтримуються лише числові значення", - "pt-br": - "Somente valores numéricos são suportados neste {pickListTable:string}", + 'en-us': 'Only numeric values are supported in this {pickListTable:string}', + 'de-ch': + 'Es werden nur numerische Werte unterstützt {pickListTable:string}', + 'es-es': 'En este {pickListTable:string} solo se admiten valores numéricos', + 'fr-fr': + 'Seules les valeurs numériques sont prises en charge dans ce {pickListTable:string}', + 'ru-ru': + 'В этом {pickListTable:string} поддерживаются только числовые значения.', + 'uk-ua': + 'У цьому {pickListTable:string} підтримуються лише числові значення', + 'pt-br': + 'Somente valores numéricos são suportados neste {pickListTable:string}', }, noData: { - "en-us": "No Data.", - "ru-ru": "Нет данных.", - "es-es": "Sin datos.", - "fr-fr": "Aucune donnée.", - "uk-ua": "Немає даних.", - "de-ch": "Keine Daten.", - "pt-br": "Nenhum dado.", + 'en-us': 'No Data.', + 'ru-ru': 'Нет данных.', + 'es-es': 'Sin datos.', + 'fr-fr': 'Aucune donnée.', + 'uk-ua': 'Немає даних.', + 'de-ch': 'Keine Daten.', + 'pt-br': 'Nenhum dado.', }, recordSetDeletionWarning: { - "en-us": + 'en-us': 'The {recordSetTable:string} "{recordSetName:string}" will be deleted. The referenced records will NOT be deleted from the database.', - "ru-ru": + 'ru-ru': '{recordSetTable:string} "{recordSetName:string}" будет удалён. Связанные с этим записи НЕ будут удалены из базы данных.', - "es-es": + 'es-es': 'Se eliminará el {recordSetTable:string} "{recordSetName:string}". Los registros referenciados no se eliminarán de la base de datos.', - "fr-fr": - "Le {recordSetTable:string} « {recordSetName:string} » sera supprimé. Les enregistrements référencés ne seront PAS supprimés de la base de données.", - "uk-ua": + 'fr-fr': + 'Le {recordSetTable:string} « {recordSetName:string} » sera supprimé. Les enregistrements référencés ne seront PAS supprimés de la base de données.', + 'uk-ua': '{recordSetTable:string} "{recordSetName:string}" буде видалено. Записи, на які посилаються, НЕ будуть видалені з бази даних.', - "de-ch": + 'de-ch': 'Der {recordSetTable:string} "{recordSetName:string}" wird gelöscht. Die referenzierten Datensätze werden NICHT aus der Datenbank gelöscht.', - "pt-br": + 'pt-br': 'O {recordSetTable:string} "{recordSetName:string}" será excluído. Os registros referenciados NÃO serão excluídos do banco de dados.', }, saveRecordFirst: { - "en-us": "Save record first", - "ru-ru": "Сначала сохраните запись", - "es-es": "Guardar el registro primero", - "fr-fr": "Enregistrer d'abord l'enregistrement", - "uk-ua": "Спочатку збережіть запис", - "de-ch": "Datensatz zuerst speichern", - "pt-br": "Salvar registro primeiro", + 'en-us': 'Save record first', + 'ru-ru': 'Сначала сохраните запись', + 'es-es': 'Guardar el registro primero', + 'fr-fr': "Enregistrer d'abord l'enregistrement", + 'uk-ua': 'Спочатку збережіть запис', + 'de-ch': 'Datensatz zuerst speichern', + 'pt-br': 'Salvar registro primeiro', }, firstRecord: { - "en-us": "First Record", - "ru-ru": "Первая запись", - "es-es": "Primer disco", - "fr-fr": "Premier enregistrement", - "uk-ua": "Перший запис", - "de-ch": "Erster Eintrag", - "pt-br": "Primeiro Registro", + 'en-us': 'First Record', + 'ru-ru': 'Первая запись', + 'es-es': 'Primer disco', + 'fr-fr': 'Premier enregistrement', + 'uk-ua': 'Перший запис', + 'de-ch': 'Erster Eintrag', + 'pt-br': 'Primeiro Registro', + }, + goToFirstRecord: { + 'en-us': 'Go to first record', + 'ru-ru': 'Перейти к первой записи', + 'es-es': 'Ir al primer registro', + 'fr-fr': 'Aller au premier enregistrement', + 'uk-ua': 'Перейти до першого запису', + 'de-ch': 'Zum ersten Eintrag gehen', }, lastRecord: { - "en-us": "Last Record", - "ru-ru": "Последняя запись", - "es-es": "Último registro", - "fr-fr": "Dernier enregistrement", - "uk-ua": "Останній запис", - "de-ch": "Letzter Datensatz", - "pt-br": "Último registro", - }, - previousRecord: { - "en-us": "Previous Record", - "ru-ru": "Предыдущая запись", - "es-es": "Registro anterior", - "fr-fr": "Record précédent", - "uk-ua": "Попередній запис", - "de-ch": "Vorheriger Datensatz", - "pt-br": "Registro anterior", - }, - nextRecord: { - "en-us": "Next Record", - "ru-ru": "Следующая запись", - "es-es": "Próximo récord", - "fr-fr": "Prochain enregistrement", - "uk-ua": "Наступний запис", - "de-ch": "Nächster Datensatz", - "pt-br": "Próximo registro", + 'en-us': 'Last Record', + 'ru-ru': 'Последняя запись', + 'es-es': 'Último registro', + 'fr-fr': 'Dernier enregistrement', + 'uk-ua': 'Останній запис', + 'de-ch': 'Letzter Datensatz', + 'pt-br': 'Último registro', + }, + goToLastRecord: { + 'en-us': 'Go to last record', + 'ru-ru': 'Перейти к последней записи', + 'es-es': 'Ir al último registro', + 'fr-fr': 'Aller au dernier enregistrement', + 'uk-ua': 'Перейти до останнього запису', + 'de-ch': 'Zum letzten Eintrag gehen', + }, + goToPreviousRecord: { + 'en-us': 'Go to previous record', + 'ru-ru': 'Перейти к предыдущей записи', + 'es-es': 'Ir al registro anterior', + 'fr-fr': "Aller à l'enregistrement précédent", + 'uk-ua': 'Перейти до попереднього запису', + 'de-ch': 'Zum vorherigen Datensatz gehen', + }, + goToNextRecord: { + 'en-us': 'Go to next record', + 'ru-ru': 'Перейти к следующей записи', + 'es-es': 'Ir al siguiente registro', + 'fr-fr': "Aller à l'enregistrement suivant", + 'uk-ua': 'Перейти до наступного запису', + 'de-ch': 'Zum nächsten Datensatz gehen', }, currentRecord: { - "en-us": "Current object (out of {total:number|formatted})", - "ru-ru": "Текущий объект (из {total:number|formatted})", - "es-es": "Objeto actual (de {total:number|formatted})", - "fr-fr": "Objet actuel (sur {total:number|formatted})", - "uk-ua": "Поточний об'єкт (з {total:number|formatted})", - "de-ch": "Aktuelles Objekt (aus {total:number|formatted})", - "pt-br": "Objeto atual (de {total:number|formatted})", + 'en-us': 'Current object (out of {total:number|formatted})', + 'ru-ru': 'Текущий объект (из {total:number|formatted})', + 'es-es': 'Objeto actual (de {total:number|formatted})', + 'fr-fr': 'Objet actuel (sur {total:number|formatted})', + 'uk-ua': "Поточний об'єкт (з {total:number|formatted})", + 'de-ch': 'Aktuelles Objekt (aus {total:number|formatted})', + 'pt-br': 'Objeto atual (de {total:number|formatted})', }, unsavedFormUnloadProtect: { - "en-us": "This form has not been saved.", - "ru-ru": "Эта форма не была сохранена.", - "es-es": "Este formulario no ha sido guardado.", - "fr-fr": "Ce formulaire n'a pas été enregistré.", - "uk-ua": "Цю форму не збережено.", - "de-ch": "Dieses Formular wurde nicht gespeichert.", - "pt-br": "Este formulário não foi salvo.", + 'en-us': 'This form has not been saved.', + 'ru-ru': 'Эта форма не была сохранена.', + 'es-es': 'Este formulario no ha sido guardado.', + 'fr-fr': "Ce formulaire n'a pas été enregistré.", + 'uk-ua': 'Цю форму не збережено.', + 'de-ch': 'Dieses Formular wurde nicht gespeichert.', + 'pt-br': 'Este formulário não foi salvo.', }, saveConflict: { - comment: "Meaning a conflict occurred when saving", - "en-us": "Save conflict", - "ru-ru": "Сохранить конфликт", - "es-es": "Guardar conflicto", - "fr-fr": "Enregistrer le conflit", - "uk-ua": "Зберегти конфлікт", - "de-ch": "Konflikt speichern", - "pt-br": "Salvar conflito", + comment: 'Meaning a conflict occurred when saving', + 'en-us': 'Save conflict', + 'ru-ru': 'Сохранить конфликт', + 'es-es': 'Guardar conflicto', + 'fr-fr': 'Enregistrer le conflit', + 'uk-ua': 'Зберегти конфлікт', + 'de-ch': 'Konflikt speichern', + 'pt-br': 'Salvar conflito', }, saveConflictDescription: { - "en-us": - "The data shown on this page has been changed by another user or in another browser tab and is out of date. The page must be reloaded to prevent inconsistent data from being saved.", - "ru-ru": - "Данные на этой странице были изменены другим пользователем или на другой вкладке браузера и устарели. Для предотвращения сохранения несоответствующих данных необходимо перезагрузить страницу.", - "es-es": - "Los datos que se muestran en esta página han sido modificados por otro usuario o en otra pestaña del navegador y están desactualizados. Es necesario recargar la página para evitar que se guarden datos incoherentes.", - "fr-fr": + 'en-us': + 'The data shown on this page has been changed by another user or in another browser tab and is out of date. The page must be reloaded to prevent inconsistent data from being saved.', + 'ru-ru': + 'Данные на этой странице были изменены другим пользователем или на другой вкладке браузера и устарели. Для предотвращения сохранения несоответствующих данных необходимо перезагрузить страницу.', + 'es-es': + 'Los datos que se muestran en esta página han sido modificados por otro usuario o en otra pestaña del navegador y están desactualizados. Es necesario recargar la página para evitar que se guarden datos incoherentes.', + 'fr-fr': "Les données affichées sur cette page ont été modifiées par un autre utilisateur ou dans un autre onglet du navigateur et sont obsolètes. La page doit être rechargée pour éviter l'enregistrement de données incohérentes.", - "uk-ua": - "Дані, що відображаються на цій сторінці, були змінені іншим користувачем або в іншій вкладці браузера та застарілі. Сторінку необхідно перезавантажити, щоб запобігти збереженню невідповідних даних.", - "de-ch": - "Die auf dieser Seite angezeigten Daten wurden von einem anderen Benutzer oder in einem anderen Browser-Tab geändert und sind veraltet. Um die Speicherung inkonsistenter Daten zu verhindern, muss die Seite neu geladen werden.", - "pt-br": - "Os dados exibidos nesta página foram alterados por outro usuário ou em outra aba do navegador e estão desatualizados. A página deve ser recarregada para evitar que dados inconsistentes sejam salvos.", + 'uk-ua': + 'Дані, що відображаються на цій сторінці, були змінені іншим користувачем або в іншій вкладці браузера та застарілі. Сторінку необхідно перезавантажити, щоб запобігти збереженню невідповідних даних.', + 'de-ch': + 'Die auf dieser Seite angezeigten Daten wurden von einem anderen Benutzer oder in einem anderen Browser-Tab geändert und sind veraltet. Um die Speicherung inkonsistenter Daten zu verhindern, muss die Seite neu geladen werden.', + 'pt-br': + 'Os dados exibidos nesta página foram alterados por outro usuário ou em outra aba do navegador e estão desatualizados. A página deve ser recarregada para evitar que dados inconsistentes sejam salvos.', }, saveBlocked: { - "en-us": "Save blocked", - "de-ch": "Speichern blockiert", - "es-es": "Guardar bloqueado", - "fr-fr": "Enregistrer bloqué", - "ru-ru": "Сохранить заблокировано", - "uk-ua": "Зберегти заблоковано", - "pt-br": "Salvar bloqueado", + 'en-us': 'Save blocked', + 'de-ch': 'Speichern blockiert', + 'es-es': 'Guardar bloqueado', + 'fr-fr': 'Enregistrer bloqué', + 'ru-ru': 'Сохранить заблокировано', + 'uk-ua': 'Зберегти заблоковано', + 'pt-br': 'Salvar bloqueado', }, saveBlockedDescription: { - "en-us": "Form cannot be saved because of the following error:", - "ru-ru": "Форму невозможно сохранить из-за следующей ошибки:", - "es-es": "No se puede guardar el formulario debido al siguiente error:", - "fr-fr": + 'en-us': 'Form cannot be saved because of the following error:', + 'ru-ru': 'Форму невозможно сохранить из-за следующей ошибки:', + 'es-es': 'No se puede guardar el formulario debido al siguiente error:', + 'fr-fr': "Le formulaire ne peut pas être enregistré en raison de l'erreur suivante :", - "uk-ua": "Форму неможливо зберегти через таку помилку:", - "de-ch": - "Das Formular kann aufgrund des folgenden Fehlers nicht gespeichert werden:", - "pt-br": "O formulário não pode ser salvo devido ao seguinte erro:", + 'uk-ua': 'Форму неможливо зберегти через таку помилку:', + 'de-ch': + 'Das Formular kann aufgrund des folgenden Fehlers nicht gespeichert werden:', + 'pt-br': 'O formulário não pode ser salvo devido ao seguinte erro:', }, unavailableCommandButton: { - "en-us": "Command N/A", - "ru-ru": "Команда N/A", - "es-es": "Comando N/A", - "fr-fr": "Commande N/A", - "uk-ua": "Команда Немає", - "de-ch": "Befehl N/A", - "pt-br": "Comando N/A", + 'en-us': 'Command N/A', + 'ru-ru': 'Команда N/A', + 'es-es': 'Comando N/A', + 'fr-fr': 'Commande N/A', + 'uk-ua': 'Команда Немає', + 'de-ch': 'Befehl N/A', + 'pt-br': 'Comando N/A', }, commandUnavailable: { - "en-us": "Command Not Available", - "ru-ru": "Команда недоступна", - "es-es": "Comando no disponible", - "fr-fr": "Commande non disponible", - "uk-ua": "Команда недоступна", - "de-ch": "Befehl nicht verfügbar", - "pt-br": "Comando não disponível", + 'en-us': 'Command Not Available', + 'ru-ru': 'Команда недоступна', + 'es-es': 'Comando no disponible', + 'fr-fr': 'Commande non disponible', + 'uk-ua': 'Команда недоступна', + 'de-ch': 'Befehl nicht verfügbar', + 'pt-br': 'Comando não disponível', }, commandUnavailableDescription: { - "en-us": "This command is currently unavailable for Specify 7.", - "ru-ru": "Эта команда в настоящее время недоступна для Specify 7.", - "es-es": "Este comando no está disponible actualmente para Specify 7.", - "uk-ua": "Ця команда наразі недоступна для Specify 7.", - "de-ch": "Dieser Befehl ist derzeit für Specify 7 nicht verfügbar.", - "fr-fr": "Cette commande n'est actuellement pas disponible pour Specify 7.", - "pt-br": "Este comando não está disponível no momento para o Specify 7.", + 'en-us': 'This command is currently unavailable for Specify 7.', + 'ru-ru': 'Эта команда в настоящее время недоступна для Specify 7.', + 'es-es': 'Este comando no está disponible actualmente para Specify 7.', + 'uk-ua': 'Ця команда наразі недоступна для Specify 7.', + 'de-ch': 'Dieser Befehl ist derzeit für Specify 7 nicht verfügbar.', + 'fr-fr': "Cette commande n'est actuellement pas disponible pour Specify 7.", + 'pt-br': 'Este comando não está disponível no momento para o Specify 7.', }, commandUnavailableSecondDescription: { - "en-us": - "It was probably included on this form from Specify 6 and may be supported in the future.", - "ru-ru": - "Вероятно, он был включен в эту форму из Specify 6 и может поддерживаться в будущем.", - "es-es": - "Probablemente se incluyó en este formulario de la Especificación 6 y es posible que se admita en el futuro.", - "fr-fr": + 'en-us': + 'It was probably included on this form from Specify 6 and may be supported in the future.', + 'ru-ru': + 'Вероятно, он был включен в эту форму из Specify 6 и может поддерживаться в будущем.', + 'es-es': + 'Probablemente se incluyó en este formulario de la Especificación 6 y es posible que se admita en el futuro.', + 'fr-fr': "Il a probablement été inclus dans ce formulaire à partir de Specify 6 et peut être pris en charge à l'avenir.", - "uk-ua": - "Ймовірно, це було включено до цієї форми з Specify 6 і може бути підтримано в майбутньому.", - "de-ch": - "Es war wahrscheinlich in diesem Formular von Specify 6 enthalten und wird möglicherweise in Zukunft unterstützt.", - "pt-br": - "Provavelmente foi incluído neste formulário do Specify 6 e pode ser suportado no futuro.", + 'uk-ua': + 'Ймовірно, це було включено до цієї форми з Specify 6 і може бути підтримано в майбутньому.', + 'de-ch': + 'Es war wahrscheinlich in diesem Formular von Specify 6 enthalten und wird möglicherweise in Zukunft unterstützt.', + 'pt-br': + 'Provavelmente foi incluído neste formulário do Specify 6 e pode ser suportado no futuro.', }, commandName: { - "en-us": "Command name", - "ru-ru": "Имя команды", - "es-es": "Nombre del comando", - "fr-fr": "Nom de la commande", - "uk-ua": "Назва команди", - "de-ch": "Befehlsname", - "pt-br": "Nome do comando", + 'en-us': 'Command name', + 'ru-ru': 'Имя команды', + 'es-es': 'Nombre del comando', + 'fr-fr': 'Nom de la commande', + 'uk-ua': 'Назва команди', + 'de-ch': 'Befehlsname', + 'pt-br': 'Nome do comando', }, unavailablePluginButton: { - "en-us": "Plugin N/A", - "ru-ru": "Плагин N/A", - "es-es": "Complemento N/A", - "fr-fr": "Plugin N/A", - "uk-ua": "Плагін Немає", - "de-ch": "Plugin N/A", - "pt-br": "Plugin N/A", + 'en-us': 'Plugin N/A', + 'ru-ru': 'Плагин N/A', + 'es-es': 'Complemento N/A', + 'fr-fr': 'Plugin N/A', + 'uk-ua': 'Плагін Немає', + 'de-ch': 'Plugin N/A', + 'pt-br': 'Plugin N/A', }, pluginNotAvailable: { - "en-us": "Plugin Not Available", - "ru-ru": "Плагин недоступен", - "es-es": "Complemento no disponible", - "fr-fr": "Plugin non disponible", - "uk-ua": "Плагін недоступний", - "de-ch": "Plugin nicht verfügbar", - "pt-br": "Plugin não disponível", + 'en-us': 'Plugin Not Available', + 'ru-ru': 'Плагин недоступен', + 'es-es': 'Complemento no disponible', + 'fr-fr': 'Plugin non disponible', + 'uk-ua': 'Плагін недоступний', + 'de-ch': 'Plugin nicht verfügbar', + 'pt-br': 'Plugin não disponível', }, pluginNotAvailableDescription: { - "en-us": "This plugin is currently unavailable for Specify 7", - "ru-ru": "Этот плагин в настоящее время недоступен для Specify 7.", - "es-es": "Este complemento no está disponible actualmente para Specify 7", - "fr-fr": "Ce plugin n'est actuellement pas disponible pour Specify 7", - "uk-ua": "Цей плагін наразі недоступний для Specify 7", - "de-ch": "Dieses Plugin ist derzeit für Specify 7 nicht verfügbar", - "pt-br": "Este plugin não está disponível no momento para o Specify 7", + 'en-us': 'This plugin is currently unavailable for Specify 7', + 'ru-ru': 'Этот плагин в настоящее время недоступен для Specify 7.', + 'es-es': 'Este complemento no está disponible actualmente para Specify 7', + 'fr-fr': "Ce plugin n'est actuellement pas disponible pour Specify 7", + 'uk-ua': 'Цей плагін наразі недоступний для Specify 7', + 'de-ch': 'Dieses Plugin ist derzeit für Specify 7 nicht verfügbar', + 'pt-br': 'Este plugin não está disponível no momento para o Specify 7', }, wrongTableForPlugin: { comment: - "Example: ... Locality, Collecting Event or Collection Object forms.", - "en-us": - "This plugin cannot be used on the {currentTable:string} form. Try moving it to the {supportedTables:string} forms.", - "ru-ru": - "Этот плагин нельзя использовать на форме {currentTable:string}. Попробуйте перенести его на формы {supportedTables:string}.", - "es-es": - "Este complemento no se puede utilizar en el formulario {currentTable:string}. Intente moverlo a los formularios {supportedTables:string}.", - "fr-fr": - "Ce plugin ne peut pas être utilisé sur le formulaire {currentTable:string}. Essayez de le déplacer vers les formulaires {supportedTables:string}.", - "uk-ua": - "Цей плагін не можна використовувати на формі {currentTable:string}. Спробуйте перемістити його на форми {supportedTables:string}.", - "de-ch": - "Dieses Plugin kann nicht im Formular {currentTable:string} verwendet werden. Versuchen Sie, es in die Formulare {supportedTables:string} zu verschieben.", - "pt-br": - "Este plugin não pode ser usado no formulário {currentTable:string}. Tente movê-lo para os formulários {supportedTables:string}.", + 'Example: ... Locality, Collecting Event or Collection Object forms.', + 'en-us': + 'This plugin cannot be used on the {currentTable:string} form. Try moving it to the {supportedTables:string} forms.', + 'ru-ru': + 'Этот плагин нельзя использовать на форме {currentTable:string}. Попробуйте перенести его на формы {supportedTables:string}.', + 'es-es': + 'Este complemento no se puede utilizar en el formulario {currentTable:string}. Intente moverlo a los formularios {supportedTables:string}.', + 'fr-fr': + 'Ce plugin ne peut pas être utilisé sur le formulaire {currentTable:string}. Essayez de le déplacer vers les formulaires {supportedTables:string}.', + 'uk-ua': + 'Цей плагін не можна використовувати на формі {currentTable:string}. Спробуйте перемістити його на форми {supportedTables:string}.', + 'de-ch': + 'Dieses Plugin kann nicht im Formular {currentTable:string} verwendet werden. Versuchen Sie, es in die Formulare {supportedTables:string} zu verschieben.', + 'pt-br': + 'Este plugin não pode ser usado no formulário {currentTable:string}. Tente movê-lo para os formulários {supportedTables:string}.', }, wrongTableForCommand: { - "en-us": - "The command cannot be used on the {currentTable:string} form. It can only be used on the {correctTable:string} form.", - "ru-ru": - "Эту команду нельзя использовать в форме {currentTable:string}. Её можно использовать только в форме {correctTable:string}.", - "es-es": - "El comando no se puede utilizar en el formulario {currentTable:string}. Sólo se puede utilizar en el formulario {correctTable:string}.", - "fr-fr": - "La commande ne peut pas être utilisée sur le formulaire {currentTable:string}. Elle ne peut être utilisée que sur le formulaire {correctTable:string}.", - "uk-ua": - "Команду не можна використовувати у формі {currentTable:string}. Її можна використовувати лише у формі {correctTable:string}.", - "de-ch": - "Der Befehl kann nicht auf dem Formular {currentTable:string} verwendet werden. Er kann nur auf dem Formular {correctTable:string} verwendet werden.", - "pt-br": - "O comando não pode ser usado no formato {currentTable:string}. Ele só pode ser usado no formato {correctTable:string}.", + 'en-us': + 'The command cannot be used on the {currentTable:string} form. It can only be used on the {correctTable:string} form.', + 'ru-ru': + 'Эту команду нельзя использовать в форме {currentTable:string}. Её можно использовать только в форме {correctTable:string}.', + 'es-es': + 'El comando no se puede utilizar en el formulario {currentTable:string}. Sólo se puede utilizar en el formulario {correctTable:string}.', + 'fr-fr': + 'La commande ne peut pas être utilisée sur le formulaire {currentTable:string}. Elle ne peut être utilisée que sur le formulaire {correctTable:string}.', + 'uk-ua': + 'Команду не можна використовувати у формі {currentTable:string}. Її можна використовувати лише у формі {correctTable:string}.', + 'de-ch': + 'Der Befehl kann nicht auf dem Formular {currentTable:string} verwendet werden. Er kann nur auf dem Formular {correctTable:string} verwendet werden.', + 'pt-br': + 'O comando não pode ser usado no formato {currentTable:string}. Ele só pode ser usado no formato {correctTable:string}.', }, pluginName: { - "en-us": "Plugin name", - "ru-ru": "Имя плагина", - "es-es": "Nombre del complemento", - "fr-fr": "Nom du plugin", - "uk-ua": "Назва плагіна", - "de-ch": "Plugin-Name", - "pt-br": "Nome do plugin", + 'en-us': 'Plugin name', + 'ru-ru': 'Имя плагина', + 'es-es': 'Nombre del complemento', + 'fr-fr': 'Nom du plugin', + 'uk-ua': 'Назва плагіна', + 'de-ch': 'Plugin-Name', + 'pt-br': 'Nome do plugin', }, illegalBool: { comment: ` Yes/No probably shouldn't be translated as Specify 7 does not support changing which values are recognized as Yes/No in a given language `, - "en-us": "Illegal value for a Yes/No field", - "ru-ru": "Недопустимое значение для поля «Да/Нет»", - "es-es": "Valor ilegal para un campo Sí/No", - "fr-fr": "Valeur illégale pour un champ Oui/Non", - "uk-ua": "Неприпустиме значення для поля «Так/Ні»", - "de-ch": "Unzulässiger Wert für ein Ja/Nein-Feld", - "pt-br": "Valor ilegal para um campo Sim/Não", + 'en-us': 'Illegal value for a Yes/No field', + 'ru-ru': 'Недопустимое значение для поля «Да/Нет»', + 'es-es': 'Valor ilegal para un campo Sí/No', + 'fr-fr': 'Valeur illégale pour un champ Oui/Non', + 'uk-ua': 'Неприпустиме значення для поля «Так/Ні»', + 'de-ch': 'Unzulässiger Wert für ein Ja/Nein-Feld', + 'pt-br': 'Valor ilegal para um campo Sim/Não', }, requiredField: { - "en-us": "Field is required.", - "ru-ru": "Поле обязательно для заполнения.", - "es-es": "Se requiere campo.", - "fr-fr": "Le champ est obligatoire.", - "uk-ua": "Поле обов'язкове для заповнення.", - "de-ch": "Pflichtfeld.", - "pt-br": "Campo obrigatório.", + 'en-us': 'Field is required.', + 'ru-ru': 'Поле обязательно для заполнения.', + 'es-es': 'Se requiere campo.', + 'fr-fr': 'Le champ est obligatoire.', + 'uk-ua': "Поле обов'язкове для заповнення.", + 'de-ch': 'Pflichtfeld.', + 'pt-br': 'Campo obrigatório.', }, invalidValue: { - "en-us": "Invalid value", - "ru-ru": "Недопустимое значение", - "es-es": "Hoy", - "fr-fr": "Valeur invalide", - "uk-ua": "Недійсне значення", - "de-ch": "Ungültiger Wert", - "pt-br": "Valor inválido", + 'en-us': 'Invalid value', + 'ru-ru': 'Недопустимое значение', + 'es-es': 'Hoy', + 'fr-fr': 'Valeur invalide', + 'uk-ua': 'Недійсне значення', + 'de-ch': 'Ungültiger Wert', + 'pt-br': 'Valor inválido', }, requiredFormat: { - comment: "Used in field validation messages on the form", - "en-us": "Required Format: {format:string}.", - "ru-ru": "Требуемый формат: {format:string}.", - "es-es": "Formato requerido: {format:string}.", - "fr-fr": "Format requis : {format:string}.", - "uk-ua": "Необхідний формат: {format:string}.", - "de-ch": "Erforderliches Format: {format:string}.", - "pt-br": "Formato necessário: {format:string}.", + comment: 'Used in field validation messages on the form', + 'en-us': 'Required Format: {format:string}.', + 'ru-ru': 'Требуемый формат: {format:string}.', + 'es-es': 'Formato requerido: {format:string}.', + 'fr-fr': 'Format requis : {format:string}.', + 'uk-ua': 'Необхідний формат: {format:string}.', + 'de-ch': 'Erforderliches Format: {format:string}.', + 'pt-br': 'Formato necessário: {format:string}.', }, inputTypeNumber: { - "en-us": "Value must be a number", - "ru-ru": "Значение должно быть числом.", - "es-es": "El valor debe ser un número.", - "uk-ua": "Значення має бути числом", - "de-ch": "Der Wert muss eine Zahl sein", - "fr-fr": "La valeur doit être un nombre", - "pt-br": "O valor deve ser um número", + 'en-us': 'Value must be a number', + 'ru-ru': 'Значение должно быть числом.', + 'es-es': 'El valor debe ser un número.', + 'uk-ua': 'Значення має бути числом', + 'de-ch': 'Der Wert muss eine Zahl sein', + 'fr-fr': 'La valeur doit être un nombre', + 'pt-br': 'O valor deve ser um número', }, organization: { - "en-us": "Organization", - "ru-ru": "Организация", - "es-es": "Organización", - "fr-fr": "Organisation", - "uk-ua": "Організація", - "de-ch": "Organisation", - "pt-br": "Organização", + 'en-us': 'Organization', + 'ru-ru': 'Организация', + 'es-es': 'Organización', + 'fr-fr': 'Organisation', + 'uk-ua': 'Організація', + 'de-ch': 'Organisation', + 'pt-br': 'Organização', }, person: { - "en-us": "Person", - "ru-ru": "Человек", - "es-es": "Persona", - "fr-fr": "Personne", - "uk-ua": "Людина", - "de-ch": "Person", - "pt-br": "Pessoa", + 'en-us': 'Person', + 'ru-ru': 'Человек', + 'es-es': 'Persona', + 'fr-fr': 'Personne', + 'uk-ua': 'Людина', + 'de-ch': 'Person', + 'pt-br': 'Pessoa', }, other: { - "en-us": "Other", - "ru-ru": "Другой", - "es-es": "Otro", - "fr-fr": "Autre", - "uk-ua": "Інше", - "de-ch": "Andere", - "pt-br": "Outro", + 'en-us': 'Other', + 'ru-ru': 'Другой', + 'es-es': 'Otro', + 'fr-fr': 'Autre', + 'uk-ua': 'Інше', + 'de-ch': 'Andere', + 'pt-br': 'Outro', }, group: { - "en-us": "Group", - "ru-ru": "Группа", - "es-es": "Grupo", - "fr-fr": "Groupe", - "uk-ua": "Група", - "de-ch": "Gruppe", - "pt-br": "Grupo", + 'en-us': 'Group', + 'ru-ru': 'Группа', + 'es-es': 'Grupo', + 'fr-fr': 'Groupe', + 'uk-ua': 'Група', + 'de-ch': 'Gruppe', + 'pt-br': 'Grupo', }, userDefinedItems: { - "en-us": "User Defined Items", - "ru-ru": "Элементы, определяемые пользователем", - "es-es": "Elementos definidos por el usuario", - "fr-fr": "Éléments définis par l'utilisateur", - "uk-ua": "Елементи, визначені користувачем", - "de-ch": "Benutzerdefinierte Elemente", - "pt-br": "Itens definidos pelo usuário", + 'en-us': 'User Defined Items', + 'ru-ru': 'Элементы, определяемые пользователем', + 'es-es': 'Elementos definidos por el usuario', + 'fr-fr': "Éléments définis par l'utilisateur", + 'uk-ua': 'Елементи, визначені користувачем', + 'de-ch': 'Benutzerdefinierte Elemente', + 'pt-br': 'Itens definidos pelo usuário', }, entireTable: { - "en-us": "Entire Table", - "ru-ru": "Вся таблица", - "es-es": "Tabla entera", - "fr-fr": "Tableau entier", - "uk-ua": "Вся таблиця", - "de-ch": "Gesamte Tabelle", - "pt-br": "Mesa inteira", + 'en-us': 'Entire Table', + 'ru-ru': 'Вся таблица', + 'es-es': 'Tabla entera', + 'fr-fr': 'Tableau entier', + 'uk-ua': 'Вся таблиця', + 'de-ch': 'Gesamte Tabelle', + 'pt-br': 'Mesa inteira', }, fieldFromTable: { - "en-us": "Field From Table", - "ru-ru": "Поле из таблицы", - "es-es": "Campo de la tabla", - "fr-fr": "Champ de la table", - "uk-ua": "Поле з таблиці", - "de-ch": "Feld aus Tabelle", - "pt-br": "Campo da Tabela", + 'en-us': 'Field From Table', + 'ru-ru': 'Поле из таблицы', + 'es-es': 'Campo de la tabla', + 'fr-fr': 'Champ de la table', + 'uk-ua': 'Поле з таблиці', + 'de-ch': 'Feld aus Tabelle', + 'pt-br': 'Campo da Tabela', }, unsupportedCellType: { - "en-us": "Unsupported cell type", - "ru-ru": "Неподдерживаемый тип ячейки", - "es-es": "Tipo de celda no compatible", - "fr-fr": "Type de cellule non pris en charge", - "uk-ua": "Непідтримуваний тип клітинки", - "de-ch": "Nicht unterstützter Zelltyp", - "pt-br": "Tipo de célula não suportado", + 'en-us': 'Unsupported cell type', + 'ru-ru': 'Неподдерживаемый тип ячейки', + 'es-es': 'Tipo de celda no compatible', + 'fr-fr': 'Type de cellule non pris en charge', + 'uk-ua': 'Непідтримуваний тип клітинки', + 'de-ch': 'Nicht unterstützter Zelltyp', + 'pt-br': 'Tipo de célula não suportado', }, additionalResultsOmitted: { comment: ` Represents truncated search dialog output (when lots of results returned) `, - "en-us": "Additional results omitted", - "ru-ru": "Дополнительные результаты пропущены", - "es-es": "Resultados adicionales omitidos", - "fr-fr": "Résultats supplémentaires omis", - "uk-ua": "Додаткові результати пропущені", - "de-ch": "Weitere Ergebnisse ausgelassen", - "pt-br": "Resultados adicionais omitidos", + 'en-us': 'Additional results omitted', + 'ru-ru': 'Дополнительные результаты пропущены', + 'es-es': 'Resultados adicionales omitidos', + 'fr-fr': 'Résultats supplémentaires omis', + 'uk-ua': 'Додаткові результати пропущені', + 'de-ch': 'Weitere Ergebnisse ausgelassen', + 'pt-br': 'Resultados adicionais omitidos', }, recordSelectorUnloadProtect: { - "en-us": "Proceed without saving?", - "ru-ru": "Продолжить без сохранения?", - "es-es": "¿Continuar sin guardar?", - "fr-fr": "Continuer sans enregistrer ?", - "uk-ua": "Продовжити без збереження?", - "de-ch": "Ohne Speichern fortfahren?", - "pt-br": "Continuar sem salvar?", + 'en-us': 'Proceed without saving?', + 'ru-ru': 'Продолжить без сохранения?', + 'es-es': '¿Continuar sin guardar?', + 'fr-fr': 'Continuer sans enregistrer ?', + 'uk-ua': 'Продовжити без збереження?', + 'de-ch': 'Ohne Speichern fortfahren?', + 'pt-br': 'Continuar sem salvar?', }, recordSelectorUnloadProtectDescription: { comment: ` When in record set and current record is unsaved and try to navigate to another record `, - "en-us": "You might want to save this record before navigating away.", - "ru-ru": "Возможно, вы захотите сохранить эту запись, прежде чем уйти.", - "es-es": "Es posible que desees guardar este registro antes de navegar.", - "fr-fr": - "Vous souhaiterez peut-être sauvegarder cet enregistrement avant de partir.", - "uk-ua": - "Можливо, ви захочете зберегти цей запис, перш ніж залишати сторінку.", - "de-ch": - "Möglicherweise möchten Sie diesen Datensatz speichern, bevor Sie wegnavigieren.", - "pt-br": "Talvez você queira salvar este registro antes de sair navegando.", + 'en-us': 'You might want to save this record before navigating away.', + 'ru-ru': 'Возможно, вы захотите сохранить эту запись, прежде чем уйти.', + 'es-es': 'Es posible que desees guardar este registro antes de navegar.', + 'fr-fr': + 'Vous souhaiterez peut-être sauvegarder cet enregistrement avant de partir.', + 'uk-ua': + 'Можливо, ви захочете зберегти цей запис, перш ніж залишати сторінку.', + 'de-ch': + 'Möglicherweise möchten Sie diesen Datensatz speichern, bevor Sie wegnavigieren.', + 'pt-br': 'Talvez você queira salvar este registro antes de sair navegando.', }, creatingNewRecord: { - "en-us": "Creating new record", - "ru-ru": "Создание новой записи", - "es-es": "Creando nuevo registro", - "fr-fr": "Création d'un nouvel enregistrement", - "uk-ua": "Створення нового запису", - "de-ch": "Neuen Datensatz erstellen", - "pt-br": "Criando novo registro", + 'en-us': 'Creating new record', + 'ru-ru': 'Создание новой записи', + 'es-es': 'Creando nuevo registro', + 'fr-fr': "Création d'un nouvel enregistrement", + 'uk-ua': 'Створення нового запису', + 'de-ch': 'Neuen Datensatz erstellen', + 'pt-br': 'Criando novo registro', }, createNewRecordSet: { - "en-us": "Create a new record set", - "ru-ru": "Создать новый набор записей", - "es-es": "Crear un nuevo conjunto de registros", - "fr-fr": "Créer un nouvel ensemble d'enregistrements", - "uk-ua": "Створити новий набір записів", - "de-ch": "Erstellen Sie einen neuen Datensatz", - "pt-br": "Criar um novo conjunto de registros", + 'en-us': 'Create a new record set', + 'ru-ru': 'Создать новый набор записей', + 'es-es': 'Crear un nuevo conjunto de registros', + 'fr-fr': "Créer un nouvel ensemble d'enregistrements", + 'uk-ua': 'Створити новий набір записів', + 'de-ch': 'Erstellen Sie einen neuen Datensatz', + 'pt-br': 'Criar um novo conjunto de registros', }, forward: { - "en-us": "Forward", - "ru-ru": "Вперед", - "es-es": "Adelante", - "fr-fr": "Avant", - "uk-ua": "Вперед", - "de-ch": "Nach vorne", - "pt-br": "Avançar", + 'en-us': 'Forward', + 'ru-ru': 'Вперед', + 'es-es': 'Adelante', + 'fr-fr': 'Avant', + 'uk-ua': 'Вперед', + 'de-ch': 'Nach vorne', + 'pt-br': 'Avançar', }, reverse: { - "en-us": "Reverse", - "ru-ru": "Обеспечить регресс", - "es-es": "Contrarrestar", - "fr-fr": "Inverse", - "uk-ua": "Зворотний", - "de-ch": "Umkehren", - "pt-br": "Reverter", + 'en-us': 'Reverse', + 'ru-ru': 'Обеспечить регресс', + 'es-es': 'Contrarrestar', + 'fr-fr': 'Inverse', + 'uk-ua': 'Зворотний', + 'de-ch': 'Umkehren', + 'pt-br': 'Reverter', }, deletedInline: { - "en-us": "(deleted)", - "ru-ru": "(удалено)", - "es-es": "(eliminado)", - "fr-fr": "(supprimé)", - "uk-ua": "(видалено)", - "de-ch": "(gestrichen)", - "pt-br": "(apagado)", + 'en-us': '(deleted)', + 'ru-ru': '(удалено)', + 'es-es': '(eliminado)', + 'fr-fr': '(supprimé)', + 'uk-ua': '(видалено)', + 'de-ch': '(gestrichen)', + 'pt-br': '(apagado)', }, duplicateRecordSetItem: { - comment: "Example: Duplicate Record Set Item", - "en-us": "Duplicate {recordSetItemTable:string}", - "ru-ru": "Дубликат {recordSetItemTable:string}", - "es-es": "Duplicado {recordSetItemTable:string}", - "uk-ua": "Дублікат {recordSetItemTable:string}", - "de-ch": "Duplikat {recordSetItemTable:string}", - "fr-fr": "Dupliquer {recordSetItemTable:string}", - "pt-br": "Duplicado {recordSetItemTable:string}", + comment: 'Example: Duplicate Record Set Item', + 'en-us': 'Duplicate {recordSetItemTable:string}', + 'ru-ru': 'Дубликат {recordSetItemTable:string}', + 'es-es': 'Duplicado {recordSetItemTable:string}', + 'uk-ua': 'Дублікат {recordSetItemTable:string}', + 'de-ch': 'Duplikat {recordSetItemTable:string}', + 'fr-fr': 'Dupliquer {recordSetItemTable:string}', + 'pt-br': 'Duplicado {recordSetItemTable:string}', }, duplicateRecordSetItemDescription: { - "en-us": - "This record is already present in the current {recordSetTable:string}", - "ru-ru": "Эта запись уже присутствует в текущем {recordSetTable:string}", - "es-es": - "Este registro ya está presente en el actual {recordSetTable:string}", - "fr-fr": - "Cet enregistrement est déjà présent dans le {recordSetTable:string} actuel", - "uk-ua": "Цей запис вже присутній у поточному {recordSetTable:string}", - "de-ch": - "Dieser Datensatz ist bereits im aktuellen {recordSetTable:string} vorhanden.", - "pt-br": "Este registro já está presente no atual {recordSetTable:string}", + 'en-us': + 'This record is already present in the current {recordSetTable:string}', + 'ru-ru': 'Эта запись уже присутствует в текущем {recordSetTable:string}', + 'es-es': + 'Este registro ya está presente en el actual {recordSetTable:string}', + 'fr-fr': + 'Cet enregistrement est déjà présent dans le {recordSetTable:string} actuel', + 'uk-ua': 'Цей запис вже присутній у поточному {recordSetTable:string}', + 'de-ch': + 'Dieser Datensatz ist bereits im aktuellen {recordSetTable:string} vorhanden.', + 'pt-br': 'Este registro já está presente no atual {recordSetTable:string}', }, addToRecordSet: { - "en-us": "Add to {recordSetTable:string}", - "ru-ru": "Добавить в {recordSetTable:string}", - "es-es": "Añadir a {recordSetTable:string}", - "fr-fr": "Ajouter à {recordSetTable:string}", - "uk-ua": "Додати до {recordSetTable:string}", - "de-ch": "Hinzufügen zu {recordSetTable:string}", - "pt-br": "Adicionar a {recordSetTable:string}", + 'en-us': 'Add to {recordSetTable:string}', + 'ru-ru': 'Добавить в {recordSetTable:string}', + 'es-es': 'Añadir a {recordSetTable:string}', + 'fr-fr': 'Ajouter à {recordSetTable:string}', + 'uk-ua': 'Додати до {recordSetTable:string}', + 'de-ch': 'Hinzufügen zu {recordSetTable:string}', + 'pt-br': 'Adicionar a {recordSetTable:string}', }, removeFromRecordSet: { - "en-us": "Remove from {recordSetTable:string}", - "ru-ru": "Удалить из {recordSetTable:string}", - "es-es": "Eliminar de {recordSetTable:string}", - "fr-fr": "Supprimer de {recordSetTable:string}", - "uk-ua": "Видалити з {recordSetTable:string}", - "de-ch": "Entfernen aus {recordSetTable:string}", - "pt-br": "Remover de {recordSetTable:string}", + 'en-us': 'Remove from {recordSetTable:string}', + 'ru-ru': 'Удалить из {recordSetTable:string}', + 'es-es': 'Eliminar de {recordSetTable:string}', + 'fr-fr': 'Supprimer de {recordSetTable:string}', + 'uk-ua': 'Видалити з {recordSetTable:string}', + 'de-ch': 'Entfernen aus {recordSetTable:string}', + 'pt-br': 'Remover de {recordSetTable:string}', }, nothingFound: { - "en-us": "Nothing found", - "ru-ru": "Ничего не найдено", - "es-es": "No se encontró nada", - "fr-fr": "Rien trouvé", - "uk-ua": "Нічого не знайдено", - "de-ch": "Nichts gefunden", - "pt-br": "Nada encontrado", + 'en-us': 'Nothing found', + 'ru-ru': 'Ничего не найдено', + 'es-es': 'No se encontró nada', + 'fr-fr': 'Rien trouvé', + 'uk-ua': 'Нічого не знайдено', + 'de-ch': 'Nichts gefunden', + 'pt-br': 'Nada encontrado', }, carryForward: { - comment: "Verb. Button label", - "en-us": "Carry Forward", - "ru-ru": "Перенести вперед", - "es-es": "Llevar adelante", - "fr-fr": "Reporter", - "uk-ua": "Перенести далі", - "de-ch": "Übertrag", - "pt-br": "Levar adiante", + comment: 'Verb. Button label', + 'en-us': 'Carry Forward', + 'ru-ru': 'Перенести вперед', + 'es-es': 'Llevar adelante', + 'fr-fr': 'Reporter', + 'uk-ua': 'Перенести далі', + 'de-ch': 'Übertrag', + 'pt-br': 'Levar adiante', }, carryForwardEnabled: { - "en-us": "Show Carry Forward button", - "ru-ru": "Показать кнопку «Перенести вперед»", - "es-es": "Mostrar el botón Llevar adelante", - "fr-fr": "Afficher le bouton de report", - "uk-ua": "Показати кнопку «Перенести вперед»", - "de-ch": "Schaltfläche „Übertrag anzeigen“", - "pt-br": "Mostrar botão Transferir para frente", + 'en-us': 'Show Carry Forward button', + 'ru-ru': 'Показать кнопку «Перенести вперед»', + 'es-es': 'Mostrar el botón Llevar adelante', + 'fr-fr': 'Afficher le bouton de report', + 'uk-ua': 'Показати кнопку «Перенести вперед»', + 'de-ch': 'Schaltfläche „Übertrag anzeigen“', + 'pt-br': 'Mostrar botão Transferir para frente', }, bulkCarryForwardEnabled: { - "en-us": "Show Bulk Carry Forward count", - "de-ch": "Anzahl der Massenüberträge anzeigen", - "es-es": "Mostrar recuento de transferencia masiva", - "fr-fr": "Afficher le nombre de reports en masse", - "pt-br": "Mostrar contagem de transporte em massa", - "ru-ru": "Показать счетчик массового переноса данных", - "uk-ua": "Показати кількість групового перенесення", + 'en-us': 'Show Bulk Carry Forward count', + 'de-ch': 'Anzahl der Massenüberträge anzeigen', + 'es-es': 'Mostrar recuento de transferencia masiva', + 'fr-fr': 'Afficher le nombre de reports en masse', + 'pt-br': 'Mostrar contagem de transporte em massa', + 'ru-ru': 'Показать счетчик массового переноса данных', + 'uk-ua': 'Показати кількість групового перенесення', }, bulkCarryForwardCount: { - "en-us": "Bulk Carry Forward count", - "de-ch": "Anzahl der Massenüberträge", - "es-es": "Recuento de transferencia masiva", - "fr-fr": "Nombre de reports en masse", - "pt-br": "Contagem de transporte de carga a granel", - "ru-ru": "Подсчет массового переноса данных", - "uk-ua": "Кількість перенесених даних", + 'en-us': 'Bulk Carry Forward count', + 'de-ch': 'Anzahl der Massenüberträge', + 'es-es': 'Recuento de transferencia masiva', + 'fr-fr': 'Nombre de reports en masse', + 'pt-br': 'Contagem de transporte de carga a granel', + 'ru-ru': 'Подсчет массового переноса данных', + 'uk-ua': 'Кількість перенесених даних', }, carryForwardDescription: { - "en-us": "Create a new record with certain fields carried over", - "ru-ru": "Создайте новую запись с перенесенными определенными полями", - "es-es": "Crear un nuevo registro con ciertos campos transferidos", - "fr-fr": "Créer un nouvel enregistrement avec certains champs reportés", - "uk-ua": "Створити новий запис із перенесенням певних полів", - "de-ch": - "Erstellen Sie einen neuen Datensatz mit bestimmten übernommenen Feldern", - "pt-br": "Crie um novo registro com determinados campos transferidos", + 'en-us': 'Create a new record with certain fields carried over', + 'ru-ru': 'Создайте новую запись с перенесенными определенными полями', + 'es-es': 'Crear un nuevo registro con ciertos campos transferidos', + 'fr-fr': 'Créer un nouvel enregistrement avec certains champs reportés', + 'uk-ua': 'Створити новий запис із перенесенням певних полів', + 'de-ch': + 'Erstellen Sie einen neuen Datensatz mit bestimmten übernommenen Feldern', + 'pt-br': 'Crie um novo registro com determinados campos transferidos', }, carryForwardSettingsDescription: { - "en-us": "Configure fields to carry forward", - "ru-ru": "Настройте поля для переноса", - "es-es": "Configurar campos para transferir", - "fr-fr": "Configurer les champs à reporter", - "uk-ua": "Налаштуйте поля для перенесення", - "de-ch": "Konfigurieren der zu übertragenden Felder", - "pt-br": "Configurar campos para levar adiante", + 'en-us': 'Configure fields to carry forward', + 'ru-ru': 'Настройте поля для переноса', + 'es-es': 'Configurar campos para transferir', + 'fr-fr': 'Configurer les champs à reporter', + 'uk-ua': 'Налаштуйте поля для перенесення', + 'de-ch': 'Konfigurieren der zu übertragenden Felder', + 'pt-br': 'Configurar campos para levar adiante', }, bulkCarryForwardSettingsDescription: { - "en-us": "Configure fields to bulk carry forward", - "de-ch": "Konfigurieren von Feldern für die Massenübertragung", - "es-es": "Configurar campos para transferirlos en masa", - "fr-fr": "Configurer les champs pour un report en masse", - "pt-br": "Configurar campos para transporte em massa", - "ru-ru": "Настройте поля для массового переноса", - "uk-ua": "Налаштуйте поля для масового перенесення", + 'en-us': 'Configure fields to bulk carry forward', + 'de-ch': 'Konfigurieren von Feldern für die Massenübertragung', + 'es-es': 'Configurar campos para transferirlos en masa', + 'fr-fr': 'Configurer les champs pour un report en masse', + 'pt-br': 'Configurar campos para transporte em massa', + 'ru-ru': 'Настройте поля для массового переноса', + 'uk-ua': 'Налаштуйте поля для масового перенесення', }, carryForwardTableSettingsDescription: { - "en-us": "Configure fields to carry forward ({tableName:string})", - "ru-ru": "Настройте поля для переноса ({tableName:string})", - "es-es": "Configurar campos para transferir ({tableName:string})", - "fr-fr": "Configurer les champs à reporter ({tableName:string})", - "uk-ua": "Налаштуйте поля для перенесення ({tableName:string})", - "de-ch": - "Konfigurieren Sie die zu übertragenden Felder ({tableName:string})", - "pt-br": "Configurar campos para levar adiante ({tableName:string})", + 'en-us': 'Configure fields to carry forward ({tableName:string})', + 'ru-ru': 'Настройте поля для переноса ({tableName:string})', + 'es-es': 'Configurar campos para transferir ({tableName:string})', + 'fr-fr': 'Configurer les champs à reporter ({tableName:string})', + 'uk-ua': 'Налаштуйте поля для перенесення ({tableName:string})', + 'de-ch': + 'Konfigurieren Sie die zu übertragenden Felder ({tableName:string})', + 'pt-br': 'Configurar campos para levar adiante ({tableName:string})', }, bulkCarryForwardTableSettingsDescription: { - "en-us": "Configure fields to bulk carry forward ({tableName:string})", - "de-ch": - "Konfigurieren Sie Felder für den Massenübertrag ({tableName:string})", - "es-es": - "Configurar campos para transferirlos en masa ({tableName:string})", - "fr-fr": - "Configurer les champs pour un report en masse ({tableName:string})", - "pt-br": "Configurar campos para transporte em massa ({tableName:string})", - "ru-ru": "Настройте поля для массового переноса ({tableName:string})", - "uk-ua": "Налаштуйте поля для масового перенесення ({tableName:string})", + 'en-us': 'Configure fields to bulk carry forward ({tableName:string})', + 'de-ch': + 'Konfigurieren Sie Felder für den Massenübertrag ({tableName:string})', + 'es-es': + 'Configurar campos para transferirlos en masa ({tableName:string})', + 'fr-fr': + 'Configurer les champs pour un report en masse ({tableName:string})', + 'pt-br': 'Configurar campos para transporte em massa ({tableName:string})', + 'ru-ru': 'Настройте поля для массового переноса ({tableName:string})', + 'uk-ua': 'Налаштуйте поля для масового перенесення ({tableName:string})', }, carryForwardUniqueField: { - "en-us": "This field must be unique. It can not be carried over", - "ru-ru": "Это поле должно быть уникальным. Оно не может быть перенесено.", - "es-es": "Este campo debe ser único. No se puede transferir.", - "fr-fr": "Ce champ doit être unique. Il ne peut pas être reporté.", - "uk-ua": "Це поле має бути унікальним. Його не можна переносити", - "de-ch": "Dieses Feld muss eindeutig sein. Es kann nicht übertragen werden", - "pt-br": "Este campo deve ser único. Não pode ser transferido", + 'en-us': 'This field must be unique. It can not be carried over', + 'ru-ru': 'Это поле должно быть уникальным. Оно не может быть перенесено.', + 'es-es': 'Este campo debe ser único. No se puede transferir.', + 'fr-fr': 'Ce champ doit être unique. Il ne peut pas être reporté.', + 'uk-ua': 'Це поле має бути унікальним. Його не можна переносити', + 'de-ch': 'Dieses Feld muss eindeutig sein. Es kann nicht übertragen werden', + 'pt-br': 'Este campo deve ser único. Não pode ser transferido', }, carryForwardRequiredField: { - "en-us": "This field is required. It must be carried forward", - "ru-ru": "Это поле обязательно для заполнения. Его необходимо перенести.", - "es-es": "Este campo es obligatorio. Debe ser transferido", - "fr-fr": "Ce champ est obligatoire. Il doit être reporté", - "uk-ua": "Це поле обов'язкове. Його потрібно перенести", - "de-ch": "Dieses Feld ist erforderlich. Es muss übertragen werden", - "pt-br": "Este campo é obrigatório. Deve ser levado adiante", + 'en-us': 'This field is required. It must be carried forward', + 'ru-ru': 'Это поле обязательно для заполнения. Его необходимо перенести.', + 'es-es': 'Este campo es obligatorio. Debe ser transferido', + 'fr-fr': 'Ce champ est obligatoire. Il doit être reporté', + 'uk-ua': "Це поле обов'язкове. Його потрібно перенести", + 'de-ch': 'Dieses Feld ist erforderlich. Es muss übertragen werden', + 'pt-br': 'Este campo é obrigatório. Deve ser levado adiante', }, bulkCarryForwardRangeEnabled: { - "en-us": "Show Bulk Carry Forward range", - "de-ch": "Bereich für Massenüberträge anzeigen", - "es-es": "Mostrar rango de transferencia masiva", - "fr-fr": "Afficher la plage de report en masse", - "pt-br": "Mostrar intervalo de transporte em massa", - "ru-ru": "Показать диапазон массового переноса данных", - "uk-ua": "Показати діапазон масового перенесення", + 'en-us': 'Show Bulk Carry Forward range', + 'de-ch': 'Bereich für Massenüberträge anzeigen', + 'es-es': 'Mostrar rango de transferencia masiva', + 'fr-fr': 'Afficher la plage de report en masse', + 'pt-br': 'Mostrar intervalo de transporte em massa', + 'ru-ru': 'Показать диапазон массового переноса данных', + 'uk-ua': 'Показати діапазон масового перенесення', }, bulkCarryForwardRangeErrorDescription: { - "en-us": - "Cannot carry forward record through the specified {field:string} range.", - "de-ch": - "Der Datensatz kann nicht über den angegebenen Bereich {field:string} übertragen werden.", - "es-es": - "No se puede trasladar la grabación al rango {field:string} especificado.", - "fr-fr": + 'en-us': + 'Cannot carry forward record through the specified {field:string} range.', + 'de-ch': + 'Der Datensatz kann nicht über den angegebenen Bereich {field:string} übertragen werden.', + 'es-es': + 'No se puede trasladar la grabación al rango {field:string} especificado.', + 'fr-fr': "Impossible de reporter l'enregistrement sur la plage {field:string} spécifiée.", - "pt-br": - "Não é possível levar o registro adiante através do intervalo especificado {field:string}.", - "ru-ru": - "Невозможно перенести запись через указанный диапазон {field:string}.", - "uk-ua": "Неможливо перенести запис у вказаний діапазон {field:string}.", + 'pt-br': + 'Não é possível levar o registro adiante através do intervalo especificado {field:string}.', + 'ru-ru': + 'Невозможно перенести запись через указанный диапазон {field:string}.', + 'uk-ua': 'Неможливо перенести запис у вказаний діапазон {field:string}.', }, bulkCarryForwardRangeLimitExceeded: { - "en-us": "Range exceeds record limit of {limit:number}.", - "de-ch": - "Der Bereich überschreitet das Aufzeichnungslimit von {limit:number}.", - "es-es": "El rango excede el límite de registro de {limit:number}.", - "fr-fr": "La plage dépasse la limite d'enregistrement de {limit:number}.", - "pt-br": "O alcance excede o limite recorde de {limit:number}.", - "ru-ru": "Диапазон превышает предел записи {limit:number}.", - "uk-ua": "Діапазон перевищує ліміт записів {limit:number}.", + 'en-us': 'Range exceeds record limit of {limit:number}.', + 'de-ch': + 'Der Bereich überschreitet das Aufzeichnungslimit von {limit:number}.', + 'es-es': 'El rango excede el límite de registro de {limit:number}.', + 'fr-fr': "La plage dépasse la limite d'enregistrement de {limit:number}.", + 'pt-br': 'O alcance excede o limite recorde de {limit:number}.', + 'ru-ru': 'Диапазон превышает предел записи {limit:number}.', + 'uk-ua': 'Діапазон перевищує ліміт записів {limit:number}.', }, bulkCarryForwardRangeUnsupportedRelationships: { - "en-us": - "Some relationships with more than one record are not currently supported by Bulk Carry Forward:", - "de-ch": - "Einige Beziehungen mit mehr als einem Datensatz werden derzeit von Bulk Carry Forward nicht unterstützt:", - "es-es": - "Algunas relaciones con más de un registro actualmente no son compatibles con Bulk Carry Forward:", - "fr-fr": - "Certaines relations avec plusieurs enregistrements ne sont actuellement pas prises en charge par le report en bloc :", - "pt-br": - "Alguns relacionamentos com mais de um registro não são suportados atualmente pelo Bulk Carry Forward:", - "ru-ru": - "Некоторые связи с более чем одной записью в настоящее время не поддерживаются функцией массового переноса данных:", - "uk-ua": + 'en-us': + 'Some relationships with more than one record are not currently supported by Bulk Carry Forward:', + 'de-ch': + 'Einige Beziehungen mit mehr als einem Datensatz werden derzeit von Bulk Carry Forward nicht unterstützt:', + 'es-es': + 'Algunas relaciones con más de un registro actualmente no son compatibles con Bulk Carry Forward:', + 'fr-fr': + 'Certaines relations avec plusieurs enregistrements ne sont actuellement pas prises en charge par le report en bloc :', + 'pt-br': + 'Alguns relacionamentos com mais de um registro não são suportados atualmente pelo Bulk Carry Forward:', + 'ru-ru': + 'Некоторые связи с более чем одной записью в настоящее время не поддерживаются функцией массового переноса данных:', + 'uk-ua': "Деякі зв'язки з кількома записами наразі не підтримуються груповим перенесенням:", }, bulkCarryForwardRangeExistingRecords: { - "en-us": "The following numbers for {field:string} are already being used:", - "de-ch": "Folgende Nummern für {field:string} werden bereits verwendet:", - "es-es": - "Los siguientes números para {field:string} ya se están utilizando:", - "fr-fr": "Les numéros suivants pour {field:string} sont déjà utilisés :", - "pt-br": "Os seguintes números para {field:string} já estão sendo usados:", - "ru-ru": "Следующие номера для {field:string} уже используются:", - "uk-ua": "Наступні номери для {field:string} вже використовуються:", + 'en-us': 'The following numbers for {field:string} are already being used:', + 'de-ch': 'Folgende Nummern für {field:string} werden bereits verwendet:', + 'es-es': + 'Los siguientes números para {field:string} ya se están utilizando:', + 'fr-fr': 'Les numéros suivants pour {field:string} sont déjà utilisés :', + 'pt-br': 'Os seguintes números para {field:string} já estão sendo usados:', + 'ru-ru': 'Следующие номера для {field:string} уже используются:', + 'uk-ua': 'Наступні номери для {field:string} вже використовуються:', }, bulkCarryForwardRangeStart: { - "en-us": "Carry Forward Range Start", - "de-ch": "Übertragsbereichsanfang", - "es-es": "Arranque del rango de transferencia hacia adelante", - "fr-fr": "Début de la plage de report", - "pt-br": "Início do intervalo de transporte", - "ru-ru": "Начало диапазона переноса вперед", - "uk-ua": "Початок діапазону перенесення вперед", + 'en-us': 'Carry Forward Range Start', + 'de-ch': 'Übertragsbereichsanfang', + 'es-es': 'Arranque del rango de transferencia hacia adelante', + 'fr-fr': 'Début de la plage de report', + 'pt-br': 'Início do intervalo de transporte', + 'ru-ru': 'Начало диапазона переноса вперед', + 'uk-ua': 'Початок діапазону перенесення вперед', }, bulkCarryForwardRangeEnd: { - "en-us": "Carry Forward Range End", - "de-ch": "Übertragsbereichsende", - "es-es": "Fin del rango de arrastre", - "fr-fr": "Fin de la plage de report", - "pt-br": "Fim do intervalo de transporte para frente", - "ru-ru": "Конец диапазона переноса вперед", - "uk-ua": "Кінець діапазону перенесення вперед", + 'en-us': 'Carry Forward Range End', + 'de-ch': 'Übertragsbereichsende', + 'es-es': 'Fin del rango de arrastre', + 'fr-fr': 'Fin de la plage de report', + 'pt-br': 'Fim do intervalo de transporte para frente', + 'ru-ru': 'Конец диапазона переноса вперед', + 'uk-ua': 'Кінець діапазону перенесення вперед', }, createRecordSetOnBulkCarryForward: { - "en-us": "Create record set on Bulk Carry Forward", - "de-ch": "Datensatz für Massenübertrag erstellen", - "es-es": "Crear un conjunto de registros en la transferencia masiva", - "fr-fr": "Créer un ensemble d'enregistrements sur le report en masse", - "pt-br": "Criar conjunto de registros em Bulk Carry Forward", - "ru-ru": "Создать набор записей для массового переноса данных", - "uk-ua": "Створення набору записів для групового перенесення", + 'en-us': 'Create record set on Bulk Carry Forward', + 'de-ch': 'Datensatz für Massenübertrag erstellen', + 'es-es': 'Crear un conjunto de registros en la transferencia masiva', + 'fr-fr': "Créer un ensemble d'enregistrements sur le report en masse", + 'pt-br': 'Criar conjunto de registros em Bulk Carry Forward', + 'ru-ru': 'Создать набор записей для массового переноса данных', + 'uk-ua': 'Створення набору записів для групового перенесення', }, cloneButtonEnabled: { - "en-us": "Show Clone button", - "ru-ru": "Показать кнопку «Клонировать»", - "es-es": "Mostrar botón Clonar", - "fr-fr": "Afficher le bouton Cloner", - "uk-ua": "Кнопка «Показати клон»", - "de-ch": "Schaltfläche „Klonen“ anzeigen", - "pt-br": "Mostrar botão Clonar", + 'en-us': 'Show Clone button', + 'ru-ru': 'Показать кнопку «Клонировать»', + 'es-es': 'Mostrar botón Clonar', + 'fr-fr': 'Afficher le bouton Cloner', + 'uk-ua': 'Кнопка «Показати клон»', + 'de-ch': 'Schaltfläche „Klonen“ anzeigen', + 'pt-br': 'Mostrar botão Clonar', }, addButtonEnabled: { - "en-us": "Show Add button", - "ru-ru": "Показать кнопку «Добавить»", - "es-es": "Mostrar el botón Agregar", - "fr-fr": "Afficher le bouton Ajouter", - "uk-ua": "Показати кнопку «Додати»", - "de-ch": "Schaltfläche „Hinzufügen“ anzeigen", - "pt-br": "Mostrar botão Adicionar", + 'en-us': 'Show Add button', + 'ru-ru': 'Показать кнопку «Добавить»', + 'es-es': 'Mostrar el botón Agregar', + 'fr-fr': 'Afficher le bouton Ajouter', + 'uk-ua': 'Показати кнопку «Додати»', + 'de-ch': 'Schaltfläche „Hinzufügen“ anzeigen', + 'pt-br': 'Mostrar botão Adicionar', }, addButtonDescription: { - "en-us": "Create a new blank record", - "ru-ru": "Создать новую пустую запись", - "es-es": "Crear un nuevo registro en blanco", - "fr-fr": "Créer un nouvel enregistrement vierge", - "uk-ua": "Створити новий пустий запис", - "de-ch": "Erstellen Sie einen neuen leeren Datensatz", - "pt-br": "Criar um novo registro em branco", + 'en-us': 'Create a new blank record', + 'ru-ru': 'Создать новую пустую запись', + 'es-es': 'Crear un nuevo registro en blanco', + 'fr-fr': 'Créer un nouvel enregistrement vierge', + 'uk-ua': 'Створити новий пустий запис', + 'de-ch': 'Erstellen Sie einen neuen leeren Datensatz', + 'pt-br': 'Criar um novo registro em branco', }, autoNumbering: { - "en-us": "Auto Numbering", - "ru-ru": "Автоматическая нумерация", - "es-es": "Numeración automática", - "fr-fr": "Numérotation automatique", - "uk-ua": "Автоматична нумерація", - "de-ch": "Automatische Nummerierung", - "pt-br": "Numeração automática", + 'en-us': 'Auto Numbering', + 'ru-ru': 'Автоматическая нумерация', + 'es-es': 'Numeración automática', + 'fr-fr': 'Numérotation automatique', + 'uk-ua': 'Автоматична нумерація', + 'de-ch': 'Automatische Nummerierung', + 'pt-br': 'Numeração automática', }, editFormDefinition: { - "en-us": "Edit Form Definition", - "ru-ru": "Редактировать определение формы", - "es-es": "Editar definición de formulario", - "fr-fr": "Modifier la définition du formulaire", - "uk-ua": "Редагувати визначення форми", - "de-ch": "Formulardefinition bearbeiten", - "pt-br": "Editar definição de formulário", + 'en-us': 'Edit Form Definition', + 'ru-ru': 'Редактировать определение формы', + 'es-es': 'Editar definición de formulario', + 'fr-fr': 'Modifier la définition du formulaire', + 'uk-ua': 'Редагувати визначення форми', + 'de-ch': 'Formulardefinition bearbeiten', + 'pt-br': 'Editar definição de formulário', }, useAutoGeneratedForm: { - "en-us": "Use Auto Generated Form", - "ru-ru": "Использовать автоматически сгенерированную форму", - "es-es": "Utilice el formulario generado automáticamente", - "fr-fr": "Utiliser le formulaire généré automatiquement", - "uk-ua": "Використати автоматично згенеровану форму", - "de-ch": "Automatisch generiertes Formular verwenden", - "pt-br": "Usar formulário gerado automaticamente", + 'en-us': 'Use Auto Generated Form', + 'ru-ru': 'Использовать автоматически сгенерированную форму', + 'es-es': 'Utilice el formulario generado automáticamente', + 'fr-fr': 'Utiliser le formulaire généré automatiquement', + 'uk-ua': 'Використати автоматично згенеровану форму', + 'de-ch': 'Automatisch generiertes Formular verwenden', + 'pt-br': 'Usar formulário gerado automaticamente', }, useFieldLabels: { - "en-us": "Use Localized Field Labels", - "ru-ru": "Используйте локализованные метки полей", - "es-es": "Utilice etiquetas de campo localizadas", - "fr-fr": "Utiliser des étiquettes de champ localisées", - "uk-ua": "Використовуйте локалізовані мітки полів", - "de-ch": "Lokalisierte Feldbezeichnungen verwenden", - "pt-br": "Use rótulos de campo localizados", + 'en-us': 'Use Localized Field Labels', + 'ru-ru': 'Используйте локализованные метки полей', + 'es-es': 'Utilice etiquetas de campo localizadas', + 'fr-fr': 'Utiliser des étiquettes de champ localisées', + 'uk-ua': 'Використовуйте локалізовані мітки полів', + 'de-ch': 'Lokalisierte Feldbezeichnungen verwenden', + 'pt-br': 'Use rótulos de campo localizados', }, showFieldLabels: { - "en-us": "Show Localized Field Labels", - "de-ch": "Lokalisierte Feldbezeichnungen anzeigen", - "es-es": "Mostrar etiquetas de campos localizados", - "fr-fr": "Afficher les étiquettes de champ localisées", - "ru-ru": "Показать локализованные метки полей", - "uk-ua": "Показати локалізовані підписи полів", - "pt-br": "Mostrar rótulos de campos localizados", + 'en-us': 'Show Localized Field Labels', + 'de-ch': 'Lokalisierte Feldbezeichnungen anzeigen', + 'es-es': 'Mostrar etiquetas de campos localizados', + 'fr-fr': 'Afficher les étiquettes de champ localisées', + 'ru-ru': 'Показать локализованные метки полей', + 'uk-ua': 'Показати локалізовані підписи полів', + 'pt-br': 'Mostrar rótulos de campos localizados', }, showDataModelLabels: { - "en-us": "Show Data Model Field Names", - "de-ch": "Datenmodell-Feldnamen anzeigen", - "es-es": "Mostrar nombres de campos del modelo de datos", - "fr-fr": "Afficher les noms des champs du modèle de données", - "ru-ru": "Показать имена полей модели данных", - "uk-ua": "Показати назви полів моделі даних", - "pt-br": "Mostrar nomes de campos do modelo de dados", + 'en-us': 'Show Data Model Field Names', + 'de-ch': 'Datenmodell-Feldnamen anzeigen', + 'es-es': 'Mostrar nombres de campos del modelo de datos', + 'fr-fr': 'Afficher les noms des champs du modèle de données', + 'ru-ru': 'Показать имена полей модели данных', + 'uk-ua': 'Показати назви полів моделі даних', + 'pt-br': 'Mostrar nomes de campos do modelo de dados', }, editHistory: { - "en-us": "Edit history", - "ru-ru": "История редактирования", - "es-es": "Historial de edición", - "fr-fr": "Modifier l'historique", - "uk-ua": "Історія редагування", - "de-ch": "Bearbeitungsgeschichte", - "pt-br": "Editar histórico", + 'en-us': 'Edit history', + 'ru-ru': 'История редактирования', + 'es-es': 'Historial de edición', + 'fr-fr': "Modifier l'historique", + 'uk-ua': 'Історія редагування', + 'de-ch': 'Bearbeitungsgeschichte', + 'pt-br': 'Editar histórico', }, editHistoryQueryName: { - "en-us": 'Edit history for "{formattedRecord:string}"', - "ru-ru": "История изменений для «{formattedRecord:string}»", - "es-es": 'Historial de edición de "{formattedRecord:string}"', - "fr-fr": "Historique des modifications pour « {formattedRecord:string} »", - "uk-ua": 'Історія редагувань для "{formattedRecord:string}"', - "de-ch": "Bearbeitungsverlauf für „{formattedRecord:string}“", - "pt-br": 'Histórico de edição para "{formattedRecord:string}"', + 'en-us': 'Edit history for "{formattedRecord:string}"', + 'ru-ru': 'История изменений для «{formattedRecord:string}»', + 'es-es': 'Historial de edición de "{formattedRecord:string}"', + 'fr-fr': 'Historique des modifications pour « {formattedRecord:string} »', + 'uk-ua': 'Історія редагувань для "{formattedRecord:string}"', + 'de-ch': 'Bearbeitungsverlauf für „{formattedRecord:string}“', + 'pt-br': 'Histórico de edição para "{formattedRecord:string}"', }, formConfiguration: { - "en-us": "Form Configuration", - "ru-ru": "Конфигурация формы", - "es-es": "Configuración del formulario", - "fr-fr": "Configuration du formulaire", - "uk-ua": "Конфігурація форми", - "de-ch": "Formularkonfiguration", - "pt-br": "Configuração do formulário", + 'en-us': 'Form Configuration', + 'ru-ru': 'Конфигурация формы', + 'es-es': 'Configuración del formulario', + 'fr-fr': 'Configuration du formulaire', + 'uk-ua': 'Конфігурація форми', + 'de-ch': 'Formularkonfiguration', + 'pt-br': 'Configuração do formulário', }, formState: { - "en-us": "Form State", - "ru-ru": "Форма государства", - "es-es": "Estado del formulario", - "fr-fr": "État du formulaire", - "uk-ua": "Стан форми", - "de-ch": "Formularstatus", - "pt-br": "Estado do formulário", + 'en-us': 'Form State', + 'ru-ru': 'Форма государства', + 'es-es': 'Estado del formulario', + 'fr-fr': 'État du formulaire', + 'uk-ua': 'Стан форми', + 'de-ch': 'Formularstatus', + 'pt-br': 'Estado do formulário', }, recordInformation: { - "en-us": "Record Information", - "ru-ru": "Запись информации", - "es-es": "Información de registro", - "fr-fr": "Informations sur le dossier", - "uk-ua": "Інформація про запис", - "de-ch": "Datensatzinformationen", - "pt-br": "Informações do registro", + 'en-us': 'Record Information', + 'ru-ru': 'Запись информации', + 'es-es': 'Información de registro', + 'fr-fr': 'Informations sur le dossier', + 'uk-ua': 'Інформація про запис', + 'de-ch': 'Datensatzinformationen', + 'pt-br': 'Informações do registro', }, shareRecord: { - "en-us": "Share Record", - "ru-ru": "Поделиться записью", - "es-es": "Compartir registro", - "fr-fr": "Partager l'enregistrement", - "uk-ua": "Поділитися записом", - "de-ch": "Datensatz teilen", - "pt-br": "Compartilhar registro", + 'en-us': 'Share Record', + 'ru-ru': 'Поделиться записью', + 'es-es': 'Compartir registro', + 'fr-fr': "Partager l'enregistrement", + 'uk-ua': 'Поділитися записом', + 'de-ch': 'Datensatz teilen', + 'pt-br': 'Compartilhar registro', }, findUsages: { - "en-us": "Find usages", - "ru-ru": "Найти случаи использования", - "es-es": "Encuentra usos", - "fr-fr": "Trouver des utilisations", - "uk-ua": "Знайти вживання", - "de-ch": "Verwendungen finden", - "pt-br": "Encontre usos", + 'en-us': 'Find usages', + 'ru-ru': 'Найти случаи использования', + 'es-es': 'Encuentra usos', + 'fr-fr': 'Trouver des utilisations', + 'uk-ua': 'Знайти вживання', + 'de-ch': 'Verwendungen finden', + 'pt-br': 'Encontre usos', }, usagesOfPickList: { - "en-us": 'Usages of "{pickList:string}" pick list', - "ru-ru": "Использование списка выбора «{pickList:string}»", - "es-es": 'Usos de la lista de selección "{pickList:string}"', - "fr-fr": "Utilisations de la liste de sélection « {pickList:string} »", - "uk-ua": 'Використання списку вибору "{pickList:string}"', - "de-ch": "Verwendung der Auswahlliste „{pickList:string}“", - "pt-br": 'Usos da lista de seleção "{pickList:string}"', + 'en-us': 'Usages of "{pickList:string}" pick list', + 'ru-ru': 'Использование списка выбора «{pickList:string}»', + 'es-es': 'Usos de la lista de selección "{pickList:string}"', + 'fr-fr': 'Utilisations de la liste de sélection « {pickList:string} »', + 'uk-ua': 'Використання списку вибору "{pickList:string}"', + 'de-ch': 'Verwendung der Auswahlliste „{pickList:string}“', + 'pt-br': 'Usos da lista de seleção "{pickList:string}"', }, subForm: { - "en-us": "Subform", - "ru-ru": "Подчиненная форма", - "es-es": "Subform", - "fr-fr": "Sous-formulaire", - "uk-ua": "Підформа", - "de-ch": "Unterformular", - "pt-br": "Subform", + 'en-us': 'Subform', + 'ru-ru': 'Подчиненная форма', + 'es-es': 'Subform', + 'fr-fr': 'Sous-formulaire', + 'uk-ua': 'Підформа', + 'de-ch': 'Unterformular', + 'pt-br': 'Subform', }, formTable: { - "en-us": "Grid", - "ru-ru": "Сетка", - "es-es": "Red", - "fr-fr": "Grille", - "uk-ua": "Сітка", - "de-ch": "Netz", - "pt-br": "Grade", + 'en-us': 'Grid', + 'ru-ru': 'Сетка', + 'es-es': 'Red', + 'fr-fr': 'Grille', + 'uk-ua': 'Сітка', + 'de-ch': 'Netz', + 'pt-br': 'Grade', }, subviewConfiguration: { - "en-us": "Subview", - "ru-ru": "Подвид", - "es-es": "Subvista", - "uk-ua": "Підвид", - "de-ch": "Unteransicht", - "fr-fr": "Sous-vue", - "pt-br": "Subvisualização", + 'en-us': 'Subview', + 'ru-ru': 'Подвид', + 'es-es': 'Subvista', + 'uk-ua': 'Підвид', + 'de-ch': 'Unteransicht', + 'fr-fr': 'Sous-vue', + 'pt-br': 'Subvisualização', }, disableReadOnly: { - "en-us": "Disable read-only mode", - "ru-ru": "Отключить режим только для чтения", - "es-es": "Deshabilitar el modo de solo lectura", - "fr-fr": "Désactiver le mode lecture seule", - "uk-ua": "Вимкнути режим лише для читання", - "de-ch": "Deaktivieren Sie den Nur-Lese-Modus", - "pt-br": "Desativar modo somente leitura", + 'en-us': 'Disable read-only mode', + 'ru-ru': 'Отключить режим только для чтения', + 'es-es': 'Deshabilitar el modo de solo lectura', + 'fr-fr': 'Désactiver le mode lecture seule', + 'uk-ua': 'Вимкнути режим лише для читання', + 'de-ch': 'Deaktivieren Sie den Nur-Lese-Modus', + 'pt-br': 'Desativar modo somente leitura', }, enableReadOnly: { - "en-us": "Enable read-only mode", - "ru-ru": "Включить режим только для чтения", - "es-es": "Habilitar el modo de solo lectura", - "fr-fr": "Activer le mode lecture seule", - "uk-ua": "Увімкнути режим лише для читання", - "de-ch": "Aktivieren Sie den Nur-Lese-Modus", - "pt-br": "Habilitar modo somente leitura", + 'en-us': 'Enable read-only mode', + 'ru-ru': 'Включить режим только для чтения', + 'es-es': 'Habilitar el modo de solo lectura', + 'fr-fr': 'Activer le mode lecture seule', + 'uk-ua': 'Увімкнути режим лише для читання', + 'de-ch': 'Aktivieren Sie den Nur-Lese-Modus', + 'pt-br': 'Habilitar modo somente leitura', }, configureDataEntryTables: { - "en-us": "Configure data entry tables", - "ru-ru": "Настройте таблицы ввода данных", - "es-es": "Configurar tablas de entrada de datos", - "fr-fr": "Configurer les tables de saisie de données", - "uk-ua": "Налаштування таблиць для введення даних", - "de-ch": "Konfigurieren von Dateneingabetabellen", - "pt-br": "Configurar tabelas de entrada de dados", + 'en-us': 'Configure data entry tables', + 'ru-ru': 'Настройте таблицы ввода данных', + 'es-es': 'Configurar tablas de entrada de datos', + 'fr-fr': 'Configurer les tables de saisie de données', + 'uk-ua': 'Налаштування таблиць для введення даних', + 'de-ch': 'Konfigurieren von Dateneingabetabellen', + 'pt-br': 'Configurar tabelas de entrada de dados', }, configureInteractionTables: { - "en-us": "Configure interaction tables", - "ru-ru": "Настроить таблицы взаимодействия", - "es-es": "Configurar tablas de interacción", - "fr-fr": "Configurer les tables d'interaction", - "uk-ua": "Налаштування таблиць взаємодії", - "de-ch": "Konfigurieren von Interaktionstabellen", - "pt-br": "Configurar tabelas de interação", + 'en-us': 'Configure interaction tables', + 'ru-ru': 'Настроить таблицы взаимодействия', + 'es-es': 'Configurar tablas de interacción', + 'fr-fr': "Configurer les tables d'interaction", + 'uk-ua': 'Налаштування таблиць взаємодії', + 'de-ch': 'Konfigurieren von Interaktionstabellen', + 'pt-br': 'Configurar tabelas de interação', }, formMeta: { - "en-us": "Form Meta", - "ru-ru": "Форма Мета", - "es-es": "Meta del formulario", - "fr-fr": "Formulaire Méta", - "uk-ua": "Метадані форми", - "de-ch": "Formular-Metadaten", - "pt-br": "Formulário Meta", + 'en-us': 'Form Meta', + 'ru-ru': 'Форма Мета', + 'es-es': 'Meta del formulario', + 'fr-fr': 'Formulaire Méta', + 'uk-ua': 'Метадані форми', + 'de-ch': 'Formular-Metadaten', + 'pt-br': 'Formulário Meta', }, newResourceTitle: { - "en-us": "New {tableName:string}", - "ru-ru": "Новый {tableName:string}", - "es-es": "Nuevo {tableName:string}", - "fr-fr": "Nouveau {tableName:string}", - "uk-ua": "Новий {tableName:string}", - "de-ch": "Neu {tableName:string}", - "pt-br": "Novo {tableName:string}", + 'en-us': 'New {tableName:string}', + 'ru-ru': 'Новый {tableName:string}', + 'es-es': 'Nuevo {tableName:string}', + 'fr-fr': 'Nouveau {tableName:string}', + 'uk-ua': 'Новий {tableName:string}', + 'de-ch': 'Neu {tableName:string}', + 'pt-br': 'Novo {tableName:string}', }, resourceFormatter: { comment: ` When resource does not have a formatter defined, this formatter is used `, - "en-us": "{tableName:string} #{id:number}", - "ru-ru": "{tableName:string} #{id:number}", - "es-es": "{tableName:string} #{id:number}", - "fr-fr": "{tableName:string} #{id:number}", - "uk-ua": "{tableName:string} '#{id:number}", - "de-ch": "{tableName:string} #{id:number}", - "pt-br": "{tableName:string} #{id:number}", + 'en-us': '{tableName:string} #{id:number}', + 'ru-ru': '{tableName:string} #{id:number}', + 'es-es': '{tableName:string} #{id:number}', + 'fr-fr': '{tableName:string} #{id:number}', + 'uk-ua': "{tableName:string} '#{id:number}", + 'de-ch': '{tableName:string} #{id:number}', + 'pt-br': '{tableName:string} #{id:number}', }, resourceDeleted: { - "en-us": "Resource deleted", - "ru-ru": "Ресурс удален", - "es-es": "Recurso eliminado", - "fr-fr": "Ressource supprimée", - "uk-ua": "Ресурс видалено", - "de-ch": "Ressource gelöscht", - "pt-br": "Recurso excluído", + 'en-us': 'Resource deleted', + 'ru-ru': 'Ресурс удален', + 'es-es': 'Recurso eliminado', + 'fr-fr': 'Ressource supprimée', + 'uk-ua': 'Ресурс видалено', + 'de-ch': 'Ressource gelöscht', + 'pt-br': 'Recurso excluído', }, resourceDeletedDescription: { - "en-us": "Item was deleted successfully.", - "ru-ru": "Элемент был успешно удален.", - "es-es": "El artículo fue eliminado exitosamente", - "fr-fr": "L'élément a été supprimé avec succès.", - "uk-ua": "Елемент успішно видалено.", - "de-ch": "Element wurde erfolgreich gelöscht.", - "pt-br": "O item foi excluído com sucesso.", + 'en-us': 'Item was deleted successfully.', + 'ru-ru': 'Элемент был успешно удален.', + 'es-es': 'El artículo fue eliminado exitosamente', + 'fr-fr': "L'élément a été supprimé avec succès.", + 'uk-ua': 'Елемент успішно видалено.', + 'de-ch': 'Element wurde erfolgreich gelöscht.', + 'pt-br': 'O item foi excluído com sucesso.', }, dateRange: { - "en-us": "(Range: {from:string} - {to:string})", - "ru-ru": "(Диапазон: {from:string} - {to:string})", - "es-es": "(Rango: {from:string} - {to:string})", - "fr-fr": "(Plage : {from:string} - {to:string})", - "uk-ua": "(Діапазон: {from:string} - {to:string})", - "de-ch": "(Bereich: {from:string} – {to:string})", - "pt-br": "(Intervalo: {from:string} - {to:string})", + 'en-us': '(Range: {from:string} - {to:string})', + 'ru-ru': '(Диапазон: {from:string} - {to:string})', + 'es-es': '(Rango: {from:string} - {to:string})', + 'fr-fr': '(Plage : {from:string} - {to:string})', + 'uk-ua': '(Діапазон: {from:string} - {to:string})', + 'de-ch': '(Bereich: {from:string} – {to:string})', + 'pt-br': '(Intervalo: {from:string} - {to:string})', }, catalogNumberNumericFormatter: { comment: 'Meaning "Catalog Number Numeric formatter"', - "en-us": "Catalog Number Numeric", - "de-ch": "Katalognummer Numerisch", - "es-es": "Número de catálogo numérico", - "fr-fr": "Numéro de catalogue numérique", - "ru-ru": "Номер каталога Цифровой", - "uk-ua": "Номер у каталозі (числовий)", - "pt-br": "Número de catálogo Numérico", + 'en-us': 'Catalog Number Numeric', + 'de-ch': 'Katalognummer Numerisch', + 'es-es': 'Número de catálogo numérico', + 'fr-fr': 'Numéro de catalogue numérique', + 'ru-ru': 'Номер каталога Цифровой', + 'uk-ua': 'Номер у каталозі (числовий)', + 'pt-br': 'Número de catálogo Numérico', }, addCOGChildren: { - "en-us": "Add COG Children", - "de-ch": "COG-Kinder hinzufügen", - "es-es": "Agregar niños COG", - "fr-fr": "Ajouter des enfants COG", - "pt-br": "Adicionar crianças COG", - "ru-ru": "Добавить детей COG", - "uk-ua": "Додати дочірні елементи COG", + 'en-us': 'Add COG Children', + 'de-ch': 'COG-Kinder hinzufügen', + 'es-es': 'Agregar niños COG', + 'fr-fr': 'Ajouter des enfants COG', + 'pt-br': 'Adicionar crianças COG', + 'ru-ru': 'Добавить детей COG', + 'uk-ua': 'Додати дочірні елементи COG', }, } as const); diff --git a/specifyweb/frontend/js_src/lib/localization/preferences.ts b/specifyweb/frontend/js_src/lib/localization/preferences.ts index f8d8958aa40..fe34ece5760 100644 --- a/specifyweb/frontend/js_src/lib/localization/preferences.ts +++ b/specifyweb/frontend/js_src/lib/localization/preferences.ts @@ -4,2078 +4,2132 @@ * @module */ -import { createDictionary } from "./utils"; +import { createDictionary } from './utils'; // Refer to "Guidelines for Programmers" in ./README.md before editing this file export const preferencesText = createDictionary({ preferences: { - "en-us": "Preferences", - "ru-ru": "Предпочтения", - "es-es": "Preferencias", - "fr-fr": "Préférences", - "uk-ua": "Уподобання", - "de-ch": "Einstellungen", - "pt-br": "Preferências", + 'en-us': 'Preferences', + 'ru-ru': 'Предпочтения', + 'es-es': 'Preferencias', + 'fr-fr': 'Préférences', + 'uk-ua': 'Уподобання', + 'de-ch': 'Einstellungen', + 'pt-br': 'Preferências', }, customization: { - "en-us": "Customization", - "ru-ru": "Настройка", - "es-es": "Personalización", - "fr-fr": "Personnalisation", - "uk-ua": "Спеціальнізація", - "de-ch": "Anpassung", - "pt-br": "Personalização", + 'en-us': 'Customization', + 'ru-ru': 'Настройка', + 'es-es': 'Personalización', + 'fr-fr': 'Personnalisation', + 'uk-ua': 'Спеціальнізація', + 'de-ch': 'Anpassung', + 'pt-br': 'Personalização', }, userPreferences: { - "en-us": "User Preferences", - "ru-ru": "Настройки пользователя", - "es-es": "Preferencias del usuario", - "fr-fr": "Préférences de l'utilisateur", - "uk-ua": "Налаштування користувача", - "de-ch": "Benutzereinstellungen", - "pt-br": "Preferências do usuário", + 'en-us': 'User Preferences', + 'ru-ru': 'Настройки пользователя', + 'es-es': 'Preferencias del usuario', + 'fr-fr': "Préférences de l'utilisateur", + 'uk-ua': 'Налаштування користувача', + 'de-ch': 'Benutzereinstellungen', + 'pt-br': 'Preferências do usuário', }, defaultUserPreferences: { - "en-us": "Default User Preferences", - "ru-ru": "Настройки пользователя по умолчанию", - "es-es": "Preferencias de usuario predeterminadas", - "fr-fr": "Préférences utilisateur par défaut", - "uk-ua": "Параметри користувача за умовчанням", - "de-ch": "Standardbenutzereinstellungen", - "pt-br": "Preferências de usuário padrão", + 'en-us': 'Default User Preferences', + 'ru-ru': 'Настройки пользователя по умолчанию', + 'es-es': 'Preferencias de usuario predeterminadas', + 'fr-fr': 'Préférences utilisateur par défaut', + 'uk-ua': 'Параметри користувача за умовчанням', + 'de-ch': 'Standardbenutzereinstellungen', + 'pt-br': 'Preferências de usuário padrão', }, general: { - "en-us": "General", - "ru-ru": "Общий", - "es-es": "General", - "fr-fr": "Image personnalisée", - "uk-ua": "Спеціальне зображення", - "de-ch": "Allgemein", - "pt-br": "Em geral", + 'en-us': 'General', + 'ru-ru': 'Общий', + 'es-es': 'General', + 'fr-fr': 'Image personnalisée', + 'uk-ua': 'Спеціальне зображення', + 'de-ch': 'Allgemein', + 'pt-br': 'Em geral', }, ui: { - "en-us": "User Interface", - "ru-ru": "Пользовательский интерфейс", - "es-es": "Interfaz de usuario", - "fr-fr": "Interface utilisateur", - "uk-ua": "Інтерфейс користувача", - "de-ch": "Benutzeroberfläche", - "pt-br": "Interface do usuário", + 'en-us': 'User Interface', + 'ru-ru': 'Пользовательский интерфейс', + 'es-es': 'Interfaz de usuario', + 'fr-fr': 'Interface utilisateur', + 'uk-ua': 'Інтерфейс користувача', + 'de-ch': 'Benutzeroberfläche', + 'pt-br': 'Interface do usuário', }, theme: { - "en-us": "Theme", - "ru-ru": "Тема", - "es-es": "Tema", - "fr-fr": "Thème", - "uk-ua": "Тема", - "de-ch": "Thema", - "pt-br": "Tema", + 'en-us': 'Theme', + 'ru-ru': 'Тема', + 'es-es': 'Tema', + 'fr-fr': 'Thème', + 'uk-ua': 'Тема', + 'de-ch': 'Thema', + 'pt-br': 'Tema', }, useSystemSetting: { - "en-us": "Use system setting", - "ru-ru": "Использовать системные настройки", - "es-es": "Utilizar la configuración del sistema", - "fr-fr": "Utiliser les paramètres du système", - "uk-ua": "Використовуйте налаштування системи", - "de-ch": "Systemeinstellung verwenden", - "pt-br": "Usar configuração do sistema", + 'en-us': 'Use system setting', + 'ru-ru': 'Использовать системные настройки', + 'es-es': 'Utilizar la configuración del sistema', + 'fr-fr': 'Utiliser les paramètres du système', + 'uk-ua': 'Використовуйте налаштування системи', + 'de-ch': 'Systemeinstellung verwenden', + 'pt-br': 'Usar configuração do sistema', }, inheritOsSettings: { - "en-us": "Copies value from your Operating System settings", - "ru-ru": "Копирует значение из настроек вашей операционной системы", - "es-es": "Copia el valor de la configuración de su sistema operativo", - "fr-fr": "Copie la valeur des paramètres de votre système d'exploitation", - "uk-ua": "Копіює значення з налаштувань вашої операційної системи", - "de-ch": "Übernimmt den Wert aus Ihren Betriebssystemeinstellungen", - "pt-br": "Copia o valor das configurações do seu sistema operacional", + 'en-us': 'Copies value from your Operating System settings', + 'ru-ru': 'Копирует значение из настроек вашей операционной системы', + 'es-es': 'Copia el valor de la configuración de su sistema operativo', + 'fr-fr': "Copie la valeur des paramètres de votre système d'exploitation", + 'uk-ua': 'Копіює значення з налаштувань вашої операційної системи', + 'de-ch': 'Übernimmt den Wert aus Ihren Betriebssystemeinstellungen', + 'pt-br': 'Copia o valor das configurações do seu sistema operacional', }, light: { - comment: "Light mode", - "en-us": "Light", - "ru-ru": "Свет", - "es-es": "Luz", - "fr-fr": "Lumière", - "uk-ua": "світло", - "de-ch": "Hell", - "pt-br": "Luz", + comment: 'Light mode', + 'en-us': 'Light', + 'ru-ru': 'Свет', + 'es-es': 'Luz', + 'fr-fr': 'Lumière', + 'uk-ua': 'світло', + 'de-ch': 'Hell', + 'pt-br': 'Luz', }, dark: { - comment: "Dark mode", - "en-us": "Dark", - "ru-ru": "Темный", - "es-es": "Oscuro", - "fr-fr": "Sombre", - "uk-ua": "Темний", - "de-ch": "Dunkel", - "pt-br": "Escuro", + comment: 'Dark mode', + 'en-us': 'Dark', + 'ru-ru': 'Темный', + 'es-es': 'Oscuro', + 'fr-fr': 'Sombre', + 'uk-ua': 'Темний', + 'de-ch': 'Dunkel', + 'pt-br': 'Escuro', }, reduceMotion: { - "en-us": "Reduce motion", - "ru-ru": "Уменьшить движение", - "es-es": "Reducir el movimiento", - "fr-fr": "Réduire les mouvements", - "uk-ua": "Зменшити рух", - "de-ch": "Bewegung reduzieren", - "pt-br": "Reduzir movimento", + 'en-us': 'Reduce motion', + 'ru-ru': 'Уменьшить движение', + 'es-es': 'Reducir el movimiento', + 'fr-fr': 'Réduire les mouvements', + 'uk-ua': 'Зменшити рух', + 'de-ch': 'Bewegung reduzieren', + 'pt-br': 'Reduzir movimento', }, reduceMotionDescription: { - "en-us": "Disable non-essential animations and transitions.", - "ru-ru": "Отключите ненужные анимации и переходы.", - "es-es": "Desactivar animaciones y transiciones no esenciales.", - "fr-fr": "Désactivez les animations et les transitions non essentielles.", - "uk-ua": "Вимкніть необов'язкову анімацію та переходи.", - "de-ch": "Nicht erforderliche Animationen und Übergänge deaktivieren.", - "pt-br": "Desabilite animações e transições não essenciais.", + 'en-us': 'Disable non-essential animations and transitions.', + 'ru-ru': 'Отключите ненужные анимации и переходы.', + 'es-es': 'Desactivar animaciones y transiciones no esenciales.', + 'fr-fr': 'Désactivez les animations et les transitions non essentielles.', + 'uk-ua': "Вимкніть необов'язкову анімацію та переходи.", + 'de-ch': 'Nicht erforderliche Animationen und Übergänge deaktivieren.', + 'pt-br': 'Desabilite animações e transições não essenciais.', }, reduceTransparency: { - "en-us": "Reduce transparency", - "ru-ru": "Уменьшить прозрачность", - "es-es": "Reducir la transparencia", - "fr-fr": "Réduire la transparence", - "uk-ua": "Зменшити прозорість", - "de-ch": "Transparenz reduzieren", - "pt-br": "Reduzir a transparência", + 'en-us': 'Reduce transparency', + 'ru-ru': 'Уменьшить прозрачность', + 'es-es': 'Reducir la transparencia', + 'fr-fr': 'Réduire la transparence', + 'uk-ua': 'Зменшити прозорість', + 'de-ch': 'Transparenz reduzieren', + 'pt-br': 'Reduzir a transparência', }, reduceTransparencyDescription: { - "en-us": - "Whether to disable translucent backgrounds for user interface components whenever possible (e.g. table headers in tree view).", - "ru-ru": - "Следует ли отключать полупрозрачный фон для компонентов пользовательского интерфейса, когда это возможно (например, заголовки таблиц в древовидной структуре).", - "es-es": - "Si se deben deshabilitar los fondos translúcidos para los componentes de la interfaz de usuario siempre que sea posible (por ejemplo, encabezados de tabla en la vista de árbol).", - "fr-fr": + 'en-us': + 'Whether to disable translucent backgrounds for user interface components whenever possible (e.g. table headers in tree view).', + 'ru-ru': + 'Следует ли отключать полупрозрачный фон для компонентов пользовательского интерфейса, когда это возможно (например, заголовки таблиц в древовидной структуре).', + 'es-es': + 'Si se deben deshabilitar los fondos translúcidos para los componentes de la interfaz de usuario siempre que sea posible (por ejemplo, encabezados de tabla en la vista de árbol).', + 'fr-fr': "S'il faut désactiver les arrière-plans translucides pour les composants de l'interface utilisateur chaque fois que possible (par exemple, les en-têtes de tableau dans l'arborescence).", - "uk-ua": - "Чи вимикати напівпрозорий фон для компонентів інтерфейсу користувача, коли це можливо (наприклад, заголовки таблиць у перегляді дерева).", - "de-ch": - "Durchsichtige Hintergründe für Benutzeroberflächenkomponenten wann immer möglich deaktivieren (z. B. Tabellenüberschriften in der Baumansicht).", - "pt-br": - "Se deve ou não desabilitar fundos translúcidos para componentes da interface do usuário sempre que possível (por exemplo, cabeçalhos de tabela na visualização em árvore).", + 'uk-ua': + 'Чи вимикати напівпрозорий фон для компонентів інтерфейсу користувача, коли це можливо (наприклад, заголовки таблиць у перегляді дерева).', + 'de-ch': + 'Durchsichtige Hintergründe für Benutzeroberflächenkomponenten wann immer möglich deaktivieren (z. B. Tabellenüberschriften in der Baumansicht).', + 'pt-br': + 'Se deve ou não desabilitar fundos translúcidos para componentes da interface do usuário sempre que possível (por exemplo, cabeçalhos de tabela na visualização em árvore).', }, contrast: { - "en-us": "Contrast", - "ru-ru": "Контраст", - "es-es": "Contraste", - "fr-fr": "Contraste", - "uk-ua": "Контраст", - "de-ch": "Kontrast", - "pt-br": "Contraste", + 'en-us': 'Contrast', + 'ru-ru': 'Контраст', + 'es-es': 'Contraste', + 'fr-fr': 'Contraste', + 'uk-ua': 'Контраст', + 'de-ch': 'Kontrast', + 'pt-br': 'Contraste', }, increase: { - "en-us": "Increase", - "ru-ru": "Увеличивать", - "es-es": "Aumentar", - "fr-fr": "Augmenter", - "uk-ua": "Збільшити", - "de-ch": "Erhöhen", - "pt-br": "Aumentar", + 'en-us': 'Increase', + 'ru-ru': 'Увеличивать', + 'es-es': 'Aumentar', + 'fr-fr': 'Augmenter', + 'uk-ua': 'Збільшити', + 'de-ch': 'Erhöhen', + 'pt-br': 'Aumentar', }, reduce: { - "en-us": "Reduce", - "ru-ru": "Уменьшать", - "es-es": "Reducir", - "fr-fr": "Réduire", - "uk-ua": "Зменшити", - "de-ch": "Verringern", - "pt-br": "Reduzir", + 'en-us': 'Reduce', + 'ru-ru': 'Уменьшать', + 'es-es': 'Reducir', + 'fr-fr': 'Réduire', + 'uk-ua': 'Зменшити', + 'de-ch': 'Verringern', + 'pt-br': 'Reduzir', }, noPreference: { - "en-us": "No preference", - "ru-ru": "Нет предпочтений", - "es-es": "Sin preferencia", - "fr-fr": "Pas de préférence", - "uk-ua": "Без переваг", - "de-ch": "Keine Präferenz", - "pt-br": "Sem preferência", + 'en-us': 'No preference', + 'ru-ru': 'Нет предпочтений', + 'es-es': 'Sin preferencia', + 'fr-fr': 'Pas de préférence', + 'uk-ua': 'Без переваг', + 'de-ch': 'Keine Präferenz', + 'pt-br': 'Sem preferência', }, fontSize: { - "en-us": "Font size", - "ru-ru": "Размер шрифта", - "es-es": "Tamaño de fuente", - "fr-fr": "Taille de police", - "uk-ua": "Розмір шрифту", - "de-ch": "Schriftgrösse", - "pt-br": "Tamanho da fonte", + 'en-us': 'Font size', + 'ru-ru': 'Размер шрифта', + 'es-es': 'Tamaño de fuente', + 'fr-fr': 'Taille de police', + 'uk-ua': 'Розмір шрифту', + 'de-ch': 'Schriftgrösse', + 'pt-br': 'Tamanho da fonte', }, fontFamily: { - "en-us": "Font family", - "ru-ru": "Семейство шрифтов", - "es-es": "Familia de fuentes", - "fr-fr": "Famille de polices", - "uk-ua": "Сімейство шрифтів", - "de-ch": "Schrift-Familie", - "pt-br": "Família de fontes", + 'en-us': 'Font family', + 'ru-ru': 'Семейство шрифтов', + 'es-es': 'Familia de fuentes', + 'fr-fr': 'Famille de polices', + 'uk-ua': 'Сімейство шрифтів', + 'de-ch': 'Schrift-Familie', + 'pt-br': 'Família de fontes', }, fontFamilyDescription: { - "en-us": - "You can specify any font that is on your computer, even if it is not in the list. A comma-separated list of fonts is also supported, where each subsequent font will be used if the previous one is not available.", - "ru-ru": - "Вы можете указать любой шрифт, который есть на вашем компьютере, даже если его нет в списке. Также поддерживается список шрифтов, разделенных запятыми, где каждый последующий шрифт будет использоваться, если предыдущий недоступен.", - "es-es": - "Puede especificar cualquier fuente de su ordenador, incluso si no está en la lista. También se admite una lista de fuentes separadas por comas, donde se usará cada fuente subsiguiente si la anterior no está disponible.", - "fr-fr": + 'en-us': + 'You can specify any font that is on your computer, even if it is not in the list. A comma-separated list of fonts is also supported, where each subsequent font will be used if the previous one is not available.', + 'ru-ru': + 'Вы можете указать любой шрифт, который есть на вашем компьютере, даже если его нет в списке. Также поддерживается список шрифтов, разделенных запятыми, где каждый последующий шрифт будет использоваться, если предыдущий недоступен.', + 'es-es': + 'Puede especificar cualquier fuente de su ordenador, incluso si no está en la lista. También se admite una lista de fuentes separadas por comas, donde se usará cada fuente subsiguiente si la anterior no está disponible.', + 'fr-fr': "Vous pouvez spécifier n'importe quelle police présente sur votre ordinateur, même si elle ne figure pas dans la liste. Une liste de polices séparées par des virgules est également prise en charge ; chaque police suivante sera utilisée si la précédente n'est pas disponible.", - "uk-ua": + 'uk-ua': "Ви можете вказати будь-який шрифт, який є на вашому комп'ютері, навіть якщо його немає в списку. Також підтримується розділений комами список шрифтів, у якому використовуватиметься другий шрифт, якщо перший недоступний тощо.", - "de-ch": - "Sie können jede Schriftart angeben, die sich auf Ihrem Computer befindet, auch wenn diese nicht in der Liste enthalten ist. Eine durch Kommas getrennte Liste von Schriftarten wird ebenfalls unterstützt, wobei die zweite Schriftart verwendet wird, wenn die erste nicht verfügbar ist usw.", - "pt-br": - "Você pode especificar qualquer fonte que esteja no seu computador, mesmo que ela não esteja na lista. Uma lista de fontes separadas por vírgulas também é suportada, onde cada fonte subsequente será usada se a anterior não estiver disponível.", + 'de-ch': + 'Sie können jede Schriftart angeben, die sich auf Ihrem Computer befindet, auch wenn diese nicht in der Liste enthalten ist. Eine durch Kommas getrennte Liste von Schriftarten wird ebenfalls unterstützt, wobei die zweite Schriftart verwendet wird, wenn die erste nicht verfügbar ist usw.', + 'pt-br': + 'Você pode especificar qualquer fonte que esteja no seu computador, mesmo que ela não esteja na lista. Uma lista de fontes separadas por vírgulas também é suportada, onde cada fonte subsequente será usada se a anterior não estiver disponível.', }, defaultFont: { - "en-us": "(default font)", - "ru-ru": "(шрифт по умолчанию)", - "es-es": "(fuente predeterminada)", - "fr-fr": "(police par défaut)", - "uk-ua": "(типовий шрифт)", - "de-ch": "(Standardschriftart)", - "pt-br": "(fonte padrão)", + 'en-us': '(default font)', + 'ru-ru': '(шрифт по умолчанию)', + 'es-es': '(fuente predeterminada)', + 'fr-fr': '(police par défaut)', + 'uk-ua': '(типовий шрифт)', + 'de-ch': '(Standardschriftart)', + 'pt-br': '(fonte padrão)', }, maxFormWidth: { - "en-us": "Max form width", - "ru-ru": "Макс. ширина формы", - "es-es": "Ancho máximo del formulario", - "fr-fr": "Largeur maximale du formulaire", - "uk-ua": "Максимальна ширина форми", - "de-ch": "Maximale Formularbreite", - "pt-br": "Largura máxima do formulário", + 'en-us': 'Max form width', + 'ru-ru': 'Макс. ширина формы', + 'es-es': 'Ancho máximo del formulario', + 'fr-fr': 'Largeur maximale du formulaire', + 'uk-ua': 'Максимальна ширина форми', + 'de-ch': 'Maximale Formularbreite', + 'pt-br': 'Largura máxima do formulário', }, fieldBackgrounds: { - "en-us": "Field backgrounds", - "ru-ru": "Фоны полей", - "es-es": "Fondos de campo", - "fr-fr": "Milieux de terrain", - "uk-ua": "Польові фони", - "de-ch": "Feldhintergründe", - "pt-br": "Fundos de campo", + 'en-us': 'Field backgrounds', + 'ru-ru': 'Фоны полей', + 'es-es': 'Fondos de campo', + 'fr-fr': 'Milieux de terrain', + 'uk-ua': 'Польові фони', + 'de-ch': 'Feldhintergründe', + 'pt-br': 'Fundos de campo', }, fieldBackground: { - "en-us": "Field background", - "ru-ru": "Фон поля", - "es-es": "Fondo de campo", - "fr-fr": "Contexte du terrain", - "uk-ua": "Поле фону", - "de-ch": "Feldhintergrund", - "pt-br": "Contexto de campo", + 'en-us': 'Field background', + 'ru-ru': 'Фон поля', + 'es-es': 'Fondo de campo', + 'fr-fr': 'Contexte du terrain', + 'uk-ua': 'Поле фону', + 'de-ch': 'Feldhintergrund', + 'pt-br': 'Contexto de campo', }, disabledFieldBackground: { - "en-us": "Disabled field background", - "ru-ru": "Отключен фон поля", - "es-es": "Fondo de campo deshabilitado", - "fr-fr": "Fond de champ désactivé", - "uk-ua": "Вимкнений фон поля", - "de-ch": "Deaktivierter Feldhintergrund", - "pt-br": "Fundo de campo desativado", + 'en-us': 'Disabled field background', + 'ru-ru': 'Отключен фон поля', + 'es-es': 'Fondo de campo deshabilitado', + 'fr-fr': 'Fond de champ désactivé', + 'uk-ua': 'Вимкнений фон поля', + 'de-ch': 'Deaktivierter Feldhintergrund', + 'pt-br': 'Fundo de campo desativado', }, invalidFieldBackground: { - "en-us": "Invalid field background", - "ru-ru": "Неверный фон поля", - "es-es": "Fondo de campo no válido", - "fr-fr": "Fond de champ invalide", - "uk-ua": "Недійсний фон поля", - "de-ch": "Ungültiger Feldhintergrund", - "pt-br": "Fundo de campo inválido", + 'en-us': 'Invalid field background', + 'ru-ru': 'Неверный фон поля', + 'es-es': 'Fondo de campo no válido', + 'fr-fr': 'Fond de champ invalide', + 'uk-ua': 'Недійсний фон поля', + 'de-ch': 'Ungültiger Feldhintergrund', + 'pt-br': 'Fundo de campo inválido', }, requiredFieldBackground: { - "en-us": "Required field background", - "ru-ru": "Обязательное поле фон", - "es-es": "Antecedentes del campo obligatorio", - "fr-fr": "Contexte du champ obligatoire", - "uk-ua": "Обов'язковий фон поля", - "de-ch": "Feldhintergrund erforderlich", - "pt-br": "Histórico de campo obrigatório", + 'en-us': 'Required field background', + 'ru-ru': 'Обязательное поле фон', + 'es-es': 'Antecedentes del campo obligatorio', + 'fr-fr': 'Contexte du champ obligatoire', + 'uk-ua': "Обов'язковий фон поля", + 'de-ch': 'Feldhintergrund erforderlich', + 'pt-br': 'Histórico de campo obrigatório', }, darkFieldBackground: { - "en-us": "Field background (dark theme)", - "ru-ru": "Фон поля (тёмная тема)", - "es-es": "Fondo de campo (tema oscuro)", - "fr-fr": "Fond de champ (thème sombre)", - "uk-ua": "Фон поля (темна тема)", - "de-ch": "Feldhintergrund (Dunkles Thema)", - "pt-br": "Fundo de campo (tema escuro)", + 'en-us': 'Field background (dark theme)', + 'ru-ru': 'Фон поля (тёмная тема)', + 'es-es': 'Fondo de campo (tema oscuro)', + 'fr-fr': 'Fond de champ (thème sombre)', + 'uk-ua': 'Фон поля (темна тема)', + 'de-ch': 'Feldhintergrund (Dunkles Thema)', + 'pt-br': 'Fundo de campo (tema escuro)', }, darkDisabledFieldBackground: { - "en-us": "Disabled field background (dark theme)", - "ru-ru": "Отключен фон поля (тёмная тема)", - "es-es": "Fondo de campo deshabilitado (tema oscuro)", - "fr-fr": "Fond de champ désactivé (thème sombre)", - "uk-ua": "Вимкнений фон поля (темна тема)", - "de-ch": "Deaktivierter Feldhintergrund (Dunkles Thema)", - "pt-br": "Fundo de campo desativado (tema escuro)", + 'en-us': 'Disabled field background (dark theme)', + 'ru-ru': 'Отключен фон поля (тёмная тема)', + 'es-es': 'Fondo de campo deshabilitado (tema oscuro)', + 'fr-fr': 'Fond de champ désactivé (thème sombre)', + 'uk-ua': 'Вимкнений фон поля (темна тема)', + 'de-ch': 'Deaktivierter Feldhintergrund (Dunkles Thema)', + 'pt-br': 'Fundo de campo desativado (tema escuro)', }, darkInvalidFieldBackground: { - "en-us": "Invalid field background (dark theme)", - "ru-ru": "Недопустимый фон поля (тёмная тема)", - "es-es": "Fondo de campo no válido (tema oscuro)", - "fr-fr": "Largeur de colonne de grille de sous-vue flexible", - "uk-ua": "Гнучка ширина стовпця сітки вкладеного перегляду", - "de-ch": "Ungültiger Feldhintergrund (Dunkles Thema)", - "pt-br": "Fundo de campo inválido (tema escuro)", + 'en-us': 'Invalid field background (dark theme)', + 'ru-ru': 'Недопустимый фон поля (тёмная тема)', + 'es-es': 'Fondo de campo no válido (tema oscuro)', + 'fr-fr': 'Largeur de colonne de grille de sous-vue flexible', + 'uk-ua': 'Гнучка ширина стовпця сітки вкладеного перегляду', + 'de-ch': 'Ungültiger Feldhintergrund (Dunkles Thema)', + 'pt-br': 'Fundo de campo inválido (tema escuro)', }, darkRequiredFieldBackground: { - "en-us": "Required field background (dark theme)", - "ru-ru": "Обязательное поле фон (тёмная тема)", - "es-es": "Fondo del campo obligatorio (tema oscuro)", - "fr-fr": "Fond de champ obligatoire (thème sombre)", - "uk-ua": "Обов’язковий фон поля (темна тема)", - "de-ch": "Feldhintergrund erforderlich (Dunkles Thema)", - "pt-br": "Fundo de campo obrigatório (tema escuro)", + 'en-us': 'Required field background (dark theme)', + 'ru-ru': 'Обязательное поле фон (тёмная тема)', + 'es-es': 'Fondo del campo obligatorio (tema oscuro)', + 'fr-fr': 'Fond de champ obligatoire (thème sombre)', + 'uk-ua': 'Обов’язковий фон поля (темна тема)', + 'de-ch': 'Feldhintergrund erforderlich (Dunkles Thema)', + 'pt-br': 'Fundo de campo obrigatório (tema escuro)', }, dialogs: { - "en-us": "Dialogs", - "ru-ru": "Диалоги", - "es-es": "Diálogos", - "fr-fr": "Boîtes de dialogue", - "uk-ua": "Діалоги", - "de-ch": "Dialoge", - "pt-br": "Diálogos", + 'en-us': 'Dialogs', + 'ru-ru': 'Диалоги', + 'es-es': 'Diálogos', + 'fr-fr': 'Boîtes de dialogue', + 'uk-ua': 'Діалоги', + 'de-ch': 'Dialoge', + 'pt-br': 'Diálogos', }, appearance: { - "en-us": "Appearance", - "ru-ru": "Появление", - "es-es": "Apariencia", - "fr-fr": "Apparence", - "uk-ua": "Зовнішній вигляд", - "de-ch": "Aussehen", - "pt-br": "Aparência", + 'en-us': 'Appearance', + 'ru-ru': 'Появление', + 'es-es': 'Apariencia', + 'fr-fr': 'Apparence', + 'uk-ua': 'Зовнішній вигляд', + 'de-ch': 'Aussehen', + 'pt-br': 'Aparência', }, buttonsLight: { - "en-us": "Buttons (light mode)", - "de-ch": "Buttons (Helles Thema)", - "es-es": "Botones (modo luz)", - "fr-fr": "Boutons (mode lumière)", - "ru-ru": "Кнопки (световой режим)", - "uk-ua": "Кнопки (світлий режим)", - "pt-br": "Botões (modo claro)", + 'en-us': 'Buttons (light mode)', + 'de-ch': 'Buttons (Helles Thema)', + 'es-es': 'Botones (modo luz)', + 'fr-fr': 'Boutons (mode lumière)', + 'ru-ru': 'Кнопки (световой режим)', + 'uk-ua': 'Кнопки (світлий режим)', + 'pt-br': 'Botões (modo claro)', }, buttonsDark: { - "en-us": "Buttons (dark mode)", - "de-ch": "Buttons (Dunkles Thema)", - "es-es": "Botones (modo oscuro)", - "fr-fr": "Boutons (mode sombre)", - "ru-ru": "Кнопки (темный режим)", - "uk-ua": "Кнопки (темний режим)", - "pt-br": "Botões (modo escuro)", + 'en-us': 'Buttons (dark mode)', + 'de-ch': 'Buttons (Dunkles Thema)', + 'es-es': 'Botones (modo oscuro)', + 'fr-fr': 'Boutons (mode sombre)', + 'ru-ru': 'Кнопки (темный режим)', + 'uk-ua': 'Кнопки (темний режим)', + 'pt-br': 'Botões (modo escuro)', }, translucentDialog: { - "en-us": "Translucent dialogs", - "ru-ru": "Прозрачные диалоги", - "es-es": "Diálogos translúcidos", - "fr-fr": "Dialogues translucides", - "uk-ua": "Напівпрозорі діалоги", - "de-ch": "Durchscheinende Dialoge", - "pt-br": "Diálogos translúcidos", + 'en-us': 'Translucent dialogs', + 'ru-ru': 'Прозрачные диалоги', + 'es-es': 'Diálogos translúcidos', + 'fr-fr': 'Dialogues translucides', + 'uk-ua': 'Напівпрозорі діалоги', + 'de-ch': 'Durchscheinende Dialoge', + 'pt-br': 'Diálogos translúcidos', }, translucentDialogDescription: { - "en-us": "Whether dialogs have translucent background.", - "ru-ru": "Имеют ли диалоговые окна полупрозрачный фон.", - "es-es": "Si los diálogos tienen fondo translúcido.", - "fr-fr": "Si les boîtes de dialogue ont un fond translucide.", - "uk-ua": "Чи мають діалоги прозорий фон.", - "de-ch": "Dialogfenster mit durchscheinenden Hintergrund.", - "pt-br": "Se os diálogos têm fundo translúcido.", + 'en-us': 'Whether dialogs have translucent background.', + 'ru-ru': 'Имеют ли диалоговые окна полупрозрачный фон.', + 'es-es': 'Si los diálogos tienen fondo translúcido.', + 'fr-fr': 'Si les boîtes de dialogue ont un fond translucide.', + 'uk-ua': 'Чи мають діалоги прозорий фон.', + 'de-ch': 'Dialogfenster mit durchscheinenden Hintergrund.', + 'pt-br': 'Se os diálogos têm fundo translúcido.', }, alwaysPrompt: { - "en-us": "Always prompt to choose collection", - "ru-ru": "Всегда предлагать выбрать коллекцию", - "es-es": "Siempre dispuesto a elegir la colección", - "fr-fr": "Toujours invité à choisir la collection", - "uk-ua": "Завжди підкажуть вибрати колекцію", - "de-ch": "Immer zur Auswahl der Sammlung auffordern", - "pt-br": "Sempre pronto para escolher a coleção", + 'en-us': 'Always prompt to choose collection', + 'ru-ru': 'Всегда предлагать выбрать коллекцию', + 'es-es': 'Siempre dispuesto a elegir la colección', + 'fr-fr': 'Toujours invité à choisir la collection', + 'uk-ua': 'Завжди підкажуть вибрати колекцію', + 'de-ch': 'Immer zur Auswahl der Sammlung auffordern', + 'pt-br': 'Sempre pronto para escolher a coleção', }, treeEditor: { - "en-us": "Tree Editor", - "ru-ru": "Редактор дерева", - "es-es": "Editor de árboles", - "fr-fr": "Éditeur d'arborescence", - "uk-ua": "Редактор дерева", - "de-ch": "Baumeditor", - "pt-br": "Editor de Árvore", + 'en-us': 'Tree Editor', + 'ru-ru': 'Редактор дерева', + 'es-es': 'Editor de árboles', + 'fr-fr': "Éditeur d'arborescence", + 'uk-ua': 'Редактор дерева', + 'de-ch': 'Baumeditor', + 'pt-br': 'Editor de Árvore', }, treeAccentColor: { - "en-us": "Tree accent color", - "ru-ru": "Цвет акцента дерева", - "es-es": "Color de acento del árbol", - "fr-fr": "Couleur d'accent d'arbre", - "uk-ua": "Колір акценту дерева", - "de-ch": "Baumakzentfarbe", - "pt-br": "Cor de destaque da árvore", + 'en-us': 'Tree accent color', + 'ru-ru': 'Цвет акцента дерева', + 'es-es': 'Color de acento del árbol', + 'fr-fr': "Couleur d'accent d'arbre", + 'uk-ua': 'Колір акценту дерева', + 'de-ch': 'Baumakzentfarbe', + 'pt-br': 'Cor de destaque da árvore', }, synonymColor: { - "en-us": "Synonym color", - "ru-ru": "Синоним цвет", - "es-es": "Color sinónimo", - "fr-fr": "Synonyme couleur", - "uk-ua": "Синонім кольору", - "de-ch": "Synonymfarbe", - "pt-br": "Cor sinônimo", + 'en-us': 'Synonym color', + 'ru-ru': 'Синоним цвет', + 'es-es': 'Color sinónimo', + 'fr-fr': 'Synonyme couleur', + 'uk-ua': 'Синонім кольору', + 'de-ch': 'Synonymfarbe', + 'pt-br': 'Cor sinônimo', }, showNewDataSetWarning: { - "en-us": "Show new Data Set warning", - "ru-ru": "Показать предупреждение о новом наборе данных", - "es-es": "Mostrar nueva advertencia de conjunto de datos", - "fr-fr": "Afficher un nouvel avertissement sur l'ensemble de données", - "uk-ua": "Показати попередження про новий набір даних", - "de-ch": "Warnung für neuen Datensatz anzeigen", - "pt-br": "Mostrar novo aviso de conjunto de dados", + 'en-us': 'Show new Data Set warning', + 'ru-ru': 'Показать предупреждение о новом наборе данных', + 'es-es': 'Mostrar nueva advertencia de conjunto de datos', + 'fr-fr': "Afficher un nouvel avertissement sur l'ensemble de données", + 'uk-ua': 'Показати попередження про новий набір даних', + 'de-ch': 'Warnung für neuen Datensatz anzeigen', + 'pt-br': 'Mostrar novo aviso de conjunto de dados', }, showNewDataSetWarningDescription: { - "en-us": "Show an informational message when creating a new Data Set.", - "ru-ru": - "Показывать информационное сообщение при создании нового набора данных.", - "es-es": - "Mostrar un mensaje informativo al crear un nuevo conjunto de datos.", - "fr-fr": + 'en-us': 'Show an informational message when creating a new Data Set.', + 'ru-ru': + 'Показывать информационное сообщение при создании нового набора данных.', + 'es-es': + 'Mostrar un mensaje informativo al crear un nuevo conjunto de datos.', + 'fr-fr': "Afficher un message d'information lors de la création d'un nouvel ensemble de données.", - "uk-ua": - "Показувати інформаційне повідомлення під час створення нового набору даних.", - "de-ch": "Zeige eine Meldung beim erstellen eines neuen Datensatzes an.", - "pt-br": - "Exibir uma mensagem informativa ao criar um novo conjunto de dados.", + 'uk-ua': + 'Показувати інформаційне повідомлення під час створення нового набору даних.', + 'de-ch': 'Zeige eine Meldung beim erstellen eines neuen Datensatzes an.', + 'pt-br': + 'Exibir uma mensagem informativa ao criar um novo conjunto de dados.', }, header: { - "en-us": "Navigation Menu", - "ru-ru": "Меню навигации", - "es-es": "Menú de navegación", - "fr-fr": "le menu de navigation", - "uk-ua": "Навігаційне меню", - "de-ch": "Navigationsmenü", - "pt-br": "Menu de navegação", + 'en-us': 'Navigation Menu', + 'ru-ru': 'Меню навигации', + 'es-es': 'Menú de navegación', + 'fr-fr': 'le menu de navigation', + 'uk-ua': 'Навігаційне меню', + 'de-ch': 'Navigationsmenü', + 'pt-br': 'Menu de navegação', }, application: { - "en-us": "Application", - "ru-ru": "Приложение", - "es-es": "Solicitud", - "fr-fr": "Application", - "uk-ua": "застосування", - "de-ch": "Anwendung", - "pt-br": "Aplicativo", + 'en-us': 'Application', + 'ru-ru': 'Приложение', + 'es-es': 'Solicitud', + 'fr-fr': 'Application', + 'uk-ua': 'застосування', + 'de-ch': 'Anwendung', + 'pt-br': 'Aplicativo', }, allowDismissingErrors: { - "en-us": "Allow dismissing error messages", - "ru-ru": "Разрешить отклонение сообщений об ошибках", - "es-es": "Permitir descartar mensajes de error", - "fr-fr": "Autoriser le rejet des messages d'erreur", - "uk-ua": "Дозволити закривати повідомлення про помилки", - "de-ch": "Erlaube das Verwerfen von Fehlermeldungen", - "pt-br": "Permitir descartar mensagens de erro", + 'en-us': 'Allow dismissing error messages', + 'ru-ru': 'Разрешить отклонение сообщений об ошибках', + 'es-es': 'Permitir descartar mensajes de error', + 'fr-fr': "Autoriser le rejet des messages d'erreur", + 'uk-ua': 'Дозволити закривати повідомлення про помилки', + 'de-ch': 'Erlaube das Verwerfen von Fehlermeldungen', + 'pt-br': 'Permitir descartar mensagens de erro', }, updatePageTitle: { - "en-us": "Update page title", - "ru-ru": "Обновить заголовок страницы", - "es-es": "Actualizar el título de la página", - "fr-fr": "Mettre à jour le titre de la page", - "uk-ua": "Оновити назву сторінки", - "de-ch": "Seitentitel aktualisieren", - "pt-br": "Atualizar título da página", + 'en-us': 'Update page title', + 'ru-ru': 'Обновить заголовок страницы', + 'es-es': 'Actualizar el título de la página', + 'fr-fr': 'Mettre à jour le titre de la page', + 'uk-ua': 'Оновити назву сторінки', + 'de-ch': 'Seitentitel aktualisieren', + 'pt-br': 'Atualizar título da página', }, updatePageTitleDescription: { - "en-us": + 'en-us': "Whether to update the title of the page to match dialog's header.", - "ru-ru": - "Обновлять ли заголовок страницы, чтобы он соответствовал заголовку диалогового окна.", - "es-es": - "Si se debe actualizar el título de la página para que coincida con el encabezado del cuadro de diálogo.", - "fr-fr": + 'ru-ru': + 'Обновлять ли заголовок страницы, чтобы он соответствовал заголовку диалогового окна.', + 'es-es': + 'Si se debe actualizar el título de la página para que coincida con el encabezado del cuadro de diálogo.', + 'fr-fr': "S'il faut mettre à jour le titre de la page pour qu'il corresponde à l'en-tête de la boîte de dialogue.", - "uk-ua": - "Чи оновлювати назву сторінки відповідно до заголовка діалогового вікна.", - "de-ch": - "Titel der Seite so aktualisieren, dass er mit der Kopfzeile des Dialogs übereinstimmt.", - "pt-br": - "Se o título da página deve ser atualizado para corresponder ao cabeçalho da caixa de diálogo.", + 'uk-ua': + 'Чи оновлювати назву сторінки відповідно до заголовка діалогового вікна.', + 'de-ch': + 'Titel der Seite so aktualisieren, dass er mit der Kopfzeile des Dialogs übereinstimmt.', + 'pt-br': + 'Se o título da página deve ser atualizado para corresponder ao cabeçalho da caixa de diálogo.', }, updatePageTitleFormDescription: { - "en-us": "Whether to update the title of the page to match current record.", - "ru-ru": "Обновлять ли заголовок страницы для соответствия текущей записи.", - "es-es": - "Si desea actualizar el título de la página para que coincida con el registro actual.", - "fr-fr": + 'en-us': 'Whether to update the title of the page to match current record.', + 'ru-ru': 'Обновлять ли заголовок страницы для соответствия текущей записи.', + 'es-es': + 'Si desea actualizar el título de la página para que coincida con el registro actual.', + 'fr-fr': "S'il faut mettre à jour le titre de la page pour qu'il corresponde à l'enregistrement actuel.", - "uk-ua": "Чи оновлювати назву сторінки відповідно до поточного запису.", - "de-ch": - "Titel der Seite aktualisieren, damit er mit dem aktuellen Datensatz übereinstimmt.", - "pt-br": - "Se o título da página deve ser atualizado para corresponder ao registro atual.", + 'uk-ua': 'Чи оновлювати назву сторінки відповідно до поточного запису.', + 'de-ch': + 'Titel der Seite aktualisieren, damit er mit dem aktuellen Datensatz übereinstimmt.', + 'pt-br': + 'Se o título da página deve ser atualizado para corresponder ao registro atual.', }, queryComboBox: { - "en-us": "Query Combo Box", - "ru-ru": "Поле со списком запросов", - "es-es": "Cuadro combinado de consulta", - "uk-ua": "Поле зі списком запитів", - "de-ch": "Abfrage-Kombinationsfeld", - "fr-fr": "Zone de liste déroulante de requête", - "pt-br": "Caixa de combinação de consulta", + 'en-us': 'Query Combo Box', + 'ru-ru': 'Поле со списком запросов', + 'es-es': 'Cuadro combinado de consulta', + 'uk-ua': 'Поле зі списком запитів', + 'de-ch': 'Abfrage-Kombinationsfeld', + 'fr-fr': 'Zone de liste déroulante de requête', + 'pt-br': 'Caixa de combinação de consulta', }, searchAlgorithm: { - "en-us": "Search Algorithm", - "ru-ru": "Алгоритм поиска", - "es-es": "Algoritmo de búsqueda", - "fr-fr": "Algorithme de recherche", - "uk-ua": "Алгоритм пошуку", - "de-ch": "Suchalgorithmus", - "pt-br": "Algoritmo de Busca", + 'en-us': 'Search Algorithm', + 'ru-ru': 'Алгоритм поиска', + 'es-es': 'Algoritmo de búsqueda', + 'fr-fr': 'Algorithme de recherche', + 'uk-ua': 'Алгоритм пошуку', + 'de-ch': 'Suchalgorithmus', + 'pt-br': 'Algoritmo de Busca', }, treeSearchAlgorithm: { - "en-us": "Search Algorithm (for relationships with tree tables)", - "ru-ru": "Алгоритм поиска (для связей с древовидными таблицами)", - "es-es": "Algoritmo de búsqueda (para relaciones con tablas de árbol)", - "fr-fr": - "Algorithme de recherche (pour les relations avec les tables arborescentes)", - "uk-ua": "Алгоритм пошуку (для зв’язків із деревоподібними таблицями)", - "de-ch": "Suchalgorithmus (für Beziehungen mit Baumtabellen)", - "pt-br": "Algoritmo de busca (para relacionamentos com tabelas de árvore)", + 'en-us': 'Search Algorithm (for relationships with tree tables)', + 'ru-ru': 'Алгоритм поиска (для связей с древовидными таблицами)', + 'es-es': 'Algoritmo de búsqueda (para relaciones con tablas de árbol)', + 'fr-fr': + 'Algorithme de recherche (pour les relations avec les tables arborescentes)', + 'uk-ua': 'Алгоритм пошуку (для зв’язків із деревоподібними таблицями)', + 'de-ch': 'Suchalgorithmus (für Beziehungen mit Baumtabellen)', + 'pt-br': 'Algoritmo de busca (para relacionamentos com tabelas de árvore)', }, startsWithInsensitive: { - "en-us": "Starts With (case-insensitive)", - "ru-ru": "Начинается с (без учета регистра)", - "es-es": "Comienza con (sin distinguir entre mayúsculas y minúsculas)", - "fr-fr": "Commence par (insensible à la casse)", - "uk-ua": "Починається з (без урахування регістру)", - "de-ch": - "Beginnt mit (ohne Berücksichtigung der Groß- und Kleinschreibung)", - "pt-br": "Começa com (sem distinção de maiúsculas e minúsculas)", + 'en-us': 'Starts With (case-insensitive)', + 'ru-ru': 'Начинается с (без учета регистра)', + 'es-es': 'Comienza con (sin distinguir entre mayúsculas y minúsculas)', + 'fr-fr': 'Commence par (insensible à la casse)', + 'uk-ua': 'Починається з (без урахування регістру)', + 'de-ch': + 'Beginnt mit (ohne Berücksichtigung der Groß- und Kleinschreibung)', + 'pt-br': 'Começa com (sem distinção de maiúsculas e minúsculas)', }, startsWithDescription: { - "en-us": "Search for values that begin with a given query string.", - "ru-ru": "Поиск значений, начинающихся с заданной строки запроса.", - "es-es": - "Busque valores que comiencen con una cadena de consulta determinada.", - "fr-fr": - "Rechercher des valeurs commençant par une chaîne de requête donnée.", - "uk-ua": "Пошук значень, які починаються з заданого рядка запиту.", - "de-ch": - "Suchen Sie nach Werten, die mit einer bestimmten Abfragezeichenfolge beginnen.", - "pt-br": - "Pesquisar valores que começam com uma determinada sequência de consulta.", + 'en-us': 'Search for values that begin with a given query string.', + 'ru-ru': 'Поиск значений, начинающихся с заданной строки запроса.', + 'es-es': + 'Busque valores que comiencen con una cadena de consulta determinada.', + 'fr-fr': + 'Rechercher des valeurs commençant par une chaîne de requête donnée.', + 'uk-ua': 'Пошук значень, які починаються з заданого рядка запиту.', + 'de-ch': + 'Suchen Sie nach Werten, die mit einer bestimmten Abfragezeichenfolge beginnen.', + 'pt-br': + 'Pesquisar valores que começam com uma determinada sequência de consulta.', }, startsWithCaseSensitive: { - "en-us": "Starts With (case-sensitive)", - "ru-ru": "Начинается с (с учетом регистра)", - "es-es": "Comienza con (sensible a mayúsculas y minúsculas)", - "fr-fr": "Commence par (sensible à la casse)", - "uk-ua": "Починається з (з урахуванням регістру)", - "de-ch": "Beginnt mit (Groß-/Kleinschreibung beachten)", - "pt-br": "Começa com (diferencia maiúsculas de minúsculas)", + 'en-us': 'Starts With (case-sensitive)', + 'ru-ru': 'Начинается с (с учетом регистра)', + 'es-es': 'Comienza con (sensible a mayúsculas y minúsculas)', + 'fr-fr': 'Commence par (sensible à la casse)', + 'uk-ua': 'Починається з (з урахуванням регістру)', + 'de-ch': 'Beginnt mit (Groß-/Kleinschreibung beachten)', + 'pt-br': 'Começa com (diferencia maiúsculas de minúsculas)', }, startsWithCaseSensitiveDescription: { - "en-us": "Search for values that begin with a given query string.", - "ru-ru": "Поиск значений, начинающихся с заданной строки запроса.", - "es-es": - "Busque valores que comiencen con una cadena de consulta determinada.", - "fr-fr": - "Recherchez les valeurs qui commencent par une chaîne de requête donnée.", - "uk-ua": "Пошук значень, які починаються з заданого рядка запиту.", - "de-ch": - "Suchen Sie nach Werten, die mit einer bestimmten Abfragezeichenfolge beginnen.", - "pt-br": - "Pesquisar valores que começam com uma determinada sequência de consulta.", + 'en-us': 'Search for values that begin with a given query string.', + 'ru-ru': 'Поиск значений, начинающихся с заданной строки запроса.', + 'es-es': + 'Busque valores que comiencen con una cadena de consulta determinada.', + 'fr-fr': + 'Recherchez les valeurs qui commencent par une chaîne de requête donnée.', + 'uk-ua': 'Пошук значень, які починаються з заданого рядка запиту.', + 'de-ch': + 'Suchen Sie nach Werten, die mit einer bestimmten Abfragezeichenfolge beginnen.', + 'pt-br': + 'Pesquisar valores que começam com uma determinada sequência de consulta.', }, containsInsensitive: { - "en-us": "Contains (case-insensitive)", - "ru-ru": "Содержит (без учета регистра)", - "es-es": "Contiene (sin distinguir entre mayúsculas y minúsculas)", - "fr-fr": "Contient (insensible à la casse)", - "uk-ua": "Містить (незалежно від регістру)", - "de-ch": "Enthält (ohne Berücksichtigung der Groß- und Kleinschreibung)", - "pt-br": "Contém (sem distinção entre maiúsculas e minúsculas)", + 'en-us': 'Contains (case-insensitive)', + 'ru-ru': 'Содержит (без учета регистра)', + 'es-es': 'Contiene (sin distinguir entre mayúsculas y minúsculas)', + 'fr-fr': 'Contient (insensible à la casse)', + 'uk-ua': 'Містить (незалежно від регістру)', + 'de-ch': 'Enthält (ohne Berücksichtigung der Groß- und Kleinschreibung)', + 'pt-br': 'Contém (sem distinção entre maiúsculas e minúsculas)', }, containsCaseSensitive: { - "en-us": "Contains (case-sensitive)", - "ru-ru": "Содержит (с учетом регистра)", - "es-es": "Contiene (sensible a mayúsculas y minúsculas)", - "fr-fr": "Contient (sensible à la casse)", - "uk-ua": "Містить (з урахуванням регістру)", - "de-ch": "Enthält (Groß-/Kleinschreibung beachten)", - "pt-br": "Contém (diferencia maiúsculas de minúsculas)", + 'en-us': 'Contains (case-sensitive)', + 'ru-ru': 'Содержит (с учетом регистра)', + 'es-es': 'Contiene (sensible a mayúsculas y minúsculas)', + 'fr-fr': 'Contient (sensible à la casse)', + 'uk-ua': 'Містить (з урахуванням регістру)', + 'de-ch': 'Enthält (Groß-/Kleinschreibung beachten)', + 'pt-br': 'Contém (diferencia maiúsculas de minúsculas)', }, containsDescription: { - "en-us": - "Search for values that contain a given query string (case-insensitive).", - "ru-ru": - "Поиск значений, содержащих заданную строку запроса (без учета регистра).", - "es-es": - "Busque valores que contengan una cadena de consulta determinada (sin distinguir entre mayúsculas y minúsculas).", - "uk-ua": - "Пошук значень, які містять заданий рядок запиту (незалежно від регістру).", - "de-ch": - "Suche nach Werten, die eine bestimmte Abfragezeichenfolge enthalten (ohne Berücksichtigung der Groß-/Kleinschreibung).", - "fr-fr": - "Recherchez les valeurs contenant une chaîne de requête donnée (insensible à la casse).", - "pt-br": - "Pesquisar valores que contenham uma determinada sequência de consulta (sem distinção de maiúsculas e minúsculas).", + 'en-us': + 'Search for values that contain a given query string (case-insensitive).', + 'ru-ru': + 'Поиск значений, содержащих заданную строку запроса (без учета регистра).', + 'es-es': + 'Busque valores que contengan una cadena de consulta determinada (sin distinguir entre mayúsculas y minúsculas).', + 'uk-ua': + 'Пошук значень, які містять заданий рядок запиту (незалежно від регістру).', + 'de-ch': + 'Suche nach Werten, die eine bestimmte Abfragezeichenfolge enthalten (ohne Berücksichtigung der Groß-/Kleinschreibung).', + 'fr-fr': + 'Recherchez les valeurs contenant une chaîne de requête donnée (insensible à la casse).', + 'pt-br': + 'Pesquisar valores que contenham uma determinada sequência de consulta (sem distinção de maiúsculas e minúsculas).', }, containsCaseSensitiveDescription: { - "en-us": - "Search for values that contain a given query string (case-sensitive).", - "ru-ru": - "Поиск значений, содержащих заданную строку запроса (с учетом регистра).", - "es-es": - "Busque valores que contengan una cadena de consulta determinada (distingue entre mayúsculas y minúsculas).", - "fr-fr": - "Recherchez les valeurs contenant une chaîne de requête donnée (sensible à la casse).", - "uk-ua": - "Пошук значень, які містять заданий рядок запиту (з урахуванням регістру).", - "de-ch": - "Suchen Sie nach Werten, die eine bestimmte Abfragezeichenfolge enthalten (Groß-/Kleinschreibung beachten).", - "pt-br": - "Pesquisar valores que contenham uma determinada sequência de consulta (diferencia maiúsculas de minúsculas).", + 'en-us': + 'Search for values that contain a given query string (case-sensitive).', + 'ru-ru': + 'Поиск значений, содержащих заданную строку запроса (с учетом регистра).', + 'es-es': + 'Busque valores que contengan una cadena de consulta determinada (distingue entre mayúsculas y minúsculas).', + 'fr-fr': + 'Recherchez les valeurs contenant une chaîne de requête donnée (sensible à la casse).', + 'uk-ua': + 'Пошук значень, які містять заданий рядок запиту (з урахуванням регістру).', + 'de-ch': + 'Suchen Sie nach Werten, die eine bestimmte Abfragezeichenfolge enthalten (Groß-/Kleinschreibung beachten).', + 'pt-br': + 'Pesquisar valores que contenham uma determinada sequência de consulta (diferencia maiúsculas de minúsculas).', }, containsSecondDescription: { - "en-us": - "Can use _ to match any single character or % to match any number of characters.", - "ru-ru": - "Можно использовать _ для сопоставления с любым отдельным символом или % для сопоставления с любым количеством символов.", - "es-es": - "Puede utilizar _ para que coincida con cualquier carácter individual o % para que coincida con cualquier número de caracteres.", - "fr-fr": + 'en-us': + 'Can use _ to match any single character or % to match any number of characters.', + 'ru-ru': + 'Можно использовать _ для сопоставления с любым отдельным символом или % для сопоставления с любым количеством символов.', + 'es-es': + 'Puede utilizar _ para que coincida con cualquier carácter individual o % para que coincida con cualquier número de caracteres.', + 'fr-fr': "Peut utiliser _ pour correspondre à n'importe quel caractère ou % pour correspondre à n'importe quel nombre de caractères.", - "uk-ua": - "Можна використовувати _ для відповідності будь-якому одному символу або % для відповідності будь-якій кількості символів.", - "de-ch": - "Sie können _ verwenden, um ein beliebiges einzelnes Zeichen abzugleichen, oder %, um eine beliebige Anzahl von Zeichen abzugleichen.", - "pt-br": - "Pode usar _ para corresponder a qualquer caractere único ou % para corresponder a qualquer número de caracteres.", + 'uk-ua': + 'Можна використовувати _ для відповідності будь-якому одному символу або % для відповідності будь-якій кількості символів.', + 'de-ch': + 'Sie können _ verwenden, um ein beliebiges einzelnes Zeichen abzugleichen, oder %, um eine beliebige Anzahl von Zeichen abzugleichen.', + 'pt-br': + 'Pode usar _ para corresponder a qualquer caractere único ou % para corresponder a qualquer número de caracteres.', }, highlightMatch: { - "en-us": "Highlight matched substring", - "ru-ru": "Выделить совпавшую подстроку", - "es-es": "Resaltar la subcadena coincidente", - "fr-fr": "Mettre en surbrillance la sous-chaîne correspondante", - "uk-ua": "Виділіть збіг підрядка", - "de-ch": "Markieren Sie übereinstimmende Teilzeichenfolgen", - "pt-br": "Destacar substring correspondente", + 'en-us': 'Highlight matched substring', + 'ru-ru': 'Выделить совпавшую подстроку', + 'es-es': 'Resaltar la subcadena coincidente', + 'fr-fr': 'Mettre en surbrillance la sous-chaîne correspondante', + 'uk-ua': 'Виділіть збіг підрядка', + 'de-ch': 'Markieren Sie übereinstimmende Teilzeichenfolgen', + 'pt-br': 'Destacar substring correspondente', }, languageDescription: { - "en-us": "Determines field captions, usage notes and table captions.", - "ru-ru": - "Определяет заголовки полей, примечания по использованию и заголовки таблиц.", - "es-es": "Determina títulos de campos, notas de uso y títulos de tablas.", - "fr-fr": + 'en-us': 'Determines field captions, usage notes and table captions.', + 'ru-ru': + 'Определяет заголовки полей, примечания по использованию и заголовки таблиц.', + 'es-es': 'Determina títulos de campos, notas de uso y títulos de tablas.', + 'fr-fr': "Détermine les légendes des champs, les notes d'utilisation et les légendes des tableaux.", - "uk-ua": - "Визначає підписи полів, примітки щодо використання та підписи таблиць.", - "de-ch": - "Legt Feldbeschriftungen, Verwendungshinweise und Tabellenbeschriftungen fest.", - "pt-br": "Determina legendas de campo, notas de uso e legendas de tabela.", + 'uk-ua': + 'Визначає підписи полів, примітки щодо використання та підписи таблиць.', + 'de-ch': + 'Legt Feldbeschriftungen, Verwendungshinweise und Tabellenbeschriftungen fest.', + 'pt-br': 'Determina legendas de campo, notas de uso e legendas de tabela.', }, showDialogIcon: { - "en-us": "Show icon in the header", - "ru-ru": "Показать значок в заголовке", - "es-es": "Mostrar icono en el encabezado", - "fr-fr": "Afficher l'icône dans l'en-tête", - "uk-ua": "Показати значок у заголовку", - "de-ch": "Symbol in der Kopfzeile anzeigen", - "pt-br": "Mostrar ícone no cabeçalho", + 'en-us': 'Show icon in the header', + 'ru-ru': 'Показать значок в заголовке', + 'es-es': 'Mostrar icono en el encabezado', + 'fr-fr': "Afficher l'icône dans l'en-tête", + 'uk-ua': 'Показати значок у заголовку', + 'de-ch': 'Symbol in der Kopfzeile anzeigen', + 'pt-br': 'Mostrar ícone no cabeçalho', }, scaleInterface: { - "en-us": "Scale Interface", - "ru-ru": "Интерфейс масштабирования", - "es-es": "Interfaz de escala", - "fr-fr": "Interface de balance", - "uk-ua": "Інтерфейс масштабу", - "de-ch": "Waagenschnittstelle", - "pt-br": "Interface de escala", + 'en-us': 'Scale Interface', + 'ru-ru': 'Интерфейс масштабирования', + 'es-es': 'Interfaz de escala', + 'fr-fr': 'Interface de balance', + 'uk-ua': 'Інтерфейс масштабу', + 'de-ch': 'Waagenschnittstelle', + 'pt-br': 'Interface de escala', }, scaleInterfaceDescription: { - "en-us": "Scale interface to match font size.", - "ru-ru": "Масштабируйте интерфейс в соответствии с размером шрифта.", - "es-es": "Escala la interfaz para que coincida con el tamaño de la fuente.", - "fr-fr": "Adapter l'interface à la taille de la police.", - "uk-ua": "Масштабуйте інтерфейс відповідно до розміру шрифту.", - "de-ch": - "Skalieren Sie die Benutzeroberfläche, um sie an die Schriftgröße anzupassen.", - "pt-br": "Dimensione a interface para corresponder ao tamanho da fonte.", + 'en-us': 'Scale interface to match font size.', + 'ru-ru': 'Масштабируйте интерфейс в соответствии с размером шрифта.', + 'es-es': 'Escala la interfaz para que coincida con el tamaño de la fuente.', + 'fr-fr': "Adapter l'interface à la taille de la police.", + 'uk-ua': 'Масштабуйте інтерфейс відповідно до розміру шрифту.', + 'de-ch': + 'Skalieren Sie die Benutzeroberfläche, um sie an die Schriftgröße anzupassen.', + 'pt-br': 'Dimensione a interface para corresponder ao tamanho da fonte.', }, displayAuthor: { - "en-us": "Show author in the tree", - "ru-ru": "Показать автора в дереве", - "es-es": "Mostrar autor en el árbol", - "fr-fr": "Afficher l'auteur dans l'arbre", - "uk-ua": "Показати автора в дереві", - "de-ch": "Autor im Baum anzeigen", - "pt-br": "Mostrar autor", + 'en-us': 'Show author in the tree', + 'ru-ru': 'Показать автора в дереве', + 'es-es': 'Mostrar autor en el árbol', + 'fr-fr': "Afficher l'auteur dans l'arbre", + 'uk-ua': 'Показати автора в дереві', + 'de-ch': 'Autor im Baum anzeigen', + 'pt-br': 'Mostrar autor', }, welcomePage: { - "en-us": "Home Page", - "ru-ru": "Домашняя страница", - "es-es": "Página de inicio", - "fr-fr": "Page d'accueil", - "uk-ua": "Домашня сторінка", - "de-ch": "Startseite", - "pt-br": "Página inicial", + 'en-us': 'Home Page', + 'ru-ru': 'Домашняя страница', + 'es-es': 'Página de inicio', + 'fr-fr': "Page d'accueil", + 'uk-ua': 'Домашня сторінка', + 'de-ch': 'Startseite', + 'pt-br': 'Página inicial', }, content: { - "en-us": "Content", - "ru-ru": "Содержание", - "es-es": "Contenido", - "fr-fr": "Contenu", - "uk-ua": "Зміст", - "de-ch": "Inhalt", - "pt-br": "Contente", + 'en-us': 'Content', + 'ru-ru': 'Содержание', + 'es-es': 'Contenido', + 'fr-fr': 'Contenu', + 'uk-ua': 'Зміст', + 'de-ch': 'Inhalt', + 'pt-br': 'Contente', }, defaultImage: { - "en-us": "Specify Logo", - "ru-ru": "Укажите логотип", - "es-es": "Especificar logotipo", - "fr-fr": "Spécifier le logo", - "uk-ua": "Вкажіть логотип", - "de-ch": "Logo angeben", - "pt-br": "Especificar logotipo", + 'en-us': 'Specify Logo', + 'ru-ru': 'Укажите логотип', + 'es-es': 'Especificar logotipo', + 'fr-fr': 'Spécifier le logo', + 'uk-ua': 'Вкажіть логотип', + 'de-ch': 'Logo angeben', + 'pt-br': 'Especificar logotipo', }, customImage: { - "en-us": "Custom Image", - "ru-ru": "Пользовательское изображение", - "es-es": "Imagen personalizada", - "fr-fr": "Image personnalisée", - "uk-ua": "Спеціальне зображення", - "de-ch": "Benutzerdefiniertes Bild", - "pt-br": "Imagem personalizada", + 'en-us': 'Custom Image', + 'ru-ru': 'Пользовательское изображение', + 'es-es': 'Imagen personalizada', + 'fr-fr': 'Image personnalisée', + 'uk-ua': 'Спеціальне зображення', + 'de-ch': 'Benutzerdefiniertes Bild', + 'pt-br': 'Imagem personalizada', }, embeddedWebpage: { - "en-us": "Embedded web page", - "ru-ru": "Встроенная веб-страница", - "es-es": "Página web incrustada", - "fr-fr": "Page Web intégrée", - "uk-ua": "Вбудована веб-сторінка", - "de-ch": "Eingebettete Webseite", - "pt-br": "Página da web incorporada", + 'en-us': 'Embedded web page', + 'ru-ru': 'Встроенная веб-страница', + 'es-es': 'Página web incrustada', + 'fr-fr': 'Page Web intégrée', + 'uk-ua': 'Вбудована веб-сторінка', + 'de-ch': 'Eingebettete Webseite', + 'pt-br': 'Página da web incorporada', }, embeddedWebpageDescription: { - "en-us": "A URL to a page that would be embedded on the home page:", - "ru-ru": "URL-адрес страницы, которая будет встроена в домашнюю страницу:", - "es-es": "Una URL a una página que se integrará en la página de inicio:", - "fr-fr": "Une URL vers une page qui serait intégrée à la page d'accueil :", - "uk-ua": "URL-адреса сторінки, яка буде вбудована на домашній сторінці:", - "de-ch": - "Eine URL zu einer Seite, die auf der Startseite eingebettet werden soll:", - "pt-br": "Um URL para uma página que seria incorporada na página inicial:", + 'en-us': 'A URL to a page that would be embedded on the home page:', + 'ru-ru': 'URL-адрес страницы, которая будет встроена в домашнюю страницу:', + 'es-es': 'Una URL a una página que se integrará en la página de inicio:', + 'fr-fr': "Une URL vers une page qui serait intégrée à la page d'accueil :", + 'uk-ua': 'URL-адреса сторінки, яка буде вбудована на домашній сторінці:', + 'de-ch': + 'Eine URL zu einer Seite, die auf der Startseite eingebettet werden soll:', + 'pt-br': 'Um URL para uma página que seria incorporada na página inicial:', }, behavior: { - "en-us": "Behavior", - "ru-ru": "Поведение", - "es-es": "Comportamiento", - "fr-fr": "Comportement", - "uk-ua": "Поведінка", - "de-ch": "Verhalten", - "pt-br": "Comportamento", + 'en-us': 'Behavior', + 'ru-ru': 'Поведение', + 'es-es': 'Comportamiento', + 'fr-fr': 'Comportement', + 'uk-ua': 'Поведінка', + 'de-ch': 'Verhalten', + 'pt-br': 'Comportamento', }, noRestrictionsMode: { - "en-us": "No restrictions mode", - "ru-ru": "Режим без ограничений", - "es-es": "Modo sin restricciones", - "fr-fr": "Mode sans restriction", - "uk-ua": "Режим без обмежень", - "de-ch": "Modus „Keine Einschränkungen“", - "pt-br": "Modo sem restrições", + 'en-us': 'No restrictions mode', + 'ru-ru': 'Режим без ограничений', + 'es-es': 'Modo sin restricciones', + 'fr-fr': 'Mode sans restriction', + 'uk-ua': 'Режим без обмежень', + 'de-ch': 'Modus „Keine Einschränkungen“', + 'pt-br': 'Modo sem restrições', }, noRestrictionsModeWbDescription: { - "en-us": "Allows uploading data to any field in any table.", - "ru-ru": "Позволяет загружать данные в любое поле любой таблицы.", - "es-es": "Permite cargar datos a cualquier campo de cualquier tabla.", - "fr-fr": + 'en-us': 'Allows uploading data to any field in any table.', + 'ru-ru': 'Позволяет загружать данные в любое поле любой таблицы.', + 'es-es': 'Permite cargar datos a cualquier campo de cualquier tabla.', + 'fr-fr': "Permet de télécharger des données dans n'importe quel champ de n'importe quelle table.", - "uk-ua": "Дозволяє завантажувати дані в будь-яке поле будь-якої таблиці.", - "de-ch": - "Ermöglicht das Hochladen von Daten in jedes Feld einer beliebigen Tabelle.", - "pt-br": "Permite carregar dados em qualquer campo de qualquer tabela.", + 'uk-ua': 'Дозволяє завантажувати дані в будь-яке поле будь-якої таблиці.', + 'de-ch': + 'Ermöglicht das Hochladen von Daten in jedes Feld einer beliebigen Tabelle.', + 'pt-br': 'Permite carregar dados em qualquer campo de qualquer tabela.', }, noRestrictionsModeQueryDescription: { - "en-us": "Allows querying data from any field in any table.", - "ru-ru": "Позволяет запрашивать данные из любого поля любой таблицы.", - "es-es": "Permite consultar datos de cualquier campo de cualquier tabla.", - "fr-fr": + 'en-us': 'Allows querying data from any field in any table.', + 'ru-ru': 'Позволяет запрашивать данные из любого поля любой таблицы.', + 'es-es': 'Permite consultar datos de cualquier campo de cualquier tabla.', + 'fr-fr': "Permet d'interroger les données de n'importe quel champ de n'importe quelle table.", - "uk-ua": "Дозволяє запитувати дані з будь-якого поля будь-якої таблиці.", - "de-ch": - "Ermöglicht das Abfragen von Daten aus jedem Feld in jeder Tabelle.", - "pt-br": "Permite consultar dados de qualquer campo em qualquer tabela.", + 'uk-ua': 'Дозволяє запитувати дані з будь-якого поля будь-якої таблиці.', + 'de-ch': + 'Ermöglicht das Abfragen von Daten aus jedem Feld in jeder Tabelle.', + 'pt-br': 'Permite consultar dados de qualquer campo em qualquer tabela.', }, noRestrictionsModeWarning: { - "en-us": - "WARNING: enabling this may lead to data loss or database corruption. Please make sure you know what you are doing.", - "ru-ru": - "ВНИМАНИЕ: включение этого может привести к потере данных или повреждению базы данных. Убедитесь, что вы знаете, что делаете.", - "es-es": - "ADVERTENCIA: Habilitar esta opción podría provocar la pérdida de datos o la corrupción de la base de datos. Asegúrese de saber lo que está haciendo.", - "uk-ua": - "ПОПЕРЕДЖЕННЯ: увімкнення цієї функції може призвести до втрати даних або пошкодження бази даних. Переконайтеся, що ви знаєте, що робите.", - "de-ch": - "WARNUNG: Das Aktivieren dieser Option kann zu Datenverlust oder Datenbankbeschädigung führen. Bitte stellen Sie sicher, dass Sie wissen, was Sie tun.", - "fr-fr": + 'en-us': + 'WARNING: enabling this may lead to data loss or database corruption. Please make sure you know what you are doing.', + 'ru-ru': + 'ВНИМАНИЕ: включение этого может привести к потере данных или повреждению базы данных. Убедитесь, что вы знаете, что делаете.', + 'es-es': + 'ADVERTENCIA: Habilitar esta opción podría provocar la pérdida de datos o la corrupción de la base de datos. Asegúrese de saber lo que está haciendo.', + 'uk-ua': + 'ПОПЕРЕДЖЕННЯ: увімкнення цієї функції може призвести до втрати даних або пошкодження бази даних. Переконайтеся, що ви знаєте, що робите.', + 'de-ch': + 'WARNUNG: Das Aktivieren dieser Option kann zu Datenverlust oder Datenbankbeschädigung führen. Bitte stellen Sie sicher, dass Sie wissen, was Sie tun.', + 'fr-fr': "AVERTISSEMENT : l'activation de cette option peut entraîner une perte de données ou une corruption de la base de données. Veuillez vous assurer que vous savez ce que vous faites.", - "pt-br": - "AVISO: habilitar esta opção pode levar à perda de dados ou à corrupção do banco de dados. Certifique-se de saber o que está fazendo.", + 'pt-br': + 'AVISO: habilitar esta opção pode levar à perda de dados ou à corrupção do banco de dados. Certifique-se de saber o que está fazendo.', }, adminsOnlyPreference: { - "en-us": "You don't have permission to change this option", - "ru-ru": "У вас нет разрешения на изменение этой опции", - "es-es": "No tienes permiso para cambiar esta opción", - "fr-fr": "Vous n'êtes pas autorisé à modifier cette option", - "uk-ua": "Ви не маєте дозволу змінювати цей параметр", - "de-ch": "Sie haben keine Berechtigung, diese Option zu ändern", - "pt-br": "Você não tem permissão para alterar esta opção", + 'en-us': "You don't have permission to change this option", + 'ru-ru': 'У вас нет разрешения на изменение этой опции', + 'es-es': 'No tienes permiso para cambiar esta opción', + 'fr-fr': "Vous n'êtes pas autorisé à modifier cette option", + 'uk-ua': 'Ви не маєте дозволу змінювати цей параметр', + 'de-ch': 'Sie haben keine Berechtigung, diese Option zu ändern', + 'pt-br': 'Você não tem permissão para alterar esta opção', }, stickyScrolling: { - "en-us": "Sticky scroll bar", - "ru-ru": "Липкая полоса прокрутки", - "es-es": "Barra de desplazamiento fija", - "fr-fr": "Barre de défilement collante", - "uk-ua": "Липка смуга прокрутки", - "de-ch": "Klebrige Bildlaufleiste", - "pt-br": "Barra de rolagem fixa", + 'en-us': 'Sticky scroll bar', + 'ru-ru': 'Липкая полоса прокрутки', + 'es-es': 'Barra de desplazamiento fija', + 'fr-fr': 'Barre de défilement collante', + 'uk-ua': 'Липка смуга прокрутки', + 'de-ch': 'Klebrige Bildlaufleiste', + 'pt-br': 'Barra de rolagem fixa', }, foreground: { - "en-us": "Foreground", - "ru-ru": "Передний план", - "es-es": "Primer plano", - "fr-fr": "Premier plan", - "uk-ua": "Передній план", - "de-ch": "Vordergrund", - "pt-br": "Primeiro plano", + 'en-us': 'Foreground', + 'ru-ru': 'Передний план', + 'es-es': 'Primer plano', + 'fr-fr': 'Premier plan', + 'uk-ua': 'Передній план', + 'de-ch': 'Vordergrund', + 'pt-br': 'Primeiro plano', }, background: { - "en-us": "Background", - "ru-ru": "Фон", - "es-es": "Fondo", - "fr-fr": "Arrière-plan", - "uk-ua": "Фон", - "de-ch": "Hintergrund", - "pt-br": "Fundo", + 'en-us': 'Background', + 'ru-ru': 'Фон', + 'es-es': 'Fondo', + 'fr-fr': 'Arrière-plan', + 'uk-ua': 'Фон', + 'de-ch': 'Hintergrund', + 'pt-br': 'Fundo', }, sidebarTheme: { - "en-us": "Sidebar theme", - "de-ch": "Seitenleistenthema", - "es-es": "Tema de la barra lateral", - "fr-fr": "Thème de la barre latérale", - "ru-ru": "Тема боковой панели", - "uk-ua": "Тема бічної панелі", - "pt-br": "Tema da barra lateral", + 'en-us': 'Sidebar theme', + 'de-ch': 'Seitenleistenthema', + 'es-es': 'Tema de la barra lateral', + 'fr-fr': 'Thème de la barre latérale', + 'ru-ru': 'Тема боковой панели', + 'uk-ua': 'Тема бічної панелі', + 'pt-br': 'Tema da barra lateral', }, darkForeground: { - "en-us": "Foreground (dark theme)", - "ru-ru": "Передний план (тёмная тема)", - "es-es": "Primer plano (tema oscuro)", - "fr-fr": "Premier plan (thème sombre)", - "uk-ua": "Передній план (темна тема)", - "de-ch": "Vordergrund (dunkles Design)", - "pt-br": "Primeiro plano (tema escuro)", + 'en-us': 'Foreground (dark theme)', + 'ru-ru': 'Передний план (тёмная тема)', + 'es-es': 'Primer plano (tema oscuro)', + 'fr-fr': 'Premier plan (thème sombre)', + 'uk-ua': 'Передній план (темна тема)', + 'de-ch': 'Vordergrund (dunkles Design)', + 'pt-br': 'Primeiro plano (tema escuro)', }, darkBackground: { - "en-us": "Background (dark theme)", - "ru-ru": "Фон (тёмная тема)", - "es-es": "Fondo (tema oscuro)", - "fr-fr": "Arrière-plan (thème sombre)", - "uk-ua": "Фон (темна тема)", - "de-ch": "Hintergrund (dunkles Design)", - "pt-br": "Plano de fundo (tema escuro)", + 'en-us': 'Background (dark theme)', + 'ru-ru': 'Фон (тёмная тема)', + 'es-es': 'Fondo (tema oscuro)', + 'fr-fr': 'Arrière-plan (thème sombre)', + 'uk-ua': 'Фон (темна тема)', + 'de-ch': 'Hintergrund (dunkles Design)', + 'pt-br': 'Plano de fundo (tema escuro)', }, accentColor1: { - "en-us": "Accent color 1", - "ru-ru": "Акцентный цвет 1", - "es-es": "Color de acento 1", - "fr-fr": "Couleur d'accent 1", - "uk-ua": "Акцентний колір 1", - "de-ch": "Akzentfarbe 1", - "pt-br": "Cor de destaque 1", + 'en-us': 'Accent color 1', + 'ru-ru': 'Акцентный цвет 1', + 'es-es': 'Color de acento 1', + 'fr-fr': "Couleur d'accent 1", + 'uk-ua': 'Акцентний колір 1', + 'de-ch': 'Akzentfarbe 1', + 'pt-br': 'Cor de destaque 1', }, accentColor2: { - "en-us": "Accent color 2", - "ru-ru": "Акцентный цвет 2", - "es-es": "Color de acento 2", - "fr-fr": "Couleur d'accent 2", - "uk-ua": "Акцентний колір 2", - "de-ch": "Akzentfarbe 2", - "pt-br": "Cor de destaque 2", + 'en-us': 'Accent color 2', + 'ru-ru': 'Акцентный цвет 2', + 'es-es': 'Color de acento 2', + 'fr-fr': "Couleur d'accent 2", + 'uk-ua': 'Акцентний колір 2', + 'de-ch': 'Akzentfarbe 2', + 'pt-br': 'Cor de destaque 2', }, accentColor3: { - "en-us": "Accent color 3", - "ru-ru": "Акцентный цвет 3", - "es-es": "Color de acento 3", - "fr-fr": "Couleur d'accent 3", - "uk-ua": "Акцентний колір 3", - "de-ch": "Akzentfarbe 3", - "pt-br": "Cor de destaque 3", + 'en-us': 'Accent color 3', + 'ru-ru': 'Акцентный цвет 3', + 'es-es': 'Color de acento 3', + 'fr-fr': "Couleur d'accent 3", + 'uk-ua': 'Акцентний колір 3', + 'de-ch': 'Akzentfarbe 3', + 'pt-br': 'Cor de destaque 3', }, accentColor4: { - "en-us": "Accent color 4", - "ru-ru": "Акцентный цвет 4", - "es-es": "Color de acento 4", - "fr-fr": "Couleur d'accent 4", - "uk-ua": "Акцентний колір 4", - "de-ch": "Akzentfarbe 4", - "pt-br": "Cor de destaque 4", + 'en-us': 'Accent color 4', + 'ru-ru': 'Акцентный цвет 4', + 'es-es': 'Color de acento 4', + 'fr-fr': "Couleur d'accent 4", + 'uk-ua': 'Акцентний колір 4', + 'de-ch': 'Akzentfarbe 4', + 'pt-br': 'Cor de destaque 4', }, accentColor5: { - "en-us": "Accent color 5", - "ru-ru": "Акцентный цвет 5", - "es-es": "Color de acento 5", - "fr-fr": "Couleur d'accent 5", - "uk-ua": "Акцентний колір 5", - "de-ch": "Akzentfarbe 5", - "pt-br": "Cor de destaque 5", + 'en-us': 'Accent color 5', + 'ru-ru': 'Акцентный цвет 5', + 'es-es': 'Color de acento 5', + 'fr-fr': "Couleur d'accent 5", + 'uk-ua': 'Акцентний колір 5', + 'de-ch': 'Akzentfarbe 5', + 'pt-br': 'Cor de destaque 5', }, spreadsheet: { - "en-us": "Spreadsheet", - "ru-ru": "Электронная таблица", - "es-es": "Hoja de cálculo", - "fr-fr": "Tableur", - "uk-ua": "Електронна таблиця", - "de-ch": "Kalkulationstabelle", - "pt-br": "Planilha", + 'en-us': 'Spreadsheet', + 'ru-ru': 'Электронная таблица', + 'es-es': 'Hoja de cálculo', + 'fr-fr': 'Tableur', + 'uk-ua': 'Електронна таблиця', + 'de-ch': 'Kalkulationstabelle', + 'pt-br': 'Planilha', }, minSpareRows: { - "en-us": "Number of blank rows at the end", - "ru-ru": "Количество пустых строк в конце", - "es-es": "Número de filas en blanco al final", - "fr-fr": "Nombre de lignes vides à la fin", - "uk-ua": "Кількість порожніх рядків у кінці", - "de-ch": "Anzahl der leeren Zeilen am Ende", - "pt-br": "Número de linhas em branco no final", + 'en-us': 'Number of blank rows at the end', + 'ru-ru': 'Количество пустых строк в конце', + 'es-es': 'Número de filas en blanco al final', + 'fr-fr': 'Nombre de lignes vides à la fin', + 'uk-ua': 'Кількість порожніх рядків у кінці', + 'de-ch': 'Anzahl der leeren Zeilen am Ende', + 'pt-br': 'Número de linhas em branco no final', }, autoWrapCols: { - "en-us": "Navigate to the other side when reaching the edge column", - "ru-ru": "Достигнув крайней колонны, перейдите на другую сторону.", - "es-es": "Navegue hacia el otro lado al llegar a la columna del borde.", - "fr-fr": - "Naviguez de l’autre côté lorsque vous atteignez la colonne de bord", - "uk-ua": "Перейдіть на іншу сторону, коли досягнете краю колонки", - "de-ch": - "Navigieren Sie zur anderen Seite, wenn Sie die Randspalte erreichen", - "pt-br": "Navegue para o outro lado ao atingir a coluna da borda", + 'en-us': 'Navigate to the other side when reaching the edge column', + 'ru-ru': 'Достигнув крайней колонны, перейдите на другую сторону.', + 'es-es': 'Navegue hacia el otro lado al llegar a la columna del borde.', + 'fr-fr': + 'Naviguez de l’autre côté lorsque vous atteignez la colonne de bord', + 'uk-ua': 'Перейдіть на іншу сторону, коли досягнете краю колонки', + 'de-ch': + 'Navigieren Sie zur anderen Seite, wenn Sie die Randspalte erreichen', + 'pt-br': 'Navegue para o outro lado ao atingir a coluna da borda', }, autoWrapRows: { - "en-us": "Navigate to the other side when reaching the edge row", - "ru-ru": "Достигнув крайнего ряда, перейдите на другую сторону.", - "es-es": "Navegue hacia el otro lado al llegar a la fila del borde.", - "fr-fr": - "Naviguez de l’autre côté lorsque vous atteignez la rangée de bord", - "uk-ua": "Перейдіть на іншу сторону, коли досягнете крайнього ряду", - "de-ch": - "Navigieren Sie zur anderen Seite, wenn Sie die Randreihe erreichen", - "pt-br": "Navegue para o outro lado ao atingir a fileira de bordas", + 'en-us': 'Navigate to the other side when reaching the edge row', + 'ru-ru': 'Достигнув крайнего ряда, перейдите на другую сторону.', + 'es-es': 'Navegue hacia el otro lado al llegar a la fila del borde.', + 'fr-fr': + 'Naviguez de l’autre côté lorsque vous atteignez la rangée de bord', + 'uk-ua': 'Перейдіть на іншу сторону, коли досягнете крайнього ряду', + 'de-ch': + 'Navigieren Sie zur anderen Seite, wenn Sie die Randreihe erreichen', + 'pt-br': 'Navegue para o outro lado ao atingir a fileira de bordas', }, enterBeginsEditing: { - "en-us": "Enter key begins editing cell", - "ru-ru": "Клавиша Enter начинает редактирование ячейки", - "es-es": "La tecla Enter inicia la edición de la celda", - "fr-fr": "La touche Entrée commence à modifier la cellule", - "uk-ua": "Клавіша Enter починає редагування клітинки", - "de-ch": "Mit der Eingabetaste beginnt die Bearbeitung der Zelle", - "pt-br": "A tecla Enter inicia a edição da célula", + 'en-us': 'Enter key begins editing cell', + 'ru-ru': 'Клавиша Enter начинает редактирование ячейки', + 'es-es': 'La tecla Enter inicia la edición de la celda', + 'fr-fr': 'La touche Entrée commence à modifier la cellule', + 'uk-ua': 'Клавіша Enter починає редагування клітинки', + 'de-ch': 'Mit der Eingabetaste beginnt die Bearbeitung der Zelle', + 'pt-br': 'A tecla Enter inicia a edição da célula', }, tabMoveDirection: { - "en-us": "Direction of movement when Tab key is pressed", - "ru-ru": "Направление движения при нажатии клавиши Tab", - "es-es": - "Dirección de movimiento cuando se presiona la tecla Tab", - "fr-fr": - "Sens de déplacement lorsque la touche Tabulation est enfoncée", - "uk-ua": "Напрямок руху при натисканні клавіші Tab", - "de-ch": "Bewegungsrichtung beim Drücken der Taste Tab", - "pt-br": "Direção do movimento quando a tecla Tab é pressionada", + 'en-us': 'Direction of movement when Tab key is pressed', + 'ru-ru': 'Направление движения при нажатии клавиши Tab', + 'es-es': + 'Dirección de movimiento cuando se presiona la tecla Tab', + 'fr-fr': + 'Sens de déplacement lorsque la touche Tabulation est enfoncée', + 'uk-ua': 'Напрямок руху при натисканні клавіші Tab', + 'de-ch': 'Bewegungsrichtung beim Drücken der Taste Tab', + 'pt-br': 'Direção do movimento quando a tecla Tab é pressionada', }, tabMoveDirectionDescription: { - "en-us": - "You can move in the opposite direction by pressing Shift+Tab.", - "ru-ru": - "Вы можете двигаться в обратном направлении, нажав Shift+Tab.", - "es-es": - "Puedes moverte en la dirección opuesta presionando Shift+Tab.", - "fr-fr": - "Vous pouvez vous déplacer dans la direction opposée en appuyant sur Shift+Tab.", - "uk-ua": - "Ви можете рухатися в протилежному напрямку, натискаючи Shift+Tab.", - "de-ch": - "Sie können sich in die entgegengesetzte Richtung bewegen, indem Sie Umschalt+Tab drücken.", - "pt-br": - "Você pode mover na direção oposta pressionando Shift+Tab.", + 'en-us': + 'You can move in the opposite direction by pressing Shift+Tab.', + 'ru-ru': + 'Вы можете двигаться в обратном направлении, нажав Shift+Tab.', + 'es-es': + 'Puedes moverte en la dirección opuesta presionando Shift+Tab.', + 'fr-fr': + 'Vous pouvez vous déplacer dans la direction opposée en appuyant sur Shift+Tab.', + 'uk-ua': + 'Ви можете рухатися в протилежному напрямку, натискаючи Shift+Tab.', + 'de-ch': + 'Sie können sich in die entgegengesetzte Richtung bewegen, indem Sie Umschalt+Tab drücken.', + 'pt-br': + 'Você pode mover na direção oposta pressionando Shift+Tab.', }, column: { - "en-us": "Column", - "ru-ru": "Столбец", - "es-es": "Columna", - "fr-fr": "Colonne", - "uk-ua": "Колонка", - "de-ch": "Spalte", - "pt-br": "Coluna", + 'en-us': 'Column', + 'ru-ru': 'Столбец', + 'es-es': 'Columna', + 'fr-fr': 'Colonne', + 'uk-ua': 'Колонка', + 'de-ch': 'Spalte', + 'pt-br': 'Coluna', }, row: { - "en-us": "Row", - "ru-ru": "Ряд", - "es-es": "Fila", - "fr-fr": "Rangée", - "uk-ua": "рядок", - "de-ch": "Reihe", - "pt-br": "Linha", + 'en-us': 'Row', + 'ru-ru': 'Ряд', + 'es-es': 'Fila', + 'fr-fr': 'Rangée', + 'uk-ua': 'рядок', + 'de-ch': 'Reihe', + 'pt-br': 'Linha', }, enterMoveDirection: { - "en-us": "Direction of movement when Enter key is pressed", - "ru-ru": "Направление движения при нажатии клавиши Enter", - "es-es": - "Dirección de movimiento cuando se presiona la tecla Enter", - "uk-ua": "Напрямок руху, коли натиснуто клавішу Enter", - "de-ch": "Bewegungsrichtung beim Drücken der Taste Enter", - "fr-fr": - "Direction du mouvement lorsque la touche Entrer est enfoncée", - "pt-br": - "Direção do movimento quando a tecla Enter é pressionada", + 'en-us': 'Direction of movement when Enter key is pressed', + 'ru-ru': 'Направление движения при нажатии клавиши Enter', + 'es-es': + 'Dirección de movimiento cuando se presiona la tecla Enter', + 'uk-ua': 'Напрямок руху, коли натиснуто клавішу Enter', + 'de-ch': 'Bewegungsrichtung beim Drücken der Taste Enter', + 'fr-fr': + 'Direction du mouvement lorsque la touche Entrer est enfoncée', + 'pt-br': + 'Direção do movimento quando a tecla Enter é pressionada', }, enterMoveDirectionDescription: { - "en-us": - "You can move in the opposite direction by pressing Shift+Enter.", - "ru-ru": - "Вы можете двигаться в противоположном направлении, нажав Shift+Enter.", - "es-es": - "Puedes moverte en la dirección opuesta presionando Shift+Enter.", - "fr-fr": "Synonyme couleur.", - "uk-ua": - "Ви можете рухатися у протилежному напрямку, натискаючи Shift+Enter.", - "de-ch": - "Sie können sich in die entgegengesetzte Richtung bewegen, indem Sie Umschalt+Eingabe drücken.", - "pt-br": - "Você pode mover na direção oposta pressionando Shift+Enter.", + 'en-us': + 'You can move in the opposite direction by pressing Shift+Enter.', + 'ru-ru': + 'Вы можете двигаться в противоположном направлении, нажав Shift+Enter.', + 'es-es': + 'Puedes moverte en la dirección opuesta presionando Shift+Enter.', + 'fr-fr': 'Synonyme couleur.', + 'uk-ua': + 'Ви можете рухатися у протилежному напрямку, натискаючи Shift+Enter.', + 'de-ch': + 'Sie können sich in die entgegengesetzte Richtung bewegen, indem Sie Umschalt+Eingabe drücken.', + 'pt-br': + 'Você pode mover na direção oposta pressionando Shift+Enter.', }, filterPickLists: { - "en-us": "Filter pick list items", - "ru-ru": "Фильтрация элементов списка выбора", - "es-es": "Filtrar elementos de la lista de selección", - "fr-fr": "Filtrer les éléments de la liste de sélection", - "uk-ua": "Фільтр вибору елементів списку", - "de-ch": "Auswahllistenelemente filtern", - "pt-br": "Filtrar itens da lista de seleção", + 'en-us': 'Filter pick list items', + 'ru-ru': 'Фильтрация элементов списка выбора', + 'es-es': 'Filtrar elementos de la lista de selección', + 'fr-fr': 'Filtrer les éléments de la liste de sélection', + 'uk-ua': 'Фільтр вибору елементів списку', + 'de-ch': 'Auswahllistenelemente filtern', + 'pt-br': 'Filtrar itens da lista de seleção', }, exportFileDelimiter: { - "en-us": "Export file delimiter", - "ru-ru": "Разделитель экспортируемых файлов", - "es-es": "Delimitador de archivo de exportación", - "fr-fr": "Délimiteur de fichier d'exportation", - "uk-ua": "Роздільник файлу експорту", - "de-ch": "Exportdatei-Trennzeichen", - "pt-br": "Delimitador de arquivo de exportação", + 'en-us': 'Export file delimiter', + 'ru-ru': 'Разделитель экспортируемых файлов', + 'es-es': 'Delimitador de archivo de exportación', + 'fr-fr': "Délimiteur de fichier d'exportation", + 'uk-ua': 'Роздільник файлу експорту', + 'de-ch': 'Exportdatei-Trennzeichen', + 'pt-br': 'Delimitador de arquivo de exportação', }, exportCsvUtf8Bom: { - "en-us": "Add UTF-8 BOM to CSV file exports", - "ru-ru": "Добавить UTF-8 BOM в экспорт CSV-файла", - "es-es": "Agregar BOM UTF-8 a las exportaciones de archivos CSV", - "fr-fr": "Ajouter UTF-8 BOM aux exportations de fichiers CSV", - "uk-ua": "Додайте специфікацію UTF-8 до експорту файлу CSVу", - "de-ch": "UTF-8 BOM zum CSV-Dateiexport hinzufügen", - "pt-br": "Adicionar UTF-8 BOM às exportações de arquivos CSV", + 'en-us': 'Add UTF-8 BOM to CSV file exports', + 'ru-ru': 'Добавить UTF-8 BOM в экспорт CSV-файла', + 'es-es': 'Agregar BOM UTF-8 a las exportaciones de archivos CSV', + 'fr-fr': 'Ajouter UTF-8 BOM aux exportations de fichiers CSV', + 'uk-ua': 'Додайте специфікацію UTF-8 до експорту файлу CSVу', + 'de-ch': 'UTF-8 BOM zum CSV-Dateiexport hinzufügen', + 'pt-br': 'Adicionar UTF-8 BOM às exportações de arquivos CSV', }, exportCsvUtf8BomDescription: { - "en-us": - "Adds a BOM (Byte Order Mark) to exported CSV files to ensure that the file is correctly recognized and displayed by various programs (Excel, OpenRefine, etc.), preventing issues with special characters and formatting.", - "ru-ru": "Корректное отображение экспортированных CSV-файлов в Excel.", - "es-es": - "Agrega una BOM (marca de orden de bytes) a los archivos CSV exportados para garantizar que el archivo sea reconocido y mostrado correctamente por varios programas (Excel, OpenRefine, etc.), evitando problemas con caracteres especiales y formato.", - "fr-fr": + 'en-us': + 'Adds a BOM (Byte Order Mark) to exported CSV files to ensure that the file is correctly recognized and displayed by various programs (Excel, OpenRefine, etc.), preventing issues with special characters and formatting.', + 'ru-ru': 'Корректное отображение экспортированных CSV-файлов в Excel.', + 'es-es': + 'Agrega una BOM (marca de orden de bytes) a los archivos CSV exportados para garantizar que el archivo sea reconocido y mostrado correctamente por varios programas (Excel, OpenRefine, etc.), evitando problemas con caracteres especiales y formato.', + 'fr-fr': "Permet aux exportations de fichiers CSV de s'afficher correctement dans Excel.", - "uk-ua": "Змушує експорт файлів CSV правильно відображатися в Excel.", - "de-ch": - "Sorgt dafür, dass CSV-Dateiexporte in Excel korrekt angezeigt werden.", - "pt-br": - "Adiciona uma BOM (Byte Order Mark) aos arquivos CSV exportados para garantir que o arquivo seja reconhecido e exibido corretamente por vários programas (Excel, OpenRefine, etc.), evitando problemas com caracteres especiais e formatação.", + 'uk-ua': 'Змушує експорт файлів CSV правильно відображатися в Excel.', + 'de-ch': + 'Sorgt dafür, dass CSV-Dateiexporte in Excel korrekt angezeigt werden.', + 'pt-br': + 'Adiciona uma BOM (Byte Order Mark) aos arquivos CSV exportados para garantir que o arquivo seja reconhecido e exibido corretamente por vários programas (Excel, OpenRefine, etc.), evitando problemas com caracteres especiais e formatação.', }, caseSensitive: { - "en-us": "Case-sensitive", - "ru-ru": "С учетом регистра", - "es-es": "Distingue mayúsculas y minúsculas", - "fr-fr": "Sensible aux majuscules et minuscules", - "uk-ua": "Чутливий до регістру", - "de-ch": "Groß- und Kleinschreibung beachten", - "pt-br": "Maiúsculas e minúsculas", + 'en-us': 'Case-sensitive', + 'ru-ru': 'С учетом регистра', + 'es-es': 'Distingue mayúsculas y minúsculas', + 'fr-fr': 'Sensible aux majuscules et minuscules', + 'uk-ua': 'Чутливий до регістру', + 'de-ch': 'Groß- und Kleinschreibung beachten', + 'pt-br': 'Maiúsculas e minúsculas', }, caseInsensitive: { - "en-us": "Case-insensitive", - "ru-ru": "Без учета регистра", - "es-es": "No distingue entre mayúsculas y minúsculas", - "fr-fr": "Insensible à la casse", - "uk-ua": "Регістр не враховується", - "de-ch": "Groß-/Kleinschreibung wird nicht beachtet", - "pt-br": "Não diferencia maiúsculas de minúsculas", + 'en-us': 'Case-insensitive', + 'ru-ru': 'Без учета регистра', + 'es-es': 'No distingue entre mayúsculas y minúsculas', + 'fr-fr': 'Insensible à la casse', + 'uk-ua': 'Регістр не враховується', + 'de-ch': 'Groß-/Kleinschreibung wird nicht beachtet', + 'pt-br': 'Não diferencia maiúsculas de minúsculas', }, showNoReadTables: { - "en-us": 'Show tables without "Read" access', - "ru-ru": "Показать таблицы без доступа «Чтение»", - "es-es": 'Mostrar tablas sin acceso de "Lectura"', - "fr-fr": 'Afficher les tableaux sans accès "Lecture"', - "uk-ua": "Показувати таблиці без доступу «Читання»", - "de-ch": "Tabellen ohne Lesezugriff anzeigen", - "pt-br": 'Mostrar tabelas sem acesso de "Leitura"', + 'en-us': 'Show tables without "Read" access', + 'ru-ru': 'Показать таблицы без доступа «Чтение»', + 'es-es': 'Mostrar tablas sin acceso de "Lectura"', + 'fr-fr': 'Afficher les tableaux sans accès "Lecture"', + 'uk-ua': 'Показувати таблиці без доступу «Читання»', + 'de-ch': 'Tabellen ohne Lesezugriff anzeigen', + 'pt-br': 'Mostrar tabelas sem acesso de "Leitura"', }, showNoAccessTables: { - "en-us": 'Show tables without "Create" access', - "ru-ru": "Показать таблицы без права «Создать»", - "es-es": 'Mostrar tablas sin acceso "Crear"', - "fr-fr": 'Afficher les tableaux sans accès "Créer"', - "uk-ua": "Показувати таблиці без доступу «Створити»", - "de-ch": "Tabellen ohne „Erstellen“-Zugriff anzeigen", - "pt-br": 'Mostrar tabelas sem acesso "Criar"', + 'en-us': 'Show tables without "Create" access', + 'ru-ru': 'Показать таблицы без права «Создать»', + 'es-es': 'Mostrar tablas sin acceso "Crear"', + 'fr-fr': 'Afficher les tableaux sans accès "Créer"', + 'uk-ua': 'Показувати таблиці без доступу «Створити»', + 'de-ch': 'Tabellen ohne „Erstellen“-Zugriff anzeigen', + 'pt-br': 'Mostrar tabelas sem acesso "Criar"', }, textAreaAutoGrow: { - "en-us": "Text boxes grow automatically", - "ru-ru": "Текстовые поля увеличиваются автоматически", - "es-es": "Los cuadros de texto crecen automáticamente", - "fr-fr": "Les zones de texte s'agrandissent automatiquement", - "uk-ua": "Текстові поля збільшуються автоматично", - "de-ch": "Textfelder werden automatisch vergrößert", - "pt-br": "As caixas de texto crescem automaticamente", + 'en-us': 'Text boxes grow automatically', + 'ru-ru': 'Текстовые поля увеличиваются автоматически', + 'es-es': 'Los cuadros de texto crecen automáticamente', + 'fr-fr': "Les zones de texte s'agrandissent automatiquement", + 'uk-ua': 'Текстові поля збільшуються автоматично', + 'de-ch': 'Textfelder werden automatisch vergrößert', + 'pt-br': 'As caixas de texto crescem automaticamente', }, clearQueryFilters: { - "en-us": "Reset query filters", - "ru-ru": "Сбросить фильтры запроса", - "es-es": "Restablecer filtros de consulta", - "fr-fr": "Réinitialiser les filtres de requête", - "uk-ua": "Скинути фільтри запитів", - "de-ch": "Abfragefilter zurücksetzen", - "pt-br": "Redefinir filtros de consulta", + 'en-us': 'Reset query filters', + 'ru-ru': 'Сбросить фильтры запроса', + 'es-es': 'Restablecer filtros de consulta', + 'fr-fr': 'Réinitialiser les filtres de requête', + 'uk-ua': 'Скинути фільтри запитів', + 'de-ch': 'Abfragefilter zurücksetzen', + 'pt-br': 'Redefinir filtros de consulta', }, clearQueryFiltersDescription: { - "en-us": "Clears all query filters when running a Report from a Form.", - "de-ch": - "Löscht alle Abfragefilter, wenn ein Bericht aus einem Formular ausgeführt wird.", - "es-es": - "Borra todos los filtros de consulta al ejecutar un informe desde un formulario.", - "fr-fr": + 'en-us': 'Clears all query filters when running a Report from a Form.', + 'de-ch': + 'Löscht alle Abfragefilter, wenn ein Bericht aus einem Formular ausgeführt wird.', + 'es-es': + 'Borra todos los filtros de consulta al ejecutar un informe desde un formulario.', + 'fr-fr': "Efface tous les filtres de requête lors de l'exécution d'un rapport à partir d'un formulaire.", - "ru-ru": "Очищает все фильтры запроса при запуске отчета из формы.", - "uk-ua": "Очищає всі фільтри запитів під час запуску звіту з форми.", - "pt-br": - "Limpa todos os filtros de consulta ao executar um relatório de um formulário.", + 'ru-ru': 'Очищает все фильтры запроса при запуске отчета из формы.', + 'uk-ua': 'Очищає всі фільтри запитів під час запуску звіту з форми.', + 'pt-br': + 'Limpa todos os filtros de consulta ao executar um relatório de um formulário.', }, queryParamtersFromForm: { - "en-us": "Show query filters when running a Report from a Form", - "de-ch": - "Abfragefilter anzeigen, wenn ein Bericht aus einem Formular ausgeführt wird", - "es-es": - "Mostrar filtros de consulta al ejecutar un informe desde un formulario", - "fr-fr": + 'en-us': 'Show query filters when running a Report from a Form', + 'de-ch': + 'Abfragefilter anzeigen, wenn ein Bericht aus einem Formular ausgeführt wird', + 'es-es': + 'Mostrar filtros de consulta al ejecutar un informe desde un formulario', + 'fr-fr': "Afficher les filtres de requête lors de l'exécution d'un rapport à partir d'un formulaire", - "ru-ru": "Показывать фильтры запросов при запуске отчета из формы", - "uk-ua": "Показувати фільтри запитів під час запуску звіту з форми", - "pt-br": - "Mostrar filtros de consulta ao executar um relatório de um formulário", + 'ru-ru': 'Показывать фильтры запросов при запуске отчета из формы', + 'uk-ua': 'Показувати фільтри запитів під час запуску звіту з форми', + 'pt-br': + 'Mostrar filtros de consulta ao executar um relatório de um formulário', }, autoGrowAutoComplete: { - "en-us": "Allow autocomplete to grow as wide as need", - "ru-ru": - "Разрешить автозаполнению расширяться настолько, насколько это необходимо", - "es-es": "Permitir que el autocompletado crezca tanto como sea necesario", - "fr-fr": - "Sens de déplacement lorsque la touche [X27X]Tabulation[X35X] est enfoncée", - "uk-ua": - "Дозволити автозаповнення розширюватися настільки, наскільки потрібно", - "de-ch": - "Erlauben Sie der Autovervollständigung, so weit wie nötig zu wachsen", - "pt-br": - "Permitir que o preenchimento automático cresça o quanto for necessário", + 'en-us': 'Allow autocomplete to grow as wide as need', + 'ru-ru': + 'Разрешить автозаполнению расширяться настолько, насколько это необходимо', + 'es-es': 'Permitir que el autocompletado crezca tanto como sea necesario', + 'fr-fr': + 'Sens de déplacement lorsque la touche [X27X]Tabulation[X35X] est enfoncée', + 'uk-ua': + 'Дозволити автозаповнення розширюватися настільки, наскільки потрібно', + 'de-ch': + 'Erlauben Sie der Autovervollständigung, so weit wie nötig zu wachsen', + 'pt-br': + 'Permitir que o preenchimento automático cresça o quanto for necessário', }, tableNameInTitle: { - "en-us": "Include table name in the browser page title", - "ru-ru": "Включить имя таблицы в заголовок страницы браузера", - "es-es": - "Incluir el nombre de la tabla en el título de la página del navegador", - "fr-fr": - "Inclure le nom de la table dans le titre de la page du navigateur", - "uk-ua": "Включіть назву таблиці в заголовок сторінки браузера", - "de-ch": "Tabellennamen in den Seitentitel des Browsers aufnehmen", - "pt-br": "Incluir nome da tabela no título da página do navegador", + 'en-us': 'Include table name in the browser page title', + 'ru-ru': 'Включить имя таблицы в заголовок страницы браузера', + 'es-es': + 'Incluir el nombre de la tabla en el título de la página del navegador', + 'fr-fr': + 'Inclure le nom de la table dans le titre de la page du navigateur', + 'uk-ua': 'Включіть назву таблиці в заголовок сторінки браузера', + 'de-ch': 'Tabellennamen in den Seitentitel des Browsers aufnehmen', + 'pt-br': 'Incluir nome da tabela no título da página do navegador', }, focusFirstField: { - "en-us": "Focus first field", - "de-ch": "Fokus erstes Feld", - "es-es": "Enfoque el primer campo", - "fr-fr": "Concentrez-vous sur le premier champ", - "ru-ru": "Фокус на первом поле", - "uk-ua": "Перейти до першого поля", - "pt-br": "Foco primeiro no campo", + 'en-us': 'Focus first field', + 'de-ch': 'Fokus erstes Feld', + 'es-es': 'Enfoque el primer campo', + 'fr-fr': 'Concentrez-vous sur le premier champ', + 'ru-ru': 'Фокус на первом поле', + 'uk-ua': 'Перейти до першого поля', + 'pt-br': 'Foco primeiro no campo', }, doubleClickZoom: { - "en-us": "Double click to zoom", - "ru-ru": "Дважды щелкните, чтобы увеличить", - "es-es": "Haga doble clic para ampliar", - "fr-fr": "Double-cliquez pour zoomer", - "uk-ua": "Двічі клацніть, щоб збільшити", - "de-ch": "Zum Zoomen doppelklicken", - "pt-br": "Clique duas vezes para ampliar", + 'en-us': 'Double click to zoom', + 'ru-ru': 'Дважды щелкните, чтобы увеличить', + 'es-es': 'Haga doble clic para ampliar', + 'fr-fr': 'Double-cliquez pour zoomer', + 'uk-ua': 'Двічі клацніть, щоб збільшити', + 'de-ch': 'Zum Zoomen doppelklicken', + 'pt-br': 'Clique duas vezes para ampliar', }, closePopupOnClick: { - "en-us": "Close pop-up on outside click", - "ru-ru": "Закрыть всплывающее окно при внешнем щелчке", - "es-es": "Cerrar ventana emergente al hacer clic desde fuera", - "fr-fr": "Fermer la pop-up lors d'un clic extérieur", - "uk-ua": "Закрити спливаюче вікно при зовнішньому клацанні", - "de-ch": "Popup bei externem Klick schließen", - "pt-br": "Fechar pop-up ao clicar fora", + 'en-us': 'Close pop-up on outside click', + 'ru-ru': 'Закрыть всплывающее окно при внешнем щелчке', + 'es-es': 'Cerrar ventana emergente al hacer clic desde fuera', + 'fr-fr': "Fermer la pop-up lors d'un clic extérieur", + 'uk-ua': 'Закрити спливаюче вікно при зовнішньому клацанні', + 'de-ch': 'Popup bei externem Klick schließen', + 'pt-br': 'Fechar pop-up ao clicar fora', }, animateTransitions: { - "en-us": "Animate transitions", - "ru-ru": "Анимированные переходы", - "es-es": "Transiciones animadas", - "fr-fr": "Animer les transitions", - "uk-ua": "Анімація переходів", - "de-ch": "Übergänge animieren", - "pt-br": "Transições animadas", + 'en-us': 'Animate transitions', + 'ru-ru': 'Анимированные переходы', + 'es-es': 'Transiciones animadas', + 'fr-fr': 'Animer les transitions', + 'uk-ua': 'Анімація переходів', + 'de-ch': 'Übergänge animieren', + 'pt-br': 'Transições animadas', }, panInertia: { - "en-us": "Pan inertia", - "ru-ru": "Инерция сковороды", - "es-es": "Inercia de la sartén", - "fr-fr": "Inertie du bac", - "uk-ua": "Інерція панорами", - "de-ch": "Schwenkträgheit", - "pt-br": "Inércia da panela", + 'en-us': 'Pan inertia', + 'ru-ru': 'Инерция сковороды', + 'es-es': 'Inercia de la sartén', + 'fr-fr': 'Inertie du bac', + 'uk-ua': 'Інерція панорами', + 'de-ch': 'Schwenkträgheit', + 'pt-br': 'Inércia da panela', }, mouseDrags: { - "en-us": "Mouse drags", - "ru-ru": "Перетаскивание мышью", - "es-es": "El ratón arrastra", - "uk-ua": "Виділіть відповідний підрядок", - "de-ch": "Maus zieht", - "fr-fr": "Mettre en surbrillance la sous-chaîne correspondante", - "pt-br": "Arrastos do mouse", + 'en-us': 'Mouse drags', + 'ru-ru': 'Перетаскивание мышью', + 'es-es': 'El ratón arrastra', + 'uk-ua': 'Виділіть відповідний підрядок', + 'de-ch': 'Maus zieht', + 'fr-fr': 'Mettre en surbrillance la sous-chaîne correspondante', + 'pt-br': 'Arrastos do mouse', }, scrollWheelZoom: { - "en-us": "Scroll wheel zoom", - "ru-ru": "Колесо прокрутки масштабирует", - "es-es": "Zoom con rueda de desplazamiento", - "fr-fr": "Zoom avec la molette de défilement", - "uk-ua": "Масштаб колеса прокрутки", - "de-ch": "Scrollrad-Zoom", - "pt-br": "Zoom da roda de rolagem", + 'en-us': 'Scroll wheel zoom', + 'ru-ru': 'Колесо прокрутки масштабирует', + 'es-es': 'Zoom con rueda de desplazamiento', + 'fr-fr': 'Zoom avec la molette de défilement', + 'uk-ua': 'Масштаб колеса прокрутки', + 'de-ch': 'Scrollrad-Zoom', + 'pt-br': 'Zoom da roda de rolagem', }, flexibleColumnWidth: { - "en-us": "Flexible column width", - "ru-ru": "Гибкая ширина столбца", - "es-es": "Ancho de columna flexible", - "fr-fr": "Largeur de colonne flexible", - "uk-ua": "Гнучка ширина колонки", - "de-ch": "Flexible Spaltenbreite", - "pt-br": "Largura de coluna flexível", + 'en-us': 'Flexible column width', + 'ru-ru': 'Гибкая ширина столбца', + 'es-es': 'Ancho de columna flexible', + 'fr-fr': 'Largeur de colonne flexible', + 'uk-ua': 'Гнучка ширина колонки', + 'de-ch': 'Flexible Spaltenbreite', + 'pt-br': 'Largura de coluna flexível', }, flexibleSubGridColumnWidth: { - "en-us": "Flexible subview grid column width", - "ru-ru": "Гибкая ширина столбца сетки подвидов", - "es-es": "Ancho de columna de cuadrícula de subvista flexible", - "fr-fr": "Largeur de colonne de la grille de sous-vue flexible", - "uk-ua": "Гнучка ширина стовпця сітки вкладеного перегляду", - "de-ch": "Flexible Spaltenbreite des Unteransichtsrasters", - "pt-br": "Largura flexível da coluna da grade de subvisualização", + 'en-us': 'Flexible subview grid column width', + 'ru-ru': 'Гибкая ширина столбца сетки подвидов', + 'es-es': 'Ancho de columna de cuadrícula de subvista flexible', + 'fr-fr': 'Largeur de colonne de la grille de sous-vue flexible', + 'uk-ua': 'Гнучка ширина стовпця сітки вкладеного перегляду', + 'de-ch': 'Flexible Spaltenbreite des Unteransichtsrasters', + 'pt-br': 'Largura flexível da coluna da grade de subvisualização', }, closeOnEsc: { - "en-us": "Close on ESC key press", - "ru-ru": "Закрыть при нажатии клавиши ESC", - "es-es": "Cerrar al presionar la tecla ESC", - "fr-fr": "Icône et nom de la table", - "uk-ua": "Закриття натисканням клавіші ESC", - "de-ch": "Schließen durch Drücken der Taste ESC", - "pt-br": "Fechar ao pressionar a tecla ESC", + 'en-us': 'Close on ESC key press', + 'ru-ru': 'Закрыть при нажатии клавиши ESC', + 'es-es': 'Cerrar al presionar la tecla ESC', + 'fr-fr': 'Icône et nom de la table', + 'uk-ua': 'Закриття натисканням клавіші ESC', + 'de-ch': 'Schließen durch Drücken der Taste ESC', + 'pt-br': 'Fechar ao pressionar a tecla ESC', }, closeOnOutsideClick: { - "en-us": "Close on outside click", - "ru-ru": "Закрыть по внешнему щелчку", - "es-es": "Cerrar al hacer clic desde fuera", - "fr-fr": "Fermer sur clic extérieur", - "uk-ua": "Закрийте зовнішнім клацанням", - "de-ch": "Durch Klicken von außen schließen", - "pt-br": "Fechar com clique externo", + 'en-us': 'Close on outside click', + 'ru-ru': 'Закрыть по внешнему щелчку', + 'es-es': 'Cerrar al hacer clic desde fuera', + 'fr-fr': 'Fermer sur clic extérieur', + 'uk-ua': 'Закрийте зовнішнім клацанням', + 'de-ch': 'Durch Klicken von außen schließen', + 'pt-br': 'Fechar com clique externo', }, specifyNetworkBadge: { - "en-us": "Specify Network Badge", - "ru-ru": "Укажите сетевой значок", - "es-es": "Especificar la insignia de red", - "fr-fr": "Spécifier le badge réseau", - "uk-ua": "Укажіть значок мережі", - "de-ch": "Netzwerk-Badge angeben", - "pt-br": "Especificar emblema de rede", + 'en-us': 'Specify Network Badge', + 'ru-ru': 'Укажите сетевой значок', + 'es-es': 'Especificar la insignia de red', + 'fr-fr': 'Spécifier le badge réseau', + 'uk-ua': 'Укажіть значок мережі', + 'de-ch': 'Netzwerk-Badge angeben', + 'pt-br': 'Especificar emblema de rede', }, useAccessibleFullDatePicker: { - "en-us": "Use accessible full date picker", - "ru-ru": "Используйте доступный полный выбор даты", - "es-es": "Utilice el selector de fecha completo y accesible", - "fr-fr": "Utiliser un sélecteur de date complet accessible", - "uk-ua": "Використовуйте доступний повний засіб вибору дати", - "de-ch": "Verwenden Sie eine barrierefreie Datumsauswahl", - "pt-br": "Use o seletor de data completo acessível", + 'en-us': 'Use accessible full date picker', + 'ru-ru': 'Используйте доступный полный выбор даты', + 'es-es': 'Utilice el selector de fecha completo y accesible', + 'fr-fr': 'Utiliser un sélecteur de date complet accessible', + 'uk-ua': 'Використовуйте доступний повний засіб вибору дати', + 'de-ch': 'Verwenden Sie eine barrierefreie Datumsauswahl', + 'pt-br': 'Use o seletor de data completo acessível', }, useAccessibleMonthPicker: { - "en-us": "Use accessible month picker", - "ru-ru": "Используйте доступный выбор месяца", - "es-es": "Utilice el selector de meses accesible", - "fr-fr": "Utiliser le sélecteur de mois accessible", - "uk-ua": "Використовуйте доступний засіб вибору місяця", - "de-ch": "Verwenden Sie die barrierefreie Monatsauswahl", - "pt-br": "Use o seletor de meses acessível", + 'en-us': 'Use accessible month picker', + 'ru-ru': 'Используйте доступный выбор месяца', + 'es-es': 'Utilice el selector de meses accesible', + 'fr-fr': 'Utiliser le sélecteur de mois accessible', + 'uk-ua': 'Використовуйте доступний засіб вибору місяця', + 'de-ch': 'Verwenden Sie die barrierefreie Monatsauswahl', + 'pt-br': 'Use o seletor de meses acessível', }, rightAlignNumberFields: { - "en-us": "Right-Justify numeric fields", - "ru-ru": "Числовые поля с выравниванием по правому краю", - "es-es": "Justificar a la derecha los campos numéricos", - "fr-fr": "Justifier à droite les champs numériques", - "uk-ua": "Вирівнювання по правому краю числових полів", - "de-ch": "Rechtsbündiges Ausrichten numerischer Felder", - "pt-br": "Justificar à direita campos numéricos", + 'en-us': 'Right-Justify numeric fields', + 'ru-ru': 'Числовые поля с выравниванием по правому краю', + 'es-es': 'Justificar a la derecha los campos numéricos', + 'fr-fr': 'Justifier à droite les champs numériques', + 'uk-ua': 'Вирівнювання по правому краю числових полів', + 'de-ch': 'Rechtsbündiges Ausrichten numerischer Felder', + 'pt-br': 'Justificar à direita campos numéricos', }, roundedCorners: { - "en-us": "Rounded corners", - "ru-ru": "Закругленные углы", - "es-es": "esquinas redondeadas", - "fr-fr": "Coins arrondis", - "uk-ua": "Заокруглені кути", - "de-ch": "Abgerundete Ecken", - "pt-br": "Cantos arredondados", + 'en-us': 'Rounded corners', + 'ru-ru': 'Закругленные углы', + 'es-es': 'esquinas redondeadas', + 'fr-fr': 'Coins arrondis', + 'uk-ua': 'Заокруглені кути', + 'de-ch': 'Abgerundete Ecken', + 'pt-br': 'Cantos arredondados', }, limitMaxFieldWidth: { - "en-us": "Limit max field width", - "ru-ru": "Ограничить максимальную ширину поля", - "es-es": "Limitar el ancho máximo del campo", - "fr-fr": "Limiter la largeur maximale du champ", - "uk-ua": "Обмеження максимальної ширини поля", - "de-ch": "Maximale Feldbreite begrenzen", - "pt-br": "Limite máximo de largura do campo", + 'en-us': 'Limit max field width', + 'ru-ru': 'Ограничить максимальную ширину поля', + 'es-es': 'Limitar el ancho máximo del campo', + 'fr-fr': 'Limiter la largeur maximale du champ', + 'uk-ua': 'Обмеження максимальної ширини поля', + 'de-ch': 'Maximale Feldbreite begrenzen', + 'pt-br': 'Limite máximo de largura do campo', }, condenseQueryResults: { - "en-us": "Condense query results", - "ru-ru": "Сжать результаты запроса", - "es-es": "Condensar los resultados de la consulta", - "fr-fr": "Condenser les résultats de la requête", - "uk-ua": "Згорнути результати запиту", - "de-ch": "Abfrageergebnisse verdichten", - "pt-br": "Condensar resultados da consulta", + 'en-us': 'Condense query results', + 'ru-ru': 'Сжать результаты запроса', + 'es-es': 'Condensar los resultados de la consulta', + 'fr-fr': 'Condenser les résultats de la requête', + 'uk-ua': 'Згорнути результати запиту', + 'de-ch': 'Abfrageergebnisse verdichten', + 'pt-br': 'Condensar resultados da consulta', }, blurContentBehindDialog: { - "en-us": "Blur content behind the dialog", - "ru-ru": "Размытие содержимого за диалогом", - "es-es": "Desenfocar el contenido detrás del diálogo", - "fr-fr": "Flou le contenu derrière la boîte de dialogue", - "uk-ua": "Розмити вміст за діалоговим вікном", - "de-ch": "Inhalte hinter dem Dialog verwischen", - "pt-br": "Desfocar o conteúdo atrás do diálogo", + 'en-us': 'Blur content behind the dialog', + 'ru-ru': 'Размытие содержимого за диалогом', + 'es-es': 'Desenfocar el contenido detrás del diálogo', + 'fr-fr': 'Flou le contenu derrière la boîte de dialogue', + 'uk-ua': 'Розмити вміст за діалоговим вікном', + 'de-ch': 'Inhalte hinter dem Dialog verwischen', + 'pt-br': 'Desfocar o conteúdo atrás do diálogo', }, collectionSortOrderDescription: { - "en-us": "This determines the visual order of collections.", - "ru-ru": "Это определяет визуальный порядок коллекций.", - "es-es": "Esto determina el orden visual de las colecciones.", - "fr-fr": "Ceci détermine l'ordre visuel des collections.", - "uk-ua": "Це визначає візуальний порядок колекцій.", - "de-ch": "Dies bestimmt die visuelle Reihenfolge der Sammlungen.", - "pt-br": "Isso determina a ordem visual das coleções.", + 'en-us': 'This determines the visual order of collections.', + 'ru-ru': 'Это определяет визуальный порядок коллекций.', + 'es-es': 'Esto determina el orden visual de las colecciones.', + 'fr-fr': "Ceci détermine l'ordre visuel des collections.", + 'uk-ua': 'Це визначає візуальний порядок колекцій.', + 'de-ch': 'Dies bestimmt die visuelle Reihenfolge der Sammlungen.', + 'pt-br': 'Isso determina a ordem visual das coleções.', }, recordSetRecordToOpen: { - "en-us": "Record to open by default", - "ru-ru": "Запись для открытия по умолчанию", - "es-es": "Registro para abrir por defecto", - "fr-fr": "Enregistrement à ouvrir par défaut", - "uk-ua": "Запис відкривається за умовчанням", - "de-ch": "Standardmäßig zu öffnender Datensatz", - "pt-br": "Gravar para abrir por padrão", + 'en-us': 'Record to open by default', + 'ru-ru': 'Запись для открытия по умолчанию', + 'es-es': 'Registro para abrir por defecto', + 'fr-fr': 'Enregistrement à ouvrir par défaut', + 'uk-ua': 'Запис відкривається за умовчанням', + 'de-ch': 'Standardmäßig zu öffnender Datensatz', + 'pt-br': 'Gravar para abrir por padrão', }, altClickToSupressNewTab: { - "en-us": - "{altKeyName:string}+Click to suppress new tab", - "ru-ru": - "{altKeyName:string}+Нажмите, чтобы скрыть новую вкладку", - "es-es": - "{altKeyName:string}+Haga clic en para suprimir la nueva pestaña", - "fr-fr": - "{altKeyName:string}+Cliquez sur pour supprimer le nouvel onglet", - "uk-ua": - "{altKeyName:string}+Натисніть , щоб закрити нову вкладку", - "de-ch": - "{altKeyName:string}+Klicken Sie auf, um neue Registerkarten zu unterdrücken", - "pt-br": - "{altKeyName:string}+Clique em para suprimir a nova guia", + 'en-us': + '{altKeyName:string}+Click to suppress new tab', + 'ru-ru': + '{altKeyName:string}+Нажмите, чтобы скрыть новую вкладку', + 'es-es': + '{altKeyName:string}+Haga clic en para suprimir la nueva pestaña', + 'fr-fr': + '{altKeyName:string}+Cliquez sur pour supprimer le nouvel onglet', + 'uk-ua': + '{altKeyName:string}+Натисніть , щоб закрити нову вкладку', + 'de-ch': + '{altKeyName:string}+Klicken Sie auf, um neue Registerkarten zu unterdrücken', + 'pt-br': + '{altKeyName:string}+Clique em para suprimir a nova guia', }, altClickToSupressNewTabDescription: { - "en-us": - "{altKeyName:string}+Click a link that usually opens in a new tab to open it in the current tab.", - "ru-ru": - "{altKeyName:string}+Нажмите на ссылку, которая обычно открывается в новой вкладке, чтобы открыть ее в текущей вкладке.", - "es-es": - "{altKeyName:string}+Haga clic en un enlace que normalmente se abre en una nueva pestaña para abrirlo en la pestaña actual.", - "fr-fr": "Utiliser le sélecteur de mois accessible.", - "uk-ua": - "{altKeyName:string}+Натисніть посилання, яке зазвичай відкривається в новій вкладці, щоб відкрити його в поточній вкладці.", - "de-ch": - "{altKeyName:string}+Klicken Sie auf einen Link, der normalerweise in einem neuen Tab geöffnet wird, um ihn im aktuellen Tab zu öffnen.", - "pt-br": - "{altKeyName:string}+Clique em um link que geralmente abre em uma nova aba para abri-lo na aba atual.", + 'en-us': + '{altKeyName:string}+Click a link that usually opens in a new tab to open it in the current tab.', + 'ru-ru': + '{altKeyName:string}+Нажмите на ссылку, которая обычно открывается в новой вкладке, чтобы открыть ее в текущей вкладке.', + 'es-es': + '{altKeyName:string}+Haga clic en un enlace que normalmente se abre en una nueva pestaña para abrirlo en la pestaña actual.', + 'fr-fr': 'Utiliser le sélecteur de mois accessible.', + 'uk-ua': + '{altKeyName:string}+Натисніть посилання, яке зазвичай відкривається в новій вкладці, щоб відкрити його в поточній вкладці.', + 'de-ch': + '{altKeyName:string}+Klicken Sie auf einen Link, der normalerweise in einem neuen Tab geöffnet wird, um ihn im aktuellen Tab zu öffnen.', + 'pt-br': + '{altKeyName:string}+Clique em um link que geralmente abre em uma nova aba para abri-lo na aba atual.', }, makeFormDialogsModal: { - "en-us": "Make form dialogs gray out the background", - "ru-ru": "Сделать фон диалоговых окон серым", - "es-es": - "Hacer que los cuadros de diálogo del formulario tengan el fondo en gris", - "fr-fr": + 'en-us': 'Make form dialogs gray out the background', + 'ru-ru': 'Сделать фон диалоговых окон серым', + 'es-es': + 'Hacer que los cuadros de diálogo del formulario tengan el fondo en gris', + 'fr-fr': "Rendre les boîtes de dialogue de formulaire grisées sur l'arrière-plan", - "uk-ua": "Зробіть діалогові вікна форми сірими фоном", - "de-ch": "Den Hintergrund von Formulardialogen ausgrauen", - "pt-br": - "Faça com que as caixas de diálogo do formulário fiquem com o fundo acinzentado", + 'uk-ua': 'Зробіть діалогові вікна форми сірими фоном', + 'de-ch': 'Den Hintergrund von Formulardialogen ausgrauen', + 'pt-br': + 'Faça com que as caixas de diálogo do formulário fiquem com o fundo acinzentado', }, autoScrollTree: { - "en-us": "Auto scroll tree to focused node", - "ru-ru": "Автоматическая прокрутка дерева до выбранного узла", - "es-es": "Desplazamiento automático del árbol al nodo enfocado", - "fr-fr": "Arbre de défilement automatique vers le nœud ciblé", - "uk-ua": "Автоматичне прокручування дерева до виділеного вузла", - "de-ch": "Automatisches Scrollen des Baums zum fokussierten Knoten", - "pt-br": "Rolagem automática da árvore para o nó em foco", + 'en-us': 'Auto scroll tree to focused node', + 'ru-ru': 'Автоматическая прокрутка дерева до выбранного узла', + 'es-es': 'Desplazamiento automático del árbol al nodo enfocado', + 'fr-fr': 'Arbre de défilement automatique vers le nœud ciblé', + 'uk-ua': 'Автоматичне прокручування дерева до виділеного вузла', + 'de-ch': 'Automatisches Scrollen des Baums zum fokussierten Knoten', + 'pt-br': 'Rolagem automática da árvore para o nó em foco', }, sortByField: { - "en-us": "Order By Field", - "de-ch": "Nach Feld sortieren", - "es-es": "Ordenar por campo", - "fr-fr": "Trier par champ", - "pt-br": "Ordenar por campo", - "ru-ru": "Сортировать по полю", - "uk-ua": "Сортувати за полем", + 'en-us': 'Order By Field', + 'de-ch': 'Nach Feld sortieren', + 'es-es': 'Ordenar por campo', + 'fr-fr': 'Trier par champ', + 'pt-br': 'Ordenar por campo', + 'ru-ru': 'Сортировать по полю', + 'uk-ua': 'Сортувати за полем', }, lineWrap: { - "en-us": "Line wrap", - "ru-ru": "Перенос строки", - "es-es": "Ajuste de línea", - "fr-fr": "Retour à la ligne", - "uk-ua": "Обтікання лініями", - "de-ch": "Zeilenumbruch", - "pt-br": "Quebra de linha", + 'en-us': 'Line wrap', + 'ru-ru': 'Перенос строки', + 'es-es': 'Ajuste de línea', + 'fr-fr': 'Retour à la ligne', + 'uk-ua': 'Обтікання лініями', + 'de-ch': 'Zeilenumbruch', + 'pt-br': 'Quebra de linha', }, indentSize: { - "en-us": "Indent size", - "ru-ru": "Размер отступа", - "es-es": "Tamaño de sangría", - "fr-fr": "Taille du retrait", - "uk-ua": "Розмір відступу", - "de-ch": "Einzugsgröße", - "pt-br": "Tamanho do recuo", + 'en-us': 'Indent size', + 'ru-ru': 'Размер отступа', + 'es-es': 'Tamaño de sangría', + 'fr-fr': 'Taille du retrait', + 'uk-ua': 'Розмір відступу', + 'de-ch': 'Einzugsgröße', + 'pt-br': 'Tamanho do recuo', }, indentWithTab: { - "en-us": "Indent with Tab", - "ru-ru": "Отступ с Tab", - "es-es": "Sangría con Tab", - "fr-fr": "Indenter avec Tabulation", - "uk-ua": "Відступ із Tab", - "de-ch": "Einrücken mit Tabulator", - "pt-br": "Recuo com Tab", + 'en-us': 'Indent with Tab', + 'ru-ru': 'Отступ с Tab', + 'es-es': 'Sangría con Tab', + 'fr-fr': 'Indenter avec Tabulation', + 'uk-ua': 'Відступ із Tab', + 'de-ch': 'Einrücken mit Tabulator', + 'pt-br': 'Recuo com Tab', }, formHeaderFormat: { - "en-us": "Form header format", - "ru-ru": "Формат заголовка формы", - "es-es": "Formato del encabezado del formulario", - "fr-fr": "Format d'en-tête de formulaire", - "uk-ua": "Формат заголовка форми", - "de-ch": "Formularkopfzeilenformat", - "pt-br": "Formato do cabeçalho do formulário", + 'en-us': 'Form header format', + 'ru-ru': 'Формат заголовка формы', + 'es-es': 'Formato del encabezado del formulario', + 'fr-fr': "Format d'en-tête de formulaire", + 'uk-ua': 'Формат заголовка форми', + 'de-ch': 'Formularkopfzeilenformat', + 'pt-br': 'Formato do cabeçalho do formulário', }, iconAndTableName: { - "en-us": "Icon and table name", - "ru-ru": "Значок и название таблицы", - "es-es": "Icono y nombre de la tabla", - "fr-fr": "Icône et nom de la table", - "uk-ua": "Значок і назва таблиці", - "de-ch": "Symbol und Tabellenname", - "pt-br": "Ícone e nome da tabela", + 'en-us': 'Icon and table name', + 'ru-ru': 'Значок и название таблицы', + 'es-es': 'Icono y nombre de la tabla', + 'fr-fr': 'Icône et nom de la table', + 'uk-ua': 'Значок і назва таблиці', + 'de-ch': 'Symbol und Tabellenname', + 'pt-br': 'Ícone e nome da tabela', }, tableIcon: { - "en-us": "Table icon", - "ru-ru": "Значок таблицы", - "es-es": "Icono de tabla", - "fr-fr": "Icône de tableau", - "uk-ua": "Значок таблиці", - "de-ch": "Tabellensymbol", - "pt-br": "Ícone de tabela", + 'en-us': 'Table icon', + 'ru-ru': 'Значок таблицы', + 'es-es': 'Icono de tabla', + 'fr-fr': 'Icône de tableau', + 'uk-ua': 'Значок таблиці', + 'de-ch': 'Tabellensymbol', + 'pt-br': 'Ícone de tabela', }, maxHeight: { - "en-us": "Max height", - "ru-ru": "Максимальная высота", - "es-es": "Altura máxima", - "fr-fr": "hauteur maximum", - "uk-ua": "Максимальна висота", - "de-ch": "Maximale Höhe", - "pt-br": "Altura máxima", + 'en-us': 'Max height', + 'ru-ru': 'Максимальная высота', + 'es-es': 'Altura máxima', + 'fr-fr': 'hauteur maximum', + 'uk-ua': 'Максимальна висота', + 'de-ch': 'Maximale Höhe', + 'pt-br': 'Altura máxima', }, autoComplete: { - "en-us": "Auto complete", - "ru-ru": "Автозаполнение", - "es-es": "Autocompletar", - "fr-fr": + 'en-us': 'Auto complete', + 'ru-ru': 'Автозаполнение', + 'es-es': 'Autocompletar', + 'fr-fr': "Détermine les légendes des champs, les notes d'utilisation et les légendes des tableaux", - "uk-ua": - "Визначає підписи полів, примітки щодо використання та підписи таблиць", - "de-ch": "Autovervollständigung", - "pt-br": "Preenchimento automático", + 'uk-ua': + 'Визначає підписи полів, примітки щодо використання та підписи таблиць', + 'de-ch': 'Autovervollständigung', + 'pt-br': 'Preenchimento automático', }, searchCaseSensitive: { - "en-us": "Case-sensitive search", - "es-es": "Búsqueda que distingue entre mayúsculas y minúsculas", - "fr-fr": "Recherche sensible à la casse", - "uk-ua": "Пошук з урахуванням регістру", - "de-ch": "Groß- und Kleinschreibung beachten", - "ru-ru": "Поиск с учетом регистра", - "pt-br": "Pesquisa com diferenciação entre maiúsculas e minúsculas", + 'en-us': 'Case-sensitive search', + 'es-es': 'Búsqueda que distingue entre mayúsculas y minúsculas', + 'fr-fr': 'Recherche sensible à la casse', + 'uk-ua': 'Пошук з урахуванням регістру', + 'de-ch': 'Groß- und Kleinschreibung beachten', + 'ru-ru': 'Поиск с учетом регистра', + 'pt-br': 'Pesquisa com diferenciação entre maiúsculas e minúsculas', }, searchField: { - "en-us": "Search Field", - "ru-ru": "Поле поиска", - "es-es": "Campo de búsqueda", - "fr-fr": "Champ de recherche", - "uk-ua": "Поле пошуку", - "de-ch": "Suchfeld", - "pt-br": "Campo de pesquisa", + 'en-us': 'Search Field', + 'ru-ru': 'Поле поиска', + 'es-es': 'Campo de búsqueda', + 'fr-fr': 'Champ de recherche', + 'uk-ua': 'Поле пошуку', + 'de-ch': 'Suchfeld', + 'pt-br': 'Campo de pesquisa', }, createInteractions: { - "en-us": "Creating an interaction", - "ru-ru": "Создание взаимодействия", - "es-es": "Creando una interacción", - "fr-fr": "Créer une interaction", - "uk-ua": "Створення взаємодії", - "de-ch": "Erstellen einer Interaktion", - "pt-br": "Criando uma interação", + 'en-us': 'Creating an interaction', + 'ru-ru': 'Создание взаимодействия', + 'es-es': 'Creando una interacción', + 'fr-fr': 'Créer une interaction', + 'uk-ua': 'Створення взаємодії', + 'de-ch': 'Erstellen einer Interaktion', + 'pt-br': 'Criando uma interação', }, useSpaceAsDelimiter: { - "en-us": "Use space as delimiter", - "ru-ru": "Используйте пробел в качестве разделителя", - "es-es": "Utilice el espacio como delimitador", - "fr-fr": "Utiliser l'espace comme délimiteur", - "uk-ua": "Використовуйте пробіл як роздільник", - "de-ch": "Leerzeichen als Trennzeichen verwenden", - "pt-br": "Use espaço como delimitador", + 'en-us': 'Use space as delimiter', + 'ru-ru': 'Используйте пробел в качестве разделителя', + 'es-es': 'Utilice el espacio como delimitador', + 'fr-fr': "Utiliser l'espace comme délimiteur", + 'uk-ua': 'Використовуйте пробіл як роздільник', + 'de-ch': 'Leerzeichen als Trennzeichen verwenden', + 'pt-br': 'Use espaço como delimitador', }, useCommaAsDelimiter: { - "en-us": "Use comma as delimiter", - "ru-ru": "Используйте запятую в качестве разделителя", - "es-es": "Utilice la coma como delimitador", - "fr-fr": "Utiliser la virgule comme délimiteur", - "uk-ua": "Використовуйте кому як роздільник", - "de-ch": "Verwenden Sie Kommas als Trennzeichen", - "pt-br": "Use vírgula como delimitador", + 'en-us': 'Use comma as delimiter', + 'ru-ru': 'Используйте запятую в качестве разделителя', + 'es-es': 'Utilice la coma como delimitador', + 'fr-fr': 'Utiliser la virgule comme délimiteur', + 'uk-ua': 'Використовуйте кому як роздільник', + 'de-ch': 'Verwenden Sie Kommas als Trennzeichen', + 'pt-br': 'Use vírgula como delimitador', }, useNewLineAsDelimiter: { - "en-us": "Use new line as delimiter", - "ru-ru": "Использовать новую строку в качестве разделителя", - "es-es": "Utilice nueva línea como delimitador", - "fr-fr": "Utiliser une nouvelle ligne comme délimiteur", - "uk-ua": "Використовуйте новий рядок як роздільник", - "de-ch": "Neue Zeile als Trennzeichen verwenden", - "pt-br": "Use nova linha como delimitador", + 'en-us': 'Use new line as delimiter', + 'ru-ru': 'Использовать новую строку в качестве разделителя', + 'es-es': 'Utilice nueva línea como delimitador', + 'fr-fr': 'Utiliser une nouvelle ligne comme délimiteur', + 'uk-ua': 'Використовуйте новий рядок як роздільник', + 'de-ch': 'Neue Zeile als Trennzeichen verwenden', + 'pt-br': 'Use nova linha como delimitador', }, useCustomDelimiters: { - "en-us": "Use custom delimiters", - "ru-ru": "Используйте пользовательские разделители", - "es-es": "Utilice delimitadores personalizados", - "fr-fr": "Utiliser des délimiteurs personnalisés", - "uk-ua": "Використовуйте спеціальні роздільники", - "de-ch": "Benutzerdefinierte Trennzeichen verwenden", - "pt-br": "Use delimitadores personalizados", + 'en-us': 'Use custom delimiters', + 'ru-ru': 'Используйте пользовательские разделители', + 'es-es': 'Utilice delimitadores personalizados', + 'fr-fr': 'Utiliser des délimiteurs personnalisés', + 'uk-ua': 'Використовуйте спеціальні роздільники', + 'de-ch': 'Benutzerdefinierte Trennzeichen verwenden', + 'pt-br': 'Use delimitadores personalizados', }, useCustomDelimitersDescription: { - "en-us": - "A list of delimiters to use, in addition to the ones defined above. Put one delimiter per line.", - "ru-ru": - "Список разделителей для использования, в дополнение к тем, которые определены выше. Поставьте один разделитель на строку.", - "es-es": - "Una lista de delimitadores para usar, además de los definidos anteriormente. Coloque un delimitador por línea.", - "fr-fr": - "Une liste de délimiteurs à utiliser, en plus de ceux définis ci-dessus. Mettez un délimiteur par ligne.", - "uk-ua": - "Список розділювачів для використання на додаток до визначених вище. Поставте один роздільник на рядок.", - "de-ch": - "Eine Liste der zu verwendenden Trennzeichen zusätzlich zu den oben definierten. Geben Sie pro Zeile ein Trennzeichen ein.", - "pt-br": - "Uma lista de delimitadores a serem usados, além dos definidos acima. Coloque um delimitador por linha.", + 'en-us': + 'A list of delimiters to use, in addition to the ones defined above. Put one delimiter per line.', + 'ru-ru': + 'Список разделителей для использования, в дополнение к тем, которые определены выше. Поставьте один разделитель на строку.', + 'es-es': + 'Una lista de delimitadores para usar, además de los definidos anteriormente. Coloque un delimitador por línea.', + 'fr-fr': + 'Une liste de délimiteurs à utiliser, en plus de ceux définis ci-dessus. Mettez un délimiteur par ligne.', + 'uk-ua': + 'Список розділювачів для використання на додаток до визначених вище. Поставте один роздільник на рядок.', + 'de-ch': + 'Eine Liste der zu verwendenden Trennzeichen zusätzlich zu den oben definierten. Geben Sie pro Zeile ein Trennzeichen ein.', + 'pt-br': + 'Uma lista de delimitadores a serem usados, além dos definidos acima. Coloque um delimitador por linha.', }, detectAutomaticallyDescription: { - "en-us": "Detect automatically based on catalog number format.", - "ru-ru": "Автоматическое определение на основе формата каталожного номера.", - "es-es": - "Detectar automáticamente según el formato del número de catálogo.", - "fr-fr": - "Détecter automatiquement en fonction du format du numéro de catalogue.", - "uk-ua": "Визначати автоматично на основі формату номера каталогу.", - "de-ch": "Automatische Erkennung basierend auf dem Katalognummernformat.", - "pt-br": - "Detecte automaticamente com base no formato do número de catálogo.", + 'en-us': 'Detect automatically based on catalog number format.', + 'ru-ru': 'Автоматическое определение на основе формата каталожного номера.', + 'es-es': + 'Detectar automáticamente según el formato del número de catálogo.', + 'fr-fr': + 'Détecter automatiquement en fonction du format du numéro de catalogue.', + 'uk-ua': 'Визначати автоматично на основі формату номера каталогу.', + 'de-ch': 'Automatische Erkennung basierend auf dem Katalognummernformat.', + 'pt-br': + 'Detecte automaticamente com base no formato do número de catálogo.', }, use: { - comment: "Verb", - "en-us": "Use", - "ru-ru": "Использовать", - "es-es": "Usar", - "fr-fr": "Utiliser", - "uk-ua": "використання", - "de-ch": "Verwenden", - "pt-br": "Usar", + comment: 'Verb', + 'en-us': 'Use', + 'ru-ru': 'Использовать', + 'es-es': 'Usar', + 'fr-fr': 'Utiliser', + 'uk-ua': 'використання', + 'de-ch': 'Verwenden', + 'pt-br': 'Usar', }, dontUse: { - "en-us": "Don’t use", - "ru-ru": "Не использовать", - "es-es": "No utilizar", - "fr-fr": "Zoom avec la molette de défilement", - "uk-ua": "Масштаб колеса прокрутки", - "de-ch": "Nicht verwenden", - "pt-br": "Não use", + 'en-us': 'Don’t use', + 'ru-ru': 'Не использовать', + 'es-es': 'No utilizar', + 'fr-fr': 'Zoom avec la molette de défilement', + 'uk-ua': 'Масштаб колеса прокрутки', + 'de-ch': 'Nicht verwenden', + 'pt-br': 'Não use', }, position: { - "en-us": "Position", - "es-es": "Posición", - "fr-fr": "Position", - "ru-ru": "Позиция", - "uk-ua": "Позиція", - "de-ch": "Position", - "pt-br": "Posição", + 'en-us': 'Position', + 'es-es': 'Posición', + 'fr-fr': 'Position', + 'ru-ru': 'Позиция', + 'uk-ua': 'Позиція', + 'de-ch': 'Position', + 'pt-br': 'Posição', }, top: { - "en-us": "Top", - "es-es": "Arriba", - "fr-fr": "Haut", - "ru-ru": "Вершина", - "uk-ua": "Топ", - "de-ch": "Spitze", - "pt-br": "Principal", + 'en-us': 'Top', + 'es-es': 'Arriba', + 'fr-fr': 'Haut', + 'ru-ru': 'Вершина', + 'uk-ua': 'Топ', + 'de-ch': 'Spitze', + 'pt-br': 'Principal', }, bottom: { - "en-us": "Bottom", - "es-es": "Abajo", - "ru-ru": "Нижний", - "uk-ua": "Дно", - "de-ch": "Unten", - "fr-fr": "Bas", - "pt-br": "Fundo", + 'en-us': 'Bottom', + 'es-es': 'Abajo', + 'ru-ru': 'Нижний', + 'uk-ua': 'Дно', + 'de-ch': 'Unten', + 'fr-fr': 'Bas', + 'pt-br': 'Fundo', }, left: { - "en-us": "Left", - "es-es": "Izquierda", - "fr-fr": "Gauche", - "ru-ru": "Левый", - "uk-ua": "Ліворуч", - "de-ch": "Links", - "pt-br": "Esquerda", + 'en-us': 'Left', + 'es-es': 'Izquierda', + 'fr-fr': 'Gauche', + 'ru-ru': 'Левый', + 'uk-ua': 'Ліворуч', + 'de-ch': 'Links', + 'pt-br': 'Esquerda', }, right: { - "en-us": "Right", - "es-es": "Bien", - "fr-fr": "Droite", - "ru-ru": "Верно", - "uk-ua": "правильно", - "de-ch": "Rechts", - "pt-br": "Certo", + 'en-us': 'Right', + 'es-es': 'Bien', + 'fr-fr': 'Droite', + 'ru-ru': 'Верно', + 'uk-ua': 'правильно', + 'de-ch': 'Rechts', + 'pt-br': 'Certo', }, showUnsavedIndicator: { - "en-us": "Show unsaved changes indicator", - "ru-ru": "Показать индикатор несохраненных изменений", - "es-es": "Mostrar indicador de cambios no guardados", - "fr-fr": "Afficher l'indicateur de modifications non enregistrées", - "uk-ua": "Показати індикатор незбережених змін", - "de-ch": "Indikator für nicht gespeicherte Änderungen anzeigen", - "pt-br": "Mostrar indicador de alterações não salvas", + 'en-us': 'Show unsaved changes indicator', + 'ru-ru': 'Показать индикатор несохраненных изменений', + 'es-es': 'Mostrar indicador de cambios no guardados', + 'fr-fr': "Afficher l'indicateur de modifications non enregistrées", + 'uk-ua': 'Показати індикатор незбережених змін', + 'de-ch': 'Indikator für nicht gespeicherte Änderungen anzeigen', + 'pt-br': 'Mostrar indicador de alterações não salvas', }, showUnsavedIndicatorDescription: { - "en-us": + 'en-us': 'Show an "*" in the tab title when there are unsaved changes in the current tab.', - "es-es": + 'es-es': 'Mostrar un "*" en el título de la pestaña cuando haya cambios sin guardar en la pestaña actual.', - "fr-fr": + 'fr-fr': "Afficher un \"*\" dans le titre de l'onglet lorsqu'il y a des modifications non enregistrées dans l'onglet actuel.", - "ru-ru": - "Показывать «*» в заголовке вкладки, если на текущей вкладке есть несохраненные изменения.", - "uk-ua": - "Показувати «*» у заголовку вкладки, якщо в поточній вкладці є незбережені зміни.", - "de-ch": - "Zeigen Sie im Registerkartentitel ein „*“ an, wenn in der aktuellen Registerkarte nicht gespeicherte Änderungen vorhanden sind.", - "pt-br": + 'ru-ru': + 'Показывать «*» в заголовке вкладки, если на текущей вкладке есть несохраненные изменения.', + 'uk-ua': + 'Показувати «*» у заголовку вкладки, якщо в поточній вкладці є незбережені зміни.', + 'de-ch': + 'Zeigen Sie im Registerkartentitel ein „*“ an, wenn in der aktuellen Registerkarte nicht gespeicherte Änderungen vorhanden sind.', + 'pt-br': 'Exibir um "*" no título da aba quando houver alterações não salvas na aba atual.', }, autoPopulateDescription: { - "en-us": - "Auto populate the merged record with values from duplicates when opening the merging dialog.", - "ru-ru": - "Автоматически заполнять объединенную запись значениями из дубликатов при открытии диалогового окна объединения.", - "de-ch": - "Füllen Sie den zusammengeführten Datensatz beim Öffnen des Zusammenführungsdialogs automatisch mit Werten aus Duplikaten auf.", - "es-es": - "Rellene automáticamente el registro fusionado con valores de duplicados al abrir el cuadro de diálogo de fusión.", - "fr-fr": + 'en-us': + 'Auto populate the merged record with values from duplicates when opening the merging dialog.', + 'ru-ru': + 'Автоматически заполнять объединенную запись значениями из дубликатов при открытии диалогового окна объединения.', + 'de-ch': + 'Füllen Sie den zusammengeführten Datensatz beim Öffnen des Zusammenführungsdialogs automatisch mit Werten aus Duplikaten auf.', + 'es-es': + 'Rellene automáticamente el registro fusionado con valores de duplicados al abrir el cuadro de diálogo de fusión.', + 'fr-fr': "Remplir automatiquement l'enregistrement fusionné avec les valeurs des doublons lors de l'ouverture de la boîte de dialogue de fusion.", - "uk-ua": - "Автоматичне заповнення об’єднаного запису значеннями з дублікатів під час відкриття діалогового вікна об’єднання.", - "pt-br": - "Preencha automaticamente o registro mesclado com valores de duplicatas ao abrir a caixa de diálogo de mesclagem.", + 'uk-ua': + 'Автоматичне заповнення об’єднаного запису значеннями з дублікатів під час відкриття діалогового вікна об’єднання.', + 'pt-br': + 'Preencha automaticamente o registro mesclado com valores de duplicatas ao abrir a caixa de diálogo de mesclagem.', }, autoCreateVariants: { - "en-us": "Automatically create {agentVariantTable:string} records", - "ru-ru": "Автоматически создавать записи {agentVariantTable:string}", - "de-ch": "{agentVariantTable:string}-Datensätze automatisch erstellen", - "es-es": "Crear automáticamente registros {agentVariantTable:string}", - "fr-fr": - "Créer automatiquement des enregistrements {agentVariantTable:string}", - "uk-ua": "Автоматично створювати записи {agentVariantTable:string}", - "pt-br": "Criar automaticamente registros {agentVariantTable:string}", + 'en-us': 'Automatically create {agentVariantTable:string} records', + 'ru-ru': 'Автоматически создавать записи {agentVariantTable:string}', + 'de-ch': '{agentVariantTable:string}-Datensätze automatisch erstellen', + 'es-es': 'Crear automáticamente registros {agentVariantTable:string}', + 'fr-fr': + 'Créer automatiquement des enregistrements {agentVariantTable:string}', + 'uk-ua': 'Автоматично створювати записи {agentVariantTable:string}', + 'pt-br': 'Criar automaticamente registros {agentVariantTable:string}', }, autoCreateVariantsDescription: { - "en-us": - "When merging agents, automatically create {agentVariantTable:string} records based on the variations of first name/last name.", - "ru-ru": - "При объединении агентов автоматически создаются записи {agentVariantTable:string} на основе вариаций имени/фамилии.", - "de-ch": - "Beim Zusammenführen von Agenten werden automatisch {agentVariantTable:string}-Datensätze basierend auf den Variationen von Vorname/Nachname erstellt.", - "es-es": - "Al fusionar agentes, cree automáticamente registros {agentVariantTable:string} basados en las variaciones de nombre/apellido.", - "fr-fr": + 'en-us': + 'When merging agents, automatically create {agentVariantTable:string} records based on the variations of first name/last name.', + 'ru-ru': + 'При объединении агентов автоматически создаются записи {agentVariantTable:string} на основе вариаций имени/фамилии.', + 'de-ch': + 'Beim Zusammenführen von Agenten werden automatisch {agentVariantTable:string}-Datensätze basierend auf den Variationen von Vorname/Nachname erstellt.', + 'es-es': + 'Al fusionar agentes, cree automáticamente registros {agentVariantTable:string} basados en las variaciones de nombre/apellido.', + 'fr-fr': "Lors de la fusion d'agents, créez automatiquement des enregistrements {agentVariantTable:string} en fonction des variations du prénom/nom.", - "uk-ua": - "Під час об’єднання агентів автоматично створювати записи {agentVariantTable:string} на основі варіацій імені/прізвища.", - "pt-br": - "Ao mesclar agentes, crie automaticamente registros {agentVariantTable:string} com base nas variações de nome/sobrenome.", + 'uk-ua': + 'Під час об’єднання агентів автоматично створювати записи {agentVariantTable:string} на основі варіацій імені/прізвища.', + 'pt-br': + 'Ao mesclar agentes, crie automaticamente registros {agentVariantTable:string} com base nas variações de nome/sobrenome.', }, collectionPreferences: { - "en-us": "Collection Preferences", - "de-ch": "Sammlungseinstellungen", - "es-es": "Preferencias de colección", - "fr-fr": "Personnalisation", - "ru-ru": "Настройки коллекции", - "uk-ua": "Налаштування", - "pt-br": "Preferências de coleção", + 'en-us': 'Collection Preferences', + 'de-ch': 'Sammlungseinstellungen', + 'es-es': 'Preferencias de colección', + 'fr-fr': 'Personnalisation', + 'ru-ru': 'Настройки коллекции', + 'uk-ua': 'Налаштування', + 'pt-br': 'Preferências de coleção', }, rememberDialogSizes: { - "en-us": "Remember dialog window sizes", - "ru-ru": "Запомните размеры диалоговых окон", - "es-es": "Recordar los tamaños de las ventanas de diálogo", - "fr-fr": "Mémoriser les tailles des fenêtres de dialogue", - "uk-ua": "Запам'ятайте розміри діалогових вікон", - "de-ch": "Dialogfenstergrößen merken", - "pt-br": "Lembrar tamanhos de janelas de diálogo", + 'en-us': 'Remember dialog window sizes', + 'ru-ru': 'Запомните размеры диалоговых окон', + 'es-es': 'Recordar los tamaños de las ventanas de diálogo', + 'fr-fr': 'Mémoriser les tailles des fenêtres de dialogue', + 'uk-ua': "Запам'ятайте розміри діалогових вікон", + 'de-ch': 'Dialogfenstergrößen merken', + 'pt-br': 'Lembrar tamanhos de janelas de diálogo', }, rememberDialogPositions: { - "en-us": "Remember dialog window positions", - "ru-ru": "Запомнить позиции диалоговых окон", - "es-es": "Recordar las posiciones de las ventanas de diálogo", - "fr-fr": "Mémoriser les positions des fenêtres de dialogue", - "uk-ua": "Запам'ятовуйте положення діалогового вікна", - "de-ch": "Dialogfensterpositionen merken", - "pt-br": "Lembrar posições da janela de diálogo", + 'en-us': 'Remember dialog window positions', + 'ru-ru': 'Запомнить позиции диалоговых окон', + 'es-es': 'Recordar las posiciones de las ventanas de diálogo', + 'fr-fr': 'Mémoriser les positions des fenêtres de dialogue', + 'uk-ua': "Запам'ятовуйте положення діалогового вікна", + 'de-ch': 'Dialogfensterpositionen merken', + 'pt-br': 'Lembrar posições da janela de diálogo', }, autoPlayMedia: { - "en-us": "Automatically play media", - "ru-ru": "Автоматически воспроизводить медиа", - "es-es": "Reproducir automáticamente medios", - "fr-fr": "Lire automatiquement les médias", - "uk-ua": "Автоматичне відтворення медіа", - "de-ch": "Medien automatisch abspielen", - "pt-br": "Reproduzir mídia automaticamente", + 'en-us': 'Automatically play media', + 'ru-ru': 'Автоматически воспроизводить медиа', + 'es-es': 'Reproducir automáticamente medios', + 'fr-fr': 'Lire automatiquement les médias', + 'uk-ua': 'Автоматичне відтворення медіа', + 'de-ch': 'Medien automatisch abspielen', + 'pt-br': 'Reproduzir mídia automaticamente', }, useCustomTooltips: { - "en-us": "Use modern tooltips", - "ru-ru": "Используйте современные подсказки", - "es-es": "Utilice información sobre herramientas moderna", - "fr-fr": "Utiliser des info-bulles modernes", - "uk-ua": "Використовуйте сучасні підказки", - "de-ch": "Verwenden Sie moderne Tooltips", - "pt-br": "Use dicas de ferramentas modernas", + 'en-us': 'Use modern tooltips', + 'ru-ru': 'Используйте современные подсказки', + 'es-es': 'Utilice información sobre herramientas moderna', + 'fr-fr': 'Utiliser des info-bulles modernes', + 'uk-ua': 'Використовуйте сучасні підказки', + 'de-ch': 'Verwenden Sie moderne Tooltips', + 'pt-br': 'Use dicas de ferramentas modernas', }, alwaysUseQueryBuilder: { - "en-us": "Always use query builder search inside of search form", - "de-ch": - "Verwenden Sie innerhalb des Suchformulars immer die Abfrage-Generator-Suche", - "es-es": - "Utilice siempre la búsqueda del generador de consultas dentro del formulario de búsqueda", - "fr-fr": - "Utilisez toujours la recherche du générateur de requêtes dans le formulaire de recherche", - "ru-ru": "Всегда используйте конструктор запросов внутри формы поиска", - "uk-ua": "Завжди використовуйте пошук конструктора запитів у формі пошуку", - "pt-br": - "Sempre use a pesquisa do construtor de consultas dentro do formulário de pesquisa", + 'en-us': 'Always use query builder search inside of search form', + 'de-ch': + 'Verwenden Sie innerhalb des Suchformulars immer die Abfrage-Generator-Suche', + 'es-es': + 'Utilice siempre la búsqueda del generador de consultas dentro del formulario de búsqueda', + 'fr-fr': + 'Utilisez toujours la recherche du générateur de requêtes dans le formulaire de recherche', + 'ru-ru': 'Всегда используйте конструктор запросов внутри формы поиска', + 'uk-ua': 'Завжди використовуйте пошук конструктора запитів у формі пошуку', + 'pt-br': + 'Sempre use a pesquisa do construtor de consultas dentro do formulário de pesquisa', }, localizeResourceNames: { - "en-us": "Localize the names of recognized app resources", - "de-ch": "Lokalisieren Sie die Namen erkannter App-Ressourcen", - "es-es": - "Localizar los nombres de los recursos de aplicaciones reconocidos", - "fr-fr": "Localiser les noms des ressources d'application reconnues", - "ru-ru": "Локализуйте названия распознанных ресурсов приложения", - "uk-ua": "Локалізувати назви розпізнаних ресурсів програми", - "pt-br": "Localize os nomes dos recursos de aplicativos reconhecidos", + 'en-us': 'Localize the names of recognized app resources', + 'de-ch': 'Lokalisieren Sie die Namen erkannter App-Ressourcen', + 'es-es': + 'Localizar los nombres de los recursos de aplicaciones reconocidos', + 'fr-fr': "Localiser les noms des ressources d'application reconnues", + 'ru-ru': 'Локализуйте названия распознанных ресурсов приложения', + 'uk-ua': 'Локалізувати назви розпізнаних ресурсів програми', + 'pt-br': 'Localize os nomes dos recursos de aplicativos reconhecidos', }, splitLongXml: { - "en-us": "Split long lines of XML into multiple lines", - "de-ch": "Teilen Sie lange XML-Zeilen in mehrere Zeilen auf", - "es-es": "Dividir líneas largas de XML en varias líneas", - "fr-fr": "Diviser les longues lignes de XML en plusieurs lignes", - "ru-ru": "Разделить длинные строки XML на несколько строк", - "uk-ua": "Розділіть довгі рядки XML на кілька рядків", - "pt-br": "Dividir longas linhas de XML em várias linhas", + 'en-us': 'Split long lines of XML into multiple lines', + 'de-ch': 'Teilen Sie lange XML-Zeilen in mehrere Zeilen auf', + 'es-es': 'Dividir líneas largas de XML en varias líneas', + 'fr-fr': 'Diviser les longues lignes de XML en plusieurs lignes', + 'ru-ru': 'Разделить длинные строки XML на несколько строк', + 'uk-ua': 'Розділіть довгі рядки XML на кілька рядків', + 'pt-br': 'Dividir longas linhas de XML em várias linhas', }, url: { - "en-us": "URL", - "de-ch": "URL", - "es-es": "URL", - "fr-fr": "URL", - "uk-ua": "URL", - "ru-ru": "URL", - "pt-br": "URL", + 'en-us': 'URL', + 'de-ch': 'URL', + 'es-es': 'URL', + 'fr-fr': 'URL', + 'uk-ua': 'URL', + 'ru-ru': 'URL', + 'pt-br': 'URL', }, pickAttachment: { - "en-us": "Pick an attachment", - "es-es": "Elige un archivo adjunto", - "fr-fr": "Choisissez une pièce jointe", - "ru-ru": "Выберите вложение", - "uk-ua": "Виберіть вкладення", - "de-ch": "Wählen Sie einen Anhang", - "pt-br": "Escolha um anexo", + 'en-us': 'Pick an attachment', + 'es-es': 'Elige un archivo adjunto', + 'fr-fr': 'Choisissez une pièce jointe', + 'ru-ru': 'Выберите вложение', + 'uk-ua': 'Виберіть вкладення', + 'de-ch': 'Wählen Sie einen Anhang', + 'pt-br': 'Escolha um anexo', }, attachmentFailed: { - "en-us": "The attachment failed to load.", - "de-ch": "Der Anhang konnte nicht geladen werden.", - "es-es": "No se pudo cargar el archivo adjunto.", - "fr-fr": "La pièce jointe n'a pas pu être chargée.", - "ru-ru": "Не удалось загрузить вложение.", - "uk-ua": "Не вдалося завантажити вкладений файл.", - "pt-br": "O anexo não pôde ser carregado.", + 'en-us': 'The attachment failed to load.', + 'de-ch': 'Der Anhang konnte nicht geladen werden.', + 'es-es': 'No se pudo cargar el archivo adjunto.', + 'fr-fr': "La pièce jointe n'a pas pu être chargée.", + 'ru-ru': 'Не удалось загрузить вложение.', + 'uk-ua': 'Не вдалося завантажити вкладений файл.', + 'pt-br': 'O anexo não pôde ser carregado.', }, pickImage: { - "en-us": "Pick an image", - "de-ch": "Wählen Sie ein Bild aus", - "es-es": "Elige una imagen", - "fr-fr": "Choisissez une image", - "ru-ru": "Выберите изображение", - "uk-ua": "Виберіть зображення", - "pt-br": "Escolha uma imagem", + 'en-us': 'Pick an image', + 'de-ch': 'Wählen Sie ein Bild aus', + 'es-es': 'Elige una imagen', + 'fr-fr': 'Choisissez une image', + 'ru-ru': 'Выберите изображение', + 'uk-ua': 'Виберіть зображення', + 'pt-br': 'Escolha uma imagem', }, customLogo: { - "en-us": "Expanded Image URL", - "de-ch": "Erweiterte Bild-URL", - "es-es": "URL de imagen expandida", - "fr-fr": "URL de l'image étendue", - "ru-ru": "URL-адрес развернутого изображения", - "uk-ua": "Розширена URL-адреса зображення", - "pt-br": "URL da imagem expandida", + 'en-us': 'Expanded Image URL', + 'de-ch': 'Erweiterte Bild-URL', + 'es-es': 'URL de imagen expandida', + 'fr-fr': "URL de l'image étendue", + 'ru-ru': 'URL-адрес развернутого изображения', + 'uk-ua': 'Розширена URL-адреса зображення', + 'pt-br': 'URL da imagem expandida', }, customLogoCollapsed: { - "en-us": "Collapsed Image URL", - "de-ch": "URL des minimierten Bilds", - "es-es": "URL de imagen contraída", - "fr-fr": "URL de l'image réduite", - "ru-ru": "URL свернутого изображения", - "uk-ua": "URL-адреса згорнутого зображення", - "pt-br": "URL da imagem recolhida", + 'en-us': 'Collapsed Image URL', + 'de-ch': 'URL des minimierten Bilds', + 'es-es': 'URL de imagen contraída', + 'fr-fr': "URL de l'image réduite", + 'ru-ru': 'URL свернутого изображения', + 'uk-ua': 'URL-адреса згорнутого зображення', + 'pt-br': 'URL da imagem recolhida', }, customLogoDescription: { - "en-us": - "A URL to an image that would be displayed next to the Specify logo in the navigation menu.", - "de-ch": - "Eine URL zu einem Bild, das neben dem angegebenen Logo im Navigationsmenü angezeigt wird.", - "es-es": - "Una URL a una imagen que se mostrará junto al logotipo Especificar en el menú de navegación.", - "fr-fr": - "Une URL vers une image qui serait affichée à côté du logo Specify dans le menu de navigation.", - "ru-ru": - "URL-адрес изображения, которое будет отображаться рядом с логотипом «Указать» в меню навигации.", - "uk-ua": - "URL-адреса зображення, яке відображатиметься поруч із «Вказати логотип» у меню навігації.", - "pt-br": - "Um URL para uma imagem que seria exibida ao lado do logotipo Especificar no menu de navegação.", + 'en-us': + 'A URL to an image that would be displayed next to the Specify logo in the navigation menu.', + 'de-ch': + 'Eine URL zu einem Bild, das neben dem angegebenen Logo im Navigationsmenü angezeigt wird.', + 'es-es': + 'Una URL a una imagen que se mostrará junto al logotipo Especificar en el menú de navegación.', + 'fr-fr': + 'Une URL vers une image qui serait affichée à côté du logo Specify dans le menu de navigation.', + 'ru-ru': + 'URL-адрес изображения, которое будет отображаться рядом с логотипом «Указать» в меню навигации.', + 'uk-ua': + 'URL-адреса зображення, яке відображатиметься поруч із «Вказати логотип» у меню навігації.', + 'pt-br': + 'Um URL para uma imagem que seria exibida ao lado do logotipo Especificar no menu de navegação.', }, showLineNumber: { - "en-us": "Show query result line number", - "de-ch": "Zeilennummer des Abfrageergebnisses anzeigen", - "es-es": "Mostrar el número de línea del resultado de la consulta", - "fr-fr": "Afficher le numéro de ligne du résultat de la requête", - "ru-ru": "Показать номер строки результата запроса", - "uk-ua": "Показати номер рядка результату запиту", - "pt-br": "Mostrar número da linha do resultado da consulta", + 'en-us': 'Show query result line number', + 'de-ch': 'Zeilennummer des Abfrageergebnisses anzeigen', + 'es-es': 'Mostrar el número de línea del resultado de la consulta', + 'fr-fr': 'Afficher le numéro de ligne du résultat de la requête', + 'ru-ru': 'Показать номер строки результата запроса', + 'uk-ua': 'Показати номер рядка результату запиту', + 'pt-br': 'Mostrar número da linha do resultado da consulta', }, saveButtonColor: { - "en-us": "Save button color", - "de-ch": "Farbe der Schaltfläche „Speichern“", - "es-es": "Guardar el color del botón", - "fr-fr": "Couleur du bouton Enregistrer", - "ru-ru": "Сохранить цвет кнопки", - "uk-ua": "Зберегти колір кнопки", - "pt-br": "Cor do botão Salvar", + 'en-us': 'Save button color', + 'de-ch': 'Farbe der Schaltfläche „Speichern“', + 'es-es': 'Guardar el color del botón', + 'fr-fr': 'Couleur du bouton Enregistrer', + 'ru-ru': 'Сохранить цвет кнопки', + 'uk-ua': 'Зберегти колір кнопки', + 'pt-br': 'Cor do botão Salvar', }, secondaryButtonColor: { - "en-us": "Secondary button color", - "es-es": "Color del botón secundario", - "fr-fr": "Couleur du bouton secondaire", - "ru-ru": "Цвет вторичной кнопки", - "uk-ua": "Колір вторинної кнопки", - "de-ch": "Sekundäre Schaltflächenfarbe", - "pt-br": "Cor do botão secundário", + 'en-us': 'Secondary button color', + 'es-es': 'Color del botón secundario', + 'fr-fr': 'Couleur du bouton secondaire', + 'ru-ru': 'Цвет вторичной кнопки', + 'uk-ua': 'Колір вторинної кнопки', + 'de-ch': 'Sekundäre Schaltflächenfarbe', + 'pt-br': 'Cor do botão secundário', }, secondaryLightButtonColor: { - "en-us": "Secondary light button color", - "de-ch": "Farbe der sekundären Lichttaste", - "es-es": "Color del botón de luz secundaria", - "fr-fr": "Couleur du bouton lumineux secondaire", - "ru-ru": "Цвет кнопки вторичного освещения", - "uk-ua": "Колір вторинної світлової кнопки", - "pt-br": "Cor do botão de luz secundária", + 'en-us': 'Secondary light button color', + 'de-ch': 'Farbe der sekundären Lichttaste', + 'es-es': 'Color del botón de luz secundaria', + 'fr-fr': 'Couleur du bouton lumineux secondaire', + 'ru-ru': 'Цвет кнопки вторичного освещения', + 'uk-ua': 'Колір вторинної світлової кнопки', + 'pt-br': 'Cor do botão de luz secundária', }, dangerButtonColor: { - "en-us": "Danger button color", - "de-ch": "Farbe der Gefahrenschaltfläche", - "es-es": "Color del botón de peligro", - "fr-fr": "Couleur du bouton de danger", - "ru-ru": "Цвет кнопки «Опасность»", - "uk-ua": "Колір кнопки небезпеки", - "pt-br": "Cor do botão de perigo", + 'en-us': 'Danger button color', + 'de-ch': 'Farbe der Gefahrenschaltfläche', + 'es-es': 'Color del botón de peligro', + 'fr-fr': 'Couleur du bouton de danger', + 'ru-ru': 'Цвет кнопки «Опасность»', + 'uk-ua': 'Колір кнопки небезпеки', + 'pt-br': 'Cor do botão de perigo', }, infoButtonColor: { - "en-us": "Info button color", - "de-ch": "Farbe der Info-Schaltfläche", - "es-es": "Color del botón de información", - "fr-fr": "Couleur du bouton d'information", - "ru-ru": "Цвет кнопки информации", - "uk-ua": "Колір інформаційної кнопки", - "pt-br": "Cor do botão de informações", + 'en-us': 'Info button color', + 'de-ch': 'Farbe der Info-Schaltfläche', + 'es-es': 'Color del botón de información', + 'fr-fr': "Couleur du bouton d'information", + 'ru-ru': 'Цвет кнопки информации', + 'uk-ua': 'Колір інформаційної кнопки', + 'pt-br': 'Cor do botão de informações', }, warningButtonColor: { - "en-us": "Warning button color", - "de-ch": "Farbe der Warnschaltfläche", - "es-es": "Color del botón de advertencia", - "fr-fr": "Couleur du bouton d'avertissement", - "ru-ru": "Цвет кнопки предупреждения", - "uk-ua": "Колір кнопки попередження", - "pt-br": "Cor do botão de aviso", + 'en-us': 'Warning button color', + 'de-ch': 'Farbe der Warnschaltfläche', + 'es-es': 'Color del botón de advertencia', + 'fr-fr': "Couleur du bouton d'avertissement", + 'ru-ru': 'Цвет кнопки предупреждения', + 'uk-ua': 'Колір кнопки попередження', + 'pt-br': 'Cor do botão de aviso', }, successButtonColor: { - "en-us": "Success button color", - "de-ch": "Farbe der Schaltfläche „Erfolg“", - "es-es": "Color del botón de éxito", - "fr-fr": "Couleur du bouton de réussite", - "ru-ru": "Цвет кнопки «Успех»", - "uk-ua": "Колір кнопки успіху", - "pt-br": "Cor do botão de sucesso", + 'en-us': 'Success button color', + 'de-ch': 'Farbe der Schaltfläche „Erfolg“', + 'es-es': 'Color del botón de éxito', + 'fr-fr': 'Couleur du bouton de réussite', + 'ru-ru': 'Цвет кнопки «Успех»', + 'uk-ua': 'Колір кнопки успіху', + 'pt-br': 'Cor do botão de sucesso', }, openAsReadOnly: { - "en-us": "Open all records in read-only mode", - "de-ch": "Alle Datensätze im schreibgeschützten Modus öffnen", - "es-es": "Abrir todos los registros en modo de solo lectura", - "fr-fr": "Ouvrir tous les enregistrements en mode lecture seule", - "ru-ru": "Открыть все записи в режиме только для чтения", - "uk-ua": "Відкрити всі записи в режимі лише для читання", - "pt-br": "Abra todos os registros no modo somente leitura", + 'en-us': 'Open all records in read-only mode', + 'de-ch': 'Alle Datensätze im schreibgeschützten Modus öffnen', + 'es-es': 'Abrir todos los registros en modo de solo lectura', + 'fr-fr': 'Ouvrir tous les enregistrements en mode lecture seule', + 'ru-ru': 'Открыть все записи в режиме только для чтения', + 'uk-ua': 'Відкрити всі записи в режимі лише для читання', + 'pt-br': 'Abra todos os registros no modo somente leitura', }, displayBasicView: { - "en-us": "Display basic view", - "de-ch": "Basisansicht anzeigen", - "es-es": "Mostrar vista básica", - "fr-fr": "Afficher la vue de base", - "ru-ru": "Отображение базового вида", - "uk-ua": "Відобразити базовий вигляд", - "pt-br": "Exibir visualização básica", + 'en-us': 'Display basic view', + 'de-ch': 'Basisansicht anzeigen', + 'es-es': 'Mostrar vista básica', + 'fr-fr': 'Afficher la vue de base', + 'ru-ru': 'Отображение базового вида', + 'uk-ua': 'Відобразити базовий вигляд', + 'pt-br': 'Exibir visualização básica', }, showComparisonOperatorsForString: { - "en-us": "Show comparison operators for text-based fields", - "de-ch": "Vergleichsoperatoren für textbasierte Felder anzeigen", - "es-es": "Mostrar operadores de comparación para campos basados en texto", - "fr-fr": "Afficher les opérateurs de comparaison pour les champs textuels", - "pt-br": "Mostrar operadores de comparação para campos baseados em texto", - "ru-ru": "Показать операторы сравнения для текстовых полей", - "uk-ua": "Показати оператори порівняння для текстових полів", + 'en-us': 'Show comparison operators for text-based fields', + 'de-ch': 'Vergleichsoperatoren für textbasierte Felder anzeigen', + 'es-es': 'Mostrar operadores de comparación para campos basados en texto', + 'fr-fr': 'Afficher les opérateurs de comparaison pour les champs textuels', + 'pt-br': 'Mostrar operadores de comparação para campos baseados em texto', + 'ru-ru': 'Показать операторы сравнения для текстовых полей', + 'uk-ua': 'Показати оператори порівняння для текстових полів', }, showComparisonOperatorsDescription: { - "en-us": - "Allows the following filters to apply to text fields: Greater Than, Less Than, Greater Than or Equal to, and Less Than or Equal to", - "de-ch": - "Ermöglicht die Anwendung der folgenden Filter auf Textfelder: Größer als, Kleiner als, Größer als oder gleich und Kleiner als oder gleich", - "es-es": - "Permite aplicar los siguientes filtros a los campos de texto: Mayor que, Menor que, Mayor o igual que y Menor o igual que", - "fr-fr": + 'en-us': + 'Allows the following filters to apply to text fields: Greater Than, Less Than, Greater Than or Equal to, and Less Than or Equal to', + 'de-ch': + 'Ermöglicht die Anwendung der folgenden Filter auf Textfelder: Größer als, Kleiner als, Größer als oder gleich und Kleiner als oder gleich', + 'es-es': + 'Permite aplicar los siguientes filtros a los campos de texto: Mayor que, Menor que, Mayor o igual que y Menor o igual que', + 'fr-fr': "Permet d'appliquer les filtres suivants aux champs de texte : Supérieur à, Inférieur à, Supérieur ou égal à et Inférieur ou égal à", - "pt-br": - "Permite que os seguintes filtros sejam aplicados aos campos de texto: Maior que, Menor que, Maior ou igual a e Menor ou igual a", - "ru-ru": - "Позволяет применять к текстовым полям следующие фильтры: «Больше чем», «Меньше чем», «Больше или равно» и «Меньше или равно».", - "uk-ua": - "Дозволяє застосовувати до текстових полів такі фільтри: «Більше ніж», «Менше ніж», «Більше або дорівнює» та «Менше або дорівнює»", + 'pt-br': + 'Permite que os seguintes filtros sejam aplicados aos campos de texto: Maior que, Menor que, Maior ou igual a e Menor ou igual a', + 'ru-ru': + 'Позволяет применять к текстовым полям следующие фильтры: «Больше чем», «Меньше чем», «Больше или равно» и «Меньше или равно».', + 'uk-ua': + 'Дозволяє застосовувати до текстових полів такі фільтри: «Більше ніж», «Менше ніж», «Більше або дорівнює» та «Менше або дорівнює»', }, basicView: { - "en-us": "Basic view", - "de-ch": "Basisansicht", - "es-es": "Vista básica", - "fr-fr": "Vue de base", - "ru-ru": "Базовый вид", - "uk-ua": "Основний вигляд", - "pt-br": "Visão básica", + 'en-us': 'Basic view', + 'de-ch': 'Basisansicht', + 'es-es': 'Vista básica', + 'fr-fr': 'Vue de base', + 'ru-ru': 'Базовый вид', + 'uk-ua': 'Основний вигляд', + 'pt-br': 'Visão básica', }, detailedView: { - "en-us": "Detailed view", - "de-ch": "Detailansicht", - "es-es": "Vista detallada", - "fr-fr": "Vue détaillée", - "ru-ru": "Подробный вид", - "uk-ua": "Детальний вигляд", - "pt-br": "Visão detalhada", + 'en-us': 'Detailed view', + 'de-ch': 'Detailansicht', + 'es-es': 'Vista detallada', + 'fr-fr': 'Vue détaillée', + 'ru-ru': 'Подробный вид', + 'uk-ua': 'Детальний вигляд', + 'pt-br': 'Visão detalhada', }, attachmentPreviewMode: { - "en-us": "Attachment preview mode", - "de-ch": "Anhangsvorschaumodus", - "es-es": "Modo de vista previa de archivos adjuntos", - "fr-fr": "Mode d'aperçu des pièces jointes", - "ru-ru": "Режим предварительного просмотра вложений", - "uk-ua": "Режим попереднього перегляду вкладених файлів", - "pt-br": "Modo de visualização de anexos", + 'en-us': 'Attachment preview mode', + 'de-ch': 'Anhangsvorschaumodus', + 'es-es': 'Modo de vista previa de archivos adjuntos', + 'fr-fr': "Mode d'aperçu des pièces jointes", + 'ru-ru': 'Режим предварительного просмотра вложений', + 'uk-ua': 'Режим попереднього перегляду вкладених файлів', + 'pt-br': 'Modo de visualização de anexos', }, fullResolution: { - "en-us": "Full Resolution", - "de-ch": "Volle Auflösung", - "es-es": "Resolución completa", - "fr-fr": "Pleine résolution", - "ru-ru": "Полное разрешение", - "uk-ua": "Повна роздільна здатність", - "pt-br": "Resolução completa", + 'en-us': 'Full Resolution', + 'de-ch': 'Volle Auflösung', + 'es-es': 'Resolución completa', + 'fr-fr': 'Pleine résolution', + 'ru-ru': 'Полное разрешение', + 'uk-ua': 'Повна роздільна здатність', + 'pt-br': 'Resolução completa', }, thumbnail: { - "en-us": "Thumbnail", - "de-ch": "Miniaturansicht", - "es-es": "Uña del pulgar", - "fr-fr": "Vignette", - "ru-ru": "Миниатюра", - "uk-ua": "Мініатюра", - "pt-br": "Miniatura", + 'en-us': 'Thumbnail', + 'de-ch': 'Miniaturansicht', + 'es-es': 'Uña del pulgar', + 'fr-fr': 'Vignette', + 'ru-ru': 'Миниатюра', + 'uk-ua': 'Мініатюра', + 'pt-br': 'Miniatura', }, addSearchBarHomePage: { - "en-us": "Add Search Bar on home page", - "de-ch": "Suchleiste auf der Startseite hinzufügen", - "es-es": "Agregar barra de búsqueda en la página de inicio", - "fr-fr": "Ajouter une barre de recherche sur la page d'accueil", - "ru-ru": "Добавить панель поиска на домашнюю страницу", - "uk-ua": "Додайте рядок пошуку на головну сторінку", - "pt-br": "Adicionar barra de pesquisa na página inicial", + 'en-us': 'Add Search Bar on home page', + 'de-ch': 'Suchleiste auf der Startseite hinzufügen', + 'es-es': 'Agregar barra de búsqueda en la página de inicio', + 'fr-fr': "Ajouter une barre de recherche sur la page d'accueil", + 'ru-ru': 'Добавить панель поиска на домашнюю страницу', + 'uk-ua': 'Додайте рядок пошуку на головну сторінку', + 'pt-br': 'Adicionar barra de pesquisa na página inicial', }, inheritanceCatNumberPref: { - "en-us": - "Enable the inheritance of the primary catalog number to its empty siblings.", - "de-ch": - "Aktivieren Sie die Vererbung der primären Katalognummer an ihre leeren Geschwister.", - "es-es": - "Habilitar la herencia del número de catálogo principal a sus hermanos vacíos.", - "fr-fr": + 'en-us': + 'Enable the inheritance of the primary catalog number to its empty siblings.', + 'de-ch': + 'Aktivieren Sie die Vererbung der primären Katalognummer an ihre leeren Geschwister.', + 'es-es': + 'Habilitar la herencia del número de catálogo principal a sus hermanos vacíos.', + 'fr-fr': "Activez l'héritage du numéro de catalogue principal vers ses frères vides.", - "pt-br": - "Habilitar a herança do número do catálogo primário para seus irmãos vazios.", - "ru-ru": - "Включить наследование основного каталожного номера его пустыми родственными элементами.", - "uk-ua": - "Увімкнути успадкування основного каталожного номера його порожнім братам і сестрам.", + 'pt-br': + 'Habilitar a herança do número do catálogo primário para seus irmãos vazios.', + 'ru-ru': + 'Включить наследование основного каталожного номера его пустыми родственными элементами.', + 'uk-ua': + 'Увімкнути успадкування основного каталожного номера його порожнім братам і сестрам.', }, inheritanceCatNumberParentCOPref: { - "en-us": - "Enable the inheritance of the parent catalog number to its empty children.", - "de-ch": - "Aktivieren Sie die Vererbung der übergeordneten Katalognummer an ihre leeren untergeordneten Elemente.", - "es-es": - "Habilitar la herencia del número de catálogo principal a sus hijos vacíos.", - "fr-fr": + 'en-us': + 'Enable the inheritance of the parent catalog number to its empty children.', + 'de-ch': + 'Aktivieren Sie die Vererbung der übergeordneten Katalognummer an ihre leeren untergeordneten Elemente.', + 'es-es': + 'Habilitar la herencia del número de catálogo principal a sus hijos vacíos.', + 'fr-fr': "Activer l'héritage du numéro de catalogue parent à ses enfants vides.", - "pt-br": - "Habilitar a herança do número do catálogo pai para seus filhos vazios.", - "ru-ru": - "Включить наследование родительского каталожного номера его пустыми дочерними элементами.", - "uk-ua": - "Увімкнути успадкування батьківського каталожного номера його порожнім дочірнім елементам.", + 'pt-br': + 'Habilitar a herança do número do catálogo pai para seus filhos vazios.', + 'ru-ru': + 'Включить наследование родительского каталожного номера его пустыми дочерними элементами.', + 'uk-ua': + 'Увімкнути успадкування батьківського каталожного номера його порожнім дочірнім елементам.', + }, + alt: { + comment: 'Alt key on the keyboard', + 'en-us': 'Alt', + }, + macOption: { + comment: 'Option key on the macOS keyboard', + 'en-us': '⌥', + }, + ctrl: { + comment: 'Ctrl key on the keyboard', + 'en-us': 'Ctrl', + }, + macControl: { + comment: 'Control key on the macOS keyboard', + 'en-us': '⌃', + }, + macMeta: { + comment: 'Meta/Command key on the macOS keyboard', + 'en-us': '⌘', + }, + macShift: { + comment: 'Shift key on the macOS keyboard', + 'en-us': '⇧', + }, + shift: { + comment: 'Shift key on the keyboard', + 'en-us': 'Shift', + }, + keyboardShortcuts: { + 'en-us': 'Keyboard Shortcuts', + }, + pressKeys: { 'en-us': 'Press desired key combination...' }, + noKeyAssigned: { 'en-us': 'No key binding assigned' }, + openRelatedRecordInNewTab: { + 'en-us': 'Open related record in new tab', + }, + openFormMeta: { + 'en-us': 'Open form meta', + }, + urlShortcuts: { + 'en-us': 'Keyboard Shortcuts to URLs', + }, + pages: { + 'en-us': 'Pages', + }, + overlays: { + 'en-us': 'Overlays', + }, + customPages: { + 'en-us': 'Custom Pages', + }, + relativeOrAbsoluteUrl: { + 'en-us': 'Relative or absolute URL', }, } as const); diff --git a/specifyweb/frontend/js_src/lib/utils/ajax/helpers.ts b/specifyweb/frontend/js_src/lib/utils/ajax/helpers.ts index 8380a24bda9..f8afb568331 100644 --- a/specifyweb/frontend/js_src/lib/utils/ajax/helpers.ts +++ b/specifyweb/frontend/js_src/lib/utils/ajax/helpers.ts @@ -6,14 +6,21 @@ import { setDevelopmentGlobal } from '../types'; // These HTTP methods do not require CSRF protection export const csrfSafeMethod = new Set(['GET', 'HEAD', 'OPTIONS', 'TRACE']); -export const isExternalUrl = (url: string): boolean => - /* - * Blob URLs may point to the same origin, but should be treated as external - * by the navigator - */ - url.startsWith('blob:') || - new URL(url, globalThis.location.origin).origin !== - globalThis.location.origin; +export function isExternalUrl(url: string): boolean { + try { + /* + * Blob URLs may point to the same origin, but should be treated as external + * by the navigator + */ + return ( + url.startsWith('blob:') || + new URL(url, globalThis.location.origin).origin !== + globalThis.location.origin + ); + } catch { + return true; + } +} /** * Make sure the given URL is from current origin and give it back without diff --git a/specifyweb/frontend/js_src/lib/utils/types.ts b/specifyweb/frontend/js_src/lib/utils/types.ts index 09c889d6082..0013acf8863 100644 --- a/specifyweb/frontend/js_src/lib/utils/types.ts +++ b/specifyweb/frontend/js_src/lib/utils/types.ts @@ -217,11 +217,13 @@ export const ensure = * empty string is used. However, empty string is not a LocalizedString, so * this hack is needed * - * @example Valid use case + * @example + * // Valid use case * localized('') * - * @example Invalid use case + * @example * ```ts + * // Invalid use case * // Wrong: * localized(table.name) * // Should use table label instead (unless there is a good reason to use