hensei-web/components/weapon/WeaponKeySelect/index.tsx
Justin Edmund 3d67622353
Fix i18n migration to next-intl (#430)
## Summary
- Fixed translation key format compatibility with next-intl
- Fixed pluralization format from i18next to next-intl format
- Fixed dynamic translation key error handling
- Updated server components to match API response structure
- Fixed useSearchParams import location

## Changes
- Changed pluralization from `{{count}} items` to `{count} items` format
- Added proper error handling for missing translation keys
- Fixed import paths for next-intl hooks
- Fixed PartyPageClient trying to set non-existent appState.parties

## Test plan
- [x] Verified translations render correctly
- [x] Tested pluralization works with different counts
- [x] Confirmed no console errors about missing translations
- [x] Tested party page functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-09-03 16:25:59 -07:00

219 lines
5.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/navigation'
import { useTranslations } from 'next-intl'
import classNames from 'classnames'
import { getCookie } from 'cookies-next'
import Select from '~components/common/Select'
import SelectGroup from '~components/common/SelectGroup'
import SelectItem from '~components/common/SelectItem'
import { appState } from '~utils/appState'
import styles from './index.module.scss'
// Props
interface Props {
open: boolean
gridWeapon: GridWeapon
weaponKey?: WeaponKey
series: number
slot: number
onChange?: (value: WeaponKey, slot: number) => void
onOpenChange: () => void
onClose?: () => void
sendValidity: (isValid: boolean) => void
}
// Constants
const pendulumNames = [
{ en: 'Pendulum', jp: 'ペンデュラム' },
{ en: 'Chain', jp: 'チェイン' },
]
const telumaNames = [{ en: 'Teluma', jp: 'テルマ' }]
const emblemNames = [{ en: 'Emblem', jp: 'エンブレム' }]
const gauphNames = [
{ en: 'Gauph Key', jp: 'ガフスキー' },
{ en: 'Ultima Key', jp: 'ガフスキーΩ' },
{ en: 'Gate of Omnipotence', jp: 'ガフスキー' },
]
const providenceNames = [{ en: 'Providence Teluma', jp: 'オリジンステルマ' }]
const emptyWeaponKey: WeaponKey = {
id: 'no-key',
granblue_id: '-1',
series: [0],
slot: 0,
slug: '',
group: 0,
order: 0,
name: { en: '', ja: '' },
}
const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
function useFieldSet(
{
open,
gridWeapon,
weaponKey,
series,
slot,
onChange,
onOpenChange,
onClose,
sendValidity,
},
ref
) {
const router = useRouter()
const locale =
getCookie('NEXT_LOCALE') && ['en', 'ja'].includes(getCookie('NEXT_LOCALE') as string)
? (getCookie('NEXT_LOCALE') as string)
: 'en'
const t = useTranslations('common')
const [keys, setKeys] = useState<WeaponKey[][]>([])
const [error, setError] = useState('')
const errorClasses = classNames({
[styles.errors]: true,
[styles.visible]: error !== '',
})
useEffect(() => {
const keys = flattenWeaponKeys()
const filteredKeys = filterWeaponKeys(keys)
setKeys(groupWeaponKeys(filteredKeys))
}, [series])
function flattenWeaponKeys() {
const keys: WeaponKey[] = []
for (let setName of Object.keys(appState.weaponKeys)) {
const set = appState.weaponKeys[setName]
set.map((key) => keys.push(key))
}
return keys
}
function filterWeaponKeys(weaponKeys: WeaponKey[]) {
// Filter weapon keys based on the series and slot provided
return weaponKeys.filter(
(key) => key.series.includes(series) && key.slot == slot
)
}
function groupWeaponKeys(weaponKeys: WeaponKey[]) {
const numGroups = Math.max.apply(
Math,
weaponKeys.map((key) => key.group)
)
let groupedKeys = []
for (let i = 0; i <= numGroups; i++) {
const values = weaponKeys.filter((key) => key.group == i)
if (values.length > 0) groupedKeys[i] = values
}
return groupedKeys.filter(() => true)
}
function weaponKeyGroup(index: number) {
;['α', 'β', 'γ', 'Δ'].sort((a, b) => a.localeCompare(b, 'el'))
const sortByOrder = (a: WeaponKey, b: WeaponKey) =>
a.order > b.order ? 1 : -1
const options =
keys &&
keys.length > 0 &&
keys[index].length > 0 &&
keys[index].sort(sortByOrder).map((item, i) => {
return (
<SelectItem
key={i}
value={item.id}
data-granblue-id={item.granblue_id}
>
{item.name.en}
</SelectItem>
)
})
let name: { [key: string]: string } = {}
if (series == 2 && index == 0) name = pendulumNames[0]
else if (series == 2 && slot == 1 && index == 1) name = pendulumNames[1]
else if (series === 3 || series === 34) name = telumaNames[0]
else if (series === 17) name = gauphNames[slot]
else if (series === 24) name = emblemNames[index]
return (
<SelectGroup
key={index}
label={series == 17 && slot == 2 ? name.en : `${name.en}s`}
separator={false}
>
{options}
</SelectGroup>
)
}
function handleChange(value: string) {
const keys = flattenWeaponKeys()
const found = keys.find((key) => key.id == value)
const weaponKey = found ? found : emptyWeaponKey
if (
['14005', '14006', '14007'].includes(`${weaponKey.granblue_id}`) &&
gridWeapon.transcendence_step < 3
) {
setError(`${weaponKey.name[locale]} requires 3rd Transcendence`)
sendValidity(false)
} else if (onChange) {
setError('')
onChange(weaponKey, slot)
sendValidity(true)
}
}
const emptyOption = () => {
let name = ''
if (series === 2) name = pendulumNames[0].en
else if (series === 3 || series === 34) name = telumaNames[0].en
else if (series === 17) name = gauphNames[slot].en
else if (series === 24) name = emblemNames[0].en
return `No ${name}`
}
return (
<>
<Select
key={`weapon-key-${slot}`}
value={weaponKey ? weaponKey.id : emptyWeaponKey.id}
open={open}
onClose={onClose}
onOpenChange={onOpenChange}
onValueChange={handleChange}
trigger={{
bound: true,
}}
ref={ref}
overlayVisible={false}
>
<SelectItem key={emptyWeaponKey.id} value={emptyWeaponKey.id}>
{emptyOption()}
</SelectItem>
{Array.from(Array(keys?.length)).map((x, i) => {
return weaponKeyGroup(i)
})}
</Select>
<p className={errorClasses}>{error}</p>
</>
)
}
)
export default WeaponKeySelect