mintlee commited on
Commit
4c846d3
·
1 Parent(s): d300944

change api

Browse files
Files changed (2) hide show
  1. .env +1 -1
  2. test.ipynb +377 -4
.env CHANGED
@@ -1,2 +1,2 @@
1
- GEMINI_API_KEY = AIzaSyAk1LTwWMZyTfPAKmsn6JzFtI1MpnI7FH8
2
  MONGODB_URI = mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0
 
1
+ GEMINI_API_KEY = AIzaSyDIPbH7zKoeTS5aKMQuMjzBBiVlWadcmr8
2
  MONGODB_URI = mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0
test.ipynb CHANGED
@@ -252,12 +252,12 @@
252
  "output_type": "stream",
253
  "text": [
254
  "✅ Đã xóa 0 file trong collection 'root_file' của db 'word'\n",
255
- "✅ Đã xóa 0 file trong collection 'root_file' của db 'excel'\n",
256
- "✅ Đã xóa 3 file trong collection 'root_file' của db 'pptx'\n",
257
  "✅ Đã xóa 0 file trong collection 'root_file' của db 'csv'\n",
258
  "✅ Đã xóa 0 file trong collection 'final_file' của db 'word'\n",
259
- "✅ Đã xóa 0 file trong collection 'final_file' của db 'excel'\n",
260
- "✅ Đã xóa 3 file trong collection 'final_file' của db 'pptx'\n",
261
  "✅ Đã xóa 0 file trong collection 'final_file' của db 'csv'\n"
262
  ]
263
  }
@@ -864,6 +864,379 @@
864
  "str = 'Kiểm tra ngoại quan giá đỡ'\n",
865
  "len(str)"
866
  ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
  }
868
  ],
869
  "metadata": {
 
252
  "output_type": "stream",
253
  "text": [
254
  "✅ Đã xóa 0 file trong collection 'root_file' của db 'word'\n",
255
+ "✅ Đã xóa 8 file trong collection 'root_file' của db 'excel'\n",
256
+ "✅ Đã xóa 0 file trong collection 'root_file' của db 'pptx'\n",
257
  "✅ Đã xóa 0 file trong collection 'root_file' của db 'csv'\n",
258
  "✅ Đã xóa 0 file trong collection 'final_file' của db 'word'\n",
259
+ "✅ Đã xóa 7 file trong collection 'final_file' của db 'excel'\n",
260
+ "✅ Đã xóa 0 file trong collection 'final_file' của db 'pptx'\n",
261
  "✅ Đã xóa 0 file trong collection 'final_file' của db 'csv'\n"
262
  ]
263
  }
 
864
  "str = 'Kiểm tra ngoại quan giá đỡ'\n",
865
  "len(str)"
866
  ]
