mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
Merge remote-tracking branch 'origin/master' into feat/cloud-sync-saika
This commit is contained in:
@@ -8,7 +8,7 @@ import {
|
||||
TelegramIcon,
|
||||
RedditIcon,
|
||||
LinkIcon,
|
||||
} from './icons';
|
||||
} from './Icons';
|
||||
import logo from './affine-text-logo.png';
|
||||
import {
|
||||
StyledBigLink,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
ConnectorIcon,
|
||||
UndoIcon,
|
||||
RedoIcon,
|
||||
} from './icons';
|
||||
} from './Icons';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
|
||||
@@ -11,8 +11,8 @@ import type {
|
||||
AnimateRadioProps,
|
||||
AnimateRadioItemProps,
|
||||
} from './type';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { EdgelessIcon, PaperIcon } from './icons';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import { EdgelessIcon, PaperIcon } from './Icons';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
import { Content } from '@/ui/layout';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import EditorModeSwitch from '@/components/editor-mode-switch';
|
||||
import QuickSearchButton from './quick-search-button';
|
||||
import Header from './header';
|
||||
import QuickSearchButton from './QuickSearchButton';
|
||||
import Header from './Header';
|
||||
import usePropsUpdated from '@/hooks/use-props-updated';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
|
||||
@@ -8,10 +8,10 @@ import {
|
||||
} from './styles';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import { getWarningMessage, shouldShowWarning } from './utils';
|
||||
import EditorOptionMenu from './header-right-items/editor-option-menu';
|
||||
import TrashButtonGroup from './header-right-items/trash-button-group';
|
||||
import EditorOptionMenu from './header-right-items/EditorOptionMenu';
|
||||
import TrashButtonGroup from './header-right-items/TrashButtonGroup';
|
||||
import ThemeModeSwitch from './header-right-items/theme-mode-switch';
|
||||
import SyncUser from './header-right-items/sync-user';
|
||||
import SyncUser from './header-right-items/SyncUser';
|
||||
|
||||
const BrowserWarning = ({
|
||||
show,
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import Header from './header';
|
||||
import Header from './Header';
|
||||
import { StyledPageListTittleWrapper } from './styles';
|
||||
import QuickSearchButton from './quick-search-button';
|
||||
import QuickSearchButton from './QuickSearchButton';
|
||||
|
||||
export type PageListHeaderProps = PropsWithChildren<{
|
||||
icon?: ReactNode;
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { IconButton, IconButtonProps } from '@/ui/button';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
export const QuickSearchButton = ({
|
||||
onClick,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from '@blocksuite/icons';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { IconButton } from '@/ui/button';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Button } from '@/ui/button';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { useRouter } from 'next/router';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from 'react';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { MoonIcon, SunIcon } from './icons';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import { MoonIcon, SunIcon } from './Icons';
|
||||
import { StyledThemeModeSwitch, StyledSwitchItem } from './style';
|
||||
|
||||
export const ThemeModeSwitch = () => {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export * from './header';
|
||||
export * from './editor-header';
|
||||
export * from './page-list-header';
|
||||
export * from './Header';
|
||||
export * from './EditorHeader';
|
||||
export * from './PageListHeader';
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
StyledIslandWrapper,
|
||||
StyledTransformIcon,
|
||||
} from './style';
|
||||
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons';
|
||||
import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons';
|
||||
import Grow from '@mui/material/Grow';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import useCurrentPageMeta from '@/hooks/use-current-page-meta';
|
||||
export type IslandItemNames = 'contact' | 'shortcuts';
|
||||
export const HelpIsland = ({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@/styles';
|
||||
import Loading from './loading';
|
||||
import Loading from './Loading';
|
||||
|
||||
// Used for the full page loading
|
||||
const StyledLoadingContainer = styled('div')(() => {
|
||||
@@ -1,3 +1,3 @@
|
||||
import Loading from './loading';
|
||||
export * from './page-loading';
|
||||
import Loading from './Loading';
|
||||
export * from './PageLoading';
|
||||
export default Loading;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getDataCenter } from '@affine/datacenter';
|
||||
import { styled } from '@/styles';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { GoogleIcon, StayLogOutIcon } from './icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { GoogleIcon, StayLogOutIcon } from './Icons';
|
||||
|
||||
export const GoogleLoginButton = () => {
|
||||
const { triggerLoginModal } = useModal();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useConfirm } from '@/providers/confirm-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { PageMeta } from '@/providers/app-state-provider';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
@@ -12,18 +12,18 @@ import {
|
||||
StyledTitleWrapper,
|
||||
} from './styles';
|
||||
import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table';
|
||||
import { OperationCell, TrashOperationCell } from './operation-cell';
|
||||
import Empty from './empty';
|
||||
import { OperationCell, TrashOperationCell } from './OperationCell';
|
||||
import Empty from './Empty';
|
||||
import { Content } from '@/ui/layout';
|
||||
import React from 'react';
|
||||
import DateCell from '@/components/page-list/date-cell';
|
||||
import DateCell from '@/components/page-list/DateCell';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useTheme } from '@/providers/themeProvider';
|
||||
import { useTheme } from '@/providers/ThemeProvider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
const FavoriteTag = ({
|
||||
pageMeta: { favorite, id },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { AddIcon } from '@blocksuite/icons';
|
||||
import { StyledModalFooterContent } from './style';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { Command } from 'cmdk';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Command } from 'cmdk';
|
||||
import { StyledListItem, StyledNotFound } from './style';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { PaperIcon, EdgelessIcon } from '@blocksuite/icons';
|
||||
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useSwitchToConfig } from './config';
|
||||
import { NoResultSVG } from './noResultSVG';
|
||||
import { NoResultSVG } from './NoResultSVG';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import usePageHelper from '@/hooks/use-page-helper';
|
||||
import usePageMetaList from '@/hooks/use-page-meta-list';
|
||||
@@ -6,12 +6,12 @@ import {
|
||||
StyledModalDivider,
|
||||
StyledShortcut,
|
||||
} from './style';
|
||||
import { Input } from './input';
|
||||
import { Results } from './results';
|
||||
import { Footer } from './footer';
|
||||
import { Input } from './Input';
|
||||
import { Results } from './Results';
|
||||
import { Footer } from './Footer';
|
||||
import { Command } from 'cmdk';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
type TransitionsModalProps = {
|
||||
|
||||
@@ -13,7 +13,8 @@ export const useMacKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t('Strikethrough')]: '⌘+⇧+S',
|
||||
[t('Inline code')]: ' ⌘+E',
|
||||
[t('Code block')]: '⌘+⌥+C',
|
||||
[t('Link')]: '⌘+K',
|
||||
[t('Hyperlink(with selected text)')]: '⌘+K',
|
||||
[t('Quick search')]: '⌘+K',
|
||||
[t('Body text')]: '⌘+⌥+0',
|
||||
[t('Heading', { number: '1' })]: '⌘+⌥+1',
|
||||
[t('Heading', { number: '2' })]: '⌘+⌥+2',
|
||||
@@ -56,7 +57,8 @@ export const useWindowsKeyboardShortcuts = (): ShortcutTip => {
|
||||
[t('Strikethrough')]: 'Ctrl+Shift+S',
|
||||
[t('Inline code')]: ' Ctrl+E',
|
||||
[t('Code block')]: 'Ctrl+Alt+C',
|
||||
[t('Link')]: 'Ctrl+K',
|
||||
[t('Hyperlink(with selected text)')]: 'Ctrl+K',
|
||||
[t('Quick search')]: 'Ctrl+K',
|
||||
[t('Body text')]: 'Ctrl+Shift+0',
|
||||
[t('Heading', { number: '1' })]: 'Ctrl+Shift+1',
|
||||
[t('Heading', { number: '2' })]: 'Ctrl+Shift+2',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createPortal } from 'react-dom';
|
||||
import { KeyboardIcon } from './icons';
|
||||
import { KeyboardIcon } from './Icons';
|
||||
import {
|
||||
StyledListItem,
|
||||
StyledModalHeader,
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './delete';
|
||||
export * from './Delete';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './general';
|
||||
export * from './General';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './leave';
|
||||
export * from './Leave';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './workspace-setting';
|
||||
export * from './WorkspaceSetting';
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { WorkspaceSetting } from '@/components/workspace-setting';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { getDataCenter, WorkspaceType } from '@affine/datacenter';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
|
||||
export type WorkspaceDetails = Record<
|
||||
string,
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './workspace-create';
|
||||
export * from './WorkspaceCreate';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { styled } from '@/styles';
|
||||
import { AffineIcon } from '../../icons/icons';
|
||||
import { AffineIcon } from '../../icons/Icons';
|
||||
import {
|
||||
WorkspaceItemAvatar,
|
||||
LoginItemWrapper,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { SelectorPopperContent } from './SelectorPopperContent';
|
||||
import { useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceType } from '@affine/datacenter';
|
||||
import { AffineIcon } from '../icons/icons';
|
||||
import { AffineIcon } from '../icons/Icons';
|
||||
|
||||
export const WorkspaceSelector = () => {
|
||||
const [isShow, setIsShow] = useState(false);
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
} from '@blocksuite/icons';
|
||||
import Link from 'next/link';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useModal } from '@/providers/global-modal-provider';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useAppState } from '@/providers/app-state-provider/context';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import useLocalStorage from '@/hooks/use-local-storage';
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
"Strikethrough": "Strikethrough",
|
||||
"Inline code": "Inline code",
|
||||
"Code block": "Code block",
|
||||
"Link": "Link",
|
||||
"Hyperlink(with selected text)": "Hyperlink(with selected text)",
|
||||
"Body text": "Body text",
|
||||
"Heading": "Heading {{number}}",
|
||||
"Increase indent": "Increase indent",
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
// cSpell:ignore Tolgee
|
||||
import { fetchTolgee } from './request';
|
||||
|
||||
/**
|
||||
* Returns all project languages
|
||||
*
|
||||
* See https://tolgee.io/api#operation/getAll_6
|
||||
* @example
|
||||
* ```ts
|
||||
* const languages = [
|
||||
* {
|
||||
* id: 1000016008,
|
||||
* name: 'English',
|
||||
* tag: 'en',
|
||||
* originalName: 'English',
|
||||
* flagEmoji: '🇬🇧',
|
||||
* base: true
|
||||
* },
|
||||
* {
|
||||
* id: 1000016013,
|
||||
* name: 'Spanish',
|
||||
* tag: 'es',
|
||||
* originalName: 'español',
|
||||
* flagEmoji: '🇪🇸',
|
||||
* base: false
|
||||
* },
|
||||
* {
|
||||
* id: 1000016009,
|
||||
* name: 'Simplified Chinese',
|
||||
* tag: 'zh-Hans',
|
||||
* originalName: '简体中文',
|
||||
* flagEmoji: '🇨🇳',
|
||||
* base: false
|
||||
* },
|
||||
* {
|
||||
* id: 1000016012,
|
||||
* name: 'Traditional Chinese',
|
||||
* tag: 'zh-Hant',
|
||||
* originalName: '繁體中文',
|
||||
* flagEmoji: '🇭🇰',
|
||||
* base: false
|
||||
* }
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
export const getAllProjectLanguages = async (size = 1000) => {
|
||||
const url = `/languages?size=${size}`;
|
||||
const resp = await fetchTolgee(url);
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(url + ' ' + resp.status + '\n' + (await resp.text()));
|
||||
}
|
||||
const json: {
|
||||
_embedded: {
|
||||
languages: {
|
||||
id: number;
|
||||
name: string;
|
||||
tag: string;
|
||||
originalName: string;
|
||||
flagEmoji: string;
|
||||
base: boolean;
|
||||
}[];
|
||||
};
|
||||
page: unknown;
|
||||
} = await resp.json();
|
||||
return json._embedded.languages;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns translations in project
|
||||
*
|
||||
* See https://tolgee.io/api#operation/getTranslations_
|
||||
*/
|
||||
export const getTranslations = async () => {
|
||||
const url = '/translations';
|
||||
const resp = await fetchTolgee(url);
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(url + ' ' + resp.status + '\n' + (await resp.text()));
|
||||
}
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all translations for specified languages
|
||||
*
|
||||
* See https://tolgee.io/api#operation/getAllTranslations_1
|
||||
*/
|
||||
export const getLanguagesTranslations = async <T extends string>(
|
||||
languages: T
|
||||
) => {
|
||||
const url = `/translations/${languages}`;
|
||||
const resp = await fetchTolgee(url);
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(url + ' ' + resp.status + '\n' + (await resp.text()));
|
||||
}
|
||||
const json: { [key in T]?: Record<string, string> } = await resp.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
export const getRemoteTranslations = async (languages: string) => {
|
||||
const translations = await getLanguagesTranslations(languages);
|
||||
if (!(languages in translations)) {
|
||||
return {};
|
||||
}
|
||||
// The assert is safe because we checked above
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return translations[languages]!;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates new key
|
||||
*
|
||||
* See https://tolgee.io/api#operation/create_2
|
||||
*/
|
||||
export const createsNewKey = async (
|
||||
key: string,
|
||||
translations: Record<string, string>
|
||||
) => {
|
||||
const url = '/translations/keys/create';
|
||||
const resp = await fetchTolgee(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ name: key, translations }),
|
||||
});
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(url + ' ' + resp.status + '\n' + (await resp.text()));
|
||||
}
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tags a key with tag. If tag with provided name doesn't exist, it is created
|
||||
*
|
||||
* See https://tolgee.io/api#operation/tagKey_1
|
||||
*/
|
||||
export const addTag = async (keyId: string, tagName: string) => {
|
||||
const url = `/keys/${keyId}/tags`;
|
||||
const resp = await fetchTolgee(url, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ name: tagName }),
|
||||
});
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(url + ' ' + resp.status + '\n' + (await resp.text()));
|
||||
}
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
/**
|
||||
* Tags a key with tag. If tag with provided name doesn't exist, it is created
|
||||
*
|
||||
* See https://tolgee.io/api#operation/tagKey_1
|
||||
*/
|
||||
export const removeTag = async (keyId: string, tagId: number) => {
|
||||
const url = `/keys/${keyId}/tags/${tagId}`;
|
||||
const resp = await fetchTolgee(url, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(url + ' ' + resp.status + '\n' + (await resp.text()));
|
||||
}
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
};
|
||||
|
||||
// export const addTagByKey = async (key: string, tag: string) => {
|
||||
// // TODO get key id by key name
|
||||
// // const keyId =
|
||||
// // addTag(keyId, tag);
|
||||
// };
|
||||
|
||||
/**
|
||||
* Exports data
|
||||
*
|
||||
* See https://tolgee.io/api#operation/export_1
|
||||
*/
|
||||
export const exportResources = async () => {
|
||||
const url = `/export`;
|
||||
const resp = await fetchTolgee(url);
|
||||
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error(url + ' ' + resp.status + '\n' + (await resp.text()));
|
||||
}
|
||||
return resp;
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
// cSpell:ignore Tolgee
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { format } from 'prettier';
|
||||
import { getAllProjectLanguages, getRemoteTranslations } from './api';
|
||||
import type { TranslationRes } from './utils';
|
||||
|
||||
const RES_DIR = path.resolve(process.cwd(), 'src', 'resources');
|
||||
|
||||
const countKeys = (obj: TranslationRes) => {
|
||||
let count = 0;
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
Object.entries(obj).forEach(([_, value]) => {
|
||||
if (typeof value === 'string') {
|
||||
count++;
|
||||
} else {
|
||||
count += countKeys(value);
|
||||
}
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
const getBaseTranslations = async (baseLanguage: { tag: string }) => {
|
||||
try {
|
||||
const baseTranslationsStr = await fs.readFile(
|
||||
path.resolve(RES_DIR, `${baseLanguage.tag}.json`),
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
const baseTranslations = JSON.parse(baseTranslationsStr);
|
||||
return baseTranslations;
|
||||
} catch (e) {
|
||||
console.error('base language:', JSON.stringify(baseLanguage));
|
||||
console.error('Failed to read base language', e);
|
||||
const translations = await getRemoteTranslations(baseLanguage.tag);
|
||||
await fs.writeFile(
|
||||
path.resolve(RES_DIR, `${baseLanguage.tag}.json`),
|
||||
JSON.stringify(translations, null, 4)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
console.log('Loading project languages...');
|
||||
const languages = await getAllProjectLanguages();
|
||||
const baseLanguage = languages.find(language => language.base);
|
||||
if (!baseLanguage) {
|
||||
console.error(JSON.stringify(languages));
|
||||
throw new Error('Could not find base language');
|
||||
}
|
||||
console.log(`Loading ${baseLanguage.tag} languages translations as base...`);
|
||||
|
||||
const baseTranslations = await getBaseTranslations(baseLanguage);
|
||||
const baseKeyNum = countKeys(baseTranslations);
|
||||
const languagesWithTranslations = await Promise.all(
|
||||
languages.map(async language => {
|
||||
console.log(`Loading ${language.tag} translations...`);
|
||||
const translations = await getRemoteTranslations(language.tag);
|
||||
const keyNum = countKeys(translations);
|
||||
|
||||
return {
|
||||
...language,
|
||||
translations,
|
||||
completeRate: keyNum / baseKeyNum,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const availableLanguages = languagesWithTranslations.filter(
|
||||
language => language.completeRate > 0
|
||||
);
|
||||
|
||||
availableLanguages
|
||||
// skip base language
|
||||
.filter(i => !i.base)
|
||||
.forEach(async language => {
|
||||
await fs.writeFile(
|
||||
path.resolve(RES_DIR, `${language.tag}.json`),
|
||||
JSON.stringify(
|
||||
{
|
||||
'// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.':
|
||||
'',
|
||||
...language.translations,
|
||||
},
|
||||
null,
|
||||
4
|
||||
) + '\n'
|
||||
);
|
||||
});
|
||||
|
||||
console.log('Generating meta data...');
|
||||
const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
// Run \`pnpm run download-resources\` to regenerate.
|
||||
// To overwrite this, please overwrite ${path.basename(__filename)}
|
||||
${availableLanguages
|
||||
.map(
|
||||
language =>
|
||||
`import ${language.tag.replaceAll('-', '_')} from './${
|
||||
language.tag
|
||||
}.json'`
|
||||
)
|
||||
.join('\n')}
|
||||
|
||||
export const LOCALES = [
|
||||
${availableLanguages
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
.map(({ translations, ...language }) =>
|
||||
JSON.stringify({
|
||||
...language,
|
||||
res: '__RES_PLACEHOLDER',
|
||||
}).replace(
|
||||
'"__RES_PLACEHOLDER"',
|
||||
language.tag.replaceAll('-', '_')
|
||||
)
|
||||
)
|
||||
.join(',\n')}
|
||||
] as const;
|
||||
`;
|
||||
|
||||
await fs.writeFile(
|
||||
path.resolve(RES_DIR, 'index.ts'),
|
||||
format(code, {
|
||||
parser: 'typescript',
|
||||
singleQuote: true,
|
||||
trailingComma: 'es5',
|
||||
tabWidth: 4,
|
||||
arrowParens: 'avoid',
|
||||
})
|
||||
);
|
||||
console.log('Done');
|
||||
};
|
||||
|
||||
main();
|
||||
@@ -1,55 +0,0 @@
|
||||
// cSpell:ignore Tolgee
|
||||
const TOLGEE_API_KEY = process.env['TOLGEE_API_KEY'];
|
||||
const TOLGEE_API_URL = 'https://i18n.affine.pro';
|
||||
|
||||
if (!TOLGEE_API_KEY) {
|
||||
throw new Error(`Please set "TOLGEE_API_KEY" as environment variable!`);
|
||||
}
|
||||
|
||||
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',
|
||||
});
|
||||
|
||||
const isRequest = (input: RequestInfo | URL): input is Request => {
|
||||
return typeof input === 'object' && !('href' in input);
|
||||
};
|
||||
|
||||
return new Proxy(fetch, {
|
||||
apply(
|
||||
target,
|
||||
thisArg: unknown,
|
||||
argArray: Parameters<typeof globalThis.fetch>
|
||||
) {
|
||||
if (isRequest(argArray[0])) {
|
||||
// Request
|
||||
if (!argArray[0].headers) {
|
||||
argArray[0] = {
|
||||
...argArray[0],
|
||||
url: `${baseUrl}${argArray[0].url}`,
|
||||
headers,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// URL or URLLike + ?RequestInit
|
||||
if (typeof argArray[0] === 'string') {
|
||||
argArray[0] = `${baseUrl}${argArray[0]}`;
|
||||
}
|
||||
if (!argArray[1]) {
|
||||
argArray[1] = {};
|
||||
}
|
||||
if (!argArray[1].headers) {
|
||||
argArray[1].headers = headers;
|
||||
}
|
||||
}
|
||||
// console.log('fetch', argArray);
|
||||
return target.apply(thisArg, argArray);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchTolgee = withTolgee(globalThis.fetch);
|
||||
@@ -1,154 +0,0 @@
|
||||
// cSpell:ignore Tolgee
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { createsNewKey, getRemoteTranslations } from './api';
|
||||
import type { TranslationRes } from './utils';
|
||||
|
||||
const BASE_JSON_PATH = path.resolve(
|
||||
process.cwd(),
|
||||
'src',
|
||||
'resources',
|
||||
'en.json'
|
||||
);
|
||||
const BASE_LANGUAGES = 'en' as const;
|
||||
|
||||
/**
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* flatRes({ a: { b: 'c' } }); // { 'a.b': 'c' }
|
||||
* ```
|
||||
*/
|
||||
const flatRes = (obj: TranslationRes) => {
|
||||
const getEntries = (o: TranslationRes, prefix = ''): [string, string][] =>
|
||||
Object.entries(o).flatMap<[string, string]>(([k, v]) =>
|
||||
typeof v !== 'string'
|
||||
? getEntries(v, `${prefix}${k}.`)
|
||||
: [[`${prefix}${k}`, v]]
|
||||
);
|
||||
return Object.fromEntries(getEntries(obj));
|
||||
};
|
||||
|
||||
const differenceObject = (
|
||||
newObj: Record<string, string>,
|
||||
oldObj: Record<string, string>
|
||||
) => {
|
||||
const add: string[] = [];
|
||||
const remove: string[] = [];
|
||||
const modify: string[] = [];
|
||||
const both: string[] = [];
|
||||
|
||||
Object.keys(newObj).forEach(key => {
|
||||
if (!(key in oldObj)) {
|
||||
add.push(key);
|
||||
} else {
|
||||
both.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(oldObj).forEach(key => {
|
||||
if (!(key in newObj)) {
|
||||
remove.push(key);
|
||||
}
|
||||
});
|
||||
|
||||
both.forEach(key => {
|
||||
if (!(key in newObj) || !(key in oldObj)) {
|
||||
throw new Error('Unreachable');
|
||||
}
|
||||
const newVal = newObj[key];
|
||||
const oldVal = oldObj[key];
|
||||
if (newVal !== oldVal) {
|
||||
modify.push(key);
|
||||
}
|
||||
});
|
||||
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(
|
||||
await readFile(BASE_JSON_PATH, {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
);
|
||||
const flatLocalTranslations = flatRes(baseLocalTranslations);
|
||||
console.log(
|
||||
`Loading local base translations success! Total ${
|
||||
Object.keys(flatLocalTranslations).length
|
||||
} keys`
|
||||
);
|
||||
|
||||
console.log('Fetch remote base translations...');
|
||||
const baseRemoteTranslations = await getRemoteTranslations(BASE_LANGUAGES);
|
||||
const flatRemoteTranslations = flatRes(baseRemoteTranslations);
|
||||
console.log(
|
||||
`Fetch remote base translations success! Total ${
|
||||
Object.keys(flatRemoteTranslations).length
|
||||
} keys`
|
||||
);
|
||||
|
||||
const diff = differenceObject(flatLocalTranslations, flatRemoteTranslations);
|
||||
|
||||
console.log(''); // new line
|
||||
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}`);
|
||||
await createsNewKey(key, { [BASE_LANGUAGES]: val });
|
||||
});
|
||||
|
||||
// TODO remove unused tags from used keys
|
||||
|
||||
// diff.remove.forEach(key => {
|
||||
// // TODO set unused tag
|
||||
// // console.log(`Add ${DEPRECATED_TAG_NAME} to ${key}`);
|
||||
// addTagByKey(key, DEPRECATED_TAG_NAME);
|
||||
// });
|
||||
|
||||
// diff.modify.forEach(key => {
|
||||
// // TODO warn different between local and remote base translations
|
||||
// });
|
||||
|
||||
// TODO send notification
|
||||
};
|
||||
|
||||
main();
|
||||
@@ -1,3 +0,0 @@
|
||||
export interface TranslationRes {
|
||||
[x: string]: string | TranslationRes;
|
||||
}
|
||||
@@ -10,9 +10,9 @@ import '../utils/print-build-info';
|
||||
import ProviderComposer from '@/components/provider-composer';
|
||||
import type { PropsWithChildren, ReactElement, ReactNode } from 'react';
|
||||
import type { NextPage } from 'next';
|
||||
import { AppStateProvider } from '@/providers/app-state-provider/provider';
|
||||
import ConfirmProvider from '@/providers/confirm-provider';
|
||||
import { ModalProvider } from '@/providers/global-modal-provider';
|
||||
import { AppStateProvider } from '@/providers/app-state-provider/Provider';
|
||||
import ConfirmProvider from '@/providers/ConfirmProvider';
|
||||
import { ModalProvider } from '@/providers/GlobalModalProvider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -20,7 +20,7 @@ import { PageLoading } from '@/components/loading';
|
||||
import Head from 'next/head';
|
||||
import '@/libs/i18n';
|
||||
|
||||
const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), {
|
||||
const ThemeProvider = dynamic(() => import('@/providers/ThemeProvider'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const DynamicBlocksuite = ({
|
||||
const openWorkspace: LoadWorkspaceHandler = async (workspaceId: string) => {
|
||||
if (workspaceId) {
|
||||
const dc = await getDataCenter();
|
||||
return dc.load(workspaceId, { providerId: 'selfhosted' });
|
||||
return dc.load(workspaceId, { providerId: 'local' });
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
} from './context';
|
||||
import { Page, Workspace as StoreWorkspace } from '@blocksuite/store';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
const DynamicBlocksuite = dynamic(() => import('./dynamic-blocksuite'), {
|
||||
const DynamicBlocksuite = dynamic(() => import('./DynamicBlocksuite'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ Let us know what you think of this latest version.
|
||||
5. You can self-host locally with Docker.
|
||||
|
||||
```basic
|
||||
docker run -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-self-hosted:alpha-abbey-wood
|
||||
docker run -it --name affine -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-self-hosted:alpha-abbey-wood
|
||||
```
|
||||
|
||||
**Looking for Markdown syntax or keyboard shortcuts?**
|
||||
@@ -24,6 +24,9 @@ docker run -d -v [YOUR_PATH]:/app/data -p 3000:3000 ghcr.io/toeverything/affine-
|
||||
|
||||
- Manage your pages from the collapsible **sidebar**, which allows you to add **favourites** and restore deleted files from the **trash**
|
||||
- Search through all your content with the quick search - activate with `Ctrl/⌘ + K`
|
||||
- A friendly Reminder:
|
||||
- In the case of unselected text, `Ctrl/⌘ + K` activates quick search;
|
||||
- In the case of selected text, `Ctrl/⌘ + K` will firstly ask to add a hyperlink, and then using `Ctrl/⌘ + K` again activates the quick search
|
||||
- Quickly format text with the **pop-up toolbar** (highlight any text to give it a try)
|
||||
- Copy and paste **images** into your pages, resize them and add captions
|
||||
- Add horizontal line dividers to your text with `---` and `***`
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, test, expect } from 'vitest';
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { isMobile } from '../get-is-mobile';
|
||||
|
||||
describe('get-is-mobile', () => {
|
||||
test.describe('get-is-mobile', () => {
|
||||
test('get-is-mobile', () => {
|
||||
expect(
|
||||
isMobile(
|
||||
|
||||
Reference in New Issue
Block a user