| Strings Words Characters | |||
|---|---|---|---|
| 726 3,918 30,760 |
|
All strings | Browse Translate Zen |
| 568 2,894 21,224 |
|
Translated strings | Browse Translate Zen |
| 568 2,894 21,224 |
|
Strings waiting for review | Browse Translate Zen |
| 158 1,024 9,536 |
|
Unfinished strings | Browse Translate Zen |
| 157 622 4,188 |
|
Untranslated strings | Browse Translate Zen |
| 1 402 5,348 |
|
Strings marked for edit | Browse Translate Zen |
| 6 57 365 |
|
Strings with any failing checks | Browse Translate Zen |
| 6 57 365 |
|
Translated strings with any failing checks | Browse Translate Zen |
| 1 2 10 |
|
Failing check: Double space | Browse Translate Zen |
| 3 30 200 |
|
Failing check: Mismatched full stop | Browse Translate Zen |
| 1 9 54 |
|
Failing check: Mismatched exclamation mark | Browse Translate Zen |
| 2 25 155 |
|
Failing check: XML markup | Browse Translate Zen |
Summary
| Project website | www.odoo.com | |
|---|---|---|
| Project reviewers45 |
|
|
| Translation license | BSD 2-Clause "Simplified" License | |
| Translation process |
|
|
| File mask |
web_studio/i18n/*.po
|
|
| Translation file |
Download
web_studio/i18n/zh_CN.po
|
|
| Last change | May 12, 2026, 2:05 a.m. | |
| Last change made by | Chloe Wang (chwa) | |
| Language | Chinese (Simplified Han script) | |
| Language code | zh_Hans | |
| Text direction | Left to right | |
| Case sensitivity | Case-insensitive | |
| Number of speakers | 1,286,444,445 | |
| Number of plurals | 1 | |
| Plural type | None | |
| Plurals | ||
| Plural formula |
0
|
|
05/12/2026
String statistics
| Strings percent | Hosted strings | Words percent | Hosted words | Characters percent | Hosted characters | |
|---|---|---|---|---|---|---|
| Total | 726 | 3,918 | 30,760 | |||
| Approved | 0% | 0 | 0% | 0 | 0% | 0 |
| Waiting for review | 78% | 568 | 73% | 2,894 | 68% | 21,224 |
| Translated | 78% | 568 | 73% | 2,894 | 68% | 21,224 |
| Needs editing | 1% | 1 | 10% | 402 | 17% | 5,348 |
| Read-only | 0% | 0 | 0% | 0 | 0% | 0 |
| Failing checks | 1% | 6 | 1% | 57 | 1% | 365 |
| Strings with suggestions | 0% | 0 | 0% | 0 | 0% | 0 |
| Untranslated strings | 21% | 157 | 15% | 622 | 13% | 4,188 |
Quick numbers
and previous 30 days
Trends of last 30 days
—
Hosted words
—
—
Hosted strings
—
—
Translated
—
—
Contributors
—
|
Translation added |
|
|
Translation added |
|
|
Contributor joined |
Contributor joined
05/11/2026
|
None
Resource updated |
The “
web_studio/i18n/zh_CN.po” file was changed.
05/04/2026
|
None
String added in the repository |
|
None
String added in the repository |
|
None
String added in the repository |
|
None
String added in the repository |
|
None
String added in the repository |
|
None
String added in the repository |
|
| 726 | File in original format as translated in the repository | gettext PO file | |||||||
|---|---|---|---|---|---|---|---|---|---|
| 726 | All strings, converted files enriched with comments; suitable for offline translation | CSV | gettext MO | gettext PO | TBX | TMX | XLIFF 1.1 with gettext extensions | XLIFF 1.1 | XLSX |
| 158 | Unfinished strings, converted files enriched with comments; suitable for offline translation | CSV | gettext MO | gettext PO | TBX | TMX | XLIFF 1.1 with gettext extensions | XLIFF 1.1 | XLSX |
None
function copyElementOnDrag() {
let element;
let copy;
function clone(_element) {
element = _element;
copy = element.cloneNode(true);
}
function insert() {
if (element) {
element.insertAdjacentElement("beforebegin", copy);
}
}
function clean() {
if (copy) {
copy.remove();
}
copy = null;
element = null;
}
return { clone, insert, clean };
}
export class InteractiveEditor extends Component {
static template = "web_studio.InteractiveEditor";
static components = {};
static props = {
editor: true,
slots: { type: Object },
editorContainerRef: { type: Object },
rendererRef: { type: Object },
};
setup() {
this.defaultSidebar = DefaultViewSidebar;
this.action = useService("action");
this.orm = useService("orm");
this.addDialog = useOwnedDialogs();
this.notification = useService("notification");
/* DagDrop: from sidebar to View, and within the view */
const getNearestHook = this.getNearestHook.bind(this);
// Those are fine because editor defines the t-key
const prepareForDrag = this.props.editor.prepareForDrag;
const editorHookValidation = this.props.editor.isValidHook || (() => true);
const isValidHook = ({ hook, element }) => {
if (hook.dataset.hookId) {
if (!this.viewEditorModel.dropHooks[hook.dataset.hookId].accept({ element })) {
return false;
}
} else if (
!editorHookValidation({ hook, element, viewEditorModel: this.viewEditorModel })
) {
return false;
}
return true;
};
this.addViewStructure = this.props.editor.addViewStructure;
const styleNearestHook =
this.props.editor.styleNearestHook ||
((ref, hook) => {
hook.classList.add("o_web_studio_nearest_hook");
});
function removeBootStrapClasses(element) {
const bootstrapClasses = Array.from(element.classList).filter(
(c) => c.startsWith("position-") || c.startsWith("w-") || c.startsWith("h-")
);
if (!bootstrapClasses.length) {
return () => {};
}
element.classList.remove(...bootstrapClasses);
return () => {
element.classList.add(...bootstrapClasses);
};
}
let cleanUps;
const copyOnDrag = copyElementOnDrag();
const editorHighlightHooks = this.props.editor.highlightHooks;
const highlightHooks = ({ element }) => {
const hooks = getHooks(this.viewRef.el).filter((hook) =>
isValidHook({ hook, element })
);
if (editorHighlightHooks) {
return editorHighlightHooks({ element, hooks });
}
return () => {};
};
useDraggable({
ref: this.props.editorContainerRef,
elements: ".o-draggable",
onWillStartDrag: ({ element }) => {
cleanUps = [];
if (element.closest(".o_web_studio_component")) {
copyOnDrag.clone(element);
}
},
onDragStart: ({ element }) => {
cleanUps.push(removeBootStrapClasses(element));
cleanUps.push(highlightHooks({ element }));
copyOnDrag.insert();
if (prepareForDrag) {
cleanUps.push(
prepareForDrag({
element,
viewEditorModel: this.viewEditorModel,
ref: this.props.editorContainerRef,
})
);
}
},
onDrag: ({ x, y, element }) => {
cleanHooks(this.viewRef.el);
element.classList.remove("o-draggable--drop-ready");
const hook = getNearestHook(element, { x, y });
if (!hook) {
return;
}
if (!isValidHook({ hook, element })) {
return;
}
styleNearestHook(this.props.rendererRef, hook);
element.classList.add("o-draggable--drop-ready");
},
onDrop: ({ element }) => {
const targetHook = getActiveHook(this.viewRef.el);
if (!targetHook) {
return;
}
let targetInfo = pick(targetHook.dataset, "xpath", "position", "type", "infos");
if (targetHook.dataset.hookId) {
targetInfo = this.viewEditorModel.dropHooks[
targetHook.dataset.hookId
].getHookConfig({ targetInfo });
}
const droppedData = element.dataset;
const isNew = element.classList.contains("o_web_studio_component");
const structure = isNew ? droppedData.structure : "field"; // only fields can be moved
if (isNew) {
const dropInfo = droppedData.drop ? JSON.parse(droppedData.drop) : undefined;
this.addStructure(structure, dropInfo, targetInfo);
} else {
this.moveStructure(structure, droppedData, {
xpath: targetInfo.xpath,
position: targetInfo.position,
});
}
},
onDragEnd: ({ element }) => {
cleanHooks(this.viewRef.el);
if (cleanUps) {
cleanUps.forEach((c) => c());
cleanUps = null;
}
copyOnDrag.clean();
},
});
this.applyAutoClick = async () => {
if (!this.autoClick) {
return;
}
const { targetInfo, tag, attrs } = this.autoClick;
// First step: locate node in new arch
let xpathToClick = targetInfo.xpath;
if (tag) {
// We are trying to select a new node of which targetInfo could be its parent
if (targetInfo.position !== "inside") {
xpathToClick = xpathToClick.split("/").slice(0, -1).join("/");
}
const attrForXpath = Object.entries(attrs)
.filter(([, value]) => !!value)
.map(([attName, value]) =>
function copyElementOnDrag() {
let element;
let copy;
function clone(_element) {
element = _element;
copy = element.cloneNode(true);
}
function insert() {
if (element) {
element.insertAdjacentElement("beforebegin", copy);
}
}
function clean() {
if (copy) {
copy.remove();
}
copy = null;
element = null;
}
return { clone, insert, clean };
}
export class InteractiveEditor extends Component {
static template = "web_studio.InteractiveEditor";
static components = {};
static props = {
editor: true,
slots: { type: Object },
editorContainerRef: { type: Object },
rendererRef: { type: Object },
};
setup() {
this.defaultSidebar = DefaultViewSidebar;
this.action = useService("action");
this.orm = useService("orm");
this.addDialog = useOwnedDialogs();
this.notification = useService("notification");
/* DagDrop: from sidebar to View, and within the view */
const getNearestHook = this.getNearestHook.bind(this);
// Those are fine because editor defines the t-key
const prepareForDrag = this.props.editor.prepareForDrag;
const editorHookValidation = this.props.editor.isValidHook || (() => true);
const isValidHook = ({ hook, element }) => {
if (hook.dataset.hookId) {
if (!this.viewEditorModel.dropHooks[hook.dataset.hookId].accept({ element })) {
return false;
}
} else if (
!editorHookValidation({ hook, element, viewEditorModel: this.viewEditorModel })
) {
return false;
}
return true;
};
this.addViewStructure = this.props.editor.addViewStructure;
const styleNearestHook =
this.props.editor.styleNearestHook ||
((ref, hook) => {
hook.classList.add("o_web_studio_nearest_hook");
});
function removeBootStrapClasses(element) {
const bootstrapClasses = Array.from(element.classList).filter(
(c) => c.startsWith("position-") || c.startsWith("w-") || c.startsWith("h-")
);
if (!bootstrapClasses.length) {
return () => {};
}
element.classList.remove(...bootstrapClasses);
return () => {
element.classList.add(...bootstrapClasses);
};
}
let cleanUps;
const copyOnDrag = copyElementOnDrag();
const editorHighlightHooks = this.props.editor.highlightHooks;
const highlightHooks = ({ element }) => {
const hooks = getHooks(this.viewRef.el).filter((hook) =>
isValidHook({ hook, element })
);
if (editorHighlightHooks) {
return editorHighlightHooks({ element, hooks });
}
return () => {};
};
useDraggable({
ref: this.props.editorContainerRef,
elements: ".o-draggable",
onWillStartDrag: ({ element }) => {
cleanUps = [];
if (element.closest(".o_web_studio_component")) {
copyOnDrag.clone(element);
}
},
onDragStart: ({ element }) => {
cleanUps.push(removeBootStrapClasses(element));
cleanUps.push(highlightHooks({ element }));
copyOnDrag.insert();
if (prepareForDrag) {
cleanUps.push(
prepareForDrag({
element,
viewEditorModel: this.viewEditorModel,
ref: this.props.editorContainerRef,
})
);
}
},
onDrag: ({ x, y, element }) => {
cleanHooks(this.viewRef.el);
element.classList.remove("o-draggable--drop-ready");
const hook = getNearestHook(element, { x, y });
if (!hook) {
return;
}
if (!isValidHook({ hook, element })) {
return;
}
styleNearestHook(this.props.rendererRef, hook);
element.classList.add("o-draggable--drop-ready");
},
onDrop: ({ element }) => {
const targetHook = getActiveHook(this.viewRef.el);
if (!targetHook) {
return;
}
let targetInfo = pick(targetHook.dataset, "xpath", "position", "type", "infos");
if (targetHook.dataset.hookId) {
targetInfo = this.viewEditorModel.dropHooks[
targetHook.dataset.hookId
].getHookConfig({ targetInfo });
}
const droppedData = element.dataset;
const isNew = element.classList.contains("o_web_studio_component");
const structure = isNew ? droppedData.structure : "field"; // only fields can be moved
if (isNew) {
const dropInfo = droppedData.drop ? JSON.parse(droppedData.drop) : undefined;
this.addStructure(structure, dropInfo, targetInfo);
} else {
this.moveStructure(structure, droppedData, {
xpath: targetInfo.xpath,
position: targetInfo.position,
});
}
},
onDragEnd: ({ element }) => {
cleanHooks(this.viewRef.el);
if (cleanUps) {
cleanUps.forEach((c) => c());
cleanUps = null;
}
copyOnDrag.clean();
},
});
this.applyAutoClick = async () => {
if (!this.autoClick) {
return;
}
const { targetInfo, tag, attrs } = this.autoClick;
// First step: locate node in new arch
let xpathToClick = targetInfo.xpath;
if (tag) {
// We are trying to select a new node of which targetInfo could be its parent
if (targetInfo.position !== "inside") {
xpathToClick = xpathToClick.split("/").slice(0, -1).join("/");
}
const attrForXpath = Object.entries(attrs)
.filter(([, value]) => !!value)
.map(([attName, value]) =>