Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Announce scrolling state for VoiceOver #1644

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 88 additions & 1 deletion compose/ui/ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) {
}
}

// This task updates the translations of the localizable strings in this module.
// This task updates the translations of the localizable strings for the desktopMain target.
// It obtains them from Android's base repository.
tasks.register("updateTranslations", UpdateTranslationsTask.class) {
group = "localization"
Expand Down Expand Up @@ -386,6 +386,93 @@ tasks.register("updateTranslations", UpdateTranslationsTask.class) {
]
}

// This task updates the translations of the localizable strings for the uikitMain target.
// It obtains them from compose multiplatform repository.
// See also `scripts/convertCrowdinToStringsXml.sh`.
tasks.register("updateTranslationsIos", UpdateTranslationsTask.class) {
group = "localization"
gitRepo = "https://github.com/JetBrains/compose-multiplatform-core"
repoResDirectories = ["compose/ui/ui/src/uikitMain/res"]
targetDirectory = project.file("src/uikitMain/kotlin/androidx/compose/ui/platform/l10n")
targetPackageName = "androidx.compose.ui.platform.l10n"
kotlinStringsPackageName = "androidx.compose.ui.platform"
stringByResourceName = [
"first_page": "FirstPage",
"last_page": "LastPage",
"next_page": "NextPage",
"previous_page": "PreviousPage"
]

// Currently, strings are used in accessibility features, which limits the language list to the
// languages supported in accessibility on iOS: https://support.apple.com/en-us/111748.
locales = [
"ar", // Arabic
"eu", // Basque
"bn", // Bengali (India)
// "bh_IN", // Bhojpuri (India)
"bg", // Bulgarian
"zh_HK", // Cantonese (Hong Kong)
"ca", // Catalan
"hr", // Croatian
"cs", // Czech
"da", // Danish
"nl_BE", // Dutch (Belgium)
"nl", // Dutch (Netherlands)
"en_AU", // English (Australia)
"en_IN", // English (India)
"en_IE", // English (Ireland)
"en_GB", // English (Scotland)
"en_ZA", // English (South Africa)
"en_GB", // English (UK)
"en", // English (US)
"fa", // Farsi
"fi", // Finnish
"fr_BE", // French (Belgium)
"fr_CA", // French (Canada)
"fr", // French (France)
"gl", // Galician
"de", // German
"el", // Greek
"iw", // Hebrew
"hi", // Hindi
"hu", // Hungarian
"in", // Indonesian
"it", // Italian
"ja", // Japanese
"kn", // Kannada
"ko", // Korean
"ms", // Malay
"zh_CN", // Chinese (China mainland)
// "zh_CN", // Chinese (Liaoning, China mainland)
// "zh_CN", // Chinese (Shaanxi, China mainland)
// "zh_CN", // Chinese (Sichuan, China mainland)
"zh_TW", // Chinese (Taiwan)
"mr", // Marathi
"nb", // Norwegian
"pl", // Polish
"pt_BR", // Portuguese (Brazil)
"pt", // Portuguese (Portugal)
"ro", // Romanian
"ru", // Russian
// "zh_CN", // Shanghainese (China mainland)
"sk", // Slovak
"sl", // Slovenian
"es_AR", // Spanish (Argentina)
"es_CL", // Spanish (Chile)
"es_CO", // Spanish (Colombia)
"es_MX", // Spanish (Mexico)
"es", // Spanish (Spain)
"sv", // Swedish
"th", // Thai
"tr", // Turkish
"ta", // Tamil
"te", // Telugu
"uk", // Ukrainian
"ca_ES", // Valencian
"vi", // Vietnamese
]
}

