Browse Source

Merge pull request #6519 from nocodb/fix/i8n-2

pull/6521/head
Muhammed Mustafa 1 year ago committed by GitHub
parent
commit
9654385aa6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      packages/nc-gui/components/account/Profile.vue
  2. 14
      packages/nc-gui/components/api-client/Headers.vue
  3. 10
      packages/nc-gui/components/api-client/Params.vue
  4. 4
      packages/nc-gui/components/cell/Currency.vue
  5. 8
      packages/nc-gui/components/cell/DatePicker.vue
  6. 8
      packages/nc-gui/components/cell/DateTimePicker.vue
  7. 4
      packages/nc-gui/components/cell/Decimal.vue
  8. 11
      packages/nc-gui/components/cell/Duration.vue
  9. 4
      packages/nc-gui/components/cell/Email.vue
  10. 4
      packages/nc-gui/components/cell/Float.vue
  11. 4
      packages/nc-gui/components/cell/Integer.vue
  12. 8
      packages/nc-gui/components/cell/Json.vue
  13. 4
      packages/nc-gui/components/cell/MultiSelect.vue
  14. 4
      packages/nc-gui/components/cell/Percent.vue
  15. 8
      packages/nc-gui/components/cell/PhoneNumber.vue
  16. 4
      packages/nc-gui/components/cell/SingleSelect.vue
  17. 4
      packages/nc-gui/components/cell/Text.vue
  18. 8
      packages/nc-gui/components/cell/TextArea.vue
  19. 8
      packages/nc-gui/components/cell/TimePicker.vue
  20. 4
      packages/nc-gui/components/cell/Url.vue
  21. 8
      packages/nc-gui/components/cell/YearPicker.vue
  22. 10
      packages/nc-gui/components/shared-view/AskPassword.vue
  23. 18
      packages/nc-gui/components/smartsheet/Form.vue
  24. 15
      packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue
  25. 12
      packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue
  26. 2
      packages/nc-gui/components/smartsheet/column/DateOptions.vue
  27. 4
      packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue
  28. 20
      packages/nc-gui/components/smartsheet/column/DecimalOptions.vue
  29. 2
      packages/nc-gui/components/smartsheet/column/DefaultValue.vue
  30. 4
      packages/nc-gui/components/smartsheet/column/DurationOptions.vue
  31. 109
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  32. 22
      packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue
  33. 6
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  34. 10
      packages/nc-gui/components/smartsheet/column/PercentOptions.vue
  35. 12
      packages/nc-gui/components/smartsheet/column/PgBinaryOptions.vue
  36. 6
      packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue
  37. 4
      packages/nc-gui/components/smartsheet/column/RatingOptions.vue
  38. 26
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  39. 8
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  40. 12
      packages/nc-gui/components/smartsheet/grid/Table.vue
  41. 6
      packages/nc-gui/components/smartsheet/grid/index.vue
  42. 6
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  43. 6
      packages/nc-gui/components/virtual-cell/QrCode.vue
  44. 107
      packages/nc-gui/components/webhook/Editor.vue
  45. 155
      packages/nc-gui/lang/en.json
  46. 4
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId].vue
  47. 8
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/index.vue
  48. 8
      packages/nc-gui/pages/signup/[[token]].vue

6
packages/nc-gui/components/account/Profile.vue

@ -15,9 +15,9 @@ const formValidator = ref()
const formRules = {
title: [
{ required: true, message: t('error.nameRequired') },
{ min: 2, message: t('error.nameMinLength') },
{ max: 60, message: t('error.nameMaxLength') },
{ required: true, message: t('msg.error.nameRequired') },
{ min: 2, message: t('msg.error.nameMinLength') },
{ max: 60, message: t('msg.error.nameMaxLength') },
],
}

14
packages/nc-gui/components/api-client/Headers.vue

@ -66,10 +66,10 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
<tr>
<th></th>
<th>
<div class="text-left font-normal ml-2">Header Name</div>
<div class="text-left font-normal ml-2">{{ $t('labels.headerName') }}</div>
</th>
<th>
<div class="text-left font-normal ml-2">Value</div>
<div class="text-left font-normal ml-2">{{ $t('placeholder.value') }}</div>
</th>
<th class="w-8"></th>
</tr>
@ -87,9 +87,9 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
<a-form-item class="form-item">
<a-auto-complete
v-model:value="headerRow.name"
placeholder="Key"
class="nc-input-hook-header-key"
:options="headerList"
:placeholder="$t('placeholder.key')"
:filter-option="filterOption"
/>
</a-form-item>
@ -97,7 +97,11 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
<td class="px-2">
<a-form-item class="form-item">
<a-input v-model:value="headerRow.value" placeholder="Value" class="!rounded-md nc-input-hook-header-value" />
<a-input
v-model:value="headerRow.value"
:placeholder="$t('placeholder.value')"
class="!rounded-md nc-input-hook-header-value"
/>
</a-form-item>
</td>
@ -120,7 +124,7 @@ const filterOption = (input: string, option: Option) => option.value.toUpperCase
<td :colspan="12" class="">
<NcButton size="small" type="secondary" @click="addHeaderRow">
<div class="flex flex-row items-center gap-x-1">
<div>Add Header</div>
<div>{{ $t('labels.addHeader') }}</div>
<component :is="iconMap.plus" class="flex mx-auto" />
</div>
</NcButton>

10
packages/nc-gui/components/api-client/Params.vue

