Browse Source

Merge branch 'master' of github.com:nocodb/nocodb

pull/636/head
Pranav C 3 years ago
parent
commit
f12f4befbe
  1. 9
      .all-contributorsrc
  2. 44
      .github/workflows/publish-noco-i18n.yml
  3. 6
      README.md
  4. 22
      cypress.json
  5. 90
      cypress/integration/common/4c_form_view_detailed.js
  6. 24
      cypress/support/page_objects/mainPage.js
  7. 4
      packages/nc-gui/components/utils/language.vue
  8. 2
      packages/nc-gui/lang/da.json
  9. 2
      packages/nc-gui/lang/de.json
  10. 2
      packages/nc-gui/lang/en.json
  11. 2
      packages/nc-gui/lang/es.json
  12. 2
      packages/nc-gui/lang/fi.json
  13. 2
      packages/nc-gui/lang/fr.json
  14. 2
      packages/nc-gui/lang/hr.json
  15. 2
      packages/nc-gui/lang/id.json
  16. 2
      packages/nc-gui/lang/it_IT.json
  17. 2
      packages/nc-gui/lang/iw.json
  18. 4
      packages/nc-gui/lang/ja.json
  19. 2
      packages/nc-gui/lang/ko.json
  20. 2
      packages/nc-gui/lang/nl.json
  21. 2
      packages/nc-gui/lang/no.json
  22. 2
      packages/nc-gui/lang/pt.json
  23. 2
      packages/nc-gui/lang/ru.json
  24. 2
      packages/nc-gui/lang/sv.json
  25. 2
      packages/nc-gui/lang/th.json
  26. 2
      packages/nc-gui/lang/uk.json
  27. 2
      packages/nc-gui/lang/vi.json
  28. 247
      packages/nc-gui/lang/zh.json
  29. 2
      packages/nc-gui/lang/zh_CN.json
  30. 2
      packages/nc-gui/lang/zh_HK.json
  31. 2
      packages/nc-gui/lang/zh_TW.json
  32. 23
      packages/noco-i18n/.gitignore
  33. 62
      packages/noco-i18n/README.md
  34. 5
      packages/noco-i18n/babel.config.js
  35. 12230
      packages/noco-i18n/package-lock.json
  36. 43
      packages/noco-i18n/package.json
  37. BIN
      packages/noco-i18n/public/favicon.ico
  38. 17
      packages/noco-i18n/public/index.html
  39. 26
      packages/noco-i18n/src/App.vue
  40. BIN
      packages/noco-i18n/src/assets/logo.png
  41. 211
      packages/noco-i18n/src/components/Landing.vue
  42. 68
      packages/noco-i18n/src/index.js
  43. 4
      packages/noco-i18n/src/main.js
  44. 2
      packages/nocodb/src/plugins/mino/Minio.ts

9
.all-contributorsrc

