WIP: multi lang

This commit is contained in:
wataru 2023-06-11 13:25:44 +09:00
parent e6b58a8613
commit 52a954ebaa
23 changed files with 324 additions and 2014 deletions

View File

@ -1,10 +1 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8" />
<title>Voice Changer Client Demo</title>
<script defer src="index.js"></script></head>
<body style="width: 100%; height: 100%; margin: 0px">
<div id="app" style="width: 100%; height: 100%"></div>
</body>
</html>
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>

File diff suppressed because one or more lines are too long

31
client/demo/dist/index.js.LICENSE.txt vendored Normal file
View File

@ -0,0 +1,31 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

View File

@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.143",
"@dannadori/voice-changer-client-js": "^1.0.144",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
@ -24,8 +24,8 @@
"@babel/preset-env": "^7.22.5",
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@types/node": "^20.2.6",
"@types/react": "^18.2.9",
"@types/node": "^20.3.0",
"@types/react": "^18.2.11",
"@types/react-dom": "^18.2.4",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2",
@ -37,7 +37,7 @@
"eslint-plugin-react": "^7.32.2",
"eslint-webpack-plugin": "^4.0.1",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.2",
"html-webpack-plugin": "^5.5.3",
"npm-run-all": "^4.1.5",
"postcss-loader": "^7.3.3",
"postcss-nested": "^6.0.1",
@ -3290,9 +3290,9 @@
}
},
"node_modules/@dannadori/voice-changer-client-js": {
"version": "1.0.143",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.143.tgz",
"integrity": "sha512-rMmqpzUx6Rc0IDDpSMwZXLsHN1LibX79PjpgWiM5RTzyVLMg3wwlrZKdDeh1h0uJ+aKcFhqr1HNQSL3PXpzucw==",
"version": "1.0.144",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.144.tgz",
"integrity": "sha512-gxavGAXgxNbCIkGUrezvfs9WXykSwTdQSsig+i6OGRSxD+9eM70jFmhMuwxqjeYjJRc5zUtVw9OzCEXPIupZhA==",
"dependencies": {
"@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^3.14.1",
@ -3986,9 +3986,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.2.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.6.tgz",
"integrity": "sha512-GQBWUtGoefMEOx/vu+emHEHU5aw6JdDoEtZhoBrHFPZbA/YNRFfN996XbBASEWdvmLSLyv9FKYppYGyZjCaq/g=="
"version": "20.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz",
"integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ=="
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
@ -4009,9 +4009,9 @@
"dev": true
},
"node_modules/@types/react": {
"version": "18.2.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz",
"integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==",
"version": "18.2.11",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.11.tgz",
"integrity": "sha512-+hsJr9hmwyDecSMQAmX7drgbDpyE+EgSF6t7+5QEBAn1tQK7kl1vWZ4iRf6SjQ8lk7dyEULxUmZOIpN0W5baZA==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
@ -7048,9 +7048,9 @@
}
},
"node_modules/html-webpack-plugin": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.2.tgz",
"integrity": "sha512-2KsxTJQmtqsT1JGaZJmoMW25wpC0HM9gpW3jH/UMH62To0UKlzRUbJ/FtQNhZ0gd4gWMoetEYkyG8FMNqEO66Q==",
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz",
"integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==",
"dev": true,
"dependencies": {
"@types/html-minifier-terser": "^6.0.0",
@ -14233,9 +14233,9 @@
}
},
"@dannadori/voice-changer-client-js": {
"version": "1.0.143",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.143.tgz",
"integrity": "sha512-rMmqpzUx6Rc0IDDpSMwZXLsHN1LibX79PjpgWiM5RTzyVLMg3wwlrZKdDeh1h0uJ+aKcFhqr1HNQSL3PXpzucw==",
"version": "1.0.144",
"resolved": "https://registry.npmjs.org/@dannadori/voice-changer-client-js/-/voice-changer-client-js-1.0.144.tgz",
"integrity": "sha512-gxavGAXgxNbCIkGUrezvfs9WXykSwTdQSsig+i6OGRSxD+9eM70jFmhMuwxqjeYjJRc5zUtVw9OzCEXPIupZhA==",
"requires": {
"@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^3.14.1",
@ -14811,9 +14811,9 @@
"dev": true
},
"@types/node": {
"version": "20.2.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.6.tgz",
"integrity": "sha512-GQBWUtGoefMEOx/vu+emHEHU5aw6JdDoEtZhoBrHFPZbA/YNRFfN996XbBASEWdvmLSLyv9FKYppYGyZjCaq/g=="
"version": "20.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.3.0.tgz",
"integrity": "sha512-cumHmIAf6On83X7yP+LrsEyUOf/YlociZelmpRYaGFydoaPdxdt80MAbu6vWerQT2COCp2nPvHdsbD7tHn/YlQ=="
},
"@types/prop-types": {
"version": "15.7.5",
@ -14834,9 +14834,9 @@
"dev": true
},
"@types/react": {
"version": "18.2.9",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz",
"integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==",
"version": "18.2.11",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.11.tgz",
"integrity": "sha512-+hsJr9hmwyDecSMQAmX7drgbDpyE+EgSF6t7+5QEBAn1tQK7kl1vWZ4iRf6SjQ8lk7dyEULxUmZOIpN0W5baZA==",
"dev": true,
"requires": {
"@types/prop-types": "*",
@ -17113,9 +17113,9 @@
}
},
"html-webpack-plugin": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.2.tgz",
"integrity": "sha512-2KsxTJQmtqsT1JGaZJmoMW25wpC0HM9gpW3jH/UMH62To0UKlzRUbJ/FtQNhZ0gd4gWMoetEYkyG8FMNqEO66Q==",
"version": "5.5.3",
"resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.5.3.tgz",
"integrity": "sha512-6YrDKTuqaP/TquFH7h4srYWsZx+x6k6+FbsTm0ziCwGHDP78Unr1r9F/H4+sGmMbX08GQcJ+K64x55b+7VM/jg==",
"dev": true,
"requires": {
"@types/html-minifier-terser": "^6.0.0",

View File

@ -24,8 +24,8 @@
"@babel/preset-env": "^7.22.5",
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@types/node": "^20.2.6",
"@types/react": "^18.2.9",
"@types/node": "^20.3.0",
"@types/react": "^18.2.11",
"@types/react-dom": "^18.2.4",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2",
@ -37,7 +37,7 @@
"eslint-plugin-react": "^7.32.2",
"eslint-webpack-plugin": "^4.0.1",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.2",
"html-webpack-plugin": "^5.5.3",
"npm-run-all": "^4.1.5",
"postcss-loader": "^7.3.3",
"postcss-nested": "^6.0.1",
@ -52,7 +52,7 @@
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.143",
"@dannadori/voice-changer-client-js": "^1.0.144",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",

View File

@ -14,6 +14,7 @@ import { ClientType, useIndexedDB } from "@dannadori/voice-changer-client-js";
import { INDEXEDDB_KEY_DEFAULT_MODEL_TYPE } from "./const";
import { Demo } from "./components/demo/010_Demo";
import { ClientSelector } from "./001_ClientSelector";
import { useMessageBuilder } from "./hooks/useMessageBuilder";
library.add(fas, far, fab);
@ -40,9 +41,21 @@ const App = () => {
const AppStateWrapper = () => {
const { appGuiSettingState, clientType, setClientType } = useAppRoot()
const messageBuilderState = useMessageBuilder()
// エラーメッセージ登録
useMemo(() => {
messageBuilderState.setMessage(__filename, "Problem", { "ja": "ちょっと問題が起きたみたいです。", "en": "Looks like there's a bit of a problem." })
messageBuilderState.setMessage(__filename, "Problem-sub1", { "ja": "このアプリで管理している情報をクリアすると回復する場合があります。", "en": "" })
messageBuilderState.setMessage(__filename, "Problem-sub2", { "ja": "下記のボタンを押して情報をクリアします。", "en": "If you clear the information being managed by this app, it may be recoverable." })
messageBuilderState.setMessage(__filename, "Problem-action1", { "ja": "アプリを初期化", "en": "Initialize" })
messageBuilderState.setMessage(__filename, "Problem-action2", { "ja": "初期化せずリロード", "en": "Reload without initialize" })
}, [])
// エラーバウンダリー設定
const [error, setError] = useState<{ error: Error, errorInfo: ErrorInfo }>()
const { getItem, removeDB } = useIndexedDB({ clientType: null })
const errorComponent = useMemo(() => {
const errorName = error?.error.name || "no error name"
const errorMessage = error?.error.message || "no error message"
@ -58,13 +71,13 @@ const AppStateWrapper = () => {
return (
<div className="error-container">
<div className="top-error-message">
{messageBuilderState.getMessage(__filename, "Problem")}
</div>
<div className="top-error-description">
<p></p>
<p></p>
<p><button onClick={onClearCacheClicked}></button></p>
<p><button onClick={onReloadClicked}></button></p>
<p> {messageBuilderState.getMessage(__filename, "Problem-sub1")}</p>
<p> {messageBuilderState.getMessage(__filename, "Problem-sub2")}</p>
<p><button onClick={onClearCacheClicked}>{messageBuilderState.getMessage(__filename, "Problem-action1")}</button></p>
<p><button onClick={onReloadClicked}>{messageBuilderState.getMessage(__filename, "Problem-action2")}</button></p>
</div>
<div className="error-detail">
<div className="error-name">

View File

@ -1,5 +1,7 @@
import React, { useMemo } from "react";
import { isDesktopApp } from "./const";
import { useAppRoot } from "./001_provider/001_AppRootProvider";
import { useMessageBuilder } from "./hooks/useMessageBuilder";
export type TitleProps = {
@ -9,6 +11,13 @@ export type TitleProps = {
}
export const Title = (props: TitleProps) => {
const messageBuilderState = useMessageBuilder()
useMemo(() => {
messageBuilderState.setMessage(__filename, "github", { "ja": "github", "en": "github" })
messageBuilderState.setMessage(__filename, "manual", { "ja": "マニュアル", "en": "manual" })
messageBuilderState.setMessage(__filename, "screenCapture", { "ja": "録画ツール", "en": "Record Screen" })
messageBuilderState.setMessage(__filename, "support", { "ja": "支援", "en": "Donation" })
}, [])
const githubLink = useMemo(() => {
return isDesktopApp() ?
@ -16,14 +25,14 @@ export const Title = (props: TitleProps) => {
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer") }}>
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">github</div>
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</span>
)
:
(
<a className="link tooltip" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">github</div>
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</a>
)
}, [])
@ -33,16 +42,16 @@ export const Title = (props: TitleProps) => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://zenn.dev/wok/books/0004_vc-client-v_1_5_1_x") }}>
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md") }}>
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text">manual</div>
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</span>
)
:
(
<a className="link tooltip" href="https://zenn.dev/wok/books/0004_vc-client-v_1_5_1_x" target="_blank" rel="noopener noreferrer">
<a className="link tooltip" href="https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text">manual</div>
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</a>
)
}, [])
@ -58,7 +67,7 @@ export const Title = (props: TitleProps) => {
// @ts-ignore
window.electronAPI.openBrowser("https://w-okada.github.io/screen-recorder-ts/")
}}>
screen capture
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
</div>
@ -71,7 +80,7 @@ export const Title = (props: TitleProps) => {
<p onClick={() => {
window.open("https://w-okada.github.io/screen-recorder-ts/", '_blank', "noreferrer")
}}>
screen capture
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
</div>
@ -85,7 +94,7 @@ export const Title = (props: TitleProps) => {
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">donate()</div>
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "support")}</div>
</span>
)
:
@ -93,7 +102,7 @@ export const Title = (props: TitleProps) => {
<a className="link tooltip" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">
donate()
{messageBuilderState.getMessage(__filename, "support")}
</div>
</a>
)

View File

@ -12,8 +12,6 @@ export const ClientSelector = () => {
setItem(INDEXEDDB_KEY_DEFAULT_MODEL_TYPE, clientType)
}
const selectableClientTypes = useMemo(() => {
const ua = window.navigator.userAgent.toLowerCase();
if (ua.indexOf("mac os x") !== -1) {

View File

@ -0,0 +1,37 @@
import { useRef } from "react"
export type Message = {
file: string,
id: string,
message: { [lang: string]: string }
}
export type MessageBuilderStateAndMethod = {
setMessage: (file: string, id: string, message: { [lang: string]: string }) => void
getMessage: (file: string, id: string) => string
}
export const useMessageBuilder_old = (): MessageBuilderStateAndMethod => {
const messagesRef = useRef<Message[]>([])
const setMessage = (file: string, id: string, message: { [lang: string]: string }) => {
if (messagesRef.current.find(x => { return x.file == file && x.id == id })) {
console.warn("duplicate message is registerd", file, id, message)
} else {
messagesRef.current.push({ file, id, message })
}
}
const getMessage = (file: string, id: string) => {
let lang = window.navigator.language
if (lang != "ja") {
lang = "en"
}
console.log(messagesRef.current)
return messagesRef.current.find(x => { return x.file == file && x.id == id })?.message[lang] || "unknwon message"
}
return {
setMessage,
getMessage
}
}

View File

@ -27,8 +27,10 @@ export const useAppRoot = (): AppRootValue => {
export const AppRootProvider = ({ children }: Props) => {
const audioContextState = useAudioConfig()
const appGuiSettingState = useAppGuiSetting()
const [clientType, setClientType] = useState<ClientType | null>(null)
useEffect(() => {
if (!clientType) {
return

View File

@ -3,6 +3,7 @@ import React, { useContext, useEffect, useRef } from "react";
import { ReactNode } from "react";
import { useVCClient } from "../001_globalHooks/001_useVCClient";
import { useAppRoot } from "./001_AppRootProvider";
import { useMessageBuilder } from "../hooks/useMessageBuilder";
type Props = {
children: ReactNode;
@ -25,7 +26,14 @@ export const useAppState = (): AppStateValue => {
export const AppStateProvider = ({ children }: Props) => {
const appRoot = useAppRoot()
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext, clientType: appRoot.clientType })
const messageBuilderState = useMessageBuilder()
useEffect(() => {
messageBuilderState.setMessage(__filename, "ioError", {
"ja": "エラーが頻発しています。対象としているフレームワークのモデルがロードされているか確認してください。",
"en": "Frequent errors occur. Please check if the model of the framework being targeted is loaded."
})
}, [])
const initializedRef = useRef<boolean>(false)
useEffect(() => {
@ -60,7 +68,7 @@ export const AppStateProvider = ({ children }: Props) => {
useEffect(() => {
if (clientState.clientState.ioErrorCount > 100) {
alert("エラーが頻発しています。対象としているフレームワークのモデルがロードされているか確認してください。")
alert(messageBuilderState.getMessage(__filename, "ioError"))
clientState.clientState.resetIoErrorCount()
}

View File

@ -1,9 +1,15 @@
import React, { useMemo } from "react";
// import { useGuiState } from "./001_GuiStateProvider";
import { useMessageBuilder } from "../../hooks/useMessageBuilder";
export const WaitingDialog = () => {
// const guiState = useGuiState()
const messageBuilderState = useMessageBuilder()
useMemo(() => {
messageBuilderState.setMessage(__filename, "wait", { "ja": "しばらくお待ちください", "en": "please wait..." })
messageBuilderState.setMessage(__filename, "wait_sub1", { "ja": "ONNXファイルを生成しています。", "en": "generating ONNX file." })
messageBuilderState.setMessage(__filename, "wait_sub2", { "ja": "しばらくお待ちください(1分程度)。", "en": "please wait... (about 1 min)." })
}, [])
const dialog = useMemo(() => {
// const closeButtonRow = (
@ -17,19 +23,21 @@ export const WaitingDialog = () => {
// </div>
// )
const content = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-row left-padding-1">
<div className="body-item-text">
</div>
<div className="body-item-text">
please wait... (about 1 min)
{messageBuilderState.getMessage(__filename, "wait_sub1")}
</div>
<div className="body-item-text">
{messageBuilderState.getMessage(__filename, "wait_sub2")}
</div>
<div className="body-item-text"></div>
</div>
)
return (
<div className="dialog-frame">
<div className="dialog-title">export onnx file</div>
<div className="dialog-title">{messageBuilderState.getMessage(__filename, "wait")}</div>
<div className="dialog-content">
{content}
{/* {closeButtonRow} */}

View File

@ -1,30 +1,42 @@
import React, { useMemo } from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { getMessage } from "./messages/MessageBuilder";
// import { getMessage } from "./messages/MessageBuilder";
import { isDesktopApp } from "../../const";
import { useAppRoot } from "../../001_provider/001_AppRootProvider";
import { useMessageBuilder } from "../../hooks/useMessageBuilder";
export const StartingNoticeDialog = () => {
const guiState = useGuiState()
const { appGuiSettingState } = useAppRoot()
const messageBuilderState = useMessageBuilder()
useMemo(() => {
messageBuilderState.setMessage(__filename, "support", { "ja": "支援", "en": "Donation" })
messageBuilderState.setMessage(__filename, "support_message_1", { "ja": "このソフトウェアを気に入ったら開発者にコーヒーをご馳走してあげよう。黄色いアイコンから。", "en": "This software is supported by donations. Thank you for your support!" })
messageBuilderState.setMessage(__filename, "support_message_2", { "ja": "コーヒーをご馳走する。", "en": "I will support a developer by buying coffee." })
messageBuilderState.setMessage(__filename, "directml_1", { "ja": "directML版は実験的バージョンです。以下の既知の問題があります。", "en": "DirectML version is an experimental version. There are the known issues as follows." })
messageBuilderState.setMessage(__filename, "directml_2", { "ja": "(1) 一部の設定変更を行うとgpuを使用していても変換処理が遅くなることが発生します。もしこの現象が発生したらGPUの値を-1にしてから再度0に戻してください。", "en": "(1) When some settings are changed, conversion process becomes slow even when using GPU. If this occurs, reset the GPU value to -1 and then back to 0." })
messageBuilderState.setMessage(__filename, "click_to_start", { "ja": "スタートボタンを押してください。", "en": "Click to start" })
messageBuilderState.setMessage(__filename, "start", { "ja": "スタート", "en": "start" })
}, [])
const coffeeLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" /> donate
<div className="tooltip-text tooltip-text-100px">donate()</div>
<span className="link" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" /> {messageBuilderState.getMessage(__filename, "support_message_2")}
</span>
)
:
(
<a className="link tooltip" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" /> Donate
<div className="tooltip-text tooltip-text-100px">
donate()
</div>
<a className="link" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" /> {messageBuilderState.getMessage(__filename, "support_message_2")}
</a>
)
}, [])
@ -38,7 +50,9 @@ export const StartingNoticeDialog = () => {
<div className="body-item-text">
</div>
<div className="body-button-container body-button-container-space-around">
<div className="body-button" onClick={() => { guiState.stateControls.showStartingNoticeCheckbox.updateState(false) }} >start</div>
<div className="body-button" onClick={() => { guiState.stateControls.showStartingNoticeCheckbox.updateState(false) }} >
{messageBuilderState.getMessage(__filename, "start")}
</div>
</div>
<div className="body-item-text"></div>
</div>
@ -47,7 +61,7 @@ export const StartingNoticeDialog = () => {
const donationMessage = (
<div className="dialog-content-part">
<div>
{getMessage("donate_1")}
{messageBuilderState.getMessage(__filename, "support_message_1")}
</div>
<div>
{coffeeLink}
@ -58,25 +72,24 @@ export const StartingNoticeDialog = () => {
const directMLMessage = (
<div className="dialog-content-part">
<div>
{getMessage("notice_1")}
{messageBuilderState.getMessage(__filename, "directml_1")}
</div>
<div className="left-padding-1">
{getMessage("notice_2")}
{messageBuilderState.getMessage(__filename, "directml_2")}
</div>
</div>
)
const clickToStartMessage = (
<div className="dialog-content-part">
<div>
{getMessage("click_to_start_1")}
{messageBuilderState.getMessage(__filename, "click_to_start")}
</div>
</div>
)
const lang = window.navigator.language
const edition = appGuiSettingState.edition
const content = (
<div className="body-row">
{lang != "ja" || edition.indexOf("onnxdirectML-cuda") >= 0 ? donationMessage : <></>}
{donationMessage}
{edition.indexOf("onnxdirectML-cuda") >= 0 ? directMLMessage : <></>}
{clickToStartMessage}
</div>

View File

@ -2,6 +2,7 @@ import React, { useMemo, useState } from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { InitialFileUploadSetting, fileSelector } from "@dannadori/voice-changer-client-js";
import { useMessageBuilder } from "../../hooks/useMessageBuilder";
export type uploadData = {
@ -23,8 +24,14 @@ export const ModelSlotManagerDialog = () => {
const [mode, setMode] = useState<Mode>("localFile")
const [fromNetTargetIndex, setFromNetTargetIndex] = useState<number>(0)
const [lang, setLang] = useState<string>("All")
const messageBuilderState = useMessageBuilder()
useMemo(() => {
messageBuilderState.setMessage(__filename, "change_icon", { "ja": "アイコン変更", "en": "chage icon" })
messageBuilderState.setMessage(__filename, "rename", { "ja": "リネーム", "en": "rename" })
messageBuilderState.setMessage(__filename, "download", { "ja": "ダウンロード", "en": "download" })
messageBuilderState.setMessage(__filename, "terms_of_use", { "ja": "利用規約", "en": "terms of use" })
}, [])
/////////////////////////////////////////
// Slot Manager
/////////////////////////////////////////
@ -130,7 +137,7 @@ export const ModelSlotManagerDialog = () => {
const isRegisterd = modelFileName.length > 0 ? true : false
const name = x.name && x.name.length > 0 ? x.name : isRegisterd ? modelFileName : "blank"
const termOfUseUrlLink = x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[terms of use]</a> : <></>
const termOfUseUrlLink = x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[{messageBuilderState.getMessage(__filename, "terms_of_use")}]</a> : <></>
const nameValueClass = isRegisterd ? "model-slot-detail-row-value-pointable" : "model-slot-detail-row-value"
const nameValueAction = isRegisterd ? async (index: number) => {
@ -168,21 +175,41 @@ export const ModelSlotManagerDialog = () => {
return (
<div key={index} className="model-slot">
<img src={iconUrl} className={iconClass} onClick={() => { iconAction(index) }}></img>
<div className="tooltip">
<img src={iconUrl} className={iconClass} onClick={() => { iconAction(index) }} />
<div className="tooltip-text tooltip-text-thin tooltip-text-lower">
{messageBuilderState.getMessage(__filename, "change_icon")}
</div>
</div>
<div className="model-slot-detail">
<div className="model-slot-detail-row">
<div className="model-slot-detail-row-label">[{index}]</div>
<div className={nameValueClass} onClick={() => { nameValueAction(index) }}>{name}</div>
<div className={nameValueClass + " tooltip"} onClick={() => { nameValueAction(index) }}>
{name}
<div className="tooltip-text tooltip-text-thin">
{messageBuilderState.getMessage(__filename, "rename")}
</div>
</div>
<div className="">{termOfUseUrlLink}</div>
</div>
<div className="model-slot-detail-row">
<div className="model-slot-detail-row-label">model:</div>
<div className={fileValueClass} onClick={() => { fileValueAction(x.modelFile) }}>{modelFileName}</div>
<div className={fileValueClass + " tooltip"} onClick={() => { fileValueAction(x.modelFile) }}>
{modelFileName}
<div className="tooltip-text tooltip-text-thin">
{messageBuilderState.getMessage(__filename, "download")}
</div>
</div>
<div className="model-slot-button model-slot-detail-row-button" onClick={() => { onRVCModelLoadClicked(index) }}>select</div>
</div>
<div className="model-slot-detail-row">
<div className="model-slot-detail-row-label">index:</div>
<div className={fileValueClass} onClick={() => { fileValueAction(x.indexFile) }}>{indexFileName}</div>
<div className={fileValueClass + " tooltip"} onClick={() => { fileValueAction(x.indexFile) }}>
{indexFileName}
<div className="tooltip-text tooltip-text-thin">
{messageBuilderState.getMessage(__filename, "download")}
</div>
</div>
<div className="model-slot-button model-slot-detail-row-button" onClick={() => { onRVCIndexLoadClicked(index) }}>select</div>
</div>
<div className="model-slot-detail-row">
@ -209,7 +236,7 @@ export const ModelSlotManagerDialog = () => {
</div>
</div>
</div >
)
})
@ -267,7 +294,7 @@ export const ModelSlotManagerDialog = () => {
}
const options = (
serverSetting.serverSetting.sampleModels.filter(x => { return lang == "All" ? true : x.lang == lang }).map((x, index) => {
const termOfUseUrlLink = x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[terms of use]</a> : <></>
const termOfUseUrlLink = x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[{messageBuilderState.getMessage(__filename, "terms_of_use")}]</a> : <></>
return (
<div key={index} className="model-slot">

View File

@ -4,6 +4,7 @@ import { useGuiState } from "../001_GuiStateProvider";
import { useAppRoot } from "../../../001_provider/001_AppRootProvider";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useIndexedDB } from "@dannadori/voice-changer-client-js";
import { useMessageBuilder } from "../../../hooks/useMessageBuilder";
export type HeaderAreaProps = {
@ -13,6 +14,7 @@ export type HeaderAreaProps = {
export const HeaderArea = (props: HeaderAreaProps) => {
const { appGuiSettingState, setClientType } = useAppRoot()
const messageBuilderState = useMessageBuilder()
const { clientSetting, clearSetting } = useAppState()
const { setIsConverting, isConverting } = useGuiState()
@ -20,6 +22,13 @@ export const HeaderArea = (props: HeaderAreaProps) => {
const { removeItem } = useIndexedDB({ clientType: clientType })
const { setItem } = useIndexedDB({ clientType: null })
useMemo(() => {
messageBuilderState.setMessage(__filename, "github", { "ja": "github", "en": "github" })
messageBuilderState.setMessage(__filename, "manual", { "ja": "マニュアル", "en": "manual" })
messageBuilderState.setMessage(__filename, "screenCapture", { "ja": "録画ツール", "en": "Record Screen" })
messageBuilderState.setMessage(__filename, "support", { "ja": "支援", "en": "Donation" })
}, [])
const githubLink = useMemo(() => {
return isDesktopApp() ?
@ -27,14 +36,14 @@ export const HeaderArea = (props: HeaderAreaProps) => {
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer") }}>
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">github</div>
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</span>
)
:
(
<a className="link tooltip" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">github</div>
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</a>
)
}, [])
@ -46,14 +55,14 @@ export const HeaderArea = (props: HeaderAreaProps) => {
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md") }}>
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text">manual</div>
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</span>
)
:
(
<a className="link tooltip" href="https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text">manual</div>
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</a>
)
}, [])
@ -69,7 +78,7 @@ export const HeaderArea = (props: HeaderAreaProps) => {
// @ts-ignore
window.electronAPI.openBrowser("https://w-okada.github.io/screen-recorder-ts/")
}}>
screen capture
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
</div>
@ -82,7 +91,7 @@ export const HeaderArea = (props: HeaderAreaProps) => {
<p onClick={() => {
window.open("https://w-okada.github.io/screen-recorder-ts/", '_blank', "noreferrer")
}}>
screen capture
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
</div>
@ -96,7 +105,7 @@ export const HeaderArea = (props: HeaderAreaProps) => {
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">donate()</div>
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "support")}</div>
</span>
)
:
@ -104,7 +113,7 @@ export const HeaderArea = (props: HeaderAreaProps) => {
<a className="link tooltip" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">
donate()
{messageBuilderState.getMessage(__filename, "support")}
</div>
</a>
)

View File

@ -1,6 +1,7 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { useGuiState } from "../001_GuiStateProvider"
import { useMessageBuilder } from "../../../hooks/useMessageBuilder"
export type ModelSlotAreaProps = {
}
@ -9,6 +10,13 @@ export type ModelSlotAreaProps = {
export const ModelSlotArea = (_props: ModelSlotAreaProps) => {
const { serverSetting, getInfo } = useAppState()
const guiState = useGuiState()
const messageBuilderState = useMessageBuilder()
useMemo(() => {
messageBuilderState.setMessage(__filename, "edit", { "ja": "編集", "en": "edit" })
}, [])
const modelTiles = useMemo(() => {
if (!serverSetting.serverSetting.modelSlots) {
return []
@ -21,7 +29,7 @@ export const ModelSlotArea = (_props: ModelSlotAreaProps) => {
const name = x.name.length > 8 ? x.name.substring(0, 7) + "..." : x.name
const iconElem = x.iconFile.length > 0 ?
<img className="model-slot-tile-icon" src={x.iconFile} alt={x.name} /> :
<div className="model-slot-tile-icon-no-entry">no entry.</div>
<div className="model-slot-tile-icon-no-entry">no image</div>
const clickAction = async () => {
const dummyModelSlotIndex = (Math.floor(Date.now() / 1000)) * 1000 + index
@ -55,7 +63,7 @@ export const ModelSlotArea = (_props: ModelSlotAreaProps) => {
<div className="model-slot-tiles-container">{modelTiles}</div>
<div className="model-slot-buttons">
<div className="model-slot-button" onClick={onModelSlotEditClicked}>
edit
{messageBuilderState.getMessage(__filename, "edit")}
</div>
</div>

View File

@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { useGuiState } from "../001_GuiStateProvider"
import { OnnxExporterInfo } from "@dannadori/voice-changer-client-js"
import { useMessageBuilder } from "../../../hooks/useMessageBuilder"
export type CharacterAreaProps = {
}
@ -10,6 +11,14 @@ export type CharacterAreaProps = {
export const CharacterArea = (_props: CharacterAreaProps) => {
const { serverSetting, clientSetting, initializedRef, volume, bufferingTime, performance } = useAppState()
const guiState = useGuiState()
const messageBuilderState = useMessageBuilder()
useMemo(() => {
messageBuilderState.setMessage(__filename, "terms_of_use", { "ja": "利用規約", "en": "terms of use" })
messageBuilderState.setMessage(__filename, "export_to_onnx", { "ja": "onnx出力", "en": "export to onnx" })
messageBuilderState.setMessage(__filename, "save_default", { "ja": "設定保存", "en": "save setting" })
messageBuilderState.setMessage(__filename, "alert_onnx", { "ja": "ボイチェン中はonnx出力できません", "en": "cannot export onnx when voice conversion is enabled" })
}, [])
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
@ -38,7 +47,7 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
}
const icon = selected.iconFile.length > 0 ? selected.iconFile : "./assets/icons/human.png"
const selectedTermOfUseUrlLink = selected.termsOfUseUrl ? <a href={selected.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="portrait-area-terms-of-use-link">[terms of use]</a> : <></>
const selectedTermOfUseUrlLink = selected.termsOfUseUrl ? <a href={selected.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="portrait-area-terms-of-use-link">[{messageBuilderState.getMessage(__filename, "terms_of_use")}]</a> : <></>
return (
@ -235,7 +244,7 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
const onnxExportButtonAction = async () => {
if (guiState.isConverting) {
alert("cannot export onnx when voice conversion is enabled")
alert(messageBuilderState.getMessage(__filename, "alert_onnx"))
return
}
@ -253,7 +262,7 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
}
const exportOnnx = selected.modelFile.endsWith("pth") ? (
<div className="character-area-button" onClick={onnxExportButtonAction}>export onnx</div>
<div className="character-area-button" onClick={onnxExportButtonAction}>{messageBuilderState.getMessage(__filename, "export_to_onnx")}</div>
) : <></>
return (
<div className="character-area-control">
@ -262,7 +271,7 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
</div>
<div className="character-area-control-field">
<div className="character-area-buttons">
<div className="character-area-button" onClick={onUpdateDefaultClicked}>save default</div>
<div className="character-area-button" onClick={onUpdateDefaultClicked}>{messageBuilderState.getMessage(__filename, "save_default")}</div>
{exportOnnx}
</div>
</div>

View File

@ -57,7 +57,7 @@ export const RecorderArea = (_props: RecorderAreaProps) => {
<div className="config-sub-area-control left-padding-1">
<div className="config-sub-area-control-title">dev</div>
<div className="config-sub-area-control-title">output</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-auido-io">
<select className="body-select" value={audioOutputForAnalyzer} onChange={(e) => {

View File

@ -1,5 +1,5 @@
const messages: {
const messages_: {
[id: string]: {
[lang: string]: string
}
@ -23,15 +23,15 @@ const messages: {
}
export const getMessage = (id: string) => {
export const getMessage_ = (id: string) => {
let lang = window.navigator.language
if (lang != "ja") {
lang = "en"
}
if (!messages[id]) {
if (!messages_[id]) {
return "undefined message."
}
return messages[id][lang]
return messages_[id][lang]
}

View File

@ -974,7 +974,12 @@ body {
.tooltip-text-100px {
width: 100px;
}
.tooltip-text-thin {
line-height: 1rem;
}
.tooltip-text-right {
line-height: 1rem;
}
.tooltip-text:before {
content: "";
position: absolute;
@ -989,6 +994,11 @@ body {
top: 30px;
left: 0px;
}
.tooltip:hover .tooltip-text-lower {
display: inline-block;
top: 60px;
left: 0px;
}
.tooltip {
position: relative;

View File

@ -0,0 +1,37 @@
import { useRef } from "react"
export type Message = {
file: string,
id: string,
message: { [lang: string]: string }
}
export type MessageBuilderStateAndMethod = {
setMessage: (file: string, id: string, message: { [lang: string]: string }) => void
getMessage: (file: string, id: string) => string
}
export const useMessageBuilder = (): MessageBuilderStateAndMethod => {
const messagesRef = useRef<Message[]>([])
const setMessage = (file: string, id: string, message: { [lang: string]: string }) => {
if (messagesRef.current.find(x => { return x.file == file && x.id == id })) {
console.warn("duplicate message is registerd", file, id, message)
} else {
messagesRef.current.push({ file, id, message })
}
}
const getMessage = (file: string, id: string) => {
let lang = window.navigator.language
if (lang != "ja") {
lang = "en"
}
console.log(messagesRef.current)
return messagesRef.current.find(x => { return x.file == file && x.id == id })?.message[lang] || "unknwon message"
}
return {
setMessage,
getMessage
}
}

View File

@ -1,12 +1,12 @@
{
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.143",
"version": "1.0.144",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.143",
"version": "1.0.144",
"license": "ISC",
"dependencies": {
"@types/readable-stream": "^2.3.15",

View File

@ -1,6 +1,6 @@
{
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.143",
"version": "1.0.144",
"description": "",
"main": "dist/index.js",
"directories": {