I'm trying to approach translations (aka i18n) in Postgres, so far I've come up with a following pattern for storing strings in master language and its translations:
-- available translation languages
CREATE TYPE lang AS ENUM ('fr', 'de', 'cn');
-- strings in master language, e.g. en
CREATE TABLE strings (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
string text NOT NULL
);
-- strings translations in other languages, e.g. fr, de
CREATE TABLE translations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
string_id uuid NOT NULL REFERENCES strings (id) ON UPDATE CASCADE ON DELETE CASCADE,
lang lang NOT NULL,
string text NOT NULL
);
-- a collection of things with a name and a description
CREATE TABLE things (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name uuid REFERENCES strings (id) ON UPDATE CASCADE,
description uuid REFERENCES strings (id) ON UPDATE CASCADE
);
So basically a Thing has a name and a description and they both reference Strings by id. A String has a master language text (strings.string), and also there are Translations which reference Strings by id.
A small example:
db=# select id, name, description from things;
id | name | description
--------------------------------------+--------------------------------------+--------------------------------------
df2ac652-cae7-4c90-ad85-05793e67ba47 | ce5a6cb6-6f14-4775-bed8-62ed871fdefc | 635e144d-f64f-4e2b-90f8-1280b1b7d24e
(1 row)
db=# select strings.id, strings.string from strings inner join things on (strings.id = things.name or strings.id = things.description); id | string
--------------------------------------+-----------------------------------
ce5a6cb6-6f14-4775-bed8-62ed871fdefc | Cool Thing
635e144d-f64f-4e2b-90f8-1280b1b7d24e | Some Cool Thing description here
(2 rows)
The only problem is that I can't figure out a proper efficient way to retrieve Things with substituted values for a particular language. Say, I want to retrieve it in master language, then I'd probably do a join:
SELECT
things.id AS id,
strings.string AS name
FROM things
INNER JOIN strings
ON (things.name = strings.id);
This would return:
id | name
-------------------------------------+------------
df2ac652-cae7-4c90-ad85-05793e67ba47 | Cool Thing
(1 row)
But I cannot add description, since I've already used strings.string AS name
in the above query.
Maybe my approach to i18n is fundamentally wrong and I'm not seeing a simpler solution here. Any help is very much appreciated.
答案 0 :(得分:1)
You can just chain the join
s together:
SELECT t.id AS id, s.string AS name, trfr.string as name_fr
FROM things t INNER JOIN
strings s
ON t.name = s.id INNER JOIN
translations trfr
ON tr.id = t.name AND lang = 'fr';
I do find your data model a bit confusing.
First, use serial
for the primary key instead of uuids, unless you have a real business reason for using uuids. Numbers are much easier to work with.
Second, having a table with string
and string_id
is just confusing. Your names should be clearer. Maybe something like: string_id
and string_in_language
.
Third, I would not make lang
an enumerated type. I would make it a reference table. You might definitely want to store additional information about the language -- say, the default first day of the week to use, or the full name, or the default currency symbol.
答案 1 :(得分:0)
Gordon Linoff gave me a hint, so I'll post an answer in case anyone else has the same issue
SELECT
t.id AS id,
tn.string AS name, -- name translation
td.string AS description -- description translation
FROM things t
INNER JOIN translations tn
ON t.name = tn.string_id
INNER JOIN translations td
ON t.description = td.string_id
WHERE tn.lang = 'fr' AND td.lang = 'fr';
To use a fallback master language:
SELECT
t.id AS id,
COALESCE(tn.string, sn.string) AS name, -- name translation
COALESCE(td.string, sd.string) AS description -- description translation
FROM things t
LEFT OUTER JOIN strings sn
ON t.name = sn.id
LEFT OUTER JOIN strings sd
ON t.description = sd.id
LEFT OUTER JOIN translations tn
ON t.name = tn.string_id
AND tn.lang = 'fr'
LEFT OUTER JOIN translations td
ON t.description = td.string_id
AND td.lang = 'fr';