@ -24,11 +24,11 @@ const deleteParamRow = (i: number) => {
<thead class="h-8">
<tr>
<th>
<div class="text-left font-normal ml-2">Parameter Name</div>
<div class="text-left font-normal ml-2">{{ $t('title.parameterName') }}</div>
</th>
<th>
<div class="text-left font-normal ml-2">Value</div>
<div class="text-left font-normal ml-2">{{ $t('placeholder.value') }}</div>
</th>
<th class="w-8">
@ -41,13 +41,13 @@ const deleteParamRow = (i: number) => {
<tr v-for="(paramRow, idx) in vModel" :key="idx" class="!h-2 overflow-hidden">
<td class="px-2">
<a-form-item class="form-item">
<a-input v-model:value="paramRow.name" placeholder="Key" class="!rounded-lg" />
<a-input v-model:value="paramRow.name" :placeholder="$t('placeholder.key')" class="!rounded-lg" />
</a-form-item>
</td>
<td class="px-2">
<a-form-item class="form-item">
<a-input v-model:value="paramRow.value" placeholder="Value" class="!rounded-lg" />
<a-input v-model:value="paramRow.value" :placeholder="$t('placeholder.value')" class="!rounded-lg" />
</a-form-item>
</td>
@ -69,7 +69,7 @@ const deleteParamRow = (i: number) => {
<td :colspan="12" class="">
<NcButton size="small" type="secondary" @click="addParamRow">
<div class="flex flex-row items-center gap-x-1">
<div>Add Parameter</div>
<div>{{ $t('activity.addParameter') }}</div>
<component :is="iconMap.plus" class="flex mx-auto" />
</div>
</NcButton>

4
packages/nc-gui/components/cell/Currency.vue

@ -79,7 +79,7 @@ onMounted(() => {
v-model="vModel"
type="number"
class="w-full h-full text-sm border-none rounded-md outline-none"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="submitCurrency"
@keydown.down.stop
@keydown.left.stop
@ -93,7 +93,7 @@ onMounted(() => {
@contextmenu.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<!-- only show the numeric value as previously string value was accepted -->
<span v-else-if="!isNaN(vModel)">{{ currency }}</span>

8
packages/nc-gui/components/cell/DatePicker.vue

@ -25,6 +25,8 @@ const { modelValue, isPk } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const { t } = useI18n()
const { showNull } = useGlobal()
const columnMeta = inject(ColumnInj, null)!
@ -84,11 +86,11 @@ watch(
const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return '(Optional)'
return t('labels.optional')
} else if (modelValue === null && showNull.value) {
return 'NULL'
return t('general.null')
} else if (isDateInvalid.value) {
return 'Invalid date'
return t('msg.invalidDate')
} else {
return ''
}

8
packages/nc-gui/components/cell/DateTimePicker.vue

@ -40,6 +40,8 @@ const editable = inject(EditModeInj, ref(false))
const isLockedMode = inject(IsLockedInj, ref(false))
const { t } = useI18n()
const isEditColumn = inject(EditColumnInj, ref(false))
const column = inject(ColumnInj)!
@ -137,11 +139,11 @@ watch(
const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return '(Optional)'
return t('labels.optional')
} else if (modelValue === null && showNull.value) {
return 'NULL'
return t('general.null')
} else if (isDateInvalid.value) {
return 'Invalid date'
return t('msg.invalidDate')
} else {
return ''
}

4
packages/nc-gui/components/cell/Decimal.vue

@ -97,7 +97,7 @@ watch(isExpandedFormOpen, () => {
class="outline-none !py-2 !px-1 border-none rounded-md w-full h-full !text-sm"
type="number"
:step="precision"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
style="letter-spacing: 0.06rem"
@blur="editEnabled = false"
@keydown.down.stop="onKeyDown"
@ -110,7 +110,7 @@ watch(isExpandedFormOpen, () => {
@selectstart.capture.stop
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null capitalize">{{ $t('general.null') }}</span>
<span v-else class="text-sm">{{ displayValue }}</span>
</template>

11
packages/nc-gui/components/cell/Duration.vue

@ -23,6 +23,8 @@ const { modelValue, showValidationError = true } = defineProps<Props>()
const emit = defineEmits(['update:modelValue'])
const { t } = useI18n()
const { showNull } = useGlobal()
const column = inject(ColumnInj)
@ -39,7 +41,9 @@ const isEditColumn = inject(EditColumnInj, ref(false))
const durationType = computed(() => parseProp(column?.value?.meta)?.duration || 0)
const durationPlaceholder = computed(() => (isEditColumn.value ? '(Optional)' : durationOptions[durationType.value].title))
const durationPlaceholder = computed(() =>
isEditColumn.value ? `(${t('labels.optional')})` : durationOptions[durationType.value].title,
)
const localState = computed({
get: () => convertMS2Duration(modelValue, durationType.value),
@ -105,13 +109,12 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@mousedown.stop
/>
<span v-else-if="modelValue === null && showNull" class="nc-null">NULL</span>
<span v-else-if="modelValue === null && showNull" class="nc-null capitalize">{{ $t('general.null') }}</span>
<span v-else> {{ localState }}</span>
<div v-if="showWarningMessage && showValidationError" class="duration-warning">
<!-- TODO: i18n -->
Please enter a number
{{ $t('msg.plsEnterANumber') }}
</div>
</div>
</template>

4
packages/nc-gui/components/cell/Email.vue

@ -71,7 +71,7 @@ watch(
:ref="focus"
v-model="vModel"
class="w-full outline-none text-sm px-1 py-2"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@ -84,7 +84,7 @@ watch(
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<nuxt-link
v-else-if="validEmail"

4
packages/nc-gui/components/cell/Float.vue

@ -51,7 +51,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
class="outline-none px-1 border-none w-full h-full text-sm"
type="number"
step="0.1"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@ -63,7 +63,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@selectstart.capture.stop
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<span v-else class="text-sm">{{ vModel }}</span>
</template>

4
packages/nc-gui/components/cell/Integer.vue

@ -88,7 +88,7 @@ function onKeyDown(e: any) {
class="outline-none py-2 px-1 border-none w-full h-full text-sm"
type="number"
style="letter-spacing: 0.06rem"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown="onKeyDown"
@keydown.down.stop
@ -99,7 +99,7 @@ function onKeyDown(e: any) {
@selectstart.capture.stop
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<span v-else class="text-sm">{{ displayValue }}</span>
</template>

8
packages/nc-gui/components/cell/Json.vue

@ -150,10 +150,12 @@ useSelectedCellKeyupListener(active, (e) => {
</a-button>
<div v-if="!isForm || isExpanded" class="flex flex-row my-1">
<a-button type="text" size="small" :onclick="clear"><div class="text-xs">Cancel</div></a-button>
<a-button type="text" size="small" :onclick="clear"
><div class="text-xs">{{ $t('general.cancel') }}</div></a-button
>
<a-button type="primary" size="small" :disabled="!!error || localValue === vModel" @click="onSave">
<div class="text-xs">Save</div>
<div class="text-xs">{{ $t('general.save') }}</div>
</a-button>
</div>
</div>
@ -172,7 +174,7 @@ useSelectedCellKeyupListener(active, (e) => {
</span>
</div>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<span v-else>{{ vModel }}</span>
</component>

4
packages/nc-gui/components/cell/MultiSelect.vue

@ -378,7 +378,7 @@ const selectedOpts = computed(() => {
v-model:value="vModel"
mode="multiple"
class="w-full overflow-hidden"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
:bordered="false"
clear-icon
show-search
@ -421,7 +421,7 @@ const selectedOpts = computed(() => {
<div class="flex gap-2 text-gray-500 items-center h-full">
<component :is="iconMap.plusThick" class="min-w-4" />
<div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong>
{{ $t('msg.selectOption.createNewOptionNamed') }} <strong>{{ searchVal }}</strong>
</div>
</div>
</a-select-option>

4
packages/nc-gui/components/cell/Percent.vue

@ -42,7 +42,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
class="w-full !text-sm !border-none !outline-none focus:ring-0 text-base p-1"
:class="{ '!px-2': editEnabled }"
type="number"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@ -54,6 +54,6 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@selectstart.capture.stop
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null capitalize">{{ $t('general.null') }}</span>
<span v-else>{{ vModel }}</span>
</template>

8
packages/nc-gui/components/cell/PhoneNumber.vue

@ -15,6 +15,8 @@ const rowHeight = inject(RowHeightInj, ref(undefined))
const { showNull } = useGlobal()
const { t } = useI18n()
const editEnabled = inject(EditModeInj)!
const isEditColumn = inject(EditColumnInj, ref(false))
@ -46,7 +48,7 @@ watch(
() => editEnabled.value,
() => {
if (parseProp(column.value.meta)?.validate && !editEnabled.value && localState.value && !isMobilePhone(localState.value)) {
message.error('Invalid Phone Number')
message.error(t('msg.invalidPhoneNumber'))
localState.value = undefined
return
}
@ -61,7 +63,7 @@ watch(
:ref="focus"
v-model="vModel"
class="w-full outline-none text-sm px-1 py-2"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@ -74,7 +76,7 @@ watch(
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<a v-else-if="validEmail" class="text-sm underline hover:opacity-75" :href="`tel:${vModel}`" target="_blank">
<LazyCellClampedText :value="vModel" :lines="rowHeight" />

4
packages/nc-gui/components/cell/SingleSelect.vue

@ -286,7 +286,7 @@ const selectedOpt = computed(() => {
v-model:value="vModel"
class="w-full overflow-hidden"
:class="{ 'caret-transparent': !hasEditRoles }"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
:allow-clear="!column.rqd && editAllowed"
:bordered="false"
:open="isOpen && editAllowed"
@ -324,7 +324,7 @@ const selectedOpt = computed(() => {
<div class="flex gap-2 text-gray-500 items-center h-full">
<component :is="iconMap.plusThick" class="min-w-4" />
<div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong>
{{ $t('msg.selectOption.createNewOptionNamed') }} <strong>{{ searchVal }}</strong>
</div>
</div>
</a-select-option>

4
packages/nc-gui/components/cell/Text.vue

@ -33,7 +33,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
:ref="focus"
v-model="vModel"
class="h-full w-full outline-none p-2 bg-transparent"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@ -46,7 +46,7 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<LazyCellClampedText v-else :value="vModel" :lines="rowHeight" />
</template>

8
packages/nc-gui/components/cell/TextArea.vue

@ -84,7 +84,7 @@ onClickOutside(inputWrapperRef, (e) => {
:style="{
minHeight: `${height}px`,
}"
:placeholder="isEditColumn ? '(Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.optional') : ''"
@blur="editEnabled = false"
@keydown.alt.enter.stop
@keydown.shift.enter.stop
@ -99,7 +99,7 @@ onClickOutside(inputWrapperRef, (e) => {
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<LazyCellClampedText v-else-if="rowHeight" :value="vModel" :lines="rowHeight" class="mr-7" />
@ -113,7 +113,7 @@ onClickOutside(inputWrapperRef, (e) => {
@click.stop="isVisible = !isVisible"
>
<NcTooltip placement="bottom">
<template #title>Expand</template>
<template #title>{{ $t('title.expand') }}</template>
<component
:is="iconMap.expand"
class="transform dark:(!text-white) group-hover:(!text-grey-800 scale-120) text-gray-500 text-xs"
@ -135,8 +135,8 @@ onClickOutside(inputWrapperRef, (e) => {
<a-textarea
ref="inputRef"
v-model:value="vModel"
placeholder="Enter text"
class="p-1 !pt-1 !pr-3 !border-0 !border-r-0 !focus:outline-transparent nc-scrollbar-md !text-black"
:placeholder="$t('activity.enterText')"
:bordered="false"
:auto-size="{ minRows: 20, maxRows: 20 }"
:disabled="readOnly"

8
packages/nc-gui/components/cell/TimePicker.vue

@ -38,6 +38,8 @@ const isTimeInvalid = ref(false)
const dateFormat = isMysql(column.value.base_id) ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD HH:mm:ssZ'
const { t } = useI18n()
const localState = computed({
get() {
if (!modelValue) {
@ -89,11 +91,11 @@ watch(
const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return '(Optional)'
return t('labels.optional')
} else if (modelValue === null && showNull.value) {
return 'NULL'
return t('general.null')
} else if (isTimeInvalid.value) {
return 'Invalid time'
return t('msg.invalidTime')
} else {
return ''
}

4
packages/nc-gui/components/cell/Url.vue

@ -91,7 +91,7 @@ watch(
v-if="editEnabled"
:ref="focus"
v-model="vModel"
:placeholder="isEditColumn ? 'Enter default URL (Optional)' : ''"
:placeholder="isEditColumn ? $t('labels.enterDefaultUrlOptional') : ''"
class="outline-none text-sm w-full px-2 py-2 bg-transparent h-full"
@blur="editEnabled = false"
@keydown.down.stop
@ -105,7 +105,7 @@ watch(
@mousedown.stop
/>
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<span v-else-if="vModel === null && showNull" class="nc-null uppercase"> $t('general.null')</span>
<nuxt-link
v-else-if="isValid && !cellUrlOptions?.overlay"

8
packages/nc-gui/components/cell/YearPicker.vue

@ -33,6 +33,8 @@ const isEditColumn = inject(EditColumnInj, ref(false))
const isYearInvalid = ref(false)
const { t } = useI18n()
const localState = computed({
get() {
if (!modelValue) {
@ -76,11 +78,11 @@ watch(
const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return '(Optional)'
return t('labels.optional')
} else if (modelValue === null && showNull.value) {
return 'NULL'
return t('general.null')
} else if (isYearInvalid.value) {
return 'Invalid year'
return t('msg.invalidTime')
} else {
return ''
}

10
packages/nc-gui/components/shared-view/AskPassword.vue

@ -35,27 +35,27 @@ const focus: VNodeRef = (el: typeof InputPassword) => el?.$el?.querySelector('in
<template #header>
<div class="flex flex-row items-center gap-x-2">
<GeneralIcon icon="key" />
This shared view is protected
{{ $t('msg.thisSharedViewIsProtected') }}
</div>
</template>
<div class="mt-2">
<a-form ref="formRef" :model="formState" name="create-new-table-form" @finish="onFinish">
<a-form-item name="password" :rules="[{ required: true, message: 'Password is required' }]">
<a-form-item name="password" :rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]">
<a-input-password
ref="focus"
v-model:value="formState.password"
class="nc-input-md"
hide-details
size="large"
placeholder="Enter password"
:placeholder="$t('msg.enterPassword')"
/>
</a-form-item>
</a-form>
<div class="flex flex-row justify-end gap-x-2 mt-6">
<NcButton type="primary" html-type="submit" @click="onFinish"
>Unlock
<template #loading> Verifying Password </template>
>{{ $t('general.unlock') }}
<template #loading> {{ $t('msg.verifyingPassword') }}</template>
</NcButton>
</div>
</div>

18
packages/nc-gui/components/smartsheet/Form.vue

@ -389,8 +389,10 @@ watch(view, (nextView) => {
<template>
<template v-if="isMobileMode">
<div class="pl-6 pr-[120px] py-6 bg-white flex-col justify-start items-start gap-2.5 inline-flex">
<div class="text-gray-500 text-5xl font-semibold leading-16">Available<br />in Desktop</div>
<div class="text-gray-500 text-base font-medium leading-normal">Form View is currently not supported on mobile.</div>
<div class="text-gray-500 text-5xl font-semibold leading-16">
{{ $t('general.available') }}<br />{{ $t('title.inDesktop') }}
</div>
<div class="text-gray-500 text-base font-medium leading-normal">{{ $t('msg.formViewNotSupportedOnMobile') }}</div>
</div>
</template>
<template v-else>
@ -399,16 +401,20 @@ watch(view, (nextView) => {
<div v-if="formViewData" class="items-center justify-center text-center mt-2">
<a-alert type="success">
<template #message>
<div class="text-center">{{ formViewData.success_msg || 'Successfully submitted form data' }}</div>
<div class="text-center">{{ formViewData.success_msg || $t('msg.successfullySubmittedFormData') }}</div>
</template>
</a-alert>
<div v-if="formViewData.show_blank_form" class="text-gray-400 mt-4">
New form will be loaded after {{ secondsRemain }} seconds
{{
$t('msg.newFormWillBeLoaded', {
seconds: secondsRemain,
})
}}
</div>
<div v-if="formViewData.submit_another_form || !isPublic" class="text-center mt-4">
<a-button type="primary" size="large" @click="submitted = false"> Submit Another Form</a-button>
<a-button type="primary" size="large" @click="submitted = false"> {{ $t('activity.submitAnotherForm') }}</a-button>
</div>
</div>
</a-col>
@ -773,7 +779,7 @@ watch(view, (nextView) => {
v-if="!localColumns.length"
class="mt-4 border-dashed border-2 border-gray-400 py-3 text-gray-400 text-center"
>
Drag and drop fields here to add
{{ $t('activity.dragAndDropFieldsHereToAdd') }}
</div>
</template>
</Draggable>

15
packages/nc-gui/components/smartsheet/column/BarcodeOptions.vue

@ -22,6 +22,8 @@ const vModel = useVModel(props, 'modelValue', emit)
const { setAdditionalValidations, validateInfos, column } = useColumnCreateStoreOrThrow()
const { t } = useI18n()
const columnsAllowedAsBarcodeValue = computed<SelectProps['options']>(() => {
return fields.value
?.filter(
@ -67,8 +69,8 @@ watch(columnsAllowedAsBarcodeValue, (newColumnsAllowedAsBarcodeValue) => {
})
setAdditionalValidations({
fk_barcode_value_column_id: [{ required: true, message: 'Required' }],
barcode_format: [{ required: true, message: 'Required' }],
fk_barcode_value_column_id: [{ required: true, message: t('general.required') }],
barcode_format: [{ required: true, message: t('general.required') }],
})
const showBarcodeValueColumnInfoIcon = computed(() => !columnsAllowedAsBarcodeValue.value?.length)
@ -86,16 +88,15 @@ const showBarcodeValueColumnInfoIcon = computed(() => !columnsAllowedAsBarcodeVa
<a-select
v-model:value="vModel.fk_barcode_value_column_id"
:options="columnsAllowedAsBarcodeValue"
placeholder="Select a column for the Barcode value"
not-found-content="No valid Column Type can be found."
:placeholder="$t('placeholder.barcodeColumn')"
:not-found-content="$t('placeholder.notFoundContent')"
@click.stop
/>
<div v-if="showBarcodeValueColumnInfoIcon" class="pl-2">
<a-tooltip placement="bottom">
<template #title>
<span>
The valid Column Types for a Barcode Column are: Number, Single Line Text, Long Text, Phone Number, URL, Email,
Decimal. Please create one first.
{{ $t('msg.validColumnsForBarCode') }}
</span>
</template>
<component :is="iconMap.info" class="cursor-pointer" />
@ -111,7 +112,7 @@ const showBarcodeValueColumnInfoIcon = computed(() => !columnsAllowedAsBarcodeVa
<a-select
v-model:value="vModel.meta.barcodeFormat"
:options="supportedBarcodeFormats"
placeholder="Select a Barcode format"
:placeholder="$t('placeholder.selectBarcodeFormat')"
@click.stop
/>
</a-form-item>

12
packages/nc-gui/components/smartsheet/column/CurrencyOptions.vue

@ -12,6 +12,8 @@ const props = defineProps<{
const emit = defineEmits(['update:value'])
const { t } = useI18n()
const vModel = useVModel(props, 'value', emit)
const validators = {
@ -20,7 +22,7 @@ const validators = {
validator: (_: any, locale: any) => {
return new Promise<void>((resolve, reject) => {
if (!validateCurrencyLocale(locale)) {
return reject(new Error('Invalid locale'))
return reject(new Error(t('msg.invalidLocale')))
}
resolve()
})
@ -32,7 +34,7 @@ const validators = {
validator: (_: any, currencyCode: any) => {
return new Promise<void>((resolve, reject) => {
if (!validateCurrencyCode(currencyCode)) {
return reject(new Error('Invalid Currency Code'))
return reject(new Error(t('msg.invalidCurrencyCode')))
}
resolve()
})
@ -54,7 +56,7 @@ const currencyLocaleList = ref<{ text: string; value: string }[]>([])
const isMoney = computed(() => vModel.value.dt === 'money')
const message = computed(() => {
if (isMoney.value && isPg.value) return "PostgreSQL 'money' type has own currency settings"
if (isMoney.value && isPg.value) return t('msg.postgresHasItsOwnCurrencySettings')
return ''
})
@ -77,7 +79,7 @@ currencyLocales().then((locales) => {
<template>
<a-row gutter="8">
<a-col :span="12">
<a-form-item v-bind="validateInfos['meta.currency_locale']" label="Currency Locale">
<a-form-item v-bind="validateInfos['meta.currency_locale']" :label="$t('title.currencyLocale')">
<a-select
v-model:value="vModel.meta.currency_locale"
class="w-52"
@ -94,7 +96,7 @@ currencyLocales().then((locales) => {
</a-col>
<a-col :span="12">
<a-form-item v-bind="validateInfos['meta.currency_code']" label="Currency Code">
<a-form-item v-bind="validateInfos['meta.currency_code']" :label="$t('title.currencyCode')">
<a-select
v-model:value="vModel.meta.currency_code"
class="w-52"

2
packages/nc-gui/components/smartsheet/column/DateOptions.vue

@ -16,7 +16,7 @@ if (!vModel.value.meta?.date_format) {
</script>
<template>
<a-form-item label="Date Format">
<a-form-item :label="$t('labels.dateFormat')">
<a-select v-model:value="vModel.meta.date_format" dropdown-class-name="nc-dropdown-date-format">
<a-select-option v-for="(format, i) of dateFormats" :key="i" :value="format">
<div class="flex flex-row items-center">

4
packages/nc-gui/components/smartsheet/column/DateTimeOptions.vue

@ -21,14 +21,14 @@ if (!vModel.value.meta?.time_format) {
</script>
<template>
<a-form-item label="Date Format">
<a-form-item :label="$t('labels.dateFormat')">
<a-select v-model:value="vModel.meta.date_format" class="nc-date-select" dropdown-class-name="nc-dropdown-date-format">
<a-select-option v-for="(format, i) of dateFormats" :key="i" :value="format">
{{ format }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Time Format">
<a-form-item :label="$t('labels.timeFormat')">
<a-select v-model:value="vModel.meta.time_format" class="nc-time-select" dropdown-class-name="nc-dropdown-time-format">
<a-select-option v-for="(format, i) of timeFormats" :key="i" :value="format">
{{ format }}

20
packages/nc-gui/components/smartsheet/column/DecimalOptions.vue

@ -9,15 +9,17 @@ const emit = defineEmits(['update:value'])
const precisionFormats = [1, 2, 3, 4, 5, 6, 7, 8]
const { t } = useI18n()
const precisionFormatsDisplay = {
1: '1.0',
2: '1.00',
3: '1.000',
4: '1.0000',
5: '1.00000',
6: '1.000000',
7: '1.0000000',
8: '1.00000000',
1: t('placeholder.decimal1'),
2: t('placeholder.decimal2'),
3: t('placeholder.decimal3'),
4: t('placeholder.decimal4'),
5: t('placeholder.decimal5'),
6: t('placeholder.decimal6'),
7: t('placeholder.decimal7'),
8: t('placeholder.decimal8'),
}
const vModel = useVModel(props, 'value', emit)
@ -31,7 +33,7 @@ onMounted(() => {
</script>
<template>
<a-form-item label="Precision">
<a-form-item :label="$t('placeholder.precision')">
<a-select v-model:value="vModel.meta.precision" dropdown-class-name="nc-dropdown-decimal-format">
<a-select-option v-for="(format, i) of precisionFormats" :key="i" :value="format">
<div class="flex flex-row items-center">

2
packages/nc-gui/components/smartsheet/column/DefaultValue.vue

@ -46,7 +46,7 @@ useProvideSmartsheetRowStore(vModel, rowRef)
</script>
<template>
<div class="!my-3 text-xs">Default Value</div>
<div class="!my-3 text-xs">{{ $t('placeholder.defaultValue') }}</div>
<div class="flex flex-row gap-2">
<div class="border-1 flex items-center w-full px-3 my-[-4px] border-gray-300 rounded-md">
<LazySmartsheetCell :column="vModel" :model-value="cdfValue" :edit-enabled="true" />

4
packages/nc-gui/components/smartsheet/column/DurationOptions.vue

@ -26,11 +26,11 @@ vModel.value.meta = {
<template>
<a-row>
<a-col :span="24">
<span class="prose-sm mt-2">A duration of time in minutes or seconds (e.g. 1:23).</span>
<span class="prose-sm mt-2">{{ $t('labels.durationInfo') }}</span>
</a-col>
<a-col :span="24">
<a-form-item label="Duration Format">
<a-form-item :label="$t('labels.durationFormat')">
<a-select v-model:value="vModel.meta.duration" class="w-52" dropdown-class-name="nc-dropdown-duration-option">
<a-select-option v-for="(duration, i) of durationOptionList" :key="i" :value="duration.id">
{{ duration.title }}

109
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -34,6 +34,8 @@ const vModel = useVModel(props, 'value', emit)
const { formState, setAdditionalValidations, validateInfos, sqlUi, column } = useColumnCreateStoreOrThrow()
const { t } = useI18n()
const { loadMagic, predictFunction: _predictFunction } = useNocoEe()
enum JSEPNode {
@ -155,17 +157,17 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
const calleeName = parsedTree.callee.name.toUpperCase()
// validate function name
if (!availableFunctions.includes(calleeName)) {
errors.add(`'${calleeName}' function is not available`)
errors.add(t('msg.formula.functionNotAvailable', { function: calleeName }))
}
// validate arguments
const validation = formulas[calleeName] && formulas[calleeName].validation
if (validation && validation.args) {
if (validation.args.rqd !== undefined && validation.args.rqd !== parsedTree.arguments.length) {
errors.add(`'${calleeName}' required ${validation.args.rqd} arguments`)
errors.add(t('msg.formula.requiredArgumentsFormula', { requiredArguments: validation.args.rqd, calleeName }))
} else if (validation.args.min !== undefined && validation.args.min > parsedTree.arguments.length) {
errors.add(`'${calleeName}' required minimum ${validation.args.min} arguments`)
errors.add(t('msg.formula.minRequiredArgumentsFormula', { minRequiredArguments: validation.args.min, calleeName }))
} else if (validation.args.max !== undefined && validation.args.max < parsedTree.arguments.length) {
errors.add(`'${calleeName}' required maximum ${validation.args.max} arguments`)
errors.add(t('msg.formula.maxRequiredArgumentsFormula', { maxRequiredArguments: validation.args.max, calleeName }))
}
}
parsedTree.arguments.map((arg: Record<string, any>) => validateAgainstMeta(arg, errors))
@ -181,7 +183,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
formulaTypes.DATE,
(v: any) => {
if (!validateDateWithUnknownFormat(v)) {
typeErrors.add('The first parameter of WEEKDAY() should have date value')
typeErrors.add(t('msg.formula.firstParamWeekDayHaveDate'))
}
},
typeErrors,
@ -195,9 +197,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
typeof v !== 'string' ||
!['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'].includes(v.toLowerCase())
) {
typeErrors.add(
'The second parameter of WEEKDAY() should have the value either "sunday", "monday", "tuesday", "wednesday", "thursday", "friday" or "saturday"',
)
typeErrors.add(t('msg.formula.secondParamWeekDayHaveDate'))
}
},
typeErrors,
@ -213,7 +213,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
formulaTypes.DATE,
(v: any) => {
if (!validateDateWithUnknownFormat(v)) {
typeErrors.add('The first parameter of DATEADD() should have date value')
typeErrors.add(t('msg.formula.firstParamDateAddHaveDate'))
}
},
typeErrors,
@ -224,7 +224,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
formulaTypes.NUMERIC,
(v: any) => {
if (typeof v !== 'number') {
typeErrors.add('The second parameter of DATEADD() should have numeric value')
typeErrors.add(t('msg.formula.secondParamDateAddHaveNumber'))
}
},
typeErrors,
@ -235,7 +235,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
formulaTypes.STRING,
(v: any) => {
if (!['day', 'week', 'month', 'year'].includes(v)) {
typeErrors.add('The third parameter of DATEADD() should have the value either "day", "week", "month" or "year"')
typeErrors.add(typeErrors.add(t('msg.formula.thirdParamDateAddHaveDate')))
}
},
typeErrors,
@ -247,7 +247,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
formulaTypes.DATE,
(v: any) => {
if (!validateDateWithUnknownFormat(v)) {
typeErrors.add('The first parameter of DATETIME_DIFF() should have date value')
typeErrors.add(t('msg.formula.firstParamDateDiffHaveDate'))
}
},
typeErrors,
@ -258,7 +258,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
formulaTypes.DATE,
(v: any) => {
if (!validateDateWithUnknownFormat(v)) {
typeErrors.add('The second parameter of DATETIME_DIFF() should have date value')
typeErrors.add(t('msg.formula.secondParamDateDiffHaveDate'))
}
},
typeErrors,
@ -290,9 +290,7 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
'y',
].includes(v)
) {
typeErrors.add(
'The third parameter of DATETIME_DIFF() should have value either "milliseconds", "ms", "seconds", "s", "minutes", "m", "hours", "h", "days", "d", "weeks", "w", "months", "M", "quarters", "Q", "years", or "y"',
)
typeErrors.add(t('msg.formula.thirdParamDateDiffHaveDate'))
}
},
typeErrors,
@ -304,7 +302,11 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
errors = new Set([...errors, ...typeErrors])
} else if (parsedTree.type === JSEPNode.IDENTIFIER) {
if (supportedColumns.value.filter((c) => !column || column.value?.id !== c.id).every((c) => c.title !== parsedTree.name)) {
errors.add(`Column '${parsedTree.name}' is not available`)
errors.add(
t('msg.formula.columnNotAvailable', {
columnName: parsedTree.name,
}),
)
}
// check circular reference
@ -388,12 +390,12 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
}
// vertices not same as visited = cycle found
if (vertices !== visited) {
errors.add('Can’t save field because it causes a circular reference')
errors.add(t('msg.formula.cantSaveCircularReference'))
}
}
} else if (parsedTree.type === JSEPNode.BINARY_EXP) {
if (!availableBinOps.includes(parsedTree.operator)) {
errors.add(`'${parsedTree.operator}' operation is not available`)
errors.add(t('msg.formula.operationNotAvailable', { operation: parsedTree.operator }))
}
validateAgainstMeta(parsedTree.left, errors)
validateAgainstMeta(parsedTree.right, errors)
@ -401,10 +403,10 @@ function validateAgainstMeta(parsedTree: any, errors = new Set(), typeErrors = n
// do nothing
} else if (parsedTree.type === JSEPNode.COMPOUND) {
if (parsedTree.body.length) {
errors.add('Can’t save field because the formula is invalid')
errors.add(t('msg.formula.cantSaveFieldFormulaInvalid'))
}
} else {
errors.add('Can’t save field because the formula is invalid')
errors.add(t('msg.formula.cantSaveFieldFormulaInvalid'))
}
return errors
}
@ -418,11 +420,11 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
func(parsedTree.value)
} else if (expectedType === formulaTypes.NUMERIC) {
if (typeof parsedTree.value !== 'number') {
typeErrors.add('Numeric type is expected')
typeErrors.add(t('msg.formula.numericTypeIsExpected'))
}
} else if (expectedType === formulaTypes.STRING) {
if (typeof parsedTree.value !== 'string') {
typeErrors.add('string type is expected')
typeErrors.add(t('msg.formula.stringTypeIsExpected'))
}
}
} else if (parsedTree.type === JSEPNode.IDENTIFIER) {
@ -435,9 +437,14 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
if (col.uidt === UITypes.Formula) {
const foundType = getRootDataType(jsep((col as any).formula_raw))
if (foundType === 'N/A') {
typeErrors.add(`Not supported to reference column ${col.title}`)
typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: col.title }))
} else if (expectedType !== foundType) {
typeErrors.add(`Type ${expectedType} is expected but found Type ${foundType}`)
typeErrors.add(
t('msg.formula.typeIsExpectedButFound', {
type: expectedType,
found: foundType,
}),
)
}
} else {
switch (col.uidt) {
@ -451,7 +458,11 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
case UITypes.URL:
if (expectedType !== formulaTypes.STRING) {
typeErrors.add(
`Column '${parsedTree.name}' with ${formulaTypes.STRING} type is found but ${expectedType} type is expected`,
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: formulaTypes.STRING,
expectedType,
}),
)
}
break
@ -466,7 +477,11 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
case UITypes.Currency:
if (expectedType !== formulaTypes.NUMERIC) {
typeErrors.add(
`Column '${parsedTree.name}' with ${formulaTypes.NUMERIC} type is found but ${expectedType} type is expected`,
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: formulaTypes.NUMERIC,
expectedType,
}),
)
}
break
@ -478,7 +493,11 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
case UITypes.LastModifiedTime:
if (expectedType !== formulaTypes.DATE) {
typeErrors.add(
`Column '${parsedTree.name}' with ${formulaTypes.DATE} type is found but ${expectedType} type is expected`,
t('msg.formula.columnWithTypeFoundButExpected', {
columnName: parsedTree.name,
columnType: formulaTypes.DATE,
expectedType,
}),
)
}
break
@ -498,19 +517,29 @@ function validateAgainstType(parsedTree: any, expectedType: string, func: any, t
case UITypes.Collaborator:
case UITypes.QrCode:
default:
typeErrors.add(`Not supported to reference column '${parsedTree.name}'`)
typeErrors.add(t('msg.formula.notSupportedToReferenceColumn', { columnName: parsedTree.name }))
break
}
}
} else if (parsedTree.type === JSEPNode.UNARY_EXP || parsedTree.type === JSEPNode.BINARY_EXP) {
if (expectedType !== formulaTypes.NUMERIC) {
// parsedTree.name won't be available here
typeErrors.add(`${formulaTypes.NUMERIC} type is found but ${expectedType} type is expected`)
typeErrors.add(
t('msg.formula.typeIsExpectedButFound', {
type: formulaTypes.NUMERIC,
found: expectedType,
}),
)
}
} else if (parsedTree.type === JSEPNode.CALL_EXP) {
const calleeName = parsedTree.callee.name.toUpperCase()
if (formulas[calleeName]?.type && expectedType !== formulas[calleeName].type) {
typeErrors.add(`${expectedType} not matched with ${formulas[calleeName].type}`)
typeErrors.add(
t('msg.formula.typeIsExpectedButFound', {
type: expectedType,
found: formulas[calleeName].type,
}),
)
}
}
return typeErrors
@ -692,7 +721,7 @@ const predictFunction = async () => {
<template>
<div class="formula-wrapper">
<a-form-item v-bind="validateInfos.formula_raw" label="Formula">
<a-form-item v-bind="validateInfos.formula_raw" :label="$t('datatype.Formula')">
<GeneralIcon
v-if="isEeUI"
icon="magic"
@ -712,14 +741,20 @@ const predictFunction = async () => {
</a-form-item>
<div class="text-gray-600 mt-2 mb-4 prose-sm">
Hint: Use {} to reference columns, e.g: {column_name}. For more, please check out
{{
// As using {} in translation will be treated as placeholder, and this translation contain {} as part of th text
$t('msg.formula.hintStart', {
placeholder1: '{}',
placeholder2: '{column_name}',
})
}}
<a class="prose-sm" href="https://docs.nocodb.com/setup-and-usages/formulas#available-formula-features" target="_blank">
Formulas.
{{ $t('msg.formula.hintEnd') }}
</a>
</div>
<div class="h-[250px] overflow-auto scrollbar-thin-primary">
<a-list ref="sugListRef" :data-source="suggestion" :locale="{ emptyText: 'No suggested formula was found' }">
<a-list ref="sugListRef" :data-source="suggestion" :locale="{ emptyText: $t('msg.formula.noSuggestedFormulaFound') }">
<template #renderItem="{ item, index }">
<a-list-item
:ref="
@ -740,9 +775,9 @@ const predictFunction = async () => {
<a-col :span="18">
<div v-if="item.type === 'function'" class="text-xs text-gray-500">
{{ item.description }} <br /><br />
Syntax: <br />
{{ $t('labels.syntax') }}: <br />
{{ item.syntax }} <br /><br />
Examples: <br />
{{ $t('labels.examples') }}: <br />
<div v-for="(example, idx) of item.examples" :key="idx">
<div>({{ idx + 1 }}): {{ example }}</div>

22
packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue

@ -19,8 +19,10 @@ const { setAdditionalValidations, validateInfos, onDataTypeChange, sqlUi, isXcdb
const projectStore = useProject()
const { tables } = storeToRefs(projectStore)
const { t } = useI18n()
setAdditionalValidations({
childId: [{ required: true, message: 'Required' }],
childId: [{ required: true, message: t('general.required') }],
})
const onUpdateDeleteOptions = sqlUi === MssqlUi ? ['NO ACTION'] : ['NO ACTION', 'CASCADE', 'RESTRICT', 'SET NULL', 'SET DEFAULT']
@ -110,7 +112,14 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
@change="onDataTypeChange"
>
<a-select-option v-for="(option, i) of onUpdateDeleteOptions" :key="i" :value="option">
{{ option }}
<template v-if="option === 'NO ACTION'">{{ $t('title.links.noAction') }}</template>
<template v-else-if="option === 'CASCADE'">{{ $t('title.links.cascade') }}</template>
<template v-else-if="option === 'RESTRICT'">{{ $t('title.links.restrict') }}</template>
<template v-else-if="option === 'SET NULL'">{{ $t('title.links.setNull') }}</template>
<template v-else-if="option === 'SET DEFAULT'">{{ $t('title.links.setDefault') }}</template>
<template v-else>
{{ option }}
</template>
</a-select-option>
</a-select>
</a-form-item>
@ -124,7 +133,14 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
@change="onDataTypeChange"
>
<a-select-option v-for="(option, i) of onUpdateDeleteOptions" :key="i" :value="option">
{{ option }}
<template v-if="option === 'NO ACTION'">{{ $t('title.links.noAction') }}</template>
<template v-else-if="option === 'CASCADE'">{{ $t('title.links.cascade') }}</template>
<template v-else-if="option === 'RESTRICT'">{{ $t('title.links.restrict') }}</template>
<template v-else-if="option === 'SET NULL'">{{ $t('title.links.setNull') }}</template>
<template v-else-if="option === 'SET DEFAULT'">{{ $t('title.links.setDefault') }}</template>
<template v-else>
{{ option }}
</template>
</a-select-option>
</a-select>
</a-form-item>

6
packages/nc-gui/components/smartsheet/column/LookupOptions.vue

@ -14,6 +14,8 @@ const vModel = useVModel(props, 'value', emit)
const meta = inject(MetaInj, ref())
const { t } = useI18n()
const { setAdditionalValidations, validateInfos, onDataTypeChange, isEdit } = useColumnCreateStoreOrThrow()
const projectStore = useProject()
@ -22,8 +24,8 @@ const { tables } = storeToRefs(projectStore)
const { metas } = useMetas()
setAdditionalValidations({
fk_relation_column_id: [{ required: true, message: 'Required' }],
fk_lookup_column_id: [{ required: true, message: 'Required' }],
fk_relation_column_id: [{ required: true, message: t('general.required') }],
fk_lookup_column_id: [{ required: true, message: t('general.required') }],
})
if (!vModel.value.fk_relation_column_id) vModel.value.fk_relation_column_id = null

10
packages/nc-gui/components/smartsheet/column/PercentOptions.vue

@ -20,7 +20,7 @@ if (!vModel.value.meta?.precision) vModel.value.meta.precision = precisions[0].i
<template>
<div class="flex flex-col mt-2 gap-2">
<div class="flex flex-row space-x-2">
<a-form-item class="flex w-1/2" label="Precision">
<a-form-item class="flex w-1/2" :label="$t('placeholder.precision')">
<a-select v-model:value="vModel.meta.precision" dropdown-class-name="nc-dropdown-precision">
<a-select-option v-for="(precision, i) of precisions" :key="i" :value="precision.id">
<div class="flex flex-row items-center">
@ -31,15 +31,15 @@ if (!vModel.value.meta?.precision) vModel.value.meta.precision = precisions[0].i
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="Default Number (%)">
<a-input v-model:value="vModel.meta.default" name="default" type="number" />
<a-form-item :label="$t('labels.defaultNumberPercent')">
<a-input v-model:value="vModel.meta.default" :name="$t('labels.default')" type="number" />
</a-form-item>
</div>
<div class="flex flex-row mt-2">
<a-form-item>
<div class="flex flex-row space-x-2 items-center">
<a-switch v-model:checked="vModel.meta.negative" name="negative" />
<div class="text-xs">Allow negative numbers</div>
<a-switch v-model:checked="vModel.meta.negative" :name="$t('labels.negative')" />
<div class="text-xs">{{ $t('placeholder.allowNegativeNumbers') }}</div>
</div>
</a-form-item>
</div>

12
packages/nc-gui/components/smartsheet/column/PgBinaryOptions.vue

@ -23,10 +23,14 @@ vModel.value.meta = {
<template>
<a-row class="my-2" gutter="8">
<a-col :span="24">
<a-form-item v-bind="validateInfos['meta.format']" label="Binary encoding format">
<a-select v-model:value="vModel.meta.format" placeholder="Binary encoding format" class="!w-full nc-link-singular">
<a-select-option value="escape">Escape</a-select-option>
<a-select-option value="hex">Hex</a-select-option>
<a-form-item v-bind="validateInfos['meta.format']" :label="$t('labels.binaryEncodingFormat')">
<a-select
v-model:value="vModel.meta.format"
:placeholder="$t('labels.binaryEncodingFormat')"
class="!w-full nc-link-singular"
>
<a-select-option value="escape">{{ $t('general.escape') }}</a-select-option>
<a-select-option value="hex">{{ $t('general.hex') }}</a-select-option>
</a-select>
</a-form-item>
</a-col>

6
packages/nc-gui/components/smartsheet/column/QrCodeOptions.vue

@ -14,6 +14,8 @@ const meta = inject(MetaInj, ref())
const activeView = inject(ActiveViewInj, ref())
const { t } = useI18n()
const reloadDataHook = inject(ReloadViewDataHookInj)!
const { fields, metaColumnById } = useViewColumns(activeView, meta, () => reloadDataHook.trigger())
@ -42,7 +44,7 @@ onMounted(() => {
})
setAdditionalValidations({
fk_qr_value_column_id: [{ required: true, message: 'Required' }],
fk_qr_value_column_id: [{ required: true, message: t('general.required') }],
})
</script>
@ -57,7 +59,7 @@ setAdditionalValidations({
<a-select
v-model:value="vModel.fk_qr_value_column_id"
:options="columnsAllowedAsQrValue"
placeholder="Select a column for the QR code value"
:placeholder="$t('placeholder.selectAColumnForTheQRCodeValue')"
@click.stop
/>
</a-form-item>

4
packages/nc-gui/components/smartsheet/column/RatingOptions.vue

@ -71,7 +71,7 @@ watch(
<template>
<a-row :gutter="8">
<a-col :span="12">
<a-form-item label="Icon">
<a-form-item :label="$t('labels.icon')">
<a-select v-model:value="vModel.meta.iconIdx" class="w-52" dropdown-class-name="nc-dropdown-rating-icon">
<a-select-option v-for="(icon, i) of iconList" :key="i" :value="i">
<div class="flex items-center">
@ -95,7 +95,7 @@ watch(
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="Max">
<a-form-item :label="$t('labels.max')">
<a-select v-model:value="vModel.meta.max" class="w-52" dropdown-class-name="nc-dropdown-rating-color">
<a-select-option v-for="(v, i) in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]" :key="i" :value="v">
{{ v }}

26
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -21,21 +21,23 @@ const { tables } = storeToRefs(projectStore)
const { metas } = useMetas()
const { t } = useI18n()
setAdditionalValidations({
fk_relation_column_id: [{ required: true, message: 'Required' }],
fk_rollup_column_id: [{ required: true, message: 'Required' }],
rollup_function: [{ required: true, message: 'Required' }],
fk_relation_column_id: [{ required: true, message: t('general.required') }],
fk_rollup_column_id: [{ required: true, message: t('general.required') }],
rollup_function: [{ required: true, message: t('general.required') }],
})
const aggrFunctionsList = [
{ text: 'count', value: 'count' },
{ text: 'min', value: 'min' },
{ text: 'max', value: 'max' },
{ text: 'avg', value: 'avg' },
{ text: 'sum', value: 'sum' },
{ text: 'countDistinct', value: 'countDistinct' },
{ text: 'sumDistinct', value: 'sumDistinct' },
{ text: 'avgDistinct', value: 'avgDistinct' },
{ text: t('datatype.Count'), value: 'count' },
{ text: t('general.min'), value: 'min' },
{ text: t('general.max'), value: 'max' },
{ text: t('general.avg'), value: 'avg' },
{ text: t('general.sum'), value: 'sum' },
{ text: t('general.countDistinct'), value: 'countDistinct' },
{ text: t('general.sumDistinct'), value: 'sumDistinct' },
{ text: t('general.avgDistinct'), value: 'avgDistinct' },
]
if (!vModel.value.fk_relation_column_id) vModel.value.fk_relation_column_id = null
@ -133,7 +135,7 @@ const cellIcon = (column: ColumnType) =>
</a-form-item>
</div>
<a-form-item label="Aggregate function" v-bind="validateInfos.rollup_function">
<a-form-item :label="$t('labels.aggregateFunction')" v-bind="validateInfos.rollup_function">
<a-select
v-model:value="vModel.rollup_function"
dropdown-class-name="nc-dropdown-rollup-function"

8
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -49,6 +49,8 @@ const defaultOption = ref()
const isKanban = inject(IsKanbanInj, ref(false))
const { t } = useI18n()
const validators = {
colOptions: [
{
@ -61,13 +63,13 @@ const validators = {
if ((opt as any).status === 'remove') continue
if (!opt.title.length) {
return reject(new Error("Select options can't be null"))
return reject(new Error(t('msg.selectOption.cantBeNull')))
}
if (vModel.value.uidt === UITypes.MultiSelect && opt.title.includes(',')) {
return reject(new Error("MultiSelect columns can't have commas(',')"))
return reject(new Error(t('msg.selectOption.multiSelectCantHaveCommas')))
}
if (options.value.filter((el) => el.title === opt.title && (el as any).status !== 'remove').length > 1) {
return reject(new Error("Select options can't have duplicates"))
return reject(new Error(t('msg.selectOption.cantHaveDuplicates')))
}
}
resolve()

12
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -235,7 +235,7 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
if (!ctx || !hasEditPermission.value || (!isLinksOrLTAR(fields.value[ctx.col]) && isVirtualCol(fields.value[ctx.col]))) return
if (fields.value[ctx.col]?.uidt === UITypes.Links) {
return message.info('Links column clear is not supported yet')
return message.info(t('msg.linkColumnClearNotSupportedYet'))
}
const rowObj = dataRef.value[ctx.row]
@ -270,10 +270,10 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
activeCell.row = ctx.row
scrollToCell?.()
} else {
throw new Error('Record could not be found')
throw new Error(t('msg.recordCouldNotBeFound'))
}
} else {
throw new Error('Page size changed')
throw new Error(t('msg.pageSizeChanged'))
}
},
args: [clone(ctx), clone(columnObj), clone(rowObj), clone(paginationDataRef.value)],
@ -297,10 +297,10 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
activeCell.row = ctx.row
scrollToCell?.()
} else {
throw new Error('Record could not be found')
throw new Error(t('msg.recordCouldNotBeFound'))
}
} else {
throw new Error('Page size changed')
throw new Error(t('msg.pageSizeChanged'))
}
},
args: [clone(ctx), clone(columnObj), clone(rowObj), clone(paginationDataRef.value)],
@ -1104,7 +1104,7 @@ watch(
await loadData?.()
} catch (e) {
console.log(e)
message.error('Error loading data')
message.error(t('msg.errorLoadingData'))
} finally {
isViewDataLoading.value = false
}

6
packages/nc-gui/components/smartsheet/grid/index.vue

@ -39,6 +39,8 @@ const route = router.currentRoute
const { xWhere, eventBus } = useSmartsheetStoreOrThrow()
const { t } = useI18n()
const bulkUpdateDlg = ref(false)
const routeQuery = computed(() => route.value.query as Record<string, string>)
@ -151,10 +153,10 @@ const addRowExpandOnClose = (row: Row) => {
const toggleOptimisedQuery = () => {
if (optimisedQuery.value) {
optimisedQuery.value = false
message.info('Optimised query disabled')
message.info(t('msg.optimizedQueryDisabled'))
} else {
optimisedQuery.value = true
message.info('Optimised query enabled')
message.info(t('msg.optimizedQueryEnabled'))
}
}

6
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -43,9 +43,11 @@ const { nestedLevel, parentId, autoSave, hookId, modelValue, showLoading, webHoo
const nested = computed(() => nestedLevel.value > 0)
const { t } = useI18n()
const logicalOps = [
{ value: 'and', text: 'AND' },
{ value: 'or', text: 'OR' },
{ value: 'and', text: t('general.and') },
{ value: 'or', text: t('general.or') },
]
const meta = inject(MetaInj, ref())

6
packages/nc-gui/components/virtual-cell/QrCode.vue

@ -61,7 +61,7 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = us
{{ qrValue }}
</div>
</template>
<img v-if="showQrCode" :src="qrCodeLarge" alt="QR Code" />
<img v-if="showQrCode" :src="qrCodeLarge" :alt="$t('title.qrCode')" />
</a-modal>
<div v-if="tooManyCharsForQrCode" class="text-left text-wrap mt-2 text-[#e65100] text-[10px]">
{{ $t('labels.qrCodeValueTooLong') }}
@ -71,10 +71,10 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = us
:class="{ 'mx-auto': !isGallery }"
:style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem` }"
:src="qrCode"
alt="QR Code"
:alt="$t('title.qrCode')"
@click="showQrModal"
/>
<img v-else-if="showQrCode" class="mx-auto" :src="qrCode" alt="QR Code" @click="showQrModal" />
<img v-else-if="showQrCode" class="mx-auto" :src="qrCode" :alt="$t('title.qrCode')" @click="showQrModal" />
<div v-if="showEditNonEditableFieldWarning" class="text-left text-wrap mt-2 text-[#e65100] text-xs">
{{ $t('msg.warning.nonEditableFields.computedFieldUnableToClear') }}
</div>

107
packages/nc-gui/components/webhook/Editor.vue

@ -94,22 +94,22 @@ const formInput = ref({
'Email': [
{
key: 'to',
label: 'To Address',
placeholder: 'To Address',
label: t('labels.toAddress'),
placeholder: t('labels.toAddress'),
type: 'SingleLineText',
required: true,
},
{
key: 'subject',
label: 'Subject',
placeholder: 'Subject',
label: t('labels.subject'),
placeholder: t('labels.subject'),
type: 'SingleLineText',
required: true,
},
{
key: 'body',
label: 'Body',
placeholder: 'Body',
label: t('labels.body'),
placeholder: t('labels.body'),
type: 'LongText',
required: true,
},
@ -117,8 +117,8 @@ const formInput = ref({
'Slack': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
label: t('labels.body'),
placeholder: t('labels.body'),
type: 'LongText',
required: true,
},
@ -126,8 +126,8 @@ const formInput = ref({
'Microsoft Teams': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
label: t('labels.body'),
placeholder: t('labels.body'),
type: 'LongText',
required: true,
},
@ -135,8 +135,8 @@ const formInput = ref({
'Discord': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
label: t('labels.body'),
placeholder: t('labels.body'),
type: 'LongText',
required: true,
},
@ -144,8 +144,8 @@ const formInput = ref({
'Mattermost': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
label: t('labels.body'),
placeholder: t('labels.body'),
type: 'LongText',
required: true,
},
@ -153,15 +153,15 @@ const formInput = ref({
'Twilio': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
label: t('labels.body'),
placeholder: t('labels.body'),
type: 'LongText',
required: true,
},
{
key: 'to',
label: 'Comma separated Mobile #',
placeholder: 'Comma separated Mobile #',
label: t('labels.commaSeparatedMobileNumber'),
placeholder: t('labels.commaSeparatedMobileNumber'),
type: 'LongText',
required: true,
},
@ -169,15 +169,15 @@ const formInput = ref({
'Whatsapp Twilio': [
{
key: 'body',
label: 'Body',
placeholder: 'Body',
label: t('labels.body'),
placeholder: t('labels.body'),
type: 'LongText',
required: true,
},
{
key: 'to',
label: 'Comma separated Mobile #',
placeholder: 'Comma separated Mobile #',
label: t('labels.commaSeparatedMobileNumber'),
placeholder: t('labels.commaSeparatedMobileNumber'),
type: 'LongText',
required: true,
},
@ -196,26 +196,26 @@ const showLogs = computed(
)
const eventList = ref<Record<string, any>[]>([
{ text: ['After', 'Insert'], value: ['after', 'insert'] },
{ text: ['After', 'Update'], value: ['after', 'update'] },
{ text: ['After', 'Delete'], value: ['after', 'delete'] },
{ text: ['After', 'Bulk Insert'], value: ['after', 'bulkInsert'] },
{ text: ['After', 'Bulk Update'], value: ['after', 'bulkUpdate'] },
{ text: ['After', 'Bulk Delete'], value: ['after', 'bulkDelete'] },
{ text: [t('general.after'), t('general.insert')], value: ['after', 'insert'] },
{ text: [t('general.after'), t('general.update')], value: ['after', 'update'] },
{ text: [t('general.after'), t('general.delete')], value: ['after', 'delete'] },
{ text: [t('general.after'), t('general.bulkInsert')], value: ['after', 'bulkInsert'] },
{ text: [t('general.after'), t('general.bulkUpdate')], value: ['after', 'bulkUpdate'] },
{ text: [t('general.after'), t('general.bulkDelete')], value: ['after', 'bulkDelete'] },
])
const notificationList = computed(() => {
return isEeUI
? [{ type: 'URL' }]
? [{ type: 'URL', text: t('datatype.URL') }]
: [
{ type: 'URL' },
{ type: 'Email' },
{ type: 'Slack' },
{ type: 'Microsoft Teams' },
{ type: 'Discord' },
{ type: 'Mattermost' },
{ type: 'Twilio' },
{ type: 'Whatsapp Twilio' },
{ type: 'URL', text: t('datatype.URL') },
{ type: 'Email', text: t('datatype.Email') },
{ type: 'Slack', text: t('general.slack') },
{ type: 'Microsoft Teams', text: t('general.microsoftTeams') },
{ type: 'Discord', text: t('general.discord') },
{ type: 'Mattermost', text: t('general.matterMost') },
{ type: 'Twilio', text: t('general.twilio') },
{ type: 'Whatsapp Twilio', text: t('general.whatsappTwilio') },
]
})
@ -524,7 +524,7 @@ onMounted(async () => {
ref="titleDomRef"
v-model="hookRef.title"
class="flex flex-grow text-lg font-medium capitalize outline-none bg-inherit nc-text-field-hook-title"
placeholder="Webhook Title"
:placeholder="$t('placeholder.webhookTitle')"
:contenteditable="true"
@blur="isRenaming = false"
@focus="isRenaming = true"
@ -535,7 +535,7 @@ onMounted(async () => {
</div>
<div class="flex flex-row gap-2">
<NcButton class="nc-btn-webhook-test" type="secondary" size="small" @click="testWebhook">
<div class="flex items-center px-1">Test Webhook</div>
<div class="flex items-center px-1">{{ $t('activity.testWebhook') }}</div>
</NcButton>
<NcButton
@ -546,7 +546,7 @@ onMounted(async () => {
:disabled="!isValid"
@click.prevent="saveHooks"
>
<template #loading> Saving </template>
<template #loading> {{ $t('general.saving') }} </template>
<div class="flex items-center px-1">{{ $t('general.save') }}</div>
</NcButton>
</div>
@ -562,7 +562,7 @@ onMounted(async () => {
>
<a-form :model="hookRef" name="create-or-edit-webhook">
<a-form-item>
<div class="form-field-header">Event</div>
<div class="form-field-header">{{ $t('general.event') }}</div>
<a-row type="flex" :gutter="[16, 16]">
<a-col :span="12">
<a-form-item v-bind="validateInfos.eventOperation">
@ -608,7 +608,7 @@ onMounted(async () => {
<MdiCellphoneMessage v-if="notificationOption.type === 'Twilio'" class="mr-2" />
{{ notificationOption.type }}
{{ notificationOption.text }}
</div>
</a-select-option>
</NcSelect>
@ -654,11 +654,11 @@ onMounted(async () => {
/>
</a-tab-pane>
<a-tab-pane key="params" tab="Parameters" force-render>
<a-tab-pane key="params" :tab="$t('title.parameter')" force-render>
<LazyApiClientParams v-model="hookRef.notification.payload.parameters" class="p-4" />
</a-tab-pane>
<a-tab-pane key="headers" tab="Headers" class="nc-tab-headers">
<a-tab-pane key="headers" :tab="$t('title.headers')" class="nc-tab-headers">
<LazyApiClientHeaders v-model="hookRef.notification.payload.headers" class="!p-4" />
</a-tab-pane>
@ -682,7 +682,7 @@ onMounted(async () => {
v-model="hookRef.notification.payload.channels"
:selected-channel-list="hookRef.notification.payload.channels"
:available-channel-list="slackChannels"
placeholder="Select Slack channels"
:placeholder="$t('placeholder.selectSlackChannels')"
/>
</a-form-item>
</a-col>
@ -695,7 +695,7 @@ onMounted(async () => {
v-model="hookRef.notification.payload.channels"
:selected-channel-list="hookRef.notification.payload.channels"
:available-channel-list="teamsChannels"
placeholder="Select Microsoft Teams channels"
:placeholder="$t('placeholder.selectTeamsChannels')"
/>
</a-form-item>
</a-col>
@ -708,7 +708,7 @@ onMounted(async () => {
v-model="hookRef.notification.payload.channels"
:selected-channel-list="hookRef.notification.payload.channels"
:available-channel-list="discordChannels"
placeholder="Select Discord channels"
:placeholder="$t('placeholder.selectDiscordChannels')"
/>
</a-form-item>
</a-col>
@ -721,7 +721,7 @@ onMounted(async () => {
v-model="hookRef.notification.payload.channels"
:selected-channel-list="hookRef.notification.payload.channels"
:available-channel-list="mattermostChannels"
placeholder="Select Mattermost channels"
:placeholder="$t('placeholder.selectMattermostChannels')"
/>
</a-form-item>
</a-col>
@ -747,7 +747,7 @@ onMounted(async () => {
class="nc-check-box-hook-condition"
@update:checked="hookRef.condition = $event"
>
On Condition
{{ $t('activity.onCondition') }}
</a-checkbox>
<LazySmartsheetToolbarColumnFilter
@ -767,11 +767,16 @@ onMounted(async () => {
<a-col :span="24">
<div v-if="isBodyShown" class="text-gray-600">
<div class="flex items-center">
<em>Use context variable <strong>data</strong> to refer the record under consideration</em>
<em
>{{ $t('msg.webhookBodyMsg1') }} <strong>{{ $t('msg.webhookBodyMsg2') }}</strong>
{{ $t('msg.webhookBodyMsg3') }}</em
>
<a-tooltip bottom>
<template #title>
<span> <strong>data</strong> : Row data <br /> </span>
<span>
<strong>{{ $t('general.data') }}</strong> : {{ $t('title.rowData') }} <br />
</span>
</template>
<component :is="iconMap.info" class="ml-2" />
</a-tooltip>

155
packages/nc-gui/lang/en.json

@ -59,10 +59,20 @@
"empty": "Empty",
"changeIcon": "Change Icon",
"save": "Save",
"available": "Available",
"abort": "Abort",
"saving": "Saving",
"cancel": "Cancel",
"null": "Null",
"escape": "Escape",
"hex": "Hex",
"clear": "Clear",
"slack": "Slack",
"microsoftTeams": "Microsoft Teams",
"discord": "Discord",
"matterMost": "Mattermost",
"twilio": "Twilio",
"whatsappTwilio": "WhatsApp Twilio",
"submit": "Submit",
"create": "Create",
"details": "Details",
@ -73,6 +83,9 @@
"action": "Action",
"insert": "Insert",
"delete": "Delete",
"bulkInsert": "Bulk Insert",
"bulkDelete": "Bulk Delete",
"bulkUpdate": "Bulk Update",
"deleting": "Deleting",
"update": "Update",
"rename": "Rename",
@ -86,6 +99,7 @@
"deprecated": "Deprecated",
"showAll": "Show all",
"hideAll": "Hide all",
"notFound": "Not found",
"showMore": "Show more",
"showOptions": "Show options",
"hideOptions": "Hide options",
@ -150,7 +164,15 @@
"active": "Active",
"inactive": "Inactive",
"linked": "linked",
"finish": "Finish"
"finish": "Finish",
"min": "Min",
"max": "Max",
"avg": "Avg",
"sum": "Sum",
"count": "Count",
"countDistinct": "Count Distinct",
"sumDistinct": "Sum Distinct",
"avgDistinct": "Avg Distinct"
},
"objects": {
"workspace": "Workspace",
@ -257,16 +279,26 @@
"isNotNull": "is not null"
},
"title": {
"parameter": "Parameter",
"headers": "Headers",
"parameterName": "Parameter Name",
"currencyLocale": "Currency Locale",
"currencyCode": "Currency Code",
"searchMembers": "Search Members",
"noMembersFound": "No members found",
"dateJoined": "Date Joined",
"tokenName": "Token name",
"inDesktop": "in Desktop",
"rowData": "Row data",
"creator": "Creator",
"qrCode": "QR Code",
"termsOfService": "Terms of Service",
"updateSelectedRows": "Update Selected Rows",
"noFiltersAdded": "No filters added",
"editCards": "Edit Cards",
"noFieldsFound": "No fields found",
"displayValue": "Display Value",
"expand": "Expand",
"hideAll":"Hide all",
"hideSystemFields": "hideSystemFields",
"removeFile": "Remove File",
@ -343,12 +375,38 @@
"defaultView": "Default View",
"relations": "Relations",
"switchLanguage": "Switch Language",
"renameFile": "Rename File"
"renameFile": "Rename File",
"links": {
"noAction": "No Action",
"cascade": "Cascade",
"restrict": "Restrict",
"setNull": "Set NULL",
"setDefault": "Set Default"
}
},
"labels": {
"toAddress": "To Address",
"subject": "Subject",
"body": "Body",
"commaSeparatedMobileNumber": "Comma separated Mobile #",
"headerName": "Header Name",
"icon": "Icon",
"max": "Max",
"binaryEncodingFormat": "Binary encoding format",
"syntax": "Syntax",
"examples": "Examples",
"durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).",
"addHeader": "Add Header",
"enterDefaultUrlOptional": "Enter default URL (Optional)",
"negative": "Negative",
"default": "Default",
"defaultNumberPercent": "Default Number (%)",
"durationFormat": "Duration Format",
"dateFormat": "Date Format",
"timeFormat": "Time Format",
"singularLabel": "Singular Label",
"pluralLabel": "Plural Label",
"optional": "Optional",
"optional": "(Optional)",
"clickToMake": "Click to make",
"visibleForRole": "visible for role:",
"inUI": "in UI Dashboard",
@ -519,6 +577,7 @@
"zoomInToViewColumns": "Zoom in to view columns"
},
"activity": {
"onCondition": "On Condition",
"bulkDownload": "Bulk Download",
"attachFile": "Attach File",
"viewAttachment": "View Attachments",
@ -526,7 +585,11 @@
"addFiles": "Add File(s)",
"hideInUI": "Hide in UI",
"addBase": "Add Base",
"addParameter": "Add Parameter",
"submitAnotherForm": "Submit Another Form",
"dragAndDropFieldsHereToAdd": "Drag and drop fields here to add",
"editBase": "Edit Base",
"enterText": "Enter text",
"okEditBase": "Ok & Edit Base",
"showInUI": "Show in UI",
"outOfSync": "Out of sync",
@ -736,6 +799,14 @@
"clientCA": "Select CA file"
},
"placeholder": {
"selectSlackChannels": "Select Slack channels",
"selectTeamsChannels": "Select Microsoft Teams channels",
"selectDiscordChannels": "Select Discord channels",
"selectMattermostChannels": "Select Mattermost channels",
"webhookTitle": "Webhook Title",
"barcodeColumn": "Select a column for the Barcode value",
"notFoundContent": "No valid Column Type can be found.",
"selectBarcodeFormat": "Select a Barcode format",
"projName": "Enter Project Name",
"selectGroupField": "Select a Grouping Field",
"selectGroupFieldNotFound" : "No Single Select Field can be found. Please create one first.",
@ -748,6 +819,8 @@
"save": "Save password",
"confirm": "Confirm new password"
},
"selectAColumnForTheQRCodeValue": "Select a column for the QR code value",
"allowNegativeNumbers": "Allow negative numbers",
"searchProjectTree": "Search tables",
"searchFields": "Search fields",
"searchColumn": "Search {search} column",
@ -757,30 +830,95 @@
"defaultValue": "Default value",
"filterByEmail": "Filter by E-mail",
"filterQuery": "Filter query",
"selectField": "Select field"
"selectField": "Select field",
"precision": "Precision",
"decimal1": "1.0",
"decimal2": "1.00",
"decimal3": "1.000",
"decimal4": "1.0000",
"decimal5": "1.00000",
"decimal6": "1.000000",
"decimal7": "1.0000000",
"decimal8": "1.00000000",
"value": "Value",
"key": "Key"
},
"msg": {
"enterPassword":"Enter password",
"bySigningUp": "By signing up, you agree to the",
"subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter",
"verifyingPassword": "Verifying Password",
"thisSharedViewIsProtected": "This shared view is protected",
"successfullySubmittedFormData": "Successfully submitted form data",
"formViewNotSupportedOnMobile": "Form view is not supported on mobile",
"newFormWillBeLoaded": "New form will be loaded after {seconds} seconds",
"optimizedQueryDisabled": "Optimized query is disabled",
"optimizedQueryEnabled": "Optimized query is enabled",
"invalidTime": "Invalid Time",
"linkColumnClearNotSupportedYet": "Link column clear is not supported yet",
"recordCouldNotBeFound": "Record could not be found",
"invalidPhoneNumber": "Invalid phone number",
"pageSizeChanged": "Page size changed",
"errorLoadingData": "Error loading data",
"webhookBodyMsg1": "Use context variable",
"webhookBodyMsg2": "body",
"webhookBodyMsg3": "to refer the record under consideration",
"formula": {
"hintStart": "Hint: Use {placeholder1} to reference columns, e.g: {placeholder2}. For more, please check out",
"hintEnd": "Formulas.",
"noSuggestedFormulaFound": "No suggested formula found",
"numericTypeIsExpected": "Numeric type is expected",
"stringTypeIsExpected": "String type is expected",
"operationNotAvailable": "{operation} operation not available",
"cantSaveFieldFormulaInvalid": "Can’t save field because formula is invalid",
"notSupportedToReferenceColumn": "Not supported to reference column {columnName}",
"typeIsExpectedButFound": "Type {type} is expected but found Type {found}",
"requiredArgumentsFormula": "{calleeName} requires {requiredArguments} arguments",
"minRequiredArgumentsFormula": "{calleeName} required minimum {minRequiredArguments} arguments",
"maxRequiredArgumentsFormula": "{calleeName} required maximum {maxRequiredArguments} arguments",
"functionNotAvailable": "{function} function is not available",
"firstParamWeekDayHaveDate": "The first parameter of WEEKDAY() should have date value",
"secondParamWeekDayHaveDate": "The second parameter of WEEKDAY() should have the value either \"sunday\", \"monday\", \"tuesday\", \"wednesday\", \"thursday\", \"friday\" or \"saturday\"",
"firstParamDateAddHaveDate": "The first parameter of DATEADD() should have date value",
"secondParamDateAddHaveNumber": "The second parameter of DATEADD() should have numeric value",
"thirdParamDateAddHaveDate": "The third parameter of DATEADD() should have the value either \"day\", \"week\", \"month\" or \"year\"",
"firstParamDateDiffHaveDate": "The first parameter of DATEDIFF() should have date value",
"secondParamDateDiffHaveDate": "The second parameter of DATEDIFF() should have date value",
"thirdParamDateDiffHaveDate": "The third parameter of DATETIME_DIFF() should have value either \"milliseconds\", \"ms\", \"seconds\", \"s\", \"minutes\", \"m\", \"hours\", \"h\", \"days\", \"d\", \"weeks\", \"w\", \"months\", \"M\", \"quarters\", \"Q\", \"years\", or \"y\"",
"columnNotAvailable": "Column {columnName} is not available",
"cantSaveCircularReference": "Can’t save field because it causes a circular reference",
"columnWithTypeFoundButExpected": "Column {columnName} with {columnType} type is found but {expectedType} type is expected",
"columnNotMatchedWithType": "{columnName} is not matched with {columnType}"
},
"selectOption": {
"cantBeNull": "Select options can't be null",
"multiSelectCantHaveCommas": "MultiSelect columns can't have commas(',')",
"cantHaveDuplicates": "Select options can't have duplicates",
"createNewOptionNamed": "Create new option named"
},
"plsEnterANumber": "Please enter a number",
"invalidDate": "Invalid date",
"invalidLocale": "Invalid locale",
"invalidCurrencyCode": "Invalid Currency Code",
"postgresHasItsOwnCurrencySettings": "PostgreSQL 'money' type has own currency settings",
"validColumnsForBarCode": "The valid Column Types for a Barcode Column are: Number, Single Line Text, Long Text, Phone Number, URL, Email, Decimal. Please create one first.",
"hm": {
"title": "Has Many Relation",
"icon": "HasManyIcon",
"tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with multiple records from table "
},
"mm": {
"title": "Many to Many Relation",
"icon": "ManytoManyIcon",
"tooltip_desc": "Multiple records from table ",
"tooltip_desc2": " can be linked with multiple records from table "
},
"bt": {
"title": "Belongs to Relation",
"icon": "BelongsToIcon",
"tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a record from table "
},
"oo": {
"title": "One to One Relation",
"icon": "OnetoOneIcon",
"tooltip_desc": "A single record from table ",
"tooltip_desc2": " can be linked with a single record from table "
},
@ -968,6 +1106,7 @@
"invalidChar": "Invalid character in folder path.",
"invalidDbCredentials": "Invalid database credentials.",
"unableToConnectToDb": "Unable to connect to database, please check your database is up.",
"invalidYear": "Invalid year",
"userDoesntHaveSufficientPermission": "User does not exist or have sufficient permission to create schema.",
"dbConnectionStatus": "Invalid database parameters",
"dbConnectionFailed": "Connection Failure:",

4
packages/nc-gui/pages/index/[typeOrId]/form/[viewId].vue

@ -75,11 +75,11 @@ watch(
>
<div class="w-full flex flex-col gap-4">
<!-- todo: i18n -->
<h2 class="text-xl font-semibold">This shared view is protected</h2>
<h2 class="text-xl font-semibold">{{ $t('msg.thisSharedViewIsProtected') }}</h2>
<a-form layout="vertical" no-style :model="form" @finish="loadSharedView">
<a-form-item name="password" :rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]">
<a-input-password v-model:value="form.password" size="large" :placeholder="$t('msg.info.signUp.enterPassword')" />
<a-input-password v-model:value="form.password" size="large" :placeholder="$t('msg.enterPassword')" />
</a-form-item>
<Transition name="layout">

8
packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index/index.vue

@ -87,7 +87,7 @@ const onDecode = async (scannedCodeValue: string) => {
{{ sharedFormView.subheading }}
</h2>
<a-alert v-if="notFound" type="warning" class="my-4 text-center" message="Not found" />
<a-alert v-if="notFound" type="warning" class="my-4 text-center" :message="$t('general.notFound')" />
<template v-else-if="submitted">
<div class="flex justify-center">
@ -96,15 +96,15 @@ const onDecode = async (scannedCodeValue: string) => {
type="success"
class="my-4 text-center"
outlined
:message="sharedFormView.success_msg || 'Successfully submitted form data'"
:message="sharedFormView.success_msg || $t('msg.successfullySubmittedFormData')"
/>
<p v-if="sharedFormView.show_blank_form" class="text-xs text-slate-500 dark:text-slate-300 text-center my-4">
New form will be loaded after {{ secondsRemain }} seconds
{{ $t('msg.newFormWillBeLoaded', { seconds: secondsRemain }) }}
</p>
<div v-if="sharedFormView.submit_another_form" class="text-center">
<a-button type="primary" @click="submitted = false"> Submit Another Form</a-button>
<a-button type="primary" @click="submitted = false"> {{ $t('activity.submitAnotherForm') }}</a-button>
</div>
</div>
</div>

8
packages/nc-gui/pages/signup/[[token]].vue

@ -214,7 +214,7 @@ onMounted(async () => {
size="small"
class="my-1 hover:(ring ring-accent ring-opacity-100) focus:(!ring !ring-accent ring-opacity-100)"
/>
<div class="prose-xs text-gray-500">Subscribe to our weekly newsletter</div>
<div class="prose-xs text-gray-500">{{ $t('msg.subscribeToOurWeeklyNewsletter') }}</div>
</div>
<div class="text-end prose-sm">
@ -227,8 +227,10 @@ onMounted(async () => {
</div>
<div class="prose-sm mt-4 text-gray-500">
By signing up, you agree to the
<a class="prose-sm !text-gray-500 underline" target="_blank" href="https://nocodb.com/policy-nocodb">Terms of Service</a>
{{ $t('msg.bySigningUp') }}
<a class="prose-sm !text-gray-500 underline" target="_blank" href="https://nocodb.com/policy-nocodb">
{{ $t('title.termsOfService') }}</a
>
</div>
</div>
</NuxtLayout>

Loading…
Cancel
Save