867
+ },
868
+ {
869
+ "cell_type": "code",
870
+ "execution_count": null,
871
+ "metadata": {},
872
+ "outputs": [],
873
+ "source": [
874
+ "import os\n",
875
+ "import zipfile\n",
876
+ "import copy\n",
877
+ "import time\n",
878
+ "import xml.etree.ElementTree as ET\n",
879
+ "from typing import List, Dict, Any, Optional, Tuple\n",
880
+ "from utils.utils import translate_text, unzip_office_file, preprocess_text, postprocess_text, translate_single_text\n",
881
+ "from pymongo import MongoClient\n",
882
+ "import gridfs\n",
883
+ "from io import BytesIO\n",
884
+ "import shutil\n",
885
+ "import io\n",
886
+ "import re\n",
887
+ "from typing import Dict\n",
888
+ "\n",
889
+ "\n",
890
+ "NS_MAIN = {'main': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}\n",
891
+ "NS_DRAWING = {'xdr': \"http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing\"}\n",
892
+ "NS_A = {'a': \"http://schemas.openxmlformats.org/drawingml/2006/main\"}\n",
893
+ "\n",
894
+ "# --- Hàm đăng ký namespace (quan trọng khi ghi file) ---\n",
895
+ "def register_namespaces(xml_file):\n",
896
+ " \"\"\"Đọc và đăng ký các namespace từ file XML.\"\"\"\n",
897
+ " namespaces = dict([\n",
898
+ " node for _, node in ET.iterparse(xml_file, events=['start-ns'])\n",
899
+ " ])\n",
900
+ " for ns, uri in namespaces.items():\n",
901
+ " ET.register_namespace(ns, uri)\n",
902
+ "\n",
903
+ " # Đăng ký thêm namespace phổ biến nếu chưa có\n",
904
+ " if 'main' not in namespaces and '' not in namespaces and NS_MAIN['main'] not in namespaces.values():\n",
905
+ " ET.register_namespace('', NS_MAIN['main'])\n",
906
+ " elif 'main' not in namespaces and NS_MAIN['main'] not in namespaces.values():\n",
907
+ " ET.register_namespace('main', NS_MAIN['main'])\n",
908
+ "\n",
909
+ " # Đăng ký namespaces cho drawing nếu cần\n",
910
+ " if 'xdr' not in namespaces and NS_DRAWING['xdr'] not in namespaces.values():\n",
911
+ " ET.register_namespace('xdr', NS_DRAWING['xdr'])\n",
912
+ " if 'a' not in namespaces and NS_A['a'] not in namespaces.values():\n",
913
+ " ET.register_namespace('a', NS_A['a'])\n",
914
+ "\n",
915
+ "\n",
916
+ "def extract_text_from_sheet(unzipped_folder_path: str) -> Optional[Tuple[List[Dict[str, Any]], Dict[str, Any]]]:\n",
917
+ " \"\"\"\n",
918
+ " Trích xuất text, lưu lại định dạng của run đầu tiên nếu là Rich Text,\n",
919
+ " bao gồm cả text từ TextBoxes trong drawings.\n",
920
+ " \"\"\"\n",
921
+ " modifiable_nodes = []\n",
922
+ " shared_strings_path = os.path.join(unzipped_folder_path, \"xl\", \"sharedStrings.xml\")\n",
923
+ " worksheets_folder = os.path.join(unzipped_folder_path, \"xl\", \"worksheets\")\n",
924
+ " drawings_folder = os.path.join(unzipped_folder_path, \"xl\", \"drawings\") # Thêm dòng này\n",
925
+ "\n",
926
+ " shared_tree = None\n",
927
+ " sheet_trees = {}\n",
928
+ " drawing_trees = {} # Thêm dòng này\n",
929
+ "\n",
930
+ " # --- Xử lý sharedStrings.xml ---\n",
931
+ " if os.path.exists(shared_strings_path):\n",
932
+ " try:\n",
933
+ " register_namespaces(shared_strings_path) # Đảm bảo register_namespaces được gọi\n",
934
+ " shared_tree = ET.parse(shared_strings_path)\n",
935
+ " root_shared = shared_tree.getroot()\n",
936
+ "\n",
937
+ " for si_element in root_shared.findall('main:si', NS_MAIN):\n",
938
+ " text_parts = []\n",
939
+ " # Tìm tất cả <t> con, bất kể chúng nằm trong <r> hay không\n",
940
+ " t_elements = si_element.findall('.//main:t', NS_MAIN)\n",
941
+ "\n",
942
+ " first_r = si_element.find('./main:r', NS_MAIN)\n",
943
+ " first_rpr_clone = None\n",
944
+ " is_rich_text = first_r is not None # Rich text nếu có ít nhất một <r>\n",
945
+ "\n",
946
+ " if is_rich_text:\n",
947
+ " # Cố gắng tìm <rPr> bên trong <r> đầu tiên\n",
948
+ " first_rpr_candidate = si_element.find('./main:r/main:rPr', NS_MAIN)\n",
949
+ " if first_rpr_candidate is not None:\n",
950
+ " first_rpr_clone = copy.deepcopy(first_rpr_candidate)\n",
951
+ " else:\n",
952
+ " # Nếu <r> đầu tiên không có <rPr>, kiểm tra <si><rPh><rPr> (Phonetic properties, ít gặp hơn)\n",
953
+ " # Hoặc có thể không có định dạng nào cụ thể ở run đầu\n",
954
+ " pass\n",
955
+ "\n",
956
+ "\n",
957
+ " for t_node in t_elements:\n",
958
+ " if t_node.text:\n",
959
+ " text_parts.append(t_node.text)\n",
960
+ " full_text = \"\".join(text_parts)\n",
961
+ "\n",
962
+ " if not full_text or full_text.isspace(): continue\n",
963
+ "\n",
964
+ " # Logic xác định type dựa trên sự hiện diện của <r> và <rPr> đã được điều chỉnh\n",
965
+ " if is_rich_text : # Chỉ cần có <r> là đủ, first_rpr_clone có thể là None\n",
966
+ " modifiable_nodes.append({\n",
967
+ " 'type': 'shared_rich',\n",
968
+ " 'original_text': full_text,\n",
969
+ " 'element': si_element,\n",
970
+ " 'first_format': first_rpr_clone, # Sẽ là None nếu <r> đầu không có <rPr>\n",
971
+ " 'source_file': os.path.join(\"xl\", \"sharedStrings.xml\"),\n",
972
+ " 'sheet_name': None\n",
973
+ " })\n",
974
+ " elif t_elements:\n",
975
+ " direct_t = si_element.find('./main:t', NS_MAIN)\n",
976
+ " if direct_t is not None:\n",
977
+ " modifiable_nodes.append({\n",
978
+ " 'type': 'shared_simple',\n",
979
+ " 'original_text': full_text,\n",
980
+ " 'element': direct_t, # Tham chiếu <t>\n",
981
+ " 'first_format': None,\n",
982
+ " 'source_file': os.path.join(\"xl\", \"sharedStrings.xml\"),\n",
983
+ " 'sheet_name': None\n",
984
+ " })\n",
985
+ " # else: ít khả năng xảy ra nếu t_elements có phần tử\n",
986
+ "\n",
987
+ " except Exception as e:\n",
988
+ " print(f\"Lỗi xử lý sharedStrings: {e}\")\n",
989
+ " import traceback\n",
990
+ " traceback.print_exc()\n",
991
+ "\n",
992
+ "\n",
993
+ " # --- Xử lý các file sheetX.xml (Inline Strings) ---\n",
994
+ " if os.path.isdir(worksheets_folder):\n",
995
+ " for sheet_filename in sorted(os.listdir(worksheets_folder)):\n",
996
+ " if sheet_filename.lower().endswith(\".xml\"):\n",
997
+ " sheet_file_path = os.path.join(worksheets_folder, sheet_filename)\n",
998
+ " try:\n",
999
+ " register_namespaces(sheet_file_path) # Đảm bảo register_namespaces được gọi\n",
1000
+ " sheet_tree = ET.parse(sheet_file_path)\n",
1001
+ " sheet_trees[sheet_filename] = sheet_tree\n",
1002
+ " root_sheet = sheet_tree.getroot()\n",
1003
+ " for cell in root_sheet.findall('.//main:c[@t=\"inlineStr\"]', NS_MAIN):\n",
1004
+ " t_element = cell.find('.//main:is/main:t', NS_MAIN) # Sửa lại tìm kiếm <t>\n",
1005
+ " if t_element is not None and t_element.text is not None and t_element.text.strip():\n",
1006
+ " modifiable_nodes.append({\n",
1007
+ " 'type': 'inline',\n",
1008
+ " 'original_text': t_element.text,\n",
1009
+ " 'element': t_element,\n",
1010
+ " 'first_format': None,\n",
1011
+ " 'source_file': os.path.join(\"xl\", \"worksheets\", sheet_filename),\n",
1012
+ " 'sheet_name': sheet_filename\n",
1013
+ " })\n",
1014
+ " except Exception as e:\n",
1015
+ " print(f\"Lỗi xử lý sheet {sheet_filename}: {e}\")\n",
1016
+ " import traceback\n",
1017
+ " traceback.print_exc()\n",
1018
+ " else:\n",
1019
+ " print(f\"Cảnh báo: Không tìm thấy thư mục worksheets: {worksheets_folder}\")\n",
1020
+ "\n",
1021
+ "\n",
1022
+ " # --- Xử lý các file drawingX.xml (Text Boxes, Shapes with Text) ---\n",
1023
+ " if os.path.isdir(drawings_folder):\n",
1024
+ " for drawing_filename in sorted(os.listdir(drawings_folder)):\n",
1025
+ " if drawing_filename.lower().endswith(\".xml\"):\n",
1026
+ " drawing_file_path = os.path.join(drawings_folder, drawing_filename)\n",
1027
+ " try:\n",
1028
+ " register_namespaces(drawing_file_path) # Đảm bảo register_namespaces được gọi\n",
1029
+ " drawing_tree = ET.parse(drawing_file_path)\n",
1030
+ " drawing_trees[drawing_filename] = drawing_tree\n",
1031
+ " root_drawing = drawing_tree.getroot()\n",
1032
+ "\n",
1033
+ " # TextBoxes và Shapes có text thường nằm trong <xdr:sp> (shape) -> <xdr:txBody> (text body)\n",
1034
+ " # Bên trong <xdr:txBody> là các <a:p> (paragraph)\n",
1035
+ " for p_element in root_drawing.findall('.//xdr:txBody/a:p', {**NS_DRAWING, **NS_A}):\n",
1036
+ " text_parts = []\n",
1037
+ " # Lấy text từ tất cả <a:t> trong paragraph này\n",
1038
+ " t_elements = p_element.findall('.//a:t', NS_A)\n",
1039
+ "\n",
1040
+ " first_r = p_element.find('./a:r', NS_A) # Tìm <a:r> con trực tiếp đầu tiên của <a:p>\n",
1041
+ " first_rpr_clone = None # Định dạng của run đầu tiên trong paragraph\n",
1042
+ "\n",
1043
+ " is_rich_text_paragraph = first_r is not None # Coi là rich nếu có <a:r>\n",
1044
+ "\n",
1045
+ " if is_rich_text_paragraph:\n",
1046
+ " # Tìm <a:rPr> bên trong <a:r> đầu tiên của <a:p>\n",
1047
+ " first_rpr = first_r.find('./a:rPr', NS_A)\n",
1048
+ " if first_rpr is not None:\n",
1049
+ " first_rpr_clone = copy.deepcopy(first_rpr)\n",
1050
+ "\n",
1051
+ " for t_node in t_elements:\n",
1052
+ " if t_node.text:\n",
1053
+ " text_parts.append(t_node.text)\n",
1054
+ " full_text = \"\".join(text_parts)\n",
1055
+ "\n",
1056
+ " if not full_text or full_text.isspace(): continue\n",
1057
+ "\n",
1058
+ " # Lưu node là <a:p> vì chúng ta sẽ thay thế toàn bộ nội dung của nó\n",
1059
+ " # (các <a:r> và <a:t> bên trong)\n",
1060
+ " modifiable_nodes.append({\n",
1061
+ " 'type': 'drawing_text', # Loại mới cho text trong drawing\n",
1062
+ " 'original_text': full_text,\n",
1063
+ " 'element': p_element, # Tham chiếu đến <a:p>\n",
1064
+ " 'first_format': first_rpr_clone, # Lưu định dạng <a:rPr> của <a:r> đầu tiên (hoặc None)\n",
1065
+ " 'source_file': os.path.join(\"xl\", \"drawings\", drawing_filename),\n",
1066
+ " 'sheet_name': None # Có thể tìm cách liên kết ngược lại sheet nếu cần\n",
1067
+ " })\n",
1068
+ " except Exception as e:\n",
1069
+ " print(f\"Lỗi xử lý drawing {drawing_filename}: {e}\")\n",
1070
+ " import traceback\n",
1071
+ " traceback.print_exc()\n",
1072
+ " else:\n",
1073
+ " print(f\"Thông tin: Không tìm thấy thư mục drawings: {drawings_folder}\")\n",
1074
+ "\n",
1075
+ "\n",
1076
+ " global_data = {\n",
1077
+ " \"shared_tree\": shared_tree,\n",
1078
+ " \"sheet_trees\": sheet_trees,\n",
1079
+ " \"drawing_trees\": drawing_trees, # Thêm dòng này\n",
1080
+ " \"shared_strings_path\": shared_strings_path,\n",
1081
+ " \"worksheets_folder\": worksheets_folder,\n",
1082
+ " \"drawings_folder\": drawings_folder # Thêm dòng này\n",
1083
+ " }\n",
1084
+ " return modifiable_nodes, global_data\\\n",
1085
+ "\n",
1086
+ "\n",
1087
+ "\n",
1088
+ "def zip_folder_to_excel_file(folder_path, file_name):\n",
1089
+ " try:\n",
1090
+ " # Nén thư mục thành file .xlsx trong RAM\n",
1091
+ " xlsx_buffer = io.BytesIO()\n",
1092
+ " with zipfile.ZipFile(xlsx_buffer, 'w', zipfile.ZIP_DEFLATED) as zipf:\n",
1093
+ " for root, _, files in os.walk(folder_path):\n",
1094
+ " for file in files:\n",
1095
+ " file_path = os.path.join(root, file)\n",
1096
+ " archive_path = os.path.relpath(file_path, folder_path)\n",
1097
+ " zipf.write(file_path, archive_path)\n",
1098
+ "\n",
1099
+ " xlsx_buffer.seek(0)\n",
1100
+ "\n",
1101
+ " client = MongoClient(\"mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0\")\n",
1102
+ " db = client['excel'] \n",
1103
+ " fs = gridfs.GridFS(db, collection='final_file')\n",
1104
+ "\n",
1105
+ " file_id = fs.put(xlsx_buffer.read(), filename=file_name)\n",
1106
+ " print(f\"✅ Đã lưu file Excel vào MongoDB với ID: {file_id}\")\n",
1107
+ " return file_id\n",
1108
+ "\n",
1109
+ " except Exception as e:\n",
1110
+ " print(f\"❌ Lỗi khi nén và lưu Excel vào MongoDB: {e}\")\n",
1111
+ " return None\n",
1112
+ " \n",
1113
+ "\n",
1114
+ "\n",
1115
+ "def translate_xlsx(file_id, file_name, source_lang='en', target_lang='vi', batch_size_segments=50, max_words_per_segment=100, delay_between_requests=1):\n",
1116
+ " \n",
1117
+ " client = MongoClient(\"mongodb+srv://admin:[email protected]/?retryWrites=true&w=majority&appName=Cluster0\")\n",
1118
+ " db = client['excel']\n",
1119
+ " fs = gridfs.GridFS(db, collection='root_file')\n",
1120
+ " \n",
1121
+ " ppt_file = fs.get(file_id)\n",
1122
+ " excel_file = BytesIO(ppt_file.read())\n",
1123
+ "\n",
1124
+ " xml_folder = unzip_office_file(excel_file)\n",
1125
+ " path_to_workbook_xml = os.path.join(xml_folder, \"xl\", \"workbook.xml\")\n",
1126
+ " translate_sheet_names_via_regex(path_to_workbook_xml, source_lang, target_lang)\n",
1127
+ "\n",
1128
+ " modifiable_nodes, global_data = extract_text_from_sheet(xml_folder)\n",
1129
+ "\n",
1130
+ " original_texts = get_text_list_from_nodes(modifiable_nodes)\n",
1131
+ "\n",
1132
+ " all_results = [None] * len(original_texts)\n",
1133
+ " current_index = 0\n",
1134
+ " processed_count = 0\n",
1135
+ " api_call_counter = 0 # Track API calls for delay logic\n",
1136
+ "\n",
1137
+ " while current_index < len(original_texts):\n",
1138
+ " batch_texts_to_translate = []\n",
1139
+ " batch_original_indices = [] # 0-based indices for assignment\n",
1140
+ " batch_end_index = min(current_index + batch_size_segments, len(original_texts))\n",
1141
+ " found_long_segment_at = -1 # 0-based index in original_texts\n",
1142
+ "\n",
1143
+ " # 1. Build the next potential batch, stopping if a long segment is found\n",
1144
+ " for i in range(current_index, batch_end_index):\n",
1145
+ " segment = original_texts[i]\n",
1146
+ " word_count = count_words(segment)\n",
1147
+ "\n",
1148
+ " if word_count <= max_words_per_segment:\n",
1149
+ " batch_texts_to_translate.append(segment)\n",
1150
+ " batch_original_indices.append(i)\n",
1151
+ " else:\n",
1152
+ " found_long_segment_at = i\n",
1153
+ " break # Stop building this batch\n",
1154
+ "\n",
1155
+ " # --- Process the findings ---\n",
1156
+ "\n",
1157
+ " # 2. Translate the VALID batch collected *before* the long segment (if any)\n",
1158
+ " if batch_texts_to_translate:\n",
1159
+ " # Add delay BEFORE the API call if it's not the very first call\n",
1160
+ " if api_call_counter > 0 and delay_between_requests > 0:\n",
1161
+ " time.sleep(delay_between_requests)\n",
1162
+ "\n",
1163
+ " translated_batch = _translate_batch_helper(\n",
1164
+ " batch_texts_to_translate,\n",
1165
+ " [idx + 1 for idx in batch_original_indices], # 1-based for logging\n",
1166
+ " source_lang,\n",
1167
+ " target_lang\n",
1168
+ " )\n",
1169
+ " api_call_counter += 1\n",
1170
+ " # Assign results back\n",
1171
+ " for batch_idx, original_idx in enumerate(batch_original_indices):\n",
1172
+ " all_results[original_idx] = translated_batch[batch_idx]\n",
1173
+ " processed_count += len(batch_texts_to_translate)\n",
1174
+ "\n",
1175
+ " # 3. Handle the long segment INDIVIDUALLY (if one was found)\n",
1176
+ " if found_long_segment_at != -1:\n",
1177
+ " long_segment_index = found_long_segment_at\n",
1178
+ " long_segment_text = str(original_texts[long_segment_index])\n",
1179
+ " # word_count = count_words(long_segment_text) # Recalculate for log clarity\n",
1180
+ "\n",
1181
+ " try:\n",
1182
+ " translated = translate_single_text(long_segment_text, source_lang, target_lang)\n",
1183
+ " \n",
1184
+ " final = [translated]\n",
1185
+ " api_call_counter += 1\n",
1186
+ "\n",
1187
+ " if len(final) == 1:\n",
1188
+ " all_results[long_segment_index] = final[0]\n",
1189
+ " else:\n",
1190
+ " print(f\" *** CRITICAL ERROR: Long segment translation result count mismatch! Expected 1, got {len(final)}. Marking as failed.\")\n",
1191
+ " all_results[long_segment_index] = \"<translation_length_mismatch_error>\"\n",
1192
+ "\n",
1193
+ " except Exception as e:\n",
1194
+ " print(f\" *** ERROR during translation of long segment {long_segment_index + 1}: {e}. Marking as failed.\")\n",
1195
+ " # traceback.print_exc() # Uncomment for detailed debug\n",
1196
+ " all_results[long_segment_index] = \"<translation_api_error>\"\n",
1197
+ " # Do not increment api_call_counter if the API call itself failed before returning\n",
1198
+ "\n",
1199
+ " processed_count += 1\n",
1200
+ " # Update current_index to start AFTER this long segment\n",
1201
+ " current_index = long_segment_index + 1\n",
1202
+ "\n",
1203
+ " else:\n",
1204
+ " # No long segment was found in the range checked.\n",
1205
+ " # Move current_index to the end of the range examined.\n",
1206
+ " current_index = batch_end_index\n",
1207
+ "\n",
1208
+ " missing_count = 0\n",
1209
+ " final_texts_for_nodes = []\n",
1210
+ " for i, res in enumerate(all_results):\n",
1211
+ " if res is None:\n",
1212
+ " print(f\"LỖI LOGIC: Segment {i+1} không được xử lý! Giữ lại text gốc: '{original_texts[i]}'\")\n",
1213
+ " final_texts_for_nodes.append(original_texts[i])\n",
1214
+ " missing_count += 1\n",
1215
+ " else:\n",
1216
+ " final_texts_for_nodes.append(res)\n",
1217
+ "\n",
1218
+ " if missing_count > 0:\n",
1219
+ " print(f\"CẢNH BÁO NGHIÊM TRỌNG: {missing_count} segments bị bỏ lỡ trong quá trình xử lý.\")\n",
1220
+ "\n",
1221
+ " if len(final_texts_for_nodes) != len(original_texts):\n",
1222
+ " print(f\"LỖI NGHIÊM TRỌNG: Số lượng text cuối cùng ({len(final_texts_for_nodes)}) không khớp với gốc ({len(original_texts)}). Hủy bỏ cập nhật.\")\n",
1223
+ " else:\n",
1224
+ " # Gán vào node\n",
1225
+ " for i, node_info in enumerate(modifiable_nodes):\n",
1226
+ " node_info['modified_text'] = final_texts_for_nodes[i]\n",
1227
+ " \n",
1228
+ " save_success = apply_and_save_changes(modifiable_nodes, global_data)\n",
1229
+ " if not save_success:\n",
1230
+ " print(\"LỖI NGHIÊM TRỌNG: Không thể lưu thay đổi vào file XML.\")\n",
1231
+ " else:\n",
1232
+ " # Only zip if saving XML was successful\n",
1233
+ " final_id = zip_folder_to_excel_file(xml_folder, file_name)\n",
1234
+ " if final_id:\n",
1235
+ " shutil.rmtree(xml_folder) # Mark folder as 'handled' by zipping\n",
1236
+ " else:\n",
1237
+ " print(\"LỖI NGHIÊM TRỌNG: Không thể tạo file XLSX đã dịch cuối cùng.\")\n",
1238
+ " return final_id"
1239
+ ]
1240
  }
1241
  ],
1242
  "metadata": {