feat: internationalize sound data and buttons

This commit is contained in:
yozuru 2025-04-19 23:52:34 +08:00
parent c8273371e0
commit 10f59a55a6
No known key found for this signature in database
22 changed files with 512 additions and 190 deletions

View file

@ -32,7 +32,9 @@ const paragraphKeys = [
</div> </div>
)) ))
} }
<button class="button" id="use-moodist">{t('buttons.useMoodist')}</button> <button class="button" id="use-moodist"
>{t('buttons.use-moodist.label')}</button
>
</Container> </Container>
</section> </section>
@ -60,8 +62,7 @@ const paragraphKeys = [
& .paragraph { & .paragraph {
padding: 30px 0; padding: 30px 0;
background: linear-gradient( background: linear-gradient(
transparent, transparent var(--color-neutral-50) 10%,
var(--color-neutral-50) 10%,
var(--color-neutral-50) 90%, var(--color-neutral-50) 90%,
transparent transparent
); );

View file

@ -2,7 +2,7 @@ import { useMemo, useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { BiSolidHeart } from 'react-icons/bi/index'; import { BiSolidHeart } from 'react-icons/bi/index';
import { Howler } from 'howler'; import { Howler } from 'howler';
import { I18nextProvider, useTranslation } from 'react-i18next'; import { I18nextProvider } from 'react-i18next';
import i18n from '@/i18n'; import i18n from '@/i18n';
import { useSoundStore } from '@/stores/sound'; import { useSoundStore } from '@/stores/sound';
@ -23,17 +23,14 @@ import type { Sound, Category as CategoryType } from '@/data/types';
import { subscribe } from '@/lib/event'; import { subscribe } from '@/lib/event';
interface AppProps { interface AppProps {
locale: string; // 接收来自 Astro 的 locale locale: string;
} }
export function App({ locale }: AppProps) { export function App({ locale }: AppProps) {
if (locale && i18n.language !== locale) { if (locale && i18n.language !== locale) {
i18n.changeLanguage(locale); i18n.changeLanguage(locale);
} }
const { t } = useTranslation();
const categoriesData = useMemo(() => sounds.categories, []); const categoriesData = useMemo(() => sounds.categories, []);
const categories = categoriesData; // 暂时不翻译
const favorites = useSoundStore(useShallow(state => state.getFavorites())); const favorites = useSoundStore(useShallow(state => state.getFavorites()));
const pause = useSoundStore(state => state.pause); const pause = useSoundStore(state => state.pause);
@ -48,7 +45,6 @@ export function App({ locale }: AppProps) {
favorites.includes(sound.id), favorites.includes(sound.id),
); );
// 暂时不翻译 sound labels
return favorites return favorites
.map(favoriteId => .map(favoriteId =>
favoriteSoundsData.find(sound => sound.id === favoriteId), favoriteSoundsData.find(sound => sound.id === favoriteId),
@ -92,11 +88,11 @@ export function App({ locale }: AppProps) {
icon: <BiSolidHeart />, icon: <BiSolidHeart />,
id: 'favorites', id: 'favorites',
sounds: favoriteSounds, sounds: favoriteSounds,
title: t('categories.favorites'), titleKey: 'sounds.favorites.title',
}); });
} }
return [...favs, ...categories]; return [...favs, ...categoriesData];
}, [favoriteSounds, categories, t]); }, [favoriteSounds, categoriesData]);
return ( return (
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>

View file

@ -44,14 +44,14 @@ export function PlayButton() {
<span aria-hidden="true"> <span aria-hidden="true">
<BiPause /> <BiPause />
</span>{' '} </span>{' '}
{t('common.pause')} {t('buttons.pause.label')}
</> </>
) : ( ) : (
<> <>
<span aria-hidden="true"> <span aria-hidden="true">
<BiPlay /> <BiPlay />
</span>{' '} </span>{' '}
{t('common.play')} {t('buttons.play.label')}
</> </>
)} )}
</button> </button>

View file