androidx {
name = "Compose UI primitives"
type = LibraryType.PUBLISHED_LIBRARY
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package androidx.compose.ui.platform

import androidx.compose.ui.text.intl.Locale

internal class TranslationsCache<Key>(private val getTranslations: (String) -> Map<Key, String>?) {
internal fun getString(key: Key): String {
val locale = Locale.current
val tag = localeTag(language = locale.language, region = locale.region)
val translation = translationByLocaleTag.getOrPut(tag) {
findTranslation(locale)
}
return translation.get(key = key) ?: error("Missing translation for $key")
}

/**
* Translations we've already loaded, mapped by the locale tag (see [localeTag]).
*/
private val translationByLocaleTag = mutableMapOf<String, Map<Key, String>>()

/**
* Returns the tag for the given locale.
*
* Note that this is our internal format; this isn't the same as [Locale.toLanguageTag].
*/
private fun localeTag(language: String, region: String) = when {
language == "" -> ""
region == "" -> language
else -> "${language}_$region"
}

/**
* Returns a sequence of locale tags to use as keys to look up the translation for the given locale.
*
* Note that we don't need to check children (e.g. use `fr_FR` if `fr` is missing) because the
* translations should never have a missing parent.
*/
private fun localeTagChain(locale: Locale) = sequence {
if (locale.region != "") {
yield(localeTag(language = locale.language, region = locale.region))
}
if (locale.language != "") {
yield(localeTag(language = locale.language, region = ""))
}
yield(localeTag("", ""))
}

/**
* Finds a translation map for the given locale.
*/
private fun findTranslation(locale: Locale): Map<Key, String> {
// We don't need to merge translations because each one should contain all the strings.
return localeTagChain(locale).firstNotNullOf { getTranslations(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@

package androidx.compose.ui.platform

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.platform.l10n.en
import androidx.compose.ui.platform.l10n.translationFor

Expand All @@ -32,68 +29,16 @@ internal value class Strings private constructor(@Suppress("unused") private val
val Paste = Strings(2)
val SelectAll = Strings(3)
// When adding values here, make sure to also add them in ui/build.gradle,
// updateTranslations task (stringByResourceName parameter), and re-run the task
// updateTranslationsDesktop task (stringByResourceName parameter), and re-run the task
}
}

@Composable
@ReadOnlyComposable
internal fun getString(string: Strings): String {
val locale = Locale.current
val tag = localeTag(language = locale.language, region = locale.region)
val translation = translationByLocaleTag.getOrPut(tag) {
findTranslation(locale)
}
return translation[string] ?: error("Missing translation for $string")
}

/**
* A single translation; should contain all the [Strings].
*/
internal typealias Translation = Map<Strings, String>

/**
* Translations we've already loaded, mapped by the locale tag (see [localeTag]).
*/
private val translationByLocaleTag = mutableMapOf<String, Translation>()
private val cache = TranslationsCache(::translationFor)

/**
* Returns the tag for the given locale.
*
* Note that this is our internal format; this isn't the same as [Locale.toLanguageTag].
*/
private fun localeTag(language: String, region: String) = when {
language == "" -> ""
region == "" -> language
else -> "${language}_$region"
}

/**
* Returns a sequence of locale tags to use as keys to look up the translation for the given locale.
*
* Note that we don't need to check children (e.g. use `fr_FR` if `fr` is missing) because the
* translations should never have a missing parent.
*/
private fun localeTagChain(locale: Locale) = sequence {
if (locale.region != "") {
yield(localeTag(language = locale.language, region = locale.region))
}
if (locale.language != "") {
yield(localeTag(language = locale.language, region = ""))
}
yield(localeTag("", ""))
}

/**
* Finds a [Translation] for the given locale.
*/
private fun findTranslation(locale: Locale): Map<Strings, String> {
// We don't need to merge translations because each one should contain all the strings.
return localeTagChain(locale).firstNotNullOf { translationFor(it) }
}
internal fun getString(string: Strings): String = cache.getString(string)

/**
* This object is only needed to provide a namespace for the [Translation] provider functions
* This object is only needed to provide a namespace for the translation map provider functions
* (e.g. [Translations.en]), to avoid polluting the global namespace.
*/
internal object Translations
internal object Translations
Loading
Loading