diff --git a/locales/en.json b/locales/en.json new file mode 100644 index 0000000..2bc971e --- /dev/null +++ b/locales/en.json @@ -0,0 +1,28 @@ +{ + "app.home.install": "Install", + "app.home.repo": "GitHub Repo", + "app.home.slogan": "Load your Scratch extensions anywhere.", + "app.home.subdescription1": "Chibi loads extensions by directly injecting them into the Scratch virtual machine, which allows you to use your favorite extensions without being restricted by the editor itself.", + "app.home.subdescription2": "Chibi implements the loader independently, which means extensions have a unified implementation standard rather rely on the editor.", + "app.home.subdescription3": "Chibi adds support for non-sandbox extensions and TurboWarp extensions. This means you can seamlessly sideload these extensions into your projects.", + "app.home.subtitle1": "Load ANY Scratch extensions in ANY Scratch-based editors.", + "app.home.subtitle2": "Write once, Run everywhere", + "app.home.subtitle3": "Compatible with most popular extension loading methods", + "app.home.title": "Chibi", + "app.name": "Chibi", + "app.navigation.gallary": "Extension Gallary", + "app.navigation.home": "Home", + "app.navigation.management": "Manage Extension", + "app.navigation.notConnected": "Not Connected", + "app.navigation.settings": "Settings", + "app.navigation.version": "Version:", + "app.settings.extension": "Extension", + "app.settings.extension.dontExposeCtx": "Don't Expose \"Scratch\" Object Globally", + "app.settings.extension.loadFromGallaryOnly": "Load From Gallery's Extensions Only", + "app.settings.extension.noConfirmDialog": "No Confirmation Dialog While Loading Project", + "app.settings.project": "Project", + "app.settings.project.convertProcCall": "Convert Sideload Extension's Blocks Into Procedures Call", + "app.title.gallary": "Extension Gallary", + "app.title.manage": "Manage Extension", + "app.title.settings": "Settings" +} diff --git a/locales/index.ts b/locales/index.ts new file mode 100644 index 0000000..d46d2cd --- /dev/null +++ b/locales/index.ts @@ -0,0 +1,7 @@ +import en from './en.json'; +import zhCN from './zh-cn.json'; + +export const messages = { + en: en, + 'zh-CN': zhCN +}; diff --git a/locales/zh-cn.json b/locales/zh-cn.json new file mode 100644 index 0000000..7b802e9 --- /dev/null +++ b/locales/zh-cn.json @@ -0,0 +1,28 @@ +{ + "app.home.install": "安装", + "app.home.repo": "GitHub 仓库", + "app.home.slogan": "全平台的 Scratch 扩展加载器", + "app.home.subdescription1": "Chibi 通过注入 Scratch 虚拟机的方式加载扩展,这意味着你可以使用自己喜欢的扩展,而不受编辑器本身的限制。", + "app.home.subdescription2": "Chibi 独立实现了扩展加载部分,这意味着扩展有了统一的实现标准,而不依赖于编辑器。", + "app.home.subdescription3": "Chibi 增加了对非沙盒扩展和 TurboWarp 扩展的支持。这意味着您可以将这些扩展无缝侧载到您的项目中。", + "app.home.subtitle1": "不讲武德,在任何平台加载你所喜爱的扩展", + "app.home.subtitle2": "一次码,四处行", + "app.home.subtitle3": "兼容主流的扩展加载方式,打破阻碍扩展加载的壁垒", + "app.home.title": "Chibi (琪比)", + "app.name": "Chibi", + "app.navigation.gallary": "扩展橱窗", + "app.navigation.home": "首页", + "app.navigation.management": "扩展管理", + "app.navigation.notConnected": "未连接", + "app.navigation.settings": "设置", + "app.navigation.version": "连接版本:", + "app.settings.extension": "扩展", + "app.settings.extension.dontExposeCtx": "不全局暴露 \"Scratch\" 对象", + "app.settings.extension.loadFromGallaryOnly": "仅允许加载橱窗扩展", + "app.settings.extension.noConfirmDialog": "不确认加载带侧载扩展的项目", + "app.settings.project": "项目", + "app.settings.project.convertProcCall": "将侧载扩展积木转换为自定义积木", + "app.title.gallary": "扩展橱窗", + "app.title.manage": "扩展管理", + "app.title.settings": "设置" +} diff --git a/package.json b/package.json index 144992f..dfdebdf 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,14 @@ "start": "vite", "build": "tsc && vite build", "lint": "eslint ./src/ --ext .js,.ts,tsx,jsx", - "preview": "vite preview" + "preview": "vite preview", + "extract": "formatjs extract 'src/**/*.{ts,tsx}' --ignore='**/*.d.ts' --out-file locales/en.json --format simple", + "postinstall": "patch-package" }, "dependencies": { + "@cookbook/solid-intl": "^0.1.2", "@fontsource/roboto": "^5.0.8", + "@formatjs/cli": "^6.2.1", "@suid/icons-material": "^0.6.11", "@suid/material": "^0.15.1", "solid-js": "^1.7.8" @@ -21,6 +25,8 @@ "@typescript-eslint/parser": "^6.7.5", "eslint": "^8.51.0", "eslint-plugin-solid": "^0.13.0", + "patch-package": "^8.0.0", + "postinstall-postinstall": "^2.1.0", "sharp": "^0.32.6", "svgo": "^3.0.2", "typescript": "^5.0.2", diff --git a/patches/@cookbook+solid-intl+0.1.2.patch b/patches/@cookbook+solid-intl+0.1.2.patch new file mode 100644 index 0000000..78ebf9b --- /dev/null +++ b/patches/@cookbook+solid-intl+0.1.2.patch @@ -0,0 +1,38 @@ +diff --git a/node_modules/@cookbook/solid-intl/package.json b/node_modules/@cookbook/solid-intl/package.json +index 75735fa..f35ab11 100644 +--- a/node_modules/@cookbook/solid-intl/package.json ++++ b/node_modules/@cookbook/solid-intl/package.json +@@ -28,13 +28,28 @@ + "exports": { + ".": { + "solid": "./dist/source/index.js", +- "import": "./dist/esm/index.js", ++ "import": { ++ "types": "./dist/types/index.d.ts", ++ "default": "./dist/esm/index.js" ++ }, + "browser": { +- "import": "./dist/esm/index.js", +- "require": "./dist/cjs/index.cjs" ++ "import": { ++ "types": "./dist/types/index.d.ts", ++ "default": "./dist/esm/index.js" ++ }, ++ "require": { ++ "types": "./dist/types/index.d.ts", ++ "default": "./dist/cjs/index.cjs" ++ } ++ }, ++ "require": { ++ "default": "./dist/cjs/index.cjs", ++ "types": "./dist/types/index.d.ts" + }, +- "require": "./dist/cjs/index.cjs", +- "node": "./dist/cjs/index.cjs" ++ "node": { ++ "default": "./dist/cjs/index.cjs", ++ "types": "./dist/types/index.d.ts" ++ } + } + }, + "scripts": { diff --git a/src/App.tsx b/src/App.tsx index 3565577..0dc99e8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import Home from './home'; import Settings from './settings'; import Gallary from './gallary'; import { createSignal, createEffect, onMount, Show } from 'solid-js'; +import { defineMessages, useIntl } from '@cookbook/solid-intl'; export interface ExtensionInfo { name: string; @@ -38,10 +39,30 @@ interface ChibiDispatchedSettings { type ChibiDispatched = ChibiDispatchedSettings | ChibiDispatchedExtensions | ChibiDispatchedClientInfo; +const messages = defineMessages({ + chibi: { + id: 'app.name', + defaultMessage: 'Chibi', + }, + manage: { + id: 'app.title.manage', + defaultMessage: 'Manage Extension', + }, + gallary: { + id: 'app.title.gallary', + defaultMessage: 'Extension Gallary', + }, + settings: { + id: 'app.title.settings', + defaultMessage: 'Settings', + } +}); + const subtitleMap = { - manage: 'Manage Extension', - gallary: 'Extension Gallary', - settings: 'Settings' + default: messages.chibi, + manage: messages.manage, + gallary: messages.gallary, + settings: messages.settings } as const; const initialHash = window.location.hash.trim().slice(1); @@ -51,19 +72,20 @@ function App () { const [clientInfo, setClientInfo] = createSignal(null); const [extensionInfos, setExtensionInfos] = createSignal([]); const [settings, setSettings] = createSignal>({}); + const intl = useIntl(); createEffect(() => { window.location.hash = page(); if (page() in subtitleMap) { - document.title = `Chibi | ${subtitleMap[page() as keyof typeof subtitleMap]}`; + document.title = `${intl.formatMessage(messages.chibi)} | ${intl.formatMessage(subtitleMap[page() as keyof typeof subtitleMap])}`; } else { - document.title = `Chibi`; + document.title = `${intl.formatMessage(messages.chibi)}`; } }); onMount(() => { if (!window.opener) return; - window.addEventListener("message", (event: MessageEvent) => { + window.addEventListener('message', (event: MessageEvent) => { if (!('type' in event.data)) return; switch ((event.data as ChibiDispatched).type) { case 'handshake': diff --git a/src/home.tsx b/src/home.tsx index 9541696..9f4b028 100644 --- a/src/home.tsx +++ b/src/home.tsx @@ -8,9 +8,11 @@ import { CardMedia, Stack } from '@suid/material'; +import { useIntl } from '@cookbook/solid-intl'; import CodeIcon from '@suid/icons-material/Code'; function Home () { + const intl = useIntl(); return ( chibimoth - Chibi - Load your Scratch extensions anywhere. + + {intl.formatMessage({ + id: 'app.home.title', + defaultMessage: 'Chibi' + })} + + + {intl.formatMessage({ + id: 'app.home.slogan', + defaultMessage: 'Load your Scratch extensions anywhere.' + })} + + > + {intl.formatMessage({ + id: 'app.home.install', + defaultMessage: 'Install' + })} + + > + {intl.formatMessage({ + id: 'app.home.repo', + defaultMessage: 'GitHub Repo' + })} + - Load ANY Scratch extensions in ANY Scratch-based editors. + {intl.formatMessage({ + id: 'app.home.subtitle1', + defaultMessage: 'Load ANY Scratch extensions in ANY Scratch-based editors.', + description: 'Load ANY Scratch extensions in ANY Scratch-based editors.' + })} - Chibi loads extensions by directly injecting them into the Scratch virtual machine, - which allows you to use your favorite extensions without being restricted by the editor itself. + {intl.formatMessage({ + id: 'app.home.subdescription1', + defaultMessage: 'Chibi loads extensions by directly injecting them into the Scratch virtual machine, which allows you to use your favorite extensions without being restricted by the editor itself.', + description: 'Chibi loads extensions by directly injecting them into the Scratch virtual machine, which allows you to use your favorite extensions without being restricted by the editor itself.' + })} @@ -96,10 +125,18 @@ function Home () { textAlign: 'center' }}> - Write once, Run everywhere + {intl.formatMessage({ + id: 'app.home.subtitle2', + defaultMessage: 'Write once, Run everywhere', + description: 'Write once, Run everywhere' + })} - Chibi implements the loader independently, which means extensions have a unified implementation standard rather rely on the editor. + {intl.formatMessage({ + id: 'app.home.subdescription2', + defaultMessage: 'Chibi implements the loader independently, which means extensions have a unified implementation standard rather rely on the editor.', + description: 'Chibi implements the loader independently, which means extensions have a unified implementation standard rather rely on the editor.' + })} - Compatible with most popular extension loading methods + {intl.formatMessage({ + id: 'app.home.subtitle3', + defaultMessage: 'Compatible with most popular extension loading methods', + description: 'Compatible with most popular extension loading methods' + })} - Chibi adds support for non-sandbox extensions and TurboWarp extensions. This means you can seamlessly sideload these extensions into your projects. + {intl.formatMessage({ + id: 'app.home.subdescription3', + defaultMessage: 'Chibi adds support for non-sandbox extensions and TurboWarp extensions. This means you can seamlessly sideload these extensions into your projects.', + description: 'Chibi adds support for non-sandbox extensions and TurboWarp extensions. This means you can seamlessly sideload these extensions into your projects.' + })} diff --git a/src/index.tsx b/src/index.tsx index e683ddd..7563f85 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,9 +4,15 @@ import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; import { render } from 'solid-js/web'; +import { IntlProvider } from '@cookbook/solid-intl'; import './index.css'; +import { messages } from '../locales'; import App from './App'; const root = document.getElementById('root'); -render(() => , root!); +render(() => ( + + + +), root!); diff --git a/src/navigation.tsx b/src/navigation.tsx index 53dfed1..e343f89 100644 --- a/src/navigation.tsx +++ b/src/navigation.tsx @@ -18,17 +18,47 @@ import ExtensionIcon from '@suid/icons-material/ExtensionRounded'; import MenuIcon from '@suid/icons-material/Menu'; import GallaryIcon from '@suid/icons-material/StorefrontRounded'; import SettingsIcon from '@suid/icons-material/SettingsRounded'; +import { defineMessages, useIntl } from '@cookbook/solid-intl'; import type { ClientInfo } from './App'; +import type { MessageDescriptor } from '@cookbook/solid-intl'; interface NavigationProps { - subtitleMap: Record; + subtitleMap: Record; page (): string; navigateTo (page: string): void; clientInfo (): ClientInfo | null; } +const messages = defineMessages({ + home: { + id: 'app.navigation.home', + defaultMessage: 'Home' + }, + manage: { + id: 'app.navigation.management', + defaultMessage: 'Manage Extension' + }, + gallary: { + id: 'app.navigation.gallary', + defaultMessage: 'Extension Gallary' + }, + settings: { + id: 'app.navigation.settings', + defaultMessage: 'Settings' + }, + version: { + id: 'app.navigation.version', + defaultMessage: 'Version: ' + }, + notConnected: { + id: 'app.navigation.notConnected', + defaultMessage: 'Not Connected' + } +}); + function Navigation (props: NavigationProps) { const [drawerOpen, setDrawerOpen] = createSignal(false); + const intl = useIntl(); return ( <> @@ -49,7 +79,7 @@ function Navigation (props: NavigationProps) { component='div' sx={{ flexGrow: 1, userSelect: 'none' }} > - {props.page() in props.subtitleMap ? props.subtitleMap[props.page()] : 'Chibi'} + {intl.formatMessage(props.page() in props.subtitleMap ? props.subtitleMap[props.page()] : props.subtitleMap.default)} @@ -77,7 +107,7 @@ function Navigation (props: NavigationProps) { - + - + - + @@ -119,11 +149,11 @@ function Navigation (props: NavigationProps) { - + - {props.clientInfo() ? `Version: ${props.clientInfo()!.version}` : 'Not connected'} + {props.clientInfo() ? `${intl.formatMessage(messages.version)} ${props.clientInfo()!.version}` : intl.formatMessage(messages.notConnected)} diff --git a/src/settings.tsx b/src/settings.tsx index 0660905..9627019 100644 --- a/src/settings.tsx +++ b/src/settings.tsx @@ -7,6 +7,7 @@ import { Typography } from '@suid/material'; import { createSignal, Show } from 'solid-js'; +import { useIntl } from '@cookbook/solid-intl'; import type { SettingsInfo, ClientInfo } from './App'; interface SettingsProps { @@ -15,6 +16,7 @@ interface SettingsProps { } function Settings (props: SettingsProps) { + const intl = useIntl(); return ( @@ -26,7 +28,10 @@ function Settings (props: SettingsProps) { }} color='text.secondary' > - Project + {intl.formatMessage({ + id: 'app.settings.project', + defaultMessage: 'Project' + })} @@ -39,7 +44,10 @@ function Settings (props: SettingsProps) { alignItems: 'center' }}> - Convert Sideload Extension's Blocks Into Procedures Call + {intl.formatMessage({ + id: 'app.settings.project.convertProcCall', + defaultMessage: 'Convert Sideload Extension\'s Blocks Into Procedures Call' + })} - Extension + {intl.formatMessage({ + id: 'app.settings.extension', + defaultMessage: 'Extension' + })} @@ -88,7 +99,10 @@ function Settings (props: SettingsProps) { alignItems: 'center' }}> - Don't Expose "Scratch" Object Globally + {intl.formatMessage({ + id: 'app.settings.extension.dontExposeCtx', + defaultMessage: 'Don\'t Expose "Scratch" Object Globally' + })} - No Confirmation Dialog While Loading Project + {intl.formatMessage({ + id: 'app.settings.extension.noConfirmDialog', + defaultMessage: 'No Confirmation Dialog While Loading Project' + })} - Load From Gallery's Extensions Only + {intl.formatMessage({ + id: 'app.settings.extension.loadFromGallaryOnly', + defaultMessage: 'Load From Gallery\'s Extensions Only' + })}