diff --git a/.github/workflows/affine-keck.yml b/.github/workflows/affine-keck.yml.bak
similarity index 100%
rename from .github/workflows/affine-keck.yml
rename to .github/workflows/affine-keck.yml.bak
diff --git a/.github/workflows/lint.yml b/.github/workflows/languages-sync.yml
similarity index 52%
rename from .github/workflows/lint.yml
rename to .github/workflows/languages-sync.yml
index 8efce5cbd4..33b0b79e93 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/languages-sync.yml
@@ -1,10 +1,17 @@
-name: Lint
+name: Languages Sync
on:
push:
- branches: [master]
- # pull_request:
- # branches: [master]
+ branches: [ "develop", "master" ]
+ paths:
+ - 'libs/datasource/i18n/**'
+ - '.github/workflows/languages-sync.yml'
+ pull_request:
+ branches: [ "develop", "master" ]
+ paths:
+ - 'libs/datasource/i18n/**'
+ - '.github/workflows/languages-sync.yml'
+ workflow_dispatch:
# Cancels all previous workflow runs for pull requests that have not completed.
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
@@ -18,11 +25,9 @@ jobs:
main:
strategy:
matrix:
- node-version: [16]
+ node-version: [18]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
- # TODO Remove the next line after cleaning all errors
- continue-on-error: true
steps:
- name: Checkout
@@ -43,20 +48,16 @@ jobs:
- name: Install node modules
run: pnpm install
- - name: Lint
- if: always()
- run: pnpm run lint:with-cache
+ - name: Check Language Key
+ if: github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/master'
+ working-directory: ./libs/datasource/i18n
+ run: pnpm run sync-languages:check
+ env:
+ TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
- # - name: Check
- # if: always()
- # run: pnpm run check
-
- # - name: Format Check
- # if: always()
- # run: pnpm run format:ci
-
- # - name: Build
- # run: pnpm run build
-
- # - name: Test
- # run: pnpm run test
+ - name: Sync Languages
+ if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'
+ working-directory: ./libs/datasource/i18n
+ run: pnpm run sync-languages
+ env:
+ TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
diff --git a/.github/workflows/livedemo.yml b/.github/workflows/livedemo.yml
new file mode 100644
index 0000000000..d5ed5b9987
--- /dev/null
+++ b/.github/workflows/livedemo.yml
@@ -0,0 +1,87 @@
+name: Build AFFiNE-Local
+
+on:
+ push:
+ branches: [master]
+ # pull_request:
+ # branches: [master]
+
+# Cancels all previous workflow runs for pull requests that have not completed.
+# See https://docs.github.com/en/actions/using-jobs/using-concurrency
+concurrency:
+ # The concurrency group contains the workflow name and the branch name for
+ # pull requests or the commit hash for any other events.
+ group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }}
+ cancel-in-progress: true
+
+env:
+ REGISTRY: ghcr.io
+ NAMESPACE: toeverything
+ AFFINE_IMAGE_NAME: LIVEDEMO
+ IMAGE_TAG_LATEST: nightly-latest
+ LOCAL_CACHE: localhost:5000/toeverything/relocate:latest
+
+jobs:
+ ligo-virgo:
+ runs-on: self-hosted
+ environment: development
+ permissions:
+ contents: read
+ packages: write
+
+ services:
+ registry:
+ image: registry:2
+ ports:
+ - 5000:5000
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: Log in to the Container registry
+ uses: docker/login-action@v2
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v1
+
+ - name: Set up Docker Buildx
+ id: buildx
+ uses: docker/setup-buildx-action@v1
+ with:
+ driver-opts: network=host
+
+ - name: Extract metadata (tags, labels) for Docker (AFFiNE-Local)
+ id: meta_affine
+ uses: docker/metadata-action@v4
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.AFFINE_IMAGE_NAME }}
+ tags: ${{ env.IMAGE_TAG_LATEST }}
+
+ - name: Build and push Docker image (AFFINE-Local)
+ uses: docker/build-push-action@v3
+ env:
+ HUBSPOT_API_SECRET: ${{ secrets.SuperSecret }}
+ with:
+ context: .
+ push: true
+ file: ./.github/deployment/Dockerfile-affine
+ tags: ${{ env.LOCAL_CACHE }}
+ target: AFFiNE
+
+ - name: Build and push Docker image (AFFINE-Local)
+ uses: docker/build-push-action@v3
+ with:
+ context: .
+ platforms: linux/amd64,linux/arm64/v8
+ file: ./.github/deployment/Dockerfile-affine-multiplatform
+ push: ${{ github.ref == 'refs/heads/master' && true || false }}
+ tags: ${{ steps.meta_affine.outputs.tags }}
+ labels: ${{ steps.meta_affine.outputs.labels }}
+ target: AFFiNE
+ build-args: |
+ BASE_IMAGE=${{ env.LOCAL_CACHE }}
diff --git a/apps/ligo-virgo/src/template.html b/apps/ligo-virgo/src/template.html
index d0c4a84e0b..badce60082 100644
--- a/apps/ligo-virgo/src/template.html
+++ b/apps/ligo-virgo/src/template.html
@@ -4,6 +4,7 @@
<%= htmlWebpackPlugin.options.title %>
+ <%= htmlWebpackPlugin.options.hubspotScript %>
diff --git a/apps/ligo-virgo/webpack.config.js b/apps/ligo-virgo/webpack.config.js
index 11be2e1c76..377f32551a 100644
--- a/apps/ligo-virgo/webpack.config.js
+++ b/apps/ligo-virgo/webpack.config.js
@@ -12,6 +12,13 @@ const Style9Plugin = require('style9/webpack');
const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER;
+function generateHubspotScript() {
+ const hubspotApiSecret = process.env.HUBSPOT_API_SECRET;
+ if (hubspotApiSecret) {
+ return ``;
+ }
+}
+
module.exports = function (webpackConfig) {
const config = getNxWebpackConfig(webpackConfig);
@@ -175,6 +182,7 @@ module.exports = function (webpackConfig) {
), //favicon path
template: path.resolve(__dirname, './src/template.html'),
publicPath: '/',
+ hubspotScript: generateHubspotScript(),
}),
new Style9Plugin(),
isProd &&
diff --git a/apps/venus/src/app/IndexPage/Alternatives.tsx b/apps/venus/src/app/IndexPage/Alternatives.tsx
index 17403e01c9..4c7645a9d8 100644
--- a/apps/venus/src/app/IndexPage/Alternatives.tsx
+++ b/apps/venus/src/app/IndexPage/Alternatives.tsx
@@ -7,7 +7,7 @@ import { useMediaQuery } from '@mui/material';
const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
position: 'relative',
- width: '20em',
+ width: '24em',
height: '128px',
transform: 'translateY(-8px)',
overflowY: 'hidden',
@@ -21,7 +21,7 @@ const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
height: 'inherit',
position: 'absolute',
left: '0',
- top: '0',
+ top: '-23px',
paddingTop: '22px',
lineHeight: '96px',
'@media (max-width: 1024px)': {
@@ -105,7 +105,7 @@ export const AlternativesProduct = () => {
)}
>
{
}}
>
-
+
-
+
-
+
-
+
-
+
{
const changeLanguage = (event: any) => {
i18n.changeLanguage(event);
};
- const matchesIPAD = useMediaQuery('(max-width: 768px)');
+ const matchesIPAD = useMediaQuery('(max-width: 1080px)');
+ if (matchesIPAD) {
+ return ;
+ }
return (
@@ -160,6 +164,7 @@ const Container = styled(Grid)({
transform: 'translateX(-50%)',
width: '100%',
paddingTop: '1em',
+ paddingBottom: '1em',
backgroundColor: '#fff',
zIndex: 1500,
maxWidth: '1440px',
diff --git a/apps/venus/src/app/common/MobileHeader.tsx b/apps/venus/src/app/common/MobileHeader.tsx
new file mode 100644
index 0000000000..f55a793a45
--- /dev/null
+++ b/apps/venus/src/app/common/MobileHeader.tsx
@@ -0,0 +1,208 @@
+import Close from '@mui/icons-material/Close';
+import ExpandLess from '@mui/icons-material/ExpandLess';
+import ExpandMore from '@mui/icons-material/ExpandMore';
+import GitHubIcon from '@mui/icons-material/GitHub';
+import Menu from '@mui/icons-material/Menu';
+import { Button, Grid } from '@mui/joy';
+import { useRef, useState } from 'react';
+// eslint-disable-next-line no-restricted-imports
+import {
+ Checkbox,
+ Collapse,
+ Drawer,
+ List,
+ ListItemButton,
+ ListItemText,
+ styled,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
+import AFFiNETextLogo from './affine-text-logo.png';
+
+export const MobileHeader = () => {
+ const navigate = useNavigate();
+ const { i18n, t } = useTranslation();
+ const anchor = useRef(null);
+ const [openedDrawer, setDrawerState] = useState(false);
+ const handleDrawerOpenState = () => {
+ setDrawerState(!openedDrawer);
+ };
+ const [contactUsCollapse, setContactUsCollapse] = useState(false);
+ const handleContactUsClick = () => {
+ setContactUsCollapse(!contactUsCollapse);
+ };
+
+ const [languageCollapse, setLanguageCollapse] = useState(false);
+ const handleLanguageClick = () => {
+ setLanguageCollapse(!languageCollapse);
+ };
+
+ return (
+
+
+ navigate('/')}
+ />
+
+
+ window.open(
+ 'https://github.com/toeverything/AFFiNE'
+ )
+ }
+ >
+
+
+
+ {openedDrawer ? : }
+
+
+
+ setDrawerState(false)}
+ >
+
+
+ navigate('/aboutus')}>
+
+
+
+ window.open('https://blog.affine.pro')
+ }
+ >
+
+
+
+ window.open('https://docs.affine.pro/')
+ }
+ >
+
+
+
+ window.open('https://feedback.affine.pro/')
+ }
+ >
+
+
+
+
+ {contactUsCollapse ? (
+
+ ) : (
+
+ )}
+
+
+
+ window.open('https://discord.gg/Arn7TqJBvG')
+ }
+ >
+
+
+
+ window.open('https://t.me/affineworkos')
+ }
+ >
+
+
+
+ window.open(
+ 'https://www.reddit.com/r/Affine/'
+ )
+ }
+ >
+
+
+
+ window.open(
+ 'https://medium.com/@affineworkos'
+ )
+ }
+ >
+
+
+
+ window.open(
+ 'mailto:contact@toeverything.info'
+ )
+ }
+ >
+
+
+
+
+
+ {languageCollapse ? : }
+
+
+ i18n.changeLanguage('en')}
+ >
+
+
+
+ i18n.changeLanguage('zh')}
+ >
+
+
+
+
+
+
+
+
+ );
+};
+
+const Container = styled(Grid)({
+ position: 'fixed',
+ top: 0,
+ left: '50%',
+ transform: 'translateX(-50%)',
+ width: '100%',
+ paddingTop: '1em',
+ backgroundColor: '#fff',
+ zIndex: 1500,
+ maxWidth: '1440px',
+ margin: 'auto',
+ marginTop: '0 !important',
+});
+
+const StyledImage = styled('img')({
+ height: '24px',
+ marginRight: '16px',
+ cursor: 'pointer',
+});
+
+const StyledDrawerContainer = styled('div')({
+ paddingTop: '72px',
+});
diff --git a/apps/venus/src/app/i18n/resources/en.json b/apps/venus/src/app/i18n/resources/en.json
index 789a577584..80381df1d7 100644
--- a/apps/venus/src/app/i18n/resources/en.json
+++ b/apps/venus/src/app/i18n/resources/en.json
@@ -10,6 +10,7 @@
"Alternative": "Alternative",
"Check GitHub": "Check GitHub",
"Try it Online": "Try it Online",
+ "language": "Language",
"description1": {
"part1": "Affine is the next-generation collaborative knowledge base for professionals.",
"part2": "It's not just a collection of Docs, whiteboard, and tables.",
diff --git a/apps/venus/src/app/i18n/resources/zh.json b/apps/venus/src/app/i18n/resources/zh.json
index 9a3e51a2ca..545a644c28 100644
--- a/apps/venus/src/app/i18n/resources/zh.json
+++ b/apps/venus/src/app/i18n/resources/zh.json
@@ -10,6 +10,7 @@
"Alternative": "的另一种选择",
"Check GitHub": "GitHub中查看",
"Try it Online": "在线试用",
+ "language": "语言",
"description1": {
"part1": "Affine是面向专业人士的下一代协同知识库",
"part2": "它不仅仅是一个文档、白板和表格的集合。",
diff --git a/libs/datasource/i18n/README.md b/libs/datasource/i18n/README.md
index ff902b333c..c454be380a 100644
--- a/libs/datasource/i18n/README.md
+++ b/libs/datasource/i18n/README.md
@@ -8,7 +8,7 @@ import { useTranslation } from '@toeverything/datasource/i18n';
// base.json
// {
// 'Text': 'some text',
-// 'Switch to language': 'Switch to {language}',
+// 'Switch to language': 'Switch to {{language}}', // <- you can interpolation by curly brackets
// };
const App = () => {
@@ -41,10 +41,12 @@ const App = () => {
export TOLGEE_API_KEY=tgpak_XXXXXXX
```
-- Run the `sync` script from package script
+- Run the `sync-languages:check` to check all languages
+- Run the `sync-languages` script to add new keys to the tolgee platform
## References
+- [AFFiNE | Tolgee](https://i18n.affine.pro/)
+- [Tolgee Documentation](https://tolgee.io/docs/)
- [i18next](https://www.i18next.com/)
- [react-i18next](https://react.i18next.com/)
-- [Tolgee](https://tolgee.io/docs/)
diff --git a/libs/datasource/i18n/package.json b/libs/datasource/i18n/package.json
index 8f6afe9b85..eb1657865e 100644
--- a/libs/datasource/i18n/package.json
+++ b/libs/datasource/i18n/package.json
@@ -2,11 +2,11 @@
"name": "@toeverything/datasource/i18n",
"version": "0.0.1",
"scripts": {
- "sync": "NODE_OPTIONS=--experimental-fetch ts-node src/scripts/sync.ts"
+ "sync-languages": "NODE_OPTIONS=--experimental-fetch ts-node src/scripts/sync.ts",
+ "sync-languages:check": "pnpm run sync-languages --check"
},
"dependencies": {
"i18next": "^21.9.1",
- "jotai": "^1.8.1",
"react-i18next": "^11.18.4"
}
}
diff --git a/libs/datasource/i18n/src/index.ts b/libs/datasource/i18n/src/index.ts
index 187ed40aae..a421494bf8 100644
--- a/libs/datasource/i18n/src/index.ts
+++ b/libs/datasource/i18n/src/index.ts
@@ -19,6 +19,8 @@ declare module 'react-i18next' {
}
}
+const STORAGE_KEY = 'i18n_lng';
+
const LOCALES = [
{ value: 'en', text: 'English', res: en_US },
{ value: 'zh', text: '简体中文', res: zh_CN },
@@ -29,11 +31,26 @@ const resources = LOCALES.reduce(
{}
);
+const fallbackLng = LOCALES[0].value;
+const standardizeLocale = (language: string) => {
+ if (LOCALES.find(locale => locale.value === language)) return language;
+ if (
+ LOCALES.find(
+ locale => locale.value === language.slice(0, 2).toLowerCase()
+ )
+ )
+ return language;
+ return fallbackLng;
+};
+
+const language = standardizeLocale(
+ localStorage.getItem(STORAGE_KEY) ?? navigator.language
+);
+
const i18n = i18next.createInstance();
i18n.use(initReactI18next).init({
- // TODO auto detect
- lng: LOCALES[0].value,
- fallbackLng: LOCALES[0].value,
+ lng: language,
+ fallbackLng,
debug: process.env['NODE_ENV'] === 'development',
resources,
@@ -42,6 +59,10 @@ i18n.use(initReactI18next).init({
},
});
+i18n.on('languageChanged', lng => {
+ localStorage.setItem(STORAGE_KEY, lng);
+});
+
const I18nProvider = I18nextProvider;
export { i18n, useTranslation, I18nProvider, LOCALES };
diff --git a/libs/datasource/i18n/src/scripts/request.ts b/libs/datasource/i18n/src/scripts/request.ts
index 64e18bd9bd..c016909a24 100644
--- a/libs/datasource/i18n/src/scripts/request.ts
+++ b/libs/datasource/i18n/src/scripts/request.ts
@@ -9,6 +9,7 @@ if (!TOLGEE_API_KEY) {
const withTolgee = (
fetch: typeof globalThis.fetch
): typeof globalThis.fetch => {
+ const baseUrl = `${TOLGEE_API_URL}/v2/projects`;
const headers = new Headers({
'X-API-Key': TOLGEE_API_KEY,
'Content-Type': 'application/json',
@@ -29,14 +30,14 @@ const withTolgee = (
if (!argArray[0].headers) {
argArray[0] = {
...argArray[0],
- url: `${TOLGEE_API_URL}/v2/projects${argArray[0].url}`,
+ url: `${baseUrl}${argArray[0].url}`,
headers,
};
}
} else {
// URL or URLLike + ?RequestInit
if (typeof argArray[0] === 'string') {
- argArray[0] = TOLGEE_API_URL + argArray[0];
+ argArray[0] = `${baseUrl}${argArray[0]}`;
}
if (!argArray[1]) {
argArray[1] = {};
diff --git a/libs/datasource/i18n/src/scripts/sync.ts b/libs/datasource/i18n/src/scripts/sync.ts
index 5b264bf329..a71a0de61f 100644
--- a/libs/datasource/i18n/src/scripts/sync.ts
+++ b/libs/datasource/i18n/src/scripts/sync.ts
@@ -78,6 +78,41 @@ const differenceObject = (
return { add, remove, modify };
};
+function warnDiff(diff: { add: string[]; remove: string[]; modify: string[] }) {
+ if (diff.add.length) {
+ console.log('New keys found:', diff.add.join(', '));
+ //See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-notice-message
+ process.env['CI'] &&
+ console.log(
+ `::notice file=${BASE_JSON_PATH},line=1,title=New keys::${diff.add.join(
+ ', '
+ )}`
+ );
+ }
+ if (diff.remove.length) {
+ console.warn('[WARN]', 'Unused keys found:', diff.remove.join(', '));
+ process.env['CI'] &&
+ console.warn(
+ `::notice file=${BASE_JSON_PATH},line=1,title=Unused keys::${diff.remove.join(
+ ', '
+ )}`
+ );
+ }
+ if (diff.modify.length) {
+ console.warn(
+ '[WARN]',
+ 'Inconsistent keys found:',
+ diff.modify.join(', ')
+ );
+ process.env['CI'] &&
+ console.warn(
+ `::warning file=${BASE_JSON_PATH},line=1,title=Inconsistent keys::${diff.modify.join(
+ ', '
+ )}`
+ );
+ }
+}
+
const main = async () => {
console.log('Loading local base translations...');
const baseLocalTranslations = JSON.parse(
@@ -107,17 +142,14 @@ const main = async () => {
);
console.log(''); // new line
- diff.add.length && console.log('New keys found:', diff.add.join(', '));
- diff.remove.length &&
- console.warn('[WARN]', 'Unused keys found:', diff.remove.join(', '));
- diff.modify.length &&
- console.warn(
- '[WARN]',
- 'Inconsistent keys found:',
- diff.modify.join(', ')
- );
+ warnDiff(diff);
console.log(''); // new line
+ if (process.argv.slice(2).includes('--check')) {
+ // check mode
+ return;
+ }
+
diff.add.forEach(async key => {
const val = flatLocalTranslations[key];
console.log(`Creating new key: ${key} -> ${val}`);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 52c187aa40..164e93cf73 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -580,11 +580,9 @@ importers:
libs/datasource/i18n:
specifiers:
i18next: ^21.9.1
- jotai: ^1.8.1
react-i18next: ^11.18.4
dependencies:
i18next: 21.9.1
- jotai: 1.8.1
react-i18next: 11.18.4_i18next@21.9.1
libs/datasource/jwt: