본 포스팅은 17ヶ国の多言語対応Tips 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
실제 슬라이드의 일본어
부분을 번역했다는 점 양해바랍니다.
17개국 다국어 대응 Tips
2016/02/19 (금)
Konifar (@konifar)
DroidKaigi
android-material-design-icon-generator-plugin
Taptrip
전 세계
친구를 만들 수 있다자동번역
으로 간단하게 커뮤니케이션250개국/500만 명
이 이용https://play.google.com/store/apps/details?id=com.taptrip
Taptrip Google Analytics
Taptrip supports 17
languages
DroidKaigi 2016
https://github.com/konifar/droidkaigi2016
아랍어 대응
언어 변경 방법
Q. 다국어 대응은 힘든가?
A. 「대응」의 정도에 따라서
Q. 어디까지 대응할 것인가?
A. 어플 종류와 확보한 공수에 따라서
이쪽은 개발 내용의 그림이 잡히지 않는다면 판단하기 어렵다
오늘의 목표
다국어 대응의 구현 이미지, 개발 비용이 대략 알게 된다
오늘 이야기할 내용
오늘 이야기하지 않는 내용
1. strings.xml 관리방법
values-xx/strings.xml
<string name="all_sessions">All Sessions</string>
<string name="all_sessions">جميع الجلسات</string>
<string name="all_sessions">すべてのセッション</string>
인도네시아어 등 특정 언어에 주의
예전 Java가 실수한 흔적으로 특수한 일반적인 언어 코드와 다른 부분이 있다
인도네시아어 등 특정 언어에 주의
private String convertOldISOCodes(String language) {
language = language.toLowerCase().intern();
switch (language) {
case "he": // 히브리어
return "iw";
case "yi": // 이디시어
return "ji";
case "id": // 인도네시아어
return "in";
default:
return language;
}
}
하드 코딩 항목 색출
Lint > Internationalization
Translation Editor
17개 언어 대응하면 힘들다
언어 전환
Locale.getDefault()
값이 바뀌면, 거기에 대응해서 표시가 변경된다언어 전환을 어플로 제어한다
public static void setLocale(Context context, String languageId) {
Configuration config = new Configuration();
Locale locale = new Locale(languageId);
Locale.setDefault(locale);
config.locale = locale;
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
2. 번역 흐름
Taptrip의 경우
3~4 항목이 이번에 할 이야기
기계번역 or 사람번역
부정확
비싸
지만 정확Google 번역의 정밀도
일본어 <=> 한국어
는 매우 높은 정밀도Taptrip에서의 앙케이트 결과
기계번역의 경우의 Tips
사람번역 서비스의 선정 포인트
단가 x 속도 x 기능의 풍부함
4~8엔
정도30분~1일
GENGO
4~5엔
3시간
정도로 맞춘다xml적용의 귀차니즘
해결 1 : 도구를 사용해서 변환
해결 2 : xml 그대로 번역 의뢰한다
AS Plugin 「Alice」
※ 서버의 콜백 실패로 아직 움직이고 있지 않다
Google 공식 번역 서비스를 이용한다
Google 공식 번역 서비스를 이용시의 궁리
<!-- The action for submitting a form. This text is on a button that can fit 30 chars -->
<string name="login_submit_button">Sign in</string>
http://developer.android.com/intl/ja/distribute/tools/localization-checklist.html
<string name="countdown">
<xliff:g id="time" example="5 days>%1$s</xliff:g>until holiday
</string>
http://developer.android.com/intl/ja/distribute/tools/localization-checklist.html
아랍어 편집시의 궁리
3. 수사(數詞)의 복수형 대응
일본어에서의 「수사(數詞)」
<= 바뀌지않는다
영어에서의 「수사(數詞)」
<=복수형으로 변화
아랍어에서의 「수사(數詞)」
?????
Language Plural Rules
http://www.unicode.org/cldr/charts/27/supplemental/language_plural_rules.html
values/strings.xml
<plurals name="number_with_brackets">
<item quantity="one">(%1$d person)</item>
<item quantity="other">(%1$d people)</item>
</plurals>
values-ja/strings.xml
<plurals name="number_with_brackets">
<item quantity="other">(%1$d人)</item>
</plurals>
values-ar/strings.xml
<plurals name="number_with_brackets">
<item quantity="zero">(%1$d أشخاص)</item>
<item quantity="one">(%1$d شخص)</item>
<item quantity="two">(%1$d شخصين)</item>
<item quantity="few">(%1$d أشخاص)</item>
<item quantity="many">(%1$d شخص)</item>
<item quantity="other">(%1$d شخص)</item>
</plurals>
getQuantityString()
getResources().getQuantityString(R.plurals.number_with_brackets, count, count);
숫자 단락 문자
← 소수점
← 소수점
← 마침표
← 여러가지 다르다
NumberFormat를 사용
NumberFormat.getNumberInstance().format(1);
// 1
// 10,000
NumberFormat를 사용
=> 1、 10,000
=> ¥1、¥10,000
=> 1、10,000
=> 1%、100%
4. 날짜 현지화
날짜 현지화
일본어 / 아랍어
2月19日 (MMMd)
public static String getMonthDate(Date date, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), FORMAT_MMDD);
return new SimpleDateFormat(pattern).format(date);
} else {
int flag = DateUtils.FORMAT_ABBREV_ALL | DateUtils.FORMAT_NO_YEAR;
return DateUtils.formatDateTime(context, date.getTime(), flag);
}
}
10:00 (kkmm)
public static String getHourMinute(Date date) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), FORMAT_KKMM);
return new SimpleDateFormat(pattern).format(date);
} else {
return String.valueOf(DateFormat.format(FORMAT_KKMM, date));
}
}
2016年2月18日 16:30
public static String getLongFormatDate(Date date, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), FORMAT_YYYYMMDDKKMM);
return new SimpleDateFormat(pattern).format(date);
} else {
java.text.DateFormat dayOfWeekFormat = java.text.DateFormat.getDateInstance(java.text.DateFormat.LONG);
java.text.DateFormat shortTimeFormat = java.text.DateFormat.getTimeInstance(java.text.DateFormat.SHORT);
dayOfWeekFormat.setTimeZone(LocaleUtil.getDisplayTimeZone(context));
shortTimeFormat.setTimeZone(LocaleUtil.getDisplayTimeZone(context));
return dayOfWeekFormat.format(date) + " " + shortTimeFormat.format(date);
}
}
시차 보정
도쿄 10:00 / 런던 1:00
DateFormat을 사용해 타임존을 고려
public static Date getDisplayDate(@NonNull Date date, Context context) {
DateFormat formatTokyo = SimpleDateFormat.getDateTimeInstance();
formatTokyo.setTimeZone(CONFERENCE_TIMEZONE);
DateFormat formatLocal = SimpleDateFormat.getDateTimeInstance();
formatLocal.setTimeZone(getDisplayTimeZone(context));
try {
return formatLocal.parse(formatTokyo.format(date));
} catch (ParseException e) {
Log.e(TAG, "date: " + date + "can not parse." + e);
return date;
}
}
5. RTL (Right To Left) 대응
This is RTL
DroidKaigi 2016
언어 변경 방법
Q. 애시당초 해야만 하는가?
아랍어권 친구에게 물어봤다
A. 하는게 좋다고 합니다
단 Drawer등의 대응은 가지각색
Omran이 추천하는 뉴스 앱은 좌측에서부터 Drawer가 나온다
AndroidManifest.xml
<application
android:name=".MainApplication"
android:allowBackup="true"
android:configChanges="locale"
android:icon="@mipmap/ic_launcher"
android:labe="@string/app_name"
android:supportRtl="true"
android:theme="@style/APpTheme">
Start & End attributes
layout_toRight만 지정하면 날짜만 오른쪽에 치우기 때문에 더 오른쪽에 표시하려다 아무것도 표시되지 않는다
<android.support.v7.widget.CardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/spacing_small"
android:layout_toRightOf="@+id/txt_stime"
↓
<android.support.v7.widget.CardView
android:id="@+id/card_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/spacing_small"
android:layout_marginStart="@dimen/spacing_small"
android:layout_toEndOf="@id/txt_stime"
android:layout_toRightOf="@+id/txt_stime"
Add RTL Support Where Possible
ViewPager
TabLayout은 RTL에 대응하지만, ViewPager는 대응하고 안하고 있다.
RtlViewPager
private int convert(int position) {
if (position >= 0 && isRtl()) {
return getAdapter() == null ? 0 : getAdapter().getCount() - position - 1;
} else {
return position;
}
}
protected boolean isRtl() {
return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == ViewCompat.LAYOUT_DIRECTION_RTL;
}
getLayoutDirectionFromLocale()으로 설정 로케일에 따른 방향이 취득된다
RTL Mark 삽입
문자의 첫 문자가 아랍어가 아닐 때, 좌측에서 이어지는 문제가 있다.
강제적으로 RTL으로 할 특수문자
를 넣는 것으로 해결한다.
RTL Mark in strings.xml
strings.xml 내부라면, ‏
을 첫 줄에 넣기만 하면 된다
<string name="about_youtube">‏YouTube قناة</string>
RTL Mark in Java
RTL Mark using DataBinding
@BindingAdapter("textRtlConsidered")
public static void setTextRtlConsidered(TextView textView, String text) {
textView.setText(LocaleUtil.getRtlConsideredText(text));
}
public static String getRtlConsideredText(String text) {
if (shouldRtl()) {
return "\u200F" + text;
} else {
return text;
}
}
<TextView
android:id="@+id/txt_speaker_name"
style="@style/TextSub"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
app:textRtlConsidered="@{session.speaker.name}" />
android:text
대신에 app:textRtlConsidered
를 사용하는 것
이미지의 반전
android:autoMirrored
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemeas.android.com/apk/res/android"
xmlns:tools="http://schemeas.android.com/tools"
android:autoMirrored="true"
android:src="@drawable/ic_search_white_24dp"
tools:ignore="UnusedAttribute" />
아라비아 숫자
Q. 어떻게 구사하면 좋을까?
다시 Omran에 물어봤다
솔직히 Omran만으로는 사용이 모르겠다
동료에게도 물어봤다
A. 유스 케이스이나 지역에 상당히 의존한다
NumberFormat, DateUtils를 사용
6. 상세한 디자인 조정
언어에따른 텍스트 길이의 차이
일본어의 1.5~2배를 가정한다
행 고정 + 생략
<TextView
android:id="@+id/txt_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:layout_marginBottom="@dimen/line_spacing"
android:maxLines="2"
android:text="5 лет с последующим 'Hatena Закладка', чтобы продолжить разработку технологии приложения" />
태그 등 생략하고 싶지 않은 케이스
비어져 나올 떄는 다음 행으로 표시한다
<org.apmem.tools.layouts.FlowLayout
android:id="@+id/tag_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/txt_place"
style="@style/Tag"
android:background="@drawable/tag_language"
android:text="@{session.place.name}" />
<io.github.droidkaigi.confsched.widget.CategoryView
android:id="@+id/txt_category"
style="@style/Tag"
언어에 따라 행간을 바꾼다
정리
strings.xml의 관리
언어마다 준비는 필수입니다. AndroidStudio의 기능을 활용합시다.
번역 흐름
가능하면 사람 번역으로 정밀도를 높입시다. 플러그인이나 스크립트를 구사해 수고를 줄입시다
복수형 대응
plurals를 제대로 사용합시다
날짜 현지화
DateUtils, SimpleDateFormat를 사용합시다.
RTL (Right To Left)
아랍권을 메인으로 한다면 제대로 합시다. start, end 대응만으로도 상당히 좋아집니다
세밀한 디자인 조정
디자이너와 협력하고, 문자의 길이나 행간을 고려합시다
어디까지 다국어에 대응할지는 어플에 따라 다르다
어디까지 할 것인가의 인식을 맞춰, 가능한 것부터 합시다
감사합니다
https://github.com/konifar/droidkaigi2016
매우 참고되는 링크
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024