├── public ├── _redirects ├── robots.txt ├── favicon.ico ├── logo512.png ├── manifest.json └── index.html ├── src ├── react-app-env.d.ts ├── assets │ ├── kofi.png │ ├── logo.png │ ├── checkmark.png │ ├── github-white.png │ ├── reddit-white.png │ ├── stripe-white.png │ ├── henryfellerhofflogo.png │ ├── question-mark-white.png │ └── henryfellerhoff-energetic.png ├── constants │ ├── Variables.ts │ ├── Template.ts │ ├── Interfaces.ts │ ├── IPA.ts │ └── Letters.ts ├── components │ ├── transcription-page │ │ ├── ExportButtons.scss │ │ ├── TranscriptionEditor.scss │ │ ├── ExportButtons.tsx │ │ ├── TranscriptionDescription.scss │ │ ├── TranscriptionEditor.tsx │ │ └── TranscriptionDescription.tsx │ ├── button-containers │ │ ├── LanguageSelectionButtons.scss │ │ └── LanguageSelectionButtons.tsx │ ├── LandingPage.scss │ ├── util │ │ └── ScrollToTop.tsx │ ├── buttons │ │ ├── CheckboxButton.scss │ │ ├── HideButton.scss │ │ ├── ExportButton.scss │ │ ├── HideButton.tsx │ │ ├── ExportButton.tsx │ │ ├── CoffeeButton.tsx │ │ ├── CheckboxButton.tsx │ │ ├── LanguageSelectButton.tsx │ │ ├── CoffeeButton.scss │ │ └── LanguageSelectButton.scss │ ├── LandingPage.tsx │ ├── landing-page │ │ ├── Hero.tsx │ │ ├── Hero.scss │ │ ├── Description.scss │ │ ├── Demonstration.scss │ │ ├── Description.tsx │ │ └── Demonstration.tsx │ ├── support-page │ │ ├── SupportCard.tsx │ │ ├── RightCard.tsx │ │ ├── LeftCard.tsx │ │ └── SupportCard.scss │ ├── SupportPage.tsx │ ├── input │ │ ├── TextInput.scss │ │ └── TextInput.tsx │ ├── TranscriptionPage.scss │ ├── SupportPage.scss │ ├── header │ │ ├── Navbar.tsx │ │ └── Navbar.scss │ ├── footer │ │ ├── Footer.tsx │ │ └── Footer.scss │ ├── TranscriptionPage.tsx │ └── display │ │ ├── ResultDisplay.scss │ │ └── ResultDisplay.tsx ├── util │ ├── StringHelper.ts │ ├── CopyResult.ts │ ├── Helper.ts │ └── CreatePDF.ts ├── App.test.tsx ├── styles │ ├── _colors.scss │ ├── reset.scss │ └── _variables.scss ├── transcription │ ├── french │ │ ├── parse-letters │ │ │ ├── parseH.ts │ │ │ ├── README.md │ │ │ ├── parseJ.ts │ │ │ ├── parseQ.ts │ │ │ ├── parseP.ts │ │ │ ├── parseD.ts │ │ │ ├── parseL.ts │ │ │ ├── parseM.ts │ │ │ ├── parseN.ts │ │ │ ├── parseV.ts │ │ │ ├── parseZ.ts │ │ │ ├── parseS.ts │ │ │ ├── parseX.ts │ │ │ ├── parseB.ts │ │ │ ├── parseY.ts │ │ │ ├── parseF.ts │ │ │ ├── parseT.ts │ │ │ ├── parseG.ts │ │ │ ├── parseC.ts │ │ │ ├── parseR.ts │ │ │ ├── parseU.ts │ │ │ ├── parseŒ.ts │ │ │ ├── parseI.ts │ │ │ ├── parseA.ts │ │ │ ├── parseO.ts │ │ │ └── parseE.ts │ │ ├── parse-functions │ │ │ ├── transcribeDefault.ts │ │ │ ├── transcribeFollowingVowel.ts │ │ │ ├── transcribeFollowingConsonant.ts │ │ │ ├── transcribeFollowingFrontVowel.ts │ │ │ ├── transcribeIntervocalic.ts │ │ │ ├── transcribeDoubleLetter.ts │ │ │ ├── transcribeFollowingBackVowel.ts │ │ │ ├── transcribePrefix.ts │ │ │ ├── transcribeLetter.ts │ │ │ ├── transcribeSuffix.ts │ │ │ ├── transcribeFinalConsonant.ts │ │ │ └── transcribeFollowingLetter.ts │ │ ├── FrenchNotes.ts │ │ ├── FrenchHelper.ts │ │ ├── FrenchRules.ts │ │ ├── FrenchExceptions.ts │ │ └── ParseFrench.ts │ ├── german │ │ └── ParseGerman.ts │ ├── italian │ │ └── ParseItalian.ts │ └── latin │ │ ├── LatinRules.ts │ │ └── ParseLatin.ts ├── index.css ├── app.scss ├── index.js ├── hooks │ └── UseWindowDimensions.tsx ├── App.js └── serviceWorker.ts ├── readme ├── showcase.gif ├── logo-circle.png └── OpenIPA-title.png ├── .gitignore ├── tsconfig.json ├── package.json └── README.md /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/public/logo512.png -------------------------------------------------------------------------------- /readme/showcase.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/readme/showcase.gif -------------------------------------------------------------------------------- /src/assets/kofi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/kofi.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /readme/logo-circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/readme/logo-circle.png -------------------------------------------------------------------------------- /readme/OpenIPA-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/readme/OpenIPA-title.png -------------------------------------------------------------------------------- /src/assets/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/checkmark.png -------------------------------------------------------------------------------- /src/assets/github-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/github-white.png -------------------------------------------------------------------------------- /src/assets/reddit-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/reddit-white.png -------------------------------------------------------------------------------- /src/assets/stripe-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/stripe-white.png -------------------------------------------------------------------------------- /src/constants/Variables.ts: -------------------------------------------------------------------------------- 1 | const Variables = { 2 | fadeDuration: 250, 3 | }; 4 | 5 | export default Variables; 6 | -------------------------------------------------------------------------------- /src/assets/henryfellerhofflogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/henryfellerhofflogo.png -------------------------------------------------------------------------------- /src/assets/question-mark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/question-mark-white.png -------------------------------------------------------------------------------- /src/assets/henryfellerhoff-energetic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hfellerhoff/openipa-old/HEAD/src/assets/henryfellerhoff-energetic.png -------------------------------------------------------------------------------- /src/components/transcription-page/ExportButtons.scss: -------------------------------------------------------------------------------- 1 | .ipa__transcription__export-container { 2 | margin-top: 20px; 3 | display: flex; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/button-containers/LanguageSelectionButtons.scss: -------------------------------------------------------------------------------- 1 | .ipa__landing-page__hero__button-container { 2 | margin-top: 10px; 3 | 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | flex-wrap: wrap; 8 | } 9 | -------------------------------------------------------------------------------- /src/util/StringHelper.ts: -------------------------------------------------------------------------------- 1 | export const capitalizeFirstLetter = (text: string) => { 2 | if (text.length > 1) { 3 | return text.charAt(0).toUpperCase() + text.substring(1, text.length); 4 | } else { 5 | return text.charAt(0).toUpperCase(); 6 | } 7 | }; 8 | -------------------------------------------------------------------------------- /src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/LandingPage.scss: -------------------------------------------------------------------------------- 1 | @import '../styles/colors'; 2 | @import '../styles/variables'; 3 | 4 | .ipa__landing-page__container { 5 | background: $white; 6 | width: 100%; 7 | display: flex; 8 | flex-direction: column; 9 | transition: all 0.3s ease-in-out; 10 | 11 | overflow: hidden; 12 | } 13 | -------------------------------------------------------------------------------- /src/styles/_colors.scss: -------------------------------------------------------------------------------- 1 | $darkgray: #262626; 2 | $darkgray-gradient: #3b3b3b; 3 | $gray: #404040; 4 | $lightgray-gradient: #454545; 5 | $lightgray: #808080; 6 | $primary: #2ec794; 7 | $muted-primary: #27ab7f; 8 | $caution: #dec400; 9 | $white: #ffffff; 10 | $offwhite: #e6e6e6; 11 | $reddit: #ff4400; 12 | $github: #3d576a; 13 | $kofi: #ff5f5f; 14 | $stripe: #3ea4ff; 15 | -------------------------------------------------------------------------------- /src/transcription/french/parse-letters/parseH.ts: -------------------------------------------------------------------------------- 1 | import { ParseLetterProps, Phoneme } from '../../../constants/Interfaces'; 2 | import transcribeLetter from '../parse-functions/transcribeLetter'; 3 | 4 | const parseH = ({ phoneme, nextletter }: ParseLetterProps): Phoneme => { 5 | return transcribeLetter(phoneme, nextletter, 'h', ''); 6 | }; 7 | 8 | export default parseH; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /src/transcription/french/parse-letters/README.md: -------------------------------------------------------------------------------- 1 | # Letter Parsing Notes 2 | 3 | - Transcribe from the least important rule to the most important rule - rules that follow will overwrite rules from before if they apply. 4 | 5 | ### Template Transcription Order 6 | 7 | 1. transcribeDoubleLetter 8 | 2. transcribeFinalConsonant 9 | 3. transcribeFollowingFrontVowel / transcribeFollowingBackVowel 10 | 4. transcribeFollowingLetter 11 | 5. transcribeLetter 12 | -------------------------------------------------------------------------------- /src/components/util/ScrollToTop.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | import Variables from '../../constants/Variables'; 4 | 5 | const ScrollToTop = () => { 6 | const { pathname } = useLocation(); 7 | 8 | useEffect(() => { 9 | setTimeout(() => { 10 | window.scrollTo(0, 0); 11 | }, Variables.fadeDuration); 12 | }, [pathname]); 13 | 14 | return null; 15 | }; 16 | 17 | export default ScrollToTop; 18 | -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | @import './styles/colors'; 2 | @import './styles/variables'; 3 | * { 4 | font-family: 'Apple SD Gothic Neo', 'Trebuchet MS', 'Open Sans', sans-serif; 5 | } 6 | 7 | body { 8 | background: $gray; 9 | @media screen and (max-width: 800px) { 10 | position: relative; 11 | } 12 | } 13 | 14 | .fade-in { 15 | animation: fadein-keyframe 0.25s; 16 | } 17 | 18 | @keyframes fadein-keyframe { 19 | from { 20 | opacity: 0; 21 | } 22 | to { 23 | opacity: 1; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/buttons/CheckboxButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colors'; 2 | @import '../../styles/variables'; 3 | 4 | .ipa__transcription__option-checkbox { 5 | height: 20px; 6 | width: 20px; 7 | border-radius: 10px; 8 | background: $offwhite; 9 | box-shadow: 0px 5px 10px 2px rgba(0, 0, 0, 0.1); 10 | 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | } 15 | 16 | .ipa__transcription__option-checkbox-image { 17 | height: 15px; 18 | width: 15px; 19 | } 20 | -------------------------------------------------------------------------------- /src/transcription/french/parse-functions/transcribeDefault.ts: -------------------------------------------------------------------------------- 1 | import { Phoneme } from '../../../constants/Interfaces'; 2 | 3 | const getRule = (text: string, ipa: string): string => { 4 | return `By default, '${text}' letters are transcribed as [${ipa}].`; 5 | }; 6 | 7 | const transcribeDefault = (letters: string[], ipa: string): Phoneme => { 8 | return { 9 | text: letters[0], 10 | ipa: ipa, 11 | rule: getRule(letters[0], ipa), 12 | }; 13 | }; 14 | 15 | export default transcribeDefault; 16 | -------------------------------------------------------------------------------- /src/components/buttons/HideButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colors'; 2 | @import '../../styles/variables'; 3 | 4 | .ipa__transcription__hide-button { 5 | background: $darkgray; 6 | border: none; 7 | color: $white; 8 | width: 65px; 9 | height: 25px; 10 | padding: 5px 0px; 11 | border-radius: 20px; 12 | font-weight: 700; 13 | font-size: 12px; 14 | box-shadow: 0px 5px 10px 3px rgba(0, 0, 0, 0.2); 15 | 16 | visibility: hidden; 17 | @media screen and (max-width: 800px) { 18 | visibility: visible; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/buttons/ExportButton.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/colors'; 2 | @import '../../styles/variables'; 3 | 4 | .ipa__transcription__export-button { 5 | background: $darkgray; 6 | border: none; 7 | color: $white; 8 | padding-top: 10px; 9 | padding-bottom: 10px; 10 | height: 40px; 11 | border-radius: 20px; 12 | font-weight: 600; 13 | font-size: 14px; 14 | box-shadow: 0px 5px 10px 3px rgba(0, 0, 0, 0.2); 15 | 16 | @media screen and (max-width: 800px) { 17 | flex: 1; 18 | } 19 | 20 | @extend %half-side-padding; 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react" 17 | }, 18 | "include": ["src", "index.tsx"] 19 | } 20 | -------------------------------------------------------------------------------- /src/components/buttons/HideButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './HideButton.scss'; 3 | 4 | interface Props { 5 | shouldShow: boolean; 6 | setShouldShow: React.Dispatch>; 7 | } 8 | 9 | const HideButton: React.FC = ({ shouldShow, setShouldShow }) => { 10 | return ( 11 | 17 | ); 18 | }; 19 | 20 | export default HideButton; 21 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "IPA Translator", 3 | "name": "IPA Translator", 4 | "icons": [ 5 | { 6 | "src": "favicon-32x32.png", 7 | "sizes": "32x32", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "favicon-16x16.png", 12 | "sizes": "16x16", 13 | "type": "image/png" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/transcription/french/parse-letters/parseJ.ts: -------------------------------------------------------------------------------- 1 | import { ParseLetterProps, Phoneme } from '../../../constants/Interfaces'; 2 | import IPA from '../../../constants/IPA'; 3 | import transcribeFinalConsonant from '../parse-functions/transcribeFinalConsonant'; 4 | import transcribeDefault from '../parse-functions/transcribeDefault'; 5 | 6 | const parseJ = ({ phoneme, nextletter }: ParseLetterProps): Phoneme => { 7 | phoneme = transcribeDefault(nextletter, IPA.FRICATIVE_G); 8 | phoneme = transcribeFinalConsonant(phoneme, nextletter); 9 | 10 | return phoneme; 11 | }; 12 | 13 | export default parseJ; 14 | -------------------------------------------------------------------------------- /src/constants/Template.ts: -------------------------------------------------------------------------------- 1 | import { Result } from './Interfaces'; 2 | 3 | const ResultTemplate: Result = { 4 | lines: [ 5 | { 6 | words: [ 7 | { 8 | syllables: [], 9 | }, 10 | ], 11 | }, 12 | ], 13 | }; 14 | 15 | const getResultTemplate = () => { 16 | return { 17 | lines: [ 18 | { 19 | words: [ 20 | { 21 | syllables: [], 22 | }, 23 | ], 24 | }, 25 | ], 26 | }; 27 | }; 28 | 29 | const Template = { 30 | Result: ResultTemplate, 31 | getResultTemplate, 32 | }; 33 | 34 | export default Template; 35 | -------------------------------------------------------------------------------- /src/components/buttons/ExportButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { PulseLoader } from 'react-spinners'; 3 | import './ExportButton.scss'; 4 | 5 | interface Props { 6 | title: string; 7 | onClick: () => void; 8 | isLoading?: boolean; 9 | } 10 | 11 | const ExportButton: React.FC = ({ title, onClick, isLoading }) => { 12 | if (isLoading === undefined) isLoading = false; 13 | return ( 14 | 17 | ); 18 | }; 19 | 20 | export default ExportButton; 21 | -------------------------------------------------------------------------------- /src/components/buttons/CoffeeButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './CoffeeButton.scss'; 3 | import ReactGA from 'react-ga'; 4 | 5 | interface Props {} 6 | 7 | const CoffeeButton: React.FC = () => { 8 | return ( 9 | 15 | Buy me a coffee 16 | Buy me a coffee 17 | 18 | ); 19 | }; 20 | 21 | export default CoffeeButton; 22 | -------------------------------------------------------------------------------- /src/transcription/french/parse-letters/parseQ.ts: -------------------------------------------------------------------------------- 1 | import { ParseLetterProps, Phoneme } from '../../../constants/Interfaces'; 2 | import IPA from '../../../constants/IPA'; 3 | import transcribeFinalConsonant from '../parse-functions/transcribeFinalConsonant'; 4 | import transcribeFollowingLetter from '../parse-functions/transcribeFollowingLetter'; 5 | 6 | const parseQ = ({ nextletter, phoneme }: ParseLetterProps): Phoneme => { 7 | phoneme = transcribeFinalConsonant(phoneme, nextletter); 8 | 9 | // --- 'qu' --- 10 | phoneme = transcribeFollowingLetter(phoneme, nextletter, ['u'], IPA.K); 11 | 12 | // --- Default --- 13 | return phoneme; 14 | }; 15 | 16 | export default parseQ; 17 | -------------------------------------------------------------------------------- /src/transcription/french/FrenchNotes.ts: -------------------------------------------------------------------------------- 1 | import IPA from '../../constants/IPA'; 2 | 3 | const Notes = { 4 | FINAL_E_HALFCLOSED: ` Note: Final [${IPA.OPEN_E}] vowels are closed to a half-closed [${IPA.OPEN_E}] or a fully closed [${IPA.CLOSED_E}]. In practice, pronunciation is closer to [${IPA.CLOSED_E}], so that is what has been notated here.`, 5 | GLIDE_FOLLOWING: ` Note: medial '-ill' and '-il' glides are transcribed separately from a preceding vowel.`, 6 | LIASON: ` Note: This normally silent consonant is pronounced in this case due to liason, as the following word begins with a vowel. Liason rules are complex, so take this transcription with a grain of salt.`, 7 | }; 8 | 9 | export default Notes; 10 | -------------------------------------------------------------------------------- /src/transcription/french/parse-functions/transcribeFollowingVowel.ts: -------------------------------------------------------------------------------- 1 | import { Phoneme } from '../../../constants/Interfaces'; 2 | import { isVowel } from '../../../util/Helper'; 3 | 4 | const getRule = (letter: string, ipa: string): string => { 5 | return `'${letter}' consonants followed by a vowel are transcribed as [${ipa}].`; 6 | }; 7 | 8 | const transcribeFollowingVowel = ( 9 | phoneme: Phoneme, 10 | letters: string[], 11 | ipa: string 12 | ): Phoneme => { 13 | if (isVowel(letters[1])) { 14 | return { 15 | text: letters[0], 16 | ipa, 17 | rule: getRule(letters[0], ipa), 18 | }; 19 | } 20 | return phoneme; 21 | }; 22 | 23 | export default transcribeFollowingVowel; 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | import { BrowserRouter } from 'react-router-dom'; 7 | import ScrollToTop from './components/util/ScrollToTop'; 8 | 9 | ReactDOM.render( 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | // If you want your app to work offline and load faster, you can change 18 | // unregister() to register() below. Note this comes with some pitfalls. 19 | // Learn more about service workers: https://bit.ly/CRA-PWA 20 | serviceWorker.unregister(); 21 | -------------------------------------------------------------------------------- /src/constants/Interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Phoneme { 2 | text: string; 3 | ipa: string; 4 | rule: string; 5 | } 6 | 7 | export interface Word { 8 | syllables: Phoneme[]; 9 | } 10 | 11 | export interface Line { 12 | words: Word[]; 13 | } 14 | 15 | export interface Result { 16 | lines: Line[]; 17 | } 18 | 19 | export enum Languages { 20 | Latin = 'latin', 21 | German = 'german', 22 | Italian = 'italian', 23 | French = 'french', 24 | } 25 | 26 | export interface ParseLetterProps { 27 | charArray: string[]; 28 | phoneme: Phoneme; 29 | index: number; 30 | indexToAdd: number; 31 | nextletter: string[]; 32 | previousIPA: string; 33 | } 34 | 35 | export type ParseLetterReturn = [Phoneme, number]; 36 | -------------------------------------------------------------------------------- /src/hooks/UseWindowDimensions.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | function getWindowDimensions() { 4 | const { innerWidth: width, innerHeight: height } = window; 5 | return { 6 | width, 7 | height, 8 | }; 9 | } 10 | 11 | export default function useWindowDimensions() { 12 | const [windowDimensions, setWindowDimensions] = useState( 13 | getWindowDimensions() 14 | ); 15 | 16 | useEffect(() => { 17 | function handleResize() { 18 | setWindowDimensions(getWindowDimensions()); 19 | } 20 | 21 | window.addEventListener('resize', handleResize); 22 | return () => window.removeEventListener('resize', handleResize); 23 | }, []); 24 | 25 | return windowDimensions; 26 | } 27 | -------------------------------------------------------------------------------- /src/transcription/french/parse-functions/transcribeFollowingConsonant.ts: -------------------------------------------------------------------------------- 1 | import { Phoneme } from '../../../constants/Interfaces'; 2 | import { isConsonant } from '../../../util/Helper'; 3 | 4 | const getRule = (letter: string, ipa: string): string => { 5 | return `'${letter}' consonants followed by a consonant are transcribed as [${ipa}].`; 6 | }; 7 | 8 | const transcribeFollowingConsonant = ( 9 | phoneme: Phoneme, 10 | letters: string[], 11 | ipa: string 12 | ): Phoneme => { 13 | if (isConsonant(letters[1])) { 14 | return { 15 | text: letters[0], 16 | ipa, 17 | rule: getRule(letters[0], ipa), 18 | }; 19 | } 20 | return phoneme; 21 | }; 22 | 23 | export default transcribeFollowingConsonant; 24 | -------------------------------------------------------------------------------- /src/transcription/french/parse-functions/transcribeFollowingFrontVowel.ts: -------------------------------------------------------------------------------- 1 | import { Phoneme } from '../../../constants/Interfaces'; 2 | import { isFrontVowel } from '../../../util/Helper'; 3 | 4 | const getRule = (letter: string, ipa: string): string => { 5 | return `'${letter}' consonants followed by a front vowel are transcribed as [${ipa}].`; 6 | }; 7 | 8 | const transcribeFollowingFrontVowel = ( 9 | phoneme: Phoneme, 10 | letters: string[], 11 | ipa: string 12 | ): Phoneme => { 13 | if (isFrontVowel(letters[1])) { 14 | return { 15 | text: letters[0], 16 | ipa, 17 | rule: getRule(letters[0], ipa), 18 | }; 19 | } 20 | return phoneme; 21 | }; 22 | 23 | export default transcribeFollowingFrontVowel; 24 | -------------------------------------------------------------------------------- /src/components/LandingPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Hero from './landing-page/Hero'; 3 | import Description from './landing-page/Description'; 4 | import Demonstration from './landing-page/Demonstration'; 5 | import { RouteComponentProps } from 'react-router'; 6 | import './LandingPage.scss'; 7 | import Footer from './footer/Footer'; 8 | 9 | interface Props extends RouteComponentProps {} 10 | 11 | const LandingPage: React.FC = () => { 12 | return ( 13 | <> 14 |
15 | 16 | 17 | 18 |
19 |