Spaces:
Sleeping
Sleeping
| // ============================================================================= | |
| // populate_vi_remaining.js | |
| // | |
| // Purpose : Seed Vietnamese (*Vi) fields for collections not covered by | |
| // populate_vi.js (which handled: locations, activity, travelguides). | |
| // Touches : hotel, room, promotions, advertisement, visa_articles | |
| // Run with : mongosh <connection-string> --quiet --eval "load('scripts/populate_vi_remaining.js')" | |
| // or inside a mongosh shell: load('scripts/populate_vi_remaining.js') | |
| // | |
| // Idempotent: Each section skips documents that already have a non-null Vi field. | |
| // Ja / Ko : Left empty intentionally — the frontend useLocalizedField hook | |
| // falls back to English, which is acceptable at this stage. | |
| // ============================================================================= | |
| // --------------------------------------------------------------------------- | |
| // Shared lookup tables (copied from populate_vi.js — do not import) | |
| // --------------------------------------------------------------------------- | |
| const cityMap = { | |
| 'Hanoi': 'Hà Nội', 'Ho Chi Minh City': 'TP. Hồ Chí Minh', 'Da Nang': 'Đà Nẵng', | |
| 'Hai Phong': 'Hải Phòng', 'Can Tho': 'Cần Thơ', 'Hoi An': 'Hội An', | |
| 'Nha Trang': 'Nha Trang', 'Phu Quoc': 'Phú Quốc', 'Hue': 'Huế', | |
| 'Ha Long': 'Hạ Long', 'Da Lat': 'Đà Lạt', 'Vung Tau': 'Vũng Tàu', | |
| 'Quy Nhon': 'Quy Nhơn', 'Phan Thiet': 'Phan Thiết', 'Buon Ma Thuot': 'Buôn Ma Thuột', | |
| 'Vinh': 'Vinh', 'Thanh Hoa': 'Thanh Hóa', 'Hai Duong': 'Hải Dương', | |
| 'Nam Dinh': 'Nam Định', 'Thai Nguyen': 'Thái Nguyên', 'Viet Tri': 'Việt Trì', | |
| 'Ha Giang': 'Hà Giang', 'Cao Bang': 'Cao Bằng', 'Lang Son': 'Lạng Sơn', | |
| 'Lao Cai': 'Lào Cai', 'Dien Bien Phu': 'Điện Biên Phủ', 'Son La': 'Sơn La', | |
| 'Hoa Binh': 'Hòa Bình', 'Ninh Binh': 'Ninh Bình', 'Dong Hoi': 'Đồng Hới', | |
| 'Pleiku': 'Pleiku', 'Tuy Hoa': 'Tuy Hòa', 'Ca Mau': 'Cà Mau', 'Soc Trang': 'Sóc Trăng', | |
| // Common aliases that may appear in hotel.city | |
| 'HCMC': 'TP. Hồ Chí Minh', 'Saigon': 'TP. Hồ Chí Minh', | |
| 'Ho Chi Minh': 'TP. Hồ Chí Minh', 'HCM': 'TP. Hồ Chí Minh' | |
| }; | |
| // --------------------------------------------------------------------------- | |
| // Hotel-specific lookup tables | |
| // --------------------------------------------------------------------------- | |
| // Confirmed from HotelType.java: HOTEL, VILLA, APARTMENT, RESORT, HOSTEL, MOTEL, GUEST_HOUSE | |
| const hotelTypeViMap = { | |
| 'HOTEL': 'Khách sạn', | |
| 'VILLA': 'Biệt thự', | |
| 'APARTMENT': 'Căn hộ', | |
| 'RESORT': 'Resort', | |
| 'HOSTEL': 'Nhà nghỉ', | |
| 'MOTEL': 'Nhà nghỉ ven đường', | |
| 'GUEST_HOUSE': 'Nhà khách' | |
| }; | |
| const amenityViMap = { | |
| 'Free WiFi': 'WiFi miễn phí', | |
| 'WiFi': 'WiFi', | |
| 'Swimming Pool': 'Bể bơi', | |
| 'Pool': 'Bể bơi', | |
| 'Breakfast': 'Bữa sáng', | |
| 'Free Breakfast': 'Bữa sáng miễn phí', | |
| 'Parking': 'Bãi đỗ xe', | |
| 'Free Parking': 'Bãi đỗ xe miễn phí', | |
| 'Gym': 'Phòng tập gym', | |
| 'Fitness Center': 'Trung tâm thể dục', | |
| 'Restaurant': 'Nhà hàng', | |
| 'Bar': 'Quầy bar', | |
| 'Spa': 'Spa', | |
| 'Air Conditioning': 'Điều hòa không khí', | |
| 'Airport Shuttle': 'Đưa đón sân bay', | |
| 'Room Service': 'Dịch vụ phòng', | |
| '24-Hour Front Desk': 'Lễ tân 24/7', | |
| 'Concierge': 'Dịch vụ hỗ trợ khách', | |
| 'Laundry': 'Dịch vụ giặt ủi', | |
| 'Laundry Service': 'Dịch vụ giặt ủi', | |
| 'Meeting Room': 'Phòng họp', | |
| 'Business Center': 'Trung tâm kinh doanh', | |
| 'Kids Club': 'Khu vui chơi trẻ em', | |
| 'Beach Access': 'Lối ra bãi biển', | |
| 'Private Beach': 'Bãi biển riêng', | |
| 'Hot Tub': 'Bồn tắm nước nóng', | |
| 'Jacuzzi': 'Bồn Jacuzzi', | |
| 'Sauna': 'Phòng xông hơi', | |
| 'Minibar': 'Minibar', | |
| 'Safe': 'Két an toàn', | |
| 'Balcony': 'Ban công', | |
| 'Sea View': 'View biển', | |
| 'Mountain View': 'View núi', | |
| 'City View': 'View thành phố', | |
| 'Non-smoking': 'Không hút thuốc', | |
| 'Pet Friendly': 'Thân thiện thú cưng', | |
| 'Elevator': 'Thang máy', | |
| 'Electric Kettle': 'Ấm đun nước điện', | |
| 'TV': 'TV', | |
| 'Cable TV': 'Truyền hình cáp' | |
| }; | |
| // Helper: translate a list of amenity strings, return first N as Vietnamese | |
| function translateAmenities(amenities, count) { | |
| if (!amenities || amenities.length === 0) return []; | |
| const result = []; | |
| for (let i = 0; i < Math.min(count, amenities.length); i++) { | |
| result.push(amenityViMap[amenities[i]] || amenities[i]); | |
| } | |
| return result; | |
| } | |
| // Helper: build a nameVi for a hotel | |
| function buildHotelNameVi(hotel) { | |
| const typeLabel = hotelTypeViMap[hotel.hotelType] || 'Khách sạn'; | |
| const cityVi = cityMap[hotel.city] || hotel.city || ''; | |
| // If the English name already contains a proper-noun identifier we keep it | |
| // and simply prepend the Vietnamese type label + city. | |
| // Pattern: "<TypeLabel> <ProperNoun>, <CityVi>" | |
| const name = hotel.name || ''; | |
| // Strip common English hotel-type words from start of name for cleaner result | |
| const cleaned = name | |
| .replace(/^(Hotel|Resort|Villa|Hostel|Apartment|Guesthouse|Guest House|Motel)\s+/i, '') | |
| .trim(); | |
| if (cleaned) { | |
| return typeLabel + ' ' + cleaned + (cityVi ? ', ' + cityVi : ''); | |
| } | |
| return typeLabel + (cityVi ? ' ' + cityVi : ''); | |
| } | |
| // Helper: build addressVi — swap English city name with Vietnamese equivalent | |
| function buildAddressVi(hotel) { | |
| if (!hotel.address) return ''; | |
| let addr = hotel.address; | |
| // Replace city names in the address string | |
| for (const [en, vi] of Object.entries(cityMap)) { | |
| addr = addr.replace(new RegExp(en, 'gi'), vi); | |
| } | |
| // Common street-type translations | |
| addr = addr.replace(/\bStreet\b/gi, 'Đường'); | |
| addr = addr.replace(/\bAvenue\b/gi, 'Đại lộ'); | |
| addr = addr.replace(/\bDistrict\b/gi, 'Quận'); | |
| addr = addr.replace(/\bWard\b/gi, 'Phường'); | |
| return addr; | |
| } | |
| // Helper: build shortDescriptionVi | |
| function buildShortDescVi(hotel) { | |
| const typeLabel = hotelTypeViMap[hotel.hotelType] || 'Khách sạn'; | |
| const stars = hotel.starRating || ''; | |
| const cityVi = cityMap[hotel.city] || hotel.city || ''; | |
| const ams = translateAmenities(hotel.amenities, 3); | |
| const amStr = ams.length > 0 ? ' với ' + ams.join(', ') + '.' : '.'; | |
| const starPart = stars ? stars + ' sao ' : ''; | |
| return typeLabel + ' ' + starPart + 'tại ' + cityVi + amStr; | |
| } | |
| // Helper: build descriptionVi (2-3 sentences) | |
| function buildDescVi(hotel) { | |
| const typeLabel = hotelTypeViMap[hotel.hotelType] || 'Khách sạn'; | |
| const stars = hotel.starRating || ''; | |
| const cityVi = cityMap[hotel.city] || hotel.city || ''; | |
| const name = hotel.name || typeLabel; | |
| const ams = translateAmenities(hotel.amenities, 4); | |
| const starPart = stars ? stars + ' sao ' : ''; | |
| let sentence1 = 'Chào mừng đến với ' + name + ', ' + typeLabel.toLowerCase() + | |
| ' ' + starPart + 'tại ' + cityVi + '.'; | |
| let sentence2 = ''; | |
| if (ams.length > 0) { | |
| sentence2 = 'Cơ sở lưu trú sở hữu đầy đủ tiện nghi bao gồm ' + ams.join(', ') + '.'; | |
| } else { | |
| sentence2 = 'Cơ sở lưu trú cung cấp dịch vụ chất lượng cao cho du khách.'; | |
| } | |
| let sentence3 = 'Phù hợp cho cả kỳ nghỉ gia đình lẫn chuyến công tác tại ' + cityVi + '.'; | |
| return sentence1 + ' ' + sentence2 + ' ' + sentence3; | |
| } | |
| // ============================================================================= | |
| // SECTION 1 — hotel | |
| // ============================================================================= | |
| print(''); | |
| print('=== [1/5] Updating hotel collection ==='); | |
| let hotelUpdated = 0; | |
| db.hotel.find({}).forEach(function(hotel) { | |
| // Only set fields that are currently null/missing (idempotent guard) | |
| const setFields = {}; | |
| if (!hotel.nameVi) { | |
| setFields.nameVi = buildHotelNameVi(hotel); | |
| } | |
| if (!hotel.addressVi) { | |
| setFields.addressVi = buildAddressVi(hotel); | |
| } | |
| if (!hotel.shortDescriptionVi) { | |
| setFields.shortDescriptionVi = buildShortDescVi(hotel); | |
| } | |
| if (!hotel.descriptionVi) { | |
| setFields.descriptionVi = buildDescVi(hotel); | |
| } | |
| if (Object.keys(setFields).length > 0) { | |
| db.hotel.updateOne({ _id: hotel._id }, { $set: setFields }); | |
| hotelUpdated++; | |
| } | |
| }); | |
| print(' Updated ' + hotelUpdated + ' hotels with nameVi / addressVi / shortDescriptionVi / descriptionVi'); | |
| // ============================================================================= | |
| // SECTION 2 — room | |
| // ============================================================================= | |
| print(''); | |
| print('=== [2/5] Updating room collection ==='); | |
| // Confirmed from RoomType.java: SINGLE, DOUBLE, SUITE, FAMILY | |
| const roomTypeViMap = { | |
| 'SINGLE': 'Phòng Đơn', | |
| 'DOUBLE': 'Phòng Đôi', | |
| 'SUITE': 'Phòng Suite', | |
| 'FAMILY': 'Phòng Gia đình' | |
| }; | |
| let roomUpdated = 0; | |
| db.room.find({}).forEach(function(room) { | |
| const setFields = {}; | |
| if (!room.nameVi) { | |
| const typeVi = roomTypeViMap[room.type] || room.type || 'Phòng'; | |
| setFields.nameVi = typeVi; | |
| } | |
| if (!room.descriptionVi) { | |
| const typeVi = roomTypeViMap[room.type] || room.type || 'Phòng'; | |
| const areaPart = room.size ? ' rộng ' + room.size + 'm²' : ''; | |
| const bedPart = room.bedType ? ', trang bị ' + room.bedType : ''; | |
| const guestPart = room.maxOccupancy ? ', sức chứa tối đa ' + room.maxOccupancy + ' khách' : ''; | |
| setFields.descriptionVi = typeVi + areaPart + bedPart + guestPart + '.'; | |
| } | |
| if (Object.keys(setFields).length > 0) { | |
| db.room.updateOne({ _id: room._id }, { $set: setFields }); | |
| roomUpdated++; | |
| } | |
| }); | |
| print(' Updated ' + roomUpdated + ' rooms with nameVi / descriptionVi'); | |
| // ============================================================================= | |
| // SECTION 3 — promotions | |
| // ============================================================================= | |
| print(''); | |
| print('=== [3/5] Updating promotions collection ==='); | |
| // Confirmed from PromotionCategory.java: HOTEL, FLIGHT, CAR, ACTIVITY, ALL | |
| const promoCategoryViMap = { | |
| 'HOTEL': 'khách sạn', | |
| 'FLIGHT': 'chuyến bay', | |
| 'CAR': 'thuê xe', | |
| 'ACTIVITY': 'hoạt động', | |
| 'ALL': 'tất cả dịch vụ' | |
| }; | |
| // Promotion type field uses plain strings: "PERCENTAGE" or "FIXED_AMOUNT" | |
| function buildPromoTitleVi(promo) { | |
| const categoryVi = promo.category ? (promoCategoryViMap[promo.category] || promo.category.toLowerCase()) : 'dịch vụ'; | |
| if (promo.type === 'PERCENTAGE' && promo.value) { | |
| return 'Giảm ' + promo.value + '% ' + categoryVi; | |
| } | |
| if (promo.type === 'FIXED_AMOUNT' && promo.value) { | |
| const formattedVal = Number(promo.value).toLocaleString('vi-VN') + 'đ'; | |
| return 'Giảm ' + formattedVal + ' cho ' + categoryVi; | |
| } | |
| // Fallback: use the code or a generic title | |
| if (promo.code) { | |
| return 'Ưu đãi đặc biệt – mã ' + promo.code; | |
| } | |
| return 'Ưu đãi đặc biệt cho ' + categoryVi; | |
| } | |
| function buildPromoDescVi(promo) { | |
| const categoryVi = promo.category ? (promoCategoryViMap[promo.category] || promo.category.toLowerCase()) : 'dịch vụ'; | |
| let parts = []; | |
| if (promo.type === 'PERCENTAGE' && promo.value) { | |
| parts.push('Giảm ngay ' + promo.value + '% cho ' + categoryVi + '.'); | |
| } else if (promo.type === 'FIXED_AMOUNT' && promo.value) { | |
| const formattedVal = Number(promo.value).toLocaleString('vi-VN') + 'đ'; | |
| parts.push('Tiết kiệm ' + formattedVal + ' khi đặt ' + categoryVi + '.'); | |
| } else { | |
| parts.push('Ưu đãi hấp dẫn cho ' + categoryVi + '.'); | |
| } | |
| if (promo.minSpend) { | |
| const formattedMin = Number(promo.minSpend).toLocaleString('vi-VN') + 'đ'; | |
| parts.push('Áp dụng cho đơn hàng tối thiểu ' + formattedMin + '.'); | |
| } | |
| if (promo.endDate) { | |
| parts.push('Ưu đãi có hiệu lực đến ' + promo.endDate + '.'); | |
| } | |
| return parts.join(' '); | |
| } | |
| let promoUpdated = 0; | |
| db.promotions.find({}).forEach(function(promo) { | |
| const setFields = {}; | |
| if (!promo.titleVi) { | |
| setFields.titleVi = buildPromoTitleVi(promo); | |
| } | |
| if (!promo.descriptionVi) { | |
| setFields.descriptionVi = buildPromoDescVi(promo); | |
| } | |
| if (Object.keys(setFields).length > 0) { | |
| db.promotions.updateOne({ _id: promo._id }, { $set: setFields }); | |
| promoUpdated++; | |
| } | |
| }); | |
| print(' Updated ' + promoUpdated + ' promotions with titleVi / descriptionVi'); | |
| // ============================================================================= | |
| // SECTION 4 — advertisement | |
| // ============================================================================= | |
| print(''); | |
| print('=== [4/5] Updating advertisement collection ==='); | |
| // Keyword-swap map for ad titles (longest-match wins via ordered checks) | |
| const adTitleKeywordMap = [ | |
| // Common marketing phrases | |
| ['Summer Sale', 'Khuyến mãi mùa hè'], | |
| ['Flash Sale', 'Flash Sale – Giảm giá sốc'], | |
| ['New Destinations', 'Điểm đến mới'], | |
| ['New Arrival', 'Sản phẩm mới'], | |
| ['Limited Offer', 'Ưu đãi có hạn'], | |
| ['Special Offer', 'Ưu đãi đặc biệt'], | |
| ['Exclusive Deal', 'Ưu đãi độc quyền'], | |
| ['Weekend Deal', 'Ưu đãi cuối tuần'], | |
| ['Holiday Sale', 'Khuyến mãi kỳ nghỉ'], | |
| ['Early Bird', 'Đặt sớm – Giá ưu đãi'], | |
| ['Last Minute', 'Ưu đãi phút chót'], | |
| ['Best Price', 'Giá tốt nhất'], | |
| ['Top Hotels', 'Khách sạn nổi bật'], | |
| ['Featured Hotels', 'Khách sạn nổi bật'], | |
| ['Luxury Hotels', 'Khách sạn cao cấp'], | |
| ['Budget Hotels', 'Khách sạn giá rẻ'], | |
| ['Beach Resorts', 'Resort biển'], | |
| ['City Break', 'Du lịch thành phố'], | |
| ['Family Travel', 'Du lịch gia đình'], | |
| ['Honeymoon', 'Tuần trăng mật'], | |
| ['Adventure Travel', 'Du lịch phiêu lưu'], | |
| ['Cultural Tour', 'Tour văn hóa'], | |
| ['Food Tour', 'Tour ẩm thực'], | |
| ['Visa Services', 'Dịch vụ visa'], | |
| ['Car Rental', 'Thuê xe'], | |
| ['Flight Deals', 'Ưu đãi vé máy bay'], | |
| ['Hotel Deals', 'Ưu đãi khách sạn'], | |
| ['Travel Insurance', 'Bảo hiểm du lịch'], | |
| ['Tour Packages', 'Gói tour'], | |
| ['Group Tours', 'Tour nhóm'] | |
| ]; | |
| // Try to match any keyword; return mapped value or original | |
| function translateAdText(text) { | |
| if (!text) return ''; | |
| for (let i = 0; i < adTitleKeywordMap.length; i++) { | |
| const pair = adTitleKeywordMap[i]; | |
| if (text.toLowerCase().indexOf(pair[0].toLowerCase()) !== -1) { | |
| return text.replace(new RegExp(pair[0], 'gi'), pair[1]); | |
| } | |
| } | |
| // No mapping found — return original (frontend at least shows something) | |
| return text; | |
| } | |
| let adUpdated = 0; | |
| db.advertisement.find({}).forEach(function(ad) { | |
| const setFields = {}; | |
| if (!ad.titleVi) { | |
| setFields.titleVi = translateAdText(ad.title) || (ad.title || ''); | |
| } | |
| if (!ad.descriptionVi) { | |
| // Never leave null — fall back to English copy if no translation found | |
| const translated = translateAdText(ad.description); | |
| setFields.descriptionVi = translated || (ad.description || ''); | |
| } | |
| if (Object.keys(setFields).length > 0) { | |
| db.advertisement.updateOne({ _id: ad._id }, { $set: setFields }); | |
| adUpdated++; | |
| } | |
| }); | |
| print(' Updated ' + adUpdated + ' advertisements with titleVi / descriptionVi'); | |
| // ============================================================================= | |
| // SECTION 5 — visa_articles | |
| // ============================================================================= | |
| print(''); | |
| print('=== [5/5] Updating visa_articles collection ==='); | |
| // Country name translations for common visa article destinations | |
| const countryViMap = { | |
| 'United States': 'Mỹ', | |
| 'USA': 'Mỹ', | |
| 'US': 'Mỹ', | |
| 'United Kingdom': 'Anh', | |
| 'UK': 'Anh', | |
| 'Japan': 'Nhật Bản', | |
| 'South Korea': 'Hàn Quốc', | |
| 'Korea': 'Hàn Quốc', | |
| 'China': 'Trung Quốc', | |
| 'Australia': 'Úc', | |
| 'Canada': 'Canada', | |
| 'Germany': 'Đức', | |
| 'France': 'Pháp', | |
| 'Singapore': 'Singapore', | |
| 'Thailand': 'Thái Lan', | |
| 'Malaysia': 'Malaysia', | |
| 'Indonesia': 'Indonesia', | |
| 'Philippines': 'Philippines', | |
| 'Taiwan': 'Đài Loan', | |
| 'Hong Kong': 'Hồng Kông', | |
| 'India': 'Ấn Độ', | |
| 'Russia': 'Nga', | |
| 'Italy': 'Ý', | |
| 'Spain': 'Tây Ban Nha', | |
| 'Portugal': 'Bồ Đào Nha', | |
| 'Netherlands': 'Hà Lan', | |
| 'Switzerland': 'Thụy Sĩ', | |
| 'New Zealand': 'New Zealand', | |
| 'Vietnam': 'Việt Nam', | |
| 'Cambodia': 'Campuchia', | |
| 'Laos': 'Lào', | |
| 'Myanmar': 'Myanmar', | |
| 'Nepal': 'Nepal', | |
| 'Sri Lanka': 'Sri Lanka', | |
| 'Maldives': 'Maldives', | |
| 'Dubai': 'Dubai', | |
| 'UAE': 'UAE', | |
| 'Turkey': 'Thổ Nhĩ Kỳ', | |
| 'Greece': 'Hy Lạp', | |
| 'Czech Republic': 'Cộng hòa Séc', | |
| 'Austria': 'Áo', | |
| 'Belgium': 'Bỉ', | |
| 'Sweden': 'Thụy Điển', | |
| 'Norway': 'Na Uy', | |
| 'Denmark': 'Đan Mạch', | |
| 'Finland': 'Phần Lan', | |
| 'Poland': 'Ba Lan', | |
| 'Mexico': 'Mexico', | |
| 'Brazil': 'Brazil', | |
| 'Argentina': 'Argentina', | |
| 'South Africa': 'Nam Phi', | |
| 'Egypt': 'Ai Cập', | |
| 'Morocco': 'Maroc', | |
| 'Kenya': 'Kenya' | |
| }; | |
| // Citizen country translations (used in title patterns like "for US Citizens") | |
| const citizenViMap = { | |
| 'US Citizens': 'công dân Mỹ', | |
| 'American Citizens': 'công dân Mỹ', | |
| 'UK Citizens': 'công dân Anh', | |
| 'British Citizens': 'công dân Anh', | |
| 'Australian Citizens': 'công dân Úc', | |
| 'Canadian Citizens': 'công dân Canada', | |
| 'Indian Citizens': 'công dân Ấn Độ', | |
| 'Chinese Citizens': 'công dân Trung Quốc', | |
| 'Japanese Citizens': 'công dân Nhật Bản', | |
| 'Korean Citizens': 'công dân Hàn Quốc', | |
| 'Vietnamese Citizens': 'công dân Việt Nam', | |
| 'European Citizens': 'công dân châu Âu', | |
| 'Citizens': 'công dân' | |
| }; | |
| // Category translations | |
| const visaCategoryViMap = { | |
| 'tourist': 'du lịch', | |
| 'business': 'công tác', | |
| 'student': 'du học', | |
| 'work': 'lao động', | |
| 'transit': 'quá cảnh', | |
| 'e-visa': 'visa điện tử', | |
| 'evisa': 'visa điện tử', | |
| 'on arrival': 'visa khi đến nơi', | |
| 'visa-free': 'miễn visa' | |
| }; | |
| function buildVisaTitleVi(article) { | |
| if (!article.title) return ''; | |
| let title = article.title; | |
| // Replace citizen phrases first (longer matches before shorter) | |
| for (const [en, vi] of Object.entries(citizenViMap)) { | |
| title = title.replace(new RegExp(en, 'gi'), vi); | |
| } | |
| // Replace country names | |
| for (const [en, vi] of Object.entries(countryViMap)) { | |
| title = title.replace(new RegExp('\\b' + en + '\\b', 'gi'), vi); | |
| } | |
| // Replace category keywords | |
| for (const [en, vi] of Object.entries(visaCategoryViMap)) { | |
| title = title.replace(new RegExp('\\b' + en + '\\b', 'gi'), vi); | |
| } | |
| // Common structural words | |
| title = title.replace(/\bVisa Guide\b/gi, 'Hướng dẫn Visa'); | |
| title = title.replace(/\bVisa Requirements\b/gi, 'Yêu cầu Visa'); | |
| title = title.replace(/\bVisa Application\b/gi, 'Đăng ký Visa'); | |
| title = title.replace(/\bVisa\b/gi, 'Visa'); | |
| title = title.replace(/\bHow to Apply\b/gi, 'Cách đăng ký'); | |
| title = title.replace(/\bComplete Guide\b/gi, 'Hướng dẫn toàn diện'); | |
| title = title.replace(/\bEverything You Need to Know\b/gi, 'Tất cả những gì bạn cần biết'); | |
| title = title.replace(/\bfor\b/gi, 'cho'); | |
| title = title.replace(/\bto\b/gi, 'đến'); | |
| return title; | |
| } | |
| function buildVisaExcerptVi(article) { | |
| // If we can derive something useful from country + category, do so. | |
| // Otherwise copy the English excerpt (never leave null). | |
| if (!article.excerpt && !article.country) return ''; | |
| const countryVi = countryViMap[article.country] || article.country || ''; | |
| const categoryVi = article.category ? (visaCategoryViMap[article.category.toLowerCase()] || article.category) : ''; | |
| if (countryVi) { | |
| let parts = ['Hướng dẫn đầy đủ về thủ tục xin visa ' + (categoryVi ? categoryVi + ' ' : '') + 'đến ' + countryVi + '.']; | |
| if (article.processingTime) { | |
| parts.push('Thời gian xử lý: ' + article.processingTime + '.'); | |
| } | |
| if (article.fee) { | |
| parts.push('Lệ phí: ' + article.fee + ' USD.'); | |
| } | |
| return parts.join(' '); | |
| } | |
| // Fallback: return English excerpt so the field is never null | |
| return article.excerpt || ''; | |
| } | |
| let visaUpdated = 0; | |
| db.visa_articles.find({}).forEach(function(article) { | |
| const setFields = {}; | |
| if (!article.titleVi) { | |
| const titleVi = buildVisaTitleVi(article); | |
| // If nothing changed from the original (no keywords matched), keep English title | |
| setFields.titleVi = titleVi || article.title || ''; | |
| } | |
| if (!article.excerptVi) { | |
| // Never leave null — buildVisaExcerptVi already falls back to English copy | |
| setFields.excerptVi = buildVisaExcerptVi(article); | |
| if (!setFields.excerptVi) { | |
| setFields.excerptVi = article.excerpt || ''; | |
| } | |
| } | |
| if (Object.keys(setFields).length > 0) { | |
| db.visa_articles.updateOne({ _id: article._id }, { $set: setFields }); | |
| visaUpdated++; | |
| } | |
| }); | |
| print(' Updated ' + visaUpdated + ' visa articles with titleVi / excerptVi'); | |
| // ============================================================================= | |
| // VERIFICATION — sample one document from each collection | |
| // ============================================================================= | |
| print(''); | |
| print('=== Verification: sample documents ==='); | |
| // hotel | |
| const sampleHotel = db.hotel.findOne({}, { name: 1, nameVi: 1, shortDescriptionVi: 1 }); | |
| if (sampleHotel) { | |
| print(''); | |
| print('Sample Hotel:'); | |
| print(' name: ' + sampleHotel.name); | |
| print(' nameVi: ' + sampleHotel.nameVi); | |
| print(' shortDescriptionVi: ' + sampleHotel.shortDescriptionVi); | |
| } else { | |
| print(' (no hotel documents found)'); | |
| } | |
| // room | |
| const sampleRoom = db.room.findOne({}, { name: 1, nameVi: 1, descriptionVi: 1 }); | |
| if (sampleRoom) { | |
| print(''); | |
| print('Sample Room:'); | |
| print(' name: ' + sampleRoom.name); | |
| print(' nameVi: ' + sampleRoom.nameVi); | |
| print(' descriptionVi: ' + sampleRoom.descriptionVi); | |
| } else { | |
| print(' (no room documents found)'); | |
| } | |
| // promotions | |
| const samplePromo = db.promotions.findOne({}, { title: 1, titleVi: 1, descriptionVi: 1 }); | |
| if (samplePromo) { | |
| print(''); | |
| print('Sample Promotion:'); | |
| print(' title: ' + samplePromo.title); | |
| print(' titleVi: ' + samplePromo.titleVi); | |
| print(' descriptionVi: ' + samplePromo.descriptionVi); | |
| } else { | |
| print(' (no promotion documents found)'); | |
| } | |
| // advertisement | |
| const sampleAd = db.advertisement.findOne({}, { title: 1, titleVi: 1, descriptionVi: 1 }); | |
| if (sampleAd) { | |
| print(''); | |
| print('Sample Advertisement:'); | |
| print(' title: ' + sampleAd.title); | |
| print(' titleVi: ' + sampleAd.titleVi); | |
| print(' descriptionVi: ' + sampleAd.descriptionVi); | |
| } else { | |
| print(' (no advertisement documents found)'); | |
| } | |
| // visa_articles | |
| const sampleVisa = db.visa_articles.findOne({}, { title: 1, titleVi: 1, excerptVi: 1 }); | |
| if (sampleVisa) { | |
| print(''); | |
| print('Sample VisaArticle:'); | |
| print(' title: ' + sampleVisa.title); | |
| print(' titleVi: ' + sampleVisa.titleVi); | |
| print(' excerptVi: ' + sampleVisa.excerptVi); | |
| } else { | |
| print(' (no visa_articles documents found)'); | |
| } | |
| print(''); | |
| print('=== populate_vi_remaining.js complete ==='); | |
| print(' hotel: ' + hotelUpdated + ' updated'); | |
| print(' room: ' + roomUpdated + ' updated'); | |
| print(' promotions: ' + promoUpdated + ' updated'); | |
| print(' advertisement: ' + adUpdated + ' updated'); | |
| print(' visa_articles: ' + visaUpdated + ' updated'); | |