치환이란 어떠한 것을 다른 무언가로 교체하는 것을 의미한다. 언뜻 보면 바꾸는 것과 비슷해 보이나 치환은 바꾼다는 뜻보단 대체로서의 의미가 맞는 말이다. 웹 프로그래밍의 경우 HTML 소스와 함께 병행하여 쓰다보니 소스가 상당히 보기 불편해 지는 경우가 많다. 이럴때 사용할 수 있는게 치환자인데 이는 비공개 소스에 대한 유저용 치환자를 제공해 주는 템플릿 환경에서 많이 사용되곤한다.
간단한 예를 보면 아래와 같다.
<div class="apple">
총 사과개수: {총 사과개수}<br>
남은 사과개수: {남은 사과개수}<br>
</div>
<?php
ob_start();
include __DIR__.'/apple.html';
$html = ob_get_clean();
$subs = array('{총 사과개수}'=>40,'{남은 사과개수}'=>15);
echo str_replace(array_keys($subs), array_values($subs), $html);
/** result
총 사과개수: 40
남은 사과개수: 15
***/
위와 같이 html 소스는 보기용으로만 사용하고 치환자를 이용하여 프로그램을 입히는 식으로 사용이 가능하다. 물론 간단한 예제이기에 저 위의 소스를 출력하기 위해서 사용하는건 아니고 조금더 복잡한 내용을 가진 html 소스에서 사용할 수 있다.
두번째로 알아볼 방법은 바로 언어팩을 만드는 방법이다. 언어팩 같은 경우 여러 기술들이 있겠지만 내가 사용하는 방법은 주로 아래와 같다.
<?php
$lang_pack = array(
'안녕하세요 저는 웹개발자 입니다.'=>'Hello I am a web developer.',
'제가 좋아하는 숫자는 %s 이며 싫어하는 숫자는 %s입니다.'=>'My favorite number is %s and my dislike is %s.',
'세일은 %s% 이상 입니다.'=>'Sale is over %s%.',
'제가 좋아하는 숫자는 $1 이며 싫어하는 숫자는 $2입니다.'=>'My favorite number is $1 and my dislike is $2.',
);
// 치환 번역 함수
function lang_subs($txt = '', $args = array(), $replace = false){
global $lang_pack;
if( empty($lang_pack[$txt])){ return $txt; }
if( !empty($lang_pack[$txt])){
$txt = $lang_pack[$txt];
if( count($args) > 0){
if( $replace === false){
$args_str = implode("','",$args);
// sprintf 에서 2번째 인자에서 %[값]으로 인식하여 %를 하나더 붙이지 않으면 %문자가 출력되지 않기때문에 치환
$txt = str_replace(array("%","%%s"),array("%%","%s"),$txt);
eval('$txt = sprintf($txt,\''.$args_str.'\');');
}
else{
$txt = str_replace(array_keys($args),array_values($args),$txt);
}
}
}
return $txt;
}
echo lang_subs('안녕하세요 저는 웹개발자 입니다.').'<br>';
echo lang_subs('제가 좋아하는 숫자는 %s 이며 싫어하는 숫자는 %s입니다.',array(8,4)).'<br>';
echo lang_subs('세일은 %s% 이상 입니다.',array(50)).'<br>';
echo lang_subs('제가 좋아하는 숫자는 $1 이며 싫어하는 숫자는 $2입니다.',array('$1'=>8,'$2'=>4),true).'<br>';
/** result
Hello I am a web developer.
My favorite number is 8 and my dislike is 4.
Sale is over 50%.
My favorite number is 8 and my dislike is 4.
*/
위의 예제는 실제 내가 번역 작업할때 쓰던 방법중 하나로 간단하게 나마 보여준 예제이며 규모가 큰 사이트의 경우 DB에 저장하여 미 번역까지 자동으로 기록되도록 구현을 했었다. 번역 규칙이 너무 다방면이고 프로그램과 섞이다보니 sprintf 방식과 replace 방식을 많이 사용했는데 sprintf 의 경우 조금 까다로운면이 있어서 처리하는데 삽질을 하다가 결국 eval 함수를 사용하였다.
eval 함수의 경우 사용자 입력이 되는 구간이라면 사용을 권장하지는 않는다. 만약 eval 함수를 사용하지 않고 다른 방식으로 한다면 아래와 같이 explode 함수를 이용하여 %s 단위로 배열처리해도 같은 결과를 얻을 수 있다.
<?php
$lang_pack = array(
'안녕하세요 저는 웹개발자 입니다.'=>'Hello I am a web developer.',
'제가 좋아하는 숫자는 %s 이며 싫어하는 숫자는 %s입니다.'=>'My favorite number is %s and my dislike is %s.',
'세일은 %s% 이상 입니다.'=>'Sale is over %s%.',
'제가 좋아하는 숫자는 $1 이며 싫어하는 숫자는 $2입니다.'=>'My favorite number is $1 and my dislike is $2.',
);
// 치환 번역 함수
function lang_subs($txt = '', $args = array(), $replace = false){
global $lang_pack;
if( empty($lang_pack[$txt])){ return $txt; }
if( !empty($lang_pack[$txt])){
$txt = $lang_pack[$txt];
if( count($args) > 0){
if( $replace === false){
$ex_txt = explode("%s",$txt);
foreach($ex_txt as $k=>$v){$ex_txt[$k] = $v.$args[$k];}
$txt = implode("",$ex_txt);
}
else{
$txt = str_replace(array_keys($args),array_values($args),$txt);
}
}
}
return $txt;
}
echo lang_subs('안녕하세요 저는 웹개발자 입니다.').'<br>';
echo lang_subs('제가 좋아하는 숫자는 %s 이며 싫어하는 숫자는 %s입니다.',array(8,4)).'<br>';
echo lang_subs('세일은 %s% 이상 입니다.',array(50)).'<br>';
echo lang_subs('제가 좋아하는 숫자는 $1 이며 싫어하는 숫자는 $2입니다.',array('$1'=>8,'$2'=>4),true).'<br>';
/** result
Hello I am a web developer.
My favorite number is 8 and my dislike is 4.
Sale is over 50%.
My favorite number is 8 and my dislike is 4.
*/
솔직히 번역 작업은 개발자의 수고가 많이 들어가는 작업이다. 특히나 자바스크립트 파일 영역은 PHP를 사용할 수 없기때문에 난감할때가 있다. 당시 내가 처리했던 방식은 자바스크립트 전용 언어팩 함수를 만든뒤 AJAX 비동기 통신을 통해 PHP에서 번역이 필요한 부분을 날려주고, 캐시를 통해 저장한뒤 페이지 새로고침이 안된경우에는 캐시된 번역본을 먼저 찾고 없을 경우 AJAX 통신을 하도록 구성하였다.