Browse Source

resolve merging conflicts

pull/4140/head
flisowna 2 years ago
parent
commit
1ee9fb0d0d
  1. 8
      .github/workflows/release-docker.yml
  2. 2
      .github/workflows/release-previous-docker.yml
  3. 12
      packages/nc-cli/package-lock.json
  4. 27
      packages/nc-gui/app.vue
  5. BIN
      packages/nc-gui/assets/img/brand/Transparent.png
  6. BIN
      packages/nc-gui/assets/img/brand/favicon-128.png
  7. BIN
      packages/nc-gui/assets/img/brand/favicon-16.png
  8. BIN
      packages/nc-gui/assets/img/brand/favicon-32.png
  9. BIN
      packages/nc-gui/assets/img/brand/favicon-64.png
  10. BIN
      packages/nc-gui/assets/img/brand/full-logo.png
  11. BIN
      packages/nc-gui/assets/img/brand/text.png
  12. BIN
      packages/nc-gui/assets/img/icon.png
  13. BIN
      packages/nc-gui/assets/img/icons/256.png
  14. 10
      packages/nc-gui/assets/style.scss
  15. 9
      packages/nc-gui/components.d.ts
  16. 8
      packages/nc-gui/components/account/AppStore.vue
  17. 45
      packages/nc-gui/components/account/License.vue
  18. 146
      packages/nc-gui/components/account/ResetPassword.vue
  19. 56
      packages/nc-gui/components/account/SignupSettings.vue
  20. 262
      packages/nc-gui/components/account/Token.vue
  21. 281
      packages/nc-gui/components/account/UserList.vue
  22. 256
      packages/nc-gui/components/account/UsersModal.vue
  23. 4
      packages/nc-gui/components/cell/Email.vue
  24. 149
      packages/nc-gui/components/cell/MultiSelect.vue
  25. 7
      packages/nc-gui/components/cell/Percent.vue
  26. 126
      packages/nc-gui/components/cell/SingleSelect.vue
  27. 4
      packages/nc-gui/components/cell/Text.vue
  28. 1
      packages/nc-gui/components/dashboard/TreeView.vue
  29. 38
      packages/nc-gui/components/dlg/TableCreate.vue
  30. 1
      packages/nc-gui/components/dlg/TableRename.vue
  31. 2
      packages/nc-gui/components/dlg/ViewCreate.vue
  32. 19
      packages/nc-gui/components/general/language/Menu.vue
  33. 6
      packages/nc-gui/components/shared-view/AskPassword.vue
  34. 4
      packages/nc-gui/components/smartsheet/ApiSnippet.vue
  35. 133
      packages/nc-gui/components/smartsheet/Cell.vue
  36. 49
      packages/nc-gui/components/smartsheet/Grid.vue
  37. 34
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  38. 7
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  39. 4
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  40. 42
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  41. 12
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  42. 136
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  43. 27
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  44. 75
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  45. 7
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue
  46. 7
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  47. 16
      packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue
  48. 101
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  49. 7
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  50. 11
      packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue
  51. 9
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  52. 134
      packages/nc-gui/components/tabs/auth/UserManagement.vue
  53. 10
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  54. 56
      packages/nc-gui/components/virtual-cell/Lookup.vue
  55. 101
      packages/nc-gui/composables/useColumn.ts
  56. 14
      packages/nc-gui/composables/useColumnCreateStore.ts
  57. 3
      packages/nc-gui/composables/useExpandedFormStore.ts
  58. 28
      packages/nc-gui/composables/useMenuCloseOnEsc/index.ts
  59. 5
      packages/nc-gui/composables/useMultiSelect/index.ts
  60. 5
      packages/nc-gui/composables/useProject.ts
  61. 25
      packages/nc-gui/composables/useSmartsheetRowStore.ts
  62. 2
      packages/nc-gui/composables/useTabs.ts
  63. 11
      packages/nc-gui/composables/useViewData.ts
  64. 36
      packages/nc-gui/composables/useVirtualCell.ts
  65. 24
      packages/nc-gui/lang/ar.json
  66. 25
      packages/nc-gui/lang/bn_IN.json
  67. 24
      packages/nc-gui/lang/da.json
  68. 24
      packages/nc-gui/lang/de.json
  69. 26
      packages/nc-gui/lang/en.json
  70. 24
      packages/nc-gui/lang/es.json
  71. 24
      packages/nc-gui/lang/fa.json
  72. 24
      packages/nc-gui/lang/fi.json
  73. 24
      packages/nc-gui/lang/fr.json
  74. 24
      packages/nc-gui/lang/he.json
  75. 24
      packages/nc-gui/lang/hi.json
  76. 24
      packages/nc-gui/lang/hr.json
  77. 24
      packages/nc-gui/lang/id.json
  78. 24
      packages/nc-gui/lang/it.json
  79. 24
      packages/nc-gui/lang/ja.json
  80. 24
      packages/nc-gui/lang/ko.json
  81. 24
      packages/nc-gui/lang/lv.json
  82. 24
      packages/nc-gui/lang/nl.json
  83. 24
      packages/nc-gui/lang/no.json
  84. 24
      packages/nc-gui/lang/pl.json
  85. 24
      packages/nc-gui/lang/pt.json
  86. 24
      packages/nc-gui/lang/pt_BR.json
  87. 24
      packages/nc-gui/lang/ru.json
  88. 24
      packages/nc-gui/lang/sl.json
  89. 24
      packages/nc-gui/lang/sv.json
  90. 24
      packages/nc-gui/lang/th.json
  91. 24
      packages/nc-gui/lang/tr.json
  92. 24
      packages/nc-gui/lang/uk.json
  93. 24
      packages/nc-gui/lang/vi.json
  94. 24
      packages/nc-gui/lang/zh-Hans.json
  95. 234
      packages/nc-gui/lang/zh-Hant.json
  96. 34
      packages/nc-gui/layouts/base.vue
  97. 2
      packages/nc-gui/layouts/default.vue
  98. 8
      packages/nc-gui/lib/constants.ts
  99. 3
      packages/nc-gui/lib/enums.ts
  100. 1
      packages/nc-gui/lib/types.ts
  101. Some files were not shown because too many files have changed in this diff Show More

8
.github/workflows/release-docker.yml

