arrow_back All posts

Translating a Semantic Model in TMDL: Fixing the Field Parameter Labels Together with AI While I Govern Every Change

A Korean folk art illustration of a wooden bookshelf with a framed sign reading 'A culture file translates names, not values', three wrapped book cases labelled en-US, ko-KR, and nl-NL, and a locked wooden chest labelled values, suggesting that a culture file opens every name in the model while the values need a different key

In this writing, I want to share how I added Korean and Dutch to my Contoso semantic model by writing culture files in TMDL, and how one field parameter showed me where a culture file stops. I fixed that last part together with AI, and I checked every change it made before I committed it.

My plan was to keep this a model-only change: one culture per language, applied in TMDL view, then the report opened in the Power BI service in that language. I wanted to know which words on a report page come from the names in the model, and which come from somewhere a culture file cannot reach. The first two commits went as planned. The third one is the reason I am writing this.

1. One Culture Already There, and Nine Captions to Write

I ran everything against directlake_import_composite, the composite model behind my contoso_project report, part Direct Lake and part import. I was live editing it from Power BI Desktop with the project saved as a PBIP, and that detail comes back in section 3.

The model already had one culture. Model explorer listed en-US under Cultures, which I had added in an earlier commit.

Power BI Desktop Model view with the Live editing your model using Direct Lake banner across the top, and Model explorer on the right listing Cultures (1) with en-US underneath

The Cultures section of the Use Model explorer in Power BI article describes this area as the place to view all translated versions of the data model.

Then I picked what to translate. I started with the sales table and two of its measures, Sales Amount and Gross Margin %.

Model explorer with the sales table expanded, its thirteen columns and six measures listed, and the Gross Margin % and Sales Amount measures underlined in red

From each of the other tables I picked one column: CategoryName in product, Birthday in customer, and Month in date. With the four table names, that made nine captions, a set small enough to check by eye.

2. Writing the Korean Culture in TMDL View

Power BI Desktop has no dialog for translations. In the Common use cases for TMDL view section of the Use Tabular Model Definition Language (TMDL) view in Power BI article, the perspectives scenario ends by saying the same method works for other semantic model metadata that lack a graphical interface, such as translations. I asked AI for a starting script built from the objects I had highlighted, and pasted it into a new TMDL view tab.

TMDL view in Power BI Desktop with a tab named Script 1 holding a createOrReplace script for cultureInfo ko-KR with translations and model Model, Korean captions for the sales, product, customer, and date tables, two sales measures, and three columns, and the Problems tab showing 0

Under cultureInfo ko-KR come translations, model Model, and each table, with its measures and columns inside it, each carrying a caption. The Translations in TMDL section of the Object definitions in Tabular Model Definition Language (TMDL) article explains that TMDL replicates the model hierarchy underneath each culture, and that three properties can be translated: caption, description, and displayFolder. I only needed the first.

Its example starts with culture pt-PT. The culture already in my model is written as cultureInfo en-US, as the preview below shows, so I used cultureInfo to match, and the Problems tab stayed at 0. I did not try the other spelling, so I can only tell you which one my files use.

Before applying, I clicked Preview.

The TMDL view Preview pane comparing the model before and after the script, with cultureInfo en-US unchanged on both sides and a new cultureInfo ko-KR block added below it in green, listing sales with Sales Amount before Gross Margin %, then customer, product, and date

The new block landed below cultureInfo en-US, which stayed untouched. The order inside it was not mine. My script had Gross Margin % before Sales Amount and product before customer, and the preview swapped both pairs. A note in the Preview changes to the semantic model section of the Use Tabular Model Definition Language (TMDL) view in Power BI article explains why: the preview compares the model before and after the script runs, not the script itself, so objects appear in the default TMDL order.

I applied it.

TMDL view after Apply showing the notification Changes applied to the model, with Model explorer on the right now listing Cultures (2)

