IJ-MR-138960 IJPL-156223 Implement language and region selection before EUA screen

GitOrigin-RevId: 7926f4a26e0f2e09cc4662df2e548f35edb76f8e
This commit is contained in:
Vera Petrenkova
2024-07-05 19:48:53 +00:00
committed by intellij-monorepo-bot
parent c2e018c7e3
commit d74acd35ce
15 changed files with 340 additions and 14 deletions
@@ -78,7 +78,7 @@ public class DynamicBundle extends AbstractBundle {
@NotNull Locale locale,
@NotNull BiFunction<? super @NotNull ClassLoader, ? super Locale, ? extends @NotNull ResourceBundle> bundleResolver) {
Path bundlePath = FileSystems.getDefault().getPath(FileUtil.toCanonicalPath(defaultPath, '.'));
ClassLoader pluginClassLoader = DefaultBundleService.isDefaultBundle() ? null: LocalizationUtil.INSTANCE.getPluginClassLoader(bundleClassLoader);
ClassLoader pluginClassLoader = DefaultBundleService.isDefaultBundle() ? null: LocalizationUtil.INSTANCE.getPluginClassLoader(bundleClassLoader, locale);
List<Path> paths = LocalizationUtil.INSTANCE.getLocalizedPaths(bundlePath, locale);
Map<LocalizationOrder, ResourceBundle> bundleOrderMap = new HashMap<>();
if (pluginClassLoader != null) {
@@ -267,6 +267,17 @@ public class DynamicBundle extends AbstractBundle {
.computeIfAbsent(pathToBundle, __ -> resolveResourceBundle(loader, pathToBundle, locale));
}
@ApiStatus.Internal
public static @NotNull ResourceBundle getResourceBundleLocalized(@NotNull ClassLoader loader, @NotNull @NonNls String pathToBundle, @NotNull Locale locale) {
ResourceBundle bundle = (DefaultBundleService.isDefaultBundle() ? ourDefaultCache : ourCache)
.computeIfAbsent(loader, __ -> CollectionFactory.createConcurrentSoftValueMap())
.get(pathToBundle);
if (bundle != null && bundle.getLocale().equals(locale)) {
return bundle;
}
return resolveResourceBundle(loader, pathToBundle, locale);
}
public static @Nullable ResourceBundle getPluginBundle(@NotNull PluginDescriptor pluginDescriptor) {
ClassLoader classLoader = pluginDescriptor.getPluginClassLoader();
String baseName = pluginDescriptor.getResourceBundleBaseName();
@@ -18,4 +18,5 @@ interface LocalizationStateService {
fun getSelectedLocale(): String
fun setSelectedLocale(locale: String)
fun getLastSelectedLocale(): String
}
@@ -54,8 +54,9 @@ object LocalizationUtil {
@Internal
@JvmOverloads
fun getPluginClassLoader(defaultLoader: ClassLoader? = null): ClassLoader? {
val langBundle = findLanguageBundle() ?: return null
fun getPluginClassLoader(defaultLoader: ClassLoader? = null, locale: Locale = getLocale()): ClassLoader? {
if (locale == Locale.ENGLISH || locale == Locale.ROOT) return null
val langBundle = findLanguageBundle(locale) ?: return null
return langBundle.pluginDescriptor?.classLoader ?: defaultLoader
}
@@ -45,6 +45,10 @@ internal class LocalizationStateServiceImpl : LocalizationStateService, Persiste
return localizationState.selectedLocale
}
override fun getLastSelectedLocale(): String {
return localizationState.lastSelectedLocale
}
override fun setSelectedLocale(locale: String) {
localizationState.lastSelectedLocale = localizationState.selectedLocale
localizationState.selectedLocale = locale
@@ -655,6 +655,7 @@ f:com.intellij.ide.Prefs
- sf:NOT_SET:com.intellij.ide.Region
- externalName():java.lang.String
- s:fromExternalName(java.lang.String):com.intellij.ide.Region
- getDisplayKey():java.lang.String
- getDisplayName():java.lang.String
- getDisplayOrdinal():I
- s:valueOf(java.lang.String):com.intellij.ide.Region
@@ -0,0 +1,17 @@
title.language.and.region=Language and Region
description.language.and.region=<p align="center">Select a region to ensure that licensing, JetBrains Marketplace, and other region-specific features and links work correctly. See the <a style="display:none" href='https://www.jetbrains.com/help/idea/2024.2/language-and-region.html'>documentation</a> for details</p>
language.en=English
language.zhCN=Chinese (Simplified) \u4E2D\u6587\u8BED\u8A00\u5305
language.ja=Japanese \u65E5\u672C\u8A9E
language.ko=Korean \uD55C\uAD6D\uC778
title.region.africa=Africa
title.region.america=Americas
title.region.asia=Asia Pacific (except for China Mainland)
title.region.china=China Mainland
title.region.europe=Europe
title.region.not_set=Not specified
title.region.not.set.label=Region is not specified
button.next=Next
@@ -0,0 +1,26 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide;
import com.intellij.DynamicBundle;
import org.jetbrains.annotations.*;
import java.util.function.Supplier;
@ApiStatus.Internal
public final class LanguageAndRegionBundle {
public static final @NonNls String BUNDLE_FQN = "messages.LanguageAndRegionBundle";
private static final DynamicBundle BUNDLE = new DynamicBundle(LanguageAndRegionBundle.class, BUNDLE_FQN);
private LanguageAndRegionBundle() { }
public static @Nls @NotNull String message(@PropertyKey(resourceBundle = BUNDLE_FQN) @NotNull String key,
@Nullable Object @NotNull ... params) {
return BUNDLE.getMessage(key, params);
}
public static @NotNull Supplier<@Nls @NotNull String> messagePointer(@PropertyKey(resourceBundle = BUNDLE_FQN) @NotNull String key,
@Nullable Object @NotNull ... params) {
return BUNDLE.getLazyMessage(key, params);
}
}
@@ -35,6 +35,10 @@ public enum Region {
return IdeBundle.message(displayKey);
}
public String getDisplayKey() {
return displayKey;
}
public int getDisplayOrdinal() {
return displayOrdinal;
}
@@ -8781,6 +8781,10 @@ f:com.intellij.ide.trustedProjects.impl.UntrustedProjectNotificationProvider
- com.intellij.ui.EditorNotificationProvider
- <init>():V
- collectNotificationData(com.intellij.openapi.project.Project,com.intellij.openapi.vfs.VirtualFile):java.util.function.Function
f:com.intellij.ide.ui.ButtonPanel
- javax.swing.JPanel
- <init>(javax.swing.JButton):V
- f:getButton():javax.swing.JButton
f:com.intellij.ide.ui.ColorBlindnessInternalAction
- com.intellij.openapi.project.DumbAwareAction
- <init>():V
@@ -0,0 +1,61 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.ui
import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI
import com.intellij.ide.ui.laf.darcula.ui.OnboardingDialogButtons.BUTTON_HOVER_BORDER_COLOR
import com.intellij.ide.ui.laf.darcula.ui.OnboardingDialogButtons.DEFAULT_BUTTON_HOVER_BORDER_COLOR
import com.intellij.util.ui.JBDimension
import com.intellij.util.ui.JBSwingUtilities
import com.intellij.util.ui.JBUI
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.awt.BorderLayout
import java.awt.Graphics
import java.awt.event.ActionEvent
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.*
class ButtonPanel(val button: JButton) : JPanel(BorderLayout(0, 0)) {
init {
val buttonGap = 4
preferredSize = JBDimension(280, 40 + (buttonGap * 2))
isOpaque = false
add(button)
border = JBUI.Borders.empty(buttonGap, 0)
}
}
@ApiStatus.Internal
internal fun createButton(isDefault: Boolean, @Nls text: String, icon: Icon? = null, onClick: (JButton) -> Unit): JButton {
val btn = object : JButton() {
override fun getComponentGraphics(g: Graphics?): Graphics {
return JBSwingUtilities.runGlobalCGTransform(this, super.getComponentGraphics(g))
}
}
btn.putClientProperty("ActionToolbar.smallVariant", true)
btn.putClientProperty(DarculaButtonUI.DEFAULT_STYLE_KEY, isDefault)
btn.putClientProperty(DarculaButtonUI.AVOID_EXTENDING_BORDER_GRAPHICS, true)
val listener: MouseAdapter = object : MouseAdapter() {
override fun mouseEntered(e: MouseEvent) {
btn.putClientProperty("JButton.borderColor", if (isDefault) DEFAULT_BUTTON_HOVER_BORDER_COLOR else BUTTON_HOVER_BORDER_COLOR)
btn.repaint()
}
override fun mouseExited(e: MouseEvent) {
btn.putClientProperty("JButton.borderColor", null)
btn.repaint()
}
}
btn.addMouseMotionListener(listener)
btn.addMouseListener(listener)
btn.action = object : AbstractAction(text, null) {
override fun actionPerformed(e: ActionEvent) {
onClick.invoke(btn)
}
}
btn.icon = icon
btn.horizontalTextPosition = SwingConstants.LEFT
return btn
}
@@ -0,0 +1,176 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.ui
import com.intellij.DynamicBundle
import com.intellij.icons.AllIcons
import com.intellij.ide.LanguageAndRegionBundle
import com.intellij.ide.Region
import com.intellij.ide.RegionSettings
import com.intellij.l10n.LocalizationStateService
import com.intellij.openapi.application.impl.RawSwingDispatcher
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.ui.awt.RelativePoint
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.panels.VerticalLayout
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.panel
import com.intellij.util.ui.JBDimension
import com.intellij.util.ui.JBFont
import com.intellij.util.ui.JBSwingUtilities
import com.intellij.util.ui.JBUI
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.Nls
import java.awt.Dimension
import java.awt.Graphics
import java.util.*
import javax.swing.*
import javax.swing.border.Border
private val localeMappings = mapOf(Locale.CHINA to listOf("zh-CN", "CN"), Locale.JAPANESE to listOf("ja", "JP"),
Locale.KOREAN to listOf("ko", "KR", "KP"))
private class LanguageAndRegionDialog(private var selectedLanguage: Locale) : DialogWrapper(null, null, true, IdeModalityType.IDE, false) {
private var region = getRegionForLocale(selectedLanguage)
private val TOP_BOTTOM_BORDERS = 4
private val LEFT_RIGHT_BORDERS = 16
init {
isResizable = false
init()
}
override fun createCenterPanel(): JComponent {
val centerPanel = object : JPanel(VerticalLayout(JBUI.scale(10), SwingConstants.CENTER)) {
override fun getComponentGraphics(g: Graphics?): Graphics {
return JBSwingUtilities.runGlobalCGTransform(this, super.getComponentGraphics(g))
}
}.apply {
isOpaque = false
border = JBUI.Borders.empty()
}
centerPanel.preferredSize = JBDimension(640, 470)
val header = JLabel(getMessageBundle().getString("title.language.and.region")).apply {
font = JBFont.h1()
alignmentX = JLabel.CENTER_ALIGNMENT
}
centerPanel.add(panel {
row {
cell(header).align(Align.CENTER)
}
row {
text(getMessageBundle().getString("description.language.and.region"))
}
}.withPreferredWidth(350), VerticalLayout.CENTER)
val buttonsPanel = JPanel(VerticalLayout(JBUI.scale(3), SwingConstants.CENTER))
buttonsPanel.add(getLanguageButton())
buttonsPanel.add(getRegionButton())
buttonsPanel.add(getNextButton())
centerPanel.add(buttonsPanel, VerticalLayout.CENTER)
return centerPanel
}
private fun getLanguageButton() =
ButtonPanel(createButton(false, getLocaleName(selectedLanguage), AllIcons.General.ChevronDown) { createLanguagePopup(it) })
private fun getRegionButton() = ButtonPanel(
createButton(false, getRegionName(region), AllIcons.General.ChevronDown) { createRegionPopup(it) })
private fun getNextButton() = ButtonPanel(createButton(true, getMessageBundle().getString("button.next"), null) { this.doOKAction() })
override fun createContentPaneBorder(): Border {
return JBUI.Borders.empty()
}
private fun createRegionPopup(button: JButton) {
val regions = Region.entries.sortedBy { it.displayOrdinal }
val popup = JBPopupFactory.getInstance()
.createPopupChooserBuilder(regions)
.setRequestFocus(true)
.setRenderer { _, value, _, _, _ ->
if (value == null) return@setRenderer JLabel()
val label = JBLabel(getRegionName(value), null, SwingConstants.LEFT)
label.border = JBUI.Borders.empty(TOP_BOTTOM_BORDERS, LEFT_RIGHT_BORDERS)
return@setRenderer label
}
.setMinSize(Dimension(280, 100))
.setResizable(false)
.setCancelOnClickOutside(true)
.setItemChosenCallback {
region = it
button.text = getRegionLabel(it)
}
.createPopup()
popup.show(RelativePoint.getSouthWestOf(button))
}
private fun createLanguagePopup(button: JButton) {
val locales = mutableListOf(Locale.ENGLISH) + localeMappings.keys
val popup = JBPopupFactory.getInstance()
.createPopupChooserBuilder(locales)
.setRequestFocus(true)
.setRenderer { _, value, _, _, _ ->
if (value == null) return@setRenderer JLabel()
val label = JBLabel(getLocaleName(value), null, SwingConstants.LEFT)
label.border = JBUI.Borders.empty(TOP_BOTTOM_BORDERS, LEFT_RIGHT_BORDERS)
return@setRenderer label
}
.setMinSize(Dimension(280, 100))
.setResizable(false)
.setCancelOnClickOutside(true)
.setItemChosenCallback {
selectedLanguage = it
contentPanel.removeAll()
init()
contentPanel.revalidate()
contentPanel.repaint()
}
.createPopup()
popup.show(RelativePoint.getSouthWestOf(button))
}
private fun getRegionForLocale(locale: Locale): Region {
return when (locale) {
Locale.CHINA -> Region.CHINA
Locale.JAPANESE, Locale.KOREAN -> Region.APAC
else -> Region.NOT_SET
}
}
@Nls
private fun getLocaleName(locale: Locale): String {
return getMessageBundle().getString("language." + locale.toLanguageTag().replace("-", ""))
}
@Nls
private fun getRegionName(region: Region): String {
return getMessageBundle().getString(region.displayKey)
}
private fun getRegionLabel(region: Region): String {
return getMessageBundle().getString(if (region == Region.NOT_SET) "title.region.not.set.label" else region.displayKey)
}
override fun doOKAction() {
LocalizationStateService.getInstance()?.setSelectedLocale(selectedLanguage.toLanguageTag())
RegionSettings.setRegion(region)
super.doOKAction()
}
private fun getMessageBundle() = DynamicBundle.getResourceBundleLocalized(this::class.java.classLoader, LanguageAndRegionBundle.BUNDLE_FQN, selectedLanguage)
}
internal fun getLanguageAndRegionDialogIfNeeded(): (suspend () -> Boolean)? {
val locale = Locale.getDefault()
val matchingLanguage = localeMappings.values.flatten().find { locale.toLanguageTag().contains(it) }
if (matchingLanguage == null) return null
return suspend {
withContext(RawSwingDispatcher) {
val matchingLocale = localeMappings.entries.find { it.value.contains(matchingLanguage) }!!.key
LanguageAndRegionDialog(matchingLocale).showAndGet()
}
}
}
@@ -1,6 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.actionSystem.impl;
import com.intellij.diagnostic.LoadingState;
import com.intellij.ide.ActivityTracker;
import com.intellij.ide.DataManager;
import com.intellij.ide.ProhibitAWTEvents;
@@ -183,7 +184,7 @@ class PreCachedDataContext implements AsyncDataContext, UserDataHolder, AnAction
boolean isEDT = EDT.isCurrentThreadEdt();
boolean noRulesSection = isEDT && ActionUpdater.Companion.isNoRulesInEDTSection();
boolean rulesSuppressed = isEDT && Registry.is("actionSystem.update.actions.suppress.dataRules.on.edt");
boolean rulesSuppressed = isEDT && LoadingState.COMPONENTS_LOADED.isOccurred() && Registry.is("actionSystem.update.actions.suppress.dataRules.on.edt");
boolean rulesAllowed = !CommonDataKeys.PROJECT.is(dataId) && !rulesSuppressed && !noRulesSection;
Object answer = getDataInner(dataId, rulesAllowed, !noRulesSection);
@@ -16,16 +16,14 @@ import com.intellij.ide.plugins.PluginSet
import com.intellij.ide.plugins.marketplace.statistics.PluginManagerUsageCollector
import com.intellij.ide.plugins.marketplace.statistics.enums.DialogAcceptanceResultEnum
import com.intellij.ide.plugins.saveBundledPluginsState
import com.intellij.ide.ui.IconMapLoader
import com.intellij.ide.ui.LafManager
import com.intellij.ide.ui.NotRoamableUiSettings
import com.intellij.ide.ui.UISettings
import com.intellij.ide.ui.*
import com.intellij.ide.ui.customization.CustomActionsSchema
import com.intellij.ide.ui.html.initGlobalStyleSheet
import com.intellij.ide.ui.laf.LafManagerImpl
import com.intellij.idea.AppExitCodes
import com.intellij.idea.AppMode
import com.intellij.idea.IdeStarter
import com.intellij.l10n.LocalizationStateService
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.impl.ActionConfigurationCustomizer
import com.intellij.openapi.application.*
@@ -117,6 +115,15 @@ internal suspend fun loadApp(
}
}
val languageAndRegionTaskDeferred: Deferred<(suspend () -> Boolean)?>? = if (AppMode.isHeadless() || euaDocumentDeferred.await() == null) {
null
}
else {
async(CoroutineName("language and region")) {
getLanguageAndRegionDialogIfNeeded()
}
}
initServiceContainerJob.join()
val initTelemetryJob = launch(CoroutineName("opentelemetry configuration")) {
@@ -187,9 +194,20 @@ internal suspend fun loadApp(
)
if (!app.isHeadlessEnvironment) {
euaTaskDeferred?.await()?.let {
languageAndRegionTaskDeferred?.await()?.let {
cssInit?.join()
it()
val languageOkPressed = it()
euaTaskDeferred?.await()?.let {
it()
if (languageOkPressed) {
val localizationStateService = LocalizationStateService.getInstance() ?: return@let
if (localizationStateService.getLastSelectedLocale() != localizationStateService.getSelectedLocale()) {
preloadJob.cancel()
applicationStarter.cancel()
ApplicationManager.getApplication().restart()
}
}
}
}
}
@@ -1,6 +1,7 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ui;
import com.intellij.diagnostic.LoadingState;
import com.intellij.idea.AppMode;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
@@ -62,7 +63,7 @@ public abstract class AbstractExpandableItemsHandler<KeyType, ComponentType exte
}
};
private boolean myEnabled = Registry.is("ide.expansion.hints.enabled");
private boolean myEnabled = LoadingState.COMPONENTS_LOADED.isOccurred() && Registry.is("ide.expansion.hints.enabled");
private final MovablePopup myPopup;
private KeyType myKey;
private Rectangle myKeyItemBounds;
@@ -125,7 +126,7 @@ public abstract class AbstractExpandableItemsHandler<KeyType, ComponentType exte
@Override
public void mouseExited(MouseEvent event) {
// don't hide the hint if mouse exited to it
if (Registry.is("ide.hide.expandable.tooltip.owner.mouse.exit") || myTipComponent.getMousePosition() == null) {
if (LoadingState.COMPONENTS_LOADED.isOccurred() && Registry.is("ide.hide.expandable.tooltip.owner.mouse.exit") || myTipComponent.getMousePosition() == null) {
hideHint();
}
}
@@ -383,7 +383,7 @@ public class AbstractPopup implements JBPopup, ScreenAreaConsumer, AlignedPopup
myCancelOnWindow = cancelOnWindow;
myMinSize = minSize;
if (Registry.is("ide.popup.horizontal.scroll.bar.opaque")) {
if (LoadingState.COMPONENTS_LOADED.isOccurred() && Registry.is("ide.popup.horizontal.scroll.bar.opaque")) {
forHorizontalScrollBar(bar -> bar.setOpaque(true));
}
@@ -1650,7 +1650,7 @@ public class AbstractPopup implements JBPopup, ScreenAreaConsumer, AlignedPopup
private PopupComponentFactory.PopupType getMostSuitablePopupType() {
boolean forceDialog = myMayBeParent || SystemInfo.isMac && !(myOwner instanceof IdeFrame) && myOwner.isShowing();
if (Registry.is("allow.dialog.based.popups")) {
if (LoadingState.COMPONENTS_LOADED.isOccurred() && Registry.is("allow.dialog.based.popups")) {
boolean noFocus = !myFocusable || !myRequestFocus;
boolean cannotBeDialog = noFocus; // && SystemInfo.isXWindow