Vì sao Font Awesome được đóng gói thành font từ năm 2012, vì sao Odoo đứng yên ở bản 4.7.0 suốt một thập kỷ, và điều gì thay đổi khi Odoo 20 thay nó bằng Material Symbols.
Odoo 19 đóng gói kèm một bản Font Awesome 4.7.0, thư viện phát hành tháng 10/2016. Nó nằm trong web/static/src/libs/fontawesome/, ngay cạnh các font thật, nạp qua đúng những asset bundle đó. Suốt một thập kỷ, cách xếp chỗ này cũng có lý, vì nó đúng là một file font. Nhưng nó không phải chuyện chữ nghĩa, và chính khác biệt đó là lý do Odoo 20 xóa nó đi.
Đây là bài đi kèm loạt bài về font (phần 1, phần 2, phần 3), vốn cố ý để font biểu tượng ra ngoài. Tất cả những gì bên dưới là câu chuyện các bài kia không kể: cái tên từ đâu ra, việc tách giấy phép đã tốn những gì, base_fontawesome của OCA thực chất dùng để làm gì, và thứ gì thay thế nó.
Đã kiểm chứng trên odoo/odoo tại commit e6bb2da nhánh master (ngày 4/8/2026) và nhánh 19.0. Odoo 20.0 chưa phát hành; master là nơi nó đang được dựng, nên những gì tiếp theo là trạng thái của công việc đó chứ không phải một phiên bản đã ra mắt.
Vì sao một bộ biểu tượng lại đóng gói thành font
Font Awesome ra mắt lần đầu ngày 3/3/2012. Dave Gandy làm nó cho Twitter Bootstrap, thứ đang cần một bộ biểu tượng đáng tin mà chưa có bộ nào của riêng mình. Cái tên đến từ một câu đùa: một tập của sitcom NewsRadio, trong đó nhân vật tỷ phú lập dị do Stephen Root thủ vai muốn một dinh thự như Xanadu “nhưng đừng có cái tên dở hơi” và cuối cùng chọn Fort Awesome.
Nó được đóng gói thành font vì năm 2012 đó là định dạng duy nhất giải quyết trọn vẹn bài toán cùng lúc. Nhà thiết kế muốn biểu tượng co giãn được trên màn hình retina vừa xuất hiện, lấy màu theo chữ xung quanh, và chạy được trên trình duyệt lùi tới tận IE8. Một font làm cả ba việc đó miễn phí. Ánh xạ mỗi biểu tượng vào một điểm mã trong vùng Private Use Area của Unicode, khai báo họ font, rồi để CSS kéo glyph ra:
.fa-check:before {
content: "\f00c";
}
Glyph thừa hưởng color, font-size và line-height từ phần tử cha, bởi với trình duyệt thì nó là một ký tự. Toàn bộ mẹo nằm ở đó, và đó là một mẹo hay. Đó cũng là lý do thư viện này được gọi là font, vì sao nó nằm trong thư mục fonts, và vì sao nó cứ xuất hiện trong những cuộc trò chuyện về chữ nghĩa mà đáng ra nó không thuộc về.
Cái giá lộ ra về sau. Điểm mã trong Private Use Area nghĩa là công nghệ hỗ trợ người khuyết tật đọc một glyph trang trí thành ký tự lạ, trừ khi markup ẩn nó đi. Một glyph chỉ mang một màu, nên biểu tượng nhiều màu là bất khả. Font biểu tượng đòi một request @font-face chặn luồng trước khi bất kỳ biểu tượng nào được vẽ, khiến mọi biểu tượng hoặc vô hình hoặc thành ô vuông dự phòng cho tới khi font tải xong, trong khi SVG nội tuyến được vẽ cùng lúc với markup xung quanh. Và khi font tải hỏng, mọi biểu tượng trên trang đều thành ô vuông, đúng kiểu hỏng mà phần 1 mô tả khi thiếu glyph cho một hệ chữ. Một khi công cụ SVG đủ chín, những lý do để mã hóa hình ảnh thành văn bản cũng cạn. Bản thân Font Awesome đã thêm bộ công cụ dựa trên SVG từ phiên bản 5, và nay tự giới thiệu là “bộ công cụ SVG, font và CSS mang tính biểu tượng”, với SVG được nêu trước.
Bản của Odoo dừng lại ở năm 2016
Phần đầu file addons/web/static/src/libs/fontawesome/css/font-awesome.css4 trên nhánh 19.0 không đổi một chữ kể từ ngày nó được đưa vào:
/*!
* Based on Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
* License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
*/
4.7.0 là bản phát hành cuối của dòng 4.x. Chín năm sau, Odoo 19 vẫn phục vụ nó.
Lý do lộ ra chỉ qua một con số đếm. Trên nhánh 19.0, chuỗi fa fa- xuất hiện 2.754 lần trong 797 file XML, và đó mới là bản lõi, chưa tính module nào của khách hàng. Mỗi lần xuất hiện là một điểm gọi gắn chặt với tên class của Font Awesome 4. Một thư viện thêm vào rẻ đến thế thì gỡ ra cũng đắt đúng theo mức nó từng hữu ích.
Việc tách giấy phép biến nâng cấp thành một quyết định
Font Awesome 4 không rắc rối. File font theo SIL OFL 1.1, phần CSS và LESS theo MIT, các file còn lại theo CC BY 3.0. Dự án cũng đã bỏ yêu cầu ghi công từ ba phiên bản lớn trước đó: “Không còn bắt buộc ghi công từ Font Awesome 3.0, nhưng chúng tôi rất trân trọng nếu bạn làm vậy.”
Phiên bản 5, phát hành tháng 12/2017, tái cấu trúc toàn bộ. Danh mục tách thành Free và Pro, với Pro là sản phẩm trả phí, còn nhánh Free nhận một giấy phép ba ngả phụ thuộc vào định dạng file chứ không vào cả dự án:
- Biểu tượng đóng gói dạng file SVG và JS: CC BY 4.0, và ghi công lại trở thành bắt buộc.
- File font cho web và desktop: SIL OFL 1.1.
- Mọi thứ không phải font cũng không phải biểu tượng: MIT.
Song song với giấy phép, phiên bản 5 đổi tên các tiền tố class. Một fa duy nhất tách thành fas, far và fab cho kiểu solid, regular và brand, nên nâng cấp chưa bao giờ chỉ là tăng số phiên bản. Với một codebase có hàng nghìn điểm gọi fa fa-, đó là một cuộc viết lại hàng loạt cộng thêm một lần rà soát pháp lý, đổi lại là những biểu tượng chẳng ai đang bị chặn vì thiếu.
Nói cho rõ phần nào có căn cứ: lịch sử commit của Odoo không nêu lý do thư viện đứng yên ở 4.7.0 suốt một thập kỷ, và chúng tôi không khẳng định riêng việc đổi giấy phép giải thích được điều đó. Thứ việc đổi giấy phép giải thích được là vì sao “cứ nâng cấp Font Awesome đi” chưa bao giờ là việc nhỏ như cái tên của nó.
base_fontawesome: câu trả lời của OCA
Chính khoảng đóng băng đó là chỗ base_fontawesome17 (OCA/server-tools) lấp vào. Bản lõi đóng gói một phiên bản cũ và việc dịch chuyển nó cần trọn một chu kỳ phát hành, nên module chọn cách tráo asset. Toàn bộ nhiệm vụ của nó là một lệnh thay thế trong manifest, lặp lại cho mọi bundle có nạp thư viện:
"assets": {
"web.assets_backend": [
("replace", "web/static/src/libs/fontawesome/css/font-awesome.css",
"base_fontawesome/static/src/css/fontawesome.css"),
"base_fontawesome/static/lib/fontawesome-6.7.2/css/all.css",
"base_fontawesome/static/lib/fontawesome-6.7.2/css/v4-shims.css",
],
"web.assets_frontend": [ ... cùng lệnh replace ... ],
"web.report_assets_common": [ ... cùng lệnh replace ... ],
},
Không model, không view, chỉ một tuple ("replace", …) nhắm vào web.assets_backend, web.assets_frontend và web.report_assets_common cùng lúc. Để ý v4-shims.css trong danh sách: đó là lớp tương thích giữ cho tên class của Font Awesome 4 vẫn chạy được với thư viện phiên bản 6, tức đúng vấn đề do việc đổi tên class ở trên tạo ra, và được chính dự án gốc giải quyết.
Đụng cả ba bundle không phải vì kỹ tính, mà vì cấu trúc bắt buộc thế. Một glyph biểu tượng phải hiện ra y hệt nhau trong nút ở backend, trên trang website và trong header của PDF, nên không có biến riêng theo tầng để định nghĩa lại như cách phần 1 định nghĩa lại một họ font. Bạn phải thay stylesheet dùng chung ở mọi nơi nó được nạp.
Module đi kèm, base_fontawesome_web_editor18, sửa một tác dụng phụ. Font Awesome 6.7.2 tái cấu trúc CSS để định nghĩa biểu tượng qua một thuộc tính tùy chỉnh --fa thay vì các mẫu selector mà bộ chọn biểu tượng của Odoo quét tìm, nên trình soạn WYSIWYG không còn tìm thấy biểu tượng nào. Module vá tiện ích nhận diện để nó tìm thuộc tính mới:
patch(fonts, {
fontIcons: [{base: "fa", parser: /\.(fa-(?:\w|-)+)/i}],
_isValidFontAwesomeRule(selectorText, cssText) {
return selectorText && cssText && cssText.includes("--fa:");
},
...
});
Phiên bản: base_fontawesome có từ 12.0 đến 18.0. base_fontawesome_web_editor phủ 17.0 và 18.0.
Odoo 20 gỡ bỏ nó
Bản thay thế được đưa lên master trong PR #2736065, commit 3e15a7b6, và thông điệp của nó không úp mở:
Commit này gỡ bỏ thư viện Font Awesome và đưa Material Symbols vào làm hệ biểu tượng chính. Để phủ những biểu tượng Material Symbols không có, thư viện Odoo UI Icon (OI) vẫn được duy trì (ví dụ biểu tượng tùy chỉnh cho kanban view, hay các biểu tượng thương hiệu như Facebook hoặc GitHub).
Bản thân thư viện OI không mới với đợt chuyển đổi này. Nó ra mắt trong Odoo 16.0 ngày 26/1/2022, được thiết kế để cùng tồn tại với Font Awesome 4.7 và lấp một khoảng trống thư viện cũ để lại. Commit gốc11 viết: “Mục tiêu là cung cấp một dải biểu tượng rộng hơn, làm riêng cho giao diện của chúng tôi, và bù vào chỗ FontAwesome thiếu hình tượng cho những nghiệp vụ và chức năng cụ thể.” Việc hai hệ biểu tượng cùng tồn tại kéo dài bốn năm; cuộc chuyển đổi năm 2026 gộp chúng thành một dưới Material Symbols, với OI nay là phương án dự phòng cho mọi thứ Material Symbols không có.
Giao kèo về markup đổi từ mỗi biểu tượng một class sang một thuộc tính:
<!-- trước -->
<i class="fa fa-check"/>
<!-- sau -->
<i class="oi" data-icon="check"/>
Odoo không chỉ đơn giản đổi nhà cung cấp này lấy nhà cung cấp khác. Material Symbols không phủ hết những gì Font Awesome từng phủ, nên font biểu tượng của chính Odoo gánh phần còn lại. Bản đồ tương thích ghi lại quyết định này, web/static/src/webclient/icons_mappings/fa_to_ms.scss12, là hiện vật rõ ràng nhất của cả cuộc chuyển đổi: 792 tên của Font Awesome 4, trong đó 585 tên ánh xạ sang một biểu tượng Material Symbols và 207 tên ánh xạ sang một Odoo UI Icon. 207 tên đó phần lớn là dấu hiệu thương hiệu, loại biểu tượng mà một bộ icon đa dụng của Google chẳng có lý do gì phải mang theo.
$fa-icon-map: (
"adjust": ("ms", "contrast"),
"align-center": ("ms", "format_align_center"),
"amazon": ("oi", "amazon"),
"ambulance": ("ms", "emergency"),
...
);
Bản đồ đó được nói rõ là một cây cầu, không phải điểm đến. Commit gọi các ánh xạ này là “tạm thời” và nói chúng “được dự tính sẽ gỡ bỏ khi cuộc chuyển đổi hoàn tất.”
Hai commit nữa hoàn tất công việc. d4113ca8 dọn những chỗ vốn chẳng dùng class fa bao giờ, nơi SCSS đặt thẳng họ font và mã ký tự của glyph, nên xuyên thủng luôn lớp tương thích. Rồi c5a40a610, commit khép lại PR đã dẫn tới bài viết này, #2568409, quét toàn bộ điểm gọi trên cả codebase, kèm các PR tương ứng ở enterprise, design-themes, upgrade và documentation.
Cuộc quét có hiệu quả. So với 2.754 lần xuất hiện fa fa- trên 19.0, master tại thời điểm viết bài còn 90, nằm trong 14 file, và 69 trong số đó nằm gọn trong một bundle spreadsheet được vendor vào. Bản lõi đã xong.
Vậy rốt cuộc thứ gì tốt lên?
Viết một biểu tượng trong Odoo trước đây đòi bạn phải biết những thứ lẽ ra không cần biết.
Trước kia có hai hệ biểu tượng và hai cách viết. Một biểu tượng Font Awesome là class="fa fa-arrow-left". Một biểu tượng của chính Odoo là class="oi oi-arrow-left". Cùng một việc trên màn hình, khác tiền tố, và chọn đúng nghĩa là phải biết biểu tượng bạn cần tình cờ nằm ở thư viện nào trong hai. Đó không phải kiến thức về tính năng bạn đang làm. Đó là kiến thức về lịch sử vendor của Odoo.
Commit c5a40a610 nói thẳng nó đã gộp cái gì:
Các mẫu cũ như
class="oi oi-fw oi-arrow-left"hayclass="fa fa-fw fa-arrow-left"được chuyển sang định dạng mới dùngclass="oi oi-fw"cùng thuộc tính data-icon (ví dụdata-icon="west"), bảo đảm nhất quán với hệ thống dựa trên Material Symbols.
Giờ thì class không còn nói biểu tượng đến từ đâu. icons.scss13 gọi .oi là “class chung cho biểu tượng Material Symbols và thư viện mở rộng OI”, còn cái tên trong data-icon quyết định mọi thứ còn lại. Những biểu tượng Odoo vẫn tự cung cấp mang tiền tố oi_ thuần túy để không đụng tên với Google:
<i class="oi" data-icon="check"/> <!-- Material Symbols -->
<i class="oi" data-icon="oi_amazon"/> <!-- của riêng Odoo, chủ yếu là dấu hiệu thương hiệu -->
Ba điều theo sau đó, và chúng mới là lợi tức thật của cuộc chuyển đổi.
Bản đặc và bản viền thôi là hai biểu tượng khác nhau. Font Awesome có fa-star và fa-star-o là hai cái tên riêng, và quy ước hậu tố -o phải học thuộc theo từng biểu tượng. Quy tắc nền mới đặt content: attr(data-icon) và nối thêm _f khi phần tử cũng mang oi-filled, nên một cái tên cộng một bổ ngữ là phủ được cả hai. Bản đồ tương thích là chỗ cái giá cũ hiện ra. Nó chứa 41 cặp như vậy, hai tên Font Awesome cùng quy về một tên Material Symbols chỉ khác nhau ở chuyện đặc hay rỗng, "star": ("ms", "star", "filled") nằm ngay trên "star-o": ("ms", "star"). Tức 82 trong 792 mục của nó dành cho một phân biệt mà hệ thống mới diễn đạt bằng đúng một class.
Tên biểu tượng giờ đọc được ngay chỗ bạn đang nhìn. data-icon="west" phân giải qua một ligature của font, nghĩa là tên biểu tượng chính là đoạn văn bản và font thay nó bằng glyph. Soi một phần tử, bạn thấy west. Dưới thời Font Awesome, cái tên chỉ tồn tại dưới dạng mã ký tự trong một stylesheet, content: "\f060", nên devtools chỉ cho bạn thấy một ký tự thuộc vùng dùng riêng, không gì hơn.
Thêm một biểu tượng không còn phải đụng CSS. Font Awesome cần một quy tắc cho mỗi biểu tượng để buộc một class với mã ký tự của nó, nên mỗi biểu tượng Odoo dùng lại tốn thêm một mục trong một stylesheet chỉ có phình ra. Ở hệ thống mới, một biểu tượng là một dòng trong icons_wishlist.txt và một lần chạy generate_icons.py, script này dựng lại font và đồng thời xuất ra ms_icons.js để các biểu tượng mới tìm được trong hộp thoại media. Phần hỗ trợ viết từ phải sang trái cũng đi theo lối đó: thay vì lật ảnh tùy ứng, rtl_icons.scss14 là một danh sách thuần các tên biểu tượng có hướng, và chúng được lật gương dưới .o_rtl.
Commit d4113ca8 cho thấy thiết kế cũ tốn gì khi người ta tìm cách đi vòng quanh nó. Một số chỗ bỏ qua hẳn phần class và đặt thẳng họ font cùng mã ký tự trong SCSS, khiến chúng hỏng xuyên cả lớp tương thích, vì một mã ký tự không phải một cái tên và chẳng gì dịch nổi nó. Những chỗ đó nay đã biến mất, thay bằng biểu tượng có tên như mọi thứ khác.
File Font Awesome duy nhất còn sống sót
Liệt kê file trên master và một hiện vật vẫn còn đứng đó, trơ trọi, không stylesheet nào nạp nó (đến hôm nay vẫn còn):
addons/web/static/src/libs/fontawesome/fonts/fontawesome-webfont.ttf
Nó sống sót là nhờ email. addons/mail/controllers/mail.py16 chuyển biểu tượng thành ảnh PNG ở phía máy chủ, vì không thể trông cậy các trình email tải được một web font rồi vẽ glyph từ đó:
@staticmethod
def _get_icon_rendering_info(icon, font):
# default to 'oi' (material icons)
info = {
'path': 'web/static/src/libs/materialsymbols/material_symbols_outlined_subset.woff2',
...
}
if font == 'oi' and icon.isdigit():
# custom odoo icon
info['path'] = 'web/static/lib/odoo_ui_icons/fonts/odoo_ui_icons.woff2'
...
elif font == 'fa':
# legacy fontawesome icon
info['path'] = 'web/static/src/libs/fontawesome/fonts/fontawesome-webfont.ttf'
Một nhánh fa, được chú thích là legacy ở cả hai dòng, giữ lại cho những giá trị biểu tượng đã lưu mà cuộc chuyển đổi không viết lại. Một cái kết hợp cảnh: lần dùng Font Awesome cuối cùng còn sót lại trong Odoo chính là trường hợp duy nhất file này được đối xử đúng như bản chất xưa nay của nó, một túi glyph để tra theo điểm mã rồi vẽ ra ảnh bitmap.
Nên làm gì
Trên 17.0 và 18.0. Nếu cần biểu tượng mới hơn năm 2016, hãy cài base_fontawesome, và thêm base_fontawesome_web_editor nếu người biên tập dùng bộ chọn biểu tượng của WYSIWYG. Cả hai đều miễn phí và dễ đọc, mà như phần 2 đã lập luận, chừng đó đã đủ là lý do để kiểm tra OCA trước khi viết bất cứ thứ gì.
Trên 19.0. Chưa module nào được port. Bản Font Awesome 4.7.0 của bản lõi là thứ bạn có, và đó là phiên bản cuối cùng mà câu này còn đúng.
Từ 20.0 trở đi. Hãy viết class="oi" data-icon="…" và thôi viết fa fa-*. Bản ánh xạ trong fa_to_ms.scss sẽ giữ cho markup cũ vẫn hiển thị, nhưng chính commit của nó gọi nó là tạm thời, nên hãy xem mọi thứ nó phủ là một cảnh báo ngừng hỗ trợ chứ không phải một API được bảo đảm.
Trước lần nâng cấp đó, hãy đếm xem bạn phơi ra bao nhiêu:
grep -rn "fa fa-\|'fa-\|\"fa-" --include="*.xml" --include="*.js" --include="*.scss" your_addons/
Bản lõi đi từ 2.754 lần xuống 90 nhờ một đầu việc chuyên trách, các PR phối hợp trên năm repository và một bản đồ tương thích 792 mục. Module tùy chỉnh chẳng có bộ máy nào như thế. Bất cứ con số nào lệnh grep kia trả về chính là phần của cuộc nâng cấp Odoo 20 mà chưa ai dự trù, và khác với phần lớn việc nâng cấp, nó đủ máy móc để bắt đầu sớm.
Điểm lớn hơn chính là điều bài viết này mở đầu. Font Awesome bị xếp chung với các font suốt một thập kỷ vì định dạng file của nó, và cách xếp ấy lặng lẽ hàm ý rằng đây là chuyện chữ nghĩa. Chưa bao giờ là vậy. Biểu tượng là một thành phần giao diện tình cờ khoác bộ áo của một font, và Odoo 20 là lúc chúng cuối cùng cũng thay áo.
-
Trang Wikipedia, phát hành lần đầu 3/3/2012 — Font Awesome
-
Giấy phép phiên bản 4: font SIL OFL 1.1, code MIT, tài liệu CC BY 3.0 — Font Awesome
-
Giấy phép bản Free: biểu tượng CC BY 4.0, font SIL OFL 1.1, code MIT — Font Awesome
-
font-awesome.css trên nhánh 19.0, bản vendor 4.7.0 — Odoo ↩︎
-
PR #273606 — thêm hệ biểu tượng Material Symbols, gỡ thư viện Font Awesome — Odoo ↩︎
-
commit 3e15a7b — [IMP] web, *: add Material Symbols icon system (2026-04-01) — Odoo ↩︎
-
commit cda1af3 — [IMP] web, html_editor: add a subset font for Material Symbols icons — Odoo
-
commit d4113ca — [IMP] *: remove FontAwesome font-family — Odoo ↩︎
-
PR #256840 — [IMP] *: migrate to Material Symbols icon system — Odoo ↩︎
-
commit c5a40a6 — [IMP] *: apply new icon implementation globally (2026-07-09) — Odoo ↩︎
-
commit 546477dafab3 — [ADD] web: .oi, odoo ui icons (2022-01-26, Odoo 16.0) — Odoo ↩︎
-
fa_to_ms.scss — bản đồ 792 mục từ Font Awesome 4 sang Material Symbols — Odoo ↩︎
-
icons.scss — class nền .oi, bổ ngữ oi-filled và các tiện ích cỡ biểu tượng — Odoo ↩︎
-
rtl_icons.scss — các biểu tượng có hướng được lật gương dưới .o_rtl — Odoo ↩︎
-
mail.py — _get_icon_rendering_info và nhánh Font Awesome legacy — Odoo ↩︎
-
mã nguồn module base_fontawesome (18.0) — OCA server-tools ↩︎
-
mã nguồn module base_fontawesome_web_editor (18.0) — OCA server-tools ↩︎