Changes applied to the model. Model explorer went from one culture to two. Then I switched to the report view to see whether Desktop would show any of it, and it did not.

The Sales Overview page in Power BI Desktop with a prm_dim slicer listing Gender, Year, and Brand, a table with Year, Sales Amount, Total Cost, Gross Margin, and Gross Margin % columns, and the Data pane, all in English

This one is documented. In the Organize project for metadata translation section of the Use Locale Values in Multiple-Language Power BI Reports article, Power BI Desktop doesn't load metadata translations in its report designer, only semantic model object names. So the screen I spend most of my day in was the one place I could not check this work.

3. Three Files in the Commit, and None of Them in the Report

Saving the project wrote the change to disk, and VS Code showed where it went.

VS Code with the directlake_import_composite.SemanticModel definition folder open, a new ko-KR.tmdl marked U next to en-US.tmdl in the cultures folder, model.tmdl and TMDLScripts Script 1.tmdl marked M, and ko-KR.tmdl open in the editor

A new ko-KR.tmdl sits in the cultures folder next to en-US.tmdl, its contents in the same order the preview showed. The TMDL folder structure section of the Tabular Model Definition Language (TMDL) article lists one file for each culture linguistic schema, so a new language is a new file rather than an edit to an existing one.

The second change is one line in model.tmdl.

The model.tmdl diff in VS Code with culture: en-US on line 2, the ref table lines from sales to _CalcGroup_Period, and one added line, ref cultureInfo ko-KR, below ref cultureInfo en-US

ref cultureInfo ko-KR, below the en-US one, with the model's own language, culture: en-US, still on line 2. The Deterministic collection ordering section of the Tabular Model Definition Language (TMDL) article explains that these ref lines keep the order of cultures stable between saves.

The third file is not part of the model at all.

The diff of TMDLScripts Script 1.tmdl in VS Code, empty on the left and holding the whole createOrReplace ko-KR script on the right

My TMDL view tab was saved as a file too. The TMDL script tabs section of the Use Tabular Model Definition Language (TMDL) view in Power BI article says that in a Power BI Project, each script tab is saved as a .tmdl file in the TMDLScripts folder, so the script I ran went into the repository next to the file it produced.

The VS Code Source Control pane with the commit message KR translate tmdl update and three staged files, model.tmdl, ko-KR.tmdl, and Script 1.tmdl, all under directlake_import_composite.SemanticModel

Three files, all under the semantic model folder, and not one under contoso_project.Report. The Power BI support for metadata translation section of the Plan Translation for Multiple-Language Reports in Power BI article describes a translated caption as an alternative name that appears when the report is rendered in a different language. The object keeps its own name, and the report keeps pointing at it.

One more thing, to be precise. The commit is not what put Korean into the service. The opening of the Learn About Editing Semantic Models in Direct Lake in Power BI Desktop article says there is no publish action when you live edit, because changes made in Power BI Desktop happen to the semantic model in the Fabric workspace. The captions arrived there when I clicked Apply. The commit was for my repository.

4. The Service Followed My Browser

I opened the report in the Power BI service, and it was in English.

The Sales Overview report in the Power BI service in English, with the same prm_dim slicer and table as in Desktop and an empty Filters pane

That was correct, because my browser had asked for English. In the Load a report in Power BI section of the Use Locale Values in Multiple-Language Power BI Reports article, the browser sends an Accept-Language header with a culture name, the service uses it to set the language and locale of the report, and users control that value through their regional settings. In Edge, the Preferred languages list says websites will appear in the first language in the list that they support, so I moved Korean to the top.

Edge settings on the Languages page with Korean moved to the top of Preferred languages, above English (United States) and English

Then I reloaded the report.

The same report after the browser change, with the Power BI menus and Filters pane in Korean and two table headers, Sales Amount and Gross Margin %, showing Korean captions while Year, Total Cost, Gross Margin, and the slicer stay in English

