diff --git a/.github/scripts/on-release.sh b/.github/scripts/on-release.sh
index dafbf3d6a1c..275c74f8ea5 100755
--- a/.github/scripts/on-release.sh
+++ b/.github/scripts/on-release.sh
@@ -342,12 +342,14 @@ jq_arg=".packages[0].platforms[0].version = \"$RELEASE_TAG\" | \
 echo "Generating $PACKAGE_JSON_DEV ..."
 cat "$PACKAGE_JSON_TEMPLATE" | jq "$jq_arg" > "$OUTPUT_DIR/$PACKAGE_JSON_DEV"
 # On MacOS the sed command won't skip the first match. Use gsed instead.
-sed '0,/github\.com\/espressif\//!s|github\.com/espressif/|dl.espressif.cn/github_assets/espressif/|g' "$OUTPUT_DIR/$PACKAGE_JSON_DEV" > "$OUTPUT_DIR/$PACKAGE_JSON_DEV_CN"
+sed '0,/github\.com\//!s|github\.com/|dl.espressif.cn/github_assets/|g' "$OUTPUT_DIR/$PACKAGE_JSON_DEV" > "$OUTPUT_DIR/$PACKAGE_JSON_DEV_CN"
+python "$SCRIPTS_DIR/release_append_cn.py" "$OUTPUT_DIR/$PACKAGE_JSON_DEV_CN"
 if [ "$RELEASE_PRE" == "false" ]; then
     echo "Generating $PACKAGE_JSON_REL ..."
     cat "$PACKAGE_JSON_TEMPLATE" | jq "$jq_arg" > "$OUTPUT_DIR/$PACKAGE_JSON_REL"
     # On MacOS the sed command won't skip the first match. Use gsed instead.
-    sed '0,/github\.com\/espressif\//!s|github\.com/espressif/|dl.espressif.cn/github_assets/espressif/|g' "$OUTPUT_DIR/$PACKAGE_JSON_REL" > "$OUTPUT_DIR/$PACKAGE_JSON_REL_CN"
+    sed '0,/github\.com\//!s|github\.com/|dl.espressif.cn/github_assets/|g' "$OUTPUT_DIR/$PACKAGE_JSON_REL" > "$OUTPUT_DIR/$PACKAGE_JSON_REL_CN"
+    python "$SCRIPTS_DIR/release_append_cn.py" "$OUTPUT_DIR/$PACKAGE_JSON_REL_CN"
 fi
 
 # Figure out the last release or pre-release
@@ -456,14 +458,14 @@ echo "Uploading $PACKAGE_JSON_DEV ..."
 echo "Download URL: $(git_safe_upload_asset "$OUTPUT_DIR/$PACKAGE_JSON_DEV")"
 echo "Pages URL: $(git_safe_upload_to_pages "$PACKAGE_JSON_DEV" "$OUTPUT_DIR/$PACKAGE_JSON_DEV")"
 echo "Download CN URL: $(git_safe_upload_asset "$OUTPUT_DIR/$PACKAGE_JSON_DEV_CN")"
-echo "Pages CN URL: $(git_safe_upload_to_pages "$PACKAGE_JSON_DEV" "$OUTPUT_DIR/$PACKAGE_JSON_DEV_CN")"
+echo "Pages CN URL: $(git_safe_upload_to_pages "$PACKAGE_JSON_DEV_CN" "$OUTPUT_DIR/$PACKAGE_JSON_DEV_CN")"
 echo
 if [ "$RELEASE_PRE" == "false" ]; then
     echo "Uploading $PACKAGE_JSON_REL ..."
     echo "Download URL: $(git_safe_upload_asset "$OUTPUT_DIR/$PACKAGE_JSON_REL")"
     echo "Pages URL: $(git_safe_upload_to_pages "$PACKAGE_JSON_REL" "$OUTPUT_DIR/$PACKAGE_JSON_REL")"
     echo "Download CN URL: $(git_safe_upload_asset "$OUTPUT_DIR/$PACKAGE_JSON_REL_CN")"
-    echo "Pages CN URL: $(git_safe_upload_to_pages "$PACKAGE_JSON_REL" "$OUTPUT_DIR/$PACKAGE_JSON_REL_CN")"
+    echo "Pages CN URL: $(git_safe_upload_to_pages "$PACKAGE_JSON_REL_CN" "$OUTPUT_DIR/$PACKAGE_JSON_REL_CN")"
     echo
 fi
 
diff --git a/.github/scripts/release_append_cn.py b/.github/scripts/release_append_cn.py
new file mode 100755
index 00000000000..b29fe0c31ba
--- /dev/null
+++ b/.github/scripts/release_append_cn.py
@@ -0,0 +1,56 @@
+
+#!/usr/bin/env python3
+
+# Arduino IDE provides by default a package file for the ESP32. This causes version conflicts
+# when the user tries to use the JSON file with the Chinese mirrors.
+#
+# The downside is that the Arduino IDE will always warn the user that updates are available as it
+# will consider the version from the Chinese mirrors as a pre-release version.
+#
+# This script is used to append "-cn" to all versions in the package_esp32_index_cn.json file so that
+# the user can select the Chinese mirrors without conflicts.
+#
+# If Arduino ever stops providing the package_esp32_index.json file by default,
+# this script can be removed and the tags reverted.
+
+import json
+
+def append_cn_to_versions(obj):
+    if isinstance(obj, dict):
+        # dfu-util comes from arduino.cc and not from the Chinese mirrors, so we skip it
+        if obj.get("name") == "dfu-util":
+            return
+
+        for key, value in obj.items():
+            if key == "version" and isinstance(value, str):
+                if not value.endswith("-cn"):
+                    obj[key] = value + "-cn"
+            else:
+                append_cn_to_versions(value)
+
+    elif isinstance(obj, list):
+        for item in obj:
+            append_cn_to_versions(item)
+
+def process_json_file(input_path, output_path=None):
+    with open(input_path, "r", encoding="utf-8") as f:
+        data = json.load(f)
+
+    append_cn_to_versions(data)
+
+    if output_path is None:
+        output_path = input_path
+
+    with open(output_path, "w", encoding="utf-8") as f:
+        json.dump(data, f, indent=2)
+
+    print(f"Updated JSON written to {output_path}")
+
+if __name__ == "__main__":
+    import sys
+    if len(sys.argv) < 2:
+        print("Usage: python release_append_cn.py input.json [output.json]")
+    else:
+        input_file = sys.argv[1]
+        output_file = sys.argv[2] if len(sys.argv) > 2 else None
+        process_json_file(input_file, output_file)
diff --git a/docs/en/installing.rst b/docs/en/installing.rst
index 35342020864..3ca0881c398 100644
--- a/docs/en/installing.rst
+++ b/docs/en/installing.rst
@@ -70,6 +70,8 @@ To start the installation process using the Boards Manager, follow these steps:
    :figclass: align-center
 
 -  Open Boards Manager from Tools > Board menu and install *esp32* platform (and do not forget to select your ESP32 board from Tools > Board menu after installation).
+   Users in China must select the package version with the "-cn" suffix and perform updates manually.
+   Automatic updates are not supported in this region, as they target the default package without the "-cn" suffix, resulting in download failures.
 
 .. figure:: ../_static/install_guide_boards_manager_esp32.png
    :align: center