EEltaCrew
블로그

앱 안에서 언어를 바꿨는데 가끔 안 바뀌는 이유: setApplicationLocales 는 떠 있는 액티비티만 다시 만든다

게시 2026-09-16실측 기준 2026-09-07읽는 시간 약 4분elta · EltaCrew

"한국어에서 English 로 바꿔도 그대로다, 껐다 켜야 바뀐다" 는 제보의 원인은 AppCompat 이 그 순간 화면에 떠 있는 액티비티만 재생성하기 때문이었습니다. 뒤에 멈춰 있던 화면은 옛 언어로 돌아옵니다. 앱 8개에 적용한 해결책과, 같이 걸린 함정 5가지를 씁니다.

증상

리모컨 앱 설정에서 언어를 English 로 바꾸면 설정 화면은 영어가 되는데, 뒤로 나가 메인 화면으로 돌아오면 가끔 한국어 그대로였습니다. 앱을 완전히 껐다 켜면 영어로 뜹니다. "재시작하면 된다"는 말의 정체가 궁금했습니다.

원인

AppCompatDelegate.setApplicationLocales() 는 그 시점에 started 상태인 액티비티만 다시 만듭니다. 설정 화면은 새 언어로 재생성되지만, 그 뒤에 stopped 로 멈춰 있던 메인 화면은 옛 Configuration 을 들고 그대로 resume 됩니다. 앱을 껐다 켜면 Application.onCreate 에서 로케일이 다시 적용되므로 정상으로 보입니다.

그래서 이 문제는 액티비티가 여러 개인 앱에서만 생깁니다. 단일 액티비티 앱은 그 하나가 재생성되면 전체가 갱신됩니다. 제 앱 중 리모컨 4종(액티비티 6~7개), 혜택 캘린더, 링크 관리, 스크린타임, 로또 앱이 해당했습니다.

해결

Application.registerActivityLifecycleCallbacksonActivityResumed 에서, 전역 선택값(AppCompatDelegate.getApplicationLocales())과 그 액티비티 자신의 Configuration 로케일을 비교합니다. 어긋나면 recreate().

두 가지가 중요합니다.

8개 앱에 같은 클래스를 넣었습니다.

같이 발견한 것: 로케일 저장 스위치가 10개 앱에 없었다

API 33 미만에서 AppCompat 이 선택한 로케일을 저장하고 복원하려면 매니페스트에 AppLocalesMetadataHolderServiceautoStoreLocales 메타데이터가 있어야 합니다. 이게 10개 앱에 없었습니다. 있던 앱에서는 재시작 후 언어가 유지되고 없던 앱에서는 시스템 언어로 돌아갔던 이유입니다.

함정 5가지 (전부 실제로 걸린 것)

  1. 하드코딩된 한글은 언어 설정과 무관하게 그대로 뜹니다. 리모컨 버튼 라벨 97개가 android:text="외부입력" 식으로 박혀 있었습니다. values-en 에 131개 번역이 전부 있었는데도 화면에는 한글이었습니다.
  2. 한글 탐색에 grep -P '[\x{AC00}-\x{D7A3}]' 을 쓰지 않습니다. Git Bash 의 비 UTF-8 로케일에서는 "unibyte and UTF-8 locales only" 로 실패하면서 0건을 돌려줍니다. 거짓 음성입니다. 파이썬으로 찾습니다.
  3. 코드에 한글이 있다고 다 보이는 게 아닙니다. 어떤 문자열은 변수에 담기기만 하고 읽는 곳이 없어 로그로만 갔습니다. 번역 전에 실제 노출 경로를 끝까지 추적합니다.
  4. getResources().getIdentifier() 로 리소스를 이름으로 찾지 않습니다. R8 이 이름을 지워 release 에서만 깨집니다.
  5. 원본 주석의 경고를 읽습니다. NO_NAME = "(이름 없음)" 위에 "문구 판정에 쓰므로 자원으로 빼지 않는다"고 적혀 있었는데 상수만 자원화했다가 컴파일이 깨졌습니다. 자원화하려면 표시와 판정이 같은 문자열이 되도록 로직까지 옮겨야 합니다.

인앱 언어는 액티비티만 바꾼다

하나 더. AppCompat 인앱 언어는 액티비티 리소스에만 적용됩니다. 알림, 홈 위젯, 백그라운드에서 만드는 날짜 포맷은 여전히 시스템 언어를 씁니다. 앱 언어가 영어인데 알림만 한국어로 오는 상태가 됩니다. 알림·위젯을 만드는 코드에서는 저장된 언어 태그로 Context 를 따로 감싸(createConfigurationContext) 리소스를 가져옵니다.

검증

이건 타이밍 문제라 컴파일 통과로는 확인되지 않습니다. 실기기에서 화면 A → 설정 → 언어 변경 → 뒤로 → A 순서를 손으로 밟아야 합니다.

정리

Android다국어AppCompat인앱 언어

함께 읽기

R8 을 켜고 빌드는 통과했는데 앱이 깨졌다: 15개 앱에서 실제로 걸린 런타임 함정 5가지Play 의 2027년 코드 최적화 요건에 맞춰 앱 15개에 R8 을 켰습니다. 빌드는 전부 통과했지만 7개 중 2개가 실행 중에 깨졌습니다. Gson 역직렬화, getIdentifier 리소스 조회, Room _Impl, 디버그 빌드의 무효화 등 크래시 로그에 안 잡히는 유형을 정리합니다.한 페이지에 한 언어: IP 국가 판정과 한/영 병기를 버리고 브라우저 언어 + hreflang 으로 바꾼 이유웹 서비스 6개의 초기 언어를 정하는 방식이 제각각이었습니다. IP 국가로 정하던 곳은 구글봇에게만 영어가 나가 검색 결과 언어를 통제할 수 없었고, 한/영을 한 페이지에 병기하던 랜딩은 구글이 기대하는 "페이지 하나에 언어 하나" 와 어긋났습니다. 전 웹을 한 규칙으로 통일한 과정을 씁니다.홈 위젯 13개를 삼성 위젯 옆에 놓고 다시 고친 기준 7가지앱 5개의 홈 위젯 13개를 실기기 홈 화면에 삼성 날씨·페이 위젯과 나란히 놓고 보니 "코드가 있다" 와 "상용 앱처럼 보인다" 는 전혀 달랐습니다. 시스템 다크 모드 추종, 빈 상태 디자인, previewLayout 과 label, dp 모서리, 비트맵 위젯의 테마 지연까지 고친 기준을 씁니다.