Two column headers turned Korean, exactly the two measures I had translated. Total Cost, Gross Margin, and the slicer stayed in English because they were never in my script. The menus, the Filters pane, and the Total under the last row changed as well, and those words come from Power BI, not from my model.

5. Asking AI for the Rest of the Page, and for Dutch

Nine captions were enough to learn the format. For the rest of the page I turned to Claude Code in VS Code, pointed it at my commit, and asked for every name shown on the Sales Overview page, plus a second language.

Claude Code in VS Code with a prompt asking it to check the last commit, apply the same kind of translation to what is shown on the Sales Overview page such as Gender, Year, Brand, and Total Cost, and add a Dutch translation, next to the commit details for KR translate tmdl update

It extended the Korean file first.

The ko-KR.tmdl diff in VS Code with added captions for the Total Cost and Gross Margin measures, the Gender, Brand, and Year columns, and a new prm_dim table block with a caption for the table and one for its prm_dim column

Total Cost and Gross Margin, as asked. Gender in customer, Brand in product, and Year in date, the three fields behind my slicer. And one block I had not asked for by name, for prm_dim, the field parameter table, with a caption for the table and one for its column.

That last block made me look twice. The field parameter got captions for itself, but nothing for Gender, Year, or Brand inside it. Those three were translated in their own tables instead. My guess was that the slicer would pick them up from there, and the next section is where that guess was tested.

A new nl-NL.tmdl open in VS Code with Dutch captions such as Verkoop, Verkoopbedrag, Totale kosten, Brutomarge, Klant, Geslacht, Merk, Datum, Jaar, Maand, Dimensieselectie, and Dimensie

The Dutch file covers the same objects, from Verkoopbedrag for Sales Amount to Dimensie for the field parameter's column, and model.tmdl gained one more line.

The model.tmdl diff in VS Code with ref cultureInfo nl-NL added as line 27, below the en-US and ko-KR lines The VS Code Source Control pane with the commit message update kr-KR and add nl-NL and three staged files, model.tmdl, ko-KR.tmdl, and nl-NL.tmdl

Again three files, all in the semantic model. These edits were made on disk rather than in Desktop, so they reached the workspace through Git: I pushed, then updated the workspace from Source control. The Considerations and limitations section of the Learn About Editing Semantic Models in Direct Lake in Power BI Desktop article says a Power BI Project can't be published from Power BI Desktop, and names Fabric Git integration as one way to bring local PBIP files to a workspace. For Dutch I moved Dutch to the top of the same browser list.

6. The Labels No Culture File Could Reach

The report in Korean with the slicer header now in Korean but its three items still reading Gender, Year, and Brand, and the first column header of the table still reading Year while the four measure headers are in Korean

The slicer header changed. That is the caption of the field parameter's column, the one the AI added. The three items under it did not change, and neither did the Year header on the table, whose first column comes from the same field parameter. My guess was wrong.

The report in Dutch with the slicer header Dimensie, its items still Gender, Year, and Brand, the table headers Year, Verkoopbedrag, Totale kosten, Brutomarge, and Brutomarge %, and numbers shown with dots for thousands and a comma for decimals

Dutch gave the same result, with one difference that needed no work from me: the number format. In the 2016 row, the English report shows $659,936,155 for Sales Amount and 56.3% for Gross Margin %. The Dutch report shows the same two values as $659.936.155 and 56,3%, with a dot to group thousands and a comma for decimals. The Format dates and numbers with current user locale section of the Use Locale Values in Multiple-Language Power BI Reports article says Power BI visuals automatically handle locale-specific formatting behind the scenes.

The reason the labels stayed in English is in the Edit translated names section of the Implement Data Translation Using Field Parameters article: when Power BI Desktop creates a field parameter, the names it shows are hard-coded into the DAX expression. Gender, Year, and Brand on my slicer are not names of anything in the model. They are three text values typed into a table.