@ -103,11 +103,11 @@ jobs:
working-directory: ${{ env.working-directory }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2.2.1
- name: Cache Docker layers
uses: actions/cache@v3
@ -118,13 +118,13 @@ jobs:
${{ runner.os }}-buildx-
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3.2.0
with:
context: ${{ env.working-directory }}
build-args: NC_VERSION=${{ steps.get-docker-repository.outputs.DOCKER_BUILD_TAG }}

2
.github/workflows/release-previous-docker.yml

@ -14,7 +14,7 @@ jobs:
steps:
-
name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v2.1.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

12
packages/nc-cli/package-lock.json generated

@ -9008,9 +9008,9 @@
}
},
"node_modules/loader-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"dependencies": {
"big.js": "^5.2.2",
@ -22415,9 +22415,9 @@
"dev": true
},
"loader-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz",
"integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==",
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"requires": {
"big.js": "^5.2.2",

27
packages/nc-gui/app.vue

@ -6,12 +6,37 @@ const route = useRoute()
const disableBaseLayout = computed(() => route.path.startsWith('/nc/view') || route.path.startsWith('/nc/form'))
useTheme()
// TODO: Remove when https://github.com/vuejs/core/issues/5513 fixed
const key = ref(0)
const messages = [
`Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.`, // chromium based
`NotFoundError: The object can not be found here.`, // safari
"Cannot read properties of null (reading 'parentNode')",
]
if (typeof window !== 'undefined') {
// @ts-expect-error using arbitrary window key
if (!window.__ncvue) {
window.addEventListener('error', (event) => {
if (messages.includes(event.message)) {
event.preventDefault()
console.warn('Re-rendering layout because of https://github.com/vuejs/core/issues/5513')
key.value++
}
})
}
// @ts-expect-error using arbitrary window key
window.__ncvue = true
}
</script>
<template>
<a-config-provider>
<NuxtLayout :name="disableBaseLayout ? false : 'base'">
<NuxtPage />
<NuxtPage :key="key" />
</NuxtLayout>
</a-config-provider>
</template>

BIN
packages/nc-gui/assets/img/brand/Transparent.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

BIN
packages/nc-gui/assets/img/brand/favicon-128.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

BIN
packages/nc-gui/assets/img/brand/favicon-16.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 982 B

BIN
packages/nc-gui/assets/img/brand/favicon-32.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

BIN
packages/nc-gui/assets/img/brand/favicon-64.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

BIN
packages/nc-gui/assets/img/brand/full-logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
packages/nc-gui/assets/img/brand/text.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
packages/nc-gui/assets/img/icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

BIN
packages/nc-gui/assets/img/icons/256.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

10
packages/nc-gui/assets/style.scss

@ -214,17 +214,13 @@ a {
@apply z-1 relative color-transition rounded-md px-4 py-2 text-white;
&::after {
@apply ring-opacity-100 ring-[2px] ring-slate-300 rounded absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary bg-opacity-100;
@apply rounded absolute top-0 left-0 right-0 bottom-0 transition-all duration-150 ease-in-out bg-primary bg-opacity-100;
content: '';
z-index: -1;
}
&:hover::after {
@apply transform scale-110 ring ring-accent;
}
&:active::after {
@apply ring ring-accent;
@apply transform scale-110;
}
}
@ -261,7 +257,7 @@ a {
}
.ant-dropdown-menu-item, .ant-menu-item {
@apply !py-0 active:(ring ring-accent ring-opacity-100);
@apply py-0;
}
.ant-dropdown-menu-title-content,

9
packages/nc-gui/components.d.ts vendored

@ -24,6 +24,7 @@ declare module '@vue/runtime-core' {
ADivider: typeof import('ant-design-vue/es')['Divider']
ADrawer: typeof import('ant-design-vue/es')['Drawer']
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
ADropdownButton: typeof import('ant-design-vue/es')['DropdownButton']
AEmpty: typeof import('ant-design-vue/es')['Empty']
AForm: typeof import('ant-design-vue/es')['Form']
AFormItem: typeof import('ant-design-vue/es')['FormItem']
@ -104,8 +105,11 @@ declare module '@vue/runtime-core' {
MaterialSymbolsWarning: typeof import('~icons/material-symbols/warning')['default']
MdiAccount: typeof import('~icons/mdi/account')['default']
MdiAccountCircle: typeof import('~icons/mdi/account-circle')['default']
MdiAccountCircleOutline: typeof import('~icons/mdi/account-circle-outline')['default']
MdiAccountOutline: typeof import('~icons/mdi/account-outline')['default']
MdiAccountPlusOutline: typeof import('~icons/mdi/account-plus-outline')['default']
MdiAccountSupervisorOutline: typeof import('~icons/mdi/account-supervisor-outline')['default']
MdiAdd: typeof import('~icons/mdi/add')['default']
MdiAlpha: typeof import('~icons/mdi/alpha')['default']
MdiAlphaA: typeof import('~icons/mdi/alpha-a')['default']
MdiApi: typeof import('~icons/mdi/api')['default']
@ -121,6 +125,7 @@ declare module '@vue/runtime-core' {
MdiBugOutline: typeof import('~icons/mdi/bug-outline')['default']
MdiCalculator: typeof import('~icons/mdi/calculator')['default']
MdiCalendarMonth: typeof import('~icons/mdi/calendar-month')['default']
MdiCancel: typeof import('~icons/mdi/cancel')['default']
MdiCardsHeart: typeof import('~icons/mdi/cards-heart')['default']
MdiCellphoneMessage: typeof import('~icons/mdi/cellphone-message')['default']
MdiChat: typeof import('~icons/mdi/chat')['default']
@ -138,12 +143,14 @@ declare module '@vue/runtime-core' {
MdiCommentTextOutline: typeof import('~icons/mdi/comment-text-outline')['default']
MdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
MdiContentSave: typeof import('~icons/mdi/content-save')['default']
MdiContentSaveEdit: typeof import('~icons/mdi/content-save-edit')['default']
MdiCurrencyUsd: typeof import('~icons/mdi/currency-usd')['default']
MdiDatabaseOutline: typeof import('~icons/mdi/database-outline')['default']
MdiDatabaseSync: typeof import('~icons/mdi/database-sync')['default']
MdiDelete: typeof import('~icons/mdi/delete')['default']
MdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
MdiDiscord: typeof import('~icons/mdi/discord')['default']
MdiDotsHorizontal: typeof import('~icons/mdi/dots-horizontal')['default']
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiDownload: typeof import('~icons/mdi/download')['default']
MdiDownloadOutline: typeof import('~icons/mdi/download-outline')['default']
@ -194,12 +201,14 @@ declare module '@vue/runtime-core' {
MdiPlus: typeof import('~icons/mdi/plus')['default']
MdiPlusCircleOutline: typeof import('~icons/mdi/plus-circle-outline')['default']
MdiPlusOutline: typeof import('~icons/mdi/plus-outline')['default']
MdiPlusThick: typeof import('~icons/mdi/plus-thick')['default']
MdiReddit: typeof import('~icons/mdi/reddit')['default']
MdiRefresh: typeof import('~icons/mdi/refresh')['default']
MdiReload: typeof import('~icons/mdi/reload')['default']
MdiRocketLaunchOutline: typeof import('~icons/mdi/rocket-launch-outline')['default']
MdiScriptTextKeyOutline: typeof import('~icons/mdi/script-text-key-outline')['default']
MdiScriptTextOutline: typeof import('~icons/mdi/script-text-outline')['default']
MdiShieldKeyOutline: typeof import('~icons/mdi/shield-key-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiSort: typeof import('~icons/mdi/sort')['default']
MdiStar: typeof import('~icons/mdi/star')['default']

8
packages/nc-gui/components/account/AppStore.vue

@ -0,0 +1,8 @@
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">{{ $t('title.appStore') }}</div>
<div>
<LazyDashboardSettingsAppStore />
</div>
</div>
</template>

45
packages/nc-gui/components/account/License.vue

@ -0,0 +1,45 @@
<script lang="ts" setup>
import { useNuxtApp } from '#app'
import { message } from 'ant-design-vue'
import { extractSdkResponseErrorMsg, useApi } from '#imports'
const { api, isLoading } = useApi()
const {$e} = useNuxtApp()
let key = $ref('')
const loadLicense = async () => {
try {
const response = await api.orgLicense.get()
key = response.key
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const setLicense = async () => {
try {
await api.orgLicense.set({ key: key })
message.success('License key updated')
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:account:license')
}
loadLicense()
</script>
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">License</div>
<div>
<a-textarea v-model:value="key" placeholder="License key" class="!mt-2 !max-w-[600px]"></a-textarea>
</div>
<a-button class="mt-4" @click="setLicense" type="primary">Save license key</a-button>
</div>
</template>
<style scoped></style>

146
packages/nc-gui/components/account/ResetPassword.vue

@ -0,0 +1,146 @@
<script lang="ts" setup>
import { message, navigateTo, reactive, ref, useApi, useGlobal, useI18n } from '#imports'
const { api, error } = useApi({ useGlobalInstance: true })
const { t } = useI18n()
const { signOut } = useGlobal()
const formValidator = ref()
const form = reactive({
currentPassword: '',
password: '',
passwordRepeat: '',
})
const formRules = {
currentPassword: [
// Current password is required
{ required: true, message: t('msg.error.signUpRules.passwdRequired') },
],
password: [
// Password is required
{ required: true, message: t('msg.error.signUpRules.passwdRequired') },
{ min: 8, message: t('msg.error.signUpRules.passwdLength') },
],
passwordRepeat: [
// PasswordRepeat is required
{ required: true, message: t('msg.error.signUpRules.passwdRequired') },
// Passwords match
{
validator: (_: unknown, _v: string) => {
return new Promise((resolve, reject) => {
if (form.password === form.passwordRepeat) return resolve(true)
reject(new Error(t('msg.error.signUpRules.passwdMismatch')))
})
},
message: t('msg.error.signUpRules.passwdMismatch'),
},
],
}
const passwordChange = async () => {
const valid = formValidator.value.validate()
if (!valid) return
error.value = null
await api.auth.passwordChange({
currentPassword: form.currentPassword,
newPassword: form.password,
})
message.success(t('msg.success.passwordChanged'))
signOut()
navigateTo('/signin')
}
const resetError = () => {
if (error.value) error.value = null
}
</script>
<template>
<div class="mx-auto relative flex flex-col justify-center gap-2 w-full px-8 md:(bg-white) max-w-[900px]">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">{{ $t('activity.changePwd') }}</div>
<a-form
ref="formValidator"
data-testid="nc-user-settings-form"
layout="vertical"
class="change-password lg:max-w-3/4 w-full !mx-auto"
no-style
:model="form"
@finish="passwordChange"
>
<Transition name="layout">
<div v-if="error" class="mx-auto mb-4 bg-red-500 text-white rounded-lg w-3/4 p-1">
<div data-testid="nc-user-settings-form__error" class="flex items-center gap-2 justify-center">
<MaterialSymbolsWarning />
{{ error }}
</div>
</div>
</Transition>
<a-form-item :label="$t('placeholder.password.current')" name="currentPassword" :rules="formRules.currentPassword">
<a-input-password
v-model:value="form.currentPassword"
data-testid="nc-user-settings-form__current-password"
size="large"
class="password"
:placeholder="$t('placeholder.password.current')"
@focus="resetError"
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.new')" name="password" :rules="formRules.password">
<a-input-password
v-model:value="form.password"
data-testid="nc-user-settings-form__new-password"
size="large"
class="password"
:placeholder="$t('placeholder.password.new')"
@focus="resetError"
/>
</a-form-item>
<a-form-item :label="$t('placeholder.password.confirm')" name="passwordRepeat" :rules="formRules.passwordRepeat">
<a-input-password
v-model:value="form.passwordRepeat"
data-testid="nc-user-settings-form__new-password-repeat"
size="large"
class="password"
:placeholder="$t('placeholder.password.confirm')"
@focus="resetError"
/>
</a-form-item>
<div class="text-center">
<button data-testid="nc-user-settings-form__submit" class="scaling-btn bg-opacity-100" type="submit">
<span class="flex items-center gap-2">
<MdiKeyChange />
{{ $t('activity.changePwd') }}
</span>
</button>
</div>
</a-form>
</div>
</template>
<style lang="scss">
.change-password {
.ant-input-affix-wrapper,
.ant-input {
@apply !appearance-none my-1 border-1 border-solid border-primary border-opacity-50 rounded;
}
.password {
input {
@apply !border-none !m-0;
}
}
}
</style>

56
packages/nc-gui/components/account/SignupSettings.vue

@ -0,0 +1,56 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import { extractSdkResponseErrorMsg, useApi } from '#imports'
const { api } = useApi()
const { t } = useI18n()
let settings = $ref<{ invite_only_signup?: boolean }>({ invite_only_signup: false })
const loadSettings = async () => {
try {
const response = await api.orgAppSettings.get()
settings = response
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const saveSettings = async () => {
try {
await api.orgAppSettings.set(settings)
message.success(t('msg.success.settingsSaved'))
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
loadSettings()
</script>
<template>
<div data-testid="nc-app-settings">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">Settings</div>
<div class="flex justify-center">
<a-form-item>
<a-checkbox
v-model:checked="settings.invite_only_signup"
v-e="['c:account:enable-signup']"
class="nc-checkbox nc-invite-only-signup-checkbox"
name="virtual"
@change="saveSettings"
>
{{ $t('labels.inviteOnlySignup') }}
</a-checkbox>
</a-form-item>
</div>
</div>
</template>
<style scoped>
:deep(.ant-checkbox-wrapper) {
@apply !flex-row-reverse !flex !justify-start gap-4;
justify-content: start;
}
</style>

262
packages/nc-gui/components/account/Token.vue

@ -0,0 +1,262 @@
<script lang="ts" setup>
import { Empty, Modal, message } from 'ant-design-vue'
import type { ApiTokenType, RequestParams, UserType } from 'nocodb-sdk'
import { extractSdkResponseErrorMsg, useApi, useCopy, useNuxtApp } from '#imports'
const { api, isLoading } = useApi()
const { $e } = useNuxtApp()
const { copy } = useCopy()
const { t } = useI18n()
let tokens = $ref<UserType[]>([])
let currentPage = $ref(1)
let showNewTokenModal = $ref(false)
const currentLimit = $ref(10)
let selectedTokenData = $ref<ApiTokenType>({})
const searchText = ref<string>('')
const pagination = reactive({
total: 0,
pageSize: 10,
})
const loadTokens = async (page = currentPage, limit = currentLimit) => {
currentPage = page
try {
const response: any = await api.orgTokens.list({
query: {
limit,
offset: searchText.value.length === 0 ? (page - 1) * limit : 0,
},
} as RequestParams)
if (!response) return
pagination.total = response.pageInfo.totalRows ?? 0
pagination.pageSize = 10
tokens = response.list as UserType[]
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
loadTokens()
const deleteToken = async (token: string) => {
Modal.confirm({
title: t('msg.info.deleteTokenConfirmation'),
type: 'warn',
onOk: async () => {
try {
// todo: delete token
await api.orgTokens.delete(token)
message.success(t('msg.success.tokenDeleted'))
await loadTokens()
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:account:token:delete')
},
})
}
const generateToken = async () => {
try {
await api.orgTokens.create(selectedTokenData)
showNewTokenModal = false
// Token generated successfully
message.success(t('msg.success.tokenGenerated'))
selectedTokenData = {}
await loadTokens()
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:api-token:generate')
}
const copyToken = (token: string | undefined) => {
if (!token) return
copy(token)
// Copied to clipboard
message.info(t('msg.info.copiedToClipboard'))
$e('c:api-token:copy')
}
const descriptionInput = (el) => {
el?.focus()
}
</script>
<template>
<div class="h-full overflow-y-scroll scrollbar-thin-dull pt-2">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">Token Management</div>
<div class="max-w-[900px] mx-auto p-4" data-testid="nc-token-list">
<div class="py-2 flex gap-4 items-center">
<div class="flex-grow"></div>
<MdiReload class="cursor-pointer" @click="loadTokens" />
<a-button data-testid="nc-token-create" size="small" type="primary" @click="showNewTokenModal = true">
<div class="flex items-center gap-1">
<MdiAdd />
Add new token
</div>
</a-button>
</div>
<a-table
:row-key="(record) => record.id"
:data-source="tokens"
:pagination="{ position: ['bottomCenter'] }"
:loading="isLoading"
size="small"
@change="loadTokens($event.current)"
>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- Created By -->
<a-table-column key="created_by" :title="$t('labels.createdBy')" data-index="created_by">
<template #default="{ text }">
<div v-if="text">
{{ text }}
</div>
<div v-else class="text-gray-400">N/A</div>
</template>
</a-table-column>
<!-- Description -->
<a-table-column key="description" :title="$t('labels.description')" data-index="description">
<template #default="{ text }">
{{ text }}
</template>
</a-table-column>
<!-- Token -->
<a-table-column key="token" :title="$t('labels.token')" data-index="token">
<template #default="{ text, record }">
<div class="w-[320px]">
<span v-if="record.show">{{ text }}</span>
<span v-else>*******************************************</span>
</div>
</template>
</a-table-column>
<!-- Actions -->
<a-table-column key="actions" :title="$t('labels.actions')" data-index="token">
<template #default="{ record }">
<div class="flex items-center gap-2">
<a-tooltip placement="bottom">
<template #title>
<span v-if="record.show"> {{ $t('general.hide') }} </span>
<span v-else> {{ $t('general.show') }} </span>
</template>
<a-button type="text" class="!rounded-md nc-toggle-token-visibility" @click="record.show = !record.show">
<template #icon>
<MaterialSymbolsVisibilityOff v-if="record.show" class="flex mx-auto h-[1.1rem]" />
<MaterialSymbolsVisibility v-else class="flex mx-auto h-[1rem]" />
</template>
</a-button>
</a-tooltip>
<a-tooltip placement="bottom">
<template #title> {{ $t('general.copy') }}</template>
<a-button type="text" class="!rounded-md" @click="copyToken(record.token)">
<template #icon>
<MdiContentCopy class="flex mx-auto h-[1rem]" />
</template>
</a-button>
</a-tooltip>
<a-dropdown
:trigger="['click']"
class="flex"
placement="bottomRight"
overlay-class-name="nc-dropdown-api-token-mgmt"
>
<div class="flex flex-row items-center">
<a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]">
<IcBaselineMoreVert class="nc-token-menu" />
</div>
</a-button>
</div>
<template #overlay>
<a-menu data-testid="nc-token-row-action-icon">
<a-menu-item>
<div class="flex flex-row items-center py-3 h-[1rem] nc-delete-token" @click="deleteToken(record.token)">
<MdiDeleteOutline class="flex" />
<div class="text-xs pl-2">{{ $t('general.remove') }}</div>
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
</template>
</a-table-column>
</a-table>
</div>
<a-modal
v-model:visible="showNewTokenModal"
:closable="false"
width="28rem"
centered
:footer="null"
wrap-class-name="nc-modal-generate-token"
>
<div class="relative flex flex-col h-full">
<a-button type="text" class="!absolute top-0 right-0 rounded-md -mt-2 -mr-3" @click="showNewTokenModal = false">
<template #icon>
<MaterialSymbolsCloseRounded class="flex mx-auto" />
</template>
</a-button>
<!-- Generate Token -->
<div class="flex flex-row justify-center w-full -mt-1 mb-3">
<a-typography-title :level="5">{{ $t('title.generateToken') }}</a-typography-title>
</div>
<!-- Description -->
<a-form
ref="form"
:model="selectedTokenData"
name="basic"
layout="vertical"
class="flex flex-col justify-center space-y-6"
no-style
autocomplete="off"
@finish="generateToken"
>
<a-input
:ref="descriptionInput"
v-model:value="selectedTokenData.description"
data-testid="nc-token-modal-description"
:placeholder="$t('labels.description')"
/>
<!-- Generate -->
<div class="flex flex-row justify-center">
<a-button type="primary" html-type="submit" data-testid="nc-token-modal-save">
{{ $t('general.generate') }}
</a-button>
</div>
</a-form>
</div>
</a-modal>
</div>
</template>
<style scoped></style>

281
packages/nc-gui/components/account/UserList.vue

@ -0,0 +1,281 @@
<script lang="ts" setup>
import { Modal, message } from 'ant-design-vue'
import type { RequestParams, UserType } from 'nocodb-sdk'
import { Role, extractSdkResponseErrorMsg, useApi, useCopy, useDashboard, useNuxtApp } from '#imports'
import type { User } from '~/lib'
const { api, isLoading } = useApi()
const { $e } = useNuxtApp()
const { t } = useI18n()
const { dashboardUrl } = $(useDashboard())
const { copy } = useCopy()
let users = $ref<UserType[]>([])
let currentPage = $ref(1)
const currentLimit = $ref(10)
const showUserModal = ref(false)
const userMadalKey = ref(0)
const searchText = ref<string>('')
const pagination = reactive({
total: 0,
pageSize: 10,
})
const loadUsers = async (page = currentPage, limit = currentLimit) => {
currentPage = page
try {
const response: any = await api.orgUsers.list({
query: {
limit,
offset: searchText.value.length === 0 ? (page - 1) * limit : 0,
query: searchText.value,
},
} as RequestParams)
if (!response) return
pagination.total = response.pageInfo.totalRows ?? 0
pagination.pageSize = 10
users = response.list as UserType[]
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
loadUsers()
const updateRole = async (userId: string, roles: Role) => {
try {
await api.orgUsers.update(userId, {
roles,
} as unknown as UserType)
message.success(t('msg.success.roleUpdated'))
$e('a:org-user:role-updated', { role: roles })
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const deleteUser = async (userId: string) => {
Modal.confirm({
title: 'Are you sure you want to delete this user?',
type: 'warn',
content: 'On deleting, user will remove from organization and any sync source(Airtable) created by user will get removed',
onOk: async () => {
try {
await api.orgUsers.delete(userId)
message.success(t('msg.success.userDeleted'))
await loadUsers()
$e('a:org-user:user-deleted')
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
},
})
}
const resendInvite = async (user: User) => {
try {
await api.orgUsers.resendInvite(user.id)
// Invite email sent successfully
message.success(t('msg.success.inviteEmailSent'))
await loadUsers()
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
$e('a:org-user:resend-invite')
}
const copyInviteUrl = (user: User) => {
if (!user.invite_token) return
copy(`${dashboardUrl}#/signup/${user.invite_token}`)
// Invite URL copied to clipboard
message.success(t('msg.success.inviteURLCopied'))
$e('c:user:copy-url')
}
const copyPasswordResetUrl = async (user: User) => {
try {
const { reset_password_url } = await api.orgUsers.generatePasswordResetToken(user.id)
copy(reset_password_url)
// Invite URL copied to clipboard
message.success(t('msg.success.passwordResetURLCopied'))
$e('c:user:copy-url')
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
</script>
<template>
<div data-testid="nc-super-user-list">
<div class="text-xl mt-4 mb-8 text-center font-weight-bold">User Management</div>
<div class="max-w-[900px] mx-auto p-4">
<div class="py-2 flex gap-4 items-center">
<a-input-search
v-model:value="searchText"
size="small"
class="max-w-[300px]"
placeholder="Filter by email"
@blur="loadUsers"
@keydown.enter="loadUsers"
>
</a-input-search>
<div class="flex-grow"></div>
<MdiReload class="cursor-pointer" @click="loadUsers" />
<a-button
data-testid="nc-super-user-invite"
size="small"
type="primary"
@click="
() => {
showUserModal = true
userMadalKey++
}
"
>
<div class="flex items-center gap-1">
<MdiAdd />
Invite new user
</div>
</a-button>
</div>
<a-table
:row-key="(record) => record.id"
:data-source="users"
:pagination="{ position: ['bottomCenter'] }"
:loading="isLoading"
size="small"
@change="loadUsers($event.current)"
>
<template #emptyText>
<a-empty :image="Empty.PRESENTED_IMAGE_SIMPLE" :description="$t('labels.noData')" />
</template>
<!-- Email -->
<a-table-column key="email" :title="$t('labels.email')" data-index="email">
<template #default="{ text }">
<div>
{{ text }}
</div>
</template>
</a-table-column>
<!-- Role -->
<a-table-column key="roles" :title="$t('objects.role')" data-index="roles">
<template #default="{ record }">
<div>
<div v-if="record.roles.includes('super')" class="font-weight-bold">Super Admin</div>
<a-select
v-else
v-model:value="record.roles"
class="w-[220px] nc-user-roles"
:dropdown-match-select-width="false"
@change="updateRole(record.id, record.roles)"
>
<a-select-option
class="nc-users-list-role-option"
:value="Role.OrgLevelCreator"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
{{ $t('msg.info.roles.orgCreator') }}
</span>
</a-select-option>
<a-select-option
class="nc-users-list-role-option"
:value="Role.OrgLevelViewer"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
{{ $t('msg.info.roles.orgViewer') }}
</span>
</a-select-option>
</a-select>
</div>
</template>
</a-table-column>
<!-- &lt;!&ndash; Projects &ndash;&gt;
<a-table-column key="projectsCount" :title="$t('objects.projects')" data-index="projectsCount">
<template #default="{ text }">
<div>
{{ text }}
</div>
</template>
</a-table-column> -->
<!-- Actions -->
<a-table-column key="id" :title="$t('labels.actions')" data-index="id">
<template #default="{ text, record }">
<div v-if="!record.roles.includes('super')" class="flex items-center gap-2">
<a-dropdown :trigger="['click']" class="flex" placement="bottomRight" overlay-class-name="nc-dropdown-user-mgmt">
<div class="flex flex-row items-center">
<a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]">
<MdiDotsHorizontal class="nc-user-row-action" />
</div>
</a-button>
</div>
<template #overlay>
<a-menu>
<template v-if="record.invite_token">
<a-menu-item>
<!-- Resend invite Email -->
<div class="flex flex-row items-center py-3" @click="resendInvite(record)">
<MdiEmailArrowRightOutline class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(record)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</div>
</a-menu-item>
</template>
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyPasswordResetUrl(record)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyPasswordResetURL') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="deleteUser(text)">
<MdiDeleteOutline data-testid="nc-super-user-delete" class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('general.delete') }}</div>
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</div>
<span v-else></span>
</template>
</a-table-column>
</a-table>
<LazyAccountUsersModal :key="userMadalKey" :show="showUserModal" @closed="showUserModal = false" @reload="loadUsers" />
</div>
</div>
</template>

256
packages/nc-gui/components/account/UsersModal.vue

@ -0,0 +1,256 @@
<script setup lang="ts">
import type { UserType } from 'nocodb-sdk'
import {
Form,
computed,
extractSdkResponseErrorMsg,
isEmail,
message,
ref,
useCopy,
useDashboard,
useI18n,
useNuxtApp,
} from '#imports'
import type { User } from '~/lib'
import { Role } from '~/lib'
interface Props {
show: boolean
selectedUser?: User
}
interface Users {
emails: string
role: Role.OrgLevelCreator | Role.OrgLevelViewer
invitationToken?: string
}
const { show } = defineProps<Props>()
const emit = defineEmits(['closed', 'reload'])
const { t } = useI18n()
const { $api, $e } = useNuxtApp()
const { copy } = useCopy()
const { dashboardUrl } = $(useDashboard())
const usersData = $ref<Users>({ emails: '', role: Role.OrgLevelViewer, invitationToken: undefined })
const formRef = ref()
const useForm = Form.useForm
const validators = computed(() => {
return {
emails: [
{
validator: (rule: any, value: string, callback: (errMsg?: string) => void) => {
if (!value || value.length === 0) {
callback('Email is required')
return
}
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !isEmail(e))
if (invalidEmails.length > 0) {
callback(`${invalidEmails.length > 1 ? ' Invalid emails:' : 'Invalid email:'} ${invalidEmails.join(', ')} `)
} else {
callback()
}
},
},
],
}
})
const { validateInfos } = useForm(usersData, validators)
const saveUser = async () => {
$e('a:org-user:invite', { role: usersData.role })
await formRef.value?.validateFields()
try {
// todo: update sdk(swagger.json)
const res = await $api.orgUsers.add({
roles: usersData.role,
email: usersData.emails,
} as unknown as UserType)
usersData.invitationToken = res.invite_token
emit('reload')
// Successfully updated the user details
message.success(t('msg.success.userAdded'))
} catch (e: any) {
console.error(e)
message.error(await extractSdkResponseErrorMsg(e))
}
}
const inviteUrl = $computed(() => (usersData.invitationToken ? `${dashboardUrl}#/signup/${usersData.invitationToken}` : null))
const copyUrl = async () => {
if (!inviteUrl) return
await copy(inviteUrl)
// Copied shareable base url to clipboard!
message.success(t('msg.success.shareableURLCopied'))
$e('c:shared-base:copy-url')
}
const clickInviteMore = () => {
$e('c:user:invite-more')
usersData.invitationToken = undefined
usersData.role = Role.OrgLevelViewer
usersData.emails = ''
}
const emailInput = ref((el) => {
el?.focus()
})
</script>
<template>
<a-modal
:footer="null"
centered
:visible="show"
:closable="false"
width="max(50vw, 44rem)"
wrap-class-name="nc-modal-invite-user"
@cancel="emit('closed')"
>
<div class="flex flex-col">
<div class="flex flex-row justify-between items-center pb-1.5 mb-2 border-b-1 w-full">
<a-typography-title class="select-none" :level="4"> {{ $t('activity.inviteUser') }}</a-typography-title>
<a-button type="text" class="!rounded-md mr-1 -mt-1.5" @click="emit('closed')">
<template #icon>
<MaterialSymbolsCloseRounded data-testid="nc-root-user-invite-modal-close" class="flex mx-auto" />
</template>
</a-button>
</div>
<div class="px-2 mt-1.5">
<template v-if="usersData.invitationToken">
<div class="flex flex-col mt-1 border-b-1 pb-5">
<div class="flex flex-row items-center pl-1.5 pb-1 h-[1.1rem]">
<MdiAccountOutline />
<div class="text-xs ml-0.5 mt-0.5">Copy Invite Token</div>
</div>
<a-alert class="mt-1" type="success" show-icon>
<template #message>
<div class="flex flex-row justify-between items-center py-1">
<div class="flex pl-2 text-green-700 text-xs">
{{ inviteUrl }}
</div>
<a-button type="text" class="!rounded-md -mt-0.5" @click="copyUrl">
<template #icon>
<MdiContentCopy class="flex mx-auto text-green-700 h-[1rem]" />
</template>
</a-button>
</div>
</template>
</a-alert>
<div class="flex text-xs text-gray-500 mt-2 justify-start ml-2">
{{ $t('msg.info.userInviteNoSMTP') }}
{{ usersData.invitationToken && usersData.emails }}
</div>
<div class="flex flex-row justify-start mt-4 ml-2">
<a-button size="small" outlined @click="clickInviteMore">
<div class="flex flex-row justify-center items-center space-x-0.5">
<MaterialSymbolsSendOutline class="flex mx-auto text-gray-600 h-[0.8rem]" />
<div class="text-xs text-gray-600">{{ $t('activity.inviteMore') }}</div>
</div>
</a-button>
</div>
</div>
</template>
<div v-else class="flex flex-col pb-4">
<div class="flex flex-row items-center pl-2 pb-1 h-[1rem]">
<MdiAccountOutline />
<div class="text-xs ml-0.5 mt-0.5">{{ $t('activity.inviteUser') }}</div>
</div>
<div class="border-1 py-3 px-4 rounded-md mt-1">
<a-form
ref="formRef"
:validate-on-rule-change="false"
:model="usersData"
validate-trigger="onBlur"
@finish="saveUser"
>
<div class="flex flex-row space-x-4">
<div class="flex flex-col w-3/4">
<a-form-item
v-bind="validateInfos.emails"
validate-trigger="onBlur"
name="emails"
:rules="[{ required: true, message: 'Please input email' }]"
>
<div class="ml-1 mb-1 text-xs text-gray-500">{{ $t('datatype.Email') }}:</div>
<a-input
:ref="emailInput"
v-model:value="usersData.emails"
validate-trigger="onBlur"
:placeholder="$t('labels.email')"
/>
</a-form-item>
</div>
<div class="flex flex-col w-2/4">
<a-form-item name="role" :rules="[{ required: true, message: 'Role required' }]">
<div class="ml-1 mb-1 text-xs text-gray-500">{{ $t('labels.selectUserRole') }}</div>
<a-select v-model:value="usersData.role" class="nc-user-roles" dropdown-class-name="nc-dropdown-user-role">
<a-select-option
class="nc-role-option"
:value="Role.OrgLevelCreator"
:label="$t(`objects.roleType.orgLevelCreator`)"
>
<div>{{ $t(`objects.roleType.orgLevelCreator`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
{{ $t('msg.info.roles.orgCreator') }}
</span>
</a-select-option>
<a-select-option
class="nc-role-option"
:value="Role.OrgLevelViewer"
:label="$t(`objects.roleType.orgLevelViewer`)"
>
<div>{{ $t(`objects.roleType.orgLevelViewer`) }}</div>
<span class="text-gray-500 text-xs whitespace-normal">
{{ $t('msg.info.roles.orgViewer') }}
</span>
</a-select-option>
</a-select>
</a-form-item>
</div>
</div>
<div class="flex flex-row justify-center">
<a-button type="primary" html-type="submit">
<div class="flex flex-row justify-center items-center space-x-1.5">
<MaterialSymbolsSendOutline class="flex h-[0.8rem]" />
<div>{{ $t('activity.invite') }}</div>
</div>
</a-button>
</div>
</a-form>
</div>
</div>
</div>
</div>
</a-modal>
</template>

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

@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, computed, inject, isEmail, useVModel } from '#imports'
import { EditModeInj, computed, inject, useVModel, validateEmail } from '#imports'
interface Props {
modelValue: string | null | undefined
@ -18,7 +18,7 @@ const editEnabled = inject(EditModeInj)
const vModel = useVModel(props, 'modelValue', emits)
const validEmail = computed(() => vModel.value && isEmail(vModel.value))
const validEmail = computed(() => vModel.value && validateEmail(vModel.value))
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>

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

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk'
@ -8,11 +9,15 @@ import {
IsKanbanInj,
ReadonlyInj,
computed,
enumColor,
extractSdkResponseErrorMsg,
h,
inject,
onMounted,
reactive,
ref,
useEventListener,
useMetas,
useProject,
useSelectedCellKeyupListener,
watch,
@ -36,6 +41,10 @@ const readOnly = inject(ReadonlyInj)!
const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false))
const isPublic = inject(IsPublicInj, ref(false))
const selectedIds = ref<string[]>([])
const aselect = ref<typeof AntSelect>()
@ -44,7 +53,17 @@ const isOpen = ref(false)
const isKanban = inject(IsKanbanInj, ref(false))
const options = computed<SelectOptionType[]>(() => {
const searchVal = ref<string | null>()
const { $api } = useNuxtApp()
const { getMeta } = useMetas()
// a variable to keep newly created options value
// temporary until it's add the option to column meta
const tempSelectedOptsState = reactive<string[]>([])
const options = computed<(SelectOptionType & { value?: string })[]>(() => {
if (column?.value.colOptions) {
const opts = column.value.colOptions
? (column.value.colOptions as SelectOptionsType).options.filter((el: SelectOptionType) => el.title !== '') || []
@ -52,21 +71,35 @@ const options = computed<SelectOptionType[]>(() => {
for (const op of opts.filter((el: SelectOptionType) => el.order === null)) {
op.title = op.title?.replace(/^'/, '').replace(/'$/, '')
}
return opts
return opts.map((o: SelectOptionType) => ({ ...o, value: o.title }))
}
return []
})
const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
const vModel = computed({
get: () =>
selectedIds.value.reduce((acc, id) => {
const title = options.value.find((op) => op.id === id)?.title
get: () => {
const selected = selectedIds.value.reduce((acc, id) => {
const title = (options.value.find((op) => op.id === id) || options.value.find((op) => op.title === id))?.title
if (title) acc.push(title)
return acc
}, [] as string[]),
set: (val) => emit('update:modelValue', val.length === 0 ? null : val.join(',')),
}, [] as string[])
if (tempSelectedOptsState.length) selected.push(...tempSelectedOptsState)
return selected
},
set: (val) => {
if (isOptionMissing.value && val.length && val[val.length - 1] === searchVal.value) {
return addIfMissingAndSave()
}
emit('update:modelValue', val.length === 0 ? null : val.join(','))
},
})
const selectedTitles = computed(() =>
@ -86,8 +119,6 @@ const selectedTitles = computed(() =>
: [],
)
const v = Math.floor(Math.random() * 1000)
const handleClose = (e: MouseEvent) => {
if (aselect.value && !aselect.value.$el.contains(e.target)) {
isOpen.value = false
@ -96,9 +127,10 @@ const handleClose = (e: MouseEvent) => {
onMounted(() => {
selectedIds.value = selectedTitles.value.flatMap((el) => {
const item = options.value.find((op) => op.title === el)?.id
if (item) {
return [item]
const item = options.value.find((op) => op.title === el)
const itemIdOrTitle = item?.id || item?.title
if (itemIdOrTitle) {
return [itemIdOrTitle]
}
return []
@ -110,10 +142,10 @@ useEventListener(document, 'click', handleClose)
watch(
() => modelValue,
() => {
selectedIds.value = selectedIds.value = selectedTitles.value.flatMap((el) => {
const item = options.value.find((op) => op.title === el)?.id
if (item) {
return [item]
selectedIds.value = selectedTitles.value.flatMap((el) => {
const item = options.value.find((op) => op.title === el)
if (item && (item.id || item.title)) {
return [(item.id || item.title)!]
}
return []
@ -139,8 +171,65 @@ useSelectedCellKeyupListener(active, (e) => {
isOpen.value = true
}
break
default:
isOpen.value = true
break
}
})
const activeOptCreateInProgress = ref(0)
async function addIfMissingAndSave() {
if (!searchVal.value || isPublic.value) return false
try {
tempSelectedOptsState.push(searchVal.value)
const newOptValue = searchVal?.value
searchVal.value = ''
activeOptCreateInProgress.value++
if (newOptValue && !options.value.some((o) => o.title === newOptValue)) {
const newOptions = [...options.value]
newOptions.push({
title: newOptValue,
value: newOptValue,
color: enumColor.light[(options.value.length + 1) % enumColor.light.length],
})
column.value.colOptions = { options: newOptions.map(({ value: _, ...rest }) => rest) }
await $api.dbTableColumn.update((column.value as { fk_column_id?: string })?.fk_column_id || (column.value?.id as string), {
...column.value,
})
activeOptCreateInProgress.value--
if (!activeOptCreateInProgress.value) {
await getMeta(column.value.fk_model_id!, true)
vModel.value = [...vModel.value]
tempSelectedOptsState.splice(0, tempSelectedOptsState.length)
}
} else {
activeOptCreateInProgress.value--
}
} catch (e) {
// todo: handle error
console.log(e)
activeOptCreateInProgress.value--
message.error(await extractSdkResponseErrorMsg(e))
}
}
const search = () => {
searchVal.value = aselect.value?.$el?.querySelector('.ant-select-selection-search-input')?.value
}
const onTagClick = (e: Event, onClose: Function) => {
// check clicked element is remove icon
if (
(e.target as HTMLElement)?.classList.contains('ant-tag-close-icon') ||
(e.target as HTMLElement)?.closest('.ant-tag-close-icon')
) {
e.stopPropagation()
onClose()
}
}
</script>
<template>
@ -151,17 +240,20 @@ useSelectedCellKeyupListener(active, (e) => {
mode="multiple"
class="w-full"
:bordered="false"
clear-icon
:show-arrow="!readOnly"
:show-search="false"
:show-search="active || editable"
:open="isOpen && (active || editable)"
:disabled="readOnly"
:class="{ '!ml-[-8px]': readOnly }"
:dropdown-class-name="`nc-dropdown-multi-select-cell ${isOpen ? 'active' : ''}`"
@keydown.enter.stop
@click="isOpen = active && !isOpen"
@search="search"
@keydown.stop
@click="isOpen = (active || editable) && !isOpen"
>
<a-select-option
v-for="op of options"
:key="op.id"
:key="op.id || op.title"
:value="op.title"
:data-testid="`select-option-${column.title}-${rowIndex}`"
@click.stop
@ -181,14 +273,24 @@ useSelectedCellKeyupListener(active, (e) => {
</a-tag>
</a-select-option>
<a-select-option v-if="searchVal && isOptionMissing && !isPublic" :key="searchVal" :value="searchVal">
<div class="flex gap-2 text-gray-500 items-center h-full">
<MdiPlusThick class="min-w-4" />
<div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong>
</div>
</div>
</a-select-option>
<template #tagRender="{ value: val, onClose }">
<a-tag
v-if="options.find((el) => el.title === val)"
class="rounded-tag"
class="rounded-tag nc-selected-option"
:style="{ display: 'flex', alignItems: 'center' }"
:color="options.find((el) => el.title === val)?.color"
:closable="active && (vModel.length > 1 || !column?.rqd)"
:closable="(active || editable) && (vModel.length > 1 || !column?.rqd)"
:close-icon="h(MdiCloseCircle, { class: ['ms-close-icon'] })"
@click="onTagClick($event, onClose)"
@close="onClose"
>
<span
@ -254,6 +356,3 @@ useSelectedCellKeyupListener(active, (e) => {
@apply "flex overflow-hidden";
}
</style>
<!--
-->

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

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, inject, useVModel } from '#imports'
interface Props {
@ -12,15 +13,21 @@ const emits = defineEmits(['update:modelValue'])
const editEnabled = inject(EditModeInj)
const vModel = useVModel(props, 'modelValue', emits)
const focus: VNodeRef = (el) => {
;(el as HTMLInputElement)?.focus()
}
</script>
<template>
<input
v-if="editEnabled"
:ref="focus"
v-model="vModel"
class="w-full !border-none text-base"
:class="{ '!px-2': editEnabled }"
type="number"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop
@keydown.right.stop

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

@ -1,9 +1,22 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2'
import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType } from 'nocodb-sdk'
import { ActiveCellInj, ColumnInj, IsKanbanInj, ReadonlyInj, computed, inject, ref, useEventListener, watch } from '#imports'
import { useSelectedCellKeyupListener } from '~/composables/useSelectedCellKeyupListener'
import {
ActiveCellInj,
ColumnInj,
EditModeInj,
IsKanbanInj,
ReadonlyInj,
computed,
enumColor,
extractSdkResponseErrorMsg,
inject,
ref,
useSelectedCellKeyupListener,
watch,
} from '#imports'
interface Props {
modelValue?: string | undefined
@ -20,18 +33,27 @@ const readOnly = inject(ReadonlyInj)!
const active = inject(ActiveCellInj, ref(false))
const editable = inject(EditModeInj, ref(false))
const aselect = ref<typeof AntSelect>()
const isOpen = ref(false)
const isKanban = inject(IsKanbanInj, ref(false))
const vModel = computed({
get: () => modelValue,
set: (val) => emit('update:modelValue', val || null),
})
const isPublic = inject(IsPublicInj, ref(false))
const { $api } = useNuxtApp()
const searchVal = ref()
const { getMeta } = useMetas()
// a variable to keep newly created option value
// temporary until it's add the option to column meta
const tempSelectedOptState = ref<string>()
const options = computed<SelectOptionType[]>(() => {
const options = computed<(SelectOptionType & { value: string })[]>(() => {
if (column?.value.colOptions) {
const opts = column.value.colOptions
? // todo: fix colOptions type, options does not exist as a property
@ -40,19 +62,27 @@ const options = computed<SelectOptionType[]>(() => {
for (const op of opts.filter((el: any) => el.order === null)) {
op.title = op.title.replace(/^'/, '').replace(/'$/, '')
}
return opts
return opts.map((o: any) => ({ ...o, value: o.title }))
}
return []
})
const handleClose = (e: MouseEvent) => {
if (aselect.value && !aselect.value.$el.contains(e.target)) {
isOpen.value = false
aselect.value.blur()
}
}
const isOptionMissing = computed(() => {
return (options.value ?? []).every((op) => op.title !== searchVal.value)
})
useEventListener(document, 'click', handleClose)
const vModel = computed({
get: () => tempSelectedOptState.value ?? modelValue,
set: (val) => {
if (isOptionMissing.value && val === searchVal.value) {
tempSelectedOptState.value = val
return addIfMissingAndSave().finally(() => {
tempSelectedOptState.value = undefined
})
}
emit('update:modelValue', val || null)
},
})
watch(isOpen, (n, _o) => {
if (!n) {
@ -74,6 +104,50 @@ useSelectedCellKeyupListener(active, (e) => {
break
}
})
async function addIfMissingAndSave() {
if (!searchVal.value || isPublic.value) return false
const newOptValue = searchVal.value
searchVal.value = ''
if (newOptValue && !options.value.some((o) => o.title === newOptValue)) {
try {
options.value.push({
title: newOptValue,
value: newOptValue,
color: enumColor.light[(options.value.length + 1) % enumColor.light.length],
})
column.value.colOptions = { options: options.value.map(({ value: _, ...rest }) => rest) }
await $api.dbTableColumn.update((column.value as { fk_column_id?: string })?.fk_column_id || (column.value?.id as string), {
...column.value,
})
vModel.value = newOptValue
await getMeta(column.value.fk_model_id!, true)
} catch (e) {
console.log(e)
message.error(await extractSdkResponseErrorMsg(e))
}
}
}
const search = () => {
searchVal.value = aselect.value?.$el?.querySelector('.ant-select-selection-search-input')?.value
}
const toggleMenu = (e: Event) => {
// todo: refactor
// check clicked element is clear icon
if (
(e.target as HTMLElement)?.classList.contains('ant-select-clear') ||
(e.target as HTMLElement)?.closest('.ant-select-clear')
) {
vModel.value = ''
return
}
isOpen.value = (active.value || editable.value) && !isOpen.value
}
</script>
<template>
@ -83,13 +157,15 @@ useSelectedCellKeyupListener(active, (e) => {
class="w-full"
:allow-clear="!column.rqd && active"
:bordered="false"
:open="isOpen"
:open="isOpen && (active || editable)"
:disabled="readOnly"
:show-arrow="!readOnly && (active || vModel === null)"
:show-arrow="!readOnly && (active || editable || vModel === null)"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen ? 'active' : ''}`"
:show-search="active || editable"
@select="isOpen = false"
@keydown.enter.stop
@click="isOpen = active && !isOpen"
@keydown.stop
@search="search"
@click="toggleMenu"
>
<a-select-option
v-for="op of options"
@ -112,6 +188,15 @@ useSelectedCellKeyupListener(active, (e) => {
</span>
</a-tag>
</a-select-option>
<a-select-option v-if="searchVal && isOptionMissing && !isPublic" :key="searchVal" :value="searchVal">
<div class="flex gap-2 text-gray-500 items-center h-full">
<MdiPlusThick class="min-w-4" />
<div class="text-xs whitespace-normal">
Create new option named <strong>{{ searchVal }}</strong>
</div>
</div>
</a-select-option>
</a-select>
</template>
@ -128,6 +213,3 @@ useSelectedCellKeyupListener(active, (e) => {
opacity: 1;
}
</style>
<!--
-->

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

@ -16,7 +16,9 @@ const readonly = inject(ReadonlyInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits)
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
const focus: VNodeRef = (el) => {
;(el as HTMLInputElement)?.focus()
}
</script>
<template>

1
packages/nc-gui/components/dashboard/TreeView.vue

@ -336,6 +336,7 @@ const onSearchCloseIconClick = () => {
>
<GeneralTooltip class="pl-5 pr-3 py-2" modifier-key="Alt">
<template #title>{{ table.table_name }}</template>
<div class="flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)">
<div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`">
<MdiDragVertical

38
packages/nc-gui/components/dlg/TableCreate.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Form, computed, onMounted, ref, useProject, useTable, useTabs, useVModel, validateTableName } from '#imports'
import { Form, computed, nextTick, onMounted, ref, useProject, useTable, useTabs, useVModel, validateTableName } from '#imports'
import { TabType } from '~/lib'
const props = defineProps<{
@ -32,13 +32,21 @@ const { table, createTable, generateUniqueTitle, tables, project } = useTable(as
const useForm = Form.useForm
const validateDuplicateAlias = (v: string) => (tables.value || []).every((t) => t.title !== (v || '')) || 'Duplicate table alias'
const validators = computed(() => {
return {
title: [
validateTableName,
validateDuplicateAlias,
{
validator: (_: any, value: any) => {
// validate duplicate alias
return new Promise((resolve, reject) => {
if ((tables.value || []).some((t) => t.title === (value || ''))) {
return reject(new Error('Duplicate table alias'))
}
return resolve(true)
})
},
},
{
validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => {
@ -62,17 +70,29 @@ const validators = computed(() => {
table_name: [validateTableName],
}
})
const { validateInfos } = useForm(table, validators)
const { validate, validateInfos } = useForm(table, validators)
const systemColumnsCheckboxInfo = SYSTEM_COLUMNS.map((c, index) => ({
value: c,
disabled: index === 0,
}))
const _createTable = async () => {
try {
await validate()
} catch (e: any) {
e.errorFields.map((f: Record<string, any>) => message.error(f.errors.join(',')))
if (e.errorFields.length) return
}
await createTable()
}
onMounted(() => {
generateUniqueTitle()
inputEl.value?.focus()
nextTick(() => {
inputEl.value?.focus()
inputEl.value?.select()
})
})
</script>
@ -87,11 +107,11 @@ onMounted(() => {
<template #footer>
<a-button key="back" size="large" @click="dialogShow = false">{{ $t('general.cancel') }}</a-button>
<a-button key="submit" size="large" type="primary" @click="createTable()">{{ $t('general.submit') }}</a-button>
<a-button key="submit" size="large" type="primary" @click="_createTable">{{ $t('general.submit') }}</a-button>
</template>
<div class="pl-10 pr-10 pt-5">
<a-form :model="table" name="create-new-table-form" @keydown.enter="createTable">
<a-form :model="table" name="create-new-table-form" @keydown.enter="_createTable">
<!-- Create A New Table -->
<div class="prose-xl font-bold self-center my-4">{{ $t('activity.createTable') }}</div>

1
packages/nc-gui/components/dlg/TableRename.vue

@ -119,6 +119,7 @@ const renameTable = async () => {
await $api.dbTable.update(tableMeta.id as string, {
project_id: tableMeta.project_id,
table_name: formState.title,
title: formState.title,
})
dialogShow.value = false

2
packages/nc-gui/components/dlg/ViewCreate.vue

@ -215,7 +215,7 @@ async function onSubmit() {
<template>
<a-modal v-model:visible="vModel" class="!top-[35%]" :confirm-loading="loading" wrap-class-name="nc-modal-view-create">
<template #title>
{{ $t(`general.${selectedViewId ? 'duplicate' : 'create'}`) }} <span class="text-capitalize">{{ typeAlias }}</span>
{{ $t(`general.${selectedViewId ? 'duplicate' : 'create'}`) }} <span class="capitalize">{{ typeAlias }}</span>
{{ $t('objects.view') }}
</template>

19
packages/nc-gui/components/general/language/Menu.vue

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { Language } from '~/lib'
import { onMounted, useGlobal, useI18n, useNuxtApp } from '#imports'
import { useGlobal, useI18n, useNuxtApp } from '#imports'
import { setI18nLanguage } from '~/plugins/a.i18n'
const { $e } = useNuxtApp()
@ -11,31 +11,14 @@ const { locale } = useI18n()
const languages = $computed(() => Object.entries(Language).sort() as [keyof typeof Language, Language][])
const isRtlLang = $computed(() => ['fa', 'ar'].includes(currentLang.value))
function applyDirection() {
const targetDirection = isRtlLang ? 'rtl' : 'ltr'
const oppositeDirection = targetDirection === 'ltr' ? 'rtl' : 'ltr'
document.body.classList.remove(oppositeDirection)
document.body.classList.add(targetDirection)
document.body.style.direction = targetDirection
}
async function changeLanguage(lang: string) {
const nextLang = lang as keyof typeof Language
await setI18nLanguage(nextLang)
currentLang.value = nextLang
applyDirection()
$e('c:navbar:lang', { lang })
}
onMounted(() => {
applyDirection()
})
</script>
<template>

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

@ -1,4 +1,6 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import type { InputPassword } from 'ant-design-vue'
import { extractSdkResponseErrorMsg, message, ref, useRoute, useSharedView, useVModel } from '#imports'
const props = defineProps<{
@ -24,6 +26,8 @@ const onFinish = async () => {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const focus: VNodeRef = (el: typeof InputPassword) => el?.$el?.querySelector('input').focus()
</script>
<template>
@ -42,7 +46,7 @@ const onFinish = async () => {
<a-form ref="formRef" :model="formState" class="mt-2" @finish="onFinish">
<a-form-item name="password" :rules="[{ required: true, message: 'Password is required' }]">
<a-input-password v-model:value="formState.password" placeholder="Enter password" />
<a-input-password :ref="focus" v-model:value="formState.password" placeholder="Enter password" />
</a-form-item>
<a-button type="primary" html-type="submit">Unlock</a-button>

4
packages/nc-gui/components/smartsheet/ApiSnippet.vue

@ -103,9 +103,9 @@ const activeLang = $computed(() => langs.find((lang) => lang.name === selectedLa
const code = $computed(() => {
if (activeLang?.name === 'nocodb-sdk') {
return `${selectedClient === 'node' ? 'const { Api } require("nocodb-sdk");' : 'import { Api } from "nocodb-sdk";'}
return `${selectedClient === 'node' ? 'const { Api } = require("nocodb-sdk");' : 'import { Api } from "nocodb-sdk";'}
const api = new Api({
baseURL: ${JSON.stringify(apiUrl)},
baseURL: "${(appInfo && appInfo.ncSiteUrl) || '/'}",
headers: {
"xc-auth": ${JSON.stringify(token as string)}
}

133
packages/nc-gui/components/smartsheet/Cell.vue

@ -10,11 +10,37 @@ import {
ReadonlyInj,
computed,
inject,
isAttachment,
isAutoSaved,
isBoolean,
isCurrency,
isDate,
isDateTime,
isDecimal,
isDuration,
isGeoData,
isEmail,
isFloat,
isInt,
isJSON,
isManualSaved,
isMultiSelect,
isPercent,
isPhoneNumber,
isPrimary,
isPrimaryKey,
isRating,
isSingleSelect,
isString,
isTextArea,
isTime,
isURL,
isYear,
provide,
ref,
toRef,
useColumn,
useDebounceFn,
useProject,
useSmartsheetRowStoreOrThrow,
useVModel,
} from '#imports'
@ -46,9 +72,7 @@ provide(EditModeInj, useVModel(props, 'editEnabled', emit))
provide(ActiveCellInj, active)
if (readOnly?.value) {
provide(ReadonlyInj, readOnly)
}
provide(ReadonlyInj, readOnly)
const isForm = inject(IsFormInj, ref(false))
@ -58,6 +82,10 @@ const isLocked = inject(IsLockedInj, ref(false))
const { currentRow } = useSmartsheetRowStoreOrThrow()
const { sqlUi } = useProject()
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
const syncValue = useDebounceFn(
() => {
currentRow.value.rowMeta.changed = false
@ -66,34 +94,6 @@ const syncValue = useDebounceFn(
500,
{ maxWait: 2000 },
)
const {
isPrimary,
isURL,
isEmail,
isJSON,
isGeoData,
isDate,
isYear,
isDateTime,
isTime,
isBoolean,
isDuration,
isRating,
isCurrency,
isAttachment,
isTextArea,
isString,
isInt,
isFloat,
isDecimal,
isSingleSelect,
isMultiSelect,
isPercent,
isPhoneNumber,
isAutoSaved,
isManualSaved,
isPrimaryKey,
} = useColumn(column)
const vModel = computed({
get: () => props.modelValue,
@ -101,9 +101,9 @@ const vModel = computed({
if (val !== props.modelValue) {
currentRow.value.rowMeta.changed = true
emit('update:modelValue', val)
if (isAutoSaved.value) {
if (isAutoSaved(column.value)) {
syncValue()
} else if (!isManualSaved.value) {
} else if (!isManualSaved(column.value)) {
emit('save')
currentRow.value.rowMeta.changed = true
}
@ -112,8 +112,7 @@ const vModel = computed({
})
const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => {
console.log('syncAndNavigate', e.target)
if (isJSON.value) return
if (isJSON(column.value)) return
if (currentRow.value.rowMeta.changed || currentRow.value.rowMeta.new) {
emit('save')
@ -128,36 +127,42 @@ const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => {
<template>
<div
class="nc-cell w-full"
:class="[`nc-cell-${(column?.uidt || 'default').toLowerCase()}`, { 'text-blue-600': isPrimary && !virtual && !isForm }]"
:class="[
`nc-cell-${(column?.uidt || 'default').toLowerCase()}`,
{ 'text-blue-600': isPrimary(column) && !virtual && !isForm },
]"
@keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)"
>
<LazyCellTextArea v-if="isTextArea" v-model="vModel" />
<!-- TODO: review the hacky type checking here (we had to move geoData check at the beginning atm,
otherwise isString would kick in also for the current GeoData types, since it's abstractType is also a string) -->
<LazyCellGeoData v-else-if="isGeoData" v-model="vModel" />
<LazyCellCheckbox v-else-if="isBoolean" v-model="vModel" />
<LazyCellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellSingleSelect v-else-if="isSingleSelect" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellMultiSelect v-else-if="isMultiSelect" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellDatePicker v-else-if="isDate" v-model="vModel" :is-pk="isPrimaryKey" />
<LazyCellYearPicker v-else-if="isYear" v-model="vModel" :is-pk="isPrimaryKey" />
<LazyCellDateTimePicker v-else-if="isDateTime" v-model="vModel" :is-pk="isPrimaryKey" />
<LazyCellTimePicker v-else-if="isTime" v-model="vModel" :is-pk="isPrimaryKey" />
<LazyCellRating v-else-if="isRating" v-model="vModel" />
<LazyCellDuration v-else-if="isDuration" v-model="vModel" />
<LazyCellEmail v-else-if="isEmail" v-model="vModel" />
<LazyCellUrl v-else-if="isURL" v-model="vModel" />
<LazyCellPhoneNumber v-else-if="isPhoneNumber" v-model="vModel" />
<LazyCellPercent v-else-if="isPercent" v-model="vModel" />
<LazyCellCurrency v-else-if="isCurrency" v-model="vModel" @save="emit('save')" />
<LazyCellDecimal v-else-if="isDecimal" v-model="vModel" />
<LazyCellInteger v-else-if="isInt" v-model="vModel" />
<LazyCellFloat v-else-if="isFloat" v-model="vModel" />
<LazyCellText v-else-if="isString" v-model="vModel" />
<LazyCellJson v-else-if="isJSON" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div v-if="(isLocked || (isPublic && readOnly && !isForm)) && !isAttachment" class="nc-locked-overlay" @click.stop.prevent />
<template v-if="column">
<LazyCellTextArea v-if="isTextArea(column)" v-model="vModel" />
<LazyCellGeoData v-else-if="isGeoData(column)" v-model="vModel" />
<LazyCellCheckbox v-else-if="isBoolean(column, abstractType)" v-model="vModel" />
<LazyCellAttachment v-else-if="isAttachment(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellSingleSelect v-else-if="isSingleSelect(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellMultiSelect v-else-if="isMultiSelect(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellDatePicker v-else-if="isDate(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellYearPicker v-else-if="isYear(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellDateTimePicker v-else-if="isDateTime(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellTimePicker v-else-if="isTime(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellRating v-else-if="isRating(column)" v-model="vModel" />
<LazyCellDuration v-else-if="isDuration(column)" v-model="vModel" />
<LazyCellEmail v-else-if="isEmail(column)" v-model="vModel" />
<LazyCellUrl v-else-if="isURL(column)" v-model="vModel" />
<LazyCellPhoneNumber v-else-if="isPhoneNumber(column)" v-model="vModel" />
<LazyCellPercent v-else-if="isPercent(column)" v-model="vModel" />
<LazyCellCurrency v-else-if="isCurrency(column)" v-model="vModel" @save="emit('save')" />
<LazyCellDecimal v-else-if="isDecimal(column)" v-model="vModel" />
<LazyCellInteger v-else-if="isInt(column, abstractType)" v-model="vModel" />
<LazyCellFloat v-else-if="isFloat(column, abstractType)" v-model="vModel" />
<LazyCellText v-else-if="isString(column, abstractType)" v-model="vModel" />
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div
v-if="(isLocked || (isPublic && readOnly && !isForm)) && !isAttachment(column)"
class="nc-locked-overlay"
@click.stop.prevent
/>
</template>
</div>
</template>

49
packages/nc-gui/components/smartsheet/Grid.vue

@ -423,6 +423,7 @@ onClickOutside(smartTable, (e) => {
const onNavigate = (dir: NavigateDir) => {
if (selected.row === null || selected.col === null) return
editEnabled = false
switch (dir) {
case NavigateDir.NEXT:
if (selected.row < data.value.length - 1) {
@ -435,8 +436,6 @@ const onNavigate = (dir: NavigateDir) => {
case NavigateDir.PREV:
if (selected.row > 0) {
selected.row--
} else {
editEnabled = false
}
break
}
@ -521,22 +520,31 @@ provide(ReloadRowDataHookInj, reloadViewDataHook)
// trigger initial data load in grid
// reloadViewDataHook.trigger()
const switchingTab = ref(false)
watch(
view,
async (next, old) => {
if (next && next.id !== old?.id) {
// whenever tab changes or view changes save any unsaved data
if (old?.id) {
const oldMeta = await getMeta(old.fk_model_id!)
if (oldMeta) {
await saveOrUpdateRecords({
viewMetaValue: old,
metaValue: oldMeta as TableType,
data: data.value,
})
try {
if (next && next.id !== old?.id) {
switchingTab.value = true
// whenever tab changes or view changes save any unsaved data
if (old?.id) {
const oldMeta = await getMeta(old.fk_model_id!)
if (oldMeta) {
await saveOrUpdateRecords({
viewMetaValue: old,
metaValue: oldMeta as TableType,
data: data.value,
})
}
}
await loadData()
}
await loadData()
} catch (e) {
console.log(e)
} finally {
switchingTab.value = false
}
},
{ immediate: true },
@ -703,7 +711,7 @@ watch(
@mouseover="selectBlock(rowIndex, colIndex)"
@contextmenu="showContextMenu($event, { row: rowIndex, col: colIndex })"
>
<div class="w-full h-full">
<div v-if="!switchingTab" class="w-full h-full">
<LazySmartsheetVirtualCell
v-if="isVirtualCol(columnObj)"
v-model="row.row[columnObj.title]"
@ -846,8 +854,7 @@ watch(
text-overflow: ellipsis;
}
td.active::after,
td.active::before {
td.active::after {
content: '';
position: absolute;
z-index: 3;
@ -860,12 +867,14 @@ watch(
// todo: replace with css variable
td.active::after {
@apply border-2 border-solid border-primary;
@apply border-2 border-solid text-primary border-current bg-primary bg-opacity-5;
}
td.active::before {
@apply bg-primary bg-opacity-5;
}
//td.active::before {
// content: '';
// z-index:4;
// @apply absolute !w-[10px] !h-[10px] !right-[-5px] !bottom-[-5px] bg-primary;
//}
}
:deep {

34
packages/nc-gui/components/smartsheet/VirtualCell.vue

@ -1,6 +1,22 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import { ActiveCellInj, CellValueInj, ColumnInj, IsFormInj, RowInj, inject, provide, ref, toRef, useVirtualCell } from '#imports'
import {
ActiveCellInj,
CellValueInj,
ColumnInj,
IsFormInj,
RowInj,
inject,
isBt,
isCount,
isFormula,
isHm,
isLookup,
isMm,
isRollup,
provide,
toRef,
} from '#imports'
import type { Row } from '~/lib'
import { NavigateDir } from '~/lib'
@ -24,8 +40,6 @@ provide(CellValueInj, toRef(props, 'modelValue'))
const isForm = inject(IsFormInj, ref(false))
const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualCell(column)
function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
emit('navigate', dir)
@ -39,12 +53,12 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
@keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
>
<LazyVirtualCellHasMany v-if="isHm" />
<LazyVirtualCellManyToMany v-else-if="isMm" />
<LazyVirtualCellBelongsTo v-else-if="isBt" />
<LazyVirtualCellRollup v-else-if="isRollup" />
<LazyVirtualCellFormula v-else-if="isFormula" />
<LazyVirtualCellCount v-else-if="isCount" />
<LazyVirtualCellLookup v-else-if="isLookup" />
<LazyVirtualCellHasMany v-if="isHm(column)" />
<LazyVirtualCellManyToMany v-else-if="isMm(column)" />
<LazyVirtualCellBelongsTo v-else-if="isBt(column)" />
<LazyVirtualCellRollup v-else-if="isRollup(column)" />
<LazyVirtualCellFormula v-else-if="isFormula(column)" />
<LazyVirtualCellCount v-else-if="isCount(column)" />
<LazyVirtualCellLookup v-else-if="isLookup(column)" />
</div>
</template>

7
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -1,4 +1,5 @@
<script lang="ts" setup>
import { useEventListener } from '@vueuse/core'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import {
IsFormInj,
@ -116,6 +117,12 @@ onMounted(() => {
formState.value.column_name = formState.value?.title
}
})
useEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Escape') {
emit('cancel')
}
})
</script>
<template>

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

@ -163,12 +163,12 @@ watch(inputs, () => {
v-model:value="element.title"
class="caption"
:data-testid="`select-column-option-input-${index}`"
@keydown.enter.prevent="element.title?.trim() && addNewOption()"
@change="optionChanged(element.id)"
/>
<MdiClose
class="ml-2 hover:!text-black"
:style="{ color: 'red' }"
class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer"
:data-testid="`select-column-option-remove-${index}`"
@click="removeOption(index)"
/>

42
packages/nc-gui/components/smartsheet/expanded-form/Header.vue

@ -25,6 +25,8 @@ const { isUIAllowed } = useUIPermission()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const saveRowAndStay = ref(0)
const save = async () => {
if (isNew.value) {
const data = await _save(state.value)
@ -34,6 +36,9 @@ const save = async () => {
await _save()
reloadTrigger?.trigger()
}
if (!saveRowAndStay.value) {
emit('cancel')
}
}
// todo: accept as a prop / inject
@ -101,14 +106,39 @@ const copyRecordUrl = () => {
</a-tooltip>
<a-button class="!text mx-1 nc-expand-form-close-btn" @click="emit('cancel')">
<!-- Cancel -->
{{ $t('general.cancel') }}
<div class="flex items-center">
<MdiCloseCircleOutline class="mr-1" />
<!-- Close -->
{{ $t('general.close') }}
</div>
</a-button>
<a-button :disabled="!isUIAllowed('tableRowUpdate')" type="primary" class="mx-1" @click="save">
<!-- Save Row -->
{{ $t('activity.saveRow') }}
</a-button>
<a-dropdown-button class="nc-expand-form-save-btn" type="primary" :disabled="!isUIAllowed('tableRowUpdate')" @click="save">
<template #overlay>
<a-menu class="nc-expand-form-save-dropdown-menu">
<a-menu-item key="0" class="!py-2 flex gap-2" @click="saveRowAndStay = 0">
<div class="flex items-center">
<MdiContentSave class="mr-1" />
{{ $t('activity.saveAndExit') }}
</div>
</a-menu-item>
<a-menu-item key="1" class="!py-2 flex gap-2 items-center" @click="saveRowAndStay = 1">
<div class="flex items-center">
<MdiContentSaveEdit class="mr-1" />
{{ $t('activity.saveAndStay') }}
</div>
</a-menu-item>
</a-menu>
</template>
<div v-if="saveRowAndStay === 0" class="flex items-center">
<MdiContentSave class="mr-1" />
{{ $t('activity.saveAndExit') }}
</div>
<div v-if="saveRowAndStay === 1" class="flex items-center">
<MdiContentSaveEdit class="mr-1" />
{{ $t('activity.saveAndStay') }}
</div>
</a-dropdown-button>
</div>
</template>

12
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -121,6 +121,12 @@ if (isKanban.value) {
}
}
}
const cellWrapperEl = (wrapperEl: HTMLElement) => {
nextTick(() => {
;(wrapperEl?.querySelector('input,select,textarea') as HTMLInputElement)?.focus()
})
}
</script>
<script lang="ts">
@ -137,7 +143,7 @@ export default {
:body-style="{ 'padding': 0, 'display': 'flex', 'flex-direction': 'column' }"
:closable="false"
class="nc-drawer-expanded-form"
:class="{ 'active': isExpanded }"
:class="{ active: isExpanded }"
>
<SmartsheetExpandedFormHeader :view="props.view" @cancel="onClose" />
@ -146,7 +152,7 @@ export default {
<div class="flex-1 overflow-auto scrollbar-thin-dull nc-form-fields-container">
<div class="w-[500px] mx-auto">
<div
v-for="col of fields"
v-for="(col, i) of fields"
v-show="!isVirtualCol(col) || !isNew || col.uidt === UITypes.LinkToAnotherRecord"
:key="col.title"
class="mt-2 py-2"
@ -157,7 +163,7 @@ export default {
<LazySmartsheetHeaderCell v-else :column="col" />
<div class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2">
<div :ref="i ? null : cellWrapperEl" class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2">
<LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" />
<LazySmartsheetCell

136
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -0,0 +1,136 @@
import type { ColumnType } from 'nocodb-sdk'
import type { PropType } from '@vue/runtime-core'
import {
ColumnInj,
computed,
defineComponent,
h,
inject,
isAttachment,
isBoolean,
isCurrency,
isDate,
isDateTime,
isGeoData,
isDecimal,
isDuration,
isEmail,
isFloat,
isInt,
isJSON,
isPercent,
isPhoneNumber,
isPrimary,
isRating,
isSet,
isSingleSelect,
isSpecificDBType,
isString,
isTextArea,
isTime,
isURL,
isYear,
toRef,
useProject,
} from '#imports'
import FilePhoneIcon from '~icons/mdi/file-phone'
import KeyIcon from '~icons/mdi/key-variant'
import JSONIcon from '~icons/mdi/code-json'
import ClockIcon from '~icons/mdi/clock-time-five'
import WebIcon from '~icons/mdi/web'
import TextAreaIcon from '~icons/mdi/card-text-outline'
import StringIcon from '~icons/mdi/alpha-a-box-outline'
import BooleanIcon from '~icons/mdi/check-box-outline'
import CalendarIcon from '~icons/mdi/calendar'
import SingleSelectIcon from '~icons/mdi/arrow-down-drop-circle'
import MultiSelectIcon from '~icons/mdi/format-list-bulleted-square'
import DatetimeIcon from '~icons/mdi/calendar-clock'
import GeoDataIcon from '~icons/mdi/map-marker'
import RatingIcon from '~icons/mdi/star'
import GenericIcon from '~icons/mdi/square-rounded'
import NumericIcon from '~icons/mdi/numeric'
import AttachmentIcon from '~icons/mdi/image-multiple-outline'
import EmailIcon from '~icons/mdi/email'
import CurrencyIcon from '~icons/mdi/currency-usd-circle-outline'
import PercentIcon from '~icons/mdi/percent-outline'
import DecimalIcon from '~icons/mdi/decimal'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import DurationIcon from '~icons/mdi/timer-outline'
const renderIcon = (column: ColumnType, abstractType: any) => {
if (isPrimary(column)) {
return KeyIcon
} else if (isJSON(column)) {
return JSONIcon
} else if (isDate(column, abstractType)) {
return CalendarIcon
} else if (isDateTime(column, abstractType)) {
return DatetimeIcon
} else if (isGeoData(column)) {
return GeoDataIcon
} else if (isSet(column)) {
return MultiSelectIcon
} else if (isSingleSelect(column)) {
return SingleSelectIcon
} else if (isBoolean(column, abstractType)) {
return BooleanIcon
} else if (isTextArea(column)) {
return TextAreaIcon
} else if (isEmail(column)) {
return EmailIcon
} else if (isYear(column, abstractType)) {
return CalendarIcon
} else if (isTime(column, abstractType)) {
return ClockIcon
} else if (isRating(column)) {
return RatingIcon
} else if (isAttachment(column)) {
return AttachmentIcon
} else if (isDecimal(column)) {
return DecimalIcon
} else if (isPhoneNumber(column)) {
return FilePhoneIcon
} else if (isURL(column)) {
return WebIcon
} else if (isCurrency(column)) {
return CurrencyIcon
} else if (isDuration(column)) {
return DurationIcon
} else if (isPercent(column)) {
return PercentIcon
} else if (isInt(column, abstractType) || isFloat(column, abstractType)) {
return NumericIcon
} else if (isString(column, abstractType)) {
return StringIcon
} else if (isSpecificDBType(column)) {
return SpecificDBTypeIcon
} else {
return GenericIcon
}
}
export default defineComponent({
name: 'CellIcon',
props: {
columnMeta: {
type: Object as PropType<ColumnType>,
required: false,
},
},
setup(props) {
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta)
const { sqlUi } = useProject()
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
return () => {
if (!column.value) return null
return h(renderIcon(column.value, abstractType.value), { class: 'text-grey mx-1 !text-xs' })
}
},
})

27
packages/nc-gui/components/smartsheet/header/VirtualCell.vue

@ -7,6 +7,12 @@ import {
MetaInj,
computed,
inject,
isBt,
isFormula,
isHm,
isLookup,
isMm,
isRollup,
isVirtualColRequired,
provide,
ref,
@ -14,7 +20,6 @@ import {
useI18n,
useMetas,
useUIPermission,
useVirtualCell,
} from '#imports'
const props = defineProps<{ column: ColumnType; hideMenu?: boolean; required?: boolean | number }>()
@ -37,14 +42,12 @@ const meta = inject(MetaInj, ref())
const isForm = inject(IsFormInj, ref(false))
const { isLookup, isBt, isRollup, isMm, isHm, isFormula } = useVirtualCell(column)
const colOptions = $computed(() => column.value?.colOptions)
const tableTile = $computed(() => meta?.value?.title)
const relationColumnOptions = $computed<LinkToAnotherRecordType | null>(() => {
if (isMm.value || isHm.value || isBt.value) {
if (isMm(column.value) || isHm(column.value) || isBt(column.value)) {
return column.value?.colOptions as LinkToAnotherRecordType
} else if ((column?.value?.colOptions as LookupType | RollupType)?.fk_relation_column_id) {
return meta?.value?.columns?.find(
@ -62,10 +65,10 @@ const relatedTableTitle = $computed(() => relatedTableMeta?.title)
const childColumn = $computed(() => {
if (relatedTableMeta?.columns) {
if (isRollup.value) {
if (isRollup(column.value)) {
return relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as RollupType).fk_rollup_column_id)
}
if (isLookup.value) {
if (isLookup(column.value)) {
return relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as LookupType).fk_lookup_column_id)
}
}
@ -76,22 +79,22 @@ const tooltipMsg = computed(() => {
if (!column.value) {
return ''
}
if (isHm.value) {
if (isHm(column.value)) {
return `'${tableTile}' ${t('labels.hasMany')} '${relatedTableTitle}'`
} else if (isMm.value) {
} else if (isMm(column.value)) {
return `'${tableTile}' & '${relatedTableTitle}' ${t('labels.manyToMany')}`
} else if (isBt.value) {
} else if (isBt(column.value)) {
return `'${column?.value?.title}' ${t('labels.belongsTo')} '${relatedTableTitle}'`
} else if (isLookup.value) {
} else if (isLookup(column.value)) {
return `'${childColumn.title}' from '${relatedTableTitle}' (${childColumn.uidt})`
} else if (isFormula.value) {
} else if (isFormula(column.value)) {
const formula = substituteColumnIdWithAliasInFormula(
(column.value?.colOptions as FormulaType)?.formula,
meta?.value?.columns as ColumnType[],
(column.value?.colOptions as any)?.formula_raw,
)
return `Formula - ${formula}`
} else if (isRollup.value) {
} else if (isRollup(column.value)) {
return `'${childColumn.title}' of '${relatedTableTitle}' (${childColumn.uidt})`
}
return ''

75
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.vue → packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -1,8 +1,8 @@
<script setup lang="ts">
import type { PropType } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { ColumnInj, inject, ref, toRef } from '#imports'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import { ColumnInj, MetaInj, defineComponent, h, inject, isBt, isHm, isLookup, isMm, isRollup, ref, toRef } from '#imports'
import GenericIcon from '~icons/mdi/square-rounded'
import HMIcon from '~icons/mdi/table-arrow-right'
import BTIcon from '~icons/mdi/table-arrow-left'
@ -13,30 +13,10 @@ import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before'
const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, ref(columnMeta)) as Ref<ColumnType & { colOptions: LookupType }>
let relationColumn: ColumnType & { colOptions: LookupType }
if (column) {
const { isLookup, isBt, isRollup, isMm, isHm } = useVirtualCell(column as Ref<ColumnType>)
if (isLookup || isBt || isRollup || isMm || isHm) {
const meta = inject(MetaInj, ref())
relationColumn = meta.value?.columns?.find((c) => c.id === column.value?.colOptions?.fk_relation_column_id) as ColumnType & {
colOptions: LinkToAnotherRecordType
}
}
}
const icon = computed(() => {
switch (column?.value?.uidt) {
const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
switch (column.uidt) {
case UITypes.LinkToAnotherRecord:
switch ((column?.value?.colOptions as LinkToAnotherRecordType)?.type) {
switch ((column.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY:
return { icon: MMIcon, color: 'text-accent' }
case RelationTypes.HAS_MANY:
@ -72,10 +52,43 @@ const icon = computed(() => {
case UITypes.Count:
return { icon: CountIcon, color: 'text-grey' }
}
return { icon: GenericIcon, color: 'text-grey' }
})
</script>
}
export default defineComponent({
name: 'VirtualCellIcon',
props: {
columnMeta: {
type: Object as PropType<ColumnType>,
required: false,
},
},
setup(props) {
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta) as Ref<ColumnType & { colOptions: LookupType }>
let relationColumn: ColumnType & { colOptions: LookupType }
return () => {
if (!column.value) return null
<template>
<component :is="icon.icon" class="mx-1 !text-xs" :class="icon.color" />
</template>
if (column && column.value) {
if (isMm(column.value) || isHm(column.value) || isBt(column.value) || isLookup(column.value) || isRollup(column.value)) {
const meta = inject(MetaInj, ref())
relationColumn = meta.value?.columns?.find(
(c) => c.id === column.value?.colOptions?.fk_relation_column_id,
) as ColumnType & {
colOptions: LinkToAnotherRecordType
}
}
}
const { icon: Icon, color } = renderIcon(column.value, relationColumn)
return h(Icon, { class: `${color} mx-1 !text-xs` })
}
},
})

7
packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue

@ -8,6 +8,7 @@ import {
inject,
ref,
useGlobal,
useMenuCloseOnEsc,
useNuxtApp,
useSmartsheetStoreOrThrow,
useViewFilters,
@ -62,10 +63,14 @@ const filterAutoSaveLoc = computed({
filterAutoSave.value = val
},
})
const open = ref(false)
useMenuCloseOnEsc(open)
</script>
<template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-filter-menu">
<a-dropdown v-model:visible="open" :trigger="['click']" overlay-class-name="nc-dropdown-filter-menu">
<div :class="{ 'nc-badge nc-active-btn': filtersLength }">
<a-button v-e="['c:filter']" class="nc-filter-menu-btn nc-toolbar-btn txt-sm" :disabled="isLocked">
<div class="flex items-center gap-1">

7
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -14,6 +14,7 @@ import {
inject,
ref,
resolveComponent,
useMenuCloseOnEsc,
useNuxtApp,
useViewColumns,
watch,
@ -119,10 +120,14 @@ const getIcon = (c: ColumnType) =>
h(isVirtualCol(c) ? resolveComponent('SmartsheetHeaderVirtualCellIcon') : resolveComponent('SmartsheetHeaderCellIcon'), {
columnMeta: c,
})
const open = ref(false)
useMenuCloseOnEsc(open)
</script>
<template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-fields-menu">
<a-dropdown v-model:visible="open" :trigger="['click']" overlay-class-name="nc-dropdown-fields-menu">
<div :class="{ 'nc-badge nc-active-btn': isAnyFieldHidden }">
<a-button v-e="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1">

16
packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import { IsLockedInj, IsPublicInj, useKanbanViewStoreOrThrow } from '#imports'
import { IsLockedInj, IsPublicInj, useKanbanViewStoreOrThrow, useMenuCloseOnEsc } from '#imports'
const { isUIAllowed } = useUIPermission()
@ -7,12 +7,14 @@ const { groupingFieldColumn } = useKanbanViewStoreOrThrow()
const isLocked = inject(IsLockedInj, ref(false))
const addOrEditStackDropdown = ref(false)
const IsPublic = inject(IsPublicInj, ref(false))
const open = ref(false)
useMenuCloseOnEsc(open)
const handleSubmit = async () => {
addOrEditStackDropdown.value = false
open.value = false
}
provide(IsKanbanInj, ref(true))
@ -21,7 +23,7 @@ provide(IsKanbanInj, ref(true))
<template>
<a-dropdown
v-if="!IsPublic && isUIAllowed('edit-column')"
v-model:visible="addOrEditStackDropdown"
v-model:visible="open"
:trigger="['click']"
overlay-class-name="nc-dropdown-kanban-add-edit-stack-menu"
>
@ -42,10 +44,10 @@ provide(IsKanbanInj, ref(true))
</div>
<template #overlay>
<LazySmartsheetColumnEditOrAddProvider
v-if="addOrEditStackDropdown"
v-if="open"
:column="groupingFieldColumn"
@submit="handleSubmit"
@cancel="addOrEditStackDropdown = false"
@cancel="open = false"
@click.stop
@keydown.stop
/>

101
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -5,12 +5,12 @@ import tinycolor from 'tinycolor2'
import {
computed,
extractSdkResponseErrorMsg,
isRtlLang,
message,
projectThemeColors,
ref,
useCopy,
useDashboard,
useDebounceFn,
useI18n,
useNuxtApp,
useProject,
@ -40,13 +40,21 @@ const passwordProtected = ref(false)
const shared = ref<SharedView>({ id: '', meta: {}, password: undefined })
const transitionDuration = computed({
get: () => shared.value.meta.transitionDuration || 250,
set: (duration) => {
shared.value.meta = { ...shared.value.meta, transitionDuration: duration > 5000 ? 5000 : duration }
const withRTL = computed({
get: () => !!shared.value.meta.rtl,
set: (rtl) => {
shared.value.meta = { ...shared.value.meta, rtl }
updateSharedViewMeta()
},
})
// const transitionDuration = computed({
// get: () => shared.value.meta.transitionDuration || 50,
// set: (duration) => {
// shared.value.meta = { ...shared.value.meta, transitionDuration: duration > 5000 ? 5000 : duration }
// },
// })
const allowCSVDownload = computed({
get: () => !!shared.value.meta.allowCSVDownload,
set: (allow) => {
@ -105,7 +113,7 @@ const sharedViewUrl = computed(() => {
viewType = 'view'
}
return `${dashboardUrl?.value}#/nc/${viewType}/${shared.value.uuid}`
return encodeURI(`${dashboardUrl?.value}#/nc/${viewType}/${shared.value.uuid}`)
})
async function saveAllowCSVDownload() {
@ -123,7 +131,7 @@ async function saveTheme() {
$e(`a:view:share:${viewTheme.value ? 'enable' : 'disable'}-theme`)
}
const saveTransitionDuration = useDebounceFn(updateSharedViewMeta, 1000, { maxWait: 2000 })
// const saveTransitionDuration = useDebounceFn(updateSharedViewMeta, 1000, { maxWait: 2000 })
async function updateSharedViewMeta() {
try {
@ -184,6 +192,10 @@ watch(passwordProtected, (value) => {
saveShareLinkPassword()
}
})
const { locale } = useI18n()
const isRtl = computed(() => isRtlLang(locale.value as any))
</script>
<template>
@ -240,7 +252,7 @@ watch(passwordProtected, (value) => {
Use Survey Mode
</a-checkbox>
<Transition name="layout" mode="out-in">
<!-- <Transition name="layout" mode="out-in">
<div v-if="surveyMode" class="flex flex-col justify-center pl-6">
<a-form-item class="!my-1" :has-feedback="false" name="transitionDuration">
<template #label>
@ -256,33 +268,7 @@ watch(passwordProtected, (value) => {
/>
</a-form-item>
</div>
</Transition>
</div>
<div>
<!-- todo: i18n -->
<a-checkbox
v-if="shared.type === ViewTypes.FORM"
v-model:checked="viewTheme"
data-testid="nc-modal-share-view__with-theme"
class="!text-sm"
>
Use Theme
</a-checkbox>
<Transition name="layout" mode="out-in">
<div v-if="viewTheme" class="flex pl-6">
<LazyGeneralColorPicker
data-testid="nc-modal-share-view__theme-picker"
class="!p-0"
:model-value="shared.meta.theme?.primaryColor"
:colors="projectThemeColors"
:row-size="9"
:advanced="false"
@input="onChangeTheme"
/>
</div>
</Transition>
</Transition> -->
</div>
<div>
@ -318,20 +304,45 @@ watch(passwordProtected, (value) => {
</Transition>
</div>
<div>
<div
v-if="
shared && (shared.type === ViewTypes.GRID || shared.type === ViewTypes.KANBAN || shared.type === ViewTypes.GALLERY)
"
>
<!-- Allow Download -->
<a-checkbox
v-if="
shared &&
(shared.type === ViewTypes.GRID || shared.type === ViewTypes.KANBAN || shared.type === ViewTypes.GALLERY)
"
v-model:checked="allowCSVDownload"
data-testid="nc-modal-share-view__with-csv-download"
class="!text-sm"
>
<a-checkbox v-model:checked="allowCSVDownload" data-testid="nc-modal-share-view__with-csv-download" class="!text-sm">
{{ $t('labels.downloadAllowed') }}
</a-checkbox>
</div>
<div v-if="shared.type === ViewTypes.FORM">
<!-- todo: i18n -->
<a-checkbox v-model:checked="viewTheme" data-testid="nc-modal-share-view__with-theme" class="!text-sm">
Use Theme
</a-checkbox>
<Transition name="layout" mode="out-in">
<div v-if="viewTheme" class="flex pl-6">
<LazyGeneralColorPicker
data-testid="nc-modal-share-view__theme-picker"
class="!p-0"
:model-value="shared.meta.theme?.primaryColor"
:colors="projectThemeColors"
:row-size="9"
:advanced="false"
@input="onChangeTheme"
/>
</div>
</Transition>
</div>
<div v-if="shared.type === ViewTypes.FORM && isRtl">
<!-- use RTL orientation in form - todo: i18n -->
<a-checkbox v-model:checked="withRTL" data-testid="nc-modal-share-view__locale" class="!text-sm">
<!-- todo i18n -->
RTL Orientation
</a-checkbox>
</div>
</div>
</div>
</a-modal>

7
packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue

@ -9,6 +9,7 @@ import {
getSortDirectionOptions,
inject,
ref,
useMenuCloseOnEsc,
useViewSorts,
watch,
} from '#imports'
@ -37,10 +38,14 @@ watch(
},
{ immediate: true },
)
const open = ref(false)
useMenuCloseOnEsc(open)
</script>
<template>
<a-dropdown offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-sort-menu">
<a-dropdown v-model:visible="open" offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-sort-menu">
<div :class="{ 'nc-badge nc-active-btn': sorts?.length }">
<a-button v-e="['c:sort']" class="nc-sort-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1">

11
packages/nc-gui/components/smartsheet/toolbar/StackedBy.vue

@ -12,6 +12,7 @@ import {
inject,
ref,
useKanbanViewStoreOrThrow,
useMenuCloseOnEsc,
useViewColumns,
watch,
} from '#imports'
@ -30,7 +31,9 @@ const { fields, loadViewColumns, metaColumnById } = useViewColumns(activeView, m
const { kanbanMetaData, loadKanbanMeta, loadKanbanData, updateKanbanMeta, groupingField } = useKanbanViewStoreOrThrow()
const stackedByDropdown = ref(false)
const open = ref(false)
useMenuCloseOnEsc(open)
watch(
() => activeView.value?.id,
@ -68,14 +71,14 @@ const singleSelectFieldOptions = computed<SelectProps['options']>(() => {
})
const handleChange = () => {
stackedByDropdown.value = false
open.value = false
}
</script>
<template>
<a-dropdown
v-if="!IsPublic"
v-model:visible="stackedByDropdown"
v-model:visible="open"
:trigger="['click']"
overlay-class-name="nc-dropdown-kanban-stacked-by-menu"
>
@ -97,7 +100,7 @@ const handleChange = () => {
</div>
<template #overlay>
<div
v-if="stackedByDropdown"
v-if="open"
class="p-3 min-w-[280px] bg-gray-50 shadow-lg nc-table-toolbar-menu max-h-[max(80vh,500px)] overflow-auto !border"
@click.stop
>

9
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -8,6 +8,7 @@ import {
message,
ref,
useI18n,
useMenuCloseOnEsc,
useNuxtApp,
useProject,
useSmartsheetStoreOrThrow,
@ -79,11 +80,15 @@ async function changeLockType(type: LockType) {
}
const { isSqlView } = useSmartsheetStoreOrThrow()
const open = ref(false)
useMenuCloseOnEsc(open)
</script>
<template>
<div>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<a-dropdown v-model:visible="open" :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-2 items-center">
<component
@ -103,7 +108,7 @@ const { isSqlView } = useSmartsheetStoreOrThrow()
</a-button>
<template #overlay>
<a-menu class="ml-6 !text-sm !px-0 !py-2 !rounded" data-testid="toolbar-actions">
<a-menu class="ml-6 !text-sm !px-0 !py-2 !rounded" data-testid="toolbar-actions" @click="open = false">
<a-menu-item-group>
<a-sub-menu
v-if="isUIAllowed('view-type')"

134
packages/nc-gui/components/tabs/auth/UserManagement.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import { OrgUserRoles } from 'nocodb-sdk'
import type { RequestParams } from 'nocodb-sdk'
import {
extractSdkResponseErrorMsg,
@ -160,6 +161,10 @@ onBeforeMount(async () => {
})
watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
const isSuperAdmin = (user: { main_roles?: string }) => {
return user.main_roles?.split(',').includes(OrgUserRoles.SUPER_ADMIN)
}
</script>
<template>
@ -252,6 +257,13 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
</div>
<div class="flex w-1/6 justify-center flex-wrap ml-4">
<div
v-if="isSuperAdmin(user)"
class="rounded-full px-2 py-1 nc-user-role"
:style="{ backgroundColor: projectRoleTagColors[OrgUserRoles.SUPER_ADMIN] }"
>
Super Admin
</div>
<div
v-if="user.roles"
class="rounded-full px-2 py-1 nc-user-role"
@ -261,71 +273,73 @@ watchDebounced(searchText, () => loadUsers(), { debounce: 300, maxWait: 600 })
</div>
</div>
<div class="flex w-1/6 flex-wrap justify-end">
<a-tooltip v-if="user.project_id" placement="bottom">
<template #title>
<span>{{ $t('activity.editUser') }}</span>
</template>
<a-button type="text" class="!rounded-md nc-user-edit" @click="onEdit(user)">
<template #icon>
<IcRoundEdit class="flex mx-auto h-[1rem] text-gray-500" />
<template v-if="!isSuperAdmin(user)">
<a-tooltip v-if="user.project_id" placement="bottom">
<template #title>
<span>{{ $t('activity.editUser') }}</span>
</template>
</a-button>
</a-tooltip>
<!-- Add user to project -->
<a-tooltip v-if="!user.project_id" placement="bottom">
<template #title>
<span>{{ $t('activity.addUserToProject') }}</span>
</template>
<a-button type="text" class="!rounded-md nc-user-invite" @click="inviteUser(user)">
<template #icon>
<MdiPlus class="flex mx-auto h-[1.1rem] text-gray-500" />
<a-button type="text" class="!rounded-md nc-user-edit" @click="onEdit(user)">
<template #icon>
<IcRoundEdit class="flex mx-auto h-[1rem] text-gray-500" />
</template>
</a-button>
</a-tooltip>
<!-- Add user to project -->
<a-tooltip v-if="!user.project_id" placement="bottom">
<template #title>
<span>{{ $t('activity.addUserToProject') }}</span>
</template>
</a-button>
</a-tooltip>
<!-- Remove user from the project -->
<a-tooltip v-else placement="bottom">
<template #title>
<span>{{ $t('activity.deleteUser') }}</span>
</template>
<a-button v-e="['c:user:delete']" type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)">
<template #icon>
<MdiDeleteOutline class="flex mx-auto h-[1.1rem] text-gray-500" />
<a-button type="text" class="!rounded-md nc-user-invite" @click="inviteUser(user)">
<template #icon>
<MdiPlus class="flex mx-auto h-[1.1rem] text-gray-500" />
</template>
</a-button>
</a-tooltip>
<!-- Remove user from the project -->
<a-tooltip v-else placement="bottom">
<template #title>
<span>{{ $t('activity.deleteUser') }}</span>
</template>
</a-button>
</a-tooltip>
<a-dropdown :trigger="['click']" class="flex" placement="bottomRight" overlay-class-name="nc-dropdown-user-mgmt">
<div class="flex flex-row items-center">
<a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]">
<IcBaselineMoreVert />
</div>
<a-button v-e="['c:user:delete']" type="text" class="!rounded-md nc-user-delete" @click="onDelete(user)">
<template #icon>
<MdiDeleteOutline class="flex mx-auto h-[1.1rem] text-gray-500" />
</template>
</a-button>
</div>
<template #overlay>
<a-menu>
<a-menu-item>
<!-- Resend invite Email -->
<div class="flex flex-row items-center py-3" @click="resendInvite(user)">
<MdiEmailArrowRightOutline class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(user)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</a-tooltip>
<a-dropdown :trigger="['click']" class="flex" placement="bottomRight" overlay-class-name="nc-dropdown-user-mgmt">
<div class="flex flex-row items-center">
<a-button type="text" class="!px-0">
<div class="flex flex-row items-center h-[1.2rem]">
<IcBaselineMoreVert />
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</a-button>
</div>
<template #overlay>
<a-menu>
<a-menu-item>
<!-- Resend invite Email -->
<div class="flex flex-row items-center py-3" @click="resendInvite(user)">
<MdiEmailArrowRightOutline class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.resendInvite') }}</div>
</div>
</a-menu-item>
<a-menu-item>
<div class="flex flex-row items-center py-3" @click="copyInviteUrl(user)">
<MdiContentCopy class="flex h-[1rem] text-gray-500" />
<div class="text-xs pl-2">{{ $t('activity.copyInviteURL') }}</div>
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</div>
</div>

10
packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue

@ -1,9 +1,9 @@
<script setup lang="ts">
import type { Input } from 'ant-design-vue'
import {
Form,
computed,
extractSdkResponseErrorMsg,
isEmail,
message,
onMounted,
projectRoleTagColors,
@ -14,6 +14,7 @@ import {
useI18n,
useNuxtApp,
useProject,
validateEmail,
} from '#imports'
import type { User } from '~/lib'
import { ProjectRole } from '~/lib'
@ -57,7 +58,7 @@ const validators = computed(() => {
callback('Email is required')
return
}
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !isEmail(e))
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !validateEmail(e))
if (invalidEmails.length > 0) {
callback(`${invalidEmails.length > 1 ? ' Invalid emails:' : 'Invalid email:'} ${invalidEmails.join(', ')} `)
} else {
@ -133,6 +134,10 @@ const clickInviteMore = () => {
usersData.role = ProjectRole.Viewer
usersData.emails = undefined
}
const emailField = (inputEl: typeof Input) => {
inputEl?.$el?.focus()
}
</script>
<template>
@ -222,6 +227,7 @@ const clickInviteMore = () => {
<div class="ml-1 mb-1 text-xs text-gray-500">{{ $t('datatype.Email') }}:</div>
<a-input
:ref="emailField"
v-model:value="usersData.emails"
validate-trigger="onBlur"
:placeholder="$t('labels.email')"

56
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -1,7 +1,6 @@
<script lang="ts" setup>
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import {
CellUrlDisableOverlayInj,
CellValueInj,
@ -10,43 +9,66 @@ import {
ReadonlyInj,
computed,
inject,
isAttachment,
provide,
ref,
refAutoReset,
useColumn,
useMetas,
watch,
} from '#imports'
const { metas, getMeta } = useMetas()
provide(ReadonlyInj, ref(true))
const column = inject(ColumnInj)! as Ref<ColumnType & { colOptions: LookupType }>
const column = inject(ColumnInj, ref())
const meta = inject(MetaInj, ref())
const value = inject(CellValueInj)
const cellValue = inject(CellValueInj, ref())
const arrValue = computed(() => (Array.isArray(value?.value) ? value?.value : [value?.value]) ?? [])
const arrValue = computed(() => {
if (!cellValue.value) return []
const relationColumn = meta.value?.columns?.find((c) => c.id === column.value.colOptions?.fk_relation_column_id) as ColumnType & {
colOptions: LinkToAnotherRecordType
}
if (Array.isArray(cellValue.value)) return cellValue.value
await getMeta(relationColumn.colOptions.fk_related_model_id!)
return [cellValue.value]
})
const relationColumn = computed(
() =>
meta.value?.columns?.find((c) => c.id === (column.value?.colOptions as LookupType)?.fk_relation_column_id) as
| (ColumnType & {
colOptions: LinkToAnotherRecordType | undefined
})
| undefined,
)
watch(
relationColumn,
async (relationCol) => {
if (relationCol && relationCol.colOptions) await getMeta(relationCol.colOptions.fk_related_model_id!)
},
{ immediate: true },
)
const lookupTableMeta = computed<Record<string, any> | undefined>(() => {
if (relationColumn.value && relationColumn.value?.colOptions)
return metas.value[relationColumn.value.colOptions.fk_related_model_id!]
const lookupTableMeta = computed(() => metas.value[relationColumn.colOptions.fk_related_model_id!])
return undefined
})
const lookupColumn = computed<any>(
const lookupColumn = computed(
() =>
lookupTableMeta.value.columns?.find(
(c: Record<string, any>) => c.id === column.value.colOptions?.fk_lookup_column_id,
) as ColumnType,
lookupTableMeta.value?.columns?.find((c: any) => c.id === (column.value?.colOptions as LookupType)?.fk_lookup_column_id) as
| ColumnType
| undefined,
)
provide(MetaInj, lookupTableMeta)
provide(CellUrlDisableOverlayInj, ref(true))
const lookupColumnMetaProps = useColumn(lookupColumn)
provide(CellUrlDisableOverlayInj, ref(true))
const timeout = 3000 // in ms
@ -94,7 +116,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
:key="i"
class="min-w-max"
:class="{
'bg-gray-100 px-1 rounded-full flex-1': !lookupColumnMetaProps.isAttachment,
'bg-gray-100 px-1 rounded-full flex-1': !isAttachment(lookupColumn),
' border-gray-200 rounded border-1': ![UITypes.Attachment, UITypes.MultiSelect, UITypes.SingleSelect].includes(
lookupColumn.uidt,
),

101
packages/nc-gui/composables/useColumn.ts

@ -1,101 +0,0 @@
import type { ColumnType } from 'nocodb-sdk'
import { SqlUiFactory, UITypes, isVirtualCol } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import { computed, useProject } from '#imports'
export function useColumn(column: Ref<ColumnType | undefined>) {
const { project } = useProject()
const uiDatatype: ComputedRef<UITypes> = computed(() => column.value?.uidt as UITypes)
const abstractType = computed(() => {
// kludge: CY test hack; column.value is being received NULL during attach cell delete operation
return (column.value && isVirtualCol(column.value)) || !column.value
? null
: SqlUiFactory.create(
project.value?.bases?.[0]?.type ? { client: project.value.bases[0].type } : { client: 'mysql2' },
).getAbstractType(column.value)
})
const dataTypeLow = computed(() => column.value?.dt?.toLowerCase())
const isBoolean = computed(() => abstractType.value === 'boolean')
const isString = computed(() => uiDatatype.value === UITypes.SingleLineText || abstractType.value === 'string')
const isTextArea = computed(() => uiDatatype.value === UITypes.LongText)
const isInt = computed(() => abstractType.value === 'integer')
const isFloat = computed(() => abstractType.value === 'float' || abstractType.value === UITypes.Number)
const isDate = computed(() => abstractType.value === 'date' || uiDatatype.value === UITypes.Date)
const isYear = computed(() => abstractType.value === 'year' || uiDatatype.value === UITypes.Year)
const isTime = computed(() => abstractType.value === 'time' || uiDatatype.value === UITypes.Time)
const isDateTime = computed(() => abstractType.value === 'datetime' || uiDatatype.value === UITypes.DateTime)
const isJSON = computed(() => uiDatatype.value === UITypes.JSON)
const isGeoData = computed(() => uiDatatype.value === UITypes.GeoData)
const isEnum = computed(() => uiDatatype.value === UITypes.SingleSelect)
const isSingleSelect = computed(() => uiDatatype.value === UITypes.SingleSelect)
const isSet = computed(() => uiDatatype.value === UITypes.MultiSelect)
const isMultiSelect = computed(() => uiDatatype.value === UITypes.MultiSelect)
const isURL = computed(() => uiDatatype.value === UITypes.URL)
const isEmail = computed(() => uiDatatype.value === UITypes.Email)
const isAttachment = computed(() => uiDatatype.value === UITypes.Attachment)
const isRating = computed(() => uiDatatype.value === UITypes.Rating)
const isCurrency = computed(() => uiDatatype.value === UITypes.Currency)
const isPhoneNumber = computed(() => uiDatatype.value === UITypes.PhoneNumber)
const isDecimal = computed(() => uiDatatype.value === UITypes.Decimal)
const isDuration = computed(() => uiDatatype.value === UITypes.Duration)
const isPercent = computed(() => uiDatatype.value === UITypes.Percent)
const isSpecificDBType = computed(() => uiDatatype.value === UITypes.SpecificDBType)
const isAutoSaved = computed(() =>
[
UITypes.SingleLineText,
UITypes.LongText,
UITypes.PhoneNumber,
UITypes.Email,
UITypes.URL,
UITypes.Number,
UITypes.Decimal,
UITypes.Percent,
UITypes.Count,
UITypes.AutoNumber,
UITypes.SpecificDBType,
UITypes.Geometry,
UITypes.GeoData,
UITypes.Duration,
].includes(uiDatatype.value),
)
const isManualSaved = computed(() => [UITypes.Currency].includes(uiDatatype.value))
const isPrimary = computed(() => column.value?.pv)
const isPrimaryKey = computed(() => !!column.value?.pk)
return {
abstractType,
dataTypeLow,
isPrimary,
isBoolean,
isString,
isTextArea,
isInt,
isFloat,
isDate,
isYear,
isTime,
isDateTime,
isJSON,
isGeoData,
isEnum,
isSet,
isURL,
isEmail,
isAttachment,
isRating,
isCurrency,
isDecimal,
isDuration,
isAutoSaved,
isManualSaved,
isSingleSelect,
isMultiSelect,
isPercent,
isPhoneNumber,
isSpecificDBType,
isPrimaryKey,
}
}

14
packages/nc-gui/composables/useColumnCreateStore.ts

@ -97,7 +97,19 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
const onUidtOrIdTypeChange = () => {
const colProp = sqlUi.value.getDataTypeForUiType(formState.value as { uidt: UITypes }, idType ?? undefined)
formState.value = {
...formState.value,
...(!isEdit.value && {
// only take title, column_name and uidt when creating a column
// to avoid the extra props from being taken (e.g. SingleLineText -> LTAR -> SingleLineText)
// to mess up the column creation
title: formState.value.title,
column_name: formState.value.column_name,
uidt: formState.value.uidt,
}),
...(isEdit.value && {
// take the existing formState.value when editing a column
// LTAR is not available in this case
...formState.value,
}),
meta: {},
rqd: false,
pk: false,

3
packages/nc-gui/composables/useExpandedFormStore.ts

@ -46,8 +46,6 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
const activeView = inject(ActiveViewInj, ref())
const { addOrEditStackRow } = useKanbanViewStoreOrThrow()
const { sharedView } = useSharedView()
// getters
@ -197,6 +195,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
}
if (activeView.value?.type === ViewTypes.KANBAN) {
const { addOrEditStackRow } = useKanbanViewStoreOrThrow()
addOrEditStackRow(row.value, isNewRow)
}

28
packages/nc-gui/composables/useMenuCloseOnEsc/index.ts

@ -0,0 +1,28 @@
import { isClient } from '@vueuse/core'
import type { Ref } from 'vue'
export function useMenuCloseOnEsc(open: Ref<boolean>) {
const handler = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
e.preventDefault()
e.stopPropagation()
open.value = false
}
}
if (isClient) {
watch(open, (nextVal, _, cleanup) => {
// bind listener when `open` is truthy
if (nextVal) {
document.addEventListener('keydown', handler, true)
// if `open` is falsy then remove the event handler
} else {
document.removeEventListener('keydown', handler, true)
}
// cleanup is called whenever the watcher is re-evaluated or stopped
cleanup(() => {
document.removeEventListener('keydown', handler, true)
})
})
}
}

5
packages/nc-gui/composables/useMultiSelect/index.ts

@ -41,6 +41,7 @@ export function useMultiSelect(
function selectCell(row: number, col: number) {
clearRangeRows()
if (selected.row === row && selected.col === col) return
editEnabled.value = false
selected.row = row
selected.col = col
@ -132,7 +133,7 @@ export function useMultiSelect(
const onKeyDown = async (e: KeyboardEvent) => {
// invoke the keyEventHandler if provided and return if it returns true
if (await keyEventHandler?.(e)) {
return
return true
}
if (
@ -267,7 +268,7 @@ export function useMultiSelect(
}
if (unref(editEnabled) || e.ctrlKey || e.altKey || e.metaKey) {
return
return true
}
/** on letter key press make cell editable and empty */

5
packages/nc-gui/composables/useProject.ts

@ -2,6 +2,7 @@ import type { OracleUi, ProjectType, TableType } from 'nocodb-sdk'
import { SqlUiFactory } from 'nocodb-sdk'
import { isString } from '@vueuse/core'
import {
ClientType,
computed,
createEventHook,
ref,
@ -54,13 +55,13 @@ const [setup, use] = useInjectionState(() => {
}
})
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '')
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || ClientType.MYSQL)
const sqlUi = computed(
() => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>,
)
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType))
const isMysql = computed(() => ['mysql', ClientType.MYSQL].includes(projectBaseType))
const isMssql = computed(() => projectBaseType === 'mssql')
const isPg = computed(() => projectBaseType === 'pg')
const isSharedBase = computed(() => projectType === 'base')

25
packages/nc-gui/composables/useSmartsheetRowStore.ts

@ -8,6 +8,9 @@ import {
deepCompare,
extractPkFromRow,
extractSdkResponseErrorMsg,
isBt,
isHm,
isMm,
message,
ref,
unref,
@ -16,7 +19,6 @@ import {
useMetas,
useNuxtApp,
useProject,
useVirtualCell,
} from '#imports'
import type { Row } from '~/lib'
@ -36,12 +38,11 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({})
// getters
const isNew = computed(() => unref(row).rowMeta?.new ?? false)
const isNew = computed(() => unref(row).rowMeta.new ?? false)
// actions
const addLTARRef = async (value: Record<string, any>, column: ColumnType) => {
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column)))
if (isHm || isMm) {
if (isHm(column) || isMm(column)) {
if (!state.value[column.title!]) state.value[column.title!] = []
if (state.value[column.title!]!.find((ln: Record<string, any>) => deepCompare(ln, value))) {
@ -50,17 +51,16 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
}
state.value[column.title!]!.push(value)
} else if (isBt) {
} else if (isBt(column)) {
state.value[column.title!] = value
}
}
// actions
const removeLTARRef = async (value: Record<string, any>, column: ColumnType) => {
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column)))
if (isHm || isMm) {
if (isHm(column) || isMm(column)) {
state.value[column.title!]?.splice(state.value[column.title!]?.indexOf(value), 1)
} else if (isBt) {
} else if (isBt(column)) {
state.value[column.title!] = null
}
}
@ -92,13 +92,14 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
const id = extractPkFromRow(row, metaValue?.columns as ColumnType[])
for (const column of metaValue?.columns ?? []) {
if (column.uidt !== UITypes.LinkToAnotherRecord) continue
const colOptions = column?.colOptions as LinkToAnotherRecordType
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column)))
const colOptions = column.colOptions as LinkToAnotherRecordType
const relatedTableMeta = metas.value?.[colOptions?.fk_related_model_id as string]
if (isHm || isMm) {
if (isHm(column) || isMm(column)) {
const relatedRows = (state.value?.[column.title!] ?? []) as Record<string, any>[]
for (const relatedRow of relatedRows) {
await linkRecord(
id,
@ -108,7 +109,7 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
{ metaValue },
)
}
} else if (isBt && state?.value?.[column.title!]) {
} else if (isBt(column) && state.value?.[column.title!]) {
await linkRecord(
id,
extractPkFromRow(state.value?.[column.title!] as Record<string, any>, relatedTableMeta.columns as ColumnType[]),

2
packages/nc-gui/composables/useTabs.ts

@ -60,7 +60,7 @@ const [setup, use] = useInjectionState(() => {
if (!tab) return
return navigateToTab(tab)
navigateToTab(tab)
}
},
})

11
packages/nc-gui/composables/useViewData.ts

@ -189,14 +189,21 @@ export function useViewData(
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value })
formattedData.value = formatData(response.list)
paginationData.value = response.pageInfo
// to cater the case like when querying with a non-zero offset
// the result page may point to the target page where the actual returned data don't display on
const expectedPage = Math.max(1, Math.ceil(paginationData.value.totalRows! / paginationData.value.pageSize!))
if (expectedPage < paginationData.value.page!) {
await changePage(expectedPage)
}
if (viewMeta.value?.type === ViewTypes.GRID) {
await loadAggCommentsCount()
}
}
async function loadGalleryData() {
if (!viewMeta?.value?.id) return
if (!viewMeta?.value?.id || isPublic.value) return
galleryData.value = await $api.dbView.galleryRead(viewMeta.value.id)
}

36
packages/nc-gui/composables/useVirtualCell.ts

@ -1,36 +0,0 @@
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { computed } from '#imports'
export function useVirtualCell(column: Ref<ColumnType | undefined>) {
const isHm = computed(
() =>
column.value?.uidt === UITypes.LinkToAnotherRecord &&
(<LinkToAnotherRecordType>column.value?.colOptions).type === RelationTypes.HAS_MANY,
)
const isMm = computed(
() =>
column.value?.uidt === UITypes.LinkToAnotherRecord &&
(<LinkToAnotherRecordType>column.value?.colOptions).type === RelationTypes.MANY_TO_MANY,
)
const isBt = computed(
() =>
column.value?.uidt === UITypes.LinkToAnotherRecord &&
(<LinkToAnotherRecordType>column.value?.colOptions).type === RelationTypes.BELONGS_TO,
)
const isLookup = computed(() => column.value?.uidt === UITypes.Lookup)
const isRollup = computed(() => column.value?.uidt === UITypes.Rollup)
const isFormula = computed(() => column.value?.uidt === UITypes.Formula)
const isCount = computed(() => column.value?.uidt === UITypes.Count)
return {
isHm,
isMm,
isBt,
isLookup,
isRollup,
isFormula,
isCount,
}
}

24
packages/nc-gui/lang/ar.json

@ -104,7 +104,9 @@
"creator": "منشيء",
"editor": "محرر",
"commenter": "معلق",
"viewer": "مشاهد"
"viewer": "مشاهد",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "كتلة برمجية"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "إعلام عبر",
"projName": "اسم المشروع",
"tableName": "اسم الجدول",
@ -296,7 +299,8 @@
"signUpWithGoogle": "التسجيل بواسطة Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "بالتسجيل، أنت توافق على شروط الخدمة",
"welcomeToNc": "مرحبا بكم في NocoDB!"
"welcomeToNc": "مرحبا بكم في NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "إنشاء مشروع",
@ -341,12 +345,14 @@
"invite": "دعوة",
"inviteMore": "دعوة المزيد",
"inviteTeam": "دعوة فريق",
"inviteUser": "Invite User",
"inviteToken": "رمز دعوة",
"newUser": "مستخدم جديد",
"editUser": "تحرير مستخدم",
"deleteUser": "إزالة المستخدم من المشروع",
"resendInvite": "إعادة إرسال دعوة البريد الإلكتروني",
"copyInviteURL": "نسخ رابط الدعوة",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "دور جديد",
"reloadRoles": "إعادة تحميل الأدوار",
"nextPage": "الصفحة التالية",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "الصفوف لكل صفحة",
"upload": "حدد الملف المراد رفعه",
"upload_sub": "أو سحب وإسقاط الملف",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "تصدير البيانات الوصفية للمشروع بنجاح",
@ -683,13 +694,16 @@
"tableDataExported": "تم تصدير جميع بيانات الجدول بنجاح",
"updated": "تم التحديث بنجاح",
"sharedViewDeleted": "تم حذف العرض المشترك بنجاح",
"userDeleted": "User deleted successfully",
"viewRenamed": "تمت إعادة التسمية بنجاح",
"tokenGenerated": "تم إنشاء الرمز بنجاح",
"tokenDeleted": "تم حذف الرمز بنجاح",
"userAddedToProject": "تمت إضافة المستخدم إلى المشروع بنجاح",
"userAdded": "Successfully added user",
"userDeletedFromProject": "تم حذف المستخدم بنجاح من المشروع",
"inviteEmailSent": "دعوة البريد الإلكتروني مرسلة بنجاح",
"inviteURLCopied": "تم نسخ عنوان URL الدعوة إلى الحافظة",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "تم نسخ عنوان URL الأساسي القابل للمشاركة إلى الحافظة!",
"embeddableHTMLCodeCopied": "تم نسخ رمز HTML القابل للدمج!",
"userDetailsUpdated": "تم تحديث تفاصيل المستخدم بنجاح",
@ -699,7 +713,9 @@
"webhookTested": "تم اختبار Webhook بنجاح",
"columnUpdated": "تم تحديث العمود",
"columnCreated": "تم إنشاء العمود",
"passwordChanged": "تم تغيير كلمة المرور بنجاح. الرجاء تسجيل الدخول مرة أخرى."
"passwordChanged": "تم تغيير كلمة المرور بنجاح. الرجاء تسجيل الدخول مرة أخرى.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

25
packages/nc-gui/lang/bn_IN.json

@ -104,7 +104,9 @@
"creator": "সরষ",
"editor": "সমদক",
"commenter": "মনতবযক",
"viewer": "দরশক"
"viewer": "দরশক",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "এর মযম অবহিত করন",
"projName": "পরকলর নম",
"tableName": "Table name",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "পরকলপ তি করন",
@ -341,12 +345,14 @@
"invite": "আমনরণ",
"inviteMore": "আরও আমনরণ",
"inviteTeam": "দলক আমনরণ করন",
"inviteUser": "Invite User",
"inviteToken": "টনক আমনরণ করন",
"newUser": "নতন বযবহরক",
"editUser": "বযবহরক সমদন করন",
"deleteUser": "পরকলপ থযবহরক সরন",
"resendInvite": "आमरण ईमल द",
"copyInviteURL": "অনিি ইউআরএল আমনরণ করন",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "নতন ভি",
"reloadRoles": "পনরড ভি",
"nextPage": "পরবর",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "পরতিি",
"upload": "আপলড করতইল নিচন করন",
"upload_sub": "অথবইল ট আনন",
@ -591,6 +601,7 @@
"tableDeleted": "Deleted table successfully",
"generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base",
"deleteViewConfirmation": "Are you sure you want to delete this view?",
"deleteTokenConfirmation": "Are you sure you want to delete this token?",
"deleteTableConfirmation": "Do you want to delete the table",
"showM2mTables": "Show M2M Tables",
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack."
@ -653,7 +664,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "পরকলপ ম সফলভ রফতি কর",
@ -683,13 +695,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +714,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/da.json

@ -104,7 +104,9 @@
"creator": "Skaber.",
"editor": "Editor.",
"commenter": "Kommenter.",
"viewer": "Viewer."
"viewer": "Viewer.",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Bemærk Via.",
"projName": "Projekt navn",
"tableName": "Tabelnavn.",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Opret projekt",
@ -341,12 +345,14 @@
"invite": "Invitere",
"inviteMore": "Inviter mere",
"inviteTeam": "Inviter Team.",
"inviteUser": "Invite User",
"inviteToken": "Inviter token",
"newUser": "Ny bruger",
"editUser": "Rediger bruger",
"deleteUser": "Fjern bruger fra projektet",
"resendInvite": "Genoplevelse Inviter e-mail på",
"copyInviteURL": "Kopier Inviter URL",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Ny rolle",
"reloadRoles": "Genindlæs roller",
"nextPage": "Næste side",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Rækker per side",
"upload": "Vælg fil for at uploade",
"upload_sub": "eller træk og slip filen",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Project Metadata eksporteres med succes",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/de.json

@ -105,7 +105,9 @@
"creator": "Ersteller",
"editor": "Bearbeiter",
"commenter": "Kommentator",
"viewer": "Zuschauer"
"viewer": "Zuschauer",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL Ansicht"
},
@ -201,6 +203,7 @@
"codeSnippet": "Code Ausschnitt"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Benachrichtigen mit",
"projName": "Projektname",
"tableName": "Tabellenname",
@ -297,7 +300,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "Mit Ihrer Anmeldung stimmen Sie den allgemeinen Nutzungsbedingungen zu",
"welcomeToNc": "Willkommen bei NocoDB!"
"welcomeToNc": "Willkommen bei NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Projekt erstellen",
@ -342,12 +346,14 @@
"invite": "Einladen",
"inviteMore": "Mehr einladen",
"inviteTeam": "Team einladen",
"inviteUser": "Invite User",
"inviteToken": "Token einladen",
"newUser": "Neuer Benutzer",
"editUser": "Benutzer bearbeiten",
"deleteUser": "Benutzer vom Projekt entfernen",
"resendInvite": "Einladungs-Email erneut versenden",
"copyInviteURL": "Einladungs-URL kopieren",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Neue Rolle",
"reloadRoles": "Rollen neu laden",
"nextPage": "Nächste Seite",
@ -481,6 +487,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Zeilen pro Seite",
"upload": "Datei zum Hochladen auswählen",
"upload_sub": "oder Drag & Drop Datei",
@ -654,7 +664,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "Die akzeptierten Dateitypen sind .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Projektmetadaten erfolgreich exportiert",
@ -684,13 +695,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "Ansicht erfolgreich umbenannt",
"tokenGenerated": "Token erfolgreich generiert",
"tokenDeleted": "Token erfolgreich gelöscht",
"userAddedToProject": "Benutzer erfolgreich zum Projekt hinzugefügt",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Benutzer erfolgreich aus dem Projekt gelöscht",
"inviteEmailSent": "Einladungs-E-Mail erfolgreich gesendet",
"inviteURLCopied": "Einladungslink wurde in Zwischenablage kopiert",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Benutzerdaten erfolgreich aktualisiert",
@ -700,7 +714,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Spalte aktualisiert",
"columnCreated": "Spalte erstellt",
"passwordChanged": "Kennwort erfolgreich geändert. Bitte melden Sie sich erneut an."
"passwordChanged": "Kennwort erfolgreich geändert. Bitte melden Sie sich erneut an.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

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

@ -106,7 +106,9 @@
"creator": "Creator",
"editor": "Editor",
"commenter": "Commenter",
"viewer": "Viewer"
"viewer": "Viewer",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -203,6 +205,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notify Via",
"projName": "Project name",
"tableName": "Table name",
@ -299,7 +302,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Create Project",
@ -344,12 +348,14 @@
"invite": "Invite",
"inviteMore": "Invite more",
"inviteTeam": "Invite Team",
"inviteUser": "Invite User",
"inviteToken": "Invite Token",
"newUser": "New User",
"editUser": "Edit user",
"deleteUser": "Remove user from project",
"resendInvite": "Resend invite E-mail",
"copyInviteURL": "Copy invite URL",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "New role",
"reloadRoles": "Reload roles",
"nextPage": "Next page",
@ -365,6 +371,8 @@
"setPrimary": "Set as Primary value",
"addRow": "Add new row",
"saveRow": "Save row",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insert New Row",
"deleteRow": "Delete Row",
"deleteSelectedRow": "Delete Selected Rows",
@ -487,6 +495,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Rows per page",
"upload": "Select file to Upload",
"upload_sub": "or drag and drop file",
@ -661,7 +673,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Project metadata exported successfully",
@ -691,13 +704,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -707,7 +723,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/es.json

@ -104,7 +104,9 @@
"creator": "Creador",
"editor": "Editor",
"commenter": "Comentarista",
"viewer": "Visor"
"viewer": "Visor",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notificar a través de",
"projName": "Nombre del proyecto",
"tableName": "Nombre de la tabla",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Crear proyecto",
@ -341,12 +345,14 @@
"invite": "Invitar",
"inviteMore": "Invitar más",
"inviteTeam": "Invitar al equipo",
"inviteUser": "Invite User",
"inviteToken": "Invitar con token",
"newUser": "Nuevo usuario",
"editUser": "Editar usuario",
"deleteUser": "Eliminar usuario del proyecto",
"resendInvite": "Reenviar la invitación al correo electrónico",
"copyInviteURL": "Copiar URL de invitación",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Nuevo rol",
"reloadRoles": "Recargar roles",
"nextPage": "Siguiente página",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Filas por página",
"upload": "Selecciona Archivo para cargar",
"upload_sub": "o arrastra y suelta el archivo",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Metadatos del proyecto exportados con éxito.",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/fa.json

@ -104,7 +104,9 @@
"creator": "ایجادکننده",
"editor": "ویرایشگر",
"commenter": "نظردهنده",
"viewer": "بیننده"
"viewer": "بیننده",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "اعلان از طریق",
"projName": "نام پروژه",
"tableName": "نام جدول",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "ایجاد پروژه",
@ -341,12 +345,14 @@
"invite": "دعوت",
"inviteMore": "دعوت از افراد بیشتر",
"inviteTeam": "دعوت از تیم",
"inviteUser": "Invite User",
"inviteToken": "توکن دعوتنامه",
"newUser": "کاربر جدید",
"editUser": "ویرایش کاربر",
"deleteUser": "حذف کاربر از پروژه",
"resendInvite": "ارسال مجدد پست الکترونیکی دعوت",
"copyInviteURL": "URL دعوت را کپی کنید",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "نقش جدید",
"reloadRoles": "بارگذاری مجدد نقشها",
"nextPage": "صفحه بعدی",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "تعداد ردیفها در هر صفحه",
"upload": "فایل را برای بارگذاری انتخاب کنید",
"upload_sub": "یا فایل را بکشید و رها کنید",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "فراداده پروژه با موفقیت خارج شد",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/fi.json

@ -104,7 +104,9 @@
"creator": "Luoja",
"editor": "Toimittaja",
"commenter": "Kommentti",
"viewer": "Katselija"
"viewer": "Katselija",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Ilmoittaa kautta",
"projName": "Projektin nimi",
"tableName": "Taulukon nimi",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Luo projekti",
@ -341,12 +345,14 @@
"invite": "Kutsua",
"inviteMore": "Kutsu lisää",
"inviteTeam": "Kutsu tiimi",
"inviteUser": "Invite User",
"inviteToken": "Kutsua token",
"newUser": "Uusi käyttäjä",
"editUser": "Muokkaa käyttäjää",
"deleteUser": "Poista käyttäjä projektista",
"resendInvite": "Lähetä sähköpostia uudelleen",
"copyInviteURL": "Kopioi kutsua URL-osoite",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Uusi rooli",
"reloadRoles": "Lataa roolit uudelleen",
"nextPage": "Seuraava sivu",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Rivit sivua kohti",
"upload": "Valitse lataa tiedosto",
"upload_sub": "tai vedä ja pudota tiedosto",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Project Metadata viedään onnistuneesti",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/fr.json

@ -104,7 +104,9 @@
"creator": "Créateur",
"editor": "Éditeur",
"commenter": "Commentateur",
"viewer": "Lecture seule"
"viewer": "Lecture seule",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "Vue SQL"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notifier via",
"projName": "Nom du projet",
"tableName": "Nom de la table",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Créer un projet",
@ -341,12 +345,14 @@
"invite": "Inviter",
"inviteMore": "Inviter plus",
"inviteTeam": "Inviter une équipe",
"inviteUser": "Invite User",
"inviteToken": "Inviter via un jeton",
"newUser": "Nouvel utilisateur",
"editUser": "Modifier l'utilisateur",
"deleteUser": "Supprimer l'utilisateur du projet",
"resendInvite": "Renvoyer une invitation par courriel",
"copyInviteURL": "Copier l'URL d'invitation",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Nouveau rôle",
"reloadRoles": "Actualiser les rôles",
"nextPage": "Page suivante",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Lignes par page",
"upload": "Sélectionner un fichier à téléverser",
"upload_sub": "ou glisser-déposer un fichier",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Les métadonnées de projet sont exportées avec succès",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Vue partagée effacée avec succès",
"userDeleted": "User deleted successfully",
"viewRenamed": "Vue renommée avec succès",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/he.json

@ -104,7 +104,9 @@
"creator": "יוצר",
"editor": "עוֹרֵך",
"commenter": "פרשן",
"viewer": "צוֹפֶה"
"viewer": "צוֹפֶה",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "תודיע דרך",
"projName": "שם הפרוייקט",
"tableName": "שם שולחן",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "צור פרויקט",
@ -341,12 +345,14 @@
"invite": "הזמן",
"inviteMore": "הזמן עוד",
"inviteTeam": "הזמן צוות",
"inviteUser": "Invite User",
"inviteToken": "הזמן טוקן",
"newUser": "משתמש חדש",
"editUser": "ערוך משתמש",
"deleteUser": "הסר משתמש מהפרויקט",
"resendInvite": "של הזמנת אימייל בשנית",
"copyInviteURL": "העתק הזמנת URL.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "תפקיד חדש",
"reloadRoles": "טען מחדש את התפקידים",
"nextPage": "עמוד הבא",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "שורות לדף",
"upload": "בחר קובץ להעלאה",
"upload_sub": "או גרור ושחרר קובץ",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "פרויקט Metadata מיוצא בהצלחה",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/hi.json

@ -104,7 +104,9 @@
"creator": "बन",
"editor": "सदक",
"commenter": "टिपणर",
"viewer": "दरशक"
"viewer": "दरशक",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notify Via",
"projName": "परिजनम",
"tableName": "Table name",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "पट बन",
@ -341,12 +345,14 @@
"invite": "आमित करन",
"inviteMore": "अधिक आमित कर",
"inviteTeam": "टम क आमित कर",
"inviteUser": "Invite User",
"inviteToken": "टकन क आमित कर",
"newUser": "नय उपयगकर",
"editUser": "यजर कित कर",
"deleteUser": "परिजन उपयगकरि",
"resendInvite": "आमरण ईमल द",
"copyInviteURL": "क आमित URL",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "नयि",
"reloadRoles": "पड भि",
"nextPage": "अगलठ",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "परतिठ पि",
"upload": "अपलड करनिए फइल क चयन कर",
"upload_sub": "यग एड डप फइल",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "परिजन सफलतवक नित क गई",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/hr.json

@ -104,7 +104,9 @@
"creator": "Kreator",
"editor": "Urednik",
"commenter": "Komentator",
"viewer": "Preglednika"
"viewer": "Preglednika",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Obavijestiti putem",
"projName": "Naziv projekta",
"tableName": "Tablica",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Stvoriti projekt",
@ -341,12 +345,14 @@
"invite": "Pozvati",
"inviteMore": "Pozvati više",
"inviteTeam": "Pozvati tim",
"inviteUser": "Invite User",
"inviteToken": "Pozvati token",
"newUser": "Novi korisnik",
"editUser": "Uredi korisnika",
"deleteUser": "Uklonite korisnika od projekta",
"resendInvite": "Ponovno pošaljite e-poštu",
"copyInviteURL": "Kopirajte URL",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Nova uloga",
"reloadRoles": "Ponovno učitajte uloge",
"nextPage": "Sljedeća stranica",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Redaka po stranici",
"upload": "Odaberite datoteku za prijenos",
"upload_sub": "ili povucite i ispustite datoteku",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Projektni metapodaci su uspješno izvozili",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/id.json

@ -104,7 +104,9 @@
"creator": "Pencipta",
"editor": "Editor",
"commenter": "Komentator",
"viewer": "Penonton"
"viewer": "Penonton",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Beri tahu VIA.",
"projName": "Nama Proyek",
"tableName": "Nama meja",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Buat Proyek",
@ -341,12 +345,14 @@
"invite": "Undang",
"inviteMore": "Undang lebih banyak",
"inviteTeam": "Undang Tim",
"inviteUser": "Invite User",
"inviteToken": "Undang token.",
"newUser": "Pengguna baru",
"editUser": "Edit Pengguna",
"deleteUser": "Hapus pengguna dari proyek",
"resendInvite": "Kirim ulang undangan e-mail",
"copyInviteURL": "Salin Undangan URL.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Peran Baru",
"reloadRoles": "Muat ulang peran",
"nextPage": "Halaman selanjutnya",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Baris per halaman",
"upload": "Pilih file untuk diunggah",
"upload_sub": "atau seret dan jatuhkan file",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Metadata proyek berhasil diekspor",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/it.json

@ -104,7 +104,9 @@
"creator": "Creatore",
"editor": "Editor",
"commenter": "Commentatore",
"viewer": "Spettatore"
"viewer": "Spettatore",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notifica tramite",
"projName": "Nome del progetto",
"tableName": "Nome della tabella",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Crea progetto",
@ -341,12 +345,14 @@
"invite": "Invita",
"inviteMore": "Invita di più",
"inviteTeam": "Invita un team.",
"inviteUser": "Invite User",
"inviteToken": "Invita con una chiave",
"newUser": "Nuovo utente",
"editUser": "Modifica utente",
"deleteUser": "Rimuovi utente dal progetto",
"resendInvite": "Reinvia e-mail di invito",
"copyInviteURL": "Copia l'URL dell'invito",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Nuovo ruolo",
"reloadRoles": "Ricarica ruoli",
"nextPage": "Pagina successiva",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Righe per pagina",
"upload": "Seleziona il file da caricare",
"upload_sub": "o trascinalo qui",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Metadati del progetto esportati con successo",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/ja.json

@ -104,7 +104,9 @@
"creator": "作成者",
"editor": "編集者",
"commenter": "コメンター",
"viewer": "閲覧者"
"viewer": "閲覧者",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "次で通知する",
"projName": "プロジェクト名",
"tableName": "テーブルの名前",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "プロジェクトを作成",
@ -341,12 +345,14 @@
"invite": "招待",
"inviteMore": "さらに招待",
"inviteTeam": "チームへ招待",
"inviteUser": "Invite User",
"inviteToken": "招待用トークン",
"newUser": "ユーザーを作成",
"editUser": "ユーザーを編集",
"deleteUser": "プロジェクトからユーザーを削除",
"resendInvite": "招待用メールを再送信",
"copyInviteURL": "招待用 URL をコピー",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "新しいロール",
"reloadRoles": "ロールをリロード",
"nextPage": "次のページ",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "1ページあたりの行数",
"upload": "アップロードするファイルを選択してください",
"upload_sub": "またはファイルをドラッグ & ドロップ",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "プロジェクトメタデータは正常にエクスポートされました",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "列が更新されました",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/ko.json

@ -104,7 +104,9 @@
"creator": "생성자",
"editor": "편집자",
"commenter": "해설자",
"viewer": "뷰어"
"viewer": "뷰어",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "공지",
"projName": "프로젝트 이름",
"tableName": "테이블 이름",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "프로젝트 생성",
@ -341,12 +345,14 @@
"invite": "초대",
"inviteMore": "더 많은 사람 초대",
"inviteTeam": "팀 초대",
"inviteUser": "Invite User",
"inviteToken": "초대 토큰",
"newUser": "사용자 추가",
"editUser": "사용자 편집",
"deleteUser": "사용자 제거",
"resendInvite": "초대 이메일 재전송",
"copyInviteURL": "초대 URL 복사",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "새로운 역할",
"reloadRoles": "역할 다시 불러오기",
"nextPage": "다음 페이지",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "페이지 당 행",
"upload": "업로드 할 파일 선택",
"upload_sub": "또는 끌어서 놓기 파일",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "프로젝트 메타 데이터를 성공적으로 내보냈습니다.",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/lv.json

@ -104,7 +104,9 @@
"creator": "Autors",
"editor": "Redaktors",
"commenter": "Komentētājs",
"viewer": "Skatītājs"
"viewer": "Skatītājs",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Paziņot izmantojot",
"projName": "Projekta nosaukums",
"tableName": "Tabulas nosaukums",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Izveidot projektu",
@ -341,12 +345,14 @@
"invite": "Uzaicināt",
"inviteMore": "Uzaicināt vēl",
"inviteTeam": "Uzaicināt komandu",
"inviteUser": "Invite User",
"inviteToken": "Uzaicināšanas talons",
"newUser": "Jauns lietotājs",
"editUser": "Rediģēt lietotāju",
"deleteUser": "Noņemt lietotāju no projektu",
"resendInvite": "Atkārtoti nosūtīt uzaicinājumu",
"copyInviteURL": "Kopēt uzaicinājuma saiti",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Jauna loma",
"reloadRoles": "Pārlādēt lomas",
"nextPage": "Nākošā lapa",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "ieraksti lapā",
"upload": "Izvēlēties datni augšupielādei",
"upload_sub": "vai vilkt un nomest datni",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Projekta metadati eksportēti veiksmīgi",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/nl.json

@ -104,7 +104,9 @@
"creator": "Creator",
"editor": "Bewerker",
"commenter": "Reviewer",
"viewer": "Kijker"
"viewer": "Kijker",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Melding via",
"projName": "Projectnaam",
"tableName": "Tabelnaam",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Maak project",
@ -341,12 +345,14 @@
"invite": "Uitnodigen",
"inviteMore": "Nodig meer gebruikers uit",
"inviteTeam": "Nodig team uit",
"inviteUser": "Invite User",
"inviteToken": "Uitnodigingstoken",
"newUser": "Nieuwe gebruiker",
"editUser": "Bewerk gebruiker",
"deleteUser": "Verwijder gebruiker van project",
"resendInvite": "Verzend uitnodigingsemail opnieuw",
"copyInviteURL": "Kopieer uitnodigingslink",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Nieuwe rol",
"reloadRoles": "Rollen verversen",
"nextPage": "Volgende pagina",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Rijen per pagina",
"upload": "Selecteer bestand om te uploaden",
"upload_sub": "of sleep het bestand naar hier",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Project metadata met succes geëxporteerd",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/no.json

@ -104,7 +104,9 @@
"creator": "Skaperen.",
"editor": "Redaktør",
"commenter": "Kommenterer",
"viewer": "Seer."
"viewer": "Seer.",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Varsle Via.",
"projName": "Prosjektnavn",
"tableName": "Tabellnavn",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Opprett prosjekt",
@ -341,12 +345,14 @@
"invite": "Invitere",
"inviteMore": "Inviter mer",
"inviteTeam": "Invitere team",
"inviteUser": "Invite User",
"inviteToken": "Inviter TOKEN.",
"newUser": "Ny bruker",
"editUser": "Rediger bruker",
"deleteUser": "Fjern brukeren fra prosjektet",
"resendInvite": "Resend invitere e-post",
"copyInviteURL": "Kopier Inviter URL.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Ny rolle",
"reloadRoles": "Oppdater roller",
"nextPage": "Neste side",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Rader per side.",
"upload": "Velg Fil for å laste opp",
"upload_sub": "eller dra og slipp filen",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Prosjektmetadata eksporteres vellykket",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/pl.json

@ -104,7 +104,9 @@
"creator": "Twórca",
"editor": "Redaktor",
"commenter": "Komentator",
"viewer": "Widz"
"viewer": "Widz",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "Widok SQL"
},
@ -200,6 +202,7 @@
"codeSnippet": "Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Powiadomić VIA.",
"projName": "Nazwa Projektu",
"tableName": "Nazwa tabeli",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Zarejestruj się przez Google",
"signInWithGoogle": "Zaloguj się przez Google",
"agreeToTos": "Rejestrując się, akceptujesz warunki korzystania z usługi",
"welcomeToNc": "Witaj w NocoDB!"
"welcomeToNc": "Witaj w NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Utwórz projekt",
@ -341,12 +345,14 @@
"invite": "Zapraszać",
"inviteMore": "Zaprosić więcej",
"inviteTeam": "Invite Team.",
"inviteUser": "Invite User",
"inviteToken": "Zaproś token.",
"newUser": "Nowy użytkownik",
"editUser": "Edytuj użytkownika",
"deleteUser": "Usuń użytkownika z projektu",
"resendInvite": "Ponownie zaprosić zaproszenie e-maila",
"copyInviteURL": "Kopiuj Zaproś Url.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Nowa rola",
"reloadRoles": "Role ponownie załaduj",
"nextPage": "Następna strona",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Wiersze na stronę",
"upload": "Wybierz plik do przesłania",
"upload_sub": "lub przeciągnij i upuść plik",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "Akceptowane typy plików to: .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Klucz parametru nie może być pusty",
"duplicateParameterKeysAreNotAllowed": "Zduplikowane klucze parametrów są niedozwolone",
"fieldRequired": "{value} nie może być puste."
"fieldRequired": "{value} nie może być puste.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Pomyślnie wyeksportowano metadane projektu",
@ -683,13 +694,16 @@
"tableDataExported": "Pomyślnie wyeksportowano wszystkie dane tabeli",
"updated": "Zaktualizowano pomyślnie",
"sharedViewDeleted": "Usunięto udostępniony widok",
"userDeleted": "User deleted successfully",
"viewRenamed": "Zmieniono nazwę widoku",
"tokenGenerated": "Token został wygenerowany pomyślnie",
"tokenDeleted": "Token usunięty pomyślnie",
"userAddedToProject": "Pomyślnie dodano użytkownika do projektu",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Użytkownik usunięty z projektu",
"inviteEmailSent": "E-mail z zaproszeniem wysłany pomyślnie",
"inviteURLCopied": "Adres URL zaproszenia skopiowany do schowka",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Skopiowano adres URL do schowka!",
"embeddableHTMLCodeCopied": "Skopiowany kod HTML do osadzenia!",
"userDetailsUpdated": "Pomyślnie zaktualizowano dane użytkownika",
@ -699,7 +713,9 @@
"webhookTested": "Webhook przetestowany pomyślnie",
"columnUpdated": "Kolumna zaktualizowana",
"columnCreated": "Kolumna utworzona",
"passwordChanged": "Hasło zostało zmienione. Zaloguj się ponownie."
"passwordChanged": "Hasło zostało zmienione. Zaloguj się ponownie.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/pt.json

@ -104,7 +104,9 @@
"creator": "O Criador",
"editor": "editor",
"commenter": "Comentarista",
"viewer": "Viewer."
"viewer": "Viewer.",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notificar via.",
"projName": "Nome do Projeto",
"tableName": "Nome da tabela",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Criar Projecto",
@ -341,12 +345,14 @@
"invite": "Convidar",
"inviteMore": "Convide mais",
"inviteTeam": "Convidar equipe",
"inviteUser": "Invite User",
"inviteToken": "Convidar Token.",
"newUser": "Novo usuário",
"editUser": "Editar usuário",
"deleteUser": "Remover usuário do projeto",
"resendInvite": "Reenviar o envide e-mail",
"copyInviteURL": "Copiar convidar URL.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Novo papel",
"reloadRoles": "Recarregar funções",
"nextPage": "Próxima página",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Linhas por página",
"upload": "Selecione Arquivo para upload",
"upload_sub": "ou arrastar e soltar arquivo",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Metadados do projeto exportado com sucesso",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/pt_BR.json

@ -104,7 +104,9 @@
"creator": "O Criador",
"editor": "editor",
"commenter": "Comentarista",
"viewer": "Viewer."
"viewer": "Viewer.",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Notificar via.",
"projName": "Nome do Projeto",
"tableName": "Nome da tabela",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Criar Projeto",
@ -341,12 +345,14 @@
"invite": "Convidar",
"inviteMore": "Convide mais",
"inviteTeam": "Convidar equipe",
"inviteUser": "Invite User",
"inviteToken": "Convidar Token.",
"newUser": "Novo usuário",
"editUser": "Editar usuário",
"deleteUser": "Remover usuário do projeto",
"resendInvite": "Reenviar o envide e-mail",
"copyInviteURL": "Copiar convidar URL.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Novo papel",
"reloadRoles": "Recarregar funções",
"nextPage": "Próxima página",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Linhas por página",
"upload": "Selecione Arquivo para upload",
"upload_sub": "ou arrastar e soltar arquivo",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Metadados do projeto exportado com sucesso",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/ru.json

@ -104,7 +104,9 @@
"creator": "Создатель",
"editor": "Редактор",
"commenter": "Комментатор",
"viewer": "Просмотр"
"viewer": "Просмотр",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Уведомлять через",
"projName": "Название проекта",
"tableName": "Название таблицы",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Создать проект",
@ -341,12 +345,14 @@
"invite": "Пригласить",
"inviteMore": "Пригласить еще",
"inviteTeam": "Пригласить команду",
"inviteUser": "Invite User",
"inviteToken": "Токен приглашения",
"newUser": "Новый пользователь",
"editUser": "Редактировать пользователя",
"deleteUser": "Удалить пользователя из проекта",
"resendInvite": "Переотправить приглашение e-mail",
"copyInviteURL": "Скопировать URL-адрес приглашения",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Новая роль",
"reloadRoles": "Перезагрузить роли",
"nextPage": "Следущая страница",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Строк на страницу",
"upload": "Выберите файл для загрузки",
"upload_sub": "или перетащите файл",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Метаданные проекта успешно экспортированы",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/sl.json

@ -104,7 +104,9 @@
"creator": "Ustvarjalec",
"editor": "Urednik",
"commenter": "Komentar",
"viewer": "Gledalca."
"viewer": "Gledalca.",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Obvestite VIO.",
"projName": "Ime Projekta",
"tableName": "Ime tabele",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Ustvari projek",
@ -341,12 +345,14 @@
"invite": "Povabi",
"inviteMore": "Povabite več.",
"inviteTeam": "Povabite ekipo",
"inviteUser": "Invite User",
"inviteToken": "Povabite žeton",
"newUser": "Nov uporabnik",
"editUser": "Uredite uporabnika",
"deleteUser": "Odstrani uporabnika iz projekta",
"resendInvite": "Ponovno pošljite e-pošto",
"copyInviteURL": "Kopiraj Povabi URL.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Nova vloga",
"reloadRoles": "Ponovno naloži vloge",
"nextPage": "Naslednja stran",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Vrstice na stran.",
"upload": "Izberite datoteko za nalaganje",
"upload_sub": "ali datoteko povlecite in spustite",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Projekt Metapodatki se je uspešno izvozil",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/sv.json

@ -104,7 +104,9 @@
"creator": "Skapare",
"editor": "Redaktör",
"commenter": "Kommentare",
"viewer": "Visare"
"viewer": "Visare",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Meddela via",
"projName": "Projektnamn",
"tableName": "Tabellnamn",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Skapa projekt",
@ -341,12 +345,14 @@
"invite": "Inbjudan",
"inviteMore": "Bjud in mer",
"inviteTeam": "Bjud in lag",
"inviteUser": "Invite User",
"inviteToken": "Bjud in token",
"newUser": "Ny användare",
"editUser": "Redigera användaren",
"deleteUser": "Ta bort användaren från projektet",
"resendInvite": "Ändra bjud in e-post",
"copyInviteURL": "Kopiera inbjudan url",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Ny roll",
"reloadRoles": "Ladda om roller",
"nextPage": "Nästa sida",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Rader per sida",
"upload": "Välj fil för att ladda upp",
"upload_sub": "eller dra och släpp filen",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Projektmetadata exporterades framgångsrikt",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/th.json

@ -104,7 +104,9 @@
"creator": "ผสราง",
"editor": "บรรณาธการ",
"commenter": "ผจารณ",
"viewer": "ผ"
"viewer": "ผ",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "แจงเตอนผาน",
"projName": "ชอโครงการ",
"tableName": "ชอตาราง",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "สรางโครงการ",
@ -341,12 +345,14 @@
"invite": "เชญชวน",
"inviteMore": "เชญมากขน",
"inviteTeam": "เชญทม",
"inviteUser": "Invite User",
"inviteToken": "เชญโทเคน",
"newUser": "ผใชใหม",
"editUser": "แกไขผใช",
"deleteUser": "ลบผใชจากโครงการ",
"resendInvite": "สงอเมลเชญสงอเมลอกครง",
"copyInviteURL": "Copy Invite URL",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "บทบาทใหม",
"reloadRoles": "โหลดบทบาทใหม",
"nextPage": "หนาตอไป",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "แถวตอหนา",
"upload": "เลอกไฟลจะอปโหลด",
"upload_sub": "หรอลากและวางไฟล",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "ขอมลเมตาของโครงการสงออกเรยบรอยแลว",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/tr.json

@ -104,7 +104,9 @@
"creator": "Yaratıcı",
"editor": "Editör",
"commenter": "Yorumcu",
"viewer": "İzleyici"
"viewer": "İzleyici",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Aracılığıyla Bildir",
"projName": "Proje adı",
"tableName": "Tablo adı",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Proje Oluştur",
@ -341,12 +345,14 @@
"invite": "Davet et",
"inviteMore": "Daha fazla davet et",
"inviteTeam": "Takıma davet et",
"inviteUser": "Invite User",
"inviteToken": "Davet kodu",
"newUser": "Yeni kullanıcı",
"editUser": "Kullanıcıyı düzenle",
"deleteUser": "Kullanıcıyı projeden kaldır",
"resendInvite": "Davet e-postasını yeniden gönder",
"copyInviteURL": "Davet linkini kopyala",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Yeni rol",
"reloadRoles": "Rolleri yeniden yükle",
"nextPage": "Sonraki Sayfa",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Sayfa başına satır",
"upload": "Yüklenecek Dosyayı Seçin",
"upload_sub": "veya dosyayı sürükleyip bırakın",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Proje metaverileri başarıyla dışa aktarıldı",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/uk.json

@ -104,7 +104,9 @@
"creator": "Творець",
"editor": "Редактор",
"commenter": "Коментатор",
"viewer": "Глядач"
"viewer": "Глядач",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Повідомити через",
"projName": "Назва проекту",
"tableName": "Назва таблиці",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Створити проект",
@ -341,12 +345,14 @@
"invite": "Запрошувати",
"inviteMore": "Запросити більше",
"inviteTeam": "Запрошувати команду",
"inviteUser": "Invite User",
"inviteToken": "Запросити токен",
"newUser": "Новий користувач",
"editUser": "Редагувати користувача",
"deleteUser": "Видалити користувача з проекту",
"resendInvite": "Перевести запрошення електронної пошти",
"copyInviteURL": "Копіювати запросити URL-адресу",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Нова роль",
"reloadRoles": "Перезавантажити ролі",
"nextPage": "Наступна сторінка",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Рядки на сторінку",
"upload": "Виберіть файл для завантаження",
"upload_sub": "або перетягніть файл",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Метадані проекту успішно експортується",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/vi.json

@ -104,7 +104,9 @@
"creator": "Người sáng tạo",
"editor": "Biên tập viên",
"commenter": "Bình luận",
"viewer": "Người xem"
"viewer": "Người xem",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL View"
},
@ -200,6 +202,7 @@
"codeSnippet": "Code Snippet"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "Thông báo qua",
"projName": "Tên dự án",
"tableName": "Tên bảng.",
@ -296,7 +299,8 @@
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "Tạo dự án",
@ -341,12 +345,14 @@
"invite": "Mời",
"inviteMore": "Mời thêm",
"inviteTeam": "Mời đội",
"inviteUser": "Invite User",
"inviteToken": "Mời Token.",
"newUser": "Người dùng mới",
"editUser": "Người dùng biên tập",
"deleteUser": "Xóa người dùng khỏi dự án",
"resendInvite": "Gửi lại lời mời e-mail",
"copyInviteURL": "Sao chép Mời URL.",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "Vai trò mới",
"reloadRoles": "Đóng vai trò",
"nextPage": "Trang tiếp theo",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "Hàng trên mỗi trang",
"upload": "Chọn tệp để tải lên",
"upload_sub": "hoặc kéo và thả tập tin",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "Metadata dự án xuất khẩu thành công",
@ -683,13 +694,16 @@
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAdded": "Successfully added user",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
@ -699,7 +713,9 @@
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"passwordChanged": "Password changed successfully. Please login again.",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

24
packages/nc-gui/lang/zh-Hans.json

@ -104,7 +104,9 @@
"creator": "创造者",
"editor": "编辑者",
"commenter": "评论者",
"viewer": "浏览者"
"viewer": "浏览者",
"orgLevelCreator": "Organization Level Creator",
"orgLevelViewer": "Organization Level Viewer"
},
"sqlVIew": "SQL 视图"
},
@ -200,6 +202,7 @@
"codeSnippet": "代码片段"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "通知通过",
"projName": "项目名",
"tableName": "表名称",
@ -296,7 +299,8 @@
"signUpWithGoogle": "使用 Google 注册",
"signInWithGoogle": "使用 Google 登录",
"agreeToTos": "注册即表明您同意服务条款",
"welcomeToNc": "欢迎来到NocoDB!"
"welcomeToNc": "欢迎来到NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "创建项目",
@ -341,12 +345,14 @@
"invite": "邀请",
"inviteMore": "邀请更多",
"inviteTeam": "邀请团队",
"inviteUser": "Invite User",
"inviteToken": "邀请令牌",
"newUser": "新用户",
"editUser": "编辑用户",
"deleteUser": "从项目中删除用户",
"resendInvite": "重新发送邀请电子邮件",
"copyInviteURL": "复制邀请链接",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "新角色",
"reloadRoles": "重新加载角色",
"nextPage": "下一页",
@ -480,6 +486,10 @@
},
"msg": {
"info": {
"roles": {
"orgCreator": "Creator can create new projects and access any invited project.",
"orgViewer": "Viewer is not allowed to create new projects but they can access any invited project."
},
"footerInfo": "每页行驶",
"upload": "选择文件以上传",
"upload_sub": "或拖放文件",
@ -653,7 +663,8 @@
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "可接受的文件类型是 .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "参数键不能为空",
"duplicateParameterKeysAreNotAllowed": "不允许重复的参数键",
"fieldRequired": "{value} 不能为空。"
"fieldRequired": "{value} 不能为空。",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "项目元数据成功导出",
@ -683,13 +694,16 @@
"tableDataExported": "成功导出所有表内数据",
"updated": "更新成功",
"sharedViewDeleted": "共享视图删除成功",
"userDeleted": "User deleted successfully",
"viewRenamed": "视图重命名成功",
"tokenGenerated": "令牌生成成功",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "添加用户到项目成功",
"userAdded": "Successfully added user",
"userDeletedFromProject": "从项目中删除用户成功",
"inviteEmailSent": "邀请邮件发送成功",
"inviteURLCopied": "邀请URL已复制到剪贴板",
"passwordResetURLCopied": "Password reset URL copied to clipboard",
"shareableURLCopied": "已将可共享的基础URL复制到剪贴板!",
"embeddableHTMLCodeCopied": "已复制可嵌入的 HTML 代码!",
"userDetailsUpdated": "成功更新用户详细信息",
@ -699,7 +713,9 @@
"webhookTested": "Webhook 测试成功",
"columnUpdated": "列已更新",
"columnCreated": "已创建列",
"passwordChanged": "密码修改成功。请重新登录。"
"passwordChanged": "密码修改成功。请重新登录。",
"settingsSaved": "Settings saved successfully",
"roleUpdated": "Role updated successfully"
}
}
}

234
packages/nc-gui/lang/zh-Hant.json

@ -16,7 +16,7 @@
"cancel": "取消",
"submit": "提交",
"create": "建立",
"duplicate": "Duplicate",
"duplicate": "複製",
"insert": "插入",
"delete": "刪除",
"update": "更新",
@ -56,26 +56,26 @@
"notification": "通知",
"reference": "參考",
"function": "功能",
"confirm": "Confirm",
"confirm": "確認",
"generate": "Generate",
"copy": "Copy",
"misc": "Miscellaneous",
"lock": "Lock",
"unlock": "Unlock",
"credentials": "Credentials",
"help": "Help",
"questions": "Questions",
"copy": "複製",
"misc": "其他",
"lock": "鎖定",
"unlock": "解鎖",
"credentials": "憑證",
"help": "幫助",
"questions": "問題",
"reachOut": "Reach out here",
"betaNote": "This feature is currently in beta.",
"betaNote": "此功能還在測試中",
"moreInfo": "More information can be found here",
"logs": "Logs",
"groupingField": "Grouping Field"
},
"objects": {
"project": "項目",
"projects": "項目",
"table": "表",
"tables": "表",
"project": "專案",
"projects": "全部專案",
"table": "資料表",
"tables": "全部資料表",
"field": "欄位",
"fields": "欄位",
"column": "列",
@ -86,8 +86,8 @@
"records": "記錄",
"webhook": "Webhook",
"webhooks": "Webhook",
"view": "檢視",
"views": "檢視",
"view": "檢視",
"views": "所有檢視",
"viewType": {
"grid": "網格",
"gallery": "圖庫",
@ -100,17 +100,19 @@
"role": "角色",
"roles": "角色",
"roleType": {
"owner": "有者",
"owner": "有者",
"creator": "創造者",
"editor": "編輯",
"commenter": "評論者",
"viewer": "檢視者"
"viewer": "檢視者",
"orgLevelCreator": "組織等級建立者",
"orgLevelViewer": "組織等級檢視者"
},
"sqlVIew": "SQL View"
},
"datatype": {
"ID": "ID",
"ForeignKey": "外鑰匙",
"ForeignKey": "外",
"SingleLineText": "單行文本",
"LongText": "長篇文章",
"Attachment": "附件",
@ -132,8 +134,8 @@
"Rating": "評分",
"Formula": "公式",
"Rollup": "捲起",
"Count": "數",
"Lookup": "抬頭",
"Count": "數",
"Lookup": "查找",
"DateTime": "日期時間",
"CreateTime": "創建時間",
"LastModifiedTime": "最後修改時間",
@ -189,36 +191,37 @@
"resetPassword": "重設密碼",
"teamAndSettings": "團隊 & 設定",
"apiDocs": "API 說明文件",
"importFromAirtable": "Import From Airtable",
"importFromAirtable": "從 Airtable 匯入",
"generateToken": "Generate Token",
"APIsAndSupport": "APIs & Support",
"helpCenter": "Help center",
"swaggerDocumentation": "Swagger Documentation",
"APIsAndSupport": "APIs 與支援",
"helpCenter": "幫助中心",
"swaggerDocumentation": "Swagger 文件",
"quickImportFrom": "Quick Import From",
"quickImport": "Quick Import",
"advancedSettings": "Advanced Settings",
"codeSnippet": "Code Snippet"
"quickImport": "快速匯入",
"advancedSettings": "進階設定",
"codeSnippet": "程式碼片段"
},
"labels": {
"createdBy": "Created By",
"notifyVia": "透過...通知",
"projName": "項目名",
"tableName": "表名稱",
"projName": "專案名",
"tableName": "資料表名稱",
"viewName": "查看名稱",
"viewLink": "查看鏈接",
"columnName": "名稱",
"columnType": "類型",
"columnName": "欄位名稱",
"columnType": "欄位類型",
"roleName": "角色名稱",
"roleDescription": "角色描述",
"databaseType": "鍵入數據庫",
"lengthValue": "長度/值",
"databaseType": "數據庫類別",
"lengthValue": "長度 / 值",
"dbType": "資料庫類型",
"sqliteFile": "SQLite 檔案",
"hostAddress": "主機位址",
"port": "連線埠號碼",
"username": "使用者名稱",
"password": "密碼",
"schemaName": "Schema name",
"database": "數據庫",
"schemaName": "Schema 名稱",
"database": "資料庫",
"action": "行動",
"actions": "行動",
"operation": "操作",
@ -228,7 +231,7 @@
"authentication": "驗證",
"token": "權杖",
"where": "在哪裡",
"cache": "緩存",
"cache": "快取",
"chat": "聊天",
"email": "電子郵件",
"storage": "貯存",
@ -246,13 +249,13 @@
"requriedCa": "必填 - CA",
"requriedIdentity": "必填 - IDENTITY",
"inflection": {
"tableName": "屈折 - 表名稱",
"tableName": "屈折 - 資料表名稱",
"columnName": "屈折 - 欄位名稱"
},
"community": {
"starUs1": "在 Github 上",
"starUs2": "幫我們按讚",
"bookDemo": "預免費 Demo",
"bookDemo": "預免費 Demo",
"getAnswered": "解惑您的問題",
"joinDiscord": "加入 Discord",
"joinCommunity": "加入 NocoDB 社群",
@ -262,41 +265,42 @@
"docReference": "文件參考文獻",
"selectUserRole": "選擇使用者角色",
"childTable": "子表格",
"childColumn": "子欄",
"childColumn": "子欄",
"onUpdate": "更新",
"onDelete": "在刪除",
"account": "Account",
"language": "Language",
"account": "帳戶",
"language": "語言",
"primaryColor": "Primary Color",
"accentColor": "Accent Color",
"customTheme": "Custom Theme",
"requestDataSource": "Request a data source you need?",
"apiKey": "API Key",
"sharedBase": "Shared Base",
"importData": "Import Data",
"importSecondaryViews": "Import Secondary Views",
"importRollupColumns": "Import Rollup Columns",
"importLookupColumns": "Import Lookup Columns",
"importAttachmentColumns": "Import Attachment Columns",
"importData": "匯入資料",
"importSecondaryViews": "匯入 Secondary Views",
"importRollupColumns": "匯入 Rollup 欄位",
"importLookupColumns": "匯入 Lookup 欄位",
"importAttachmentColumns": "匯入 Attachment 欄位",
"importFormulaColumns": "Import Formula Columns",
"noData": "No Data",
"noData": "目前沒有資料",
"goToDashboard": "Go to Dashboard",
"importing": "Importing",
"importing": "匯入中",
"flattenNested": "Flatten Nested",
"downloadAllowed": "Download allowed",
"downloadAllowed": "允許下載",
"weAreHiring": "We are Hiring!",
"primaryKey": "Primary key",
"primaryKey": "主鍵",
"hasMany": "has many",
"belongsTo": "belongs to",
"manyToMany": "have many to many relation",
"extraConnectionParameters": "Extra connection parameters",
"commentsOnly": "Comments only",
"documentation": "Documentation",
"documentation": "文件",
"subscribeNewsletter": "Subscribe to our weekly newsletter",
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"signUpWithGoogle": "使用 Google 帳號註冊",
"signInWithGoogle": "使用 Google 帳號登入",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!"
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
},
"activity": {
"createProject": "建立專案",
@ -309,7 +313,7 @@
"deleteProject": "刪除專案",
"refreshProject": "重新整理專案",
"saveProject": "儲存專案",
"deleteKanbanStack": "Delete stack?",
"deleteKanbanStack": "刪除 stack?",
"createProjectExtended": {
"extDB": "連線至外部資料庫來建立",
"excel": "從 Excel 建立專案",
@ -328,10 +332,10 @@
"projInfo": "複製專案資訊",
"themes": "主題"
},
"sort": "種類",
"addSort": "加排序選項",
"sort": "排序",
"addSort": "加排序選項",
"filter": "篩選",
"addFilter": "加過濾器",
"addFilter": "加過濾器",
"share": "分享",
"shareBase": {
"disable": "禁用共享基礎",
@ -341,12 +345,14 @@
"invite": "邀請",
"inviteMore": "邀請更多",
"inviteTeam": "邀請團隊",
"inviteUser": "邀請使用者",
"inviteToken": "邀請權杖",
"newUser": "新使用者",
"editUser": "編輯使用者",
"deleteUser": "從專案中刪除使用者",
"resendInvite": "重新發送邀請電子郵件",
"copyInviteURL": "複製邀請連結",
"copyPasswordResetURL": "Copy password reset URL",
"newRole": "新角色",
"reloadRoles": "重新載入角色",
"nextPage": "下一頁",
@ -354,11 +360,11 @@
"nextRecord": "下一步記錄",
"previousRecord": "之前的紀錄",
"copyApiURL": "複製 API 網址",
"createTable": "表創造",
"createTable": "建立資料表",
"refreshTable": "表刷新",
"renameTable": "重命名",
"deleteTable": "刪除",
"addField": "將新字段添加到此表",
"renameTable": "重命名資料表",
"deleteTable": "刪除資料表",
"addField": "將新欄位增加到此資料表",
"setPrimary": "設置為主要值",
"addRow": "新增行",
"saveRow": "儲存行",
@ -376,19 +382,19 @@
"clearMetadata": "清除中繼資料",
"exportToFile": "匯出為檔案",
"changePwd": "更改密碼",
"createView": "建立檢視",
"shareView": "分享檢視",
"createView": "建立檢視",
"shareView": "分享檢視",
"listSharedView": "共享視圖列表",
"ListView": "檢視表清單",
"copyView": "複製檢視",
"renameView": "重新命名檢視",
"deleteView": "刪除檢視",
"copyView": "複製檢視",
"renameView": "重新命名檢視",
"deleteView": "刪除檢視",
"createGrid": "創建網格視圖",
"createGallery": "創建畫廊視圖",
"createCalendar": "創建日曆視圖",
"createKanban": "創建尋呼視圖",
"createForm": "創建表單視圖",
"showSystemFields": "顯示系統字段",
"showSystemFields": "顯示系統欄位",
"copyUrl": "複製網址",
"openTab": "開啟新分頁",
"iFrame": "複製嵌入式 HTML 程式碼",
@ -416,8 +422,8 @@
"expandRecord": "Expand Record",
"deleteRecord": "Delete Record",
"erd": {
"showColumns": "Show Columns",
"showPkAndFk": "Show Primary and Foreign Keys",
"showColumns": "顯示欄位",
"showPkAndFk": "顯示主鍵與外鍵",
"showSqlViews": "Show SQL Views",
"showMMTables": "Show Many to Many tables",
"showJunctionTableNames": "Show Junction Table Names"
@ -440,13 +446,13 @@
"dark": "它確實有黑色(^⇧b)",
"light": "它是黑色嗎?(^⇧b)"
},
"addTable": "添加新表",
"inviteMore": "邀請更多用戶",
"addTable": "建立資料表",
"inviteMore": "邀請更多使用者",
"toggleNavDraw": "切換導航抽屜",
"reloadApiToken": "重新載入 API 權杖",
"generateNewApiToken": "產生新 API 權杖",
"addRole": "添加新角色",
"reloadList": "重新載列表",
"reloadList": "重新載列表",
"metaSync": "同步中繼資料",
"sqlMigration": "重新加載遷移",
"updateRestart": "更新並重新啟動",
@ -462,28 +468,32 @@
"projName": "輸入專案名稱",
"password": {
"enter": "輸入密碼",
"current": "前密碼",
"current": "前密碼",
"new": "新密碼",
"save": "儲存密碼",
"confirm": "確認新密碼"
},
"searchProjectTree": "搜索",
"searchFields": "搜索字段",
"searchColumn": "搜索{search}列",
"searchApps": "搜索應用程",
"searchProjectTree": "搜索專案樹",
"searchFields": "搜索欄位",
"searchColumn": "搜索 {search} 列",
"searchApps": "搜索應用程",
"searchModels": "搜索模型",
"noItemsFound": "未找到任何項目",
"defaultValue": "預設值",
"filterByEmail": "通過電子郵件過濾",
"filterQuery": "Filter query",
"selectField": "Select field"
"filterQuery": "過濾查詢",
"selectField": "選擇欄位"
},
"msg": {
"info": {
"roles": {
"orgCreator": "建立者可以建立新專案與存取任何受邀請的專案.",
"orgViewer": "檢視者不可建立新專案但可以存取任何受邀請的專案."
},
"footerInfo": "每頁行駛",
"upload": "選擇檔案以上傳",
"upload_sub": "或拖放檔案",
"excelSupport": "支持:.xls,.xlsx,.xlsm,.ods,.ots",
"excelSupport": "支:.xls,.xlsx,.xlsm,.ods,.ots",
"excelURL": "輸入 Excel 檔案 URL",
"csvURL": "輸入 CSV 檔案 URL",
"footMsg": "要解析為推斷數據類型的行數",
@ -496,10 +506,10 @@
"startProject": "你想啟動這個專案嗎?",
"restartProject": "你想重新啟動專案嗎?",
"deleteProject": "你想刪除這個專案嗎?",
"shareBasePrivate": "產生公開享的 Readonly Base",
"shareBasePrivate": "產生公開享的 Readonly Base",
"shareBasePublic": "網路上的任何人都可以查看",
"userInviteNoSMTP": "看起來你還沒有配置郵件!請複上面的邀請鏈接並將其發送給",
"dragDropHide": "在此處拖放字段以隱藏",
"userInviteNoSMTP": "看起來你還沒有配置郵件!請複上面的邀請鏈接並將其發送給",
"dragDropHide": "在此處拖放欄位以隱藏",
"formInput": "輸入表單輸入標籤",
"formHelpText": "添加一些幫助文本",
"onlyCreator": "僅建立者可見",
@ -510,10 +520,10 @@
"privateLinkAdditionalInfo": "具有私有連結的人只能看到此檢視表中可見的儲存格",
"afterFormSubmitted": "表格提交後",
"apiOptions": "存取專案方式",
"submitAnotherForm": "顯示“提交另一個表格”按鈕",
"submitAnotherForm": "顯示 '提交另一個表格' 按鈕",
"showBlankForm": "5 秒後顯示空白表格",
"emailForm": "發電子郵件給我",
"showSysFields": "顯示系統字段",
"showSysFields": "顯示系統欄位",
"filterAutoApply": "自動申請",
"showMessage": "顯示此消息",
"viewNotShared": "當前視圖不共享!",
@ -521,9 +531,9 @@
"collabView": "具有編輯權限或更高的合作者可以更改視圖配置。",
"lockedView": "沒有人可以編輯視圖配置,直到它被解鎖。",
"personalView": "只有您可以編輯視圖配置。默認情況下,其他合作者的個人視圖隱藏。",
"ownerDesc": "可以添加/刪除創建者。和完整編輯數據庫結構和字段。",
"creatorDesc": "可以完全編輯數據庫結構和值。",
"editorDesc": "可以編輯記錄但無法更改數據庫/字段的結構。",
"ownerDesc": "可以添加/刪除創建者。和完整編輯資料庫結構和欄位。",
"creatorDesc": "可以完全編輯資料庫結構和值。",
"editorDesc": "可以編輯記錄但無法更改資料庫/欄位的結構。",
"commenterDesc": "可以查看和評論記錄,但無法編輯任何內容",
"viewerDesc": "可以查看記錄但無法編輯任何內容",
"addUser": "新增使用者",
@ -541,7 +551,7 @@
},
"sponsor": {
"header": "你可以幫助我們!",
"message": "我們是一支小型團隊,全職工作,使Nocodb開放來源。我們相信一個像Nocodb這樣的工具應該在互聯網上的每個問題求解器上自由提供。"
"message": "我們是一個小型團隊,全職打造 NocoDB 並且開源程式碼。我們相信像 NocoDB 這樣的工具應該在網際網路上自由提供給每位問題解決者。"
},
"loginMsg": "登入 NocoDB",
"passwordRecovery": {
@ -567,7 +577,7 @@
"tablesMetadataInSync": "表元數據同步",
"addMultipleUsers": "您可以添加多個逗號(,)分隔的電子郵件",
"enterTableName": "輸入表名",
"addDefaultColumns": "添加默認列",
"addDefaultColumns": "建立預設欄位",
"tableNameInDb": "數據庫中保存的表名",
"airtable": {
"credentials": "Where to find this?"
@ -576,15 +586,15 @@
"clickOrDrag": "Click or drag file to this area to upload"
},
"metaDataRecreated": "Table metadata recreated successfully",
"invalidCredentials": "Invalid credentials",
"downloadingMoreFiles": "Downloading more files",
"copiedToClipboard": "Copied to clipboard",
"invalidCredentials": "無效憑證",
"downloadingMoreFiles": "下載更多檔案",
"copiedToClipboard": "複製到剪貼簿",
"requriedFieldsCantBeMoved": "Required field can't be moved",
"updateNotAllowedWithoutPK": "Update not allowed for table which doesn't have primary key",
"autoIncFieldNotEditable": "Auto increment field is not editable",
"editingPKnotSupported": "Editing primary key not supported",
"deletedCache": "Deleted cache successfully",
"cacheEmpty": "Cache is empty",
"cacheEmpty": "快取是空的",
"exportedCache": "Exported Cache Successfully",
"valueAlreadyInList": "This value is already in the list",
"noColumnsToUpdate": "No columns to update",
@ -592,7 +602,7 @@
"generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base",
"deleteViewConfirmation": "Are you sure you want to delete this view?",
"deleteTableConfirmation": "Do you want to delete the table",
"showM2mTables": "Show M2M Tables",
"showM2mTables": "顯示 M2M 資料表",
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack."
},
"error": {
@ -616,10 +626,10 @@
"atLeastOneSpecialChar": "One special character",
"allowedSpecialCharList": "Allowed special character list"
},
"invalidURL": "Invalid URL",
"invalidURL": "無效的 URL",
"internalError": "Some internal error occurred",
"templateGeneratorNotFound": "Template Generator cannot be found!",
"fileUploadFailed": "Failed to upload file",
"fileUploadFailed": "檔案上傳失敗",
"primaryColumnUpdateFailed": "Failed to update primary column",
"formDescriptionTooLong": "Data too long for Form Description",
"columnsRequired": "Following columns are required",
@ -629,7 +639,7 @@
"nullValueViolatesNotNull": "Null value violates not-null constraint",
"sourceHasInvalidNumbers": "Source data contains some invalid numbers",
"sourceHasInvalidBoolean": "Source data contains some invalid boolean values",
"invalidForm": "Invalid Form",
"invalidForm": "無效的表格",
"formValidationFailed": "Form validation failed",
"youHaveBeenSignedOut": "You have been signed out",
"failedToLoadList": "Failed to load list",
@ -651,9 +661,10 @@
"targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type",
"theAcceptedFileTypeIsCsv": "The accepted file type is .csv",
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"parameterKeyCannotBeEmpty": "Parameter key 不可為空",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} cannot be empty."
"fieldRequired": "{value} 不可為空.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
"exportMetadata": "專案中繼資料已成功匯出",
@ -677,29 +688,34 @@
"pluginUninstalled": "Plugin uninstalled successfully",
"pluginSettingsSaved": "Plugin settings saved successfully",
"pluginTested": "Successfully tested plugin settings",
"tableRenamed": "Table renamed successfully",
"tableRenamed": "資料表重新命名成功",
"viewDeleted": "View deleted successfully",
"primaryColumnUpdated": "Successfully updated as primary column",
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "Successfully added user to project",
"userAddedToProject": "成功增加使用者到專案",
"userAdded": "成功增加使用者",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL copied to clipboard",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"inviteURLCopied": "Invite URL 複製到剪貼簿",
"passwordResetURLCopied": "Password reset URL copied to 剪貼簿",
"shareableURLCopied": "Copied shareable base URL to 剪貼簿!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
"tableDataImported": "Successfully imported table data",
"webhookUpdated": "Webhook details updated successfully",
"webhookDeleted": "Hook deleted successfully",
"webhookTested": "Webhook tested successfully",
"columnUpdated": "Column updated",
"columnCreated": "Column created",
"passwordChanged": "Password changed successfully. Please login again."
"columnUpdated": "欄位已更新",
"columnCreated": "欄位已建立",
"passwordChanged": "密碼變更成功. 請重新登入.",
"settingsSaved": "設定儲存成功",
"roleUpdated": "角色更新成功"
}
}
}

34
packages/nc-gui/layouts/base.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { computed, navigateTo, ref, useGlobal, useNuxtApp, useRoute, useSidebar, useUIPermission } from '#imports'
import { computed, navigateTo, ref, useGlobal, useNuxtApp, useRoute, useSidebar } from '#imports'
const { signOut, signedIn, isLoading, user, currentVersion } = useGlobal()
@ -13,8 +13,6 @@ const hasSider = ref(false)
const sidebar = ref<HTMLDivElement>()
const { isUIAllowed } = useUIPermission()
const logout = () => {
signOut()
navigateTo('/signin')
@ -22,6 +20,8 @@ const logout = () => {
const { hooks } = useNuxtApp()
const isDashboard = computed(() => !!route.params.projectType)
/** when page suspensions have finished, check if a sidebar element was teleported into the layout */
hooks.hook('page:finish', () => {
if (sidebar.value) {
@ -39,7 +39,7 @@ hooks.hook('page:finish', () => {
<a-layout class="!flex-col">
<a-layout-header
v-if="!route.meta.public && signedIn && !route.meta.hideHeader"
class="flex !bg-primary items-center text-white pl-4 pr-5 shadow-lg"
class="flex !bg-primary items-center text-white !pl-2 !pr-5"
>
<div
v-if="!route.params.projectType"
@ -52,7 +52,10 @@ hooks.hook('page:finish', () => {
<template #title>
{{ currentVersion }}
</template>
<img width="35" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" />
<div class="flex items-center gap-2">
<img width="25" alt="NocoDB" src="~/assets/img/icons/512x512-trans.png" />
<img v-show="!isDashboard" width="90" alt="NocoDB" src="~/assets/img/brand/text.png" />
</div>
</a-tooltip>
</div>
@ -87,27 +90,30 @@ hooks.hook('page:finish', () => {
<template #overlay>
<a-menu class="!py-0 leading-8 !rounded">
<a-menu-item key="0" data-testid="nc-menu-accounts__user-settings" class="!rounded-t">
<nuxt-link v-e="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/user">
<MdiAt class="mt-1 group-hover:text-accent" />&nbsp;
<span class="prose group-hover:text-primary"> {{ email }}</span>
<nuxt-link v-e="['c:navbar:user:email']" class="nc-project-menu-item group !no-underline" to="/account/users">
<MdiAccountCircleOutline class="mt-1 group-hover:text-accent" />&nbsp;
<div class="prose group-hover:text-primary">
<div>Account</div>
<div class="text-xs text-gray-500">{{ email }}</div>
</div>
</nuxt-link>
</a-menu-item>
<a-menu-divider class="!m-0" />
<a-menu-item v-if="isUIAllowed('appStore')" key="0" class="!rounded-t">
<!-- <a-menu-item v-if="isUIAllowed('appStore')" key="0" class="!rounded-t">
<nuxt-link
v-e="['c:settings:appstore', { page: true }]"
class="nc-project-menu-item group !no-underline"
to="/apps"
to="/admin/users"
>
<MdiStorefrontOutline class="mt-1 group-hover:text-accent" />&nbsp;
<MdiShieldAccountOutline class="mt-1 group-hover:text-accent" />&nbsp;
<span class="prose group-hover:text-primary">{{ $t('title.appStore') }}</span>
&lt;!&ndash; todo: i18n &ndash;&gt;
<span class="prose group-hover:text-primary">Account management</span>
</nuxt-link>
</a-menu-item>
<a-menu-divider class="!m-0" />
<a-menu-divider class="!m-0" /> -->
<a-menu-item key="1" class="!rounded-b group">
<div v-e="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">

2
packages/nc-gui/layouts/default.vue

@ -20,7 +20,7 @@ export default {
<template>
<div class="w-full h-full">
<Teleport :to="hasSidebar ? '#nc-sidebar-left' : null" :disabled="!hasSidebar">
<slot :key="$route.name" name="sidebar" />
<slot name="sidebar" />
</Teleport>
<a-layout-content>

8
packages/nc-gui/lib/constants.ts

@ -18,7 +18,7 @@ export const rolePermissions = {
[Role.Super]: '*',
[Role.Admin]: {} as Record<string, boolean>,
[Role.Guest]: {} as Record<string, boolean>,
[Role.User]: {
[Role.OrgLevelCreator]: {
include: {
projectCreate: true,
projectActions: true,
@ -30,11 +30,17 @@ export const rolePermissions = {
[ProjectRole.Creator]: {
exclude: {
appStore: true,
superAdminUserManagement: true,
superAdminAppSettings: true,
appLicense: true,
},
},
[ProjectRole.Owner]: {
exclude: {
appStore: true,
superAdminUserManagement: true,
superAdminAppSettings: true,
appLicense: true,
},
},
[ProjectRole.Editor]: {

3
packages/nc-gui/lib/enums.ts

@ -1,7 +1,8 @@
export enum Role {
Super = 'super',
Admin = 'admin',
User = 'user',
OrgLevelCreator = 'org-level-creator',
OrgLevelViewer = 'org-level-viewer',
Guest = 'guest',
}

1
packages/nc-gui/lib/types.ts

@ -86,6 +86,7 @@ export interface SharedViewMeta extends Record<string, any> {
withTheme?: boolean
theme?: Partial<ThemeConfig>
allowCSVDownload?: boolean
rtl?: boolean
}
export interface SharedView {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save