Add Multilanguage support for creator notes
This commit is contained in:
@@ -5,17 +5,13 @@
|
|||||||
<XIcon />
|
<XIcon />
|
||||||
</button>
|
</button>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="ml-2 max-w-full break-words text chat chattext prose prose-invert">
|
<MultiLangDisplay value={quote} markdown={true} />
|
||||||
{#await ParseMarkdown(quote) then md}
|
|
||||||
{@html md}
|
|
||||||
{/await}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { XIcon } from "lucide-svelte";
|
import { XIcon } from "lucide-svelte";
|
||||||
import { language } from "src/lang";
|
import { language } from "src/lang";
|
||||||
import { ParseMarkdown } from "src/ts/parser";
|
import MultiLangDisplay from "../UI/GUI/MultiLangDisplay.svelte";
|
||||||
|
|
||||||
export let onRemove: () => void
|
export let onRemove: () => void
|
||||||
export let quote:string
|
export let quote:string
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
import CheckInput from "../UI/GUI/CheckInput.svelte";
|
import CheckInput from "../UI/GUI/CheckInput.svelte";
|
||||||
import { updateInlayScreen } from "src/ts/process/inlayScreen";
|
import { updateInlayScreen } from "src/ts/process/inlayScreen";
|
||||||
import { registerOnnxModel } from "src/ts/process/embedding/transformers";
|
import { registerOnnxModel } from "src/ts/process/embedding/transformers";
|
||||||
|
import MultiLangInput from "../UI/GUI/MultiLangInput.svelte";
|
||||||
|
|
||||||
|
|
||||||
let subMenu = 0
|
let subMenu = 0
|
||||||
@@ -653,9 +654,9 @@
|
|||||||
<TextAreaInput margin="both" autocomplete="off" bind:value={currentChar.data.exampleMessage}></TextAreaInput>
|
<TextAreaInput margin="both" autocomplete="off" bind:value={currentChar.data.exampleMessage}></TextAreaInput>
|
||||||
|
|
||||||
<span class="text-textcolor">{language.creatorNotes} <Help key="creatorQuotes"/></span>
|
<span class="text-textcolor">{language.creatorNotes} <Help key="creatorQuotes"/></span>
|
||||||
<TextAreaInput margin="both" autocomplete="off" bind:value={currentChar.data.creatorNotes} on:input={() => {
|
<MultiLangInput bind:value={currentChar.data.creatorNotes} className="my-2" onInput={() => {
|
||||||
currentChar.data.removedQuotes = false
|
currentChar.data.removedQuotes = false
|
||||||
}}></TextAreaInput>
|
}}></MultiLangInput>
|
||||||
|
|
||||||
<span class="text-textcolor">{language.systemPrompt} <Help key="systemPrompt"/></span>
|
<span class="text-textcolor">{language.systemPrompt} <Help key="systemPrompt"/></span>
|
||||||
<TextAreaInput margin="both" autocomplete="off" bind:value={currentChar.data.systemPrompt}></TextAreaInput>
|
<TextAreaInput margin="both" autocomplete="off" bind:value={currentChar.data.systemPrompt}></TextAreaInput>
|
||||||
|
|||||||
35
src/lib/UI/GUI/MultiLangDisplay.svelte
Normal file
35
src/lib/UI/GUI/MultiLangDisplay.svelte
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { ParseMarkdown } from "src/ts/parser";
|
||||||
|
import { parseMultilangString, toLangName } from "src/ts/util";
|
||||||
|
|
||||||
|
export let value: string
|
||||||
|
export let markdown: boolean = false
|
||||||
|
let valueObject: {[code:string]:string} = parseMultilangString(value)
|
||||||
|
let selectedLang = "en"
|
||||||
|
if(valueObject["en"] === undefined){
|
||||||
|
selectedLang = "xx"
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="flex flex-wrap max-w-fit p-1 gap-2">
|
||||||
|
{#each Object.keys(valueObject) as lang}
|
||||||
|
{#if lang !== 'xx' || Object.keys(valueObject).length === 1}
|
||||||
|
<button class="bg-bgcolor py-2 rounded-lg px-4" class:ring-1={selectedLang === lang} on:click|stopPropagation={() => {
|
||||||
|
selectedLang = lang
|
||||||
|
}}>{toLangName(lang)}</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{#if markdown}
|
||||||
|
<div class="ml-2 max-w-full break-words text chat chattext prose prose-invert">
|
||||||
|
{#await ParseMarkdown(valueObject[selectedLang]) then md}
|
||||||
|
{@html md}
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="ml-2 max-w-full break-words text chat chattext prose prose-invert">
|
||||||
|
{valueObject[selectedLang]}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
54
src/lib/UI/GUI/MultiLangInput.svelte
Normal file
54
src/lib/UI/GUI/MultiLangInput.svelte
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { encodeMultilangString, languageCodes, parseMultilangString, toLangName } from "src/ts/util";
|
||||||
|
import TextAreaInput from "./TextAreaInput.svelte";
|
||||||
|
export let value: string
|
||||||
|
let addingLang = false
|
||||||
|
let valueObject: {[code:string]:string} = parseMultilangString(value)
|
||||||
|
let selectedLang = "en"
|
||||||
|
export let className = ""
|
||||||
|
export let onInput = () => {}
|
||||||
|
const updateValue = () => {
|
||||||
|
for(let lang in valueObject){
|
||||||
|
if(valueObject[lang] === "" && lang !== selectedLang && lang!=="en" ){
|
||||||
|
delete valueObject[lang]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(valueObject.xx){
|
||||||
|
delete valueObject.xx
|
||||||
|
}
|
||||||
|
valueObject = valueObject // force update
|
||||||
|
value = encodeMultilangString(valueObject)
|
||||||
|
}
|
||||||
|
if(valueObject["en"] === undefined){
|
||||||
|
valueObject["en"] = valueObject["xx"]
|
||||||
|
delete valueObject["xx"]
|
||||||
|
updateValue()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap max-w-fit p-1 gap-2">
|
||||||
|
{#each Object.keys(valueObject) as lang}
|
||||||
|
{#if lang !== 'xx'}
|
||||||
|
<button class="bg-bgcolor py-2 rounded-lg px-4" class:ring-1={selectedLang === lang} on:click={() => {
|
||||||
|
selectedLang = lang
|
||||||
|
updateValue()
|
||||||
|
}}>{toLangName(lang)}</button>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
<button class="text-nowrap bg-bgcolor py-2 rounded-lg px-4" class:ring-1={addingLang} on:click={() => {addingLang = !addingLang}}>+</button>
|
||||||
|
</div>
|
||||||
|
{#if addingLang}
|
||||||
|
<div class="m-1 p-1 g-2 flex max-w-fit rounded-md border-t-bgcolor flex-wrap gap-1">
|
||||||
|
{#each languageCodes as lang}
|
||||||
|
<button class="bg-bgcolor py-2 rounded-lg px-4 text-nowrap" on:click={() => {
|
||||||
|
valueObject[lang] = ""
|
||||||
|
selectedLang = lang
|
||||||
|
addingLang = false
|
||||||
|
}}>{toLangName(lang)}</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<TextAreaInput autocomplete="off" bind:value={valueObject[selectedLang]} height={"20"} onInput={() => {
|
||||||
|
updateValue()
|
||||||
|
onInput()
|
||||||
|
}} additionalClass={className} />
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import { alertNormal } from "src/ts/alert";
|
import { alertNormal } from "src/ts/alert";
|
||||||
import { hubURL, type hubType } from "src/ts/characterCards";
|
import { hubURL, type hubType } from "src/ts/characterCards";
|
||||||
import { trimNonLatin } from "src/ts/storage/globalApi";
|
import { trimNonLatin } from "src/ts/storage/globalApi";
|
||||||
|
import { parseMultilangString } from "src/ts/util";
|
||||||
|
|
||||||
export let onClick = () => {}
|
export let onClick = () => {}
|
||||||
export let chara:hubType
|
export let chara:hubType
|
||||||
@@ -10,11 +11,12 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<button class="bg-darkbg rounded-lg p-4 flex flex-col hover:bg-selected transition-colors relative lg:w-96 w-full items-start" on:click={onClick}> <div class="flex gap-2 w-full">
|
<button class="bg-darkbg rounded-lg p-4 flex flex-col hover:bg-selected transition-colors relative lg:w-96 w-full items-start" on:click={onClick}>
|
||||||
|
<div class="flex gap-2 w-full">
|
||||||
<img class="w-20 min-w-20 h-20 sm:h-28 sm:w-28 rounded-md object-top object-cover" alt={chara.name} src={`${hubURL}/resource/` + chara.img}>
|
<img class="w-20 min-w-20 h-20 sm:h-28 sm:w-28 rounded-md object-top object-cover" alt={chara.name} src={`${hubURL}/resource/` + chara.img}>
|
||||||
<div class="flex flex-col flex-grow min-w-0">
|
<div class="flex flex-col flex-grow min-w-0">
|
||||||
<span class="text-textcolor text-lg min-w-0 max-w-full text-ellipsis whitespace-nowrap overflow-hidden text-start">{chara.name}</span>
|
<span class="text-textcolor text-lg min-w-0 max-w-full text-ellipsis whitespace-nowrap overflow-hidden text-start">{chara.name}</span>
|
||||||
<span class="text-textcolor2 text-xs min-w-0 max-w-full text-ellipsis break-words max-h-8 whitespace-nowrap overflow-hidden text-start">{trimNonLatin(chara.desc)}</span>
|
<span class="text-textcolor2 text-xs min-w-0 max-w-full text-ellipsis break-words max-h-8 whitespace-nowrap overflow-hidden text-start">{parseMultilangString(chara.desc).en ?? parseMultilangString(chara.desc).xx}</span>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
{#each chara.tags as tag, i}
|
{#each chara.tags as tag, i}
|
||||||
{#if i < 4}
|
{#if i < 4}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { parseMarkdownSafe } from "src/ts/parser";
|
import { parseMarkdownSafe } from "src/ts/parser";
|
||||||
import { DataBase } from "src/ts/storage/database";
|
import { DataBase } from "src/ts/storage/database";
|
||||||
import RealmLicense from "./RealmLicense.svelte";
|
import RealmLicense from "./RealmLicense.svelte";
|
||||||
import { characterFormatUpdate } from "src/ts/characters";
|
import MultiLangDisplay from "../GUI/MultiLangDisplay.svelte";
|
||||||
|
|
||||||
export let openedData:hubType
|
export let openedData:hubType
|
||||||
|
|
||||||
@@ -25,9 +25,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<div class="flex justify-start gap-4 mt-4">
|
<div class="flex justify-start gap-4 mt-4">
|
||||||
<img class="h-36 w-36 rounded-md object-top object-cover" alt={openedData.name} src={`${hubURL}/resource/` + openedData.img}>
|
<img class="h-36 w-36 rounded-md object-top object-cover" alt={openedData.name} src={`${hubURL}/resource/` + openedData.img}>
|
||||||
<span class="text-textcolor2 break-words text-base chattext prose prose-invert">
|
<MultiLangDisplay value={openedData.desc} markdown={true} />
|
||||||
{@html parseMarkdownSafe(openedData.desc)}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<RealmLicense license={openedData.license}/>
|
<RealmLicense license={openedData.license}/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
<div class="fixed top-0 left-0 h-full w-full bg-black bg-opacity-50 flex flex-col z-50 items-center justify-center" on:click={close}>
|
<div class="fixed top-0 left-0 h-full w-full bg-black bg-opacity-50 flex flex-col z-50 items-center justify-center" on:click={close}>
|
||||||
<div class="bg-darkbg rounded-md p-4 max-w-full flex flex-col w-2xl" on:click|stopPropagation>
|
<div class="bg-darkbg rounded-md p-4 max-w-full flex flex-col w-2xl max-h-full overflow-y-auto" on:click|stopPropagation>
|
||||||
|
|
||||||
{#if !$DataBase.account}
|
{#if !$DataBase.account}
|
||||||
<span class="font-bold text-2xl w-full">You must login to Risu Account upload to RisuRealm</span>
|
<span class="font-bold text-2xl w-full">You must login to Risu Account upload to RisuRealm</span>
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
<span class="text-textcolor">{language.creatorNotes}</span>
|
<span class="text-textcolor">{language.creatorNotes}</span>
|
||||||
<span class="text-textcolor2 text-sm">A description that displays when you search and when you first open a bot.</span>
|
<span class="text-textcolor2 text-sm">A description that displays when you search and when you first open a bot.</span>
|
||||||
<span class="text-textcolor2 text-sm">More than 20 characters.</span>
|
<span class="text-textcolor2 text-sm">More than 20 characters.</span>
|
||||||
<TextAreaInput autocomplete="off" bind:value={char.creatorNotes} height={"20"} />
|
<MultiLangInput bind:value={char.creatorNotes} />
|
||||||
<span class="text-textcolor">{language.tags}</span>
|
<span class="text-textcolor">{language.tags}</span>
|
||||||
<span class="text-textcolor2 text-sm">Tags to search your character easily. latin alphabets only. seperate by comma.</span>
|
<span class="text-textcolor2 text-sm">Tags to search your character easily. latin alphabets only. seperate by comma.</span>
|
||||||
<TextInput placeholder="" bind:value={tags} on:input={() => {
|
<TextInput placeholder="" bind:value={tags} on:input={() => {
|
||||||
@@ -53,18 +53,21 @@
|
|||||||
<span class="text-textcolor2 text-sm">Grotesque Contents and non-adult characters with NSFW would be banned.</span>
|
<span class="text-textcolor2 text-sm">Grotesque Contents and non-adult characters with NSFW would be banned.</span>
|
||||||
{/if}
|
{/if}
|
||||||
<Button on:click={async () => {
|
<Button on:click={async () => {
|
||||||
if(char.creatorNotes.length < 20){
|
const enNotes = creatorNotes.en
|
||||||
alertError("Creator Notes must be longer than 20 characters")
|
const latin1 = /^[\x00-\xFF]*$/
|
||||||
|
if(enNotes.length < 10){
|
||||||
|
alertError("English version of creator notes must be longer than 10 characters")
|
||||||
}
|
}
|
||||||
else{
|
if(!latin1.test(enNotes)){
|
||||||
shareRisuHub(char, {
|
alertError("English version of creator notes must contain only Latin-1 characters")
|
||||||
anon: privateMode,
|
|
||||||
nsfw: nsfwMode,
|
|
||||||
tag: tags,
|
|
||||||
license: license
|
|
||||||
})
|
|
||||||
close()
|
|
||||||
}
|
}
|
||||||
|
shareRisuHub(char, {
|
||||||
|
anon: privateMode,
|
||||||
|
nsfw: nsfwMode,
|
||||||
|
tag: tags,
|
||||||
|
license: license
|
||||||
|
})
|
||||||
|
close()
|
||||||
}} className="mt-2" size="lg">{language.shareCloud}</Button>
|
}} className="mt-2" size="lg">{language.shareCloud}</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -79,16 +82,18 @@
|
|||||||
import { shareRisuHub } from "src/ts/characterCards";
|
import { shareRisuHub } from "src/ts/characterCards";
|
||||||
import { DataBase, type character } from "src/ts/storage/database";
|
import { DataBase, type character } from "src/ts/storage/database";
|
||||||
import TextInput from "../GUI/TextInput.svelte";
|
import TextInput from "../GUI/TextInput.svelte";
|
||||||
import TextAreaInput from "../GUI/TextAreaInput.svelte";
|
|
||||||
import Button from "../GUI/Button.svelte";
|
import Button from "../GUI/Button.svelte";
|
||||||
import SelectInput from "../GUI/SelectInput.svelte";
|
import SelectInput from "../GUI/SelectInput.svelte";
|
||||||
import { CCLicenseData } from "src/ts/creation/license";
|
import { CCLicenseData } from "src/ts/creation/license";
|
||||||
import OptionInput from "../GUI/OptionInput.svelte";
|
import OptionInput from "../GUI/OptionInput.svelte";
|
||||||
|
import { parseMultilangString } from "src/ts/util";
|
||||||
|
import MultiLangInput from "../GUI/MultiLangInput.svelte";
|
||||||
export let close = () => {}
|
export let close = () => {}
|
||||||
export let char:character
|
export let char:character
|
||||||
let tags=""
|
let tags=""
|
||||||
let privateMode = false
|
let privateMode = false
|
||||||
let nsfwMode = false
|
let nsfwMode = false
|
||||||
let license = ""
|
let license = ""
|
||||||
|
let creatorNotes: {[code:string]:string} = parseMultilangString(char.creatorNotes)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -383,4 +383,43 @@ export function BufferToText(data:Uint8Array){
|
|||||||
return Buffer.from(data).toString('utf-8')
|
return Buffer.from(data).toString('utf-8')
|
||||||
}
|
}
|
||||||
return new TextDecoder().decode(data)
|
return new TextDecoder().decode(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function encodeMultilangString(data:{[code:string]:string}){
|
||||||
|
let result = ''
|
||||||
|
if(data.en){
|
||||||
|
result = data.en
|
||||||
|
}
|
||||||
|
for(const key in data){
|
||||||
|
result = `${result}\n<div hidden x-recc-lang="${key}">${data[key]}</div>`
|
||||||
|
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseMultilangString(data:string){
|
||||||
|
let result:{[code:string]:string} = {}
|
||||||
|
const regex = /<div hidden x-recc-lang="(.+?)">(.*?)<\/div>/g
|
||||||
|
let m:RegExpExecArray
|
||||||
|
while ((m = regex.exec(data)) !== null) {
|
||||||
|
if (m.index === regex.lastIndex) {
|
||||||
|
regex.lastIndex++;
|
||||||
|
}
|
||||||
|
result[m[1]] = m[2]
|
||||||
|
}
|
||||||
|
result.xx = data.replace(regex, '')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toLangName = (code:string) => {
|
||||||
|
switch(code){
|
||||||
|
case 'xx':{ //Special case for unknown language
|
||||||
|
return 'Unknown Language'
|
||||||
|
}
|
||||||
|
default:{
|
||||||
|
return new Intl.DisplayNames([code, 'en'], {type: 'language'}).of(code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const languageCodes = ["af","ak","am","an","ar","as","ay","az","be","bg","bh","bm","bn","br","bs","ca","co","cs","cy","da","de","dv","ee","el","en","eo","es","et","eu","fa","fi","fo","fr","fy","ga","gd","gl","gn","gu","ha","he","hi","hr","ht","hu","hy","ia","id","ig","is","it","iu","ja","jv","ka","kk","km","kn","ko","ku","ky","la","lb","lg","ln","lo","lt","lv","mg","mi","mk","ml","mn","mr","ms","mt","my","nb","ne","nl","nn","no","ny","oc","om","or","pa","pl","ps","pt","qu","rm","ro","ru","rw","sa","sd","si","sk","sl","sm","sn","so","sq","sr","st","su","sv","sw","ta","te","tg","th","ti","tk","tl","tn","to","tr","ts","tt","tw","ug","uk","ur","uz","vi","wa","wo","xh","yi","yo","zh","zu"]
|
||||||
Reference in New Issue
Block a user