That puts them on the other side of a line Microsoft draws between two kinds of translation. The Metadata translation section of the Plan Translation for Multiple-Language Reports in Power BI article covers tables, columns, measures, hierarchies, and hierarchy levels, and its Data translation section is about translated values for the data itself, naming field parameters as the feature that loads them. A culture file is metadata translation. My slicer needed data translation.

That answered the question I started with. A culture file translates the names in the model. A field parameter label is a value typed into DAX, and a value needs data translation.

7. Fixing the Field Parameter Together with AI, and Checking Each Part

I sent the Korean screenshot back to Claude Code with one sentence: the table name is translated, the fields inside it are not, fix it, and do the same for Dutch.

A Claude Code prompt with the Korean report screenshot attached, saying the field parameter table name is translated but the columns in the field parameter are not, and asking for a fix and the same for the Dutch translation

The fix went into the field parameter itself.

The prm_dim field parameter in Table view with a DAX expression of nine rows, Gender, Year, and Brand labelled in English, Korean, and Dutch, each with a fourth value naming its culture, and a hidden Culture column showing en-US, ko-KR, and nl-NL

Three rows became nine. Every field now appears once per language, with its label in that language and a fourth value naming the culture, which shows up as a new hidden Culture column. To show one language at a time, the AI added a hidden measure, Culture Match. It reads the first two letters of USERCULTURE(), turns them into ko-KR, nl-NL, or by default en-US, and returns 1 when the culture on the row matches.

Power BI Desktop with the Culture Match measure in the formula bar and prm_dim as its home table, and the slicer selected with a hidden visual-level filter, Culture Match is 1, in the Filters pane

Then it wrote a visual-level filter into each of the two visuals that use the field parameter. The slicer keeps the rows where Culture Match is 1.

Power BI Desktop with the table selected and a hidden visual-level filter on Culture set to Top N, showing the top 1 by Culture Match

The table got a different kind of filter, a Top N on the Culture column that keeps the one value ranked highest by Culture Match. The two filters differ because the two visuals list different things. The slicer lists the field parameter's own rows, so a filter on the measure can drop the rows for the other two languages. The table lists years or brands, so the same filter would be judged once per year or brand. The language is picked on the Culture column instead, where Top N keeps one culture for the whole table.

I checked both filter cards in Desktop before I committed, and both are hidden. The Lock or hide filters section of the Format filters in Power BI reports article says consumers can't even see a hidden filter, which is what my team's rule from Checking My Team's Rules Before the Pull Request asks for. Then I committed prm_dim.tmdl and the two visual.json files, pushed, and updated the workspace from Source control, the same route as the second commit.

The report in Korean with the slicer header and all three items in Korean, the Brand item selected, and the first column header of the table in Korean above brand names such as Adventure Works and Contoso The report in Dutch with the slicer Dimensie listing Geslacht, Jaar, and Merk, Merk selected, and the table headers Merk, Verkoopbedrag, Totale kosten, Brutomarge, and Brutomarge %

It worked in both languages. The brand names in the rows are data too, and I left them as they are, because a brand keeps its name in every language.

A result on screen was not enough for me. I wanted a documented reason for each part of the fix before I called it done, and Microsoft Learn has one for both the column and the measure.

The fourth value is a documented step. The Add a language ID column section of the Implement Data Translation Using Field Parameters article adds a fourth string to the row for each language in the field parameter's DAX to enable filtering by language, then hides the new column, since report authors never need to see a column used to select a language by filtering behind the scenes. My hidden Culture column follows that step, with full culture names such as ko-KR where the article uses two-character language identifiers such as en.

Choosing the language is where my report needed something different. In the Create a relationship section of the Add the Languages Table to Filter Field Parameters article, a Languages table is related to the field parameter, and the language is switched with a filter in the Filter pane. My captions already followed the browser, so I wanted the labels to follow it too, with nothing for the reader to pick. That is the job of Culture Match. The Remarks in the USERCULTURE function reference say that with field parameters, USERCULTURE can reliably translate dynamic titles and captions when it is used in a measure within the same model.

