[React] 웹 페이지 번역

오찬주·2025년 3월 2일

개발 log

목록 보기
15/23
post-thumbnail

친한 사람의 부탁을 받아 개인적인 웹사이트를 하나 만들고 있는게 있다.(리액트 js vite 환경에서!)

이 웹사이트는 영어 소통이 잘 되지 않는 외국인에게 안내를 해주는 목적을 가지고 있기에

자동 번역 이 필수적이었다.

이에 번역 api를 여러개 찾아보게 되었고, 구글 번역 api가 적합하다고 판단해 이를 사용하게 되었다.

Google Translate Website Translator

페이지 자체를 번역해주는 것이다! 이런 버튼을 눌러서 언어를 선택하고 페이지를 번역할 수 있다. 물론... 여기서 제공해주는 ui가 너무 구려서 ..... 따로 디자인이 필요했다. 그래서 여러 벨로그를 참고해 수정하게 되었다.

상태 관리

const [chooseCountry, setChooseCountry] = useState({
  code: "ko",
  name: "한국어",
  flag: "kr",
});
const [isHovered, setIsHovered] = useState(false);

언어 선택 버튼에 마우스가 올라갔는지 여부와 현재 선택된 언어 정보의 상태를 관리한다.

Google 번역 스크립트 추가 (useEffect)

useEffect(() => {
  const savedLanguage = localStorage.getItem("selectedLanguage");

  if (!document.querySelector('script[src*="translate_a"]')) {
    const addGoogleTranslateScript = document.createElement("script");
    addGoogleTranslateScript.src =
      "https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
    addGoogleTranslateScript.async = true;

    addGoogleTranslateScript.onload = () => {
      window.googleTranslateElementInit = () => {
        new window.google.translate.TranslateElement(
          { pageLanguage: "ko", autoDisplay: true },
          "google_translate_element"
        );

        setTimeout(() => {
          if (savedLanguage) {
            const lang = languages.find((l) => l.code === savedLanguage);
            if (lang) {
              const gtCombo = document.querySelector(".goog-te-combo");
              if (gtCombo) {
                gtCombo.value = lang.code;
                gtCombo.dispatchEvent(new Event("change"));
                setChooseCountry(lang);
              }
            }
          }
        }, 1000); // 1초 후 실행 (API 로드 대기)
      };
      window.googleTranslateElementInit();
    };

    document.body.appendChild(addGoogleTranslateScript);
  }
}, []);
  • Google 번역 위젯을 동적으로 추가 (translate_a/element.js 로드)
  • window.googleTranslateElementInit 함수를 정의하여 번역 기능 활성화
  • localStorage에서 저장된 언어를 불러와 자동 적용
  • setTimeout을 추가하여 Google 번역기가 완전히 로드된 후 언어를 변경
  • 초기 로딩 시 로컬스토리지에 savedLanguage 값이 있으면 해당 언어로 설정

언어 변경 함수

const handleLanguageChange = (lang) => {
  const value = lang.code;
  const gtCombo = document.querySelector(".goog-te-combo");
  if (gtCombo) {
    gtCombo.value = value;
    gtCombo.dispatchEvent(new Event("change"));
  }
  setChooseCountry(lang);
  localStorage.setItem("selectedLanguage", lang.code);
};
  • 사용자가 클릭한 언어로 Google 번역 위젯의 .goog-te-combo 값을 변경
  • 선택된 언어를 localStorage에 저장하여 새로고침 후에도 유지한다. (새로고침되면 언어가 초기화되는 문제가 있어 로컬 스토리지에 저장하고자 했다.

이벤트 전파를 차단

  const handleWheel = (e) => {
    e.stopPropagation();
  };

마우스 휠 이벤트가 부모 요소로 전파되지 않도록 막는 역할을 한다. 즉, 언어 목록 내부에서만 스크롤 가능하게 제한한다.

ui

 <>
      <div id="google_translate_element" style={{ display: "none" }}></div>
      <ButtonContainer
        onMouseEnter={() => setIsHovered(true)}
        onMouseLeave={() => setIsHovered(false)}
        $isMobile={isMobile}
      >
        <Flag code={chooseCountry.flag} />
        {chooseCountry.name}
        {isHovered && (
          <LanguageList onWheel={handleWheel}>
            {languages.map((lang) => (
              <LanguageItem
                key={lang.code}
                onClick={() => handleLanguageChange(lang)}
              >
                <Flag code={lang.flag} />
                {lang.name}
              </LanguageItem>
            ))}
          </LanguageList>
        )}
      </ButtonContainer>
    </>

이때 깃발의 사진은 하나하나 찾으면 당연히 어렵고 귀찮기에 웹사이트를 이용한다.

 background-image: ${(props) =>
    `url(https://cdn.weglot.com/flags/square/${props.code}.svg)`};

이런식으로! 전체 스타일 코드는 다음과 같다.

const ButtonContainer = styled.li`
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 5px 5px;
  width: 95px;
  height: 45px;
  cursor: pointer;
  background-color: #ffffff;
  outline: none;
  border: none;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.4);
  border-radius: 5px;
  font-size: 14px;
  position: fixed;
  z-index: 999;
  right: ${(props) => (props.$isMobile ? "1rem" : "3rem")};
  bottom: 2rem;
`;

const LanguageList = styled.ul`
  position: absolute;
  top: -80vh;
  left: 0;
  background-color: white;
  border: 1px solid #ccc;
  list-style: none;
  padding: 10px;
  margin: 0;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  width: 100px;
  height: 80vh;
  overflow-y: auto;
  overflow-x: hidden;
  border-radius: 8px;
  z-index: 1000;
`;

const LanguageItem = styled.li`
  cursor: pointer;
  padding: 10px 0px;
  &:hover {
    background-color: #f0f0f0;
  }
  font-size: 14px;
  border-bottom: 1px solid #b7b7b7;
`;

const Flag = styled.div`
  width: 30px;
  height: 25px;
  background-image: ${(props) =>
    `url(https://cdn.weglot.com/flags/square/${props.code}.svg)`};
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
`;

languages를 따로 모아둔 파일

export const languages = [
  { code: "ko", name: "한국어", flag: "kr" }, // 한국어
  { code: "en", name: "English", flag: "us" }, // 영어
  { code: "my", name: "မြန်မာ", flag: "mm" }, // 미얀마어
  { code: "km", name: "ភាសាខ្មែរ", flag: "kh" }, // 크메르어 (캄보디아)
  { code: "uz", name: "Oʻzbekcha", flag: "uz" }, // 우즈베크어
  { code: "id", name: "Bahasa Indonesia", flag: "id" }, // 인도네시아어
  { code: "ne", name: "नेपाली", flag: "np" }, // 네팔어
  { code: "th", name: "ไทย", flag: "th" }, // 태국어
  { code: "si", name: "සිංහල", flag: "lk" }, // 싱할라어 (스리랑카)
  { code: "vi", name: "Tiếng Việt", flag: "vn" }, // 베트남어
  { code: "lo", name: "ລາວ", flag: "la" }, // 라오스어
  { code: "tl", name: "Tagalog", flag: "ph" }, // 타갈로그어 (필리핀)
];

참고 자료

profile
프론트엔드 엔지니어를 희망합니다 :-)

0개의 댓글