여러 언어를 지원하는 사이트를 맡았다. 언어를 세션에 담고 있었다.
$_SESSION['lang'] = 'ko';
처음에는 그것이 자연스러워 보였고 화면도 잘 나왔다. 문제는 화면 밖에서 나왔다.
Table of contents
Open Table of contents
증상 — 링크를 보내면 다르게 열렸다
영어로 보던 화면의 주소를 다른 사람에게 보냈다.
A 가 영어로 보다가 링크 공유
B 가 열면 B 의 언어로 보임
주소는 같은데 보이는 내용이 사람마다 달랐다. 보낸 쪽은 무엇을 보라고 했는지 설명할 방법이 없었다.
검색 쪽에서도 문제가 있었는데 URL 이 하나라 한 언어만 수집됐다. 언어별로 구분할 근거가 주소에 없었다. hreflang 을 걸 자리도 없었다. $_SESSION 은 그 사람의 브라우저 안에만 있는 값이었다.
언어를 주소로 옮겼다
URL 앞머리에 언어를 넣었다.
/ko/product/123
/en/product/123
주소가 다르면 내용이 다르다는 것이 밖에서도 보인다. 화면 코드는 그 값을 읽어 쓰기만 하면 됐다.
링크를 보내면 받는 사람도 같은 언어로 열게 됐다. 검색도 언어별로 나뉘어 각각 수집됐다. 주소가 스스로 무엇을 담는지 말하게 된 셈이다.
언어가 없는 주소의 처리
/product/123 처럼 언어가 빠진 주소로 들어오는 경우를 정해야 했다. 고를 방법이 몇 가지 있었다.
[기본 언어로] 항상 같은 언어
[브라우저 설정] 사용자에 맞춤
[지역으로 추정] 접속 위치
브라우저가 보내는 Accept-Language 를 쓰기로 했다.
Accept-Language: ko-KR,ko;q=0.9,en;q=0.8
q 값이 선호 순서다. 그 순서대로 훑어 우리가 지원하는 것과 맞는 첫 번째를 골랐다.
사용자 선호 ko, en, ja
우리 지원 en, ja
↓
en
하나도 맞지 않으면 기본 언어로 갔다. 규격에는 406 을 줄 수도 있다고 돼 있는데 그건 쓰지 않았다.
고른 뒤에는 302 로 언어가 들어간 주소로 옮겼다.
/product/123 → /en/product/123
그러지 않으면 언어 없는 주소가 계속 돌아다닌다. 같은 주소가 사람마다 다른 언어로 열리는 처음 문제로 돌아가는 것이다.
판단 기준 — 명시적인 쪽
사용자가 고른 언어를 Cookie 에 저장해 두는 것도 넣었다. 그러면 주소와 저장이 부딪친다.
주소에 언어가 있음 → 그것
없음 + 저장된 것 → 그것
없음 + 저장도 없음 → Accept-Language
주소가 가장 세다. 저장이 이기면 받은 링크가 내 언어로 바뀌어 공유가 다시 깨지기 때문이다. 명시적으로 적힌 것이 이긴다.
문구를 파일로 뺐다
화면 문구가 PHP 코드에 박혀 있었다.
echo "장바구니";
키로 바꾸고 언어별 파일에서 읽게 했다.
echo lang('cart');
lang/ko.php 'cart' => '장바구니'
lang/en.php 'cart' => 'Cart'
lang() 이 키를 찾지 못하면 빈 문자열이 나왔고 버튼에 글자가 없었다. 무엇이 빠졌는지 화면만 봐서는 몰랐다.
[빈 칸] 뭐가 빠졌는지 모름
[키 이름] 무엇이 빠졌는지 보임
그래서 개발에서는 키 이름이 그대로 보이게 하고 운영에서는 기본 언어 문구가 나오게 했다. 운영 화면에 cart 가 보이면 그것도 이상하기 때문이다.
기본 언어 파일의 키와 다른 언어 파일의 키를 맞대어 차이를 내는 것도 만들었다. 새 문구는 기본 언어에만 넣기 쉬운데 그 목록을 돌리면 빠진 것이 나온다.
정리
- 언어를 세션에 두면 같은 주소가 사람마다 다르게 열린다
$_SESSION은 그 사람의 브라우저 안에만 있는 값이다hreflang을 걸 자리가 없어 검색도 한 언어만 수집한다- 언어를 주소에 넣으면 주소가 내용을 담는다
- 언어 없는 주소는
Accept-Language의q순서로 고른다 - 고른 뒤에는
302로 언어가 든 주소로 옮긴다 URL과Cookie가 부딪치면URL이 이긴다- 문구는
lang()과 언어 파일로 분리한다 - 빠진 번역은 개발에서 키 이름으로 드러내고 운영에서는 기본 언어로 둔다
- 기본 언어 파일과 맞대어 빠진 키 목록을 내 둔다