@ -405,6 +405,15 @@
"contributions": [
"code"
]
},
{
"login": "iqiziqi",
"name": "Ziqi",
"avatar_url": "https://avatars.githubusercontent.com/u/8640316?v=4",
"profile": "https://github.com/iqiziqi",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

44
.github/workflows/publish-noco-i18n.yml

@ -0,0 +1,44 @@
name: "Publish : noco-i18n"
on:
push:
branches: [ master ]
release:
types: [ published ]
jobs:
copy-file:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Check for update
run: |
echo "CHANGED=$([[ $(lerna ls --since ${{github.event.before}} | grep noco-i18n) = noco-i18n ]] && echo 'OK')" >> $GITHUB_ENV
- uses: actions/setup-node@v2
if: ${{ env.CHANGED == 'OK' }}
with:
node-version: 14
- name: Build noco-i18n
if: ${{ env.CHANGED == 'OK' || github.event_name == 'release' }}
run: |
cd packages/noco-i18n
npm install
npm run generate
- name: Pushes generated output
if: ${{ env.CHANGED == 'OK' || github.event_name == 'release' }}
uses: dmnemec/copy_file_to_another_repo_action@main
env:
API_TOKEN_GITHUB: ${{ secrets.GH_TOKEN }}
with:
source_file: 'packages/noco-i18n/dist/'
destination_repo: 'nocodb/noco-i18n'
destination_folder: 'docs'
user_email: 'oof1lab@gmail.com'
user_name: 'o1lab'
commit_message: 'Autorelease from github.com/nocodb/nocodb/packages/noco-i18n'

6
README.md

@ -21,7 +21,8 @@ Turns any MySQL, PostgreSQL, SQL Server, SQLite & MariaDB into a smart-spreadshe
<p align="center">
<a href="http://www.nocodb.com"><b>Website</b></a>
<a href="https://discord.gg/5RgZmkW"><b>Discord</b></a>
<a href="https://twitter.com/nocodb"><b>Twitter</b></a>
<a href="https://twitter.com/nocodb"><b>Twitter</b></a>
<a href="https://docs.nocodb.com/"><b>Documentation</b></a>
</p>
<p align="center"><img src="https://user-images.githubusercontent.com/5435402/133762127-e94da292-a1c3-4458-b09a-02cd5b57be53.png" alt="The Open Source Airtable Alternative - works on MySQL, Postgres SQL Server & MariaDB" width="1000px" /></p>
@ -220,6 +221,8 @@ npm run dev
Changes made to code automatically restart.
> nocodb/packages/nocodb includes nc-lib-gui which is the built version of nc-gui hosted in npm registry. You can visit localhost:8000/dashboard in browser after starting the backend locally if you just want to modify the backend only.
## Running Cypress tests locally
@ -312,6 +315,7 @@ Our mission is to provide the most powerful no-code interface for databases whic
<tr>
<td align="center"><a href="https://www.linkedin.com/in/zhansayam/"><img src="https://avatars.githubusercontent.com/u/41486762?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zhansaya Maksut</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=ZhansayaM" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/agkfri"><img src="https://avatars.githubusercontent.com/u/37952138?v=4?s=100" width="100px;" alt=""/><br /><sub><b>agkfri</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=agkfri" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/iqiziqi"><img src="https://avatars.githubusercontent.com/u/8640316?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ziqi</b></sub></a><br /><a href="https://github.com/nocodb/nocodb/commits?author=iqiziqi" title="Code">💻</a></td>
</tr>
</table>

22
cypress.json

@ -1,5 +1,27 @@
{
"baseUrl": "http://localhost:3000/",
"testFiles": [
"common/00_pre_configurations.js",
"common/0a_project_operations.js",
"common/1a_table_operations.js",
"common/1b_table_column_operations.js",
"common/1c_table_row_operations.js",
"common/2a_table_with_belongs_to_colulmn.js",
"common/2b_table_with_m2m_column.js",
"common/3a_filter_sort_fields_operations.js",
"common/3b_formula_column.js",
"common/3c_lookup_column.js",
"common/3d_rollup_column.js",
"common/4a_table_view_grid_gallery_form.js",
"common/4b_table_view_share.js",
"common/4c_form_view_detailed.js",
"common/4d_table_view_grid_locked.js",
"common/5a_user_role.js",
"common/5b_preview_role.js",
"common/6a_audit.js",
"common/6c_swagger_api.js",
"common/6d_language_validation.js"
],
"defaultCommandTimeout": 13000,
"pageLoadTimeout": 600000,
"viewportWidth": 1980,

90
cypress/integration/common/4c_form_view_detailed.js

@ -2,6 +2,8 @@ import { loginPage } from "../../support/page_objects/navigation"
import { isTestSuiteActive } from "../../support/page_objects/projectConstants"
import { mainPage } from "../../support/page_objects/mainPage"
let formViewURL
const genTest = (type, xcdb) => {
if(!isTestSuiteActive(type, xcdb)) return;
@ -19,6 +21,14 @@ const genTest = (type, xcdb) => {
cy.openTableTab('Country');
})
beforeEach(() => {
cy.restoreLocalStorage();
})
afterEach(() => {
cy.saveLocalStorage();
})
// Common routine to create/edit/delete GRID & GALLERY view
// Input: viewType - 'grid'/'gallery'
@ -51,7 +61,7 @@ const genTest = (type, xcdb) => {
it(`Validate ${viewType} view: Drag & drop for add/remove items`, () => {
// default, only one item in menu-bar; ensure LastUpdate field was present in form view
cy.get('.col-md-4').find('.pointer.item').its('length').should('eq', 1)
cy.get('.col-md-4').find('.pointer.item').should('not.exist')
cy.get('.nc-field-wrapper').eq(0).contains('LastUpdate').should('exist')
// drag 'LastUpdate' & drop into menu bar drag-drop box
@ -60,14 +70,13 @@ const genTest = (type, xcdb) => {
// validate- fields count in menu bar to be increased by 1 &&
// first member in 'formView' is Country
cy.get('.nc-field-wrapper').eq(0).contains('Country').should('exist')
cy.get('.col-md-4').find('.pointer.item').its('length').should('eq', 2)
cy.get('.col-md-4').find('.pointer.item').its('length').should('eq', 1)
})
it(`Validate ${viewType} view: Inverted order field member addition from menu`, () => {
cy.get('.col-md-4').find('.pointer.caption').contains('remove all').click()
// click fields in inverted order: CountryId, Country, LastUpdate, Country => City
cy.get('.col-md-4').find('.pointer.item').eq(3).click()
// click fields in inverted order: Country, LastUpdate, Country => City
cy.get('.col-md-4').find('.pointer.item').eq(2).click()
cy.get('.col-md-4').find('.pointer.item').eq(1).click()
cy.get('.col-md-4').find('.pointer.item').eq(0).click()
@ -76,7 +85,6 @@ const genTest = (type, xcdb) => {
cy.get('.nc-field-wrapper').eq(0).contains('Country => City').should('exist')
cy.get('.nc-field-wrapper').eq(1).contains('LastUpdate').should('exist')
cy.get('.nc-field-wrapper').eq(2).contains('Country').should('exist')
// hidden: cy.get('.nc-field-wrapper').eq(3).contains('CountryId').should('exist')
})
it(`Validate ${viewType}: Form header & description validation`, () => {
@ -98,20 +106,23 @@ const genTest = (type, xcdb) => {
// .nc-form : form view (right hand side)
// ensure buttons exist on left hand menu
cy.get('.col-md-4').find('.pointer.caption').contains('add all').should('exist')
cy.get('.col-md-4').find('.pointer.caption').contains('add all').should('not.exist')
cy.get('.col-md-4').find('.pointer.caption').contains('remove all').should('exist')
// click: remove-all
cy.get('.col-md-4').find('.pointer.caption').contains('remove all').click()
// form should not contain any "field remove icons" -- all fields removed
cy.get('.nc-form').find('.nc-field-remove-icon').should('not.exist')
// menu bar should contain 4 .pointer.item (countryId, Country, LastUpdate, County->City)
cy.get('.col-md-4').find('.pointer.item').its('length').should('eq', 4)
// menu bar should contain 3 .pointer.item (Country, LastUpdate, County->City)
cy.get('.col-md-4').find('.pointer.item').its('length').should('eq', 3)
// click: add all
cy.get('.col-md-4').find('.pointer.caption').contains('remove all').should('not.exist')
cy.get('.col-md-4').find('.pointer.caption').contains('add all').click()
cy.get('.col-md-4').find('.pointer.caption').contains('remove all').should('exist')
// form should contain "field remove icons"
cy.get('.nc-form').find('.nc-field-remove-icon').should('exist')
// Fix me: a dummy remove icon is left over on screen
cy.get('.nc-form').find('.nc-field-remove-icon').its('length').should('eq', 4)
// menu bar should not contain .pointer.item (column name/ field name add options)
cy.get('.col-md-4').find('.pointer.item').should('not.exist')
@ -135,7 +146,7 @@ const genTest = (type, xcdb) => {
it(`Validate ${viewType}: Submit default, with valid Show message entry`, () => {
// clicking again on view name shows blank still. work around- toggling between two views
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country').click()
// cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country').click()
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country1').click()
// fill up mandatory fields
@ -156,7 +167,7 @@ const genTest = (type, xcdb) => {
it(`Validate ${viewType}: Submit default, Enable checkbox "Submit another form`, () => {
// clicking again on view name shows blank still. work around- toggling between two views
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country').click()
// cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country').click()
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country1').click()
// fill up mandatory fields
@ -211,7 +222,38 @@ const genTest = (type, xcdb) => {
// end of test removes newly added rows from table. that step validates if row was successfully added.
})
it(`Validate ${viewType}: Email me verification, without SMTP configuration`, () => {
// open formview & enable "email me" option
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country1').click()
cy.get('.nc-form > .mx-auto').find('[type="checkbox"]').eq(2).click({ force: true })
// validate if toaster pops up requesting to activate SMTP
cy.get('.toasted:visible', { timout: 6000 })
.contains('Please activate SMTP plugin in App store for enabling email notification')
.should('exist')
})
it(`Validate ${viewType}: Email me verification, with SMTP configuration`, () => {
// activate SMTP, dummy profile
mainPage.navigationDraw(mainPage.APPSTORE).click()
mainPage.configureSMTP('admin@ex.com', 'smtp.ex.com', '8080', 'TLS')
// open form view & enable "email me" option
cy.openTableTab('Country');
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country1').click()
cy.get('.nc-form > .mx-auto').find('[type="checkbox"]').eq(2).click({ force: true })
// validate if toaster pops up informing installation of email notification
cy.get('.toasted:visible', { timout: 6000 })
.contains('Successfully installed and email notification will use SMTP configuration')
.should('exist')
// reset SMPT config's
mainPage.navigationDraw(mainPage.APPSTORE).click()
mainPage.resetSMTP()
cy.openTableTab('Country');
})
it(`Validate ${viewType}: Add/ remove field verification"`, () => {
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country1').click()
cy.get('#data-table-form-Country').should('exist')
// remove "country field"
cy.get('.nc-form').find('.nc-field-remove-icon').eq(1).click()
@ -221,6 +263,32 @@ const genTest = (type, xcdb) => {
// add it back
cy.get('.col-md-4').find('.pointer.item').contains('Country').click()
cy.get('#data-table-form-Country').should('exist')
})
it(`Validate ${viewType}: URL verification`, () => {
cy.get(`.nc-view-item.nc-${viewType}-view-item`).contains('Country1').click()
// verify URL & copy it for subsequent test
cy.url().should('contain', `&view=Country1`)
cy.url().then((url) => {
cy.log(url)
formViewURL = url
})
})
it(`Validate ${viewType}: URL validation after re-access`, () => {
// visit URL
cy.log(formViewURL)
cy.visit(formViewURL)
// New form appeared? Header & description should exist
cy.get('.nc-form', { timeout: 10000 })
.find('[placeholder="Form Title"]')
.contains('A B C D')
.should('exist')
cy.get('.nc-form', { timeout: 10000 })
.find('[placeholder="Add form description"]')
.contains('Some description about form comes here')
.should('exist')
})
it(`Delete ${viewType} view`, () => {
@ -252,6 +320,8 @@ const genTest = (type, xcdb) => {
viewTest('form')
})
}
// invoke for different API types supported

24
cypress/support/page_objects/mainPage.js

@ -125,6 +125,30 @@ export class _mainPage {
let obj = JSON.parse(localStorage['vuex'])
return obj["users"]["token"]
}
configureSMTP = (from, host, port, secure) => {
cy.get('.v-card__title.title')
.contains('SMTP')
.parents('.elevatio')
.find('button')
.contains(" Install ")
.click({ force: true })
cy.getActiveModal().find('[placeholder="eg: admin@example.com"]').click().type(from)
cy.getActiveModal().find('[placeholder="eg: smtp.example.com"]').click().type(host)
cy.getActiveModal().find('[placeholder="Port"]').click().type(port)
cy.getActiveModal().find('[placeholder="Secure"]').click().type(secure)
cy.getActiveModal().find('button').contains('Save').click()
}
resetSMTP = () => {
cy.get('.v-card__title.title')
.contains('SMTP')
.parents('.elevatio')
.find('button')
.contains(" Reset ")
.click({ force: true })
cy.getActiveModal().find('button').contains('Submit').click()
}
}

4
packages/nc-gui/components/utils/language.vue

@ -43,7 +43,7 @@ export default {
es: 'Española',
fr: 'Français',
id: 'Bahasa Indonesia',
it_IT: 'Italiana',
it_IT: 'Italiano',
ja: '日本語',
ko: '한국인',
nl: 'Nederlandse',
@ -64,7 +64,7 @@ export default {
}),
computed: {
languages() {
return ((this.$i18n && this.$i18n.availableLocales) || ['en']).sort((a, b) => order(b) - order(a))
return ((this.$i18n && this.$i18n.availableLocales) || ['en']).sort()
},
language: {
get() {

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

2
packages/nc-gui/lang/it_IT.json

@ -244,4 +244,4 @@
}
}
}
}
}

2
packages/nc-gui/lang/iw.json

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -78,7 +78,7 @@
"no": "番号",
"preferred": "優先",
"required": "必須",
"requried_ca": "必須 - C.",
"requried_ca": "必須 - C.",
"requried_identity": "必須アイデンティティ"
}
},
@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

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

@ -244,4 +244,4 @@
}
}
}
}
}

247
packages/nc-gui/lang/zh.json

@ -1,247 +0,0 @@
{
"projects": {
"my_projects": "我的项目",
"reload_projects_tooltip": "刷新项目",
"create_new_project_button": {
"text": "新项目",
"subtext_1": "创建",
"subtext_1_tooltip": "创建一个新项目",
"subtext_2": "通过连接为外部数据库创建",
"subtext_2_tooltip": "支持MySQL,PostgreSQL,SQL Server和SQLite"
},
"search_project": "搜索项目",
"import_metadata": "导入元数据",
"export_metadata": "导出元数据",
"clear_metadata": "清除元数据",
"stop_project": "停止项目",
"start_project": "启动项目",
"restart_project": "重新启动项目",
"delete_project": "删除项目",
"project_api_type_tooltip_graphql": "通过GraphQL API访问",
"project_api_type_tooltip_rest": "通过REST API可访问",
"project_empty_message": "通过创建一个新项目开始",
"import_button": {
"text": "通过上传元数据zip文件导入NoCodb项目"
},
"show_community_star": "星星",
"show_community_us_on_Github": "我们在github上",
"show_community_book_a_free_demo": "预订一个免费的演示",
"show_community_get_your_questions_answered": "收到您的问题",
"show_community_join_discord": "加入不和谐",
"show_community_follow_nocodb": "关注Nocodb.",
"search": {
"your_search_found_no_results": "您的搜索{search}找不到结果"
},
"ext_db": {
"title": {
"edit": "编辑项目",
"create": "创建项目"
},
"project_name": "输入项目名称",
"project_type": "访问项目通过",
"button": {
"save_project": "保存项目",
"update_and_restart": "更新并重新启动",
"cancel": "取消",
"cancel_tooltip": "取消并返回"
},
"credentials": {
"title": "数据库凭据",
"db_type": "数据库类型",
"sqlite_file": "sqlite文件",
"host_address": "主机地址",
"port": "端口号码",
"username": "用户名",
"password": "密码",
"db_create_if_not_exists": "数据库:创建如果不存在",
"button": {
"test_db_conn": "测试数据库连接",
"remove_db_from_env": "从环境中删除数据库"
},
"advanced": {
"title": "SSL和高级参数",
"ssl": {
"client_key": {
"title": "客户钥匙",
"toolip": "选择.key文件"
},
"client_cert": {
"title": "客户证书",
"toolip": "选择.cert文件"
},
"server_ca": {
"title": "服务器CA.",
"toolip": "选择CA File."
},
"preferred": "首选",
"usage": {
"no": "不",
"preferred": "首选",
"required": "必需的",
"requried_ca": "必填卡",
"requried_identity": "必要的身份"
}
},
"inflection": {
"table_name": "拐点 - 表名",
"column_name": "拐点 - 列名称"
},
"button": {
"edit_conn_json": "编辑连接JSON."
}
}
},
"error": {
"invalid_char_in_folder_path": "文件夹路径中的字符无效。",
"invalid_db_credentials": "数据库凭据无效。",
"unable_to_connect_to_db": "无法连接数据库,请检查您的数据库已启动。",
"user_doesnt_ve_sufficient_permission": "用户不存在或具有足够的权限来创建架构。"
},
"head": {
"title": "创建项目| Nocodb."
},
"dialog": {
"success": "连接成功了",
"failure": "连接失败:"
}
},
"notifications": {
"no_new": "没有新的通知",
"clear": "清除"
}
},
"signin": {
"title": "登入",
"enter_your_work_email": "输入您的工作电子邮件",
"enter_your_password": "输入您的密码",
"forget_password": "忘记密码了吗 ?",
"dont_ve_an_account": "没有帐户?",
"sign_up": "报名",
"form": {
"rules": {
"email_is_reqd": "电子邮件是必需的",
"email_must_be_valid": "电子邮件必须有效",
"passwd_required": "密码是必需的",
"passwd_must_be_8_chars": "您的密码必须至少为8个字符"
}
},
"head": {
"title": "登录| Nocodb.",
"meta": {
"hid": "登录NoCodb",
"name": "登录NoCodb",
"content": "登录NoCodb"
}
},
"password": {
"recovery": {
"title": "重置您的密码",
"message_1": "请提供您注册时使用的电子邮件地址。",
"message_2": "我们会向您发送一封带有链接以重置密码的电子邮件。",
"success": "请检查您的电子邮件以重置密码",
"button": "发电子邮件"
}
}
},
"signup": {
"title": "报名",
"you_will_be_the_super_admin": "你将是'超级管理'",
"already_ve_an_account": "已经有一个帐户?"
},
"treeview": {
"settings": {
"title": "设置",
"tooltip": "仅对创造者可见"
},
"app_store": "应用商店",
"team_n_auth": {
"title": "团队&auth",
"tooltip": "角色和用户管理"
},
"project_metadata": {
"title": "项目元数据",
"tooltip": "元管理"
},
"preview_as": "预览",
"reset_review": "重置预览"
},
"nav_drawer": {
"title": "意见",
"virtual_views": {
"action": {
"copy": "复制视图",
"rename": "重命名视图",
"delete": "删除视图"
},
"title": "创建一个视图",
"caption": "仅对创造者可见",
"grid": {
"title": "网格",
"create": "添加网格视图"
},
"gallery": {
"title": "画廊",
"create": "添加图库视图"
},
"calendar": {
"title": "日历",
"create": "添加日历视图"
},
"kanban": {
"title": "卡班",
"create": "添加Kanban View."
},
"form": {
"title": "形式",
"create": "添加表单视图"
}
},
"advanced": {
"title1": "分享视图",
"views_list": "观点列表",
"copy_api_url": "复制API URL."
},
"share_view": {
"title": "此视图通过私人链接共享",
"body": "具有私有链接的人只能看到此视图中可见的细胞",
"toggle": {
"option1": "限制密码访问权限",
"option2": "访问是受限制的密码"
},
"password": {
"caption": "输入密码",
"button": "保存密码"
}
}
},
"management": {
"tabs": {
"user": "用户管理",
"api": "API令牌管理",
"roles": "角色管理"
},
"meta": {
"title": "元数据操作",
"export_to_file": {
"title": "导出到文件",
"desc": "将所有元数据从元表导出到Meta目录。"
},
"import": {
"title": "进口",
"desc": "将所有元数据从Meta目录导入元表。"
},
"export_to_zip": {
"title": "出口邮编",
"desc": "将项目元导出到zip文件和下载。"
},
"import_zip": {
"title": "进口邮编",
"desc": "导入项目元拉链文件并重新启动。"
},
"reset": {
"title": "重启",
"desc": "清除元表中的所有元数据。"
}
}
}
}

2
packages/nc-gui/lang/zh_CN.json

@ -244,4 +244,4 @@
}
}
}
}
}

