컴포넌트
언어 전환기
같은 페이지의 다른 언어로 가는 링크예요. 좁은 화면에서는 아이콘만 남아요.
- #arc
- #component
- #language
- #locale
- #i18n
- #header
1280px
설치
이 명령 한 줄이면 필요한 파일과 패키지가 함께 설치돼요.
터미널터미널터미널bash
npx shadcn@latest add https://dsui.vercel.app/r/language-switcher.json파일
components/arc/language-switcher/language-switcher.module.csscomponents/arc/language-switcher/language-switcher.tsx
npm 패키지
@hugeicons/core-free-icons@^4.3.5@hugeicons/react@^1.1.10
함께 오는 항목
language-switcher.module.csslanguage-switcher.module.csslanguage-switcher.module.csscss
.link { display: inline-flex; min-height: var(--control-height-sm); align-items: center; gap: var(--space-2); border-radius: var(--radius-pill); padding: 0 var(--space-3); color: var(--text-secondary); font-size: var(--label-md); font-weight: var(--label-weight); line-height: var(--leading-body); text-decoration: none; white-space: nowrap; transition: color var(--duration-fast) var(--ease-standard), background var(--duration-fast) var(--ease-standard); }
.link svg { flex: none; }
@media (hover: hover) and (pointer: fine) {
.link:hover { background: var(--surface-muted); color: var(--foreground); }
}
/* Touch has no hover, so the press carries the whole answer. */
.link:active { background: var(--surface-sunken); color: var(--foreground); }
/* Clipped rather than removed: the name is still the link's accessible name when only the globe shows. */
@media (max-width: 1023px) {
.compactLabel { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}
@media (prefers-reduced-motion: reduce) { .link { transition: none; } }
language-switcher.tsxlanguage-switcher.tsxlanguage-switcher.tsxtsx
"use client";
import type { AnchorHTMLAttributes, ReactNode } from "react";
import { HugeiconsIcon } from "@hugeicons/react";
import { LanguageSkillIcon } from "@hugeicons/core-free-icons";
import { playUiSound } from "../sound/play";
import styles from "./language-switcher.module.css";
export interface LanguageSwitcherProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
/** The language the control moves to: its BCP 47 code and the name to show. */
to: { code: string; label: string };
/** Hides the name below the lg breakpoint. It stays the link's accessible name. */
compact?: boolean;
/** Replaces the globe. Pass a decorative icon of the same size. */
icon?: ReactNode;
}
/**
* A link to the same page in the other language. It is an anchor, not a button, because the two languages are two
* documents: the switch is a navigation, so it opens in a new tab with the usual modifiers and copies as a URL.
*/
export function LanguageSwitcher({ className, to, compact = false, icon, onClick, ...props }: LanguageSwitcherProps) {
return (
<a
{...props}
hrefLang={to.code}
lang={to.code}
className={[styles.link, className].filter(Boolean).join(" ")}
onClick={event => { playUiSound("tick"); onClick?.(event); }}
>
{icon ?? <HugeiconsIcon icon={LanguageSkillIcon} size={17} strokeWidth={1.75} aria-hidden="true" />}
<span className={compact ? styles.compactLabel : undefined}>{to.label}</span>
</a>
);
}
LanguageSwitcher.displayName = "LanguageSwitcher";
export default LanguageSwitcher;