본문으로 바로가기

MFC 다국어 지원 방법(국가별 언어설정)

category 개발언어/c++ 2016. 7. 15. 10:20

MFC 다국어 지원 방법(국가별 언어설정)

 

MFC 에서 프로젝트를 완료 했는데 뜬금 없이 다국어 지원을 요청한다.

영어,중국어 등등……. 참 난감합니다.

Code를 전부 다시 작성해야 할 판 입니다. 또 수정하다 발생 시킬 수 있는 오류가 두렵습니다.

 

MFC에 기본적인 메뉴에 대해서는 OS에 설정된 언어 별로 대응할 방법이 있습니다 만. 임의로 추가한 사용자 정의 메뉴에 대해서는 대응 방법이 모호 합니다..

 

해결 방법을 알아 보도록 하겠습니다.

목차

1    DLL을 이용하는 방법    2

1.1    DLL 프로젝트를 생성합니다.    2

1.2    프로젝트가 생성 되었으면 File Open 메뉴를 눌러 리소스 가져오기    2

1.3    DLL 프로젝트의 리소스에서 내용을 수정합니다.    4

1.4    DLL을 컴파일 합니다.    4

1.5    언어별 리소스 불러오기    4

1.6    단점검토.    5

2    MAKEINTRESOURCE를 사용하여 직접 코딩    6

2.1    CMap을 활용하여 리소스 생성하기    6

2.2    SetAt 만들기    7

2.3    파일에서 String Table 불러오기    7

2.4    사용방법.    10

 

 

 

  1. DLL을 이용하는 방법

    1. DLL 프로젝트를 생성합니다.

 

  1. 프로젝트가 생성 되었으면 File Open 메뉴를 눌러 리소스 가져오기

수정해야 할 원본 프로젝트의 리소스 파일 "xxx.rc" 을 엽니다.

그러면 아래 그림과 같이 모든 리소스가 연결되는 것을 볼 수 있습니다

외부파일에서 불러온 리소스 항목에서 필요한 리소스를 끌어서 왼쪽에 새로 만들 Dll 프로젝트로 Import 시킵니다.

모두 Import 시켰으면 리소스 파일을 닫습니다.

이때 저장 할 것이냐 라고 물으면 "저장안함" 을 선택 해야 합니다.

만일 저장 하게 되면 Open된 원본 파일에서 리소스들이 옮겨서 삭제된 상태로 저장되어 원본 파일의 리소스가 사라 지기 때문입니다.

 

  1. DLL 프로젝트의 리소스에서 내용을 수정합니다.

    StringTable은 물론 ,Dialog에 기록 된 내용,또는 Image에 그림으로 그려진 내용 등. 바꿀 내용을 모두 수정합니다.

  2. DLL을 컴파일 합니다.

    각각의 언어별로 배포할 DLL을 컴파일 합니다.

    예를 들어 영어는 lang_eng.dll, 한글은 lang_kor.dll 과 같은 형태로

    컴파일 해서 작성 해 둡니다.

  3. 언어별 리소스 불러오기

프로젝트의 Application 파일 "xxxApp.cpp" 파일에서 동적으로 DLL을 불러 오도록

코딩 합니다.

BOOL App::InitInstance

