보통 테마는 테마의 루트에 style.css 를 둔다. 그러면 코어는 style.css 파일에서 테마의 정보를 추출한다. 이게 정석이다.
그러나 오늘 sage라는 테마를 보다 보니, sage/resources 디렉토리에 functions.php 파일과 style.css 파일이 발견되었다. 이게 어떻게 가능한 가 싶어 뜯어 보니, wp-includes/themes.php 파일에 정의된 search_theme_directories() 함수에서 이런 동작을 지원하는 것이었다.
wp-content/themes 의 디렉토리를 찾아, 해당 디렉토리에 있는 style.css 파일을 발견하면 메타데이터를 읽는다.
style.css 파일이 발견되지 않는다면 해당 디렉토리의 하위 디렉토리에서 style.css를 검색한다. 파일이 빌견되면 그렇다면 테마의 루트는 해당 디렉토리가 된다.
예를 들어 wp-content/themes/foo 라는 테마 루트 디렉토리에 bar라는 하위 디렉토리를 두고 거기에 style.css를 둔다. 그러면 wp-content/themes/foo/bar/style.css로 경로가 만들어질 것이다. 이 때 코어는 wp-content/themes/foo/bar를 테마 루트로 인식하게 된다.
최근 테마 루트에 composer.json, package.json등 여러 설정 파일들이 존재하게 되고 이것들은 이 디렉토리를 포기할 수 없다. 그런데 이런 설정 파일과 테마의 템플릿이 서로 뒤섞이면 매우 지저분하다. 이것을 의식한 건 아닌가 모르겠다. 아무튼 이렇게 하면 템플릿 코드와 여러 설정 파일을 깔끔히 분리할 수 있어 좋은 방법이라고 생각한다.
워드프레스에는 WP-CRON이라는 스케쥴에 맞춰 특정 작업을 실행하는 API가 있다. 액션 스케쥴러는 이것에 기반해 사용하기 편리한 비동기 실행 큐 라이브러리이다. 오토매틱에서 제작하고, 우커머스나 구독 플러그인 등 다수 플러그인에서 이미 사용되고 있다.
이번에 진행하는 프로젝트에서 이것을 사용해 볼 기회가 있었다. 간단하게 비동기 큐가 필요했고, 구조를 복잡하게 하는 것보다는 간단한 프로토타이핑으로 단순한 구성을 원했기 때문이다.
액션 스케쥴러는 워드프레스 기반으로 개발할 때, 비동기 큐를 써야 한다면 훌륭한 선택이라고 생각한다. 하여 차후 액션 스케쥴러를 다시 써야 할 때를 위해 간단히 사용한 것을 정리한다. 공식 홈페이지에서 5분이면 간단하게 얻을 수 있는 정보의 복제 말고, 조금 애매했던 개념을 설명하는 포스트이다.
액션이란 특정 시간에 특정 작업을 하기 위한 수단이다.
액션의 청구라는 개념이 있다. 영어로는 대략 stake claim 정도로 표현되는데, 액션 테이블에서도 claim_id 라는 것이 발견되기도 하고, 공식 문서에서도 한 두번 정도 언급될 뿐 이것이 무엇인지 자세히 설명하지 않는다. 여기서 간단히 설명하자.
액션은 이벤트를 예약하는 수단이다. 그러나 이것이 실행 큐에서 실제로 실행되기 위해서는, 먼저 큐가 액션의 스케쥴을 파악하여 적절한 액션을 골라 실행하기로 결정하는 작업이 먼저 필요하다. 해당 시점에 적절한 액션을 골라 실행을 결정하는 과정이 액션의 청구(claim)이며, 실행 큐가 한번에 작업하기로 결정한 액션에게는 동일한 청구 ID (claim ID)를 부여한다.
실행 큐가 실행되면서 한 번에 액션 뭉치를 가져와 일괄적으로 작업하는 것을 배치(batch)라고 한다.
배치에는 배치 크기 (batch size)라는 개념이 있다. 액션을 청구할 때 한번에 이 갯수만큼의 액션을 정한다. 배치 크기의 기본값은 25이다. 기본값 대로라면 실행 큐는 한 번에 최대 25개의 액션을 가져와 작업을 진행할 것이다. 이 때 25개의 작업은 동시 작업(multitasking)이 되는 것은 아니고, for-loop 을 돌면서 순차 진행된다.
실행 큐는 WP CLI를 통해 커맨드라인으로 실행이 가능하다. 커맨드라인에서는 제한 시간의 제약기 없기 때문에 상당히 오랜 시간 동안 작업을 해도 에러만 없다면 아무 문제 없다.
실행 큐는 기본적으로는 WP-CRON의 ‘매 분 (every minute)’ 스케쥴에 맞춰 동작한다. 그러므로 리눅스 기준 매 분마다 정확하게 실행 큐가 동작하는 것을 보장하려면 아래처럼 작업을 한다.
wp-config.php에 define( 'DISABLE_WP_CRON', true ); 를 삽입.
실행 큐는 훅의 콜백으로 단 1개만 실행된다. 즉, 기본값 대로라면 매 분 1개의 실행 큐가 생성되어, 25개의 액션만을 청구하여 배치를 하는 것이다.
이 기본값들은 액션 스케쥴러 개발자들이 다양한 상황을 염두에 두고 매우 동작에 제약을 둔 설정이다. 만약 운영하는 서버가 성능이 보다 괜찮다면 큐를 좀 더 둘 수 있다. 이 때 ‘Increasing Initialisation Rate of Runners‘에서 말하는 것처럼 ‘action_scheduler_run_queue’ 훅에 맞춰 admin-ajax.php 에 원하는 갯수만큼의 실행 큐를 증가시켜 볼 수 있다. 간단한 로그를 찍는 코드로 테스트 해 봤을 때, 저렇게 만들어진 큐에서 작업이 진행되며 AJAX 요청을 보낸 쪽에서는 더 작업을 하지 않는 듯. 그러므로 N개를 추가시켰으면 N+1의 실행 큐가 아닌 N개의 실행 큐가 생성되는 모양이다.
여러 개의 실행 큐가 admin-ajax.php 요청을 통해 실행한다면, 비동기적으로 동시 실행되는 그림이 만들어진다. 이 때 동시 배치 (concurrent_batch)라는 정수값이 중요한 역할을 한다. 이 값의 디폴트는 1이다.
각각의 실행 큐는 각자 작업을 진행하면서, 현재 청구(claim)되어 있는 액션의 개수가 이 동시 배치를 넘는지 넘지 않는지 체크하여 초과하면, 작업을 하지 않도록 프로그래밍 되어 있다.
디폴트인 1을 바꾸지 않으면 다음과 같은 문제점이 발생한다. 설명이 복잡하니 불릿으로 나누어 설명한다.
첫번째 실행 큐가 현재 청구된 액션 수와 동시 배치 수를 비교한다. 가장 처음이므로 청구된 액션 수는 0이다.
동시 배치값 1은 0보다 크므로, 첫번째 실행 큐는 배치를 시작한다.
첫번째 실행 큐가 배치를 하기 위해 액션을 청구한다. 배치 크기만큼 (기본값이라면 최대 25개) 액션을 잡을 것이다.
두번째 실행 큐가 동작을 시작한다. admin-ajax.php를 통해 실행되므로, 첫번째 실행 큐와는 서로 독립적인 프로세스에서 동작한다.
두번째 큐와 현재 청구된 액션 수와 동시 배치 수를 비교한다. 첫번째 큐가 이미 25개의 액션을 청구한 상태이다. 엄청나게 빠른 속도로 액션들이 처리되고 있는 것이 아니라고 가정하자. 즉 아직 여전히 25개가 청구된 상태이다.
동시 배치값 1은 25보다 작다. 두번째 큐는 실행을 멈춘다.
즉 두번째 큐는 아무 일도 하지 않고 일을 마친다. 뭐, 세번째, 네번째 큐가 있더라도 액션 청구가 1보다 줄지 않는 한 일을 하지 않을 것이다.
그러므로 동시 처리 수는 (총 실행 큐의 수) * (배치 사이즈) 에 근접하게 설정해야 제대로 동시성을 기대할 수 있다. 이게 가장 핵심인 부분이다.
이 부분은 나중에 덧붙인 글이다. 동시 처리 수에 대한 개념이 부족해 보충 설명을 적는다. 위 설명은 액션이 충분히 길 때에만 적절한 설명이다. 동시 처리 수를 실행 큐 수 * 배치 사이즈에 맞추더라도 하나의 큐가 모든 작업을 하려고 들 수도 있다. 하나의 실행 큐는 자기에게 허락된 메모리량, 처리시간의 여유가 되는 만큼 최대한 액션들을 많이 가져려고 한다.
예를 들어 보자. 총 실행 큐 수는 3개, 배치 사이즈 5개, 동시 처리 수를 15개로 잡았다고 보자. 그리고 처리해야 할 액션 수는 15개라고 가정하자. 그러면 총 실행 큐 3개가 각각 액션 5개를 맡아 처리할 것 같지만, 실제로는 그렇게 동작하지 않을 수 있다.
만약 주어진 액션이 너무 간단해 하나의 실행 큐가 5개의 액션을 모두 처리한 다음엔, 그 실행 큐는 메모리와 실행 제한 시간이 허락된 선에서 추가적인 액션을 불러와 일을 한다. 만약 액션 처리가 너무 간단했다면 여전히 하나의 실행 큐가 모든 액션을 혼자 담당할 수도 있다. 다른 실행 큐가 비동기적으로 실행되기도 전에 말이다. 정말 너무 일이 간단하다면 하나의 실행 큐가 동시 처리 수와 무관하게 스케쥴 된 모든 액션들을 가져와 작업을 할 수도 있다!
액션 스케쥴러는 유연성이 상당히 높다. 액션 스케쥴러는 플러그인으로도, 내 플러그인이나 테미의 서브 프로젝트로도 사용 가능하다.
액션 스케쥴러는 원래는 wp_posts 테이블에서 액션을 관리했던 모양이다. 처음 실행하면 마이그레이션 훅을 돌리는 것이 있는데 아마 예전의 포스트 테이블의 내용을 별도 테이블로 전환하는 과정을 돌리는 것 같다. 굳이 예전으로 돌릴 필요는 없는 듯.
작업을 돌리는 데 분 단위로 동작한다는 점이 약간 제약으로 생각될 수 있다. 그러나 정말 다량의 작업량이 있다거나 지정된 스케쥴에 동작하는 것이 우선적인 목적이라면 해당 단점이 크게 부각될 것 같지는 않다. 액션 스케쥴러는 확실히 워드프레스 환경에서 상당히 괜찮은 라이브러리임에 틀림없다고 생각한다.
이 예시는 wp_temp_table 에 middle_name 이라는 필드를 추가했을 때 결과이다. 이런 식으로 필드 타입, 기본값 등등이 잘 변경되는지 확인할 수 있다.
필드가 삭제나 인덱스 삭제는 되지 않는다. 별도로 필드를 없애는 구문을 추가해야 할 것 같다.
코덱스에서는 INDEX 보다는 KEY 쓰리고 하지만, INDEX 가 안 되는 건 아니다. 당연히. 그리고 이미 만들어진 인덱스 변경도 잘 되지 않는다. 별도로 구문을 써야 한다. 마찬가지로 PK 선언시 두 칸을 띄워 쓰라고 하는데, 딱히 왜 그래야 하는지는 잘 모르겠다.
복잡한 변경은 잘 인지하지 못하는 것 같다. 예를 들어 필드의 타입과 기본값까지 동시에 변경 처리하는 경우, 그 둘의 변경점을 잘 감지하지 못하는 것 같다.
CREATE TABLE 선언시 CREATE TABLE IF NOT EXISTS 나, 별도로 테이블이 있는지 없는지 체크하는 따위의 삽질을 하지 않아도 된다. dbDelta 가 적절히 CREATE TABLE 구문과 현재 선언된 테이블의 구조를 파악해 CREATE TABLE을 그대로 사용하기도 하지만, CREATE TABLE 대신에 ALTER TABLE 를 스스로 만들어 내거나, 변경점이 없다면 아무 일도 하지 않기 때문이다.
이런 것도 한 번 체크해 보라. dbDelta() 는 까다로운 녀석이다. SQL 구문에 변경점이 전혀 없어, 딱히 변경할 것이 없어 보인다. 그래도 진짜 아무 일도 실행하지 않는지 (즉, dbDelta()가 빈 배열을 리턴하는지) 확실히 체크해 보라. 간혹, 예를 들어, 타입을 대문자로 썼다거나 하는뭔가 실수 아닌 실수가 있다면 dbDelta()는 변경할 것이 없는데도 계속 ALTER TABLE 구문을 만드는 삽질을 하고 있을 수도 있다.
같은 패턴을 쓰면 된다. 이 때 사용자는 테이블을 업데이트하기 위해 플러그인을 비활성화/활성화를 할 수 없다. 그러므로 plugins_loaded 같은 액션의 콜백에서 이 함수를 한 번 호출해야 한다.
만약 이렇게 되면 activation callback 에서 굳이 테이블을 생성하는 작업을 중복해서 할 필요는 없는 것 같다. 아, 물론 플러그인 활성화 시 바로 초기화 작업 같은 것을 해 준다면 이야기가 달라지겠지만.
결론
급하게 테스트해보고 메모하듯이 글을 적었다. 내가 틀린 것을 적었을 수도 있다! 어쨌든 변경점이 생길 때 dbDelta()가 리턴하는 결과와 테이블의 구조를 실제로 잘 체크해야 한다. dbDelta()가 기능 개선이 될 수도 있으니 말이다.
암튼 현재 dbDelta() 함수는 다른 웹 프레임워크의 마이그레이션 솔루션 (예를 들어, 장고) 만큼 테이블 변경을 세련되게 챙겨주지는 못하지만, 그래도 간단하게 테이블 변경을 찜쪄먹기에는 나름 유용한 도구로 보인다. 다만, 이 녀석이 진짜 까다로우니 조심해서 쓰도록 하자.
WP CLI로 많이 사용하는 명령어 중 하나는 워드프레스 코어 파일을 다운로드 받는 것이다.
워드프레스 루트 디렉토리를 만들고, 거기서 wp core download를 입력하면 최신 버전의 코어 파일이 다 준비되니 편리하다.
그런데, 나는 개인적으로 문제가 하나 있다. SK 브로드밴드 인터넷을 사용하면 해외 사이트로의 다운로드 속도가 미칠듯이 느리다. 대략 워드프레스 코어 파일을 압축하면 20메가바이트 내외인데, 대략 5분 이상의 시간이 걸린다. 그렇다. 나는 SK 인터넷을 사용한다. 어쩌다 집에서 다운로드를 받으면 타임아웃에 걸릴 정도로 느리다.
이 때 한가지 요령이 있다. ~/.wp-cli/cache 디렉토리에 WP CLI는 미리 파일을 캐시해 두었다가 매번 다운로드 받지 않고 여기의 파일을 재활용한다.
이 cache 디랙토리에는 core, plugin, translation 등의 디렉토리가 있는데, 이 core 디렉토리에 코어 파일을 버전과 언어별로 압축 파일을 저장해 둔다.
예를 들어, 워드프레스 5.8.3버전의 한국어 버전은 wordpress-5.8.3-ko_KR.zip으로 저장된다. 한편 영문 버전은 wordpress-5.8.3-en_US.tar.gz로 저장된다. 왜 영문 버전은 .tar.gz로 받아지고 한국어는 .zip으로 받아지는건지, 이게 규칙인지는 잘 모르겠다.
참고로 영문 코어를 받으려면 https://wordpress.org/wordpress-{version}.{zip,tar.gz} 처럼 URL을 입력하면 된다.
한국어 코어는 https://ko.wordpress.org/wordpress-{version}-ko_KR.{zip,tar.gz} 처럼 URL을 입력하면 된다.
영문 최신 버전은 https://wordpress.org/latest.tar.gz 으로 받을 수 있다. 그러므로 한줄짜리 워드프레스 설치 스크립트를 아래처럼 응용해 볼 수도 있긴 하다.
mkdir ~/wp_root && cd ~/wp_root
wget -O - 'https://wordpress.org/latest.tar.gz' | tar xzf - --strip=1
암튼 정리하자. wp core download 다운로드 속도가 너무 느려 타임아웃까지 걸리는 문제가 생길 때는 이렇게 하자.
워드프레스 코어 파일을 웹브라우저나 다운로드 관리자 같은 걸로 어쨌든 받는다.
영문이면 wordpress-{version}-en_US.tar.gz 로, 한국어는 wordpress-{version}-ko_KR.tar.gz 로 이름을 변경한다.
워드프레스에서 일반적인 포스트는 관리자 > 설정 > 고유주소에서 적절히 주소 체계를 수정할 수 있지만, 커스텀 포스트의 주소 체계는 딱 고정되어 있다. 보통 <커스텀 포스트 타입 이름>/<포스트 이름(postname, 또는 slug)> 으로 되어 있으며, <커스텀 포스 타입 이름> , 즉 “앞부분” 정도만 register_post_type() 함수의 파라미터 조정을 통해 변경할 수 있다.
우선 앞부분을 변경하는 방법부터 설명한다. ‘my-cpt’란 포스트 타입을 위 예제처럼 입력했을 때, ‘rewrite’ 파라미터 ‘slug’ 항목을 ‘cpt’로 변경하였다. 그러므로 이 타입 포스트들의 URL은 이제 /cpt/<포스트 이름> 체계를 따르게 된다. 임의로 cpt라는 문자열을 주었고 이 문자열은 슬래시도 허용한다. 그러므로 ‘my/cpt’ 같이 줘도 문제 없다.
“뒷부분” 변경하기
앞부분은 아주 쉽게 변경 가능하다. 문제는 뒷부분이다. 커스텀 포스트는 자유롭게 이 뒷부분을 변경할 수 없으며, 포스트 이름으로 고정되어 있다. 가령 이 부분을 포스트의 ID로 변경하려고 한다. 하지만 프론트엔드에서 만날 수 있는 설정으로는 이것이 불가능하다.
그럼 별도의 액션/필터를 활용하여 이 부분을 수정해 보자. 우선 목표부터 좀 설명하기 쉽게 정리하자.
목표: My CPT 타입의 포스트 ID 2476의 포스트 이름은 'aaaa'다.
현재 주소는 /cpt/aaaa/ 이다. 이것을 /cpt/2476/ 으로 변경한다.
워드프레스의 URL 매칭과 메인 쿼리 방식을 생각했을 때, 위 목표를 가능하게 하려면 다음과 같은 3가지를 작업해야 한다.
워드프레스가 커스텀 포스트를 등록할 때, 하드코딩된 permastruct(고유주소 구조)를 포스트 이름 기반에서 포스트 아이디 기반으로 수정할 것.
커스텀 포스트의 퍼마링크를 가져올 때, 정확한 주소가 나오는지 확인하고 수정할 것.
rewrite_rule 을 생성할 때 포스트 아이디 기반 다시 쓰기가 메인 쿼리에서 정확히 쿼리될 수 있는지 파라미터를 확인하고 수정할 것.
변경 #1: permastruct 수정
커스텀 포스트의 permastruct (고유주소 구조)는 커스텀 포스트 등록시, 포스트의 이름으로 고정되어 있다. 구현부는 WP_Post_Type::add_permastruct() 메소드를 참고한다.
이것을 수정하려면 ‘registered_post_type’ 액션을 사용하여 글로벌 변수 $wp_rewrite에 접근한다. 그리고 여기서 등록된 내역을 수정한다.
‘%post_id%’를 넣어준 덕분에 ‘슬래시가 아닌 아무 문자열’ 정규식 부분이 ‘연속된 숫자’로 변경된 것을 볼 수 있다.
변경 #2: permalink 수정
다시 쓰기 규칙이 변경되었으니, 이제 “/cpt/2476/”으로 주소가 변경되었을 것 같지만, 아직은 부족하다. 포스트 편집기에서 보면 퍼마링크가 생뚱맞게 “/cpt/%post_id%/”로 나올 것이다. 이것은 get_permalink() 함수가 커스텀 포스트에 대해서는 %post_id%라는 치환 문자열을 고려하지 않기 때문이다.
생뚱맞은 %post_id% 가 URL 주소로.
이 문제는 ‘post_type_link’ 필터를 통해 해결이 가능하다. 아래 코드까지 붙여 넣는다.
이제 URL 주소가 그럴듯하게 나온다! 코드를 수정한 후 반드시 관리자 > 설정 > 고유주소를 재방문하여 rewrite_rule을 다시 생성해야 하는 것을 잊지 말자.
URL이 수정되었다.
변경 #3: rewrite_rule 수정
주소는 올바르게 찍히지만, 해당 주소로 들어갔을 때 404 NOT FOUND 페이지가 나온다. 아직 한 가지가 부족하다. 아까 다시 쓰기를 했을 때 규칙을 보자.
정규 표현식: cpt/([0-9]+)(?:/([0-9]+))?/?$
다시 쓰기: index.php?p=$matches[1]&page=$matches[2]
다시 쓰기에서 p=$matches[1] 부분이 중요하다. 우리가 ‘/cpt/2476/’ 경로로 들어왔을 때, 이것은 ‘index.php?p=2476&page=’로 치환된다. 여기서 포스트 ID 2476번을 쿼리하라는 명령은 올바르게 전달되지만, 포스트 타입 파라미터가 생략되어 있다. 포스트 타입 파라미터가 생략되면 기본값인 ‘post’가 사용된다. 그러므로 실제로 SQL 쿼리문은 다음처럼 입력되어버린다.
이 설정은 XDebug 3의 설정이다. 2와 다르다. 버전을 확인하여 3.X인지 확인하자.
mode가 develop도 같이 넣어 에러 출력도 같이 더했다. 원하지 않으면 debug만 넣으면 된다.
start_with_requst=yes로 하는 방법도 있다. 그런데 이렇게 하면 언제나 디버깅이 실행된다. 브라우저에서 적절히 ON/OFF를 결정할 수 있으려면 trigger를 설정하면 된다.
브라우저에서 ON/OFF를 결정하는 방법은 세션 쿠키를 보내는 방법이다. 이때 사용하는 설정이 idekey. 이 값을 phpstorm-xdebug로 정했다. 디버깅은 VSCode 뿐만 아니라 PhpStorm에서도 번갈항 쓰는 데다, PhpStorm이 더 주력이기 때문이다.
브라우저 설정
start_with_requst=trigger로 설정했으면 이 과정이 필요하다. 브라우저에 절절히 세션 쿠키를 넣을 방법이 필요하기 때문이다. 어지간한 브라우저마다 PHP 디버깅을 위한 확장 프로그램을 제공할 것이다. 나는 주로 파이어폭스와 XDebug Session Cookie를 사용한다. 설치한 다음 쿠키 값으로 phpstorm-xdebug를 설정한다.
IDE Key는 xdebug 설정과 동일하게.
플러그인을 통해 세션 쿠키 값을 셋팅할 수 있다.
디버깅 시작
워드프레스의 프론트 메인 파일인 index.php에 적당히 브레이크 포인트를 넣고 디버깅을 시작해 봤다. 아래처럼 PHP 처리가 중간에 멈추면서, 해당 과정 중의 콜스택, 변수 정보를 모두 속속들이 볼 수 있다. 여기에서 함수 안으로 들어갈지, 다음 코드 라인으로 진행할지 결정할 수 있다.
브레이크 포인트에 딱 걸림!
현재 브레이크 포인트에 걸려서 중지된 라인이 하이라이트되었다.
디버깅 툴이다. 코드 진행을 제어할 수 있다.
현재 상태의 변수 이름과 값을 확인할 수 있다. 매우 유용하다.
콜스택이 나타난다.
배열의 활용이 두드러진 PHP 코드는 런타임 때 대체 연관 배열 변수 값이 어떤 구조인지 알아보기 어려운 경우가 많다. 이 때 사용할 수 있는 가장 강력한 툴이 바로 XDebug를 활용한 원격 디버깅인 것 같다. 이렇게 XDebug의 원격 디버깅 기능을 활용하면 프로그램의 상황을 자세히 파악하면서 개발할 수가 있다. 보다 품질 좋은 코드를 작성하려면 반드시 필요하다.
예를 들어 WP_ROOT가 /home/changwoo/develop/wordpres였다면 이 아래 foo라는 디렉토리 아래 워드프레스가 설치된 것이 보일 것이다. 그리고 이 워드프레스의 도메인은 http://foo.dev.site로 설정되어 있다. 나머지 서버 설정은 따로 진행하면 된다.
예전에 워드프레스 플러그인 만들기라는 문서를 작성한 적이 있다. 꽤 오래된 문서인데 (벌써 햇수로 6년 되었다!), 워드프레스 API는 여기서 그렇게 변경된 건 없는 것 같다.
이 문서는 귀찮아서 업데이트하지 않고 있는데, 여기서의 텍스트 번역 부분은 그 당시 워드프레스 환경 및 일반적인 PHP 애플리케이션에 대해서는 맞다. 그러나 더이상 이 방법이 올바르다고 보기 어렵다.
왜냐면 PoEdit을 이용한 소스 검색은 플러그인/테마의 헤더까지 번역문을 생성하지 않기 때문이다.
올바른 방법
플러그인 목록에 플러그인 제목과 나머지 헤더 내용까지 정확하게 번역하려면 wp-cli를 사용하는 것이 좋다. 이 앱에 대해서는 따로 소개하는 포스팅을 작성할 생각인데, 오늘은 맛보기로 미리 언급하기로 하자. 🙂
wp cli 에는 i18n 명령이 있다. i18n은 ‘internationalization’을 간단히 줄여 쓸 때 자주 사용한다. 욕 아니다. i와 n 사이에 정확히 18글자가 있기 때문이다. 굳이 번역하면 ‘국제화’ 정도?
사족으로, 이에 반대되는 개념이 지역화, 즉 ‘localization’ 인데 이것도 쓰기 어렵다고 ‘l10n’이라고 쓴다.
i18n 명령의 서브 명령어로 ‘make-pot’과 ‘make-json’ 둘이 있다.
make-json은 블록 에디터가 등장한 이후 자바스크립트에서 보다 편리하게 번역문을 처리하기 위한 새 방법을 지원하는 명령어고, 오늘 포스팅의 범위를 살짝 벗어난다.
그럼 make-pot을 살펴보자. foo라는 플러그인을 예시로 레시피를 만들어 보자. 참고로 도메인은 bar라고 가정하자. 즉 소스 코드에서 __( 'Hello, World!', 'bar' ); 처럼 번역 텍스트를 작성했다는 뜻이다.
cd <wordpress_path>/wp-content/plugins/foo
mkdir languages # 디렉토리가 만들어지지 않았다면.
wp-cli i18n make-pot . ./languages/bar.pot --domain=barCode language:Bash(bash)
wp-cli로 번역 텍스트를 추출하면 기특하게 헤더 텍스트까지 알아서 잘 고려한다. 게다가 커맨드라인 방식이라 스크립트 처리하기 용이하다.
이후 PoEdit을 이용해 번역문을 만든다. 만약 한국어로 번역했다면, 번역 파일의 이름은 bar-ko_KR.po 가 된다.
기타: XDebug 사용시 트러블슈팅
아마 xdebug를 사용하면 wp-cli로 pot 파일 생성시 ‘Maximum function nesting level of …’ 같은 에러를 만날 것이다. 이것은 xdebug가 디버깅을 위해 함수 호출 레벨을 낮춰 두기 때문이다. 아마도 소스 코드에서 번역문을 스캔할 때 꽤 많은 함수 중첩이 필요한가 보다.
플러그인에는 플러그인 헤더가 반드시 필요하다. Header Requirement 코덱스에도 잘 나와 있지만, 몇가지 보충 설명을 더하고자 한다.
플러그인 헤더를 작성하면 몇몇 내용은 플러그인 목록 정보에 반영된다. 한편 몇몇 내용은 플러그인 활성화 때 플러그인이 정상적으로 동작하는 환경인지 점검하기 위한 용도로 사용되기도 한다.
목록 정보에 반영되는 필드들
Plugin Name: 플러그인 필수 헤더. 플러그인의 제목으로 사용.
Plugin URI: 기재하면 ‘플러그인 사이트 방문’이라고 나온다.
Description: 제목 옆에 플러그인 설명란으로 사용.
Version: 버전 정보로 표시.
Author: 작성자 정보로 표시.
Author URI: 기재하면 작성자 이름이 하이퍼링크로 변경. 클릭하면 해당 주소로 이동.
플러그인 활성화시 영향을 주는 필드들
Requires at least: 플러그인이 요구하는 최소 워드프레스 버전이다. 만약 플러그인에서 사용하는 함수들을 워드프레스가 충분히 지원하는지 안전하게 확인하기 위한 용도로 사용된다. 만약 플러그인이 설치된 워드프레스가 이 버전보다 낮다면 활성화되지 않는다. 잘못하면 사이트 전체가 멈추는 불상사가 생길 수도 있기 때문이다.
Requires PHP: 플러그인이 요구하는 최소 PHP 버전이다. 마찬가지로 활성화시 PHP 버전을 체크하여 서버의 PHP 버전이 이 값보다 낮다면 플러그인은 활성화되지 않는다. ‘Requires at least’ 필드와 마찬가지로 오작동하면 사이트 전체를 멈출 수도 있기 때문이다.
다국어 지원에 영향을 주는 필드들
Text Domain: 플러그인이 사용하는 텍스트도메인을 정확히 기재한다. 다국어 파일이 제대로 만들어졌다면 플러그인이 해당 언어로 표시된다. 위 그림에서도 ‘나란 옵션 편집기’나 ‘아키스밋’이나 헤더는 영어로 작성되었지만, 한국어 번역 파일이 존재하기 때문에 해당 헤더 필드가 번역되어 출력된다.
Domain Path: 플러그인의 번역 파일을 정확히 찾기 위해 도움을 주는 필드이다.
다음에는 플러그인 목록 표시에 잘 반영되는 정확히 번역 파일 제작 방법에 대해 포스팅한다.
오늘은 wp-util 스크립트를 소개하고자 한다. 경로는 /wp-includes/js/wp-util.js 이고, 이 스크립트 안에는 두가지 도구가 있는데, 하나는 wp.template, 나머지 하나는 wp.ajax이다. 간단하게 포스팅하는 것이 1일 1워프의 주제인데, 오늘 하루에 두가지를 다 포스팅하기는 내가 너무 힘들고 분량도 아까우므로 🙂 오늘은 그중 하나인 wp.template 만 알아보도록 하자.