@ -11,13 +11,15 @@ import { fade, mix, slideX } from '@/lib/motion';
import styles from './unselect.module.css'; import styles from './unselect.module.css';
import { useTranslation } from 'react-i18next';
export function UnselectButton() { export function UnselectButton() {
const noSelected = useSoundStore(state => state.noSelected()); const noSelected = useSoundStore(state => state.noSelected());
const restoreHistory = useSoundStore(state => state.restoreHistory); const restoreHistory = useSoundStore(state => state.restoreHistory);
const hasHistory = useSoundStore(state => !!state.history); const hasHistory = useSoundStore(state => !!state.history);
const unselectAll = useSoundStore(state => state.unselectAll); const unselectAll = useSoundStore(state => state.unselectAll);
const locked = useSoundStore(state => state.locked); const locked = useSoundStore(state => state.locked);
const { t } = useTranslation();
const variants = { const variants = {
...mix(fade(), slideX(15)), ...mix(fade(), slideX(15)),
exit: { opacity: 0 }, exit: { opacity: 0 },
@ -45,16 +47,16 @@ export function UnselectButton() {
showDelay={0} showDelay={0}
content={ content={
hasHistory hasHistory
? 'Restore unselected sounds.' ? t('buttons.unselect.restore.tooltip')
: 'Unselect all sounds.' : t('buttons.unselect.tooltip')
} }
> >
<button <button
disabled={noSelected && !hasHistory} disabled={noSelected && !hasHistory}
aria-label={ aria-label={
hasHistory hasHistory
? 'Restore Unselected Sounds' ? t('buttons.unselect.restore.aria-label')
: 'Unselect All Sounds' : t('buttons.unselect.aria-label')
} }
className={cn( className={cn(
styles.unselectButton, styles.unselectButton,

View file

@ -1,11 +1,12 @@
import { useTranslation } from 'react-i18next';
import { Sounds } from '@/components/sounds'; import { Sounds } from '@/components/sounds';
import styles from './category.module.css'; import styles from './category.module.css';
import type { Category as CategoryType } from '@/data/types';
import type { Category } from '@/data/types'; interface CategoryProps extends Omit<CategoryType, 'title'> {
interface CategoryProps extends Category {
functional?: boolean; functional?: boolean;
titleKey: string;
} }
export function Category({ export function Category({
@ -13,8 +14,10 @@ export function Category({
icon, icon,
id, id,
sounds, sounds,
title, titleKey,
}: CategoryProps) { }: CategoryProps) {
const { t } = useTranslation();
return ( return (
<div className={styles.category} id={`category-${id}`}> <div className={styles.category} id={`category-${id}`}>
<div className={styles.iconContainer}> <div className={styles.iconContainer}>
@ -24,7 +27,7 @@ export function Category({
</div> </div>
</div> </div>
<div className={styles.title}>{title}</div> <div className={styles.title}>{t(titleKey)}</div>
<Sounds functional={functional} id={id} sounds={sounds} /> <Sounds functional={functional} id={id} sounds={sounds} />
</div> </div>

View file

@ -1,5 +1,6 @@
import { BiHeart, BiSolidHeart } from 'react-icons/bi/index'; import { BiHeart, BiSolidHeart } from 'react-icons/bi/index';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { useSoundStore } from '@/stores/sound'; import { useSoundStore } from '@/stores/sound';
import { cn } from '@/helpers/styles'; import { cn } from '@/helpers/styles';
@ -16,7 +17,8 @@ interface FavoriteProps {
} }
export function Favorite({ id, label }: FavoriteProps) { export function Favorite({ id, label }: FavoriteProps) {
const isFavorite = useSoundStore(state => state.sounds[id].isFavorite); const { t } = useTranslation();
const isFavorite = useSoundStore(state => state.sounds[id]?.isFavorite);
const toggleFavorite = useSoundStore(state => state.toggleFavorite); const toggleFavorite = useSoundStore(state => state.toggleFavorite);
const handleToggle = async () => { const handleToggle = async () => {
@ -36,18 +38,21 @@ export function Favorite({ id, label }: FavoriteProps) {
}; };
const variants = fade(); const variants = fade();
const handleKeyDown = useKeyboardButton(handleToggle); const handleKeyDown = useKeyboardButton(handleToggle);
const ariaLabel = isFavorite
? t('buttons.favorite.remove.aria-label', { label: label })
: t('buttons.favorite.add.aria-label', { label: label });
if (useSoundStore.getState().sounds[id] === undefined) {
return null;
}
return ( return (
<AnimatePresence initial={false} mode="wait"> <AnimatePresence initial={false} mode="wait">
<button <button
aria-label={ariaLabel}
className={cn(styles.favoriteButton, isFavorite && styles.isFavorite)} className={cn(styles.favoriteButton, isFavorite && styles.isFavorite)}
aria-label={
isFavorite
? `Remove ${label} Sound from Favorites`
: `Add ${label} Sound to Favorites`
}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();

View file

@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next';
import { useSoundStore } from '@/stores/sound'; import { useSoundStore } from '@/stores/sound';
import styles from './range.module.css'; import styles from './range.module.css';
@ -8,14 +9,20 @@ interface RangeProps {
} }
export function Range({ id, label }: RangeProps) { export function Range({ id, label }: RangeProps) {
const { t } = useTranslation();
const setVolume = useSoundStore(state => state.setVolume); const setVolume = useSoundStore(state => state.setVolume);
const volume = useSoundStore(state => state.sounds[id].volume); const soundState = useSoundStore(state => state.sounds[id]);
const isSelected = useSoundStore(state => state.sounds[id].isSelected);
const locked = useSoundStore(state => state.locked); const locked = useSoundStore(state => state.locked);
if (!soundState) {
return null;
}
const { isSelected, volume } = soundState;
return ( return (
<input <input
aria-label={`${label} sound volume`} aria-label={t('volume.aria-label', { label: label })}
autoComplete="off" autoComplete="off"
className={styles.range} className={styles.range}
disabled={!isSelected} disabled={!isSelected}

View file

@ -1,5 +1,6 @@
import { useCallback, useEffect, forwardRef, useMemo } from 'react'; import { useCallback, useEffect, forwardRef, useMemo } from 'react';
import { ImSpinner9 } from 'react-icons/im/index'; import { ImSpinner9 } from 'react-icons/im/index';
import { useTranslation } from 'react-i18next'; // 导入 useTranslation
import { Range } from './range'; import { Range } from './range';
import { Favorite } from './favorite'; import { Favorite } from './favorite';
@ -15,17 +16,21 @@ import type { Sound as SoundType } from '@/data/types';
import { useKeyboardButton } from '@/hooks/use-keyboard-button'; import { useKeyboardButton } from '@/hooks/use-keyboard-button';
interface SoundProps extends SoundType { interface SoundProps extends Omit<SoundType, 'label'> {
functional: boolean; functional: boolean;
hidden: boolean; hidden: boolean;
labelKey: string;
selectHidden: (key: string) => void; selectHidden: (key: string) => void;
unselectHidden: (key: string) => void; unselectHidden: (key: string) => void;
} }
export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound( export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound(
{ functional, hidden, icon, id, label, selectHidden, src, unselectHidden }, { functional, hidden, icon, id, labelKey, selectHidden, src, unselectHidden },
ref, ref,
) { ) {
const { t } = useTranslation(); // 获取 t 函数
const translatedLabel = useMemo(() => t(labelKey), [t, labelKey]);
const isPlaying = useSoundStore(state => state.isPlaying); const isPlaying = useSoundStore(state => state.isPlaying);
const play = useSoundStore(state => state.play); const play = useSoundStore(state => state.play);
const selectSound = useSoundStore(state => state.select); const selectSound = useSoundStore(state => state.select);
@ -43,22 +48,22 @@ export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound(
const isLoading = useLoadingStore(state => state.loaders[src]); const isLoading = useLoadingStore(state => state.loaders[src]);
const sound = useSound(src, { loop: true, volume: adjustedVolume }); const soundControls = useSound(src, { loop: true, volume: adjustedVolume });
useEffect(() => { useEffect(() => {
if (locked) return; if (locked) return;
if (isSelected && isPlaying && functional) { if (isSelected && isPlaying && functional) {
sound?.play(); soundControls?.play();
} else { } else {
sound?.pause(); soundControls?.pause();
} }
}, [isSelected, sound, isPlaying, functional, locked]); }, [isSelected, soundControls, isPlaying, functional, locked]);
useEffect(() => { useEffect(() => {
if (hidden && isSelected) selectHidden(label); if (hidden && isSelected) selectHidden(labelKey);
else if (hidden && !isSelected) unselectHidden(label); else if (hidden && !isSelected) unselectHidden(labelKey);
}, [label, isSelected, hidden, selectHidden, unselectHidden]); }, [labelKey, isSelected, hidden, selectHidden, unselectHidden]);
const select = useCallback(() => { const select = useCallback(() => {
if (locked) return; if (locked) return;
@ -82,13 +87,11 @@ export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound(
toggle(); toggle();
}, [toggle]); }, [toggle]);
const handleKeyDown = useKeyboardButton(() => { const handleKeyDown = useKeyboardButton(toggle);
toggle();
});
return ( return (
<div <div
aria-label={`${label} sound`} aria-label={t('sounds.aria-label', { name: translatedLabel })}
ref={ref} ref={ref}
role="button" role="button"
tabIndex={0} tabIndex={0}
@ -100,7 +103,7 @@ export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound(
onClick={handleClick} onClick={handleClick}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
> >
<Favorite id={id} label={label} /> <Favorite id={id} label={translatedLabel} />
<div className={styles.icon}> <div className={styles.icon}>
{isLoading ? ( {isLoading ? (
<span aria-hidden="true" className={styles.spinner}> <span aria-hidden="true" className={styles.spinner}>
@ -111,9 +114,9 @@ export const Sound = forwardRef<HTMLDivElement, SoundProps>(function Sound(
)} )}
</div> </div>
<div className={styles.label} id={id}> <div className={styles.label} id={id}>
{label} {translatedLabel}
</div> </div>
<Range id={id} label={label} /> <Range id={id} label={translatedLabel} />
</div> </div>
); );
}); });

View file

@ -1,22 +1,22 @@
import { useState, useMemo, useCallback, useRef, useEffect } from 'react'; import { useState, useMemo, useCallback, useRef, useEffect } from 'react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
import { Sound } from './sound'; import { Sound } from './sound';
import { useLocalStorage } from '@/hooks/use-local-storage'; import { useLocalStorage } from '@/hooks/use-local-storage';
import { cn } from '@/helpers/styles'; import { cn } from '@/helpers/styles';
import { fade, scale, mix } from '@/lib/motion'; import { fade, scale, mix } from '@/lib/motion';
import styles from './sounds.module.css'; import styles from './sounds.module.css';
import type { Sounds as SoundsType } from '@/data/types';
import type { Sounds } from '@/data/types';
interface SoundsProps { interface SoundsProps {
functional: boolean; functional: boolean;
id: string; id: string;
sounds: Sounds; sounds: SoundsType;
} }
export function Sounds({ functional, id, sounds }: SoundsProps) { export function Sounds({ functional, id, sounds }: SoundsProps) {
const { t } = useTranslation();
const [showAll, setShowAll] = useLocalStorage(`${id}-show-more`, false); const [showAll, setShowAll] = useLocalStorage(`${id}-show-more`, false);
const [clickedMore, setClickedMore] = useState(false); const [clickedMore, setClickedMore] = useState(false);
@ -71,7 +71,7 @@ export function Sounds({ functional, id, sounds }: SoundsProps) {
<div className={styles.sounds}> <div className={styles.sounds}>
{sounds.map((sound, index) => ( {sounds.map((sound, index) => (
<Sound <Sound
key={sound.label} key={sound.id}
{...sound} {...sound}
functional={functional} functional={functional}
hidden={!showAll && index > 5} hidden={!showAll && index > 5}
@ -84,7 +84,7 @@ export function Sounds({ functional, id, sounds }: SoundsProps) {
{sounds.length < 2 && {sounds.length < 2 &&
new Array(2 - sounds.length) new Array(2 - sounds.length)
.fill(null) .fill(null)
.map((_, index) => <div key={index} />)} .map((_, index) => <div key={`placeholder-${index}`} />)}
</div> </div>
{sounds.length > 6 && ( {sounds.length > 6 && (
@ -106,7 +106,7 @@ export function Sounds({ functional, id, sounds }: SoundsProps) {
onAnimationComplete={() => setIsAnimating(false)} onAnimationComplete={() => setIsAnimating(false)}
onAnimationStart={() => setIsAnimating(true)} onAnimationStart={() => setIsAnimating(true)}
> >
{showAll ? 'Show Less' : 'Show More'} {showAll ? t('sounds.show-less') : t('sounds.show-more')}
</motion.span> </motion.span>
</AnimatePresence> </AnimatePresence>
</button> </button>

View file

@ -24,103 +24,104 @@ import type { Category } from '../types';
export const animals: Category = { export const animals: Category = {
icon: <FaDog />, icon: <FaDog />,
id: 'animals', id: 'animals',
// 修改
sounds: [ sounds: [
{ {
icon: <PiBirdFill />, icon: <PiBirdFill />,
id: 'birds', id: 'birds',
label: 'Birds', labelKey: 'sounds.animals.birds',
src: '/sounds/animals/birds.mp3', src: '/sounds/animals/birds.mp3',
}, },
{ {
icon: <GiSeagull />, icon: <GiSeagull />,
id: 'seagulls', id: 'seagulls',
label: 'Seagulls', labelKey: 'sounds.animals.seagulls',
src: '/sounds/animals/seagulls.mp3', src: '/sounds/animals/seagulls.mp3',
}, },
{ {
icon: <GiCricket />, icon: <GiCricket />,
id: 'crickets', id: 'crickets',
label: 'Crickets', labelKey: 'sounds.animals.crickets',
src: '/sounds/animals/crickets.mp3', src: '/sounds/animals/crickets.mp3',
}, },
{ {
icon: <GiWolfHead />, icon: <GiWolfHead />,
id: 'wolf', id: 'wolf',
label: 'Wolf', labelKey: 'sounds.animals.wolf',
src: '/sounds/animals/wolf.mp3', src: '/sounds/animals/wolf.mp3',
}, },
{ {
icon: <GiOwl />, icon: <GiOwl />,
id: 'owl', id: 'owl',
label: 'Owl', labelKey: 'sounds.animals.owl',
src: '/sounds/animals/owl.mp3', src: '/sounds/animals/owl.mp3',
}, },
{ {
icon: <FaFrog />, icon: <FaFrog />,
id: 'frog', id: 'frog',
label: 'Frog', labelKey: 'sounds.animals.frog',
src: '/sounds/animals/frog.mp3', src: '/sounds/animals/frog.mp3',
}, },
{ {
icon: <PiDogBold />, icon: <PiDogBold />,
id: 'dog-barking', id: 'dog-barking',
label: 'Dog Barking', labelKey: 'sounds.animals.dog-barking',
src: '/sounds/animals/dog-barking.mp3', src: '/sounds/animals/dog-barking.mp3',
}, },
{ {
icon: <FaHorseHead />, icon: <FaHorseHead />,
id: 'horse-galopp', id: 'horse-galopp',
label: 'Horse Galopp', labelKey: 'sounds.animals.horse-galopp',
src: '/sounds/animals/horse-galopp.mp3', src: '/sounds/animals/horse-galopp.mp3',
}, },
{ {
icon: <FaCat />, icon: <FaCat />,
id: 'cat-purring', id: 'cat-purring',
label: 'Cat Purring', labelKey: 'sounds.animals.cat-purring',
src: '/sounds/animals/cat-purring.mp3', src: '/sounds/animals/cat-purring.mp3',
}, },
{ {
icon: <FaCrow />, icon: <FaCrow />,
id: 'crows', id: 'crows',
label: 'Crows', labelKey: 'sounds.animals.crows',
src: '/sounds/animals/crows.mp3', src: '/sounds/animals/crows.mp3',
}, },
{ {
icon: <GiWhaleTail />, icon: <GiWhaleTail />,
id: 'whale', id: 'whale',
label: 'Whale', labelKey: 'sounds.animals.whale',
src: '/sounds/animals/whale.mp3', src: '/sounds/animals/whale.mp3',
}, },
{ {
icon: <GiTreeBeehive />, icon: <GiTreeBeehive />,
id: 'beehive', id: 'beehive',
label: 'Beehive', labelKey: 'sounds.animals.beehive',
src: '/sounds/animals/beehive.mp3', src: '/sounds/animals/beehive.mp3',
}, },
{ {
icon: <GiEgyptianBird />, icon: <GiEgyptianBird />,
id: 'woodpecker', id: 'woodpecker',
label: 'Woodpecker', labelKey: 'sounds.animals.woodpecker',
src: '/sounds/animals/woodpecker.mp3', src: '/sounds/animals/woodpecker.mp3',
}, },
{ {
icon: <GiChicken />, icon: <GiChicken />,
id: 'chickens', id: 'chickens',
label: 'Chickens', labelKey: 'sounds.animals.chickens',
src: '/sounds/animals/chickens.mp3', src: '/sounds/animals/chickens.mp3',
}, },
{ {
icon: <GiCow />, icon: <GiCow />,
id: 'cows', id: 'cows',
label: 'Cows', labelKey: 'sounds.animals.cows',
src: '/sounds/animals/cows.mp3', src: '/sounds/animals/cows.mp3',
}, },
{ {
icon: <GiSheep />, icon: <GiSheep />,
id: 'sheep', id: 'sheep',
label: 'Sheep', labelKey: 'sounds.animals.sheep',
src: '/sounds/animals/sheep.mp3', src: '/sounds/animals/sheep.mp3',
}, },
], ],
title: 'Animals', titleKey: 'sounds.animals.title',
}; };

View file

@ -6,37 +6,38 @@ import type { Category } from '../types';
export const binaural: Category = { export const binaural: Category = {
icon: <TbWaveSine />, icon: <TbWaveSine />,
id: 'binaural', id: 'binaural',
// 修改
sounds: [ sounds: [
{ {
icon: <BsSoundwave />, icon: <BsSoundwave />,
id: 'binaural-delta', id: 'binaural-delta',
label: 'Delta', labelKey: 'sounds.binaural.binaural-delta',
src: '/sounds/binaural/binaural-delta.wav', src: '/sounds/binaural/binaural-delta.wav',
}, },
{ {
icon: <BsSoundwave />, icon: <BsSoundwave />,
id: 'binaural-theta', id: 'binaural-theta',
label: 'Theta', labelKey: 'sounds.binaural.binaural-theta',
src: '/sounds/binaural/binaural-theta.wav', src: '/sounds/binaural/binaural-theta.wav',
}, },
{ {
icon: <BsSoundwave />, icon: <BsSoundwave />,
id: 'binaural-alpha', id: 'binaural-alpha',
label: 'Alpha', labelKey: 'sounds.binaural.binaural-alpha',
src: '/sounds/binaural/binaural-alpha.wav', src: '/sounds/binaural/binaural-alpha.wav',
}, },
{ {
icon: <BsSoundwave />, icon: <BsSoundwave />,
id: 'binaural-beta', id: 'binaural-beta',
label: 'Beta', labelKey: 'sounds.binaural.binaural-beta',
src: '/sounds/binaural/binaural-beta.wav', src: '/sounds/binaural/binaural-beta.wav',
}, },
{ {
icon: <BsSoundwave />, icon: <BsSoundwave />,
id: 'binaural-gamma', id: 'binaural-gamma',
label: 'Gamma', labelKey: 'sounds.binaural.binaural-gamma',
src: '/sounds/binaural/binaural-gamma.wav', src: '/sounds/binaural/binaural-gamma.wav',
}, },
], ],
title: 'Binaural Beats', titleKey: 'sounds.binaural.title',
}; };

View file

@ -14,79 +14,80 @@ import type { Category } from '../types';
export const nature: Category = { export const nature: Category = {
icon: <BiSolidTree />, icon: <BiSolidTree />,
id: 'nature', id: 'nature',
// 修改
sounds: [ sounds: [
{ {
icon: <BiWater />, icon: <BiWater />,
id: 'river', id: 'river',
label: 'River', labelKey: 'sounds.nature.river',
src: '/sounds/nature/river.mp3', src: '/sounds/nature/river.mp3',
}, },
{ {
icon: <FaWater />, icon: <FaWater />,
id: 'waves', id: 'waves',
label: 'Waves', labelKey: 'sounds.nature.waves',
src: '/sounds/nature/waves.mp3', src: '/sounds/nature/waves.mp3',
}, },
{ {
icon: <BsFire />, icon: <BsFire />,
id: 'campfire', id: 'campfire',
label: 'Campfire', labelKey: 'sounds.nature.campfire',
src: '/sounds/nature/campfire.mp3', src: '/sounds/nature/campfire.mp3',
}, },
{ {
icon: <FaWind />, icon: <FaWind />,
id: 'wind', id: 'wind',
label: 'Wind', labelKey: 'sounds.nature.wind',
src: '/sounds/nature/wind.mp3', src: '/sounds/nature/wind.mp3',
}, },
{ {
icon: <FaWind />, icon: <FaWind />,
id: 'howling-wind', id: 'howling-wind',
label: 'Howling Wind', labelKey: 'sounds.nature.howling-wind',
src: '/sounds/nature/howling-wind.mp3', src: '/sounds/nature/howling-wind.mp3',
}, },
{ {
icon: <BiSolidTree />, icon: <BiSolidTree />,
id: 'wind-in-trees', id: 'wind-in-trees',
label: 'Wind in Trees', labelKey: 'sounds.nature.wind-in-trees',
src: '/sounds/nature/wind-in-trees.mp3', src: '/sounds/nature/wind-in-trees.mp3',
}, },
{ {
icon: <GiWaterfall />, icon: <GiWaterfall />,
id: 'waterfall', id: 'waterfall',
label: 'Waterfall', labelKey: 'sounds.nature.waterfall',
src: '/sounds/nature/waterfall.mp3', src: '/sounds/nature/waterfall.mp3',
}, },
{ {
icon: <FaRegSnowflake />, icon: <FaRegSnowflake />,
id: 'walk-in-snow', id: 'walk-in-snow',
label: 'Walk in Snow', labelKey: 'sounds.nature.walk-in-snow',
src: '/sounds/nature/walk-in-snow.mp3', src: '/sounds/nature/walk-in-snow.mp3',
}, },
{ {
icon: <FaLeaf />, icon: <FaLeaf />,
id: 'walk-on-leaves', id: 'walk-on-leaves',
label: 'Walk on Leaves', labelKey: 'sounds.nature.walk-on-leaves',
src: '/sounds/nature/walk-on-leaves.mp3', src: '/sounds/nature/walk-on-leaves.mp3',
}, },
{ {
icon: <GiStonePile />, icon: <GiStonePile />,
id: 'walk-on-gravel', id: 'walk-on-gravel',
label: 'Walk on Gravel', labelKey: 'sounds.nature.walk-on-gravel',
src: '/sounds/nature/walk-on-gravel.mp3', src: '/sounds/nature/walk-on-gravel.mp3',
}, },
{ {
icon: <BsFillDropletFill />, icon: <BsFillDropletFill />,
id: 'droplets', id: 'droplets',
label: 'Droplets', labelKey: 'sounds.nature.droplets',
src: '/sounds/nature/droplets.mp3', src: '/sounds/nature/droplets.mp3',
}, },
{ {
icon: <FaTree />, icon: <FaTree />,
id: 'jungle', id: 'jungle',
label: 'Jungle', labelKey: 'sounds.nature.jungle',
src: '/sounds/nature/jungle.mp3', src: '/sounds/nature/jungle.mp3',
}, },
], ],
title: 'Nature', titleKey: 'sounds.nature.title',
}; };

View file

@ -6,25 +6,26 @@ import type { Category } from '../types';
export const noise: Category = { export const noise: Category = {
icon: <BsSoundwave />, icon: <BsSoundwave />,
id: 'noise', id: 'noise',
// 修改
sounds: [ sounds: [
{ {
icon: <GiSoundWaves />, icon: <GiSoundWaves />,
id: 'white-noise', id: 'white-noise',
label: 'White Noise', labelKey: 'sounds.noise.white-noise',
src: '/sounds/noise/white-noise.wav', src: '/sounds/noise/white-noise.wav',
}, },
{ {
icon: <GiSoundWaves />, icon: <GiSoundWaves />,
id: 'pink-noise', id: 'pink-noise',
label: 'Pink Noise', labelKey: 'sounds.noise.pink-noise',
src: '/sounds/noise/pink-noise.wav', src: '/sounds/noise/pink-noise.wav',
}, },
{ {
icon: <GiSoundWaves />, icon: <GiSoundWaves />,
id: 'brown-noise', id: 'brown-noise',
label: 'Brown Noise', labelKey: 'sounds.noise.brown-noise',
src: '/sounds/noise/brown-noise.wav', src: '/sounds/noise/brown-noise.wav',
}, },
], ],
title: 'Noise', titleKey: 'sounds.noise.title',
}; };

View file

@ -21,103 +21,104 @@ import type { Category } from '../types';
export const places: Category = { export const places: Category = {
icon: <MdLocationPin />, icon: <MdLocationPin />,
id: 'places', id: 'places',
// 修改
sounds: [ sounds: [
{ {
icon: <BiSolidCoffeeAlt />, icon: <BiSolidCoffeeAlt />,
id: 'cafe', id: 'cafe',
label: 'Cafe', labelKey: 'sounds.places.cafe',
src: '/sounds/places/cafe.mp3', src: '/sounds/places/cafe.mp3',
}, },
{ {
icon: <BiSolidPlaneAlt />, icon: <BiSolidPlaneAlt />,
id: 'airport', id: 'airport',
label: 'Airport', labelKey: 'sounds.places.airport',
src: '/sounds/places/airport.mp3', src: '/sounds/places/airport.mp3',
}, },
{ {
icon: <FaChurch />, icon: <FaChurch />,
id: 'church', id: 'church',
label: 'Church', labelKey: 'sounds.places.church',
src: '/sounds/places/church.mp3', src: '/sounds/places/church.mp3',
}, },
{ {
icon: <MdTempleBuddhist />, icon: <MdTempleBuddhist />,
id: 'temple', id: 'temple',
label: 'Temple', labelKey: 'sounds.places.temple',
src: '/sounds/places/temple.mp3', src: '/sounds/places/temple.mp3',
}, },
{ {
icon: <MdConstruction />, icon: <MdConstruction />,
id: 'construction-site', id: 'construction-site',
label: 'Construction Site', labelKey: 'sounds.places.construction-site',
src: '/sounds/places/construction-site.mp3', src: '/sounds/places/construction-site.mp3',
}, },
{ {
icon: <TbScubaMask />, icon: <TbScubaMask />,
id: 'underwater', id: 'underwater',
label: 'Underwater', labelKey: 'sounds.places.underwater',
src: '/sounds/places/underwater.mp3', src: '/sounds/places/underwater.mp3',
}, },
{ {
icon: <TbBeerFilled />, icon: <TbBeerFilled />,
id: 'crowded-bar', id: 'crowded-bar',
label: 'Crowded Bar', labelKey: 'sounds.places.crowded-bar',
src: '/sounds/places/crowded-bar.mp3', src: '/sounds/places/crowded-bar.mp3',
}, },
{ {
icon: <GiVillage />, icon: <GiVillage />,
id: 'night-village', id: 'night-village',
label: 'Night Village', labelKey: 'sounds.places.night-village',
src: '/sounds/places/night-village.mp3', src: '/sounds/places/night-village.mp3',
}, },
{ {
icon: <FaSubway />, icon: <FaSubway />,
id: 'subway-station', id: 'subway-station',
label: 'Subway Station', labelKey: 'sounds.places.subway-station',
src: '/sounds/places/subway-station.mp3', src: '/sounds/places/subway-station.mp3',
}, },
{ {
icon: <HiOfficeBuilding />, icon: <HiOfficeBuilding />,
id: 'office', id: 'office',
label: 'Office', labelKey: 'sounds.places.office',
src: '/sounds/places/office.mp3', src: '/sounds/places/office.mp3',
}, },
{ {
icon: <FaShoppingBasket />, icon: <FaShoppingBasket />,
id: 'supermarket', id: 'supermarket',
label: 'Supermarket', labelKey: 'sounds.places.supermarket',
src: '/sounds/places/supermarket.mp3', src: '/sounds/places/supermarket.mp3',
}, },
{ {
icon: <GiCarousel />, icon: <GiCarousel />,
id: 'carousel', id: 'carousel',
label: 'Carousel', labelKey: 'sounds.places.carousel',
src: '/sounds/places/carousel.mp3', src: '/sounds/places/carousel.mp3',
}, },
{ {
icon: <AiFillExperiment />, icon: <AiFillExperiment />,
id: 'laboratory', id: 'laboratory',
label: 'Laboratory', labelKey: 'sounds.places.laboratory',
src: '/sounds/places/laboratory.mp3', src: '/sounds/places/laboratory.mp3',
}, },
{ {
icon: <BiSolidDryer />, icon: <BiSolidDryer />,
id: 'laundry-room', id: 'laundry-room',
label: 'Laundry Room', labelKey: 'sounds.places.laundry-room',
src: '/sounds/places/laundry-room.mp3', src: '/sounds/places/laundry-room.mp3',
}, },
{ {
icon: <IoRestaurant />, icon: <IoRestaurant />,
id: 'restaurant', id: 'restaurant',
label: 'Restaurant', labelKey: 'sounds.places.restaurant',
src: '/sounds/places/restaurant.mp3', src: '/sounds/places/restaurant.mp3',
}, },
{ {
icon: <FaBookOpen />, icon: <FaBookOpen />,
id: 'library', id: 'library',
label: 'Library', labelKey: 'sounds.places.library',
src: '/sounds/places/library.mp3', src: '/sounds/places/library.mp3',
}, },
], ],
title: 'Places', titleKey: 'sounds.places.title',
}; };

View file

@ -13,55 +13,56 @@ import type { Category } from '../types';
export const rain: Category = { export const rain: Category = {
icon: <BsFillCloudRainFill />, icon: <BsFillCloudRainFill />,
id: 'rain', id: 'rain',
// 修改
sounds: [ sounds: [
{ {
icon: <BsFillCloudRainFill />, icon: <BsFillCloudRainFill />,
id: 'light-rain', id: 'light-rain',
label: 'Light Rain', labelKey: 'sounds.rain.light-rain',
src: '/sounds/rain/light-rain.mp3', src: '/sounds/rain/light-rain.mp3',
}, },
{ {
icon: <BsFillCloudRainHeavyFill />, icon: <BsFillCloudRainHeavyFill />,
id: 'heavy-rain', id: 'heavy-rain',
label: 'Heavy Rain', labelKey: 'sounds.rain.heavy-rain',
src: '/sounds/rain/heavy-rain.mp3', src: '/sounds/rain/heavy-rain.mp3',
}, },
{ {
icon: <MdOutlineThunderstorm />, icon: <MdOutlineThunderstorm />,
id: 'thunder', id: 'thunder',
label: 'Thunder', labelKey: 'sounds.rain.thunder',
src: '/sounds/rain/thunder.mp3', src: '/sounds/rain/thunder.mp3',
}, },
{ {
icon: <GiWindow />, icon: <GiWindow />,
id: 'rain-on-window', id: 'rain-on-window',
label: 'Rain on Window', labelKey: 'sounds.rain.rain-on-window',
src: '/sounds/rain/rain-on-window.mp3', src: '/sounds/rain/rain-on-window.mp3',
}, },
{ {
icon: <FaCarSide />, icon: <FaCarSide />,
id: 'rain-on-car-roof', id: 'rain-on-car-roof',
label: 'Rain on Car Roof', labelKey: 'sounds.rain.rain-on-car-roof',
src: '/sounds/rain/rain-on-car-roof.mp3', src: '/sounds/rain/rain-on-car-roof.mp3',
}, },
{ {
icon: <BsUmbrellaFill />, icon: <BsUmbrellaFill />,
id: 'rain-on-umbrella', id: 'rain-on-umbrella',
label: 'Rain on Umbrella', labelKey: 'sounds.rain.rain-on-umbrella',
src: '/sounds/rain/rain-on-umbrella.mp3', src: '/sounds/rain/rain-on-umbrella.mp3',
}, },
{ {
icon: <PiTentFill />, icon: <PiTentFill />,
id: 'rain-on-tent', id: 'rain-on-tent',
label: 'Rain on Tent', labelKey: 'sounds.rain.rain-on-tent',
src: '/sounds/rain/rain-on-tent.mp3', src: '/sounds/rain/rain-on-tent.mp3',
}, },
{ {
icon: <FaLeaf />, icon: <FaLeaf />,
id: 'rain-on-leaves', id: 'rain-on-leaves',
label: 'Rain on Leaves', labelKey: 'sounds.rain.rain-on-leaves',
src: '/sounds/rain/rain-on-leaves.mp3', src: '/sounds/rain/rain-on-leaves.mp3',
}, },
], ],
title: 'Rain', titleKey: 'sounds.rain.title',
}; };

View file

@ -17,103 +17,104 @@ import type { Category } from '../types';
export const things: Category = { export const things: Category = {
icon: <MdSmartToy />, icon: <MdSmartToy />,
id: 'things', id: 'things',
// 修改
sounds: [ sounds: [
{ {
icon: <BsFillKeyboardFill />, icon: <BsFillKeyboardFill />,
id: 'keyboard', id: 'keyboard',
label: 'Keyboard', labelKey: 'sounds.things.keyboard',
src: '/sounds/things/keyboard.mp3', src: '/sounds/things/keyboard.mp3',
}, },
{ {
icon: <FaKeyboard />, icon: <FaKeyboard />,
id: 'typewriter', id: 'typewriter',
label: 'Typewriter', labelKey: 'sounds.things.typewriter',
src: '/sounds/things/typewriter.mp3', src: '/sounds/things/typewriter.mp3',
}, },
{ {
icon: <RiFilePaper2Fill />, icon: <RiFilePaper2Fill />,
id: 'paper', id: 'paper',
label: 'Paper', labelKey: 'sounds.things.paper',
src: '/sounds/things/paper.mp3', src: '/sounds/things/paper.mp3',
}, },
{ {
icon: <FaClock />, icon: <FaClock />,
id: 'clock', id: 'clock',
label: 'Clock', labelKey: 'sounds.things.clock',
src: '/sounds/things/clock.mp3', src: '/sounds/things/clock.mp3',
}, },
{ {
icon: <GiWindchimes />, icon: <GiWindchimes />,
id: 'wind-chimes', id: 'wind-chimes',
label: 'Wind Chimes', labelKey: 'sounds.things.wind-chimes',
src: '/sounds/things/wind-chimes.mp3', src: '/sounds/things/wind-chimes.mp3',
}, },
{ {
icon: <TbBowlFilled />, icon: <TbBowlFilled />,
id: 'singing-bowl', id: 'singing-bowl',
label: 'Singing Bowl', labelKey: 'sounds.things.singing-bowl',
src: '/sounds/things/singing-bowl.mp3', src: '/sounds/things/singing-bowl.mp3',
}, },
{ {
icon: <FaFan />, icon: <FaFan />,
id: 'ceiling-fan', id: 'ceiling-fan',
label: 'Ceiling Fan', labelKey: 'sounds.things.ceiling-fan',
src: '/sounds/things/ceiling-fan.mp3', src: '/sounds/things/ceiling-fan.mp3',
}, },
{ {
icon: <BiSolidDryer />, icon: <BiSolidDryer />,
id: 'dryer', id: 'dryer',
label: 'Dryer', labelKey: 'sounds.things.dryer',
src: '/sounds/things/dryer.mp3', src: '/sounds/things/dryer.mp3',
}, },
{ {
icon: <GiFilmProjector />, icon: <GiFilmProjector />,
id: 'slide-projector', id: 'slide-projector',
label: 'Slide Projector', labelKey: 'sounds.things.slide-projector',
src: '/sounds/things/slide-projector.mp3', src: '/sounds/things/slide-projector.mp3',
}, },
{ {
icon: <MdWaterDrop />, icon: <MdWaterDrop />,
id: 'boiling-water', id: 'boiling-water',
label: 'Boiling Water', labelKey: 'sounds.things.boiling-water',
src: '/sounds/things/boiling-water.mp3', src: '/sounds/things/boiling-water.mp3',
}, },
{ {
icon: <RiBubbleChartFill />, icon: <RiBubbleChartFill />,
id: 'bubbles', id: 'bubbles',
label: 'Bubbles', labelKey: 'sounds.things.bubbles',
src: '/sounds/things/bubbles.mp3', src: '/sounds/things/bubbles.mp3',
}, },
{ {
icon: <MdRadio />, icon: <MdRadio />,
id: 'tuning-radio', id: 'tuning-radio',
label: 'Tuning Radio', labelKey: 'sounds.things.tuning-radio',
src: '/sounds/things/tuning-radio.mp3', src: '/sounds/things/tuning-radio.mp3',
}, },
{ {
icon: <IoIosRadio />, icon: <IoIosRadio />,
id: 'morse-code', id: 'morse-code',
label: 'Morse Code', labelKey: 'sounds.things.morse-code',
src: '/sounds/things/morse-code.mp3', src: '/sounds/things/morse-code.mp3',
}, },
{ {
icon: <GiWashingMachine />, icon: <GiWashingMachine />,
id: 'washing-machine', id: 'washing-machine',
label: 'Washing Machine', labelKey: 'sounds.things.washing-machine',
src: '/sounds/things/washing-machine.mp3', src: '/sounds/things/washing-machine.mp3',
}, },
{ {
icon: <PiVinylRecord />, icon: <PiVinylRecord />,
id: 'vinyl-effect', id: 'vinyl-effect',
label: 'Vinyl Effect', labelKey: 'sounds.things.vinyl-effect',
src: '/sounds/things/vinyl-effect.mp3', src: '/sounds/things/vinyl-effect.mp3',
}, },
{ {
icon: <TbWiper />, icon: <TbWiper />,
id: 'windshield-wipers', id: 'windshield-wipers',
label: 'Windshield Wipers', labelKey: 'sounds.things.windshield-wipers',
src: '/sounds/things/windshield-wipers.mp3', src: '/sounds/things/windshield-wipers.mp3',
}, },
], ],
title: 'Things', titleKey: 'sounds.things.title',
}; };

View file

@ -8,43 +8,44 @@ import type { Category } from '../types';
export const transport: Category = { export const transport: Category = {
icon: <FaCarSide />, icon: <FaCarSide />,
id: 'transport', id: 'transport',
// 修改
sounds: [ sounds: [
{ {
icon: <BiSolidTrain />, icon: <BiSolidTrain />,
id: 'train', id: 'train',
label: 'Train', labelKey: 'sounds.transport.train',
src: '/sounds/transport/train.mp3', src: '/sounds/transport/train.mp3',
}, },
{ {
icon: <BiSolidTrain />, icon: <BiSolidTrain />,
id: 'inside-a-train', id: 'inside-a-train',
label: 'Inside a Train', labelKey: 'sounds.transport.inside-a-train',
src: '/sounds/transport/inside-a-train.mp3', src: '/sounds/transport/inside-a-train.mp3',
}, },
{ {
icon: <BiSolidPlaneAlt />, icon: <BiSolidPlaneAlt />,
id: 'airplane', id: 'airplane',
label: 'Airplane', labelKey: 'sounds.transport.airplane',
src: '/sounds/transport/airplane.mp3', src: '/sounds/transport/airplane.mp3',
}, },
{ {
icon: <GiSubmarine />, icon: <GiSubmarine />,
id: 'submarine', id: 'submarine',
label: 'Submarine', labelKey: 'sounds.transport.submarine',
src: '/sounds/transport/submarine.mp3', src: '/sounds/transport/submarine.mp3',
}, },
{ {
icon: <GiSailboat />, icon: <GiSailboat />,
id: 'sailboat', id: 'sailboat',
label: 'Sailboat', labelKey: 'sounds.transport.sailboat',
src: '/sounds/transport/sailboat.mp3', src: '/sounds/transport/sailboat.mp3',
}, },
{ {
icon: <TbSailboat />, icon: <TbSailboat />,
id: 'rowing-boat', id: 'rowing-boat',
label: 'Rowing Boat', labelKey: 'sounds.transport.rowing-boat',
src: '/sounds/transport/rowing-boat.mp3', src: '/sounds/transport/rowing-boat.mp3',
}, },
], ],
title: 'Transport', titleKey: 'sounds.transport.title',
}; };

View file

@ -9,49 +9,50 @@ import type { Category } from '../types';
export const urban: Category = { export const urban: Category = {
icon: <FaCity />, icon: <FaCity />,
id: 'urban', id: 'urban',
// 修改
sounds: [ sounds: [
{ {
icon: <PiRoadHorizonFill />, icon: <PiRoadHorizonFill />,
id: 'highway', id: 'highway',
label: 'Highway', labelKey: 'sounds.urban.highway',
src: '/sounds/urban/highway.mp3', src: '/sounds/urban/highway.mp3',
}, },
{ {
icon: <FaRoad />, icon: <FaRoad />,
id: 'road', id: 'road',
label: 'Road', labelKey: 'sounds.urban.road',
src: '/sounds/urban/road.mp3', src: '/sounds/urban/road.mp3',
}, },
{ {
icon: <PiSirenBold />, icon: <PiSirenBold />,
id: 'ambulance-siren', id: 'ambulance-siren',
label: 'Ambulance Siren', labelKey: 'sounds.urban.ambulance-siren',
src: '/sounds/urban/ambulance-siren.mp3', src: '/sounds/urban/ambulance-siren.mp3',
}, },
{ {
icon: <BsSoundwave />, icon: <BsSoundwave />,
id: 'busy-street', id: 'busy-street',
label: 'Busy Street', labelKey: 'sounds.urban.busy-street',
src: '/sounds/urban/busy-street.mp3', src: '/sounds/urban/busy-street.mp3',
}, },
{ {
icon: <BsPeopleFill />, icon: <BsPeopleFill />,
id: 'crowd', id: 'crowd',
label: 'Crowd', labelKey: 'sounds.urban.crowd',
src: '/sounds/urban/crowd.mp3', src: '/sounds/urban/crowd.mp3',
}, },
{ {
icon: <BiSolidTraffic />, icon: <BiSolidTraffic />,
id: 'traffic', id: 'traffic',
label: 'Traffic', labelKey: 'sounds.urban.traffic',
src: '/sounds/urban/traffic.mp3', src: '/sounds/urban/traffic.mp3',
}, },
{ {
icon: <RiSparkling2Fill />, icon: <RiSparkling2Fill />,
id: 'fireworks', id: 'fireworks',
label: 'Fireworks', labelKey: 'sounds.urban.fireworks',
src: '/sounds/urban/fireworks.mp3', src: '/sounds/urban/fireworks.mp3',
}, },
], ],
title: 'Urban', titleKey: 'sounds.urban.title',
}; };

4
src/data/types.d.ts vendored
View file

@ -1,7 +1,7 @@
export interface Sound { export interface Sound {
icon: React.ReactNode; icon: React.ReactNode;
id: string; id: string;
label: string; labelKey: string;
src: string; src: string;
} }
@ -11,7 +11,7 @@ export interface Category {
icon: React.ReactNode; icon: React.ReactNode;
id: string; id: string;
sounds: Sounds; sounds: Sounds;
title: string; titleKey: string;
} }
export type Categories = Array<Category>; export type Categories = Array<Category>;

View file

@ -16,15 +16,32 @@ const resources = {
i18n.use(initReactI18next).init({ i18n.use(initReactI18next).init({
debug: true, debug: true,
fallbackLng: 'en', fallbackLng: 'en',
interpolation: { interpolation: { escapeValue: false },
escapeValue: false,
},
lng: 'en', lng: 'en',
react: { missingKeyHandler: (lngs, ns, key, fallbackValue, updateMissing, options) => {
useSuspense: false, const resolvedLng = lngs && lngs.length > 0 ? lngs[0] : i18n.language;
}, const value = i18n.getResource(resolvedLng, ns || 'translation', key);
if (typeof value === 'object' && value !== null) {
console.warn(
`i18next: Key '${key}' in namespace '${
ns || 'translation'
}' resolved to an object, but expected a string. Check your t() call.`,
options,
);
} else {
console.warn(
`i18next: Missing key '${key}' in namespace '${
ns || 'translation'
}' for language(s) '${lngs.join(', ')}'.`,
);
}
return fallbackValue || key;
},
react: { useSuspense: false },
resources, resources,
returnObjects: true,
}); });
export default i18n; export default i18n;

View file

@ -8,14 +8,6 @@
"en": "English", "en": "English",
"zh": "简体中文" "zh": "简体中文"
}, },
"languageSwitcher": {
"label": "Language selection"
},
"common": {
"play": "Play",
"pause": "Pause",
"close": "Close"
},
"modals": { "modals": {
"reload": { "reload": {
"title": "New Content", "title": "New Content",
@ -23,13 +15,33 @@
"reloadButton": "Reload" "reloadButton": "Reload"
} }
}, },
"buttons": { "buttons": {
"playError": "Please first select a sound to play.", "play": {
"useMoodist": "Use Moodist" "label": "Play",
}, "error": "Please select a sound to play."
"categories": { },
"favorites": "Favorites" "favorite": {
"add": {
"aria-label": "Add {{label}} Sound to Favorites"
},
"remove": {
"aria-label": "Remove {{label}} Sound from Favorites"
}
},
"unselect": {
"tooltip": "Unselect all sounds.",
"aria-label": "Unselect All Sounds",
"restore": {
"tooltip": "Restore unselected sounds.",
"aria-label": "Restore Unselected Sounds"
}
},
"pause": {
"label": "Pause"
},
"use-moodist": {
"label": "Use Moodist"
}
}, },
"about": { "about": {
"section1": { "section1": {
@ -48,5 +60,132 @@
"title": "Sounds for Every Moment", "title": "Sounds for Every Moment",
"body": "Whether you're looking to unwind after a long day, enhance your focus during work, or lull yourself into a peaceful sleep, Moodist has the perfect soundscape waiting for you. The best part? It's completely free and open-source, so you can enjoy its benefits without any strings attached. Start using Moodist today and discover your new haven of tranquility and focus!" "body": "Whether you're looking to unwind after a long day, enhance your focus during work, or lull yourself into a peaceful sleep, Moodist has the perfect soundscape waiting for you. The best part? It's completely free and open-source, so you can enjoy its benefits without any strings attached. Start using Moodist today and discover your new haven of tranquility and focus!"
} }
},
"volume": {
"aria-label": "{{label}} sound volume"
},
"sounds": {
"show-less": "Show Less",
"show-more": "Show More",
"aria-label": "{{name}} sound",
"favorites": {
"title": "Favorites"
},
"animals": {
"title": "Animals",
"birds": "Birds",
"seagulls": "Seagulls",
"crickets": "Crickets",
"wolf": "Wolf",
"owl": "Owl",
"frog": "Frog",
"dog-barking": "Dog Barking",
"horse-galopp": "Horse Galopp",
"cat-purring": "Cat Purring",
"crows": "Crows",
"whale": "Whale",
"beehive": "Beehive",
"woodpecker": "Woodpecker",
"chickens": "Chickens",
"cows": "Cows",
"sheep": "Sheep"
},
"binaural": {
"title": "Binaural Beats",
"binaural-delta": "Delta",
"binaural-theta": "Theta",
"binaural-alpha": "Alpha",
"binaural-beta": "Beta",
"binaural-gamma": "Gamma"
},
"nature": {
"title": "Nature",
"river": "River",
"waves": "Waves",
"campfire": "Campfire",
"wind": "Wind",
"howling-wind": "Howling Wind",
"wind-in-trees": "Wind in Trees",
"waterfall": "Waterfall",
"walk-in-snow": "Walk in Snow",
"walk-on-leaves": "Walk on Leaves",
"walk-on-gravel": "Walk on Gravel",
"droplets": "Droplets",
"jungle": "Jungle"
},
"noise": {
"title": "Noise",
"white-noise": "White Noise",
"pink-noise": "Pink Noise",
"brown-noise": "Brown Noise"
},
"places": {
"title": "Places",
"cafe": "Cafe",
"airport": "Airport",
"church": "Church",
"temple": "Temple",
"construction-site": "Construction Site",
"underwater": "Underwater",
"crowded-bar": "Crowded Bar",
"night-village": "Night Village",
"subway-station": "Subway Station",
"office": "Office",
"supermarket": "Supermarket",
"carousel": "Carousel",
"laboratory": "Laboratory",
"laundry-room": "Laundry Room",
"restaurant": "Restaurant",
"library": "Library"
},
"rain": {
"title": "Rain",
"light-rain": "Light Rain",
"heavy-rain": "Heavy Rain",
"thunder": "Thunder",
"rain-on-window": "Rain on Window",
"rain-on-car-roof": "Rain on Car Roof",
"rain-on-umbrella": "Rain on Umbrella",
"rain-on-tent": "Rain on Tent",
"rain-on-leaves": "Rain on Leaves"
},
"things": {
"title": "Things",
"keyboard": "Keyboard",
"typewriter": "Typewriter",
"paper": "Paper",
"clock": "Clock",
"wind-chimes": "Wind Chimes",
"singing-bowl": "Singing Bowl",
"ceiling-fan": "Ceiling Fan",
"dryer": "Dryer",
"slide-projector": "Slide Projector",
"boiling-water": "Boiling Water",
"bubbles": "Bubbles",
"tuning-radio": "Tuning Radio",
"morse-code": "Morse Code",
"washing-machine": "Washing Machine",
"vinyl-effect": "Vinyl Effect",
"windshield-wipers": "Windshield Wipers"
},
"transport": {
"title": "Transport",
"train": "Train",
"inside-a-train": "Inside a Train",
"airplane": "Airplane",
"submarine": "Submarine",
"sailboat": "Sailboat",
"rowing-boat": "Rowing Boat"
},
"urban": {
"title": "Urban",
"highway": "Highway",
"road": "Road",
"ambulance-siren": "Ambulance Siren",
"busy-street": "Busy Street",
"crowd": "Crowd",
"traffic": "Traffic",
"fireworks": "Fireworks"
}
} }
} }

View file

@ -8,14 +8,6 @@
"en": "English", "en": "English",
"zh": "简体中文" "zh": "简体中文"
}, },
"languageSwitcher": {
"label": "语言选择"
},
"common": {
"play": "播放",
"pause": "暂停",
"close": "关闭"
},
"modals": { "modals": {
"reload": { "reload": {
"title": "发现新内容", "title": "发现新内容",
@ -24,11 +16,32 @@
} }
}, },
"buttons": { "buttons": {
"playError": "请先选择要播放的声音。", "play": {
"useMoodist": "使用 Moodist" "label": "播放",
}, "error": "请先选择要播放的声音。"
"categories": { },
"favorites": "收藏夹" "favorite": {
"add": {
"aria-label": "将 {{label}} 声音添加到收藏夹"
},
"remove": {
"aria-label": "从收藏夹移除 {{label}} 声音"
}
},
"unselect": {
"tooltip": "取消选择所有声音。",
"aria-label": "取消选择所有声音",
"restore": {
"tooltip": "恢复上次选择的声音。",
"aria-label": "恢复上次选择的声音"
}
},
"pause": {
"label": "暂停"
},
"use-moodist": {
"label": "使用 Moodist"
}
}, },
"about": { "about": {
"section1": { "section1": {
@ -47,5 +60,132 @@
"title": "适合每一刻的声音", "title": "适合每一刻的声音",
"body": "无论您是想在漫长的一天后放松身心在工作时提高注意力还是哄自己进入宁静的睡眠Moodist 都有完美的声音景观等着您。最棒的是?它完全免费且开源,因此您可以无任何附加条件地享受其益处。立即开始使用 Moodist发现您宁静与专注的新港湾" "body": "无论您是想在漫长的一天后放松身心在工作时提高注意力还是哄自己进入宁静的睡眠Moodist 都有完美的声音景观等着您。最棒的是?它完全免费且开源,因此您可以无任何附加条件地享受其益处。立即开始使用 Moodist发现您宁静与专注的新港湾"
} }
},
"volume": {
"aria-label": "{{label}} 声音音量"
},
"sounds": {
"show-less": "收起",
"show-more": "显示更多",
"aria-label": "{{name}} 声音",
"favorites": {
"title": "收藏夹"
},
"animals": {
"title": "动物",
"birds": "鸟鸣",
"seagulls": "海鸥",
"crickets": "蟋蟀",
"wolf": "狼嚎",
"owl": "猫头鹰",
"frog": "蛙鸣",
"dog-barking": "狗吠",
"horse-galopp": "马蹄",
"cat-purring": "猫打呼",
"crows": "乌鸦",
"whale": "鲸鱼",
"beehive": "蜂巢",
"woodpecker": "啄木鸟",
"chickens": "鸡",
"cows": "牛",
"sheep": "羊"
},
"binaural": {
"title": "双耳节拍",
"binaural-delta": "Delta 波",
"binaural-theta": "Theta 波",
"binaural-alpha": "Alpha 波",
"binaural-beta": "Beta 波",
"binaural-gamma": "Gamma 波"
},
"nature": {
"title": "自然",
"river": "河流",
"waves": "海浪",
"campfire": "篝火",
"wind": "风声",
"howling-wind": "呼啸的风",
"wind-in-trees": "林间风声",
"waterfall": "瀑布",
"walk-in-snow": "雪地行走",
"walk-on-leaves": "踩树叶",
"walk-on-gravel": "踩砾石",
"droplets": "水滴",
"jungle": "丛林"
},
"noise": {
"title": "噪音",
"white-noise": "白噪音",
"pink-noise": "粉红噪音",
"brown-noise": "布朗噪音"
},
"places": {
"title": "场所",
"cafe": "咖啡馆",
"airport": "机场",
"church": "教堂",
"temple": "寺庙",
"construction-site": "建筑工地",
"underwater": "水下",
"crowded-bar": "拥挤的酒吧",
"night-village": "夜晚村庄",
"subway-station": "地铁站",
"office": "办公室",
"supermarket": "超市",
"carousel": "旋转木马",
"laboratory": "实验室",
"laundry-room": "洗衣房",
"restaurant": "餐厅",
"library": "图书馆"
},
"rain": {
"title": "雨声",
"light-rain": "小雨",
"heavy-rain": "大雨",
"thunder": "雷声",
"rain-on-window": "雨打窗户",
"rain-on-car-roof": "雨打车顶",
"rain-on-umbrella": "雨打雨伞",
"rain-on-tent": "雨打帐篷",
"rain-on-leaves": "雨打树叶"
},
"things": {
"title": "物品",
"keyboard": "键盘",
"typewriter": "打字机",
"paper": "纸张",
"clock": "时钟",
"wind-chimes": "风铃",
"singing-bowl": "颂钵",
"ceiling-fan": "吊扇",
"dryer": "烘干机",
"slide-projector": "幻灯机",
"boiling-water": "沸水",
"bubbles": "气泡",
"tuning-radio": "调谐收音机",
"morse-code": "摩尔斯电码",
"washing-machine": "洗衣机",
"vinyl-effect": "黑胶唱片",
"windshield-wipers": "雨刮器"
},
"transport": {
"title": "交通",
"train": "火车",
"inside-a-train": "火车内部",
"airplane": "飞机",
"submarine": "潜水艇",
"sailboat": "帆船",
"rowing-boat": "划艇"
},
"urban": {
"title": "城市",
"highway": "高速公路",
"road": "马路",
"ambulance-siren": "救护车警报",
"busy-street": "繁忙街道",
"crowd": "人群",
"traffic": "交通",
"fireworks": "烟花"
}
} }
} }