스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
글로벌 앱 빌드: 사례별 현지화
사용 중인 언어에 관계없이 전 세계의 기기에서 앱을 실행하고 모든 사용자에게 탁월한 경험을 제공하도록 지원하는 방법을 알아보세요. 다양한 고객층을 위한 앱 제작 시 Apple API를 통해 탄탄한 기반을 얻을 수 있는 방법을 알아보고, 그동안의 경험을 바탕으로 사례, 문제 및 모범 사례를 공유합니다.
리소스
- Expanding Your App to New Markets
- Internationalization and Localization Guide
- Localization
- Localizing package resources
관련 비디오
WWDC23
WWDC22
WWDC21
WWDC20
-
다운로드
안녕하세요 WWDC에 오신 걸 환영합니다 저는 Apple Localization 팀의 Andreas이고 오늘 제가 설명하고 싶은 건 고품질의 현지화된 앱을 구축하는 방법의 몇 가지 사례입니다
국제화란 여러분의 앱을 전 세계의 기기에서 작동되게 준비시킨다는 뜻입니다 현지화가 잘 되면 모두가 멋진 체험과 유틸리티를 똑같이 즐길 수 있습니다 사람들의 언어와 상관없이 말이죠 Apple이 제공하는 API를 쓰면 여러분 앱의 많은 부분이 출시될 때부터 국제화 친화적입니다 이번 발표에서 여러분은 우리 경험에서 배울 겁니다 Apple의 앱을 다양한 청중에 매력적으로 다가가게 한 경험이죠 몇 가지 난제와 우리가 그걸 해결한 방법도요 먼저 현지화된 텍스트를 선언하고 로딩하면서 시작하죠 우리 문자열에 형식화된 날짜와 시간 등을 포함시키기는 쉽습니다 몇 가지 옵션을 강조하고 수준 높은 사례들을 함께 살펴보죠 Swift Package에도 현지화된 텍스트가 있을 수 있고 여러분은 현지화 작업 흐름에 대한 개선 사항들도 알게 될 겁니다 마지막으로 설명할 건 레이아웃과 SwiftUI에 새롭게 추가되는 겁니다 Apple에서는 우리 앱들이 전 세계 청중에게 멋진 체험을 제공하게 합니다 Weather 앱은 그것의 한 가지 예입니다 수백만 명의 사용자가 그걸 매일 열어 예보를 확인하고 그들이 세계 어디에 있든 그 앱은 이런 모습입니다 UI의 모든 게 사람들의 선호도에 맞춰져 있는 점에 주목하세요 우린 현재 날씨 상태에 대한 설명을 현지화하고 숫자들의 형식을 만듭니다 UI도 적절하게 조정됩니다 언어 방향이 좌에서 우인지 우에서 좌인지에 따라서요 우리가 맞춤화하는 것 중 하나를 자세히 살펴보죠 먼저 번역입니다 이 뷰의 내용은 영어로 '바람으로 날씨가 시원하게 느껴진다'입니다 다른 언어들에서 그건 이런 모습입니다 그걸 제대로 지원하기 위해 할 일은 String(localized)를 써서 문자열을 선언하는 겁니다 Xcode는 현지화를 위해 내보낼 때 그걸 발견하고 우린 그 결과를 번역기로 보낼 수 있습니다 제 Mac의 Mail 앱을 써서 그걸 해보겠습니다 우리가 거기 있는 동안 보여드리고 싶은 게 있습니다 제가 어떤 이메일의 맥락 메뉴를 열면 그걸 Archive란 특수 폴더에 이동시킬 수 있습니다 그건 제 사이드바에 있습니다 두 단어 모두가 영어로 Archive인 점에 주목하세요 하지만 스페인어 같은 다른 언어에는 동작과 폴더 이름에 다른 단어들이 있습니다 영어 단어들은 동일하지만 그것들이 다른 맥락에서 나타나면 다른 언어들에서는 다양한 단어를 쓸 수도 있습니다 이 경우에 여러분은 코드에서 두 개의 문자열을 써야 합니다 그걸 위해 문자열 이니셜라이저에 API를 올해 새로 추가했습니다 그건 이제 내정값을 갖는데 그걸 영어 문자열에 쓸 수 있어요 그런 다음 우린 현지화된 문자열의 키를 수정해서 번역기에 그 구분을 명확하게 해줍니다 이런 식으로 동일한 단어가 영어로 그 앱을 돌릴 때 나오고 스페인어 번역기는 다른 단어들을 제공할 수 있습니다 작년의 발표인 'Streamline your localized strings'는 문자열 관리의 기초를 알 수 있게 도와주고 현지화 과정으로 더 깊숙이 들어갑니다 여러분이 이 예에서 배웠으면 하는 건 때로 동일한 영단어나 전체 문장도 UI에서 다른 맥락으로 보인다는 겁니다 그런 경우에는 코드에 꼭 문자열을 두 가지 쓰세요 Weather에는 앱만 있는 게 아니죠 그건 시스템에 잘 통합되어 있기도 합니다 여기에서 보이는 건 사용자 활동으로 현재 위치에서의 날씨를 확인하기 위해 앱을 열라고 제안합니다 그걸 실행할 수 있는 방법을 살펴보죠 문자열은 이렇게 선언되어 로딩될 수 있습니다 String Interpolation을 써서 위치명을 삽입합니다 이 이름은 도시나 현 위치의 명칭이 될 수 있습니다 그 결과는 영어로 잘 나옵니다 '쿠퍼티노의 날씨를 보여줘'와 '내 위치의 날씨를 보여줘'가 각각 그렇습니다 그런데 다른 언어에선 문법 문제에 부딪칠 수 있습니다 예를 들어 독일어에선 전치사가 도시명에는 맞지만 현재 위치의 명칭을 삽입할 땐 틀릴 수 있습니다 우린한텐 다른 번역이 필요합니다 이때 해결책은 간단합니다 두 가지의 문자열을 쓰면 됩니다 첫 번째에 도시명을 삽입하는 건 괜찮고 현재 위치의 경우에는 다른 문자열을 씁니다 이는 번역기가 그 언어에 대한 올바른 문법을 쓸 수 있게 해줍니다 그럼 그건 영어와 독일어에서 잘 돌아갑니다 제가 이 사례를 만든 건 변수를 삽입하면 문장 전체에 영향을 미친다는 걸 보여드리기 위해서였습니다 문자열의 결합은 다른 언어들에선 뜻밖의 결과를 나타낼 수 있습니다 문법이 바뀌어야 하거나 대문자에 문제가 생길 수 있는데 사전에 코드를 작성할 때 그걸 알기는 힘듭니다 그 언어를 쓰는 사람들이 앱을 테스트하게 하는 게 작업 흐름에서 중대한 부분입니다 여러분이 프로그램에서 문자열을 구축하고 싶을 때 그걸 명심하세요
코드에서 문자열이 선언되는 방법을 우리가 잘 알게 됐으니 그 해설을 설명하겠습니다 이건 다시 이전 사례의 문자열인데 적절한 해설이 곁들여 있습니다 해설은 번역기에 아주 중요합니다 번역에 필요한 맥락을 여러분은 번역기에 반드시 제공해야 합니다 문자열을 선언할 때의 의도를 동일하게 유지하면서요 훌륭한 해설은 문자열이 어떤 인터페이스 요소에서 나타나는지 설명합니다 레이블이나 버튼처럼요 그건 또한 UI 요소의 맥락과 그게 화면에 나오는 곳을 설명하는데 그건 섹션 헤더나 맥락 메뉴나 사용자 활동이 될 수 있습니다 문자열에 변수가 포함되면 런타임에서 그 값을 반드시 설명하세요 그건 우리가 사례에서 봤듯이 문장의 문법을 일치시키는 데 아주 중요합니다 잊지 마세요, 여러분의 콘텐츠를 번역할 때 번역기는 런타임 때 앱을 못 볼 수 있습니다 하지만 이 조언들로 여러분은 문자열의 선언 및 번역과 그게 여러분의 앱에서 하는 역할에 관한 공동의 이해를 만들 수 있을 겁니다 여러분은 이걸 생각본 적이 없을 수 있지만 Weather 앱은 날씨를 제어하지 않습니다 그 대신 데이터를 서버에서 다운받습니다 그건 전 세계 어디에든 위치할 수 있고 그 콘텐츠를 어느 언어로 보내야 할지도 모를 수 있습니다 콘텐츠가 사용자 기기에서 다운되면 그건 늘 사용자가 선호하는 언어로 제시되어야 합니다 앱의 일부만 현지화되게 하는 건 아주 혼란스러울 수 있습니다 여기에서 Weather 앱은 기상 특보를 보여주는데 그건 서버에서 로딩된 겁니다 이건 아주 심각해 보이네요 그게 제 언어로 번역되지 않으면 전 나중에 고생할 수 있습니다 여러분의 사용자가 원격 콘텐츠를 늘 읽을 수 있게 하기 위해 여러분이 할 수 있는 일을 살펴보죠
여러분의 서버는 지원되는 언어의 목록을 앱에 보내줄 수 있습니다 그런 언어 ID들의 배열이어야 하고 기기에는 사용자가 선호하는 언어에 관한 모든 지식이 있으니 여러분 스스로 그걸 확인하고 비교하지 않아도 됩니다 Bundle.preferredLocalizations를 호출해서 프레임워크를 활용하세요 이렇게 하면 일치될 겁니다 그건 사용자의 언어 선택에 일치하는 정도에 따라 정렬된 후보 언어들의 배열을 되돌려줍니다 그리고 첫 번째가 대체로 제일 잘 맞으니 그걸 쓰면 됩니다 그리고 그 언어는 서버에 대한 이후 모든 요청에 사용돼야 합니다 그건 그걸 써서 여러분의 사용자가 이해할 수 있는 언어로 된 콘텐츠로 반응을 발생시킵니다 이 기술로 여러분은 서버에서 오는 문자열들이 UI 업데이트와 사용자에게 경보를 보여주는 것에 준비가 되어 있다는 점을 확신해도 됩니다 따라서 원격 콘텐츠를 보여줄 때 여러분 사용자들의 폭풍 같은 짜증을 방지하려면 이용 가능한 언어들을 다운받고 그걸 사용자의 선호도에 일치시키고 사용자 대면 콘텐츠를 로딩하는 모든 요청에 그 결과를 쓰세요 어쨌든 이제 더 나은 날씨로 돌아옵시다 흐리든 맑든 Weather 앱의 데이터는 풍부하고 그것의 여러 측면에 숫자와 셈이 포함됩니다 그것 중 하나에 집중해보죠 Precipitation 아래에 이렇게 써 있죠, '지난 6시간 동안 0mm' 여러분이 비슷한 걸 구축하고 싶다고 가정해보죠 다만 여기에선 'one hour'를 글자로 씁니다 이런 식으로 여러분은 코드에서 문자열을 선언할 수 있죠 영어에서는 시간의 단위가 1보다 크면 여러분은 복수형을 써야 합니다 1 hour, 2 hours처럼요 다른 변종이 사용되어야 할 때 규칙은 우크라이나어에선 훨씬 더 복잡합니다 여러분의 코드에 그 논리를 실행하는 건 좋지 않아요 그래서 여러분은 Apple의 프레임워크를 활용합니다 여러분은 코드 내에 문자열을 선언하고 stringsdict 파일만 제공하면 되죠 그건 복수형 규칙을 암호화합니다 또 다른 옵션은 Automatic Grammar Agreement를 활용하는 겁니다 이 두 기술에 관한 자세한 내용은 작년의 발표인 'Streamline your localized strings'에서 알아볼 수 있습니다 그건 쉽지만 모든 문자열에 복수형 규칙을 무조건 적용하면 안 됩니다 예를 들어 여러분의 문장에 세는 게 전혀 없고 거기에 숫자가 포함되지 않으면 복수형 규칙을 쓰면 안 됩니다 이 문장, 'Remove this city from your favorites'엔 그게 필요 없어요 숫자가 없기 때문입니다 그건 여러 도시에도 해당됩니다 그런데 문자열에 숫자가 포함되면 복수에 대한 변형을 갖추는 걸 고려해야 합니다 이전 사례의 문자열에선 다음 몇 시간 동안 강우량을 셌고 우린 그걸 1보다 큰 수에 맞게 조정하기가 얼마나 쉬운지 알았죠 하지만 문장에 단위가 있다면... 가령 지속 시간이나 시간이나 백분율 같은 거죠 형식자를 쓰는 걸 고려해야 합니다 그럼 이제 형식자를 설명하죠 이 뷰에서 Weather는 현재 습도를 백분율로 나타냅니다 이걸 SwiftUI에서 하려면 코드 한 줄만으로 가능합니다 여러분의 값을 Text()로 감싸주고 숫자가 어떤 형식이면 좋을지 명시해주기만 하면 됩니다 그리고 그에 상응하는 Swift 코드도 간단합니다 여러분의 값에 .formatted를 호출하기만 하면 됩니다
정말 그것만 하면 되고 나머지는 전부 형식자가 알아서 해줍니다 그건 퍼센트 기호를 숫자 앞이나 뒤에 놔주고 빈 칸을 넣어줄 뿐만 아니라 사용자가 선호하는 숫자 체계도 수용합니다 그건 아랍어나 힌디어 사용자들이 기대하는 거죠 그런데 그건 여러분이 구성할 데이터 종류의 시작에 불과합니다 거의 모든 것에 대한 형식자가 있는데 'Formatters: Make data human- friendly'를 시청하시길 권합니다
우리가 본 것처럼 날씨는 늘 맑은 게 아니고 비오는 날도 있습니다 물론 그 하이라이트가 Weather 앱에서 빠지면 안 되죠 Rainfall 아래에 이렇게 나옵니다 '앞으로 24시간 동안 50mm 예상' 지금 제가 있는 곳은 그 정도가 안 돼서 정말 다행이에요 그 경우에 영어로는 간단합니다 우린 '앞으로 24시간 동안 50mm 예상'이라고 말하죠 그런데 스페인어에선 문제가 훨씬 복잡합니다 강수량이 단수일 때 혹은 복수일 때 번역을 바꿔줘야 합니다 이건 형식자와 복수형 규칙을 결합해서 풀 수 있습니다 문자열 '2mm'를 형식자가 생성하고 그건 문장에 삽입되는데 스페인어의 복수형으로 바뀌어야죠 좋아요, 코드에서 이걸 하는 방법을 살펴보죠 먼저 함수를 선언합니다 강수량이 mm 단위로 얼마나 될지에 관한 파라미터를 갖는 함수를요 그건 서버에서 다운받았을 겁니다 먼저 우린 시스템에 UnitLength를 요청합니다 그건 사용자의 설정을 암호화하고 강우를 보여주는 이용 사례에 맞는 걸 고릅니다 사용자가 미터법을 사용하도록 시스템을 설정하지 않았으면 측정 타입은 선호하는 단위로 쉽게 전환될 수 있습니다
다음으로 API 구성은 우리가 그 값에 대한 형식화된 문자열을 코드 한 줄로 생성할 수 있게 해줍니다 선호 단위에는 강우를 표시하기 위해 우리가 원하는 정보가 이미 있습니다 따라서 구성 작업을 할 때 사용량을 'asProvided'로 정합니다 비가 1mm나 1인치를 초과해서 내린다면 우린 복수형의 경우를 쓰는 게 좋습니다 우린 그 값을 정수로 전환해서 그걸 확인할 수 있게 합니다 다음으로 우린 현지화된 문자열을 주어진 키로 로딩하고 내정값도 제공합니다 거기에서 String Interpolation을 써서 integerValue와 형식화된 값과 숫자 24를 포함시킵니다 그 숫자는 이 코드에 정의됐습니다 늘 24시간이니까요 String Interpolation을 쓰면 올바른 숫자 체계가 자동으로 쓰이게 합니다 키는 stringsdict 파일에 선언됩니다 그걸 살펴보겠습니다 stringsdict는 우리가 코드에서 방금 썼던 키로 시작합니다 영어에서 우린 문자열을 복수로 바꿀 필요가 없어서 그걸 위해 'Other' 범주를 씁니다 첫 파라미터는 런타임에 어떤 범주가 선택되는지 정의합니다 기억하시죠? 그건 정수 값이었습니다 둘째, 셋째 파라미터 숫자는 형식화된 문자열에 있습니다 이는 런타임에 그 문장이 어떤 모습일지를 정의합니다 스페인어 stringsdict도 구조가 같습니다만 예외는 단수와 복수 모두에 대해 번역을 제공한다는 겁니다
이제 코드에서 데이터를 구성했고 그걸 문장에 넣었습니다 stringsdict 파일에는 복수형 규칙이 포함되어 있어서 스페인어 번역에서 문법이 올바르게 쓰이게 합니다 모든 언어에서 잘 작동되는 완전히 현지화된 UI를 제공하기 힘들 때가 있습니다 반복하지만 여러분은 문자열 결합이 영어에선 되지만 다른 언어에선 뜻밖의 결과가 나올 수 있다는 걸 배웠습니다 이는 포괄적인 코드 작성을 요구할 수 있지만 이제 여러분은 모든 사용자에게 그걸 맞게 만드는 법을 아시겠죠 때론 문자열들이 의존 상태에 있거나 여러분의 앱이 쓰는 모듈 내에 있습니다 아니면 여러분은 자신의 코드를 Swift Packages를 써서 다른 개발자에게 배포할 수도 있죠 현지화의 새로운 점을 살펴보겠습니다 Swift Package를 정의하기 위해 여러분은 Swift 자체를 써서 구조를 선언하고 설정을 구축합니다 사용자 대면 콘텐츠가 있으면 defaultLocalization 파라미터를 써서 그 콘텐츠가 영어를 주 언어로 쓴다는 걸 선언하면 됩니다 그건 앱 프로젝트의 개발 언어를 명시하는 것과 비슷하죠 Xcode는 이제 그 파라미터를 읽고 여러분이 현지화된 체험을 제공하는 데 관심이 있다는 걸 인식합니다 그래서 그건 Product 메뉴에 Export Localizations 옵션을 넣죠 이 기능을 메인 앱에 대해 쓰는 데 익숙하실 텐데 그게 이젠 Swift Packages에서도 돌아갑니다 Export를 클릭하면 Xcode가 코드를 읽고 모든 문자열을 추출합니다 그것들은 번역기로 전송되는 .xcloc 파일에 들어갑니다 그리고 현지화된 콘텐츠를 패키지로 다시 불러오려면 Import Localizations를 쓰세요 그럼 Xcode는 패키지의 올바른 파일 경로에 현지화된 파일을 놓죠 Swift Package 현지화 작업 흐름은 이제 앱 현지화와 동일합니다
하지만 잊지 마세요 Swift Package에서의 문자열 로딩은 여러분이 bundle 인수를 명시할 것을 요구합니다 그걸 자세히 알 수 있는 발표는 'Swift package:' 'resources and localization'입니다 여러분이 Swift Package로 배포되는 라이브러리의 저자라면 프로젝트가 업데이트되게 하고 현지화를 작업 흐름의 정기적인 일부가 되게 하는 쉬운 방법이 있습니다 여러분은 프로젝트에 엄청난 노력과 관심을 쏟아붓고 그걸 현지화하는 건 모든 고객의 시간을 크게 절약해줍니다 그건 그 일을 눈에 띄게 해줄 수 있죠 여러분이 한층 더 노력해서 소프트웨어로 최고의 체험을 제공하려 한다는 걸 알리세요 그러니 사람들한테 말해주세요 출시부터 여러분이 지원하는 언어를 공개하세요 앱 개발자로서 여러분은 의존성을 각별하게 고려합니다 코드 품질 측면에서만이 아니고요 여러분이 사용하는 구성 요소는 앱의 나머지와 동일한 언어와 고품질 번역을 지원해야 합니다 제3자 코드가 필수 언어에 현지화되어 있지 않은 경우에도 여러분은 그 패키지의 로컬 사본을 만들어서 거기에서 현지화를 업데이트할 수 있습니다 여러분의 앱이 지원하는 언어로 앱의 모든 부분을 꼭 테스트하세요 그렇게 하면 사용자의 언어에 맞춰지지 않는 UI 요소들이 없게 할 수 있습니다 대부분의 경우 번역된 문자열은 상응하는 영어보다 길거나 짧고 그건 여러분 앱의 레이아웃에 늘 영향을 미칩니다 그게 Weather 앱에 어떤 의미인지 살펴봅시다 이건 영어로 되어 있는 앱이고 오른쪽에는 아랍어로 되어 있는 게 보입니다 명백한 점은 번역이 그 언어에 맞춰져 있을 뿐만 아니라 레이아웃 또한 적절한 방향성을 따르고 있다는 겁니다 모든 언어에 적용되는 레이아웃을 만드는 방법과 어떤 종류의 기호가 현지화된 대안을 제공하는지와 우에서 좌로 쓰는 언어에서 고려해야 할 다른 점을 자세히 알고 싶다면 ‘오른쪽에서 왼쪽으로’ 발표를 꼭 시청해보세요
여기 우측에 있는 앱은 힌디어로 되어 있습니다 확대해보죠 저 언어의 글씨는 일반적으로 높은 경향이 있습니다 자세히 보시면 레이블의 높이가 조절되어 그것에 공간을 제공하는 게 보입니다 시스템이 이걸 자동으로 해줍니다 여러분이 할 일은 UI 요소들에 고정된 높이를 주지 않는 겁니다 그게 영어 문자열에 맞을 만큼의 높이라는 이유로 모든 게 44포인트 이내에 맞을 거라고 가정하지 마세요 상황에 따라 텍스트의 높이가 더 높아질 걸 늘 예상하세요
메인 뷰로 돌아와서 화면을 올려봅니다 Weather에는 10일 예보 뷰가 있는데 이는 다음 주를 확인하는 데 아주 좋습니다
이 화면에서 눈에 띄는 점은 가장 긴 레이블에 따라 그게 요소들의 위치를 동적으로 조절하는 방식입니다 영어로 Today는 약자로 된 모든 평일 이름보다 깁니다 그런데 스페인어로는 모두가 폭이 세 글자이고 그리스어로 Today의 번역은 그 길이가 거의 두 배입니다 하지만 모든 언어에서 날씨 아이콘은 수직으로 정렬되죠 그것들에는 이웃 요소들에 대한 고정 간격이 없고 가장 긴 평일 레이블에 따라 이어진다는 뜻입니다 국제화에 효과가 좋은 레이아웃 만들기에 관해서라면 레이블이 유연해야 한다는 걸 늘 염두에 둬야 합니다 그걸 수직으로 유연하게 하는 게 얼마나 중요한지 방금 보셨는데 번역이 길어지면 레이블들이 수평으로 느는 것도 예상해야 해요 특정 레이아웃에선 그 공간 제공이 힘들 수 있습니다 가령 이 사례에서처럼요 하지만 올해 SwiftUI에선 Grid에 대한 지원이 추가됩니다 그건 이런 종류의 레이아웃을 쉽게 구축하게 해주는 새로운 뷰죠 Grid의 사용법을 자세히 살펴봅시다 행간 정렬이 있는 Grid를 선언하는 방법으로 시작합니다 그건 좌에서 우로 가는 언어는 화면 좌측에서 UI 요소들이 시작하고 우에서 좌로 가는 언어에선 화면 좌측에서 시작한다는 뜻이죠 그리고 각각의 수평 그룹에 대해 GridRow를 추가합니다 그리고 마지막으로 여러분은 열의 콘텐츠를 선언합니다 이렇게 고급스러운 레이아웃을 만드는 데 필요한 건 그게 다예요 레이블에 공간이 더 필요하면 Capsule의 크기가 줄어들 수 있죠 그게 가장 유연한 요소라서요 힘든 작업은 SwiftUI가 다 합니다 가령 뷰의 측정 크기 조절, 위치 조절을요 완전 자동으로요 또 하나의 과제는 제한된 공간에서 긴 번역이 있는 뷰를 작동되게 하는 거죠 Apple Watch에서처럼요 여기에선 Tip Function의 독일어 번역이 한 줄에 들어가기에 너무 깁니다 수정을 위해 텍스트 옆 아이콘을 제거해서 공간을 만들진 않아요 해결책은 필요하면 텍스트 줄을 둘 이상 쓰는 겁니다 그게 내정된 동작입니다 그걸 바꿔서 공간이 부족할 때 인터페이스 요소를 숨기는 건 권하지 않습니다 대개는 레이아웃을 조절할 방법이 있어서 그게 언어의 필요 사항들을 수용할 수 있습니다 Mail 앱은 그걸 창의적으로 합니다
시트 발표에는 이 이메일에 대해 동작을 취할 버튼 네 개가 있어요 버튼 제목 하나의 번역이 너무 길 때는 텍스트를 고정시키거나 새 줄 위에 그걸 감싸지 않습니다 그러면 뷰가 불균형해 보이겠죠 그 대신에 전체 레이아웃이 수평 스택에서 2열 수직 스택으로 변환됩니다
올해 SwiftUI에는 이 역동적인 레이아웃을 더 쉽게 만들게 해주는 멋진 도구가 추가됩니다 ViewThatFits입니다 본질적으로 그건 대안 레이아웃을 제공하게 해주죠 공간에 제약이 있어서 뷰가 들어가지 못할 때요
여러분은 뷰들을 서로 독립적인 것으로 선언하고 그걸 ViewThatFits에 놓으면 됩니다 뷰가 클리핑 없이 안 맞으면 SwiftUI는 자동으로 감지하고 제공된 다음 것으로 이행합니다 명심할 건 레이아웃만 변경해야 한다는 겁니다 번역이 길다고 뷰를 숨기는 건 좋지 않은 관행입니다 그건 사용자들이 UI에서 방향 찾기를 더 힘들게 합니다 유연한 레이아웃을 갖춰서 먼저 모든 인터페이스 요소들에 공간을 만들어주려 하세요
이건 현지화에만 도움이 되는 게 아닙니다 이 레이아웃은 사용자가 텍스트를 더 작거나 더 크게 하는 걸 선호하고, 다양한 기기를 쓸 때도 효과가 아주 좋습니다 올해 SwiftUI의 새롭고 멋진 기능들을 자세히 알고 싶으면 'SwiftUI로 맞춤형 레이아웃 작성’의 시청을 권합니다 다양한 접근성 선호도와 현지화된 텍스트를 갖추는 일은 레이아웃에 난제가 될 수 있습니다 인터페이스 요소들은 더 높고 넓을 수 있습니다 레이아웃을 조정해서 그 공간을 제공하는 건 난제일 수 있지만 SwiftUI로 그게 올해에는 아주 쉬워집니다
여러분이 지금 배워 가면 하는 건 코드에서 문자열을 구축하는 게 다른 언어들을 지원할 때 난제가 될 수 있다는 겁니다 여러분의 해외 사용자들과 검사자들이 주는 피드백을 경청해서 그게 모두에게 반드시 잘 작동되게 하세요 Swift에서 값을 구성하는 건 쉽고 대부분 코드 한 줄이면 됩니다 그렇게 하면 여러분의 형식화된 값들은 사용자의 선호도를 자동으로 존중합니다
여러분이 Swift Package를 제공할 때 새로운 Xcode 현지화 작업 흐름을 활용해서 완전히 현지화된 체험을 고객들에게 제공하세요 이제 SwiftUI를 사용하든 안 하든 여러분의 레이아웃은 번역된 텍스트와 접근성 설정에 공간을 제공할 수 있을 겁니다 레이아웃 도구를 써서 인터페이스 요소들을 숨기지 말고 레이아웃을 유연하게 하세요 결국 여러분의 사용자들은 그걸 고맙게 여길 겁니다 그들은 여러분의 앱이 자신의 삶에 잘 맞길 기대하고 거기에는 자신의 언어를 존중하는 것도 포함되니까요 이제 저는 맑은 한 주를 기대해하겠습니다 남은 WWDC도 즐기세요 시청해주셔서 감사합니다
-
-
1:59 - Declare strings using String(localized)
let windPerceptionLabelText = String( localized: "Wind is making it feel cooler", comment: "Explains the wind is lowering the apparent temperature" )
-
2:46 - Translation example 1
let filter = String(localized: "Archive.label", defaultValue: "Archive", comment: "Name of the Archive folder in the sidebar") let filter = String(localized: "Archive.menuItem", defaultValue: "Archive", comment: "Menu item title for moving the email into the Archive folder")
-
3:40 - Translation example 2
String(localized: "Show weather in \(locationName)", comment: "Title for a user activity to show weather at a specific city") String(localized: "Show weather in My Location", comment: "Title for a user activity to show weather at the user's current location")
-
4:58 - Comment example
String(localized: "Show weather in \(locationName)", comment: "Title for a user activity to show weather at a specific city")
-
6:40 - Localized remote content example
let allServerLanguages = ["bg", "de", "en", "es", "kk", "uk"] let language = Bundle.preferredLocalizations(from: allServerLanguages).first
-
7:56 - Numbers in a string example 1
String(localized: "\(amountOfRain) in last \(numberOfHours) hour", comment: "Label showing how much rain has fallen in the last number of hours") String(localized: "\(amountOfRain) in last ^[\(numberOfHours) hour](inflect: true)", comment: "Label showing how much rain has fallen in the last number of hours")
-
8:40 - Numbers in a string example 2
if selectedCount == 1 { return String(localized: "Remove this city from your favorites") } else { return String(localized: "Remove these cities from your favorites") }
-
9:00 - Numbers in a string example 3
String(localized: "\(amountOfRain) in last ^[\(numberOfHours) hour](inflect: true).", comment: "Label showing how much rain has fallen in the last number of hours")
-
9:29 - Formatter example
let humidity = 54 // In a SwiftUI view Text(humidity, format: .percent) // In Swift code humidity.formatted(.percent)
-
10:03 - Formatter example 2
date.formatted( .dateTime.year() .month() ) // Jun 2022 whatToExpect.formatted() // New features, exciting API, and advanced tips amountOfRain.formatted( .measurement( width: .narrow, usage: .rainfall)) // 12mm (date...<later).formatted( .components( style: .wide ) ) // 24 minutes, 18 Seconds date.formatted( .relative( presentation: .numeric ) ) // 2 minutes ago let components = PersonNameComponents() … nameComponentsFormatter .string(from: components) // Andreas Neusüß or 田中陽子 excitementLevel.formatted( .number .precision( .fractionLength(2) ) ) // 1,001.42 price.formatted( .currency( code: "EUR" ) ) // $20.99 distance.formatted( .measurement( width: .wide, usage: .road) ) // 500 feet bytesCopied.formatted( .byteCount( style: .file )) // 42.23 MB
-
11:10 - Combine a formatter with text
func expectedPrecipitationIn24Hours(for valueInMillimeters: Measurement<UnitLength>) -> String { // Use user's preferred measures let preferredUnit = UnitLength(forLocale: .current, usage: .rainfall) let valueInPreferredSystem = valueInMillimeters.converted(to: preferredUnit) // Format the amount of rainfall let formattedValue = valueInPreferredSystem .formatted(.measurement(width: .narrow, usage: .asProvided)) let integerValue = Int(valueInPreferredSystem.value.rounded()) // Load and use formatting string return String(localized: "EXPECTED_RAINFALL", defaultValue: "\(integerValue) \(formattedValue) expected in next \(24)h.", comment: "Label - How much precipitation (2nd formatted value, in mm or Inches) is expected in the next 24 hours (3rd, always 24).") }
-
12:22 - Stringsdict examples in English and Spanish
Localizable.stringsdict English: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>EXPECTED_RAINFALL</key> <dict> <key>NSStringLocalizedFormatKey</key> <string>%#@next_expected_precipitation_amount_24h@</string> <key>next_expected_precipitation_amount_24h</key> <dict> <key>NSStringFormatSpecTypeKey</key> <string>NSStringPluralRuleType</string> <key>NSStringFormatValueTypeKey</key> <string>d</string> <key>other</key> <string>%2$@ expected in next %3$dh.</string> </dict> </dict> </dict> </plist> Localizable.stringsdict Spanish: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>EXPECTED_RAINFALL</key> <dict> <key>NSStringLocalizedFormatKey</key> <string>%#@next_expected_precipitation_amount_24h@</string> <key>next_expected_precipitation_amount_24h</key> <dict> <key>NSStringFormatSpecTypeKey</key> <string>NSStringPluralRuleType</string> <key>NSStringFormatValueTypeKey</key> <string>d</string> <key>one</key> <string>Se prevé %2$@ en las próximas %3$d h.</string> <key>other</key> <string>Se prevén %2$@ en las próximas %3$d h.</string> </dict> </dict> </dict> </plist>
-
13:40 - Swift Package localization example
let package = Package( name: "FoodTruckKit", defaultLocalization: "en", products: [ .library( name: "FoodTruckKit", targets: ["FoodTruckKit"]), ], … )
-
14:41 - Loading a string in a Swift Package
let title = String(localized: "Wind", bundle: .module, comment: "Title for section that shows data about wind.")
-
18:19 - Grid example
// Requires data types "Row" and "row" to be defined struct WeatherTestView: View { var rows: [Row] var body: some View { Grid(alignment: .leading) { ForEach(rows) { row in GridRow { Text(row.dayOfWeek) Image(systemName: row.weatherCondition) .symbolRenderingMode(.multicolor) Text(row.minimumTemperature) .gridColumnAlignment(.trailing) Capsule().fill(Color.orange).frame(height: 4) Text(row.maximumTemperature) } .foregroundColor(.white) } } } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.