It had to be a measure. The Implement translations using measures and USERCULTURE section of the Use Locale Values in Multiple-Language Power BI Reports article says calculated tables are evaluated when the model loads, so in a calculated table USERCULTURE returns the culture name of the model's default language, not the reader's. prm_dim is a calculated table, which is how its partition is declared in prm_dim.tmdl, so the language check lives in the measure, and the measure reaches the two visuals through their hidden filters.

8. From a DevOps Standpoint: What a Translation Pull Request Contains

From a DevOps standpoint, this experiment changed what I look for when a translation arrives in a pull request.

Code review. Metadata translation is the easy review: one culture file and one ref line per language, all in the semantic model folder, with captions to read against object names. Data translation is a different review. The field parameter rows, the measure, and the filters have to agree with each other, and the filters live with their visuals in the report, as I wrote about in From Click-by-Click to Code. A reviewer who only opens the culture files would approve a slicer that is still in English. My team's existing check would pass the change too, because both new filters are hidden.

Version control. The script tab is committed next to its result, so my first commit holds the same nine captions twice, in Script 1.tmdl and in ko-KR.tmdl. Review the culture file. The script is only a record of what was run, and it stopped matching the model in the second commit, when the AI edited the culture file and left the script alone.

Maintenance. Another language for this page now means four edits in three files: a culture file, a ref line in model.tmdl, three rows in the field parameter, and one more pair in the SWITCH of Culture Match. The last two both land in prm_dim.tmdl, because prm_dim is the measure's home table. Miss the SWITCH and the new language quietly gets English labels, because any culture it does not list falls through to en-US.

One caution before anyone copies this. I only ever sent the service two exact culture names, ko-KR and nl-NL. The Support multiple locales for a single language section of the Use Locale Values in Multiple-Language Power BI Reports article says tables and columns need an exact match between the requested culture and a translation, while measures look for the closest one. Culture Match only reads the first two letters, so a reader asking for nl-BE would get Dutch labels from the field parameter, and I cannot tell you what the captions around them would do, because I did not test it.

A Note on Working with AI

I want to be transparent about the division of work. The first Korean script was an AI suggestion built from the objects I had highlighted, and I previewed, applied, and committed it myself. The second and third commits are Claude Code's work from the two prompts above: the extra captions, the Dutch file, the nine field parameter rows, the measure, and both filters. I read its diff in VS Code before the second commit, and I checked the measure and both filters in Desktop before the third. I pushed every commit, updated the workspace, changed the browser, and checked every result in the service, and every screenshot comes from my own session.

What made the AI's work checkable was the slow first round. I had watched nine captions go from a script to a file to a column header, so I knew what a correct culture file looked like, and I could see that the field parameter fix was a different kind of change.

Closing Thoughts

I expected a model-only change, and for two commits it was: two culture files, two ref lines, and not one report file. Then a slicer showed me that field parameter labels are not names at all, and the fix had to reach into the data and the report.

My notes from that day end with one sentence, and I still agree with it. If I am clear about the steps that make a Power BI report translate correctly in the service, and I can validate how AI fixed the field parameter table and the visuals that use it, then I can work together with AI on the rest of the job and govern the process. Section 7 is what that validation looked like for me.

There is plenty I did not test. One browser and two exact culture names. No language parameter on the report URL, which the Load a report in Power BI section of the Use Locale Values in Multiple-Language Power BI Reports article offers as a way to test without touching the browser.

The whole of it is two culture files, two ref lines, nine rows where there used to be three, one hidden column, one hidden measure, and two hidden filters.

I hope this helps having fun in exploring translations in TMDL and embracing this new era of Power BI reports that answer in the reader's language!