You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Odoo RPC为ir.ui.view的arch_db字段写入多语言HTML?

Odoo中为ir.ui.view的arch_db字段添加多语言内容的正确方式

问题描述

我想给ir.ui.view对象的arch_db字段写入多语言内容,但遇到两个问题:

  • 直接传入以语言为键、HTML为值的字典/JSON(比如{"de_DE": "德语HTML", "en_US": "美式英语HTML"})会触发验证失败;
  • 先在de_DE上下文写入HTML,再切换到en_US上下文写入时,后者会覆盖前者的所有语言内容。

请问如何为不同语言写入对应的HTML?是否可以通过RPC调用update_raw方法?


示例代码(原错误实现)

// 本示例展示错误的多语言视图创建方式
package main

import (
    "errors"
    "fmt"
    "log"

    "github.com/kolo/xmlrpc"
)

func main() {
    viewArch := TranslatedHTML{
        LangDE: `<p>Deutscher Text</p>`,
        LangEN: `<p>English text</p>`,
    }
    cl, err := NewClient(
        "http://localhost:3017",
        "odoo_17",
        "admin",
        "admin",
        LangDE,
    )
    panicOnErr(err)
    reply, err := cl.CreateView(viewArch)
    panicOnErr(err)
    fmt.Println(reply)
}

func panicOnErr(err error) {
    if err != nil {
        panic(err)
    }
}

func wrapErr(err error, msg string) error {
    if err != nil {
        return fmt.Errorf("%s: %w", msg, err)
    }
    return nil
}

type Lang string

const LangDE = Lang("de_DE")
const LangEN = Lang("en_US")

type Client struct {
    *xmlrpc.Client
    ContextLang Lang
    uid         int // 存储登录后的用户ID

    // 每次调用所需参数:
    OdooDB   string
    Username string
    Password string
}

func NewClient(url, odooDB, username, password string, contextLang Lang) (*Client, error) {
    loginClient, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil)
    if err != nil {
        return nil, wrapErr(err, "创建登录客户端失败")
    }

    var uid int
    err = loginClient.Call("authenticate", []any{
        odooDB, username, password, map[string]any{},
    }, &uid)
    if err != nil {
        return nil, wrapErr(err, "认证失败")
    }

    client, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil)
    if err != nil {
        return nil, wrapErr(err, "创建对象客户端失败")
    }

    return &Client{client, contextLang, uid, odooDB, username, password}, nil
}

func (c *Client) WithContextLang(contextLang Lang) *Client {
    return &Client{c.Client, contextLang, c.uid, c.OdooDB, c.Username, c.Password}
}

type TranslatedHTML map[Lang]string

func (th TranslatedHTML) Langs() []Lang {
    langs := make([]Lang, 0, len(th))
    for lang := range th {
        langs = append(langs, lang)
    }
    return langs
}

func (cl *Client) ExecuteKW(model, method string, args, reply any) error {
    return cl.Call(
        "execute_kw",
        []any{cl.OdooDB, cl.uid, cl.Password, model, method, args, map[string]any{"context": map[string]string{"lang": string(cl.ContextLang)}}},
        reply,
    )
}

func (cl *Client) CreateView(arch TranslatedHTML) (any, error) {
    langs := arch.Langs()
    if (len(langs)) == 0 {
        return nil, errors.New("未提供翻译内容")
    }
    firstLang := langs[0]
    restLangs := langs[1:]
    var reply any
    err := cl.WithContextLang(firstLang).ExecuteKW("ir.ui.view", "create", []any{map[string]string{"arch_db": arch[firstLang], "type": "qweb"}}, &reply)
    if err != nil {
        return reply, err
    }
    log.Printf("已创建视图,ID %d,语言 %s,内容 %s", reply.(int64), firstLang, arch[firstLang])
    viewID := reply.(int64)
    for _, lang := range restLangs {
        var reply any
        err := cl.WithContextLang(lang).ExecuteKW("ir.ui.view", "write", []any{viewID, map[string]any{"arch_db": arch[lang]}}, &reply)
        if err != nil {
            return reply, err
        }
        log.Printf("已更新视图,语言 %s,结果 %v,内容 %s", lang, reply, arch[lang])
    }
    return nil, nil
}

解决方案

核心结论

可以通过RPC调用update_raw方法来实现,这是Odoo官方提供的专门用于更新视图多语言架构的方法,不会出现覆盖问题。此外也可以通过操作翻译模型手动添加翻译,以下是具体实现:

方案1:使用update_raw方法(推荐)

ir.ui.view的update_raw方法支持在指定语言上下文下,将传入的HTML作为该语言的翻译版本存储,不会覆盖基准语言或其他语言的内容。步骤如下:

  1. 先在基准语言(比如en_US)下创建视图;
  2. 切换到目标语言上下文,调用update_raw传入对应语言的HTML架构。