2
packages/nc-gui/lang/zh_HK.json

@ -244,4 +244,4 @@
}
}
}
}
}

2
packages/nc-gui/lang/zh_TW.json

@ -244,4 +244,4 @@
}
}
}
}
}

23
packages/noco-i18n/.gitignore vendored

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

62
packages/noco-i18n/README.md

@ -0,0 +1,62 @@
# NocoDB : i18n Contribution Guide
We 've made it simple to accept new translations
1. Our i18n translations are in google spreadsheet
- Make a [copy of it](https://docs.google.com/spreadsheets/d/1kGp92yLwhs1l7lwwgeor3oN1dFl7JZWuQOa4WSeZ0TE/edit?usp=sharing). ( file > make a copy )
- Create a shareable link from your spreadsheet. ( share > get link > change to anyone with link )
2. Make necessary changes for the required language column (eg. fr)
3. Download the sheet as .csv file ( file > download > csv )
4. Visit [https://i18n.nocodb.com/](https://i18n.nocodb.com/)
5. Upload the CSV file (from step 3).
6. Select the language for which you are translating (same as in step 2)
https://i18n.nocodb.com will
- Automatically copy the translated values to clipboard.
- Automatically show the respective file to open and edit in our github. Example: [fr.json](https://github.com/nocodb/nocodb/edit/master/packages/nc-gui/lang/fr.json).
- Github will ask you to fork the repo - please do so if you haven't forked the repository and then paste the values from clipboard to the file. Alternatively you can just paste the updated JSON value to corresponding files
7. Submit PR with a link to your spreadsheet (from step 1)
- - - -
### Sample screenshot of https://i18n.nocodb.com
![image](https://user-images.githubusercontent.com/35857179/136654196-162a316c-adde-431b-8316-139168298278.png)
- - - -
# Development setup
You need vue cli in order to start the application
```
npm install -g @vue/cli
```
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```

5
packages/noco-i18n/babel.config.js

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

12230
packages/noco-i18n/package-lock.json generated

File diff suppressed because it is too large Load Diff

43
packages/noco-i18n/package.json

@ -0,0 +1,43 @@
{
"name": "noco-i18n",
"version": "1.0.0",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"csv-parser": "^3.0.0",
"papaparse": "^5.3.1",
"vue": "^3.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
packages/noco-i18n/public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
packages/noco-i18n/public/index.html

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

26
packages/noco-i18n/src/App.vue

@ -0,0 +1,26 @@
<template>
<img alt="Vue logo" src="./assets/logo.png">
<Landing />
</template>
<script>
import Landing from './components/Landing.vue'
export default {
name: 'App',
components: {
Landing
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

BIN
packages/noco-i18n/src/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

211
packages/noco-i18n/src/components/Landing.vue

@ -0,0 +1,211 @@
<template>
<div class="wrapper">
<h1>Welcome to NocoDB i18n CSV Converter</h1>
<h2>
For the guideline, please check out <a href="https://github.com/nocodb/nocodb/tree/master/packages/noco-i18n#contribution-guide" target="_blank" rel="noopener">NocoDB i18n Contribution Guide</a>.
</h2>
<table>
<tr>
<td>Upload your CSV file</td>
<td>
<div>
<input type="file" id="file" ref="csvFile" class="file-input" @change="uploadFile">
<label for="file">
<span>Select your CSV</span>
</label>
</div>
</td>
</tr>
<tr v-if="completeStep1">
<td>Select the language you translated</td>
<td>
<div class="select-wrapper">
<select v-on:change="changeItem($event)">
<option value="-">Select the language</option>
<option value="en">English</option>
<option value="fr">Français</option>
<option value="de">Deutsch</option>
<option value="es">Española</option>
<option value="pt">Portuguese</option>
<option value="it_IT">Italiano</option>
<option value="nl">Nederlandse</option>
<option value="ja">日本語</option>
<option value="ko">한국인</option>
<option value="ru">Pусский</option>
<option value="id">Bahasa Indonesia</option>
<option value="zh_CN">大陆简体</option>
<option value="zh_HK">香港繁體</option>
<option value="zh_TW">臺灣正體</option>
<option value="sv">Svenska</option>
<option value="da">Dansk</option>
<option value="vi">Tiếng Việt</option>
<option value="fi">Norsk</option>
<option value="hr">עִברִית</option>
<option value="iw">Suomalainen</option>
<option value="no">Українська</option>
<option value="th">Hrvatski</option>
<option value="uk">ไทย</option>
</select>
</div>
</td>
</tr>
</table>
<h3 class="success-msg" v-if="completeStep2">The generated JSON file has been copied to your clipboard.</h3>
</div>
</template>
<script>
const generate = require('../index');
const papa = require('papaparse');
export default {
name: 'Landing',
props: {
msg: String
},
data() {
return {
completeStep1: false,
completeStep2: false,
selectedLanguage: "-",
}
},
methods: {
async changeItem(event) {
let file = this.$refs.csvFile.files[0]
if (!file || file.type != 'text/csv') {
this.completeStep2 = false
alert('No csv is selected')
throw new Error('No csv is selected')
} else {
let targetLanguage = event.target.value;
if(targetLanguage != '-') {
papa.parse(file, {
worker: true,
// step: function(result) {
// // console.log(result)
// },
complete: function(results) {
generate(results.data, targetLanguage)
}
});
this.completeStep2 = true
} else {
this.completeStep2 = false
}
}
},
uploadFile() {
this.completeStep1 = true
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
[type="file"] {
height: 0;
overflow: hidden;
width: 0;
}
[type="file"] + label {
width: 128px;
border: none;
border-radius: 5px;
color: #fff;
cursor: pointer;
display: inline-block;
font-family: 'Rubik', sans-serif;
font-size: inherit;
font-weight: 500;
outline: none;
padding: 20px 50px;
position: relative;
vertical-align: middle;
background-color: #0180ff;
}
.select-wrapper {
position: relative;
display: block;
width: 230px;
height: 60px;
line-height: 3;
background: #2c3e50;
overflow: hidden;
border-radius: 5px;
margin-top: 20px;
margin-left: auto;
margin-right: auto;
}
select {
-webkit-appearance: none;
-moz-appearance: none;
-ms-appearance: none;
appearance: none;
outline: 0;
box-shadow: none;
border: 0 !important;
background: #0180ff;
background-image: none;
}
select {
width: 100%;
height: 100%;
margin: 0;
text-align: center;
cursor: pointer;
color: #fff;
font-family: 'Rubik', sans-serif;
font-weight: 500;
font-size: inherit;
}
select::-ms-expand {
display: none;
}
.select-wrapper::after {
content: '\25BC';
position: absolute;
top: 0;
right: 0;
bottom: 0;
padding: 5px 15px;
background: #0180ff;
color: #fff;
pointer-events: none;
}
table {
margin-left: auto;
margin-right: auto;
}
td {
padding: 10px 40px;
}
.success-msg {
color: #4657eb;
}
</style>

68
packages/noco-i18n/src/index.js

@ -0,0 +1,68 @@
module.exports = async (csvDatas, targetLanguage) => {
// function for mapping nested property
const mapPropToObject = (obj, prop, val) => {
const keys = prop.split('.');
for (let i = 0, prev = obj; i < keys.length; i++) {
// if last keys assign or overwrite value
if (i === keys.length - 1) {
prev[keys[i]] = val
} else {
// define or re-assign prev value
prev = prev[keys[i]] = prev[keys[i]] || {}
}
}
}
// const downloadJson = (exportObj, exportName) => {
// var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(exportObj, null, 2))
// var downloadAnchorNode = document.createElement('a')
// downloadAnchorNode.setAttribute("href", dataStr)
// downloadAnchorNode.setAttribute("download", exportName + ".json")
// document.body.appendChild(downloadAnchorNode)
// downloadAnchorNode.click()
// downloadAnchorNode.remove()
// }
const copyJsonToClipboard = (str, targetLanguage) => {
var el = document.createElement('textarea')
el.value = str
el.setAttribute('readonly', '')
el.style = {position: 'absolute', left: '-9999px'}
document.body.appendChild(el)
el.select()
document.execCommand('copy')
document.body.removeChild(el)
// alert("The target JSON has been copied to your clipboard")
popEditPage(targetLanguage)
}
const popEditPage = (targetLanguage) => {
var editAnchorNode = document.createElement('a')
editAnchorNode.setAttribute("href", `https://github.com/nocodb/nocodb/edit/master/packages/nc-gui/lang/${targetLanguage}.json`)
editAnchorNode.setAttribute("target", "_blank")
document.body.appendChild(editAnchorNode)
editAnchorNode.click()
editAnchorNode.remove()
}
if (!csvDatas.length) throw new Error('Empty csv')
const languageObjs = {};
const languages = Object.keys(csvDatas[0]).filter(k => k !== 'String Key' && k !== 'String');
for (const data of csvDatas) {
for (const lan of languages) {
languageObjs[lan] = languageObjs[lan] || {};
mapPropToObject(languageObjs[lan], data[0], data[lan])
}
}
for (const [ln, obj] of Object.entries(languageObjs)) {
if(languageObjs[ln]['String Key'] == targetLanguage) {
delete languageObjs[ln]['String Key'];
// downloadJson(obj, targetLanguage)
copyJsonToClipboard(JSON.stringify(obj, null, 2), targetLanguage)
return
}
}
}

4
packages/noco-i18n/src/main.js

@ -0,0 +1,4 @@
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

2
packages/nocodb/src/plugins/mino/Minio.ts

@ -61,6 +61,8 @@ export default class Minio implements IStorageAdapter {
// todo: update in ui(checkbox and number field)
this.input.port = +this.input.port || 9000;
this.input.useSSL = this.input.useSSL ==='true';
this.input.accessKey = this.input.access_key;
this.input.secretKey = this.input.access_secret;
this.minioClient = new MinioClient(this.input);
}

Loading…
Cancel
Save