This is a project for making Chinese study, through Anki, more enjoyable by automatically styling the flashcards and providing convenience features. This project enables one to focus more on studying rather than perfecting their form of studying.
The below is showcasing various phonic types and orientations. This may be configured to your preferred study needs. The variety shown is for demonstration purposes.
| Recognition | Sentence |
|---|---|
![]() |
![]() |
| Tones | Secondary Recognition |
|---|---|
![]() |
![]() |
| Secondary Sentence | Audio |
|---|---|
![]() |
![]() |
| Writing (Under Construction) |
|---|
| This one has regressed too much. It was left un-maintained after several upgrades and isn't worth showing in its current state. Works fine in prior versions if you are eager to utilize it. |
Whether your study involves simplified or traditional characters, this project can make Chinese flashcards look nice by copying/pasting a few lines of logic in your Anki template. It can also auto-magically generate some data, please go here to see what is available.
Try it out now by navigating to the jsFiddle page, or follow the instructions below to set this up within your own installation of Anki.
YOUR_FIELD_NAME with the names defined in your deck (see {{text:YOUR_FIELD_NAME}})Aside from making your flashcards look beautiful, this project has a few note-worthy features:
Simplified And/Or Traditional Character Support
Automatic Secondary Character Generation
Pinyin Or Zhuyin Phonic Support
Automatic Phonic Generation
Dictionary Integration
A full example of the Front of a recognition card is found below:
<style>
/*desktop anki*/
body {
margin: 0;
}
/*ankidroid*/
#content {
margin: 0;
}
</style>
<script>
// if the custom element material-beautify-chinese-study is not a known element
//// manually create the html script to import the module so the browser has
//// the necessary logic to create element
if (!customElements.get('material-beautify-chinese-study')) {
console.log('Custom element does not exist - Creating script to pull it in');
var script = document.createElement('script');
script.setAttribute('id', 'import-script');
script.setAttribute('type', 'module');
// this will retrieve the newest, bleeding edge version of the package
// doing this could cause issues if something in the package is updated and the older
//// version no longer exists
// one solution is to require a specific version by adding @VERSION-NUMBER like the example below
//// https://cdn.jsdelivr.net/npm/beautify-chinese-study@1.1.48/dist/beautify-chinese-study/beautify-chinese-study.esm.js
// versions can be viewed here https://www.npmjs.com/package/beautify-chinese-study
script.setAttribute('src', 'https://cdn.jsdelivr.net/npm/beautify-chinese-study/dist/beautify-chinese-study/beautify-chinese-study.esm.js');
document.body.appendChild(script);
} else {
console.log('Custom element exists.');
}
// locate the custom element material-beautify-chinese-study on the DOM
//// if it has data - remove everything
var beautify = document.querySelector('material-beautify-chinese-study');
if (beautify !== null) {
beautify.remove();
}
// create a brand new material-beautify-chinese-study element and define
//// its attributes and attribute values
// IMPORTANT - the text below that contains {{text:YOUR_FIELD_NAME}} is meant to be exchanged
//// with the field names on your Anki card fields
//// eg if the english translation field on your Anki card is called English the value you type below
//// would be
//// beautify.setAttribute('meaning', "{{text:English}}")
// Remove the above references to {{text:YOUR_FIELD_NAME}} and {{text:EngLish}}
//// even though they are comments - Anki will still see this as an error
var beautify = document.createElement('material-beautify-chinese-study');
beautify.setAttribute('id', 'beautify');
beautify.setAttribute('primary-character', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('secondary-character', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('numbered-pinyin', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('primary-character-sentence', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('secondary-character-sentence', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('sentence-numbered-pinyin', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('meaning', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('card-type', 'recognition');
beautify.setAttribute('card-orientation', 'question');
beautify.setAttribute('preferred-phonic', 'zhuyin');
// add the element to the DOM body
document.body.appendChild(beautify);
</script>
{{Audio}}{{SentenceAudio}}
A full example of the Back of a recognition card is found below:
<style>
/*desktop anki*/
body {
margin: 0;
}
/*ankidroid*/
#content {
margin: 0;
}
</style>
<script>
// if the custom element material-beautify-chinese-study is not a known element
//// manually create the html script to import the module so the browser has
//// the necessary logic to create element
if (!customElements.get('material-beautify-chinese-study')) {
var script = document.createElement('script');
script.setAttribute('id', 'import-script');
script.setAttribute('type', 'module');
// this will retrieve the newest, bleeding edge version of the package
// doing this could cause issues if something in the package is updated and the older
//// version no longer exists
// one solution is to require a specific version by adding @VERSION-NUMBER like the example below
//// https://cdn.jsdelivr.net/npm/beautify-chinese-study@1.1.48/dist/beautify-chinese-study/beautify-chinese-study.esm.js
// versions can be viewed here https://www.npmjs.com/package/beautify-chinese-study
script.setAttribute('src', 'https://cdn.jsdelivr.net/npm/beautify-chinese-study/dist/beautify-chinese-study/beautify-chinese-study.esm.js');
document.body.appendChild(script);
}
// since we are on the back of the card the custom element from the front of the card
//// may have been retained - in instances of AnkiDroid a new webview is created and
//// the element will need to be recreated from scratch
var beautify = document.querySelector('#beautify');
if (!beautify) {
// probably executing because on AnkiDroid
// recreating element from scratch
// IMPORTANT - the text below that contains {{text:YOUR_FIELD_NAME}} is meant to be exchanged
//// with the field names on your Anki card fields
//// eg if the english translation field on your Anki card is called EngLish the value you type below
//// would be
//// beautify.setAttribute('meaning', "{{text:EngLish}}")
// Remove the above references to {{text:YOUR_FIELD_NAME}} and {{text:EngLish}}
//// even though they are comments - Anki will still see this as an error
var beautify = document.createElement('material-beautify-chinese-study');
beautify.setAttribute('id', 'beautify');
beautify.setAttribute('primary-character', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('secondary-character', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('writing', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('numbered-pinyin', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('primary-character-sentence', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('secondary-character-sentence', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('sentence-numbered-pinyin', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('meaning', `{{text:YOUR_FIELD_NAME}}`);
beautify.setAttribute('card-type', 'recognition');
beautify.setAttribute('card-orientation', 'answer');
beautify.setAttribute('preferred-phonic', 'zhuyin');
document.body.appendChild(beautify);
} else {
// element already exist
// redefine the card type just for safe keeping
// redefine the card orientation to display the answer logic
beautify.setAttribute('card-type', 'recognition');
beautify.setAttribute('card-orientation', 'answer');
}
</script>
{{Audio}}{{SentenceAudio}}
The logic attempts to make custom styling and additional logic work the same across Desktop Anki and Anki Droid (ios anki not tested). The above code snippets contain inline comments to address what is being done, but an explanation is also below.
$ claude mcp add anki-templates \
-- python -m otcore.mcp_server <graph>