修正后的代码实现

// 示例:正确创建带多语言翻译的视图
package main

import (
    "errors"
    "fmt"
    "log"

    "github.com/kolo/xmlrpc"
)

func main() {
    viewArch := TranslatedHTML{
        LangDE: `<p>Deutscher Text</p>`,
        LangEN: `<p>English text</p>`,
    }
    cl, err := NewClient(
        "http://localhost:3017",
        "odoo_17",
        "admin",
        "admin",
        LangEN, // 以en_US作为基准语言
    )
    panicOnErr(err)
    reply, err := cl.CreateViewWithTranslations(viewArch)
    panicOnErr(err)
    fmt.Println(reply)
}

func panicOnErr(err error) {
    if err != nil {
        panic(err)
    }
}

func wrapErr(err error, msg string) error {
    if err != nil {
        return fmt.Errorf("%s: %w", msg, err)
    }
    return nil
}

type Lang string

const LangDE = Lang("de_DE")
const LangEN = Lang("en_US")

type Client struct {
    *xmlrpc.Client
    ContextLang Lang
    uid         int // 存储登录后的用户ID

    // 每次调用所需参数:
    OdooDB   string
    Username string
    Password string
}

func NewClient(url, odooDB, username, password string, contextLang Lang) (*Client, error) {
    loginClient, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/common", url), nil)
    if err != nil {
        return nil, wrapErr(err, "创建登录客户端失败")
    }

    var uid int
    err = loginClient.Call("authenticate", []any{
        odooDB, username, password, map[string]any{},
    }, &uid)
    if err != nil {
        return nil, wrapErr(err, "认证失败")
    }

    client, err := xmlrpc.NewClient(fmt.Sprintf("%s/xmlrpc/2/object", url), nil)
    if err != nil {
        return nil, wrapErr(err, "创建对象客户端失败")
    }

    return &Client{client, contextLang, uid, odooDB, username, password}, nil
}

func (c *Client) WithContextLang(contextLang Lang) *Client {
    return &Client{c.Client, contextLang, c.uid, c.OdooDB, c.Username, c.Password}
}

type TranslatedHTML map[Lang]string

func (cl *Client) ExecuteKW(model, method string, args, reply any) error {
    return cl.Call(
        "execute_kw",
        []any{cl.OdooDB, cl.uid, cl.Password, model, method, args, map[string]any{"context": map[string]string{"lang": string(cl.ContextLang)}}},
        reply,
    )
}

func (cl *Client) CreateViewWithTranslations(arch TranslatedHTML) (any, error) {
    // 确认基准语言内容存在
    baseArch, hasBase := arch[LangEN]
    if !hasBase {
        return nil, errors.New("必须提供基准语言en_US的内容")
    }

    // 1. 创建基准语言视图
    var viewID any
    err := cl.WithContextLang(LangEN).ExecuteKW("ir.ui.view", "create", []any{map[string]string{"arch_db": baseArch, "type": "qweb"}}, &viewID)
    if err != nil {
        return viewID, err
    }
    viewIDInt := viewID.(int64)
    log.Printf("已创建基准视图,ID: %d,语言: %s,内容: %s", viewIDInt, LangEN, baseArch)

    // 2. 为其他语言添加翻译,使用update_raw方法
    for lang, html := range arch {
        if lang == LangEN {
            continue // 跳过基准语言
        }
        var reply any
        // 调用update_raw更新对应语言的视图架构
        err := cl.WithContextLang(lang).ExecuteKW("ir.ui.view", "update_raw", []any{viewIDInt, html}, &reply)
        if err != nil {
            return reply, wrapErr(err, fmt.Sprintf("更新语言%s失败", lang))
        }
        log.Printf("已更新视图翻译,语言: %s,内容: %s", lang, html)
    }

    return viewIDInt, nil
}

方案2:手动操作ir.translation模型

如果无法使用update_raw,可以直接向翻译模型添加记录:

  • 模型:ir.translation
  • 所需字段:
    • name: 固定为"ir.ui.view,arch_db"
    • res_id: 目标视图的ID
    • lang: 目标语言代码(如de_DE)
    • type: 固定为"model"
    • value: 对应语言的HTML内容

关键说明

  • 直接用write方法会覆盖基准语言内容,因为Odoo默认将传入的arch_db视为基准语言值,而非翻译;
  • 传入字典格式验证失败,是因为arch_db字段本身是字符串类型,不支持字典输入;
  • update_raw方法会自动处理翻译逻辑,在对应语言上下文调用时,会将内容存储为该语言的翻译版本。

内容的提问来源于stack exchange,提问作者Jasper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 03:14:54