Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
ai-box
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
青山
ai-box
Commits
515af0dc
Commit
515af0dc
authored
Sep 11, 2024
by
fisherdaddy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feature: 新增 URL 解码器 & 样式优化 & 支持多语言
parent
6b538bc4
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
477 additions
and
204 deletions
+477
-204
App.jsx
src/App.jsx
+2
-0
JsonFormatter.jsx
src/components/JsonFormatter.jsx
+107
-45
LanguageSelector.jsx
src/components/LanguageSelector.jsx
+3
-1
SharedStyles.js
src/components/SharedStyles.js
+120
-0
TextToImage.jsx
src/components/TextToImage.jsx
+12
-106
UrlDecode.jsx
src/components/UrlDecode.jsx
+131
-0
i18n.js
src/js/i18n.js
+96
-17
Home.jsx
src/pages/Home.jsx
+1
-1
main.css
src/styles/main.css
+5
-34
No files found.
src/App.jsx
View file @
515af0dc
...
...
@@ -5,6 +5,7 @@ import JsonFormatter from './components/JsonFormatter';
import
Header
from
'./components/Header'
;
import
Footer
from
'./components/Footer'
;
import
TextToImage
from
'./components/TextToImage'
;
import
UrlDecode
from
'./components/UrlDecode'
;
function
App
(
op
)
{
return
(
...
...
@@ -16,6 +17,7 @@ function App(op) {
<
Route
path=
"/"
element=
{
<
Home
/>
}
/>
<
Route
path=
"/text2image"
element=
{
<
TextToImage
/>
}
/>
<
Route
path=
"/json-formatter"
element=
{
<
JsonFormatter
/>
}
/>
<
Route
path=
"/url-decode"
element=
{
<
UrlDecode
/>
}
/>
</
Routes
>
</
main
>
</
div
>
...
...
src/components/JsonFormatter.jsx
View file @
515af0dc
import
React
,
{
useState
,
useEffect
}
from
'react'
;
import
styled
from
'styled-components'
;
import
{
Title
,
Wrapper
,
Container
,
Preview
}
from
'./SharedStyles'
;
import
{
useTranslation
}
from
'../js/i18n'
;
const
Container
=
styled
.
div
`
padding: 20px;
max-width: 1200px;
margin: 0 auto;
font-family: 'Arial', sans-serif;
`
;
const
Title
=
styled
.
h2
`
color: #333;
margin-bottom: 20px;
`
;
const
InputText
=
styled
.
textarea
`
width: 100%;
height: 200px;
font-size: 14px;
padding: 10px;
border: none;
border-bottom: 1px solid #e0e0e0;
box-sizing: border-box;
outline: none;
resize: none;
const
FlexContainer
=
styled
.
div
`
display: flex;
gap: 20px;
@media (min-width: 768px) {
width: 35%;
height: 100%;
border-bottom: none;
border-right: 1px solid #e0e0e0;
}
`
;
const
TextArea
=
styled
.
textarea
`
const
PreviewContainer
=
styled
.
div
`
width: 100%;
height: 400px;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
font-size: 14px;
`
;
box-sizing: border-box;
const
JsonContainer
=
styled
.
div
`
flex: 1;
overflow: auto;
height: 400px;
background-color: #f8f8f8;
padding: 10px;
border-radius: 4px;
@media (min-width: 768px) {
width: 65%;
height: 100%;
}
`
;
const
ToggleButton
=
styled
.
span
`
...
...
@@ -58,9 +57,43 @@ const JsonList = styled.ul`
margin: 0;
`
;
const
CopyButton
=
styled
.
button
`
position: absolute;
top: 10px;
right: 10px;
background-color: transparent;
border: none;
cursor: pointer;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.3s, color 0.3s;
&:hover {
opacity: 1;
}
svg {
width: 16px;
height: 16px;
}
&.copied {
color: #34a853; // Google green color for success feedback
}
`
;
const
RelativePreviewContainer
=
styled
(
PreviewContainer
)
`
position: relative;
`
;
function
JsonFormatter
()
{
const
{
t
}
=
useTranslation
();
const
[
input
,
setInput
]
=
useState
(
''
);
const
[
parsedJson
,
setParsedJson
]
=
useState
(
null
);
const
[
isCopied
,
setIsCopied
]
=
useState
(
false
);
useEffect
(()
=>
{
try
{
...
...
@@ -71,26 +104,49 @@ function JsonFormatter() {
}
},
[
input
]);
const
handleCopy
=
()
=>
{
if
(
parsedJson
)
{
const
formattedJson
=
JSON
.
stringify
(
parsedJson
,
null
,
2
);
navigator
.
clipboard
.
writeText
(
formattedJson
).
then
(()
=>
{
setIsCopied
(
true
);
setTimeout
(()
=>
setIsCopied
(
false
),
2000
);
});
}
};
return
(
<
Wrapper
>
<
Title
>
{
t
(
'tools.jsonFormatter.title'
)
}
</
Title
>
<
Container
>
<
Title
>
JSON格式化工具
</
Title
>
<
FlexContainer
>
<
div
style=
{
{
flex
:
1
}
}
>
<
TextArea
<
InputText
placeholder=
{
t
(
'tools.jsonFormatter.inputPlaceholder'
)
}
value=
{
input
}
onChange=
{
(
e
)
=>
setInput
(
e
.
target
.
value
)
}
placeholder=
"输入JSON数据"
/>
</
div
>
<
JsonContainer
>
<
RelativePreviewContainer
>
{
parsedJson
?
(
<>
<
Preview
>
<
JsonView
data=
{
parsedJson
}
/>
</
Preview
>
<
CopyButton
onClick=
{
handleCopy
}
className=
{
isCopied
?
'copied'
:
''
}
>
{
isCopied
?
(
<
svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"currentColor"
>
<
path
d=
"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
/>
</
svg
>
)
:
(
<
svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"currentColor"
>
<
path
d=
"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
/>
</
svg
>
)
}
</
CopyButton
>
</>
)
:
(
<
pre
>
Invalid JSON
</
pre
>
<
Preview
>
{
t
(
'tools.jsonFormatter.invalidJson'
)
}
</
Preview
>
)
}
</
JsonContainer
>
</
FlexContainer
>
</
RelativePreviewContainer
>
</
Container
>
</
Wrapper
>
);
}
...
...
@@ -106,11 +162,14 @@ function JsonView({ data }) {
{
!
isExpanded
&&
<
span
>
Array
</
span
>
}
{
isExpanded
&&
(
<
JsonList
>
[
{
data
.
map
((
item
,
index
)
=>
(
<
li
key=
{
index
}
>
<
JsonView
data=
{
item
}
/>
{
index
<
data
.
length
-
1
&&
','
}
</
li
>
))
}
]
</
JsonList
>
)
}
</
div
>
...
...
@@ -124,11 +183,14 @@ function JsonView({ data }) {
{
!
isExpanded
&&
<
span
>
Object
</
span
>
}
{
isExpanded
&&
(
<
JsonList
>
{
Object
.
entries
(
data
).
map
(([
key
,
value
])
=>
(
{
'{'
}
{
Object
.
entries
(
data
).
map
(([
key
,
value
],
index
,
array
)
=>
(
<
li
key=
{
key
}
>
<
Key
>
"
{
key
}
"
</
Key
>
:
<
JsonView
data=
{
value
}
/>
{
index
<
array
.
length
-
1
&&
','
}
</
li
>
))}
{
'}'
}
</
JsonList
>
)
}
</
div
>
...
...
src/components/LanguageSelector.jsx
View file @
515af0dc
...
...
@@ -13,6 +13,8 @@ function LanguageSelector() {
<
select
id=
"lang-select"
value=
{
lang
}
onChange=
{
handleLanguageChange
}
>
<
option
value=
"zh"
>
中文
</
option
>
<
option
value=
"en"
>
English
</
option
>
<
option
value=
"ja"
>
日本語
</
option
>
<
option
value=
"ko"
>
한국어
</
option
>
</
select
>
</
div
>
);
...
...
src/components/SharedStyles.js
0 → 100644
View file @
515af0dc
import
styled
from
'styled-components'
;
export
const
Title
=
styled
.
h1
`
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
font-size: 28px;
font-weight: 500;
color: #1a73e8; // Google Blue
text-align: center;
margin-bottom: 24px;
letter-spacing: -0.5px;
position: relative;
padding-bottom: 12px;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 40px;
height: 3px;
background: linear-gradient(90deg, #4285f4, #34a853, #fbbc05, #ea4335); // Google colors
border-radius: 2px;
}
`
;
export
const
Wrapper
=
styled
.
div
`
width: 100%;
max-width: 2000px;
margin: 10px auto;
`
;
export
const
Container
=
styled
.
div
`
display: flex;
flex-direction: column;
width: 100%;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin: 10px auto;
@media (min-width: 768px) {
flex-direction: row;
height: 70vh;
}
`
;
export
const
InputText
=
styled
.
textarea
`
width: 100%;
height: 200px;
font-size: 14px;
padding: 10px;
border: none;
border-bottom: 1px solid #e0e0e0;
box-sizing: border-box;
outline: none;
resize: none;
@media (min-width: 768px) {
width: 50%;
height: 100%;
border-bottom: none;
border-right: 1px solid #e0e0e0;
}
`
;
export
const
PreviewContainer
=
styled
.
div
`
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px;
box-sizing: border-box;
@media (min-width: 768px) {
width: 50%;
height: 100%;
}
`
;
export
const
Preview
=
styled
.
div
`
word-wrap: break-word;
white-space: pre-wrap;
max-width: 100%;
text-align: left;
overflow-y: auto;
flex-grow: 1;
padding-right: 10px;
font-size: 14px;
max-height: 200px;
@media (min-width: 768px) {
max-height: none;
}
h1, h2, h3, h4 {
color: #2c3e50;
margin-top: 0.5em;
margin-bottom: 0.3em;
line-height: 1.2;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: #555;
}
`
;
\ No newline at end of file
src/components/TextToImage.jsx
View file @
515af0dc
import
React
,
{
useState
,
useRef
,
useEffect
}
from
'react'
;
import
styled
from
'styled-components'
;
const
Wrapper
=
styled
.
div
`
width: 100%;
max-width: 1000px;
margin: 10px auto;
`
;
const
Title
=
styled
.
h1
`
font-size: 20px;
color: #333;
margin-bottom: 10px;
text-align: center;
`
;
const
Container
=
styled
.
div
`
display: flex;
flex-direction: column;
width: 100%;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin: 10px auto;
@media (min-width: 768px) {
flex-direction: row;
height: 70vh;
}
`
;
const
InputText
=
styled
.
textarea
`
width: 100%;
height: 200px;
font-size: 14px;
padding: 10px;
border: none;
border-bottom: 1px solid #e0e0e0;
box-sizing: border-box;
outline: none;
resize: none;
@media (min-width: 768px) {
width: 50%;
height: 100%;
border-bottom: none;
border-right: 1px solid #e0e0e0;
}
`
;
const
PreviewContainer
=
styled
.
div
`
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px;
box-sizing: border-box;
@media (min-width: 768px) {
width: 50%;
height: 100%;
}
`
;
const
Preview
=
styled
.
div
`
word-wrap: break-word;
white-space: pre-wrap;
max-width: 100%;
text-align: left;
overflow-y: auto;
flex-grow: 1;
padding-right: 10px;
font-size: 14px;
max-height: 200px;
@media (min-width: 768px) {
max-height: none;
}
h1, h2, h3 {
color: #2c3e50;
margin-top: 0;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f1f1f1;
}
&::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: #555;
}
`
;
import
{
Title
,
Wrapper
,
Container
,
InputText
,
PreviewContainer
,
Preview
}
from
'./SharedStyles'
;
import
{
useTranslation
}
from
'../js/i18n'
;
const
DownloadButton
=
styled
.
button
`
padding: 8px 16px;
...
...
@@ -119,15 +21,17 @@ const DownloadButton = styled.button`
`
;
function
TextToImage
()
{
const
{
t
}
=
useTranslation
();
const
[
text
,
setText
]
=
useState
(
''
);
const
previewRef
=
useRef
(
null
);
const
formatText
=
(
text
)
=>
{
return
text
.
replace
(
/^###
(
.*$
)
/gim
,
'<h
3>$1</h3
>'
)
.
replace
(
/^##
(
.*$
)
/gim
,
'<h
2>$1</h2
>'
)
.
replace
(
/^#
(
.*$
)
/gim
,
'<h
1>$1</h1
>'
)
.
replace
(
/^###
(
.*$
)
/gim
,
'<h
4>$1</h4
>'
)
.
replace
(
/^##
(
.*$
)
/gim
,
'<h
3>$1</h3
>'
)
.
replace
(
/^#
(
.*$
)
/gim
,
'<h
2>$1</h2
>'
)
.
replace
(
/
\*\*(
.*
?)\*\*
/g
,
'<strong>$1</strong>'
)
.
replace
(
/
\n{2,}
/g
,
'<br/><br/>'
)
.
replace
(
/
\n
/g
,
'<br/>'
);
};
...
...
@@ -165,10 +69,10 @@ function TextToImage() {
return
(
<
Wrapper
>
<
Title
>
文字卡片生成器
</
Title
>
<
Title
>
{
t
(
'tools.text2image.title'
)
}
</
Title
>
<
Container
>
<
InputText
placeholder=
"输入文本(可包含标题,如# 标题1)"
placeholder=
{
t
(
'tools.text2image.inputPlaceholder'
)
}
value=
{
text
}
onChange=
{
(
e
)
=>
setText
(
e
.
target
.
value
)
}
/>
...
...
@@ -177,7 +81,9 @@ function TextToImage() {
ref=
{
previewRef
}
dangerouslySetInnerHTML=
{
{
__html
:
formatText
(
text
)
}
}
/>
<
DownloadButton
onClick=
{
handleDownload
}
>
导出为图片
</
DownloadButton
>
<
DownloadButton
onClick=
{
handleDownload
}
>
{
t
(
'tools.text2image.downloadButton'
)
}
</
DownloadButton
>
</
PreviewContainer
>
</
Container
>
</
Wrapper
>
...
...
src/components/UrlDecode.jsx
0 → 100644
View file @
515af0dc
import
React
,
{
useState
,
useCallback
}
from
'react'
;
import
{
Title
,
Wrapper
,
Container
,
InputText
,
Preview
}
from
'./SharedStyles'
;
import
styled
from
'styled-components'
;
import
{
useTranslation
}
from
'../js/i18n'
;
const
DecoderContainer
=
styled
(
Container
)
`
flex-direction: column;
`
;
const
StyledInputText
=
styled
(
InputText
)
`
height: 100px;
margin-bottom: 20px;
@media (min-width: 768px) {
height: 100px;
width: 100%;
}
`
;
const
PreviewWrapper
=
styled
.
div
`
width: 100%;
`
;
const
Label
=
styled
.
label
`
font-weight: 500;
font-size: 14px;
color: #5f6368;
margin-bottom: 8px;
display: block;
letter-spacing: 0.1px;
`
;
const
StyledPreview
=
styled
(
Preview
)
`
background-color: #f8f9fa;
padding: 12px 40px 12px 12px; // 增加右侧 padding 为按钮留出空间
border-radius: 8px;
border: 1px solid #dadce0;
font-size: 14px;
color: #202124;
min-height: 24px; // 确保即使内容为空,也有足够的高度容纳按钮
`
;
const
ResultContainer
=
styled
.
div
`
position: relative;
width: 100%;
`
;
const
CopyButton
=
styled
.
button
`
position: absolute;
top: 8px;
right: 8px;
background-color: transparent;
border: none;
cursor: pointer;
padding: 4px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.6;
transition: opacity 0.3s, color 0.3s;
&:hover {
opacity: 1;
}
svg {
width: 16px;
height: 16px;
}
&.copied {
color: #34a853; // Google green color for success feedback
}
`
;
function
UrlDecoder
()
{
const
{
t
}
=
useTranslation
();
const
[
input
,
setInput
]
=
useState
(
''
);
const
[
decodedText
,
setDecodedText
]
=
useState
(
''
);
const
[
isCopied
,
setIsCopied
]
=
useState
(
false
);
const
handleInputChange
=
(
e
)
=>
{
const
inputValue
=
e
.
target
.
value
;
setInput
(
inputValue
);
try
{
const
decoded
=
decodeURIComponent
(
inputValue
);
setDecodedText
(
decoded
);
}
catch
(
error
)
{
setDecodedText
(
'Invalid URL encoding'
);
}
};
const
handleCopy
=
useCallback
(()
=>
{
navigator
.
clipboard
.
writeText
(
decodedText
).
then
(()
=>
{
setIsCopied
(
true
);
setTimeout
(()
=>
setIsCopied
(
false
),
2000
);
});
},
[
decodedText
]);
return
(
<
Wrapper
>
<
Title
>
{
t
(
'tools.urlDecode.title'
)
}
</
Title
>
<
DecoderContainer
>
<
StyledInputText
id=
"urlInput"
placeholder=
{
t
(
'tools.urlDecode.inputLabel'
)
}
value=
{
input
}
onChange=
{
handleInputChange
}
/>
<
PreviewWrapper
>
<
Label
>
{
t
(
'tools.urlDecode.resultLabel'
)
}
</
Label
>
<
ResultContainer
>
<
StyledPreview
>
{
decodedText
}
</
StyledPreview
>
<
CopyButton
onClick=
{
handleCopy
}
className=
{
isCopied
?
'copied'
:
''
}
>
{
isCopied
?
(
<
svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"currentColor"
>
<
path
d=
"M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
/>
</
svg
>
)
:
(
<
svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 24 24"
fill=
"currentColor"
>
<
path
d=
"M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
/>
</
svg
>
)
}
</
CopyButton
>
</
ResultContainer
>
</
PreviewWrapper
>
</
DecoderContainer
>
</
Wrapper
>
);
}
export
default
UrlDecoder
;
\ No newline at end of file
src/js/i18n.js
View file @
515af0dc
import
{
useState
,
useEffect
}
from
'react'
;
const
i18n
=
{
en
:
{
title
:
'AI Toolbox'
,
slogan
:
'Your collection of intelligent assistants, solving various AI needs in one place.'
,
tools
:
{
text2image
:
{
title
:
'Text to Image Card'
,
description
:
'Convert text to image card'
,
inputPlaceholder
:
'Enter text (can include titles, e.g. # Title 1)'
,
downloadButton
:
'Export as Image'
},
jsonFormatter
:
{
title
:
'JSON Formatter'
,
description
:
'Beautify and validate JSON data'
,
inputPlaceholder
:
'Enter JSON data'
,
invalidJson
:
'Invalid JSON'
,
copyButton
:
'Copy'
,
copiedMessage
:
'Copied'
},
urlDecode
:
{
title
:
'URL Decoder'
,
description
:
'Decode URL-encoded strings'
,
inputLabel
:
'Enter URL to decode'
,
resultLabel
:
'Decoded result'
,
copyButton
:
'Copy'
,
copiedMessage
:
'Copied'
},
},
},
zh
:
{
title
:
'AI 工具箱'
,
slogan
:
'您的智能助手集合,一站式解决各种 AI 需求。'
,
tools
:
{
text2image
:
{
title
:
'文字卡片'
,
description
:
'将文字转换为图片卡'
description
:
'将文字转换为图片卡'
,
inputPlaceholder
:
'输入文本(可包含标题,如# 标题1)'
,
downloadButton
:
'导出为图片'
},
jsonFormatter
:
{
title
:
'JSON 格式化'
,
description
:
'美化和验证 JSON 数据'
description
:
'美化和验证 JSON 数据'
,
inputPlaceholder
:
'输入 JSON 数据'
,
invalidJson
:
'无效的 JSON'
,
copyButton
:
'复制'
,
copiedMessage
:
'已复制'
},
// 添加更多工具...
urlDecode
:
{
title
:
'URL 解码器'
,
description
:
'解码 URL 编码的字符串'
,
inputLabel
:
'输入需要解码的 URL'
,
resultLabel
:
'解码结果'
,
copyButton
:
'复制'
,
copiedMessage
:
'已复制'
},
// 添加更多翻译...
},
en
:
{
title
:
'AI Toolbox'
,
slogan
:
'Your collection of intelligent assistants, solving various AI needs in one place.'
,
},
ja
:
{
title
:
'AIツールボックス'
,
slogan
:
'あなたのインテリジェントアシスタントコレクション、様々なAIニーズを一箇所で解決します。'
,
tools
:
{
text2image
:
{
title
:
'Text to Image Card'
,
description
:
'Convert text to image card'
title
:
'テキストから画像'
,
description
:
'テキストを画像カードに変換'
,
inputPlaceholder
:
'テキストを入力(タイトルを含めることができます、例:# タイトル1)'
,
downloadButton
:
'画像としてエクスポート'
},
jsonFormatter
:
{
title
:
'JSON Formatter'
,
description
:
'Beautify and validate JSON data'
title
:
'JSONフォーマッター'
,
description
:
'JSONデータを整形し検証する'
,
inputPlaceholder
:
'JSONデータを入力'
,
invalidJson
:
'無効なJSON'
,
copyButton
:
'コピー'
,
copiedMessage
:
'コピーしました'
},
urlDecode
:
{
title
:
'URLデコーダー'
,
description
:
'URLエンコードされた文字列をデコード'
,
inputLabel
:
'デコードするURLを入力'
,
resultLabel
:
'デコード結果'
,
copyButton
:
'コピー'
,
copiedMessage
:
'コピーしました'
},
},
},
ko
:
{
title
:
'AI 도구 상자'
,
slogan
:
'당신의 지능형 어시스턴트 컬렉션, 다양한 AI 요구 사항을 한 곳에서 해결합니다.'
,
tools
:
{
text2image
:
{
title
:
'텍스트를 이미지로'
,
description
:
'텍스트를 이미지 카드로 변환'
,
inputPlaceholder
:
'텍스트 입력 (제목 포함 가능, 예: # 제목 1)'
,
downloadButton
:
'이미지로 내보내기'
},
jsonFormatter
:
{
title
:
'JSON 포맷터'
,
description
:
'JSON 데이터 정리 및 검증'
,
inputPlaceholder
:
'JSON 데이터 입력'
,
invalidJson
:
'유효하지 않은 JSON'
,
copyButton
:
'복사'
,
copiedMessage
:
'복사됨'
},
urlDecode
:
{
title
:
'URL 디코더'
,
description
:
'URL 인코딩된 문자열 디코딩'
,
inputLabel
:
'디코딩할 URL 입력'
,
resultLabel
:
'디코딩 결과'
,
copyButton
:
'복사'
,
copiedMessage
:
'복사됨'
},
// 添加更多工具...
},
// 添加更多翻译...
},
// 添加更多语言...
};
let
currentLanguage
=
localStorage
.
getItem
(
'language'
)
||
'
zh'
;
// 从本地存储获取语言设置,默认为中文
let
currentLanguage
=
localStorage
.
getItem
(
'language'
)
||
'
en'
;
let
listeners
=
[];
export
function
setLanguage
(
lang
)
{
if
(
i18n
[
lang
])
{
currentLanguage
=
lang
;
localStorage
.
setItem
(
'language'
,
lang
);
// 将语言设置保存到本地存储
localStorage
.
setItem
(
'language'
,
lang
);
listeners
.
forEach
(
listener
=>
listener
(
currentLanguage
));
}
}
...
...
@@ -56,7 +135,7 @@ export function t(key) {
let
value
=
i18n
[
currentLanguage
];
for
(
const
k
of
keys
)
{
if
(
value
[
k
]
===
undefined
)
{
return
key
;
// 如果翻译不存在,返回原始 key
return
key
;
}
value
=
value
[
k
];
}
...
...
src/pages/Home.jsx
View file @
515af0dc
...
...
@@ -6,7 +6,7 @@ import { useTranslation } from '../js/i18n';
const
tools
=
[
{
id
:
'text2image'
,
icon
:
'fa-image'
,
path
:
'/text2image'
},
{
id
:
'jsonFormatter'
,
icon
:
'fa-code'
,
path
:
'/json-formatter'
},
{
id
:
'
textTranslation'
,
icon
:
'fa-language'
,
path
:
'/text-translation
'
},
{
id
:
'
urlDecode'
,
icon
:
'fa-decode'
,
path
:
'/url-decode
'
},
];
function
Home
()
{
...
...
src/styles/main.css
View file @
515af0dc
...
...
@@ -26,8 +26,8 @@ body {
.content-wrapper
{
flex
:
1
;
padding-top
:
2
px
;
padding-bottom
:
3
0px
;
padding-top
:
10
px
;
padding-bottom
:
2
0px
;
}
header
{
...
...
@@ -56,7 +56,7 @@ main {
max-width
:
1200px
;
width
:
100%
;
margin
:
0
auto
;
padding
:
6rem
1rem
4
rem
;
padding
:
3rem
1rem
1
rem
;
}
.hero
{
...
...
@@ -88,35 +88,6 @@ h1 {
margin
:
0
auto
;
}
#tool-search
{
width
:
100%
;
padding
:
1.2rem
1.5rem
;
font-size
:
1.2rem
;
border
:
none
;
border-radius
:
12px
;
background-color
:
rgba
(
255
,
255
,
255
,
0.2
);
color
:
white
;
transition
:
all
0.3s
ease
;
}
#tool-search
::placeholder
{
color
:
rgba
(
255
,
255
,
255
,
0.7
);
}
#tool-search
:focus
{
outline
:
none
;
background-color
:
rgba
(
255
,
255
,
255
,
0.3
);
}
.search-container
i
{
position
:
absolute
;
right
:
1.5rem
;
top
:
50%
;
transform
:
translateY
(
-50%
);
color
:
rgba
(
255
,
255
,
255
,
0.7
);
font-size
:
1.2rem
;
}
.tools-section
h2
{
text-align
:
center
;
font-size
:
3rem
;
...
...
@@ -167,7 +138,6 @@ footer {
background-color
:
var
(
--card-background
);
color
:
#86868b
;
text-align
:
center
;
padding
:
1.5rem
;
font-size
:
0.9rem
;
}
...
...
@@ -184,3 +154,4 @@ footer {
font-size
:
1.5rem
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment