Merge branch 'develop' into feat/command-search
This commit is contained in:
@@ -1,10 +1,17 @@
|
|||||||
name: Lint
|
name: Languages Sync
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [ "develop", "master" ]
|
||||||
# pull_request:
|
paths:
|
||||||
# branches: [master]
|
- '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.
|
# Cancels all previous workflow runs for pull requests that have not completed.
|
||||||
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
# See https://docs.github.com/en/actions/using-jobs/using-concurrency
|
||||||
@@ -18,11 +25,9 @@ jobs:
|
|||||||
main:
|
main:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [16]
|
node-version: [18]
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
# TODO Remove the next line after cleaning all errors
|
|
||||||
continue-on-error: true
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -43,20 +48,16 @@ jobs:
|
|||||||
- name: Install node modules
|
- name: Install node modules
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Lint
|
- name: Check Language Key
|
||||||
if: always()
|
if: github.ref != 'refs/heads/develop' && github.ref != 'refs/heads/master'
|
||||||
run: pnpm run lint:with-cache
|
working-directory: ./libs/datasource/i18n
|
||||||
|
run: pnpm run sync-languages:check
|
||||||
|
env:
|
||||||
|
TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
|
||||||
|
|
||||||
# - name: Check
|
- name: Sync Languages
|
||||||
# if: always()
|
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master'
|
||||||
# run: pnpm run check
|
working-directory: ./libs/datasource/i18n
|
||||||
|
run: pnpm run sync-languages
|
||||||
# - name: Format Check
|
env:
|
||||||
# if: always()
|
TOLGEE_API_KEY: ${{ secrets.TOLGEE_API_KEY }}
|
||||||
# run: pnpm run format:ci
|
|
||||||
|
|
||||||
# - name: Build
|
|
||||||
# run: pnpm run build
|
|
||||||
|
|
||||||
# - name: Test
|
|
||||||
# run: pnpm run test
|
|
||||||
87
.github/workflows/livedemo.yml
vendored
Normal file
87
.github/workflows/livedemo.yml
vendored
Normal file
@@ -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 }}
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
<%= htmlWebpackPlugin.options.hubspotScript %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ const Style9Plugin = require('style9/webpack');
|
|||||||
|
|
||||||
const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER;
|
const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER;
|
||||||
|
|
||||||
|
function generateHubspotScript() {
|
||||||
|
const hubspotApiSecret = process.env.HUBSPOT_API_SECRET;
|
||||||
|
if (hubspotApiSecret) {
|
||||||
|
return `<script type="text/javascript" id="hs-script-loader" async defer src="//js-na1.hs-scripts.com/${hubspotApiSecret}.js"></script>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = function (webpackConfig) {
|
module.exports = function (webpackConfig) {
|
||||||
const config = getNxWebpackConfig(webpackConfig);
|
const config = getNxWebpackConfig(webpackConfig);
|
||||||
|
|
||||||
@@ -175,6 +182,7 @@ module.exports = function (webpackConfig) {
|
|||||||
), //favicon path
|
), //favicon path
|
||||||
template: path.resolve(__dirname, './src/template.html'),
|
template: path.resolve(__dirname, './src/template.html'),
|
||||||
publicPath: '/',
|
publicPath: '/',
|
||||||
|
hubspotScript: generateHubspotScript(),
|
||||||
}),
|
}),
|
||||||
new Style9Plugin(),
|
new Style9Plugin(),
|
||||||
isProd &&
|
isProd &&
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useMediaQuery } from '@mui/material';
|
|||||||
|
|
||||||
const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
|
const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
width: '20em',
|
width: '24em',
|
||||||
height: '128px',
|
height: '128px',
|
||||||
transform: 'translateY(-8px)',
|
transform: 'translateY(-8px)',
|
||||||
overflowY: 'hidden',
|
overflowY: 'hidden',
|
||||||
@@ -21,7 +21,7 @@ const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({
|
|||||||
height: 'inherit',
|
height: 'inherit',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '0',
|
left: '0',
|
||||||
top: '0',
|
top: '-23px',
|
||||||
paddingTop: '22px',
|
paddingTop: '22px',
|
||||||
lineHeight: '96px',
|
lineHeight: '96px',
|
||||||
'@media (max-width: 1024px)': {
|
'@media (max-width: 1024px)': {
|
||||||
@@ -105,7 +105,7 @@ export const AlternativesProduct = () => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="64px"
|
fontSize="96px"
|
||||||
fontWeight={900}
|
fontWeight={900}
|
||||||
sx={{
|
sx={{
|
||||||
color: '#06449d',
|
color: '#06449d',
|
||||||
@@ -135,7 +135,7 @@ export const AlternativesProduct = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="64px"
|
fontSize="96px"
|
||||||
fontWeight={900}
|
fontWeight={900}
|
||||||
sx={{
|
sx={{
|
||||||
color: '#06449d',
|
color: '#06449d',
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function IndexPage() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AFFiNEHeader />
|
<AFFiNEHeader />
|
||||||
<Grid xs={12} sx={{ display: 'flex', marginTop: '12vh!important' }}>
|
<Grid xs={12} sx={{ display: 'flex', marginTop: '20vh!important' }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'inline-flex',
|
display: 'inline-flex',
|
||||||
@@ -76,7 +76,7 @@ export function IndexPage() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="64px"
|
fontSize="96px"
|
||||||
fontWeight={900}
|
fontWeight={900}
|
||||||
sx={{
|
sx={{
|
||||||
marginRight: '0.25em',
|
marginRight: '0.25em',
|
||||||
@@ -89,7 +89,7 @@ export function IndexPage() {
|
|||||||
{t('Open Source')},
|
{t('Open Source')},
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="64px"
|
fontSize="96px"
|
||||||
fontWeight={900}
|
fontWeight={900}
|
||||||
sx={{
|
sx={{
|
||||||
'@media (max-width: 1024px)': {
|
'@media (max-width: 1024px)': {
|
||||||
@@ -120,7 +120,7 @@ export function IndexPage() {
|
|||||||
>
|
>
|
||||||
<AlternativesProduct />
|
<AlternativesProduct />
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="64px"
|
fontSize="96px"
|
||||||
fontWeight={900}
|
fontWeight={900}
|
||||||
sx={{
|
sx={{
|
||||||
color: '#06449d',
|
color: '#06449d',
|
||||||
|
|||||||
@@ -90,13 +90,14 @@ export const AFFiNEFooter = ({
|
|||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
maxWidth: '400px',
|
flexWrap: 'wrap',
|
||||||
|
maxWidth: '420px',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
marginBottom: '2em',
|
marginBottom: '2em',
|
||||||
'--joy-shadow-sm': 0,
|
'--joy-shadow-sm': 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '84px' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -144,7 +145,7 @@ export const AFFiNEFooter = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '84px' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -190,7 +191,7 @@ export const AFFiNEFooter = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '84px' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -239,7 +240,7 @@ export const AFFiNEFooter = ({
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
width: '100%',
|
width: '84px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
@@ -285,7 +286,7 @@ export const AFFiNEFooter = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: 'flex', width: '100%' }}>
|
<Box sx={{ display: 'flex', width: '84px' }}>
|
||||||
<Button
|
<Button
|
||||||
variant="plain"
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import GitHubIcon from '@mui/icons-material/GitHub';
|
|||||||
import { styled, useMediaQuery } from '@mui/material';
|
import { styled, useMediaQuery } from '@mui/material';
|
||||||
import AFFiNETextLogo from './affine-text-logo.png';
|
import AFFiNETextLogo from './affine-text-logo.png';
|
||||||
import { HoverMenu } from './HoverMenu';
|
import { HoverMenu } from './HoverMenu';
|
||||||
|
import { MobileHeader } from './MobileHeader';
|
||||||
|
|
||||||
import { options } from '../i18n';
|
import { options } from '../i18n';
|
||||||
|
|
||||||
@@ -22,7 +23,10 @@ export const AFFiNEHeader = () => {
|
|||||||
const changeLanguage = (event: any) => {
|
const changeLanguage = (event: any) => {
|
||||||
i18n.changeLanguage(event);
|
i18n.changeLanguage(event);
|
||||||
};
|
};
|
||||||
const matchesIPAD = useMediaQuery('(max-width: 768px)');
|
const matchesIPAD = useMediaQuery('(max-width: 1080px)');
|
||||||
|
if (matchesIPAD) {
|
||||||
|
return <MobileHeader />;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Container container spacing={2}>
|
<Container container spacing={2}>
|
||||||
<Grid xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
|
<Grid xs={6} sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
@@ -160,6 +164,7 @@ const Container = styled(Grid)({
|
|||||||
transform: 'translateX(-50%)',
|
transform: 'translateX(-50%)',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
paddingTop: '1em',
|
paddingTop: '1em',
|
||||||
|
paddingBottom: '1em',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
zIndex: 1500,
|
zIndex: 1500,
|
||||||
maxWidth: '1440px',
|
maxWidth: '1440px',
|
||||||
|
|||||||
208
apps/venus/src/app/common/MobileHeader.tsx
Normal file
208
apps/venus/src/app/common/MobileHeader.tsx
Normal file
@@ -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<HTMLDivElement | null>(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 (
|
||||||
|
<Container ref={anchor} container spacing={2}>
|
||||||
|
<Grid
|
||||||
|
xs={12}
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StyledImage
|
||||||
|
src={AFFiNETextLogo}
|
||||||
|
alt="affine"
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
'https://github.com/toeverything/AFFiNE'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<GitHubIcon />
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleDrawerOpenState} variant="plain">
|
||||||
|
{openedDrawer ? <Close /> : <Menu />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
<Drawer
|
||||||
|
anchor="top"
|
||||||
|
open={openedDrawer}
|
||||||
|
onClose={() => setDrawerState(false)}
|
||||||
|
>
|
||||||
|
<StyledDrawerContainer>
|
||||||
|
<List>
|
||||||
|
<ListItemButton onClick={() => navigate('/aboutus')}>
|
||||||
|
<ListItemText primary={t('AboutUs') as string} />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://blog.affine.pro')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary={t('Blog') as string} />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://docs.affine.pro/')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary={t('Docs') as string} />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://feedback.affine.pro/')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary={t('Feedback') as string} />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton onClick={handleContactUsClick}>
|
||||||
|
<ListItemText primary={t('ContactUs') as string} />
|
||||||
|
{contactUsCollapse ? (
|
||||||
|
<ExpandLess />
|
||||||
|
) : (
|
||||||
|
<ExpandMore />
|
||||||
|
)}
|
||||||
|
</ListItemButton>
|
||||||
|
<Collapse
|
||||||
|
in={contactUsCollapse}
|
||||||
|
timeout="auto"
|
||||||
|
unmountOnExit
|
||||||
|
>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://discord.gg/Arn7TqJBvG')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary="Discord" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open('https://t.me/affineworkos')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary="Telegram" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
'https://www.reddit.com/r/Affine/'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary="Reddit" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
'https://medium.com/@affineworkos'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary="Medium" />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() =>
|
||||||
|
window.open(
|
||||||
|
'mailto:contact@toeverything.info'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ListItemText primary="Email" />
|
||||||
|
</ListItemButton>
|
||||||
|
</Collapse>
|
||||||
|
<ListItemButton onClick={handleLanguageClick}>
|
||||||
|
<ListItemText primary={t('language') as string} />
|
||||||
|
{languageCollapse ? <ExpandLess /> : <ExpandMore />}
|
||||||
|
</ListItemButton>
|
||||||
|
<Collapse
|
||||||
|
in={languageCollapse}
|
||||||
|
timeout="auto"
|
||||||
|
unmountOnExit
|
||||||
|
>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => i18n.changeLanguage('en')}
|
||||||
|
>
|
||||||
|
<ListItemText primary="English" />
|
||||||
|
<Checkbox checked={i18n.language === 'en'} />
|
||||||
|
</ListItemButton>
|
||||||
|
<ListItemButton
|
||||||
|
onClick={() => i18n.changeLanguage('zh')}
|
||||||
|
>
|
||||||
|
<ListItemText primary="简体中文" />
|
||||||
|
<Checkbox checked={i18n.language === 'zh'} />
|
||||||
|
</ListItemButton>
|
||||||
|
</Collapse>
|
||||||
|
</List>
|
||||||
|
</StyledDrawerContainer>
|
||||||
|
</Drawer>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
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',
|
||||||
|
});
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"Alternative": "Alternative",
|
"Alternative": "Alternative",
|
||||||
"Check GitHub": "Check GitHub",
|
"Check GitHub": "Check GitHub",
|
||||||
"Try it Online": "Try it Online",
|
"Try it Online": "Try it Online",
|
||||||
|
"language": "Language",
|
||||||
"description1": {
|
"description1": {
|
||||||
"part1": "Affine is the next-generation collaborative knowledge base for professionals.",
|
"part1": "Affine is the next-generation collaborative knowledge base for professionals.",
|
||||||
"part2": "It's not just a collection of Docs, whiteboard, and tables.",
|
"part2": "It's not just a collection of Docs, whiteboard, and tables.",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"Alternative": "的另一种选择",
|
"Alternative": "的另一种选择",
|
||||||
"Check GitHub": "GitHub中查看",
|
"Check GitHub": "GitHub中查看",
|
||||||
"Try it Online": "在线试用",
|
"Try it Online": "在线试用",
|
||||||
|
"language": "语言",
|
||||||
"description1": {
|
"description1": {
|
||||||
"part1": "Affine是面向专业人士的下一代协同知识库",
|
"part1": "Affine是面向专业人士的下一代协同知识库",
|
||||||
"part2": "它不仅仅是一个文档、白板和表格的集合。",
|
"part2": "它不仅仅是一个文档、白板和表格的集合。",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { useTranslation } from '@toeverything/datasource/i18n';
|
|||||||
// base.json
|
// base.json
|
||||||
// {
|
// {
|
||||||
// 'Text': 'some text',
|
// 'Text': 'some text',
|
||||||
// 'Switch to language': 'Switch to {language}',
|
// 'Switch to language': 'Switch to {{language}}', // <- you can interpolation by curly brackets
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
@@ -41,10 +41,12 @@ const App = () => {
|
|||||||
export TOLGEE_API_KEY=tgpak_XXXXXXX
|
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
|
## References
|
||||||
|
|
||||||
|
- [AFFiNE | Tolgee](https://i18n.affine.pro/)
|
||||||
|
- [Tolgee Documentation](https://tolgee.io/docs/)
|
||||||
- [i18next](https://www.i18next.com/)
|
- [i18next](https://www.i18next.com/)
|
||||||
- [react-i18next](https://react.i18next.com/)
|
- [react-i18next](https://react.i18next.com/)
|
||||||
- [Tolgee](https://tolgee.io/docs/)
|
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
"name": "@toeverything/datasource/i18n",
|
"name": "@toeverything/datasource/i18n",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"scripts": {
|
"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": {
|
"dependencies": {
|
||||||
"i18next": "^21.9.1",
|
"i18next": "^21.9.1",
|
||||||
"jotai": "^1.8.1",
|
|
||||||
"react-i18next": "^11.18.4"
|
"react-i18next": "^11.18.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ declare module 'react-i18next' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STORAGE_KEY = 'i18n_lng';
|
||||||
|
|
||||||
const LOCALES = [
|
const LOCALES = [
|
||||||
{ value: 'en', text: 'English', res: en_US },
|
{ value: 'en', text: 'English', res: en_US },
|
||||||
{ value: 'zh', text: '简体中文', res: zh_CN },
|
{ value: 'zh', text: '简体中文', res: zh_CN },
|
||||||
@@ -29,11 +31,26 @@ const resources = LOCALES.reduce<Resource>(
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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();
|
const i18n = i18next.createInstance();
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
// TODO auto detect
|
lng: language,
|
||||||
lng: LOCALES[0].value,
|
fallbackLng,
|
||||||
fallbackLng: LOCALES[0].value,
|
|
||||||
debug: process.env['NODE_ENV'] === 'development',
|
debug: process.env['NODE_ENV'] === 'development',
|
||||||
|
|
||||||
resources,
|
resources,
|
||||||
@@ -42,6 +59,10 @@ i18n.use(initReactI18next).init({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
i18n.on('languageChanged', lng => {
|
||||||
|
localStorage.setItem(STORAGE_KEY, lng);
|
||||||
|
});
|
||||||
|
|
||||||
const I18nProvider = I18nextProvider;
|
const I18nProvider = I18nextProvider;
|
||||||
|
|
||||||
export { i18n, useTranslation, I18nProvider, LOCALES };
|
export { i18n, useTranslation, I18nProvider, LOCALES };
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ if (!TOLGEE_API_KEY) {
|
|||||||
const withTolgee = (
|
const withTolgee = (
|
||||||
fetch: typeof globalThis.fetch
|
fetch: typeof globalThis.fetch
|
||||||
): typeof globalThis.fetch => {
|
): typeof globalThis.fetch => {
|
||||||
|
const baseUrl = `${TOLGEE_API_URL}/v2/projects`;
|
||||||
const headers = new Headers({
|
const headers = new Headers({
|
||||||
'X-API-Key': TOLGEE_API_KEY,
|
'X-API-Key': TOLGEE_API_KEY,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -29,14 +30,14 @@ const withTolgee = (
|
|||||||
if (!argArray[0].headers) {
|
if (!argArray[0].headers) {
|
||||||
argArray[0] = {
|
argArray[0] = {
|
||||||
...argArray[0],
|
...argArray[0],
|
||||||
url: `${TOLGEE_API_URL}/v2/projects${argArray[0].url}`,
|
url: `${baseUrl}${argArray[0].url}`,
|
||||||
headers,
|
headers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// URL or URLLike + ?RequestInit
|
// URL or URLLike + ?RequestInit
|
||||||
if (typeof argArray[0] === 'string') {
|
if (typeof argArray[0] === 'string') {
|
||||||
argArray[0] = TOLGEE_API_URL + argArray[0];
|
argArray[0] = `${baseUrl}${argArray[0]}`;
|
||||||
}
|
}
|
||||||
if (!argArray[1]) {
|
if (!argArray[1]) {
|
||||||
argArray[1] = {};
|
argArray[1] = {};
|
||||||
|
|||||||
@@ -78,6 +78,41 @@ const differenceObject = (
|
|||||||
return { add, remove, modify };
|
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 () => {
|
const main = async () => {
|
||||||
console.log('Loading local base translations...');
|
console.log('Loading local base translations...');
|
||||||
const baseLocalTranslations = JSON.parse(
|
const baseLocalTranslations = JSON.parse(
|
||||||
@@ -107,17 +142,14 @@ const main = async () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
console.log(''); // new line
|
console.log(''); // new line
|
||||||
diff.add.length && console.log('New keys found:', diff.add.join(', '));
|
warnDiff(diff);
|
||||||
diff.remove.length &&
|
|
||||||
console.warn('[WARN]', 'Unused keys found:', diff.remove.join(', '));
|
|
||||||
diff.modify.length &&
|
|
||||||
console.warn(
|
|
||||||
'[WARN]',
|
|
||||||
'Inconsistent keys found:',
|
|
||||||
diff.modify.join(', ')
|
|
||||||
);
|
|
||||||
console.log(''); // new line
|
console.log(''); // new line
|
||||||
|
|
||||||
|
if (process.argv.slice(2).includes('--check')) {
|
||||||
|
// check mode
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
diff.add.forEach(async key => {
|
diff.add.forEach(async key => {
|
||||||
const val = flatLocalTranslations[key];
|
const val = flatLocalTranslations[key];
|
||||||
console.log(`Creating new key: ${key} -> ${val}`);
|
console.log(`Creating new key: ${key} -> ${val}`);
|
||||||
|
|||||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -580,11 +580,9 @@ importers:
|
|||||||
libs/datasource/i18n:
|
libs/datasource/i18n:
|
||||||
specifiers:
|
specifiers:
|
||||||
i18next: ^21.9.1
|
i18next: ^21.9.1
|
||||||
jotai: ^1.8.1
|
|
||||||
react-i18next: ^11.18.4
|
react-i18next: ^11.18.4
|
||||||
dependencies:
|
dependencies:
|
||||||
i18next: 21.9.1
|
i18next: 21.9.1
|
||||||
jotai: 1.8.1
|
|
||||||
react-i18next: 11.18.4_i18next@21.9.1
|
react-i18next: 11.18.4_i18next@21.9.1
|
||||||
|
|
||||||
libs/datasource/jwt:
|
libs/datasource/jwt:
|
||||||
|
|||||||
Reference in New Issue
Block a user