refactor(i18n): new hook api (#7273)

# NEW HOOK API

`useI18n`: same as `useAFFiNEI18N`, with additional APIs

```ts
import { useI18n } from '@affine/i18n'

const i18n = useI18n()
i18n['hello world']() -> 你好世界
```

# NEW GLOBAL i18n Instance

`I18n`: use i18n capabilities outside of React

```ts
import { I18n } from '@affine/i18n'

I18n['hello world']() -> 你好世界
```

# NEW TYPES

`I18nKeys` -> all i18n keys

`I18nString` -> An i18n message (key&options)
transfer and store i18n text outside of React
```ts
const msg: I18nString = {
  key: 'helloworld',
  options: {
    arg1: '123'
  }
}

I18n.t(msg) -> 你好世界123
```

before:

```ts
registerCommand('open-page', {
  name: t('command.open-page')
  // ^- translation happens here,
})
```

after:

```ts
registerCommand('open-page', {
  name: { key: 'command.open-page' }
  // ^- store I18nString here, translate when the command render to UI
})
```
This commit is contained in:
EYHN
2024-06-20 02:19:41 +00:00
parent 5b0f56399c
commit 7c0a686cd9
193 changed files with 553 additions and 575 deletions
@@ -16,8 +16,7 @@ import { CollectionService } from '@affine/core/modules/collection';
import { WorkspaceSubPath } from '@affine/core/shared';
import { mixpanel } from '@affine/core/utils';
import type { Collection } from '@affine/env/filter';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Trans, useI18n } from '@affine/i18n';
import {
EdgelessIcon,
LinkIcon,
@@ -81,7 +80,7 @@ const docToCommand = (
run: () => void,
getPageTitle: ReturnType<typeof useGetDocCollectionPageTitle>,
isPageJournal: (pageId: string) => boolean,
t: ReturnType<typeof useAFFiNEI18N>,
t: ReturnType<typeof useI18n>,
subTitle?: string
): CMDKCommand => {
const docMode = doc.mode$.value;
@@ -122,7 +121,7 @@ function useSearchedDocCommands(
const workspace = useService(WorkspaceService).workspace;
const getPageTitle = useGetDocCollectionPageTitle(workspace.docCollection);
const { isPageJournal } = useJournalHelper(workspace.docCollection);
const t = useAFFiNEI18N();
const t = useI18n();
const [searchTime, setSearchTime] = useState<number>(0);
@@ -202,7 +201,7 @@ export const usePageCommands = () => {
const query = useLiveData(quickSearch.query$);
const navigationHelper = useNavigateHelper();
const journalHelper = useJournalHelper(workspace.docCollection);
const t = useAFFiNEI18N();
const t = useI18n();
const onSelectPage = useCallback(
(opts: { docId: string; blockId?: string }) => {
@@ -398,7 +397,7 @@ export const collectionToCommand = (
collection: Collection,
navigationHelper: ReturnType<typeof useNavigateHelper>,
selectCollection: (id: string) => void,
t: ReturnType<typeof useAFFiNEI18N>,
t: ReturnType<typeof useI18n>,
workspace: Workspace
): CMDKCommand => {
const label = collection.name || t['Untitled']();
@@ -422,7 +421,7 @@ export const useCollectionsCommands = () => {
const quickSearch = useService(QuickSearchService).quickSearch;
const query = useLiveData(quickSearch.query$);
const navigationHelper = useNavigateHelper();
const t = useAFFiNEI18N();
const t = useI18n();
const workspace = useService(WorkspaceService).workspace;
const selectCollection = useCallback(
(id: string) => {
@@ -3,8 +3,8 @@ import type { CommandCategory } from '@affine/core/commands';
import { useDocEngineStatus } from '@affine/core/hooks/affine/use-doc-engine-status';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { QuickSearchService } from '@affine/core/modules/cmdk';
import { i18nTime } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { type I18nKeys, i18nTime } from '@affine/i18n';
import { useI18n } from '@affine/i18n';
import type { DocMeta } from '@blocksuite/store';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
@@ -32,13 +32,7 @@ import { CMDKModal } from './modal';
import { NotFoundGroup } from './not-found';
import type { CMDKCommand } from './types';
type NoParametersKeys<T> = {
[K in keyof T]: T[K] extends () => any ? K : never;
}[keyof T];
type i18nKey = NoParametersKeys<ReturnType<typeof useAFFiNEI18N>>;
const categoryToI18nKey: Record<CommandCategory, i18nKey> = {
const categoryToI18nKey = {
'affine:recent': 'com.affine.cmdk.affine.category.affine.recent',
'affine:navigation': 'com.affine.cmdk.affine.category.affine.navigation',
'affine:creation': 'com.affine.cmdk.affine.category.affine.creation',
@@ -55,7 +49,7 @@ const categoryToI18nKey: Record<CommandCategory, i18nKey> = {
'com.affine.cmdk.affine.category.editor.insert-object',
'editor:page': 'com.affine.cmdk.affine.category.editor.page',
'affine:results': 'com.affine.cmdk.affine.category.results',
};
} satisfies Record<CommandCategory, I18nKeys>;
const QuickSearchGroup = ({
category,
@@ -66,7 +60,7 @@ const QuickSearchGroup = ({
commands: CMDKCommand[];
onOpenChange?: (open: boolean) => void;
}) => {
const t = useAFFiNEI18N();
const t = useI18n();
const i18nKey = categoryToI18nKey[category];
const quickSearch = useService(QuickSearchService).quickSearch;
const query = useLiveData(quickSearch.query$);
@@ -169,7 +163,7 @@ export const CMDKContainer = ({
groups: ReturnType<typeof useCMDKCommandGroups>;
onQueryChange: (query: string) => void;
}>) => {
const t = useAFFiNEI18N();
const t = useI18n();
const [value, setValue] = useAtom(cmdkValueAtom);
const [opening, setOpening] = useState(open);
const { syncing, progress } = useDocEngineStatus();
@@ -254,7 +248,7 @@ const CMDKQuickSearchModalInner = ({
const quickSearch = useService(QuickSearchService).quickSearch;
const query = useLiveData(quickSearch.query$);
const groups = useCMDKCommandGroups();
const t = useAFFiNEI18N();
const t = useI18n();
return (
<CMDKContainer
className={styles.root}
@@ -278,7 +272,7 @@ const CMDKQuickSearchCallbackModalInner = ({
const quickSearch = useService(QuickSearchService).quickSearch;
const query = useLiveData(quickSearch.query$);
const groups = useSearchCallbackCommandGroups();
const t = useAFFiNEI18N();
const t = useI18n();
return (
<CMDKContainer
className={styles.root}
@@ -1,5 +1,5 @@
import { QuickSearchService } from '@affine/core/modules/cmdk';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useI18n } from '@affine/i18n';
import { SearchIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { useCommandState } from 'cmdk';
@@ -12,7 +12,7 @@ export const NotFoundGroup = () => {
// hack: we know that the filtered count is 3 when there is no result (create page & edgeless & append to journal, for mode === 'cmdk')
const renderNoResult = useCommandState(state => state.filtered.count === 3);
const t = useAFFiNEI18N();
const t = useI18n();
if (!renderNoResult) {
return null;
@@ -8,7 +8,7 @@ import {
useJournalRouteHelper,
} from '@affine/core/hooks/use-journal';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useI18n } from '@affine/i18n';
import {
EdgelessIcon,
MoreHorizontalIcon,
@@ -86,7 +86,7 @@ interface JournalBlockProps {
}
const EditorJournalPanel = () => {
const t = useAFFiNEI18N();
const t = useI18n();
const doc = useService(DocService).doc;
const workspace = useService(WorkspaceService).workspace;
const { journalDate, isJournal } = useJournalInfoHelper(
@@ -167,7 +167,7 @@ const sortPagesByDate = (
};
const DailyCountEmptyFallback = ({ name }: { name: NavItemName }) => {
const t = useAFFiNEI18N();
const t = useI18n();
return (
<div className={styles.dailyCountEmpty}>
@@ -180,7 +180,7 @@ const DailyCountEmptyFallback = ({ name }: { name: NavItemName }) => {
const JournalDailyCountBlock = ({ date }: JournalBlockProps) => {
const workspace = useService(WorkspaceService).workspace;
const nodeRef = useRef<HTMLDivElement>(null);
const t = useAFFiNEI18N();
const t = useI18n();
const [activeItem, setActiveItem] = useState<NavItemName>('createdToday');
const docRecords = useLiveData(useService(DocsService).list.docs$);
@@ -345,7 +345,7 @@ const ConflictList = ({
);
};
const JournalConflictBlock = ({ date }: JournalBlockProps) => {
const t = useAFFiNEI18N();
const t = useI18n();
const workspace = useService(WorkspaceService).workspace;
const docRecordList = useService(DocsService).list;
const journalHelper = useJournalHelper(workspace.docCollection);
@@ -1,5 +1,5 @@
import { IconButton, Tooltip } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useI18n } from '@affine/i18n';
import { ArrowLeftSmallIcon, ArrowRightSmallIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo } from 'react';
@@ -9,7 +9,7 @@ import { NavigatorService } from '../services/navigator';
import * as styles from './navigation-buttons.css';
export const NavigationButtons = () => {
const t = useAFFiNEI18N();
const t = useI18n();
const shortcuts = useGeneralShortcuts().shortcuts;
@@ -1,6 +1,6 @@
import { IconButton, Tooltip } from '@affine/component';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useI18n } from '@affine/i18n';
import {
CloseIcon,
DualLinkIcon,
@@ -76,7 +76,7 @@ export const DocPeekViewControls = ({
const peekView = useService(PeekViewService).peekView;
const workbench = useService(WorkbenchService).workbench;
const { jumpToPageBlock } = useNavigateHelper();
const t = useAFFiNEI18N();
const t = useI18n();
const { doc, workspace } = useDoc(docId);
const controls = useMemo(() => {
return [
@@ -1,6 +1,5 @@
import { ConfirmModal, toast } from '@affine/component';
import { Trans } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { Trans, useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useMemo } from 'react';
@@ -15,7 +14,7 @@ export const DeleteTagConfirmModal = ({
onOpenChange: (open: boolean) => void;
selectedTagIds: string[];
}) => {
const t = useAFFiNEI18N();
const t = useI18n();
const tagService = useService(TagService);
const tags = useLiveData(tagService.tagList.tags$);
const selectedTags = useMemo(() => {
@@ -1,5 +1,5 @@
import { MenuIcon, MenuItem } from '@affine/component';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useI18n } from '@affine/i18n';
import {
ExpandCloseIcon,
MoveToLeftDuotoneIcon,
@@ -115,7 +115,7 @@ export const SplitViewPanel = memo(function SplitViewPanel({
});
const SplitViewMenu = ({ view }: { view: View }) => {
const t = useAFFiNEI18N();
const t = useI18n();
const workbench = useService(WorkbenchService).workbench;
const views = useLiveData(workbench.views$);