A Software Library for Creating Language Learning Materials (such as bilingual texts)

All about language programs, courses, websites and other learning resources
davidzweig
White Belt
Posts: 25
Joined: Sun Apr 23, 2017 4:58 pm
Languages: AFew
x 57

A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby davidzweig » Mon Oct 30, 2017 2:06 am

(Edit: This post maybe should have gone in 'Language Programs and Resources' section)

There are many tools and libraries that are useful for creating language learning materials (such as bilingual texts). To name a few:

-- hunalign (http://mokk.bme.hu/en/resources/hunalign/) - you supply it with a text and it's translation and it matches up the sentences for you

-- aeneas (https://www.readbeyond.it/aeneas/) - you supply a text and an audio recording and it determines the corresponding time interval in the audio file for each fragment

-- NLTK (http://www.nltk.org/) - lots of useful tools for performing analysis on texts

-- Google language tools - translation, text-to-speach

-- A tool I made for formatting pretty bilingual texts (http://smallworld.press/show_hn.html)

These tools are written for different purposes and have different input/output formats. I am planning to make a kind of software toolbox that stitches them together.

It would be a library/set of classes/collection of modules to make it easy to run your audio/text/video through different tools by writing simple scripts. There would also be simple (multilingual) file formats for storing output, and sharing with others. 'Python' I think is a good choice of language for the library. Actually I have written a lot of the code already, over the years, but I would like to structure it better and upload it for others to use.

Here are things you could expect to do with the library using short scripts:

Dump in an MP3 audiobook
Dump in the text matching the audiobook
Break the text into sentences using the NLTK sentence segmenter
Find the audio time intervals for each sentence using aeneas, save as a special file
Run the sentences through Google translate for a translation
Format the text and translation as a pretty bilingual text

Bilingual texts, with audiobooks, personally, are my bread and butter for language learning.

You could do other things with the library too.

For example:

Load in subtitles for 100 films in your target language.
Use a lemmatizer from the NLTK library to determine the standard word forms
Perform an analysis to determine which words allow you to understand the greatest new number of sentences, given a known vocabulary (or start from scratch).
Make a list of the words in the order they should be acquired, with five example sentences using the vocabulary of previous words. A kind of 'optimal path.'
Cut out the audio for these sentences from the films and save them to a special file.

I was thinking to have a kind of blog, with 'recipes' (scripts). Creating a graphical user interface is possible, and would make it easier to use, but it would be more work to create and maintain, and would be less flexible.

Continued on next post.
Last edited by davidzweig on Thu Nov 02, 2017 3:23 am, edited 4 times in total.
12 x

davidzweig
White Belt
Posts: 25
Joined: Sun Apr 23, 2017 4:58 pm
Languages: AFew
x 57

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby davidzweig » Mon Oct 30, 2017 2:14 am

The other component to the project is a special audio player I've been working on over the summer. It has some simple functions/characteristics that I think would make it useful for language study:

You navigate through sentences, not though long files. Sentences are the smallest piece of language that can stand on their own, so it seems logical. So pressing 'next' gives you the next sentence, 'previous', the previous sentence, etc.

You can store meta-information about each sentence. For example, you can store (a recording of) the translation for every sentence. These translations can be created automatically by the software library (using google or a translated text). Or you could store some notes (a difficult word explained).

There is a (tree-like) table of contents function.

The player is controlled by tactile buttons, and has no screen. All the relevant information is 'announced.' You can go walking outside, or use it whilst driving, and there should be no reason to look at it (well it does have pretty lights).

Some photos of the (ongoing) development here: https://photos.app.goo.gl/K4RW1hIZGBDjWtSP2

With these basic functions, you can easily implement audio drills (FSI style), audio flashcards, bilingual audiobooks.

Another idea is to put a (javascript or micropython) interpreter on the player, so that you could program audible games, 'choose your adventure' style. The cpu should have the resources for this. You'd need to understand what you hear to progress in the story, and use points get translations of phrases to help you. I don't know, something like that.

The lack of screen might seem a little odd. There are some advantages. In general I'd say people tend to focus too much on written forms of language when studying. This approach has you using (only) your ears. That's not to say you can't use the player with printed materials, though. Also, it eliminates a large expense (the screen), and reduces it's size.

Sorry if this is a bit technical or out of place here. I'd like to know if it sounds like something you would be interested in, or have any suggestions. The idea was to make the software open-source, and sell the player at a small profit to, well.. eat. :-) An app is also possible, it has some advantages and disadvantages.

I hope this makes some sense. Much appreciate any thoughts.

David
3 x

davidzweig
White Belt
Posts: 25
Joined: Sun Apr 23, 2017 4:58 pm
Languages: AFew
x 57

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby davidzweig » Mon Oct 30, 2017 2:32 am

Another example for the library / player:

Send the FSI French files for voice recognition to google.
Dump all the text received back into a file. Edit the file so it's one drill prompt per line. Add a bit of 'markup' to show where the new exercises start.
Use aeneas to get the audio timing information for each line in the edited text file, and use the markup information to create a table-of-contents.
Add google translations for every sentence, and voice them through google TTS.
Save everything into a file, load onto the player.

You now have FSI French, at your pace. If you can't understand a sentence, you can press a button for a translation. Easily move between exercises.

There are clever people working on neural networks improve the quality of old recordings (see https://github.com/kuleshov/audio-super-res). It's not really useful yet, but maybe one day soon could be good to enhance these old recordings.

Another example:

I have a friend in Belgrade who made an application for learning languages with films. ( http://oaprograms.github.io/lingo-player/ ). We could make the program compatible with the output of the library. The library could use youtuble-dl to download a video playlist (reviews of board games in German, or whatever your thing is). Fetch the subtitles, reorganize them from fragments into complete sentences, translate them, and save them into a file that is loaded into the media player.
0 x

User avatar
rdearman
Site Admin
Posts: 4673
Joined: Thu May 14, 2015 4:18 pm
Location: United Kingdom
Languages: English (N)
Language Log: viewtopic.php?f=15&t=1836
x 11050
Contact:

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby rdearman » Mon Oct 30, 2017 4:49 pm

Have you looked at EMK's substudy?

https://github.com/emk/substudy

He is also working on some libraries for OCR of texting. Might be useful for you.
0 x
: 6 / 100 100 Italian paperbacks:
: 306 / 75000 Output Challenge 2019 (普通话写作):

Lollygagging Podcast available on iTunes

davidzweig
White Belt
Posts: 25
Joined: Sun Apr 23, 2017 4:58 pm
Languages: AFew
x 57

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby davidzweig » Mon Oct 30, 2017 7:09 pm

Thanks for the link, it's very helpful. I sent him an email.
0 x

mcthulhu
Orange Belt
Posts: 183
Joined: Sun Feb 26, 2017 4:01 pm
Languages: English (native); strong reading skills - Russian, Spanish, French, Italian, German, Serbo-Croatian, Macedonian, Bulgarian, Slovene, Farsi; fair reading skills - Polish, Czech, Dutch, Esperanto, Portuguese; beginner/rusty - Swedish, Norwegian, Danish
x 493

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby mcthulhu » Tue Oct 31, 2017 5:55 pm

Davidzweig, you might be interested in looking at my Jorkens project on GitHub. It's also intended to become a general-purpose toolbox or common front end to numerous language tools, some of them Web-based and others installed locally. My focus so far has been mostly on text, not audio or video, although there are options to call several TTS tools from the menus. It's written in JavaScript, but also has an interface for executing Python/NLTK scripts.

It's still very much under development, but you should be able to get an idea of where I'm going. I've been distracted by other things the last couple of months, but hope to get back to improving Jorkens soon, though in the meantime I've been using it for my own reading in several languages. I'm pretty happy with it as a reading tool, at least for the languages I've been using it with so far.

There's a wide variety of language tools out there but integrating each one under the same GUI requires additional work.
0 x

davidzweig
White Belt
Posts: 25
Joined: Sun Apr 23, 2017 4:58 pm
Languages: AFew
x 57

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby davidzweig » Wed Nov 01, 2017 7:14 pm

@mcthulhu It looks like a very interesting project, I will check it out in detail.

I wanted to make my ideas for the library a little more concrete. Let's see if we have something here. I think the best way to do this is to show how data would be represented and stored by the library (here as a JSON object).

The library would operate primarily on whole sentences. Lists of sentences, not individual words. We are processing language that can be consumed, digested, processed and understood. Think Krashen's 'comprehensible input.' A sentence is the smallest unit that stands on it own.

Edit: Copying into a text editor with word-wrap makes the following easier to read.

Code: Select all

frenchFilm =
[
   // 'subsText' and 'video' are just ids supplied by the user
   { subsText: { type: 'text' langCode: 'fr', text: 'Il m/'a dit que tu serais là.' },
      video: { type: 'videoTimeCode' file: 'frenchfilm.mp4' start: 1004.30 end:  1007.50 }
   },
   { subsText: { type: 'text' langCode: 'fr', text: 'T/'as un trou dans ton pantalon !' },
      video: { type: 'videoTimeCode' file: 'frenchfilm.mp4' start: 1008.00 end:  10010.70 }
   }
]


So, seeing this, you'd probably say: 'Great. You've invented a more verbose format for subtitles.'
But then you can do something like this:

Code: Select all

// Parameters: data object, id to be translated, id for new translation, language to translate into.
// Could also use OO syntax.
addGoogleTranslation(frenchFilm, 'subsText', 'googTranslationEng', 'en');   
voiceTextGoogleTTS(frenchFilm, 'googTranslationEng', 'googTranslateEngTTS')

frenchFim
-->
[
   { subsText: { type: 'text' langCode: 'fr', text: 'Il m/'a dit que tu serais là.' },
      video: { type: 'videoTimeCode' file: 'frenchfilm.mp4' start: 1004.30 end:  1007.50 }
      googTranslationEng: { type: 'text' langCode: 'en', text: 'He told me you'd be here.' },
      googTranslateEngTTS:  { type: 'audioFile' , langCode: 'en', file: '001.mp3' }
   },
   { subsText: { type: 'text' langCode: 'fr', text: 'T/'as un trou dans ton pantalon !' },
      video: { type: 'videoTimeCode' file: 'frenchfilm.mp4' start: 1008.00 end:  10010.70 }
      googTranslationEng: { type: 'text' langCode: 'fr', text: 'You have a hole in your trousers.' },
      googTranslateEngTTS: { type: 'audioFile', langCode: 'en', file: '002.mp3' }
   }
]


So we have a format for multilingual subtitles. For films and for audiobooks. Also we store full sentences (not spilt-up fragments).

Aligning/combining subtitles is not a trivial task. You have to use the timing information, which can be a little off, or look at the content.
Different subtitle files can split up the text into different fragments. I'll take a closer look at substudy, see if I can get a better idea of how to approach the problem.

To store the text of books, we can add a special element <p> to mark a new paragraph:

Code: Select all

aliceInWonderland
-->
[
   { englishText: { type: 'text', langCode: 'en', text: 'No, it will be better to ask nothing; maybe I'll see the name written somewhere.' },
      librivoxEnglish: { type: 'audioTimeCode', langCode: 'en',  file: 'alice01.mp3', start: 1004.30, end:  1007.50 }
      frenchTranslation: { type: 'text' langCode: 'fr', text: ' Non, il vaudra mieux ne rien demander ; peut-être que je verrai le nom écrit quelque part. »' },
   },
   { englishText: { type: 'text', langCode: 'en', text: '<p>' },
      frenchTranslation: { type: 'text' langCode: 'fr', text: '<p>' },
   },
   { englishText: { type: 'text', langCode: 'en', text: 'Deeper, deeper, and deeper still.' },
      librivoxEnglish: { type: 'audioTimeCode', langCode: 'en',  file: 'alice01.mp3', start: 1008.30, end:  1011.50 }
      frenchTranslation: { type: 'text' langCode: 'fr', text: 'Plus bas, encore plus bas, toujours plus bas. ' },
   }
   
   // The above paragraph marker is a little clunky, with two 'sources of truth'. What does the library do if only one
   // of the texts has <p>? Is it a new paragraph?
   // Could have something like this: { nodeType: 'newParagraph' }
]


So we can align texts in different languages with audio recordings.

Code: Select all

// Create a new file/object:
var aliceInWonderland = new sentenceObject(); // or something

// Add text from a text file, split into sentences with NLTK:
// Parameters: new id, text file, language
addSegmentedText(aliceInWonderland, 'aliceText', 'alice.txt', 'en')

// We could add another text to the file, lined up to the current formatting with Hunalign, with a function like this:
// Parameters: object to add to, text to align to, new id, text file, language
addAlignedTextHunalign(aliceInWonderland, 'aliceText', 'spanishTranslation', 'alicia.txt', 'es');

// Add audio, aligned to a text, with Aeneas:
// Parameters: object to add to, text to align to, new id, audio file (we know the language from the text we are aligning to)
addAlignedAudioAeneas(aliceInWonderland, 'spanishTranslation', 'spanishAudio', 'alicia.mp3')

// Probably we need some kind of undo function too.


How to use the data created by the library?
Bilingual texts, in PDF from, can be created automatically by my tool.
Audio files can be saved in sentence-by-sentence chopped up form.
Videos can be watched with a special player.
Or chop out the sentences of a film into audio files, and make a PDF script.
http://www.randomhacks.net/substudy/ (see 'Reviewing an episode as a web page' - this is great.)

I hope this makes it a bit more clear what I have in mind. I have ideas on how to add structure to these nodes (ie table of contents), I will try and get my ideas straight and make another post. A few more ideas still to come.
Last edited by davidzweig on Thu Nov 02, 2017 12:15 am, edited 3 times in total.
0 x

davidzweig
White Belt
Posts: 25
Joined: Sun Apr 23, 2017 4:58 pm
Languages: AFew
x 57

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby davidzweig » Wed Nov 01, 2017 9:55 pm

Regarding the structure.

Above is a model for how we can represent a list of sentences, with the ability to store text / audio / video information in each node. The list is still quite flat, which makes it simple to package up the data and send it to different tools.

To create a tree structure (like this: https://en.wikipedia.org/wiki/File:Bina ... ucture.svg ) from the nodes, we can either add data to nodes about their child nodes (nodes that are lower in the tree), or add data about their parent nodes.

As I'm thinking this data will be added by a user, editing a text file, we should try and reduce the amount of tags that need to be added. Something like this could work quite well:

(one sentence per line, this text has already been pre-processed by the library)

Alice in Wonderland ##root
Chapter One ##chpt1 root
Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do: once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it, ‘and what is the use of a book,’ thought Alice ‘without pictures or conversations?’
<p>
So she was considering in her own mind (as well as she could, for the hot day made her feel very sleepy and stupid), whether the pleasure of making a daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly a White Rabbit with pink eyes ran close by her.
<p>
[skip some text]
CHAPTER II. The Pool of Tears ##chpt2 root
‘Curiouser and curiouser!’ cried Alice (she was so much surprised, that for the moment she quite forgot how to speak good English); ‘now I’m opening out like the largest telescope that ever was! Good-bye, feet!’ (for when she looked down at her feet, they seemed to be almost out of sight, they were getting so far off).
<p>

etc.

So we define a root node.
And we add tags to parent nodes, and we specify their parent in turn. Sorry that sounds complicated.
Chapter One ##chpt1 root

## - start of tag
chpt1 - id of this node
root - the parent node of this node
(the newline ends the tag.)

Not saying '##' is the best syntax. Open to suggestions.

The library interprets all lines following a section title node as implicit children of it. So there is no need to add tags to every sentence (the 'leaf' nodes).

I think this arrangement is quite flexible, makes it easy to add structure to texts, and not hard to implement.

The structure is useful for a few reasons.
It makes possible to prepare PDFs with chapter titles formatted correctly, with chapters starting on new pages, if desired.
It makes it possible to navigate through content on the player.
There's no reason you couldn't use it to structure the dialogue in a film into scenes, if you wish.

In the list of nodes, in our libraries representation, we can store information about the parent:

Code: Select all

aliceInWonderland
-->
[
   { parentNodeID: 'chpt1',
      englishText: { type: 'text', langCode: 'en', text: 'No, it will be better to ask nothing; maybe I'll see the name written somewhere.' },
      librivoxEnglish: { type: 'audioTimeCode', langCode: 'en',  file: 'alice01.mp3', start: 1004.30, end:  1007.50 }
      frenchTranslation: { type: 'text' langCode: 'fr', text: ' Non, il vaudra mieux ne rien demander ; peut-être que je verrai le nom écrit quelque part. »' },
   },
   { nodeType: 'paragraph' },
   },
   { englishText: { type: 'text', langCode: 'en', text: 'Deeper, deeper, and deeper still.' },
      librivoxEnglish: { type: 'audioTimeCode', langCode: 'en',  file: 'alice01.mp3', start: 1008.30, end:  1011.50 }
      frenchTranslation: { type: 'text' langCode: 'fr', text: 'Plus bas, encore plus bas, toujours plus bas. ' },
   }

]


Or we could transform it so that internally we store the the IDs of children on each node. Anyway.

In example above, you can see there are some very long sentences. It might be desirable to break these into shorter, more digestible pieces. In fact, the following sentence could easily be reorganized into three, with no effect in meaning:

- Alice was beginning to get very tired of sitting by her sister on the bank, and of having nothing to do.
- Once or twice she had peeped into the book her sister was reading, but it had no pictures or conversations in it.
- 'And what is the use of a book,’ thought Alice ‘without pictures or conversations?’

Rather than break long sentences into separate nodes, we could use a special symbol in the text: |
They would need to be added manually into the text, but could be used to sub-divide long sentences into smaller pieces, if possible, to make them easier to 'digest'. There are some subtleties here, which can be discussed later. I suggest the nodes should reflect how the text was broken into sentences in the texts native language. Unfortunately, with our multilingual list of sentences structure, we can't deal elegantly with the situation where a translated text has merged two source sentences into a single sentence. An alternative structuring of the data is possible that gets around this problem, but it adds a lot of complexity, and I don't think it's worth it here.

To sum it up, this representation has some nice features:
We store sentences.
We can support multiple languages, and meta information about each sentence (like the grammar notes in Assimil books)
We can add structure
We can easily pack up the data and send it to different tools, and output it in different formats.

All I'm describing here is basically a way to pass lists of sentences between scripts.

I will be busy for a few days, but hope to make time to look into other people's work soon.
0 x

mcthulhu
Orange Belt
Posts: 183
Joined: Sun Feb 26, 2017 4:01 pm
Languages: English (native); strong reading skills - Russian, Spanish, French, Italian, German, Serbo-Croatian, Macedonian, Bulgarian, Slovene, Farsi; fair reading skills - Polish, Czech, Dutch, Esperanto, Portuguese; beginner/rusty - Swedish, Norwegian, Danish
x 493

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby mcthulhu » Thu Nov 02, 2017 2:23 am

"Unfortunately, with our multilingual list of sentences structure, we can't deal elegantly with the situation where a translated text has merged two source sentences into a single sentence. An alternative structuring of the data is possible that gets around this problem, but it adds a lot of complexity, and I don't think it's worth it here."

Maybe you could look at the TMX file format, which is one of the options for LF Alilgner's output. (LF Aligner is based on Hunalign, I think.) It's a standard tagged data structure used by translation tools. A TMX file can store one to one sentence pairs, or one to multiple sentences. It's a type of XML, and could be either handled as is or converted to JSON by available Python packages. Here's a sample translation unit:

Code: Select all

<tu>
<tuv>
<seg>
Il était grand temps de s’en aller, car la mare se couvrait d’oiseaux et de toutes sortes d’animaux qui y étaient tombés. Il y avait un canard, un dodo, un lory, un aiglon, et d’autres bêtes extraordinaires. Alice prit les devants, et toute la troupe nagea vers la rive.
</seg>
</tuv>
<tuv>
<seg>
It was high time to go, for the pool was getting quite crowded with the birds and animals that had fallen into it: there were a Duck and a Dodo, a Lory and an Eaglet, and several other curious creatures. Alice led the way, and the whole party swam to the shore.
</seg>
</tuv>
</tu>
0 x

davidzweig
White Belt
Posts: 25
Joined: Sun Apr 23, 2017 4:58 pm
Languages: AFew
x 57

Re: A Software Library for Creating Language Learning Materials (such as bilingual texts)

Postby davidzweig » Sat Nov 04, 2017 10:12 pm

It's a good suggestion. I haven't really looked into TMX much before, it's in a similar vein to what I had in mind.

"Each <tu> element contains at least one translation unit variant, the <tuv> element. Each <tuv> contains the segment and the information pertaining to that segment for a given language. The text itself is stored in the <seg> element, while <note> and <prop> allow you to store information specific to each <tuv>."

They also had the idea to add 'notes' to sentences.

But the structure, with multiple <seg> I think doesn't help us so much.

Let's say we have some text, in three languages:

Language A:
I have a dog. And and a cat. And a parrot.

Language B:
I have a dog and cat. And a parrot.

Language C:
I have a dog and a cat and a parrot.

If I understood correctly, in TMX it would look like this:

<tu>
<tuv>
<seg>I have a dog. </seg><seg>And and a cat. </seg><seg>And a parrot. </seg>
</tuv>
<tuv>
<seg>I have a dog and cat. </seg><seg>And a parrot. </seg>
</tuv>
<tuv>
<seg>I have a dog and a cat and a parrot. </seg>
</tuv>
</tu>

All we can really know from this structure is that:

I have a dog. And and a cat. And a parrot.

is equivalent to:

I have a dog and cat. And a parrot.

It can't tell us that 'I have a dog. And a cat.' is equivalent to 'I have a dog and cat.' (we can figure it out, but a program can't, only from the XML structure). You could make the assumption that if a <tu> element has two <seg> elements in each language, then the first <seg> elements in both languages are equivalent, and the second elements are too. But then you'd need to take care that this was true, when making the files.

[Footnote: This is similar to the approach I suggested above, but instead of using <seg> markers, you'd include "|" markers in the text. If the number of markers in a sentence in two languages is the same, you assume the fragments correspond.]

So you might rewrite the file so that 'I have a dog. And a cat.' is nested in it's own <tuv> element. But then you find Language C's 'I have a dog and a cat and a parrot.' doesn't fit in anywhere anymore.

It's a bit confusing and difficult to reason about.

Here's a different approach. You can rather do this:

Language A:
<marker 1>I have a dog. <marker 2>And and a cat. <marker 3>And a parrot. <marker 4>

Language B:
<marker 1>I have a dog and cat. <marker 3>And a parrot. <marker 4>

Language C:
<marker 1>I have a dog and cat. <marker 3>And a parrot. <marker 4>

Language D:
<marker 1>I have a dog and a cat and a parrot. <marker 4>

Here we are not associating blocks of text with each other, but associating (zero-length) points in the text with points in other translations.
You can imagine it graphically:

Code: Select all

<1>blablabla<2>blablabla<3>blablabla<4>
<1>blablablablabla<3>blablabla<4>
<1>blablablablablablablabla<4>


You are kind of drawing lines down through the steams of language at spots where they meet up. With a TMX-type structure, as you add more (wonky old project gutenburg) translations, you must discard alignment information for the texts already in your file to make the new texts fit in. This structure preserves it.

Interestingly the Hunalign 'Ladder' output format seems to have a structure like this, but I didn't see any example of more than two parallel texts in a file. I didn't find much documentation about the format.

I actually wrote an editor with a gui for doing these kind of alignments. It was quite hard to write, but I think it's making things more complex than they need to be, for our needs, if we are mostly dealing with faithful translations and google translate. Anytime you want to do anything with this data, you would be simplifying it down to a TMX type structure, for the language pairs you need. I'm not quite sure what it's good for. It's somehow interesting though, like it's scratching the surface of some deeper idea.

The idea here, is to make a format that serves our needs, like TMX does for translators. I'm not such a fan of XML though, and the goal is to keep the format minimalist, to the extent that it runs the risk of seeming trivial.

Last night I finished the second revision of the PCB for the player. Fixed a few problems, tidied some things, and added a serial port for a REPL interpreter, so you can type javascript commands directly to it for testing. The new board comes from China in about a week, then I'll solder on the components.
1 x


Return to “Language Programs and Resources”

Who is online

Users browsing this forum: No registered users and 0 guests