Automatically create alias with translated title

Hi everyone :wave:
I have a specific feature I want to implement and was cusious if I could do that with templater.

What I want to do is to find notes by searching for english words.

But the problem is:

I usually write my notes in german and so are the titles. As well as a lot of keywords within these titles.

What I have in mind for a solution

is a feature/plugin/template-code-stuff that automatically creates an alias and puts in an english translation of the german title of my note.

Is there a way to implement that function? Or is that just impossible without maybe an integrated LLM?

I wish you all a great day! :sunny:

1 Like

This is a good topic, actually, for bilingual users who are accustomed to add titles and carry out searches in English.
As you can run Javascript in Templater templates, implementing this is definitely feasible (even though LLM’s are more often used with Python).
The script would create the alias in native language if the title is in English and in English if the title is in native or any other language.

There would be a CONFIG setting on top where users would add

  • native_language: something;
  • ā€˜true’ or ā€˜false’ if they wanted conjugations, declinations etc. to go into aliases as well…
    …so Cat → cats, go → goes, going, went, gone, and the same in native languages.
    • But this can become rather unruly…having 10-12 aliases would be too much IMO.

Two scripts would be needed:

  • Online model implementation
  • Offline (local) model implementation
    • Would need voting for good multi language models first for this to work fast and reliably on any low-end PC’s as well.

I would definitely be interested in hacking together (with claude.ai) the online version, when I have the time. As I seem to have various bits of code I could mash together to make this work in Templater js.
But if I was to do it, it will be a sample template – I mean everyone has different needs – where they move their note on creation, etc.
So note to self: tp.user scripts needed for any kind of template.

1 Like

Okay, I looked into it last night.
It was working almost straight away.
But there are caveats.
I tried to have a different method created but never could spare post-processing replacements.

The caveat is that if there is another tp.user script one’s template calls and that other one is using Obsidian API’s processFrontmatter function then one needs to clean up the aliases list.
The current implementation does a simple insertion of AI generated list below the aliases frontmatter key. – As I said, I didn’t like this and wanted to pursue a ā€œget AI to generate strings without dashes we put in an array and we push thatā€ solution but lost my patience with it.
So what I did was tweak my more elaborate template with post-processing replacements, which I know is not a good programming solution…
So when I said we need a solution that works for everyone with any template sounded doable but if users have different templates with different creation mechanisms, then they would run into problems anyway. So I reverted to the original method in the end. (Simple reason: I made it work for all my templates.)

So I am offering this original method that’ll work in normal Templater scripts that don’t call other scripts with their own logic of frontmatter value merges:

Save this script as getAliases.js and place it in the user scripts folder of your choice (of Templater):

Templater will automatically see it, but there is a refresh button you can press as well if you had your Obsidian open when you added the script in your file explorer.

There are options at the top of the script you can modify:

// FILE: getAliases.js
// This script should be placed in Templater's User scripts directory
// and can be called with 'tp.user.getAliases(title)' from Templater

// CONFIG - Modify these settings as needed
const CONFIG = {
    native_language: "German", // Change to your preferred language
    INFLECT: false,  // Set to false if you don't want inflections
    include_original: true, // Whether to include original title as alias
    model1: "gemini-2.0-flash-thinking-exp",
    model2: "gemini-2.0-flash",
    API_key: "YOUR_KEY_HERE",
    max_retries: 2 // Maximum number of retry attempts
};

async function getAliases(title, tp) {
    try {
        // If no title provided, get it from tp.file.title
        if (!title && tp?.file?.title) {
            title = tp.file.title;
        }

        const prompt = createPrompt(title);
        const aliases = await communicateWithGemini(prompt);
        return formatAliases(aliases, title);
    } catch (error) {
        console.error("Error generating aliases:", error);
        return "  - ERROR: Could not generate aliases";
    }
}

function createPrompt(title) {
    // Check if the title contains spaces
    const isSingleWord = !title.includes(' ');
    
    return `Generate aliases for "${title}" with these rules:
1. If "${title}" is in English, provide translations in ${CONFIG.native_language}.
2. If "${title}" is in ${CONFIG.native_language} or any other language, provide English translations.
3. Include inflections: ${isSingleWord && CONFIG.INFLECT ? "yes" : "no"}.
4. Format as a simple list with each alias on a new line prefixed with a dash (-).
5. For multiple word phrases, include variations with each significant word.
6. Include singulars and plurals where applicable.
7. Include common synonyms in both languages.
8. Include a maximum of 6 most relevant aliases.
9. DO NOT include variations on names of people but if they are well-known, you can add 'Aristotle, the Greek philosopher', or similar handles
10. DO NOT include any extra formatting, backticks, code blocks or yaml tags in your response.`;
}

async function communicateWithGemini(message) {
    let lastError = null;
    const models = [CONFIG.model1, CONFIG.model2];

    for (const model of models) {
        for (let attempt = 0; attempt < CONFIG.max_retries; attempt++) {
            try {
                const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'x-goog-api-key': CONFIG.API_key
                    },
                    body: JSON.stringify({
                        contents: [{
                            role: 'user',
                            parts: [{ text: message }]
                        }],
                        generationConfig: {
                            temperature: 0.1,
                            maxOutputTokens: 2048,
                            topP: 1
                        }
                    })
                });

                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }

                const data = await response.json();
                
                if (data.candidates && data.candidates.length > 0) {
                    return data.candidates[0].content.parts[0].text;
                }
                
                throw new Error('No valid response from the API');

            } catch (error) {
                lastError = error;
                if (attempt < CONFIG.max_retries - 1) {
                    await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1)));
                    continue;
                }
            }
        }
    }
    
    throw lastError || new Error('Failed to get response from all models');
}

function formatAliases(response, title) {
    // Clean up the response
    let cleanedResponse = response.trim();
    
    // Remove various markdown and YAML markers
    cleanedResponse = cleanedResponse.replace(/^```(?:yaml|)\s*|\s*```$/gm, '');
    cleanedResponse = cleanedResponse.replace(/^aliases:\s*$/im, '');
    
    // Process each line
    const lines = cleanedResponse.split('\n')
        .map(line => line.trim())
        .filter(line => line) // Remove empty lines
        .map(line => {
            // Remove any existing dashes and spaces
            line = line.replace(/^[-\s]+/, '').trim();
            return `  - ${line}`;
        });

    // Add original title as an alias if configured
    if (CONFIG.include_original && title) {
        if (!lines.includes(`  - ${title}`)) {
            lines.unshift(`  - ${title}`);
        }
        // Add first-char lowercase version if different
        const firstCharLowerCase = title.charAt(0).toLowerCase() + title.slice(1);
        if (firstCharLowerCase !== title && !lines.includes(`  - ${firstCharLowerCase}`)) {
            lines.push(`  - ${firstCharLowerCase}`);
        }
    }

    return lines.join('\n');
}

module.exports = getAliases;

If the title is a single word, then it will add inflections, but since we have Include a maximum of 6 most relevant aliases in the prompt and also ask for synonyms, they are likely drowned out. You can increase 6 if you want. The inflections can be turned off or the system prompt rewritten to have custom results. Feel free to experiment.

  • Inflections I wanted to add because I remember Feature Requests regarding this in the past.

I always want the original title and its lowercase version added as well. There is a setting for that too.

We are using free Gemini, for which you can get API keys from Google. Check how here:

In the template you are using (it means every template you want to use AI generated aliases in) you need to add the script caller below the aliases key line:

aliases:
<%* tR += await tp.user.getAliases(tp.file.title) %>

If you had let title = tp.file.title; in your script, simple title is enough in the brackets.
I am only mentioning this because there may be cases when tp.file.title will give (partly) wrong results, if one uses a tp.user script that cleans titles for example (like mine).

The models can be also changed in an effort to make the process slightly faster. So gemini-2.0-flash-thinking-exp could be used as model2, but named gemini-2.0-pro or some other model that is efficient enough but faster. (The current note generation takes something like 3 seconds.)
I cannot experiment enough with this now to suit everyone’s needs.

Works on mobile as well!

The script can be enhanced:

  • Using the other way, where we push values and merge into frontmatter with Obs. API
  • Checks for internet connection and if none found, uses some local LLM (but you’d need to load the model manually with Ollama or LM Studio before).
1 Like

You are sent from heaven, Yurcee!! :pray: I can’t thank you enough! It’s just wonderful that you’ve not only found a solution for this, but have also written instructions for implementing it and made sure that everyone understands it, no matter what skill level they come from.

More and more I realize how Obsidian as a platform brings to light secondary educational effects: For, this software does not provide solutions for particularly special interests, and accordingly the audience is just as diverse as one would imagine when thinking of the group of people who take notes. And because the community manages to make itself visible as a relevant factor for the use of Obsidian, contacts between the users and such a learning field as ā€œprogrammingā€ build up very organically and with a low threshold!

Thank you very much for your efforts, Yurcee! And for allowing me to learn from you how to take my first productive step in AI and LLMs as a layman! :sparkles: I’m very grateful for this community :blush:

1 Like

My experience – as another layman – is that you need ideas (a target to achieved), the implementation (whether from forum posts and now AI that can generate code), and then from one script, using that as a sample, you can have more created, and over time some of this programming will become somehow familiar…even for non-coders.

So in this case the script above can be tweaked to be used in the same template calling another user script in which AI would also write a summary (of one’s own language) of the newly created note’s title as topic. You don’t necessarily want to keep that (and I firmly belive that Obsidian notes should be personal notes, from personal experience and I don’t want to endorse AI generated note content as such), but before writing your own lines, these generated lines can be used for perspective or mood-setter, etc.
They can be used as a web searcher…new note with some title → get response, you may even delete the note. Yes, generally Google browser is better, as you get up-to-date information. But in other cases you’d need to do more than just browse and get what you want from the first page of Google (Bing, etc.) results.
And then, this can be taken further…generate a list of topics in txt file, then with a Python script go through the topics line by line and open the lines as files in your vault (there are some CLI utilities), where they would be automatically created, with AI summaries, etc.

Creation starts with the idea first. Idea can be 10 or 90 percent of the job done. Depending on how well prepared you are for the implementation (tweaking, if you do this the 2nd, 3rd time). And with the expertise garnered in the second phase, more ideas can pop up, waiting for implementation.

Obsidian with its 3rd party plugins is a great tool for this. I cannot think there is another ecosystem that can currently surpass it (for customization functionality).

1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.