{
  "sha": "56179e906bcc36fb404378e7aa15924fd9df37b3",
  "message": "Complete English editions for existing articles and public pages (#295)",
  "author": {
    "login": "nnneeeooo-nnneeeooo",
    "name": "",
    "email": ""
  },
  "committer": {
    "login": "web-flow",
    "name": "",
    "email": ""
  },
  "created_at": "2026-10-09T13:23:12Z",
  "files": [
    {
      "filename": ".github/workflows/hourly.yml",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 0,
      "changes": 1
    },
    {
      "filename": "config/english_labels.json",
      "previous_filename": "",
      "status": "added",
      "additions": 53,
      "deletions": 0,
      "changes": 53
    },
    {
      "filename": "data/articles/2026-08-07T11.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "data/articles/2026-08-10T17.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "data/articles/2026-08-11T15.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "data/articles/2026-08-18T10.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 2,
      "changes": 4
    },
    {
      "filename": "data/articles/2026-09-05T17.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "data/articles/manual-45737aa09878f61b1b9696d0ab536074.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 10,
      "deletions": 4,
      "changes": 14
    },
    {
      "filename": "data/articles/manual-starlux-b58310-20261001.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 24,
      "deletions": 8,
      "changes": 32
    },
    {
      "filename": "data/articles/manual-tigerair-b50031-20260930.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 13,
      "deletions": 4,
      "changes": 17
    },
    {
      "filename": "data/briefings/2026-07-31-morning.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 2,
      "changes": 4
    },
    {
      "filename": "data/briefings/2026-10-01-afternoon.json",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 2,
      "changes": 4
    },
    {
      "filename": "pipeline/build.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 44,
      "deletions": 9,
      "changes": 53
    },
    {
      "filename": "templates/404.html",
      "previous_filename": "",
      "status": "modified",
      "additions": 3,
      "deletions": 3,
      "changes": 6
    },
    {
      "filename": "templates/_briefing_report.html",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "tests/test_english_backfill.py",
      "previous_filename": "",
      "status": "added",
      "additions": 80,
      "deletions": 0,
      "changes": 80
    }
  ],
  "diff": "@@ -66,6 +66,7 @@ jobs:\\n           python tests/test_write_offline.py\\n           python tests/test_provider_health.py\\n           python tests/test_build_resilience.py\\n+          python tests/test_english_backfill.py\\n           python tests/test_news_freshness.py\\n           python tests/test_news_retry.py\\n           python tests/test_news_chronology.py\\n@@ -0,0 +1,53 @@\\n+{\\n+  \"海委會主委管碧玲\": \"Ocean Affairs Council Minister Kuan Pi-ling\",\\n+  \"阿聯酋航空A350抵達赫爾辛基機場。\": \"An Emirates A350 arrives at Helsinki Airport.\",\\n+  \"KLM第107號藍陶小屋與De Kleine Schorre酒莊建築。\": \"KLM’s 107th Delft Blue miniature house and the De Kleine Schorre winery building.\",\\n+  \"四軸無人機資料照片\": \"File photo of a quadcopter drone\",\\n+  \"中華航空30日宣布，隨著新世代機隊陸續引進與全球航網持續拓展，預計將招募合計逾百名客艙組員及貨運營運人員\": \"China Airlines announced on the 30th that it plans to recruit more than 100 cabin crew and cargo operations staff as it introduces new-generation aircraft and expands its global network.\",\\n+  \"台灣虎航董事長黃世惠\": \"Tigerair Taiwan Chairman Huang Shih-hui\",\\n+  \"長榮航空23日舉辦第6屆供應商大會，與國內外超過130名產業夥伴齊聚一堂，聚焦環境保護、經濟效益與社會責任\": \"EVA Air held its sixth supplier conference on the 23rd, bringing together more than 130 industry partners from Taiwan and abroad to focus on environmental protection, economic benefits and social responsibility.\",\\n+  \"華航總經理陳漢銘\": \"China Airlines President Hsien-Ming Chen\",\\n+  \"06.09 總統出席「台灣卓越無人機海外商機聯盟大會師」 - 55322836037\": \"06.09: The president attends a gathering of the Taiwan drone alliance for overseas business opportunities - 55322836037\",\\n+  \"星宇航空18日在英國倫敦舉行的2026年SKYTRAX全球航空公司大獎獲頒五星航空認證，連續兩年獲得航空業最高榮譽，並榮獲全球最整潔航空公司第一名\": \"STARLUX Airlines received a five-star airline rating at the 2026 SKYTRAX World Airline Awards in London on the 18th, earning the industry’s top rating for a second consecutive year and ranking first as the World’s Cleanest Airline.\",\\n+  \"佳世達集團18日舉辦AI藥局5G遠距照護計畫成果發表會，展示醫藥無人機，展現跨海醫療物流能量\": \"Qisda Group presented the results of its AI pharmacy and 5G remote care project on the 18th, demonstrating a medical delivery drone and its capability for medical logistics across the sea.\",\\n+  \"歐盟航空安全局（EASA）\": \"European Union Aviation Safety Agency (EASA)\",\\n+  \"華信航空首度攜手旅行社推出「台東－澎湖」直航包機，11日啟航，從台東直飛澎湖只要75分鐘，盼促進旅遊發展\": \"Mandarin Airlines partnered with travel agencies for the first time to launch a direct Taitung–Penghu charter service on the 11th. The 75-minute flight is intended to promote tourism.\",\\n+  \"華航名古屋航線亞運限定頭墊紙\": \"China Airlines’ Asian Games special-edition headrest cover on its Nagoya route\",\\n+  \"台灣虎航2日在高雄國際機場舉辦高雄-石垣島、高雄-小松雙航線開航慶祝儀式，2航線開航後，可大幅提升南部鄉親出國旅遊的便利性，對日籍旅客在行程規劃上也更加友善\": \"Tigerair Taiwan celebrated the launch of its Kaohsiung–Ishigaki and Kaohsiung–Komatsu routes at Kaohsiung International Airport on the 2nd. The services improve international travel options for residents of southern Taiwan and make itinerary planning easier for Japanese travelers.\",\\n+  \"Sofia Airport 資料照片\": \"File photo of Sofia Airport\",\\n+  \"Sydney Kingsford Smith Airport 資料照片\": \"File photo of Sydney Kingsford Smith Airport\",\\n+  \"星宇航空「AIRSORAYAMA GOLD」完成交機 B-58554自法國土魯斯啟程返台\": \"STARLUX Airlines’ AIRSORAYAMA GOLD aircraft, B-58554, departs Toulouse, France, for Taiwan after delivery\",\\n+  \"Catania airport 資料照片\": \"File photo of Catania Airport\",\\n+  \"星宇航空「空山金」準備啟程 A350-1000 B-58554將自土魯斯飛往新家臺北\": \"STARLUX Airlines’ AIRSORAYAMA GOLD A350-1000, B-58554, prepares to depart Toulouse for its new home in Taipei\",\\n+  \"Newark Liberty International Airport 資料照片\": \"File photo of Newark Liberty International Airport\",\\n+  \"臺北松山機場\": \"Taipei Songshan Airport\",\\n+  \"澳門航空 Airbus A321neo B-MBU\": \"Air Macau Airbus A321neo B-MBU\",\\n+  \"London-Gatwick Airport 資料照片\": \"File photo of London Gatwick Airport\",\\n+  \"Changi Airport 資料照片\": \"File photo of Changi Airport\",\\n+  \"交通部民用航空局\": \"Civil Aviation Administration, Taiwan\",\\n+  \"星宇航空首航台北－布拉格 正式開啟歐洲航線布局\": \"STARLUX Airlines launches its inaugural Taipei–Prague service, marking the start of its European network\",\\n+  \"國際民航組織（ICAO）總部\": \"International Civil Aviation Organization (ICAO) headquarters\",\\n+  \"長榮航空 EVA Air\": \"EVA Air\",\\n+  \"海南航空官方資訊\": \"Hainan Airlines official news\",\\n+  \"中央社 CNA\": \"Central News Agency (CNA)\",\\n+  \"中央社 CNA 產經證券\": \"CNA Business and Finance\",\\n+  \"中央社 CNA 國籍航空\": \"CNA Taiwan Airlines\",\\n+  \"國防部・軍聞社\": \"Military News Agency, Ministry of National Defense\",\\n+  \"鏡新聞\": \"Mirror News\",\\n+  \"中央社\": \"Central News Agency (CNA)\",\\n+  \"中央通訊社\": \"Central News Agency (CNA)\",\\n+  \"COMAC 官方新聞\": \"COMAC official news\",\\n+  \"ATR 官方新聞室\": \"ATR Newsroom\",\\n+  \"臺鐵公司\": \"Taiwan Railway Corporation\",\\n+  \"聯合新聞網\": \"United Daily News\",\\n+  \"TVBS新聞網\": \"TVBS News\",\\n+  \"海委會提供\": \"Courtesy of the Ocean Affairs Council\",\\n+  \"台灣虎航提供\": \"Courtesy of Tigerair Taiwan\",\\n+  \"華航提供\": \"Courtesy of China Airlines\",\\n+  \"中華航空提供\": \"Courtesy of China Airlines\",\\n+  \"華信航空提供\": \"Courtesy of Mandarin Airlines\",\\n+  \"佳世達提供\": \"Courtesy of Qisda\",\\n+  \"星宇航空提供\": \"Courtesy of STARLUX Airlines\",\\n+  \"長榮航空提供\": \"Courtesy of EVA Air\",\\n+  \"航空自衛隊\": \"Japan Air Self-Defense Force\"\\n+}\\n@@ -167,7 +167,7 @@\\n         \"summary\": \"Public ADS-B observation data shows an Airbus A320 registered as B-LPN, using callsign CRK261, confirmed arriving at Taiwan Taoyuan International Airport (TPE) on 7 August 2026.\",\\n         \"body\": [\\n           \"Public ADS-B observation data indicates that an Airbus A320 aircraft, registration B-LPN, was observed arriving at Taiwan Taoyuan International Airport (TPE) on 7 August 2026. The site's monitoring system recorded the first valid observation at 2026-08-07T09:37:44Z and confirmed arrival at 2026-08-07T11:22:16Z.\",\\n-          \"The observation data identifies the aircraft's ICAO hex code as 780a99, with callsign CRK261. The operator ICAO code is listed as CRK, while the operator name field shows \\\"未識別\\\" (unidentified). The aircraft ICAO type code is A320, with the display model name Airbus A320.\",\\n+          \"The observation data identifies the aircraft's ICAO hex code as 780a99, with callsign CRK261. The operator ICAO code is listed as CRK, while the operator name field shows \\\"unidentified\\\". The aircraft ICAO type code is A320, with the display model name Airbus A320.\",\\n           \"The arrival determination was marked as confirmed_arrival, with the confirmation basis stated as the aircraft being within the airport geofence and alt_baro reading as ground. The primary data source for this observation is Airplanes.live, with ADSB.lol listed as the secondary data source. Cross-confirmation status is recorded as secondary_unavailable.\",\\n           \"Regarding airport information, the observation record shows the airport ICAO code as RCTP, named Taiwan Taoyuan International Airport. The total number of valid observation entries for this event is 2.\",\\n           \"The statistics note indicates that the system's observation history is still accumulating, with no comparable long-term statistics available. The site's system therefore cannot provide frequency comparison data over a longer time window for this particular combination of aircraft and airport.\",\\n@@ -31,7 +31,7 @@\\n         \"title\": \"B-KWA Boeing 737 MAX 9 Arrives at Taiwan Taoyuan International Airport (TPE)\",\\n         \"summary\": \"Callsign HGB710; B-KWA arrives at RCTP; operator unidentified\",\\n         \"body\": [\\n-          \"The site's monitoring system observed an aircraft with registration B-KWA completing an arrival at Taiwan Taoyuan International Airport. The airport ICAO code is RCTP and the airport name is 臺灣桃園國際機場. The arrival determination is confirmed_arrival, with confirmation basis being located within the airport geofence and alt_baro reading ground.\",\\n+          \"The site's monitoring system observed an aircraft with registration B-KWA completing an arrival at Taiwan Taoyuan International Airport. The airport ICAO code is RCTP and the airport name is Taiwan Taoyuan International Airport. The arrival determination is confirmed_arrival, with confirmation basis being located within the airport geofence and alt_baro reading ground.\",\\n           \"The aircraft ICAO hex is 789298, registration is B-KWA, and callsign is HGB710. The aircraft ICAO type is B39M, with the display model name Boeing 737 MAX 9. The operator ICAO is HGB, but operator IATA, operator name, operator Chinese name, and operator country or region are all listed as unidentified.\",\\n           \"The first valid observation time is 2026-08-10T15:40:11Z and the confirmed arrival time is 2026-08-10T16:28:54Z. The number of valid observation records is 2. The primary data source is Airplanes.live, the secondary data source is ADSB.lol, and the cross-confirmation status is secondary_unavailable.\",\\n           \"The event ID is fw-fe91c683d7ad4cbc and the data type is public ADS-B observation. The statistical note states that the system's observation history is still accumulating and there are no comparable long-term statistics available. Because all operator fields are unidentified, the site cannot confirm the operating purpose, route, or airline for this flight and can only record the arrival of this aircraft at Taoyuan Airport based on the observation data itself.\",\\n@@ -97,7 +97,7 @@\\n         \"title\": \"Air China Airbus A321 B-6605 Observed Arriving at Taiwan Taoyuan Int'l Airport\",\\n         \"summary\": \"Air China A321 B-6605; Taoyuan Airport observed arrival; 2026-08-11\",\\n         \"body\": [\\n-          \"The site's monitoring system observed an Airbus A321 aircraft registered B-6605, operated by Air China, arriving at Taiwan Taoyuan International Airport (TPE). Public ADS-B observation data records the aircraft's ICAO hex as 7805c4, callsign as CCA190, operator ICAO as CCA, operator IATA as CA, operator name as Air China, operator Chinese name as 中國國際航空, and the operator's country or region as China.\",\\n+          \"The site's monitoring system observed an Airbus A321 aircraft registered B-6605, operated by Air China, arriving at Taiwan Taoyuan International Airport (TPE). Public ADS-B observation data records the aircraft's ICAO hex as 7805c4, callsign as CCA190, operator ICAO as CCA, operator IATA as CA, operator name as Air China, the Chinese-language operator name as Air China, and the operator's country or region as China.\",\\n           \"The observation states that the primary data source is Airplanes.live, the secondary data source is ADSB.lol, and the cross-confirmation status is confirmed_by_primary_only. The first valid observation time is 2026-08-11T12:32:14Z, the confirmed arrival time is 2026-08-11T13:57:46Z, the arrival determination is confirmed_arrival, the confirmation basis is that the aircraft was within the airport geofence and alt_baro indicated ground, and the number of valid observation records is 2.\",\\n           \"Regarding airport information, the observation data records the airport ICAO as RCTP and the airport name as Taiwan Taoyuan International Airport. The aircraft ICAO type is A321, and the model display name is Airbus A321. The event ID is fw-e525367c412188aa, and the data type is public ADS-B observation.\",\\n           \"The statistics note states that the system's observation history is still accumulating and there is no comparable long-term statistical data available. Therefore, the site cannot provide a long-term comparison baseline for this observation, nor does it make any judgment on the rarity of this combination. This report only presents the facts recorded by the observation system and does not speculate on the flight's origin, destination, purpose, or any operational plan.\",\\n@@ -204,8 +204,8 @@\\n         \"summary\": \"Military sources state the first F-16V blk70 fighter jets will fly to Taiwan in the near future.\",\\n         \"body\": [\\n           \"The first batch of F-16V (blk70) fighter jets purchased by the Taiwan Air Force from the United States is scheduled to fly back to Taiwan in the near future and land at Zhihang Air Base in Taitung. According to military sources speaking on August 18, 2026, while preparations are underway, the exact routing details and final arrival schedule are still to be determined by the United States, and the specific timeline has not yet been confirmed.\",\\n-          \"This procurement program, code-named Project \\\"Feng Xiang\\\" (鳳翔專案), involves the acquisition of 66 brand-new F-16V (blk70) fighter jets. The air force allocated a continuous budget of NT$247,228,830,000 spanning from the year 109 to 115 (2020 to 2026) for the project. Although the original plan called for \\\"all\\\" 66 jets to fly back to Taiwan within this year, the schedule experienced slight delays due to complex factors including assembly line relocation, plant restarts, and supply chain disruptions. In response, the US side has implemented a two-shift, 20-hour daily work schedule to accelerate production.\",\\n-          \"This project is distinct from Project \\\"Feng Zhan\\\" (鳳展專案), under which the Air Force partnered with US manufacturer Lockheed Martin and Hanxiang to upgrade 140 existing F-16A/B fighter jets to the F-16V (blk20) configuration. This upgrade program was fully completed in December 112 (December 2023), successfully enhancing the capabilities of the existing fleet.\",\\n+          \"This procurement program, code-named Project \\\"Feng Xiang\\\", involves the acquisition of 66 brand-new F-16V (blk70) fighter jets. The air force allocated a continuous budget of NT$247,228,830,000 spanning from the year 109 to 115 (2020 to 2026) for the project. Although the original plan called for \\\"all\\\" 66 jets to fly back to Taiwan within this year, the schedule experienced slight delays due to complex factors including assembly line relocation, plant restarts, and supply chain disruptions. In response, the US side has implemented a two-shift, 20-hour daily work schedule to accelerate production.\",\\n+          \"This project is distinct from Project \\\"Feng Zhan\\\", under which the Air Force partnered with US manufacturer Lockheed Martin and Hanxiang to upgrade 140 existing F-16A/B fighter jets to the F-16V (blk20) configuration. This upgrade program was fully completed in December 112 (December 2023), successfully enhancing the capabilities of the existing fleet.\",\\n           \"According to previous briefings by the Air Force, the new F-16V (blk70) features greater engine thrust, an optimized airframe structure, and an extended operational range through the use of conformal fuel tanks. Additionally, the aircraft offers beyond-visual-range combat capabilities and partial stealth characteristics. It is also equipped with an Active Electronically Scanned Array (AESA) radar, which integrates multiple capabilities including air search, tracking, weapon control, electronic warfare, and communications.\"\\n         ]\\n       },\\n@@ -18,7 +18,7 @@\\n         \"title\": \"Air Canada Launches New A321XLR Heathrow Route With A Twist\",\\n         \"summary\": \"Air Canada starts new A321XLR London Heathrow route; early departures arrive late evening with Toronto US preclearance connections.\",\\n         \"body\": [\\n-          \"Simple Flying reports that Air Canada (加拿大航空) is launching a new route to London Heathrow Airport (LHR) operated by the Airbus A321XLR, with an approximate flight time of 8 hours. A twist to the schedule involves early morning departures that arrive in London late in the evening. Connections via the Toronto hub reportedly offer seamless US preclearance for passengers.\",\\n+          \"Simple Flying reports that Air Canada is launching a new route to London Heathrow Airport (LHR) operated by the Airbus A321XLR, with an approximate flight time of 8 hours. A twist to the schedule involves early morning departures that arrive in London late in the evening. Connections via the Toronto hub reportedly offer seamless US preclearance for passengers.\",\\n           \"At this time, specific launch dates and frequencies have not been disclosed by the publication. The report, dated September 5, 2026, provides an overview of the innovative scheduling and its stated benefits for travelers connecting onward to the United States.\"\\n         ]\\n       },\\n@@ -22,9 +22,14 @@\\n         ]\\n       },\\n       \"en\": {\\n-        \"title\": \"\",\\n-        \"summary\": \"\",\\n-        \"body\": []\\n+        \"title\": \"China Airlines Group donates NT$10 million and offers free relief cargo transport after Kumamoto earthquake\",\\n+        \"summary\": \"A recent magnitude 7.1 earthquake in Japan’s Kumamoto Prefecture damaged buildings, roads and transport infrastructure. As rescue and reconstruction efforts continue, China Airlines announced that its group will donate NT$10 million to support recovery in the affected area.\",\\n+        \"body\": [\\n+          \"A recent magnitude 7.1 earthquake in Japan’s Kumamoto Prefecture damaged buildings, roads and transport infrastructure. As rescue and reconstruction efforts continue, China Airlines announced that its group will donate NT$10 million to support recovery in the affected area.\",\\n+          \"In addition to financial assistance, China Airlines will provide air transport resources. From now until the end of August, it will carry relief supplies to the disaster area free of charge for government agencies, charities, religious groups and other relief organizations, helping urgently needed supplies reach local communities more quickly.\",\\n+          \"China Airlines said Kumamoto is one of the destinations served by the group. Guided by corporate social responsibility and humanitarian concern, it hopes its donations and transport support will convey the care of people in Taiwan and help affected residents rebuild their homes.\",\\n+          \"An earthquake struck the Kumamoto area of Kumamoto Prefecture on the afternoon of July 28, with a magnitude of 7.1. Uki City and Hikawa Town recorded a maximum seismic intensity of 7, and the Japanese government temporarily issued tsunami advisories for the Ariake Sea and Yatsushiro Sea. The affected area continues to face challenges including damaged buildings, blocked roads and bridges, and repairs to transport infrastructure and public facilities.\"\\n+        ]\\n       },\\n       \"sources\": [\\n         {\\n@@ -51,7 +56,8 @@\\n       \"writer\": \"manual:GPT 5.6 Terra Thinking\",\\n       \"articleFormat\": \"full\",\\n       \"availableLanguages\": [\\n-        \"zh\"\\n+        \"zh\",\\n+        \"en\"\\n       ],\\n       \"manualArticleType\": \"press_release\",\\n       \"attachments\": [\\n@@ -30,9 +30,15 @@\\n         ]\\n       },\\n       \"en\": {\\n-        \"title\": \"\",\\n-        \"summary\": \"\",\\n-        \"body\": []\\n+        \"title\": \"STARLUX Airlines takes delivery of its tenth A330-900, B-58310\",\\n+        \"summary\": \"STARLUX Airlines took delivery of its tenth A330-900, B-58310, on October 1, 2026. The aircraft has manufacturer serial number 2135 and a 297-seat cabin with 28 business class and 269 economy class seats.\",\\n+        \"body\": [\\n+          \"STARLUX Airlines has added another Airbus A330-900 to its fleet. Aircraft records show that the new aircraft, registered B-58310, was delivered on October 1, 2026, becoming the airline’s tenth A330-900. Its manufacturer serial number (MSN) is 2135, and it used the French test registration F-WWYL during production and testing at Airbus. Planespotters.net also included B-58310 in its list of aircraft delivered on October 1.\",\\n+          \"B-58310 was manufactured at the Airbus production line in Toulouse, France. Aircraft movement records before delivery show that it was parked at the Airbus Delivery Centre at Toulouse-Blagnac Airport (TLS) in mid-September, preparing for the formal handover. Production records also identify the aircraft as an A330-941 with MSN 2135.\",\\n+          \"According to currently available public fleet records, B-58310 has the same 297-seat cabin configuration as the airline’s existing A330neos, with 28 business class and 269 economy class seats, and is powered by two Rolls-Royce engines. STARLUX’s official information states that its A330neo fleet uses Rolls-Royce Trent 7000-series engines and features fully flat business class seats and individual inflight entertainment systems.\",\\n+          \"Since taking delivery of its first A330neo in 2022, STARLUX has gradually deployed this widebody type on regional routes across Asia. The A330-900 is part of the Airbus A330neo family and combines Trent 7000 engines with a new aerodynamic design. Its intended role spans regional and long-haul services, giving airlines flexibility in capacity and range planning.\",\\n+          \"The delivery of B-58310 brings the STARLUX A330-900 fleet to ten aircraft. The expansion from B-58301 through B-58310 keeps the A330neo an important part of the airline’s widebody fleet, supporting the development of its regional and long-haul network alongside the A350 family.\"\\n+        ]\\n       },\\n       \"sources\": [\\n         {\\n@@ -66,7 +72,8 @@\\n       ],\\n       \"articleFormat\": \"full\",\\n       \"availableLanguages\": [\\n-        \"zh\"\\n+        \"zh\",\\n+        \"en\"\\n       ],\\n       \"manualArticleType\": \"fleet_order\",\\n       \"attachments\": [],\\n@@ -110,9 +117,17 @@\\n         ]\\n       },\\n       \"en\": {\\n-        \"title\": \"\",\\n-        \"summary\": \"\",\\n-        \"body\": []\\n+        \"title\": \"KLM’s first Airbus A350-900 arrives in Amsterdam as long-haul fleet renewal advances\",\\n+        \"summary\": \"KLM’s first Airbus A350-900, PH-ZNA, arrived at Amsterdam Schiphol Airport on October 2, 2026. The aircraft will support the airline’s long-haul fleet renewal and is expected to enter commercial passenger service in late October.\",\\n+        \"body\": [\\n+          \"KLM’s first Airbus A350-900 arrived at Amsterdam Schiphol Airport (AMS) on October 2, 2026, formally joining the airline’s fleet. Registered PH-ZNA, the aircraft departed Toulouse, France, on its delivery flight and landed at Schiphol at approximately 4:10 p.m. local time, marking an important milestone in KLM’s introduction of the A350 family.\",\\n+          \"PH-ZNA is named after The Night Watch (De Nachtwacht), the famous painting by Dutch artist Rembrandt. KLM previously announced that its A350 fleet would be named after well-known Dutch works of art. The first aircraft carries the name The Night Watch, and subsequent deliveries will follow the same theme.\",\\n+          \"The aircraft departed the Airbus delivery centre at Toulouse-Blagnac Airport (TLS) using the commemorative delivery flight number KL350. On arrival at Schiphol, it landed on the Buitenveldertbaan runway before being escorted by airport vehicles to the KLM hangar.\",\\n+          \"KLM’s A350-900 has a three-class cabin with 331 seats: 34 in World Business Class, 26 in Premium Comfort and 271 in economy class. Air France-KLM said the A350 forms part of the group’s ongoing renewal of its long-haul fleet. Compared with older-generation widebody aircraft currently operated by KLM, the new type can reduce fuel consumption and noise. KLM said that, compared with the Boeing 777-200ER, the A350 can use approximately 25% less fuel and reduce its noise footprint by around 40%.\",\\n+          \"KLM plans to use the A350 to gradually renew its long-haul widebody fleet, initially replacing the Airbus A330-200 and A330-300 and later some Boeing 777-200ERs. The airline has planned to introduce 22 A350-900s, drawn from the Air France-KLM group’s A350 orders with Airbus.\",\\n+          \"The first A350 is expected to enter commercial passenger service in late October, initially flying between Amsterdam and Toronto Pearson International Airport (YYZ). KLM also plans to deploy the A350 on long-haul routes to Montréal-Trudeau International Airport (YUL), Kilimanjaro International Airport (JRO), Julius Nyerere International Airport in Dar es Salaam (DAR), Jomo Kenyatta International Airport in Nairobi (NBO), and Abeid Amani Karume International Airport in Zanzibar (ZNZ).\",\\n+          \"With PH-ZNA’s arrival in the Netherlands, KLM formally becomes an Airbus A350 operator. Air France, its fellow group airline, has operated the A350 since 2019. KLM’s introduction of the same type further expands the A350’s role in Air France-KLM’s long-haul fleet renewal and fleet commonality.\"\\n+        ]\\n       },\\n       \"sources\": [\\n         {\\n@@ -142,7 +157,8 @@\\n       ],\\n       \"articleFormat\": \"full\",\\n       \"availableLanguages\": [\\n-        \"zh\"\\n+        \"zh\",\\n+        \"en\"\\n       ],\\n       \"manualArticleType\": \"fleet_order\",\\n       \"attachments\": [],\\n@@ -33,9 +33,17 @@\\n         ]\\n       },\\n       \"en\": {\\n-        \"title\": \"\",\\n-        \"summary\": \"\",\\n-        \"body\": []\\n+        \"title\": \"Tigerair Taiwan takes delivery of new A320neo B-50031 and begins ferry flight from France\",\\n+        \"summary\": \"Tigerair Taiwan’s new A320-271N, B-50031, was delivered on September 30 and departed Toulouse, France, on a ferry flight to Taiwan via Riyadh and Bangkok Don Mueang.\",\\n+        \"body\": [\\n+          \"Tigerair Taiwan has added another new Airbus A320neo. Aircraft records show that the Airbus A320-271N registered B-50031 completed the delivery process on September 30 and departed Toulouse-Blagnac Airport (TLS), France, on its delivery ferry flight to Taiwan.\",\\n+          \"B-50031 has manufacturer serial number (MSN) 13167 and used the factory test registration F-WWDG. It was built at the Airbus production line in Toulouse. The aircraft completed its first test flight on September 15, followed by further tests on September 16 and 24. The aircraft database currently lists it as delivered to Tigerair Taiwan and in active service.\",\\n+          \"Delivery ferry flight records show that B-50031 departed Toulouse at 10:50 a.m. local time on September 30 using the Tigerair Taiwan callsign TTW31. Its route passed through King Khalid International Airport in Riyadh, Saudi Arabia (RUH), and Don Mueang International Airport in Bangkok, Thailand (DMK), before continuing to Taiwan Taoyuan International Airport (TPE). Such multi-leg arrangements allow single-aisle aircraft to refuel and provide crew rest during long delivery journeys.\",\\n+          \"B-50031 is an A320-271N, part of the A320neo family, powered by two Pratt & Whitney PW1100G-series geared turbofan engines. Tigerair Taiwan uses a 180-seat, all-economy configuration.\",\\n+          \"B-50031 is also part of Tigerair Taiwan’s new batch of purchased A320neos. In July this year, the airline announced that this batch would introduce the latest Airbus Airspace cabin design. Features include redesigned ceilings and sidewalls, LED mood lighting, Recaro R2 Swift seats, USB Type-A and Type-C charging ports, and holders for passengers’ smartphones or tablets.\",\\n+          \"Tigerair Taiwan had already received another A320-271N, registered B-50032, in July this year. Aircraft records show that B-50032 began its delivery ferry flight from Toulouse on July 8 and entered commercial service on July 17. The delivery of B-50031 further expands the airline’s A320neo fleet.\",\\n+          \"Tigerair Taiwan has continued to replace and expand its narrowbody fleet with A320neos in recent years. Compared with the previous-generation A320ceo, the A320neo’s newer engines and aerodynamic design improve fuel efficiency and range, making it suitable for the airline’s regional network focused on Japan, South Korea and Southeast Asia.\"\\n+        ]\\n       },\\n       \"sources\": [\\n         {\\n@@ -61,7 +69,8 @@\\n       ],\\n       \"articleFormat\": \"full\",\\n       \"availableLanguages\": [\\n-        \"zh\"\\n+        \"zh\",\\n+        \"en\"\\n       ],\\n       \"manualArticleType\": \"fleet_order\",\\n       \"attachments\": [],\\n@@ -22,8 +22,8 @@\\n         \"category\": \"taiwan_aviation\",\\n         \"headline\": \"熊本強震後啟動航空援助 華航集團捐新台幣1,000萬元並免費運送賑災物資\",\\n         \"summary\": \"日本熊本縣日前發生規模7.1強震，造成當地建築、道路及交通設施受損，災後救援與重建工作持續進行。中華航空宣布，華航集團將捐出新台幣1,000萬元，支援熊本地區災後救援及重建工作。\",\\n-        \"headline_en\": \"\",\\n-        \"summary_en\": \"\",\\n+        \"headline_en\": \"China Airlines Group donates NT$10 million and offers free relief cargo transport after Kumamoto earthquake\",\\n+        \"summary_en\": \"A recent magnitude 7.1 earthquake in Japan’s Kumamoto Prefecture damaged buildings, roads and transport infrastructure. As rescue and reconstruction efforts continue, China Airlines announced that its group will donate NT$10 million to support recovery in the affected area.\",\\n         \"event_time\": null,\\n         \"source_published_at\": \"2026-07-30T18:56:28.601000+00:00\",\\n         \"source_updated_at\": null,\\n@@ -96,8 +96,8 @@\\n         \"category\": \"taiwan_aviation\",\\n         \"headline\": \"台灣虎航接收全新A320neo客機 B-50031　自法國展開交機飛渡\",\\n         \"summary\": \"台灣虎航全新A320-271N客機B-50031於9月30日完成交付，自法國土魯斯啟程，經利雅德及曼谷廊曼飛渡返台。\",\\n-        \"headline_en\": \"\",\\n-        \"summary_en\": \"\",\\n+        \"headline_en\": \"Tigerair Taiwan takes delivery of new A320neo B-50031 and begins ferry flight from France\",\\n+        \"summary_en\": \"Tigerair Taiwan’s new A320-271N, B-50031, was delivered on September 30 and departed Toulouse, France, on a ferry flight to Taiwan via Riyadh and Bangkok Don Mueang.\",\\n         \"event_time\": null,\\n         \"source_published_at\": \"2026-09-30T08:50:00+00:00\",\\n         \"source_updated_at\": null,\\n@@ -1446,16 +1446,41 @@ def notification_summary(value: str, lang: str, title: str = \"\") -> str:\\n     return _keyword_summary(text, lang, limit_words)\\n \\n \\n+_ENGLISH_LABELS = load_json(\\n+    Path(__file__).resolve().parent.parent / \"config\" / \"english_labels.json\", {})\\n+\\n+\\n+def localized_label(value, lang: str):\\n+    if lang != \"en\" or not isinstance(value, str):\\n+        return value\\n+    if value in _ENGLISH_LABELS:\\n+        return _ENGLISH_LABELS[value]\\n+    match = re.fullmatch(r\"註冊號\\s+([A-Z0-9-]+)\", value)\\n+    return f\"Registration {match[1]}\" if match else value\\n+\\n+\\n+def localized_image(image, lang: str):\\n+    if not isinstance(image, dict):\\n+        return None\\n+    if lang != \"en\":\\n+        return image\\n+    return {**image, **{key: localized_label(image.get(key), lang)\\n+                       for key in (\"subject\", \"credit\", \"provider\")}}\\n+\\n+\\n def art_view(a, lang: str):\\n     v = dict(a[lang])\\n     v[\"display_summary\"] = notification_summary(\\n         v.get(\"summary\"), lang, v.get(\"title\"))\\n     v.update(\\n-        id=a[\"id\"], cat=a[\"cat\"], tag_class=a[\"tag_class\"], image=a[\"image\"],\\n+        id=a[\"id\"], cat=a[\"cat\"], tag_class=a[\"tag_class\"],\\n+        image=localized_image(a[\"image\"], lang),\\n         cat_label=_bi(CATS.get(a[\"cat\"]), lang, a[\"cat\"]),\\n-        source=a[\"source\"], time=a[\"time\"], meta_ts=a[\"meta_ts\"],\\n+        source=localized_label(a[\"source\"], lang), time=a[\"time\"], meta_ts=a[\"meta_ts\"],\\n         source_meta_ts=a[\"source_meta_ts\"],\\n-        sources=a[\"sources\"], url=page_url(lang, f\"news/{a['id']}/\"),\\n+        sources=[{**source, \"name\": localized_label(source[\"name\"], lang)}\\n+                 for source in a[\"sources\"]],\\n+        url=page_url(lang, f\"news/{a['id']}/\"),\\n         writer_model=a[\"writer_model\"],\\n         article_format=a[\"article_format\"],\\n         published_iso=a[\"published_iso\"],\\n@@ -1694,6 +1719,7 @@ def _hero_image_caption(image, lang: str) -> str:\\n             else \"No verified event or file photo is available yet; \"\\n                  \"showing the SKYTICAL default image.\"\\n         )\\n+    image = localized_image(image, lang)\\n     parts = []\\n     if image.get(\"subject\"):\\n         parts.append(str(image[\"subject\"]))\\n@@ -2230,14 +2256,16 @@ def merged_sources(raw):\\n     return out\\n \\n \\n-def source_status_view(sources, now):\\n-    return [{\"name\": s[\"name\"], \"ok\": s[\"ok\"], \"ago\": ago(s[\"lastFetchUtc\"], now)}\\n+def source_status_view(sources, now, lang=\"zh\"):\\n+    return [{\"name\": localized_label(s[\"name\"], lang), \"ok\": s[\"ok\"],\\n+             \"ago\": ago(s[\"lastFetchUtc\"], now)}\\n             for s in sources]\\n \\n \\n def sources_rows_view(sources, lang: str, now):\\n     return [{\\n-        \"name\": s[\"name\"], \"url\": s[\"url\"], \"fmt\": s[\"fmt\"], \"ok\": s[\"ok\"],\\n+        \"name\": localized_label(s[\"name\"], lang),\\n+        \"url\": s[\"url\"], \"fmt\": s[\"fmt\"], \"ok\": s[\"ok\"],\\n         \"kind\": _bi(KIND.get(s[\"kind\"]), lang, s[\"kind\"]),\\n         \"cover\": _bi(s[\"cover\"], lang),\\n         \"last\": ago(s[\"lastFetchUtc\"], now),\\n@@ -2351,7 +2379,7 @@ def brief_item_view(item, lang: str, published_ids, article_lookup=None):\\n     url = (page_url(lang, f\"news/{art_id}/\")\\n            if art_id in published_ids else None)\\n     article = (article_lookup or {}).get(art_id) or {}\\n-    image = article.get(\"image\") if isinstance(article.get(\"image\"), dict) else {}\\n+    image = localized_image(article.get(\"image\"), lang) or {}\\n     # Image metadata is resolved from the current prepared article on every\\n     # build; historical digest JSON never freezes a missing or stale image.\\n     fallback_image = f\"{BASE_PATH}/assets/skytical-social.png?v={ASSET_VERSION}\"\\n@@ -3872,7 +3900,7 @@ def main() -> int:\\n         ctx.update(hero=hero, hero_candidates=hero_candidates,\\n                    hero_rotation_seconds=HERO_ROTATION_SECONDS,\\n                    feed=feed, flashes=fl, agg=agg,\\n-                   source_status=source_status_view(sources, now),\\n+                   source_status=source_status_view(sources, now, lang),\\n                    stats=sv[\"tiles\"], otp=sv[\"otp\"], fleet=sv[\"fleet\"],\\n                    delays=sv[\"delays\"], stats_note=sv[\"note\"],\\n                    cat_seg=list(zip(cat_keys, t[\"cats\"])),\\n@@ -4165,13 +4193,20 @@ def main() -> int:\\n     pages += render_manual_workbench(env, build)\\n \\n     # 404 (zh chrome, bilingual body), served by GitHub Pages from the root\\n-    ctx = base_ctx(\"zh\", \"none\", \"\", title=f\"{L['zh']['siteName']} — 404\",\\n+    ctx = base_ctx(\"zh\", \"none\", \"404.html\", title=f\"{L['zh']['siteName']} — 404\",\\n                    description=L[\"zh\"][\"siteDesc\"], ticker=\"\", build=build,\\n                    hreflang=False)\\n     ctx[\"robots_meta\"] = \"noindex,follow\"\\n     render(env, \"404.html\", \"404.html\", ctx)\\n     pages += 1\\n \\n+    ctx = base_ctx(\"en\", \"none\", \"404.html\", title=f\"{L['en']['siteName']} — 404\",\\n+                   description=L[\"en\"][\"siteDesc\"], ticker=\"\", build=build,\\n+                   hreflang=False)\\n+    ctx[\"robots_meta\"] = \"noindex,follow\"\\n+    render(env, \"404.html\", \"en/404.html\", ctx)\\n+    pages += 1\\n+\\n     write_search_discovery_files(articles, briefings, now)\\n \\n     catastrophic = render_failures_are_catastrophic(\\n@@ -2,8 +2,8 @@\\n {% block content %}\\n   <div style=\"max-width:860px;margin:0 auto;padding:64px 20px 96px;\">\\n     <span class=\"card-kicker\">404</span>\\n-    <h2 style=\"margin:8px 0 12px;\">找不到頁面 / Page not found</h2>\\n-    <p class=\"text-muted\" style=\"font-size:14.5px;line-height:1.9;\">這個網址不存在或已被移除。The address does not exist or has been removed.</p>\\n-    <a href=\"{{ home_url }}\" class=\"btn btn-ghost\" style=\"margin-top:12px;\">← 返回首頁 / Front page</a>\\n+    <h2 style=\"margin:8px 0 12px;\">{% if lang == 'en' %}Page not found{% else %}找不到頁面 / Page not found{% endif %}</h2>\\n+    <p class=\"text-muted\" style=\"font-size:14.5px;line-height:1.9;\">{% if lang != 'en' %}這個網址不存在或已被移除。{% endif %}The address does not exist or has been removed.</p>\\n+    <a href=\"{{ home_url }}\" class=\"btn btn-ghost\" style=\"margin-top:12px;\">← {% if lang == 'en' %}Front page{% else %}返回首頁 / Front page{% endif %}</a>\\n   </div>\\n {% endblock %}\\n@@ -22,7 +22,7 @@ <h3>{{ sec.label }}</h3>\\n         </a>\\n         <div class=\"briefing-story-copy\">\\n           <p class=\"briefing-story-text\"><strong>{{ it.headline }}</strong>：{{ it.summary }}\\n-            <a class=\"briefing-citation\" href=\"{{ it.url }}\" aria-label=\"閱讀本站原文：{{ it.headline }}\"\\n+            <a class=\"briefing-citation\" href=\"{{ it.url }}\" aria-label=\"{% if lang == 'en' %}Read the article: {% else %}閱讀本站原文：{% endif %}{{ it.headline }}\"\\n                title=\"{{ it.headline }}\">\\n               <img src=\"{{ base }}/assets/skytical-mark.svg\" alt=\"\" width=\"16\" height=\"16\" aria-hidden=\"true\">\\n               <span>{{ it.keyword or it.headline }}</span>\\n@@ -0,0 +1,80 @@\\n+\"\"\"Guard translated publication routes, image provenance and language chrome.\"\"\"\\n+import copy\\n+import json\\n+from pathlib import Path\\n+import re\\n+import sys\\n+import unittest\\n+\\n+from jinja2 import Environment, FileSystemLoader\\n+\\n+ROOT = Path(__file__).resolve().parents[1]\\n+sys.path.insert(0, str(ROOT / \"pipeline\"))\\n+import build\\n+\\n+\\n+class EnglishBackfillTests(unittest.TestCase):\\n+    def test_previously_chinese_only_articles_are_complete(self):\\n+        expected = {\\n+            \"manual-45737aa09878f61b1b9696d0ab536074.json\": {\\n+                \"a-20260730-1856-manual-45737a\": [\"NT$10 million\", \"7.1\"]},\\n+            \"manual-tigerair-b50031-20260930.json\": {\\n+                \"a-20260930-0850-manual-tigerair-b50031\": [\"13167\", \"F-WWDG\", \"TTW31\", \"180\"]},\\n+            \"manual-starlux-b58310-20261001.json\": {\\n+                \"a-20261001-manual-starlux-b58310\": [\"2135\", \"F-WWYL\", \"297\", \"269\"],\\n+                \"a-20261004-1221-manual-klm-a350-ph-zna\": [\"PH-ZNA\", \"331\", \"34\", \"26\", \"271\", \"25%\", \"40%\"]},\\n+        }\\n+        for filename, ids in expected.items():\\n+            rows = json.loads((ROOT / \"data/articles\" / filename).read_text(encoding=\"utf-8\"))[\"articles\"]\\n+            for article_id, facts in ids.items():\\n+                article = next(row for row in rows if row[\"id\"] == article_id)\\n+                english = article[\"en\"]\\n+                self.assertEqual(article[\"availableLanguages\"], [\"zh\", \"en\"])\\n+                self.assertTrue(english[\"title\"] and english[\"summary\"])\\n+                self.assertEqual(len(english[\"body\"]), len(article[\"zh\"][\"body\"]))\\n+                text = \" \".join([english[\"title\"], english[\"summary\"], *english[\"body\"]])\\n+                self.assertIsNone(re.search(r\"[\\u3400-\\u9fff]\", text))\\n+                for fact in facts:\\n+                    self.assertIn(fact, text)\\n+                prepared = build.prep_article(article)\\n+                self.assertIn(\"en\", prepared[\"available_languages\"])\\n+                self.assertEqual(build.art_view(prepared, \"en\")[\"title\"], english[\"title\"])\\n+\\n+    def test_caption_translation_preserves_provenance_and_chinese(self):\\n+        image = {\"subject\": \"臺北松山機場\", \"credit\": \"長榮航空提供\",\\n+                 \"url\": \"https://example.com/photo.jpg\", \"link\": \"https://example.com/source\",\\n+                 \"license\": \"CC BY 4.0\", \"kind\": \"file_photo\", \"provider\": \"Example\"}\\n+        original = copy.deepcopy(image)\\n+        translated = build.localized_image(image, \"en\")\\n+        self.assertEqual(translated[\"subject\"], \"Taipei Songshan Airport\")\\n+        self.assertEqual(translated[\"credit\"], \"Courtesy of EVA Air\")\\n+        self.assertEqual(image, original)\\n+        self.assertEqual(build.localized_image(image, \"zh\"), original)\\n+        for key in (\"url\", \"link\", \"license\", \"kind\", \"provider\"):\\n+            self.assertEqual(translated[key], original[key])\\n+        self.assertEqual(build.localized_label(\"註冊號 B-17812\", \"en\"), \"Registration B-17812\")\\n+        self.assertEqual(build.localized_label(\"Unknown photographer\", \"en\"), \"Unknown photographer\")\\n+        self.assertEqual(build.localized_label(\"玄史生\", \"en\"), \"玄史生\")\\n+        self.assertIsNone(build.localized_image(None, \"en\"))\\n+\\n+    def test_english_error_page_and_briefing_accessibility(self):\\n+        env = Environment(loader=FileSystemLoader(str(ROOT / \"templates\")), autoescape=True)\\n+        template = env.get_template(\"404.html\")\\n+        english = template.blocks[\"content\"](template.new_context({\"lang\": \"en\", \"home_url\": \"/en/\"}))\\n+        text = \"\".join(english)\\n+        self.assertIn(\"Page not found\", text)\\n+        self.assertIn('href=\"/en/\"', text)\\n+        self.assertIsNone(re.search(r\"[\\u3400-\\u9fff]\", text))\\n+        report = env.get_template(\"_briefing_report.html\").render(\\n+            lang=\"en\", base=\"\", fallback_image_url=\"/fallback.png\",\\n+            t=build.L[\"en\"], b={\"date_iso\": \"2026-10-01\", \"title\": \"Afternoon Briefing\",\\n+                \"window_start\": \"Sep 30\", \"window_end\": \"Oct 1\", \"cutoff\": \"3 PM\",\\n+                \"total\": 1, \"partial\": False, \"sections\": [{\"label\": \"Taiwan Aviation\", \"items\": [{\\n+                    \"headline\": \"Delivery flight\", \"summary\": \"The aircraft departed Toulouse.\",\\n+                    \"url\": \"/en/news/test/\", \"keyword\": \"Delivery\"}]}]})\\n+        self.assertIn('aria-label=\"Read the article: Delivery flight\"', report)\\n+        self.assertNotIn(\"閱讀本站原文\", report)\\n+\\n+\\n+if __name__ == \"__main__\":\\n+    unittest.main()"
}
