aboutsummaryrefslogtreecommitdiffstats
path: root/site/src/components/LanguageSwitcher.tsx
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-10-07 17:25:43 -0700
committerPinapelz <yukais@pinapelz.com>2025-10-07 17:25:50 -0700
commitd7b5b81b5d6ec55d0847b5171c3800a8f7b5c001 (patch)
treed646d8339602599eee64910cd252de0df595bcfe /site/src/components/LanguageSwitcher.tsx
parent014443ef502eee0c337a5feb2aa0aeebb8d51557 (diff)
feat: add i18n translation (initial JP and EN)
Diffstat (limited to 'site/src/components/LanguageSwitcher.tsx')
-rw-r--r--site/src/components/LanguageSwitcher.tsx109
1 files changed, 109 insertions, 0 deletions
diff --git a/site/src/components/LanguageSwitcher.tsx b/site/src/components/LanguageSwitcher.tsx
new file mode 100644
index 0000000..8cceb35
--- /dev/null
+++ b/site/src/components/LanguageSwitcher.tsx
@@ -0,0 +1,109 @@
+import { useTranslation } from 'react-i18next';
+import { useSearchParams } from 'react-router-dom';
+import { useRef, useState, useEffect } from 'react';
+
+const languages = [
+ { code: 'en', name: 'English' },
+ { code: 'ja', name: '日本語' }
+];
+
+interface LanguageSwitcherProps {
+ variant?: 'compact' | 'standard';
+}
+
+function LanguageSwitcher({ variant = 'standard' }: LanguageSwitcherProps) {
+ const { i18n } = useTranslation();
+ const [searchParams] = useSearchParams();
+ const isMoe = searchParams.has("moe");
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef<HTMLDivElement>(null);
+
+ const currentLanguage = languages.find(lang => lang.code === i18n.language) || languages[0];
+
+ // Close the dropdown when clicking outside
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+ setIsOpen(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, []);
+
+ return (
+ <div className="relative inline-block" ref={dropdownRef}>
+ <button
+ onClick={() => setIsOpen(!isOpen)}
+ className={`
+ flex items-center justify-between
+ transition-all duration-200 text-sm rounded
+ ${variant === 'compact' ? 'px-2 py-0.5 min-w-[80px]' : 'px-3 py-1.5 min-w-[100px]'}
+ ${isMoe
+ ? 'bg-pink-200 text-pink-800 hover:bg-pink-300'
+ : 'bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-white'
+ }
+ `}
+ aria-haspopup="true"
+ aria-expanded={isOpen}
+ >
+ <span>{currentLanguage.name}</span>
+ <span className="ml-1">
+ <svg
+ className={`h-4 w-4 transition-transform ${isOpen ? 'rotate-180' : ''}`}
+ fill="none"
+ viewBox="0 0 24 24"
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ strokeWidth={2}
+ d="M19 9l-7 7-7-7"
+ />
+ </svg>
+ </span>
+ </button>
+
+ {isOpen && (
+ <div
+ className={`
+ absolute right-0 mt-1 z-10 shadow-lg rounded-md overflow-hidden
+ ${variant === 'compact' ? 'w-24' : 'w-32'}
+ ${isMoe ? 'bg-pink-100 border border-pink-300' : 'bg-gray-800 border border-gray-700'}
+ `}
+ >
+ <div className="py-1">
+ {languages.map((lang) => (
+ <button
+ key={lang.code}
+ onClick={() => {
+ i18n.changeLanguage(lang.code);
+ setIsOpen(false);
+ }}
+ className={`
+ block w-full text-left px-4 py-2 text-sm
+ ${i18n.language === lang.code
+ ? isMoe
+ ? 'bg-pink-300 text-pink-800 font-medium'
+ : 'bg-purple-700 text-white font-medium'
+ : isMoe
+ ? 'text-pink-800 hover:bg-pink-200'
+ : 'text-gray-300 hover:bg-gray-700 hover:text-white'
+ }
+ `}
+ >
+ {lang.name}
+ </button>
+ ))}
+ </div>
+ </div>
+ )}
+ </div>
+ );
+}
+
+export default LanguageSwitcher;
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage