API example for modifing a text component

Hi,

I am trying to build a solutions to translate design assets in penpot. I am looking for a bit of guidance regarding the update-file call with REST API. (doc is here)

I have managed to extract the content object by analyzing the json in get-file call, so far so good. (Code here)

But I have some issue figuring out how to update a text field using the API, if anyone could point me at a basic example of some usage of the update API (ideally for a text component) that would be awesome!

My goal is that if you create a banner or some graphic assets in penpot, we can generate translation of the assets to as many languages desired and export the files (which would really come handy when running international ads campaign or building UI in multiple languages)

Any help welcome

Interesting idea, sit I think with a slightly or somewhat similar question.

Thanks, very interesting thread in itself. :+1:

Regarding your question about User language, I don’t really know yet, though you can always use the browser language if you need a point to get started.

I am more focused on the language of the content of the artifacts themselves.

Let’s say you have designed a post for Instagram, and now you need to make the same post in Japanese, German, Spanish, and Turkish. Doing this manually can become very tedious and error prone, very quickly.

So I am working on a basic web interface where translators can work, translate / edit / review (without having to learn Penpot) - and sync the text with the penpot object file.

For this I mostly need to fetch the text content (this seems simple for now), then when I have the translations for it, update the text and export an image, then restore the original text. (Alternative, more sophisticated would be to duplicate the file and update the file in the new text, or duplicate the layers… but many edge cases in all these, so I’d rather start with a naive and simple solution to get it to work and later increase complexity.)

There are a few products for this but they are mostly closed source, and focused only on Figma.

Would you know where I can find some code example of the update-file API ?

Is this what you are looking for?

thanks, I have browsed through this repo but I could not find anything that look like an example of updating a file, maybe it’s there but I don’t see find it, not though search not through browsing. We can see some references to the main API but nothing I can see that is a clear cut example of : here is the change we are applying to a file.

I am attaching at the end the specs of the specific API I am referencing.

The things which are not clear when trying to update a text component are:

  • the actual text of the text shape seems to show up in multiple places, both in the positionData and in the content of the text shape. So not clear which need to be changed, or if one if enough.
  • The api itself for update-file is pretty rich in options but examples for these options or even basic explanation are seemingly left as an exercise for the reader.

So I was wondering if maybe there is some basic code example of calls to this api, or someone who’s using this regulary as part of the dev of penpot could bring some light on how to use this (I mean someone wrote this at some point so I am sure someone probably know how it is supposed to work).


// DOCSTRING:

// Added: on v1.17

// URI: https://design.penpot.app/api/rpc/command/update-file
// PARAMS:

type UpdateFile {
  id: uuid,
  sessionId: uuid,
  revn: int[min=0],
  vern: int[min=0],
  features?: set,
  changes?: [multi Change [dispatch=type] {
      SetOptionChange {

      },
      {
        commentThreadId: Uuid,
        pageId: Uuid,
        frameId: Uuid nullable,
        position: Point nullable
      },
      AddObjChange {
        type: == 'add-obj',
        id: Uuid,
        obj: <untitled>,
        pageId?: Uuid,
        componentId?: Uuid,
        frameId: Uuid,
        parentId?: Uuid nullable,
        index?: integer nullable,
        ignoreTouched?: boolean
      },
      ModObjChange {
        type: == 'mod-obj',
        id: Uuid,
        pageId?: Uuid,
        componentId?: Uuid,
        operations: [Operation]
      },
      DelObjChange {
        type: == 'del-obj',
        id: Uuid,
        pageId?: Uuid,
        componentId?: Uuid,
        ignoreTouched?: boolean
      },
      SetGuideChange {
        type: == 'set-guide',
        pageId: Uuid,
        id: Uuid,
        params: Guide nullable
      },
      SetFlowChange {
        type: == 'set-flow',
        pageId: Uuid,
        id: Uuid,
        params: Flow nullable
      },
      multi [dispatch=grid-type] {
        <untitled>,
        <untitled>,
        <untitled>
      },
      FixObjChange {
        type: == 'fix-obj',
        id: Uuid,
        fix?: keyword,
        pageId?: Uuid,
        componentId?: Uuid
      },
      MovObjectsChange {
        type: == 'mov-objects',
        pageId?: Uuid,
        componentId?: Uuid,
        ignoreTouched?: boolean,
        parentId: Uuid,
        shapes: anything,
        index?: integer nullable,
        afterShape?: anything,
        componentSwap?: boolean
      },
      ReorderChildrenChange {
        type: == 'reorder-children',
        pageId?: Uuid,
        componentId?: Uuid,
        ignoreTouched?: boolean,
        parentId: Uuid,
        shapes: anything
      },
      AddPageChange {
        type: == 'add-page',
        id?: Uuid,
        name?: string,
        page?: anything
      },
      ModPageChange {
        type: == 'mod-page',
        id: Uuid,
        background?: RgbColor nullable,
        name?: string
      },
      SetPagePluginData, and *,
      DelPageChange {
        type: == 'del-page',
        id: Uuid
      },
      MovPageChange {
        type: == 'mov-page',
        id: Uuid,
        index: integer
      },
      RegObjectsChange {
        type: == 'reg-objects',
        pageId?: Uuid,
        componentId?: Uuid,
        shapes: [Uuid]
      },
      AddColorChange {
        type: == 'add-color',
        color: Color
      },
      ModColorChange {
        type: == 'mod-color',
        color: Color
      },
      DelColorChange {
        type: == 'del-color',
        id: Uuid
      },
      AddRecentColorChange {

      },
      AddMediaChange {
        type: == 'add-media',
        object: FileMediaObject
      },
      ModMediaChange {
        type: == 'mod-media',
        object: FileMediaObject
      },
      DelMediaChange {
        type: == 'del-media',
        id: Uuid
      },
      AddComponentChange {
        type: == 'add-component',
        id: Uuid,
        name: string,
        shapes?: [anything],
        path?: string
      },
      ModCompoenentChange {
        type: == 'mod-component',
        id: Uuid,
        shapes?: [anything],
        name?: string
      },
      DelComponentChange {
        type: == 'del-component',
        id: Uuid,
        mainInstance?: anything,
        skipUndelete?: boolean
      },
      RestoreComponentChange {
        type: == 'restore-component',
        id: Uuid,
        pageId: Uuid
      },
      PurgeComponentChange {
        type: == 'purge-component',
        id: Uuid
      },
      AddTypogrphyChange {
        type: == 'add-typography',
        typography: Typography
      },
      ModTypogrphyChange {
        type: == 'mod-typography',
        typography: Typography
      },
      DelTypogrphyChange {
        type: == 'del-typography',
        id: Uuid
      },
      AddTemporaryTokenThemeChange {
        type: == 'add-temporary-token-theme',
        tokenTheme: TokenTheme
      },
      UpdateActiveTokenThemes {
        type: == 'update-active-token-themes',
        themeIds: set[string]
      },
      DeleteTemporaryTokenThemeChange {
        type: == 'delete-temporary-token-theme',
        id: Uuid,
        name: string
      },
      AddTokenThemeChange {
        type: == 'add-token-theme',
        tokenTheme: TokenTheme
      },
      ModTokenThemeChange {
        type: == 'mod-token-theme',
        group: string,
        name: string,
        tokenTheme: TokenTheme
      },
      DelTokenThemeChange {
        type: == 'del-token-theme',
        group: string,
        name: string
      },
      AddTokenSetChange {
        type: == 'add-token-set',
        tokenSet: TokenSet
      },
      AddTokenSetsChange {
        type: == 'add-token-sets',
        tokenSets: sequence of TokenSet
      },
      RenameTokenSetGroup {
        type: == 'rename-token-set-group',
        setGroupPath: [string],
        setGroupFname: string
      },
      ModTokenSetChange {
        type: == 'mod-token-set',
        name: string,
        tokenSet: TokenSet
      },
      MoveTokenSetBefore {
        type: == 'move-token-set-before',
        fromPath: [string],
        toPath: [string],
        beforePath: [string] nullable,
        beforeGroup: boolean nullable
      },
      MoveTokenSetGroupBefore {
        type: == 'move-token-set-group-before',
        fromPath: [string],
        toPath: [string],
        beforePath: [string] nullable,
        beforeGroup: boolean nullable
      },
      DelTokenSetChange {
        type: == 'del-token-set',
        name: string
      },
      DelTokenSetPathChange {
        type: == 'del-token-set-path',
        path: string
      },
      SetTokensLib {
        type: == 'set-tokens-lib',
        tokensLib: anything
      },
      AddTokenChange {
        type: == 'add-token',
        setName: string,
        token: Token
      },
      ModTokenChange {
        type: == 'mod-token',
        setName: string,
        name: string,
        token: Token
      },
      DelTokenChange {
        type: == 'del-token',
        setName: string,
        name: string
      }
    }],
  changesWithMetadata?: [{
      changes: [Change],
      hintOrigin?: keyword,
      hintEvents?: [string with length >= 250]
    }],
  skipValidate?: boolean
}

FileMediaObject {
      id: Uuid,
      createdAt: Inst,
      deletedAt?: Inst,
      name: string,
      width: SafeInt,
      height: SafeInt,
      mtype: string,
      fileId?: Uuid,
      mediaId: Uuid,
      thumbnailId?: Uuid,
      isLocal: boolean
    }

Typography {
      id: Uuid,
      name: string,
      fontId: string,
      fontFamily: string,
      fontVariantId: string,
      fontSize: string,
      fontWeight: string,
      fontStyle: string,
      lineHeight: string,
      letterSpacing: string,
      textTransform: string,
      modifiedAt?: Inst,
      path?: string nullable,
      pluginData?: PluginData
    }

TokenTheme {
      name: string,
      group: string,
      description: string nullable,
      isSource: boolean,
      modifiedAt?: Inst,
      sets: anything
    }

TokenSet {
      name: string,
      description?: string nullable,
      modifiedAt?: Inst,
      tokens: anything
    }

Token {
      name: string, and regex pattern matching #"^(?!\$)([a-zA-Z0-9-$]+\.?)*(?<!\.)$",
      type: OneOf[sizing,rotation,color,numeric,other,string,dimensions,stroke-width,opacity,border-radius,boolean,spacing],
      value: anything,
      description?: string nullable,
      modifiedAt?: Inst
    }

// RESPONSE:

[{
    changes: [multi Change [dispatch=type] {
        SetOptionChange,
        <untitled>,
        AddObjChange,
        ModObjChange,
        DelObjChange,
        SetGuideChange,
        SetFlowChange,
        ,
        FixObjChange,
        MovObjectsChange,
        ReorderChildrenChange,
        AddPageChange,
        ModPageChange,
        SetPagePluginData, and *,
        DelPageChange,
        MovPageChange,
        RegObjectsChange,
        AddColorChange,
        ModColorChange,
        DelColorChange,
        AddRecentColorChange,
        AddMediaChange,
        ModMediaChange,
        DelMediaChange,
        AddComponentChange,
        ModCompoenentChange,
        DelComponentChange,
        RestoreComponentChange,
        PurgeComponentChange,
        AddTypogrphyChange,
        ModTypogrphyChange,
        DelTypogrphyChange,
        AddTemporaryTokenThemeChange,
        UpdateActiveTokenThemes,
        DeleteTemporaryTokenThemeChange,
        AddTokenThemeChange,
        ModTokenThemeChange,
        DelTokenThemeChange,
        AddTokenSetChange,
        AddTokenSetsChange,
        RenameTokenSetGroup,
        ModTokenSetChange,
        MoveTokenSetBefore,
        MoveTokenSetGroupBefore,
        DelTokenSetChange,
        DelTokenSetPathChange,
        SetTokensLib,
        AddTokenChange,
        ModTokenChange,
        DelTokenChange
      }],
    fileId: uuid,
    id: uuid,
    revn: int[min=0],
    sessionId: uuid
  }]


Had a look in 5. Examples and templates but can’t find it directly either.
@madalenam would you know the answer?

Is there a way to buy support by the hour on this issue?

At least to understand which direction to take for the solution I need to build. No is an acceptable answer, I just need some clarity about what to do to move forward.

1 Like

I myself sometimes find it disappointing when a question is asked to Penpot that sometimes there is no response at all…

While it is a bit frustrating, we need to understand the project is in development, and they need to prioritize. We’ve waited that long, we can wait a little longer. There is now a big event coming up, I am sure everyone is busy.

For now, I have moved on, and I am testing other approaches to the solution.

I still would like to integrate with Penpot because translating designs is a pain and it’s great when you can sync your translators environment with the design assets.

But Penpot is a massive project and we cannot expect all our little wishes to be fulfilled immediately.

So, if it is not possible today, then maybe in a few months (hint to anyone with an answer reading in a few month, I am still interested in your input)

Hello @yashasolutions, we would like to understand better what are you trying to do?

As for right now, the specific details about updating text fields via the API, I don’t see enough information in the available documentation to provide accurate guidance. The data structure documentation shows that files can be modified, but doesn’t provide specific examples for text field updates.

2 Likes

Thanks for getting back first of all.

Indeed the documentation shows that files can be modified, but the lack of examples make it hard to use.

I was hoping someone would have some undocumented insights to share :slight_smile:

I don’t mind describing again what I am trying to do:

  • from user perspective:
    the users (in the role of translators) will paste the link to the Penpot file, and they will get a list of the text fields used in the design (ideally structured by pages/boards). In a separate interface (outside of Penpot) they will translate these texts, and then they can create/update a copy of the asserts in their language (as a new penpot file or a new page, I don’t know yet) - and potentially export all or some of the boards as PNGs.

  • from a technical perspective
    I am looking to a way to access the content of a file (so far this worked) and read all the text fields, using the REST API. Then once the texts are translated, I want to duplicate the file(or page or board) and update the texts with the translation (through the API) - then there is room for design adjustment if needed (new text lenghts/fonts - RTL changes etc.) then from outside of penpot call the export function as a PNG to automate the generation and export of visual assets to be then imported where they need to be.

I hope the use case from a user perspective and technical perspective is clearer.