{

     …….

     //AfxDbInitModule();

     m_hInstLocalDLL=NULL;

     UINT    uCP = GetOEMCP();

     switch( uCP )

     {

     case 949://한국어

     m_hInstLocalDLL = LoadLibrary ("lang_kor.dll");

         //m_hInstLocalDLL=NULL 인경우는 적용 되지 않는다

        break;

     case 437:// MS-DOS United States :

     default:

        m_hInstLocalDLL = LoadLibrary ("lang_eng.dll");

        break;

     }

    

#ifdef _WIN32

    if (m_hInstLocalDLL != NULL)

        AfxSetResourceHandle (m_hInstLocalDLL); //불러온 인스턴스 적용 되는 곳

#else

    if (m_hInstLocalDLL >= HINSTANCE_ERROR)

        AfxSetResourceHandle (m_hInstLocalDLL);

#endif

 

이렇게 하면 DLL에서 리소스를 불러와서 자동으로 메핑하게 됩니다.

이로서 언어별로 적용이 완료 됩니다.

 

  1. 단점검토.

지금까지 설명한 방법은 단점이 있습니다.

    개발이 완전히 종료된 시점에서 해당 방법은 아주 유용하지만 대부분은

개발 완료시점에서 수정 및 추가 사항이 발생하여 원본 리소스를 건드리게 되는

상황이 발생 합니다.

그때마다 앞에서 행했던 작업을 반복해야 하는 번거로움이 있습니다.

기본적으로 프로그램을 개발 할 때

LoadString을 사용하여 String Table을 불러 오도록 하는 방법을 선택 하는 것이

바람직한 코딩 방법입니다.

int LoadString(

HINSTANCE hInstance, // handle to module containing string resource

UINT uID, // resource identifier

LPTSTR lpBuffer, // pointer to buffer for resource

int nBufferMax // size of buffer

);

  1. MAKEINTRESOURCE를 사용하여 직접 코딩

프로젝트를 수행하다 보면, 개발이 완료되기 전에도 빈번한 수정이 발생합니다,

개발자가 사용자의 요구사항을 100% 이해 하지 못하고 프로젝트에 투입,

심지어 개발을 요구한 User조차도 자신이 원하는 사항을 완전하게 전달 하지

못하는 경우가 있어,

프로젝트가 완전하게 완료되었다고 판단하기가 어렵습니다.

배포된 이후에도 빈번한 수정이 발생 하게 됩니다.

이것을 피해가는 방법을 검토 해 보겠습니다.

사용자가 메뉴의 이름을 바꿀 수 있도록 우회 하는 방법을 알아 보겠습니다.

  1. CMap을 활용하여 리소스 생성하기

#include "Afxtempl.h"

class AFX_EXT_CLASS CLanguagePack:public CMap<WORD,WORD,CString,CString>

{

}

와 같이 CMAP클래스를 상속받아서 StringTable을 불러올 수 있는 클래스를

만들자

 

  1. SetAt 만들기

사용할 리소스 ID를 SetAt 으로 추가 하면 자동으로 Resource에서 문자를

찾아서 MAP에 등록 하는 클래스를 구현 합니다

void CLanguagePack::SetAt(WORD Id,LPCTSTR str)

{    

    CString mstr=str;

    CString mtemp="";

    if(mstr.IsEmpty())//내용이 비어 있으면 사전에 등록 된 내용을 적용

    {if(Lookup(Id,mtemp))mstr=mtemp;}

    if(mstr.IsEmpty()) //사전에 등록된 내용이 없으면 리소스에서 찾는다

        mstr=(CString)MAKEINTRESOURCE(Id);//리소스에 등록된 글 가져오기

    CMap<WORD,WORD,CString,CString>::SetAt(Id,mstr);

}

 

사용 예:

CLanguagePack* p_lang=new CLanguagePack();

p_lang->SetAt(IDS_ANG_DIVISION);

p_lang->SetAt(IDS_ANG_IRREGULAR);

p_lang->SetAt(IDS_ANG_REGULAR);

와 같이 리소스의 이름을 등록하면 리소스에서 문자를 찾아서 자동으로 등록합니다.

  1. 파일에서 String Table 불러오기

Language.lng에 Stringtable을 만들어 두고 불러오도록 코딩 합니다.

"Language.lng"에 바로 아래의 내용과 같이 stringtable을 미리 만들어 두고

[CMainFrame]

3=Monitoring

2=도움말(&H)

1=보기(&V)

111=최근 파일

109=인쇄 설정(&R)...

108=인쇄 미리 보기(&V)

107=인쇄(&P)...    Ctrl+P

105=모델선택

104=검사작업(F4)

103=교정작업(F3)

102=환경설정(F2)

100=관리자 메뉴(&S)

113=종료(&X)

파일에서 불러오도록 코딩을 합니다.

코딩 내용은 파일에서 지정된 resource ID 에 해당하는 String이 있으면

String을 불러오고 String이 없으면 default로 지정된 String을 파일에 자동 기록

하도록 만들어 져 있습니다.

void CLanguagePack::GetResourceFromFile(CString mfile)

{

    WORD KEY;

    CString sbuff,fmt;

char lp_str[1024]; // address of return buffer

int pos=0;

    CWinApp* p_app=AfxGetApp();

    mfile= p_app->m_pszHelpFilePath ;

if((pos=mfile.ReverseFind('\\'))>0)

        mfile=mfile.Left(pos);

mfile+="\\Language.lng";        

POSITION POS=GetStartPosition();

while(POS)

{

     GetNextAssoc(POS,KEY,sbuff);

     fmt.Format("%d",KEY);

     if(KEY==0)continue;

     if(GetPrivateProfileString(m_section,fmt,"",lp_str,1024,mfile))

     {

            sbuff=lp_str;

            sbuff.Replace("\\n","\n");//

            sbuff.Replace("\\r","\r");//

            CMap<WORD,WORD,CString,CString>::SetAt(KEY,sbuff);

     }    

     else //파일에서 읽지 못했을 때 초기값을 만들어 둔다

     {

            if(sbuff.IsEmpty())//등록이 되어 있지 않았을때 리소스에서 가져와 등록

            {

            sbuff=(CString)MAKEINTRESOURCE(KEY);

            CMap<WORD,WORD,CString,CString>::SetAt(KEY,sbuff);

            }

     sbuff.Replace("\n","\\n");//줄바꿈 구분자를 따로 넣음

     sbuff.Replace("\r","\\r");//

     fmt.Format("%d",KEY);         

     WritePrivateProfileString(m_section,fmt,sbuff,mfile);    

     }        

};

}

 

  1. 사용방법.

OnInitialUpdate(),또는 OnInitDialog () 등 시작되는 위치에 다음과 같이 코딩한다.

    

CLanguagePack m_lang("CMainFrame ");

m_lang.SetAt(IDS_CALSTEP_FORMAT);

m_lang.SetAt(IDS_CALSTEP1);

m_lang.SetAt(IDS_CALSTEP2);

m_lang.GetResourceFromFile("파일이름"); //파일에서 내용 불러 오기

만일 Dialog창에 "ID_CALSTEP_FORMAT"이라는 버튼이 있다면

SetDlgItemText(ID_CALSTEP_FORMAT, m_lang[IDS_CALSTEP_FORMAT]);

과 같이 사용하면 됩니다.

SetDlgItemText(ID_CALSTEP_FORMAT, m_lang[IDS_CALSTEP_FORMAT]);

로 간단히 불러 오는 것이 너무 간단하게 보입니다.

저게 어떻게 가능 한 것인지는 CMap이라는 클래스가 가지고 있습니다.